mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Trace route cleanup
This commit is contained in:
parent
a8787ebc21
commit
a30fc18d73
4 changed files with 146 additions and 79 deletions
|
|
@ -33,7 +33,6 @@ struct UserList: View {
|
|||
@State var node: NodeInfoEntity?
|
||||
@State private var userSelection: UserEntity? // Nothing selected by default.
|
||||
@State private var isPresentingDeleteUserMessagesConfirm: Bool = false
|
||||
@State private var isPresentingTraceRouteSentAlert = false
|
||||
|
||||
var body: some View {
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current)
|
||||
|
|
@ -126,14 +125,6 @@ struct UserList: View {
|
|||
} label: {
|
||||
Label(user.mute ? "Show Alerts" : "Hide Alerts", systemImage: user.mute ? "bell" : "bell.slash")
|
||||
}
|
||||
Button {
|
||||
let success = bleManager.sendTraceRouteRequest(destNum: user.num, wantResponse: true)
|
||||
if success {
|
||||
isPresentingTraceRouteSentAlert = true
|
||||
}
|
||||
} label: {
|
||||
Label("Trace Route", systemImage: "signpost.right.and.left")
|
||||
}
|
||||
if user.messageList.count > 0 {
|
||||
Button(role: .destructive) {
|
||||
isPresentingDeleteUserMessagesConfirm = true
|
||||
|
|
@ -143,14 +134,6 @@ struct UserList: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.alert(
|
||||
"Trace Route Sent",
|
||||
isPresented: $isPresentingTraceRouteSentAlert
|
||||
) {
|
||||
Button("OK", role: .cancel) { }
|
||||
} message: {
|
||||
Text("This could take a while, response will appear in the mesh log.")
|
||||
}
|
||||
.confirmationDialog(
|
||||
"This conversation will be deleted.",
|
||||
isPresented: $isPresentingDeleteUserMessagesConfirm,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ struct NodeList: View {
|
|||
|
||||
@State private var columnVisibility = NavigationSplitViewVisibility.all
|
||||
@State private var selectedNode: NodeInfoEntity?
|
||||
@State private var isPresentingTraceRouteSentAlert = false
|
||||
|
||||
@SceneStorage("selectedDetailView") var selectedDetailView: String?
|
||||
|
||||
|
|
@ -72,13 +73,31 @@ struct NodeList: View {
|
|||
} label: {
|
||||
Label(node.user!.mute ? "Show Alerts" : "Hide Alerts", systemImage: node.user!.mute ? "bell" : "bell.slash")
|
||||
}
|
||||
if connectedNodeNum != node.num {
|
||||
Button {
|
||||
let success = bleManager.sendTraceRouteRequest(destNum: node.user?.num ?? 0, wantResponse: true)
|
||||
if success {
|
||||
isPresentingTraceRouteSentAlert = true
|
||||
}
|
||||
} label: {
|
||||
Label("Trace Route", systemImage: "signpost.right.and.left")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.alert(
|
||||
"Trace Route Sent",
|
||||
isPresented: $isPresentingTraceRouteSentAlert
|
||||
) {
|
||||
Button("OK", role: .cancel) { }
|
||||
} message: {
|
||||
Text("This could take a while, response will appear in the trace route log for the node it was sent to.")
|
||||
}
|
||||
}
|
||||
.searchable(text: nodesQuery, prompt: "Find a node")
|
||||
.navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count)))
|
||||
.listStyle(.plain)
|
||||
|
||||
.navigationSplitViewColumnWidth(min: 100, ideal: 250, max: 500)
|
||||
.navigationBarItems(leading:
|
||||
MeshtasticLogo(),
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import MapKit
|
|||
|
||||
@available(iOS 17.0, macOS 14.0, *)
|
||||
struct TraceRouteLog: View {
|
||||
@ObservedObject var locationsHandler = LocationsHandler.shared
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
|
|
@ -20,84 +21,136 @@ struct TraceRouteLog: View {
|
|||
@State var exportString = ""
|
||||
@ObservedObject var node: NodeInfoEntity
|
||||
@State private var selectedRoute: TraceRouteEntity?
|
||||
|
||||
// Map Configuration
|
||||
@Namespace var mapScope
|
||||
@State var mapStyle: MapStyle = MapStyle.standard(elevation: .realistic, emphasis: MapStyle.StandardEmphasis.muted ,pointsOfInterest: .all, showsTraffic: true)
|
||||
@State var position = MapCameraPosition.automatic
|
||||
let distanceFormatter = MKDistanceFormatter()
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack (alignment: .top) {
|
||||
VStack {
|
||||
List(node.traceRoutes?.reversed() as? [TraceRouteEntity] ?? [], id: \.self, selection: $selectedRoute) { route in
|
||||
Text("\(route.time?.formatted() ?? "unknown".localized) - \(route.response ? (route.hops?.count == 0 && route.response ? "Direct" : "Other") : "No Response")")
|
||||
}
|
||||
.listStyle(.plain)
|
||||
}
|
||||
.navigationTitle("Trace Route List")
|
||||
VStack {
|
||||
if selectedRoute != nil {
|
||||
Divider()
|
||||
if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 > 0 {
|
||||
Text("Trace Route received by \(selectedRoute?.node?.user?.longName ?? "unknown".localized)")
|
||||
Text("Route: \(selectedRoute?.routeText ?? "unknown".localized)")
|
||||
.font(.title)
|
||||
} else if selectedRoute?.response ?? false {
|
||||
Text("Trace Route received directly by \(selectedRoute?.node?.user?.longName ?? "unknown".localized)")
|
||||
.font(.title)
|
||||
VStack {
|
||||
List(node.traceRoutes?.reversed() as? [TraceRouteEntity] ?? [], id: \.self, selection: $selectedRoute) { route in
|
||||
|
||||
Label {
|
||||
Text("\(route.time?.formatted() ?? "unknown".localized) - \(route.response ? (route.hops?.count == 0 && route.response ? "Direct" : "\(route.hops?.count == 0) Hops") : "No Response")")
|
||||
} icon: {
|
||||
Image(systemName: route.response ? (route.hops?.count == 0 && route.response ? "person.line.dotted.person" : "point.3.connected.trianglepath.dotted") : "person.slash")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
}
|
||||
let hopsArray = selectedRoute?.hops?.array as? [TraceRouteHopEntity] ?? []
|
||||
let lineCoords = hopsArray.compactMap({(hop) -> CLLocationCoordinate2D in
|
||||
return hop.coordinate ?? LocationHelper.DefaultLocation
|
||||
})
|
||||
if selectedRoute?.response ?? false {
|
||||
Map() {
|
||||
Annotation("You", coordinate: selectedRoute?.coordinate ?? LocationHelper.DefaultLocation) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color(.green))
|
||||
.strokeBorder(.white, lineWidth: 3)
|
||||
.frame(width: 15, height: 15)
|
||||
}
|
||||
.listStyle(.plain)
|
||||
}
|
||||
.frame(minHeight: 200, maxHeight: 230)
|
||||
VStack {
|
||||
if selectedRoute != nil {
|
||||
if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 > 0 {
|
||||
Text("Received by \(selectedRoute?.node?.user?.longName ?? "unknown".localized)")
|
||||
Text("Route: \(selectedRoute?.routeText ?? "unknown".localized)")
|
||||
.font(.title3)
|
||||
} else if selectedRoute?.response ?? false {
|
||||
Label {
|
||||
Text("Trace route received directly by \(selectedRoute?.node?.user?.longName ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
.annotationTitles(.automatic)
|
||||
// Direct Trace Route
|
||||
if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 == 0 {
|
||||
if selectedRoute?.node?.positions?.count ?? 0 > 0 {
|
||||
let mostRecent = selectedRoute?.node?.positions?.lastObject as! PositionEntity
|
||||
var traceRouteCoords: [CLLocationCoordinate2D] = [selectedRoute?.coordinate ?? LocationsHandler.DefaultLocation, mostRecent.coordinate]
|
||||
Annotation(selectedRoute?.node?.user?.shortName ?? "???", coordinate: mostRecent.nodeCoordinate ?? LocationHelper.DefaultLocation) {
|
||||
.font(.title3)
|
||||
}
|
||||
|
||||
let hopsArray = selectedRoute?.hops?.array as? [TraceRouteHopEntity] ?? []
|
||||
let lineCoords = hopsArray.compactMap({(hop) -> CLLocationCoordinate2D in
|
||||
return hop.coordinate ?? LocationHelper.DefaultLocation
|
||||
})
|
||||
if selectedRoute?.response ?? false {
|
||||
if selectedRoute?.coordinate != nil && (selectedRoute?.node?.positions?.count ?? 0 > 0 || false ) {
|
||||
Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) {
|
||||
Annotation("You", coordinate: selectedRoute?.coordinate ?? LocationHelper.DefaultLocation) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color(.black))
|
||||
.fill(Color(.green))
|
||||
.strokeBorder(.white, lineWidth: 3)
|
||||
.frame(width: 15, height: 15)
|
||||
}
|
||||
}
|
||||
let dashed = StrokeStyle(
|
||||
lineWidth: 3,
|
||||
lineCap: .round, lineJoin: .round, dash: [7, 10]
|
||||
)
|
||||
MapPolyline(coordinates: traceRouteCoords)
|
||||
.stroke(.blue, style: dashed)
|
||||
.annotationTitles(.automatic)
|
||||
// Direct Trace Route
|
||||
if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 == 0 {
|
||||
if selectedRoute?.node?.positions?.count ?? 0 > 0 {
|
||||
let mostRecent = selectedRoute?.node?.positions?.lastObject as! PositionEntity
|
||||
var traceRouteCoords: [CLLocationCoordinate2D] = [selectedRoute?.coordinate ?? LocationsHandler.DefaultLocation, mostRecent.coordinate]
|
||||
Annotation(selectedRoute?.node?.user?.shortName ?? "???", coordinate: mostRecent.nodeCoordinate ?? LocationHelper.DefaultLocation) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color(.black))
|
||||
.strokeBorder(.white, lineWidth: 3)
|
||||
.frame(width: 15, height: 15)
|
||||
}
|
||||
}
|
||||
let dashed = StrokeStyle(
|
||||
lineWidth: 2,
|
||||
lineCap: .round, lineJoin: .round, dash: [7, 10]
|
||||
)
|
||||
MapPolyline(coordinates: traceRouteCoords)
|
||||
.stroke(.blue, style: dashed)
|
||||
}
|
||||
} else if selectedRoute?.hops?.count ?? 0 == 0 {
|
||||
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
VStack {
|
||||
/// Distance
|
||||
if selectedRoute?.node?.positions?.count ?? 0 > 0 && selectedRoute?.coordinate != nil {
|
||||
let mostRecent = selectedRoute?.node?.positions?.lastObject as! PositionEntity
|
||||
let startPoint = CLLocation(latitude: selectedRoute?.coordinate?.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: selectedRoute?.coordinate?.longitude ?? LocationsHandler.DefaultLocation.longitude)
|
||||
|
||||
if startPoint.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 {
|
||||
let metersAway = selectedRoute?.coordinate?.distance(from:CLLocationCoordinate2D(latitude: mostRecent.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: mostRecent.longitude ?? LocationsHandler.DefaultLocation.longitude))
|
||||
Label {
|
||||
Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway ?? 0)))")
|
||||
.foregroundColor(.primary)
|
||||
} icon: {
|
||||
Image(systemName: "lines.measurement.horizontal")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
VStack {
|
||||
Label {
|
||||
Text("Trace route sent to \(selectedRoute?.node?.user?.longName ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
.font(.title3)
|
||||
Divider()
|
||||
Label {
|
||||
Text("\(selectedRoute?.time?.formatted() ?? "") - No response")
|
||||
|
||||
} icon: {
|
||||
Image(systemName: "person.slash")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
.font(.callout)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
Text("Trace Route sent to \(selectedRoute?.node?.user?.longName ?? "unknown".localized)")
|
||||
.font(.title)
|
||||
.padding(.top)
|
||||
Spacer()
|
||||
Text("\(selectedRoute?.time?.formatted() ?? "")")
|
||||
.font(.title3)
|
||||
Spacer()
|
||||
Text("No response")
|
||||
.font(.title2)
|
||||
Spacer()
|
||||
ContentUnavailableView("Select a Trace Route", systemImage: "signpost.right.and.left")
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.navigationTitle("Route Details")
|
||||
.navigationTitle("Trace Route Log")
|
||||
}
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
|
|
|
|||
|
|
@ -144,14 +144,26 @@ struct RouteRecorder: View {
|
|||
Label("Speed \(speed.formatted())", systemImage: "speedometer")
|
||||
}
|
||||
if locationsHandler.lastLocation.courseAccuracy > 0 {
|
||||
Label("Heading \(String(format: "%.2f", locationsHandler.lastLocation.course))°", systemImage: "location.circle")
|
||||
/// Heading
|
||||
let degrees = Angle.degrees(Double(locationsHandler.lastLocation.course))
|
||||
Label {
|
||||
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees)
|
||||
/// Text("Heading: \(heading.formatted())")
|
||||
Text("Heading \(String(format: "%.2f", locationsHandler.lastLocation.course))°")
|
||||
.foregroundColor(.primary)
|
||||
} icon: {
|
||||
Image(systemName: "location.circle")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
.rotationEffect(degrees)
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
.presentationDetents([.fraction(0.5)])
|
||||
.presentationDetents([.fraction(0.6)])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue