diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index d28ae0a6..27ac9bee 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - C9483F6D2773017500998F6B /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9483F6C2773017500998F6B /* MapView.swift */; }; C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */; }; C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; }; C9A7BC1027759A9600760B50 /* PositionAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A7BC0F27759A9600760B50 /* PositionAnnotationView.swift */; }; @@ -22,6 +21,7 @@ DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; }; DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553562855B02500E55709 /* LoRaConfig.swift */; }; DD2553592855B52700E55709 /* PositionConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553582855B52700E55709 /* PositionConfig.swift */; }; + DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */; }; DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2E65252767A01F00E45FC5 /* NodeDetail.swift */; }; DD3501892852FC3B000FC853 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3501882852FC3B000FC853 /* Settings.swift */; }; DD35018B2852FC79000FC853 /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD35018A2852FC79000FC853 /* UserSettings.swift */; }; @@ -127,7 +127,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - C9483F6C2773017500998F6B /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = ""; }; C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMBTileOverlay.swift; sourceTree = ""; }; C9A7BC0F27759A9600760B50 /* PositionAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionAnnotationView.swift; sourceTree = ""; }; C9A88B54278B503C00BD810A /* MapViewModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapViewModule.swift; sourceTree = ""; }; @@ -141,6 +140,7 @@ DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = ""; }; DD2553562855B02500E55709 /* LoRaConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaConfig.swift; sourceTree = ""; }; DD2553582855B52700E55709 /* PositionConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionConfig.swift; sourceTree = ""; }; + DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewSwiftUI.swift; sourceTree = ""; }; DD2E65252767A01F00E45FC5 /* NodeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetail.swift; sourceTree = ""; }; DD3501882852FC3B000FC853 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; DD35018A2852FC79000FC853 /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.swift; sourceTree = ""; }; @@ -270,9 +270,9 @@ isa = PBXGroup; children = ( C9A7BC0E27759A6800760B50 /* Custom */, - C9483F6C2773017500998F6B /* MapView.swift */, C9A88B54278B503C00BD810A /* MapViewModule.swift */, C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */, + DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */, ); path = Map; sourceTree = ""; @@ -744,6 +744,7 @@ DDC4D568275499A500A4208E /* Persistence.swift in Sources */, DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */, DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */, + DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */, DD6193792863875F00E59241 /* SerialConfig.swift in Sources */, DDA0B6B2294CDC55001356EC /* Channels.swift in Sources */, DDAF8C6926ED0D070058C060 /* deviceonly.pb.swift in Sources */, @@ -783,7 +784,6 @@ DD86D40C287F401000BAEB7A /* SaveChannelQRCode.swift in Sources */, DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */, DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */, - C9483F6D2773017500998F6B /* MapView.swift in Sources */, DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */, DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */, DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */, @@ -1006,7 +1006,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.0.10; + MARKETING_VERSION = 2.0.11; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1039,7 +1039,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.0.10; + MARKETING_VERSION = 2.0.11; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; diff --git a/Meshtastic/Persistence/PositionEntityExtension.swift b/Meshtastic/Persistence/PositionEntityExtension.swift index a17f7e06..05fb5d59 100644 --- a/Meshtastic/Persistence/PositionEntityExtension.swift +++ b/Meshtastic/Persistence/PositionEntityExtension.swift @@ -23,7 +23,7 @@ extension PositionEntity { return d / 1e7 } - var coordinate: CLLocationCoordinate2D? { + var nodeCoordinate: CLLocationCoordinate2D? { if latitudeI != 0 && longitudeI != 0 { let coord = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!) return coord @@ -31,12 +31,17 @@ extension PositionEntity { return nil } } - + var annotaton: MKPointAnnotation { let pointAnn = MKPointAnnotation() - if coordinate != nil { - pointAnn.coordinate = coordinate! + if nodeCoordinate != nil { + pointAnn.coordinate = nodeCoordinate! } return pointAnn } } + +extension PositionEntity: MKAnnotation { + public var coordinate: CLLocationCoordinate2D { nodeCoordinate! } + public var subtitle: String? { time?.formatted() } +} diff --git a/Meshtastic/Views/Map/MapView.swift b/Meshtastic/Views/Map/MapView.swift deleted file mode 100644 index 02c752be..00000000 --- a/Meshtastic/Views/Map/MapView.swift +++ /dev/null @@ -1,173 +0,0 @@ -// -// MapView.swift -// MeshtasticApple -// -// Created by Joshua Pirihi on 22/12/21. -// - -import Foundation -import UIKit -import MapKit -import SwiftUI -import CoreData -#if false -// wrap a MKMapView into something we can use in SwiftUI -struct MapView: UIViewRepresentable { - - var nodes: FetchedResults - - var mapViewDelegate = MapViewDelegate() - - // observe changes to the key in UserDefaults - @AppStorage("meshMapType") var type: String = "hybrid" - - func makeUIView(context: Context) -> MKMapView { - - let map = MKMapView(frame: .zero) - - map.userTrackingMode = .follow - - let region = MKCoordinateRegion( center: map.centerCoordinate, latitudinalMeters: CLLocationDistance(exactly: 500)!, longitudinalMeters: CLLocationDistance(exactly: 500)!) - map.setRegion(map.regionThatFits(region), animated: false) - - //self.updateMapType(map) - self.showNodePositions(to: map) - self.moveToMeshRegion(in: map) - - map.register(PositionAnnotationView.self, forAnnotationViewWithReuseIdentifier: NSStringFromClass(PositionAnnotationView.self)) - - let overlay = MKTileOverlay(urlTemplate: //"http://tiles-a.data-cdn.linz.govt.nz/services;key=7fa19132d53240708c4ff436df5b9800/tiles/v4/layer=50767/EPSG:3857/{z}/{x}/{y}.png") - "http://10.147.253.250:5050/local/map/{z}/{x}/{y}.png") - overlay.canReplaceMapContent = true - self.mapViewDelegate.renderer = MKTileOverlayRenderer(tileOverlay: overlay) - map.addOverlay(overlay) - - return map - } - - func updateUIView(_ view: MKMapView, context: Context) { - view.delegate = mapViewDelegate // (1) This should be set in makeUIView, but it is getting reset to `nil` - view.translatesAutoresizingMaskIntoConstraints = false // (2) In the absence of this, we get constraints error on rotation; and again, it seems one should do this in makeUIView, but has to be here - - self.updateMapType(view) - - self.showNodePositions(to: view) - - //if (self.needToMoveToMeshRegion) { - // self.moveToMeshRegion(in: view) - // self.needToMoveToMeshRegion = false - //} - } - - func moveToMeshRegion(in mapView: MKMapView) { - //go through the annotations and create a bounding box that encloses them - - var minLat: CLLocationDegrees = 90.0 - var maxLat: CLLocationDegrees = -90.0 - var minLon: CLLocationDegrees = 180.0 - var maxLon: CLLocationDegrees = -180.0 - - for annotation in mapView.annotations { - if annotation.isKind(of: PositionAnnotation.self) { - minLat = min(minLat, annotation.coordinate.latitude) - maxLat = max(maxLat, annotation.coordinate.latitude) - minLon = min(minLon, annotation.coordinate.longitude) - maxLon = max(maxLon, annotation.coordinate.longitude) - } - } - - //check if the mesh region looks sensible before we move to it. Otherwise we won't move the map (leave it at the current location) - if maxLat < minLat || (maxLat-minLat) > 5 || maxLon < minLon || (maxLon-minLon) > 5 { - return - } - - let centerCoord = CLLocationCoordinate2D(latitude: (minLat+maxLat)/2, longitude: (minLon+maxLon)/2) - - let span = MKCoordinateSpan(latitudeDelta: (maxLat-minLat)*1.5, longitudeDelta: (maxLon-minLon)*1.5) - - let region = mapView.regionThatFits(MKCoordinateRegion(center: centerCoord, span: span)) - - mapView.setRegion(region, animated: true) - - - } - - func updateMapType(_ map: MKMapView) { - - switch self.type { - case "satellite": - map.mapType = .satellite - break - case "standard": - map.mapType = .standard - break - case "hybrid": - map.mapType = .hybrid - break - default: - map.mapType = .hybrid - } - } -} - -private extension MapView { - - func showNodePositions(to view: MKMapView) { - - // clear any existing annotations - if !view.annotations.isEmpty { - view.removeAnnotations(view.annotations) - } - - for node in self.nodes { - // try and get the last position - if (node.positions?.count ?? 0) > 0 && (node.positions!.lastObject as! PositionEntity).coordinate != nil { - let annotation = PositionAnnotation() - annotation.coordinate = (node.positions!.lastObject as! PositionEntity).coordinate! - annotation.title = node.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown") - annotation.shortName = node.user?.shortName?.uppercased() ?? "???" - - view.addAnnotation(annotation) - } - } - } -} - -class MapViewDelegate: NSObject, MKMapViewDelegate { - - var renderer: MKTileOverlayRenderer? - - func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { - - guard !annotation.isKind(of: MKUserLocation.self) else { - // Make a fast exit if the annotation is the `MKUserLocation`, as it's not an annotation view we wish to customize. - return nil - } - - var annotationView: MKAnnotationView? - - if let annotation = annotation as? PositionAnnotation { - annotationView = self.setupPositionAnnotationView(for: annotation, on: mapView) - } - - return annotationView - } - - func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { - return self.renderer! - - } - - private func setupPositionAnnotationView(for annotation: PositionAnnotation, on mapView: MKMapView) -> PositionAnnotationView { - let identifier = NSStringFromClass(PositionAnnotationView.self) - - let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? PositionAnnotationView ?? PositionAnnotationView() - - annotationView.name = annotation.shortName ?? "???" - - annotationView.canShowCallout = true - - return annotationView - } -} -#endif diff --git a/Meshtastic/Views/Map/MapViewModule.swift b/Meshtastic/Views/Map/MapViewModule.swift index 13d5bb77..bb3bfa82 100644 --- a/Meshtastic/Views/Map/MapViewModule.swift +++ b/Meshtastic/Views/Map/MapViewModule.swift @@ -223,7 +223,7 @@ public struct MapView: UIViewRepresentable { } let annotation = PositionAnnotation() - annotation.coordinate = position.coordinate! + annotation.coordinate = position.nodeCoordinate! annotation.title = position.nodePosition!.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown") annotation.shortName = position.nodePosition!.user?.shortName?.uppercased() ?? "???" diff --git a/Meshtastic/Views/Map/MapViewSwiftUI.swift b/Meshtastic/Views/Map/MapViewSwiftUI.swift new file mode 100644 index 00000000..e02ca6da --- /dev/null +++ b/Meshtastic/Views/Map/MapViewSwiftUI.swift @@ -0,0 +1,57 @@ +// +// MapViewSwitUI.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 1/9/23. +// + +import SwiftUI +import MapKit + +struct MapViewSwiftUI: UIViewRepresentable { + + let positions: [PositionEntity] + let region: MKCoordinateRegion + let mapViewType: MKMapType + + func makeUIView(context: Context) -> MKMapView { + let mapView = MKMapView() + mapView.mapType = mapViewType + mapView.setRegion(region, animated: true) + mapView.isRotateEnabled = true + mapView.addAnnotations(positions) + mapView.delegate = context.coordinator + return mapView + } + + func updateUIView(_ mapView: MKMapView, context: Context) { + mapView.mapType = mapViewType + } + + func makeCoordinator() -> MapCoordinator { + .init() + } + + final class MapCoordinator: NSObject, MKMapViewDelegate { + + func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { + + switch annotation { + + case _ as MKClusterAnnotation: + let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "nodeGroup") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "nodeGroup") + annotationView.markerTintColor = .darkGray + return annotationView + case _ as PositionEntity: + let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "Node") + annotationView.canShowCallout = true + annotationView.glyphText = "📟" + annotationView.clusteringIdentifier = "nodeGroup" + annotationView.markerTintColor = UIColor(.accentColor) + annotationView.titleVisibility = .visible + return annotationView + default: return nil + } + } + } +} diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 932ae13a..bae52138 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -1,73 +1,62 @@ /* -Abstract: -A view showing the details for a node. -*/ + Abstract: + A view showing the details for a node. + */ import SwiftUI import MapKit import CoreLocation struct NodeDetail: View { - + @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @State private var mapType: MKMapType = .standard + @State private var showingDetailsPopover = false @State var satsInView = 0 @State private var showingShutdownConfirm: Bool = false @State private var showingRebootConfirm: Bool = false - + var node: NodeInfoEntity - + var body: some View { let hwModelString = node.user?.hwModel ?? "UNSET" - + NavigationStack { GeometryReader { bounds in VStack { if node.positions?.count ?? 0 > 0 { let mostRecent = node.positions?.lastObject as! PositionEntity - if mostRecent.coordinate != nil { - let nodeCoordinatePosition = CLLocationCoordinate2D(latitude: mostRecent.latitude!, longitude: mostRecent.longitude!) - - let regionBinding = Binding( - get: { - MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)) - }, - set: { _ in } - ) + let nodeCoordinatePosition = CLLocationCoordinate2D(latitude: mostRecent.latitude!, longitude: mostRecent.longitude!) + ZStack { + let annotations = node.positions?.array as! [PositionEntity] ZStack { - let annotations = node.positions?.array as! [PositionEntity] - - Map(coordinateRegion: regionBinding, - interactionModes: [.all], - showsUserLocation: true, - userTrackingMode: .constant(.follow), - annotationItems: annotations) { location in + MapViewSwiftUI(positions: annotations, region: MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)), mapViewType: mapType) + VStack { + Spacer() + Text(mostRecent.satsInView > 0 ? "Sats: \(mostRecent.satsInView)" : " ") + .font(.caption2) - return MapAnnotation( - coordinate: location.coordinate ?? CLLocationCoordinate2D(latitude: 0, longitude: 0), - content: { - - NodeAnnotation(time: location.time!) - } - ) + Picker("", selection: $mapType) { + Text("Standard").tag(MKMapType.standard) + Text("Hybrid").tag(MKMapType.hybrid) + Text("Satellite").tag(MKMapType.satellite) + } + .pickerStyle(SegmentedPickerStyle()) } - .ignoresSafeArea(.all, edges: [.leading, .trailing]) - .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 2) - } - Text(mostRecent.satsInView > 0 ? "Sats: \(mostRecent.satsInView)" : " ") - .offset( y:-40) + .ignoresSafeArea(.all, edges: [.top, .leading, .trailing]) + .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.65) } - } else { HStack { - + } - .padding([.top], 60) + .padding([.top], 20) } ScrollView { @@ -80,13 +69,13 @@ struct NodeDetail: View { Divider() VStack { if node.user != nil { - + Image(hwModelString) .resizable() .aspectRatio(contentMode: .fill) .frame(width: 100, height: 100) .cornerRadius(5) - + Text(String(hwModelString)) .foregroundColor(.gray) .font(.largeTitle).fixedSize() @@ -96,7 +85,7 @@ struct NodeDetail: View { if node.snr > 0 { Divider() VStack(alignment: .center) { - + Image(systemName: "waveform.path") .font(.title) .foregroundColor(.accentColor) @@ -109,15 +98,15 @@ struct NodeDetail: View { .fixedSize() } } - + if node.telemetries?.count ?? 0 >= 1 { - + let mostRecent = node.telemetries?.lastObject as! TelemetryEntity Divider() VStack(alignment: .center) { BatteryGauge(batteryLevel: Double(mostRecent.batteryLevel)) if mostRecent.voltage > 0 { - + Text(String(format: "%.2f", mostRecent.voltage) + " V") .font(.title) .foregroundColor(.gray) @@ -147,7 +136,7 @@ struct NodeDetail: View { Divider() VStack { HStack { - Image(systemName: "number") + Image(systemName: "number") .font(.title2) .foregroundColor(.accentColor) .symbolRenderingMode(.hierarchical) @@ -160,8 +149,8 @@ struct NodeDetail: View { HStack { Image(systemName: "globe") .font(.title) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) Text("MAC Address: ").font(.title) } @@ -174,8 +163,8 @@ struct NodeDetail: View { HStack { Image(systemName: "clock.badge.checkmark.fill") .font(.title) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) Text("heard.last").font(.title)+Text(":").font(.title) } @@ -190,7 +179,7 @@ struct NodeDetail: View { } else { HStack { - + VStack(alignment: .center) { CircleText(text: node.user?.shortName ?? "???", color: .accentColor) } @@ -210,7 +199,7 @@ struct NodeDetail: View { if node.snr > 0 { Divider() VStack(alignment: .center) { - + Image(systemName: "waveform.path") .font(.title) .foregroundColor(.accentColor) @@ -223,15 +212,15 @@ struct NodeDetail: View { } .padding(5) } - + if node.telemetries?.count ?? 0 >= 1 { - + let mostRecent = node.telemetries?.lastObject as! TelemetryEntity - + Divider() - + VStack(alignment: .center) { - + BatteryGauge(batteryLevel: Double(mostRecent.batteryLevel)) if mostRecent.voltage > 0 { @@ -262,7 +251,7 @@ struct NodeDetail: View { Divider() VStack { HStack { - Image(systemName: "number") + Image(systemName: "number") .font(.title2) .foregroundColor(.accentColor) .symbolRenderingMode(.hierarchical) @@ -275,9 +264,9 @@ struct NodeDetail: View { Divider() HStack { Image(systemName: "globe") - .font(.headline) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) + .font(.headline) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) Text("MAC Address: ") Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")).foregroundColor(.gray) } @@ -334,16 +323,16 @@ struct NodeDetail: View { } if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num { - + HStack { if hwModelString == "TBEAM" || hwModelString == "TECHO" || hwModelString.contains("4631") { - + Button(action: { showingShutdownConfirm = true }) { - + Label("Power Off", systemImage: "power") } .buttonStyle(.bordered) @@ -361,13 +350,13 @@ struct NodeDetail: View { } } } - + Button(action: { showingRebootConfirm = true }) { - + Label("reboot", systemImage: "arrow.triangle.2.circlepath") } .buttonStyle(.bordered) @@ -376,31 +365,31 @@ struct NodeDetail: View { .padding() .confirmationDialog("are.you.sure", - isPresented: $showingRebootConfirm - ) { - Button("reboot.node", role: .destructive) { - - if !bleManager.sendReboot(fromUser: node.user!, toUser: node.user!) { - print("Reboot Failed") - } - } + isPresented: $showingRebootConfirm + ) { + Button("reboot.node", role: .destructive) { + + if !bleManager.sendReboot(fromUser: node.user!, toUser: node.user!) { + print("Reboot Failed") } + } + } } .padding(5) } } - .offset( y:-40) + //.offset( y:-40) } .edgesIgnoringSafeArea([.leading, .trailing]) .navigationBarTitle(String(node.user?.longName ?? NSLocalizedString("unknown", comment: "")), displayMode: .inline) .padding(.bottom, 10) .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 { self.bleManager.context = context diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 1925c767..fb6980d7 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -53,8 +53,8 @@ struct NodeList: View { HStack(alignment: .bottom) { let lastPostion = node.positions!.reversed()[0] as! PositionEntity let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) - if lastPostion.coordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude { - let nodeCoord = CLLocation(latitude: lastPostion.coordinate!.latitude, longitude: lastPostion.coordinate!.longitude) + if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude { + let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude) let metersAway = nodeCoord.distance(from: myCoord) Image(systemName: "lines.measurement.horizontal") .font(.title3)