From d4abfd7729e1bd3e56001e26c27b1352397b7e75 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 18 Apr 2023 00:09:13 -0700 Subject: [PATCH] Remove location manager updates --- Meshtastic/Enums/LoraConfigEnums.swift | 25 +++ Meshtastic/Helpers/BLEManager.swift | 23 ++- Meshtastic/Helpers/LocationHelper.swift | 115 ++++++------ .../Persistence/PositionEntityExtension.swift | 2 +- .../Persistence/WaypointEntityExtension.swift | 2 +- .../Helpers/Weather/NodeWeatherForecast.swift | 2 +- .../Views/Map/Custom/MapViewSwiftUI.swift | 170 ++++++++---------- Meshtastic/Views/Map/WaypointFormView.swift | 60 ++++--- Meshtastic/Views/Nodes/NodeDetail.swift | 48 +++-- Meshtastic/Views/Nodes/NodeList.swift | 8 +- Meshtastic/Views/Nodes/NodeMap.swift | 59 +++--- Meshtastic/Views/Settings/AppSettings.swift | 42 +---- .../Views/Settings/Config/LoRaConfig.swift | 20 +-- 13 files changed, 296 insertions(+), 280 deletions(-) diff --git a/Meshtastic/Enums/LoraConfigEnums.swift b/Meshtastic/Enums/LoraConfigEnums.swift index 1e737a84..b6738878 100644 --- a/Meshtastic/Enums/LoraConfigEnums.swift +++ b/Meshtastic/Enums/LoraConfigEnums.swift @@ -156,3 +156,28 @@ enum ModemPresets: Int, CaseIterable, Identifiable { } } } + +enum Bandwidths: Int, CaseIterable, Identifiable { + + case thirtyOne = 31 + case sixtyTwo = 62 + case oneHundredTwentyFive = 125 + case twoHundredFifty = 250 + case fiveHundred = 500 + + var id: Int { self.rawValue } + var description: String { + switch self { + case .thirtyOne: + return "31 kHz" + case .sixtyTwo: + return "62 kHz" + case .oneHundredTwentyFive: + return "125 kHz" + case .twoHundredFifty: + return "250 kHz" + case .fiveHundred: + return "500 kHz" + } + } +} diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 52be56e2..c37d9757 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -36,7 +36,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { var timeoutTimerCount = 0 var timeoutTimerRuns = 0 var positionTimer: Timer? - var lastPosition: CLLocation? + var lastPosition: CLLocationCoordinate2D? let emptyNodeNum: UInt32 = 4294967295 /* Meshtastic Service Details */ var TORADIO_characteristic: CBCharacteristic! @@ -790,20 +790,19 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } } } - let locationHelper = LocationHelper() - lastPosition = locationHelper.lastLocation + lastPosition = LocationHelper.currentLocation var positionPacket = Position() - positionPacket.latitudeI = Int32(locationHelper.lastLocation?.coordinate.latitude ?? 0 * 1e7) - positionPacket.longitudeI = Int32(locationHelper.lastLocation?.coordinate.longitude ?? 0 * 1e7) - positionPacket.time = UInt32(locationHelper.lastLocation?.timestamp.timeIntervalSince1970 ?? 0) - positionPacket.timestamp = UInt32(locationHelper.lastLocation?.timestamp.timeIntervalSince1970 ?? 0) - positionPacket.altitude = Int32(locationHelper.lastLocation?.altitude ?? 0) + positionPacket.latitudeI = Int32(LocationHelper.currentLocation.latitude * 1e7) + positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7) + positionPacket.time = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970) + positionPacket.timestamp = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970) + positionPacket.altitude = Int32(LocationHelper.currentAltitude) positionPacket.satsInView = UInt32(LocationHelper.satsInView) - if LocationHelper.currentLocation.speed >= 0 { - positionPacket.groundSpeed = UInt32(locationHelper.lastLocation?.speed ?? 0 * 3.6) + if LocationHelper.currentSpeed >= 0 { + positionPacket.groundSpeed = UInt32(LocationHelper.currentSpeed * 3.6) } - if LocationHelper.currentLocation.course >= 0 { - positionPacket.groundTrack = UInt32(locationHelper.lastLocation?.course ?? 0) + if LocationHelper.currentHeading >= 0 { + positionPacket.groundTrack = UInt32(LocationHelper.currentHeading) } var meshPacket = MeshPacket() meshPacket.to = UInt32(destNum) diff --git a/Meshtastic/Helpers/LocationHelper.swift b/Meshtastic/Helpers/LocationHelper.swift index 98bd233d..26291962 100644 --- a/Meshtastic/Helpers/LocationHelper.swift +++ b/Meshtastic/Helpers/LocationHelper.swift @@ -1,45 +1,58 @@ import CoreLocation -class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate { +class LocationHelper: NSObject, ObservableObject { - private let locationManager = CLLocationManager() - static let shared = LocationHelper() - @Published var lastLocation: CLLocation? - - override init() { + static let shared = LocationHelper() - super.init() - locationManager.delegate = self - locationManager.desiredAccuracy = kCLLocationAccuracyBest - locationManager.pausesLocationUpdatesAutomatically = true - locationManager.allowsBackgroundLocationUpdates = true - locationManager.activityType = .otherNavigation - locationManager.requestWhenInUseAuthorization() - locationManager.distanceFilter = 5 - locationManager.startUpdatingLocation() - } + // Apple Park + static let DefaultLocation = CLLocationCoordinate2D(latitude: 37.3346, longitude: -122.0090) + static let DefaultAltitude = CLLocationDistance(integerLiteral: 0) + static let DefaultSpeed = CLLocationSpeed(integerLiteral: 0) + static let DefaultHeading = CLLocationDirection(integerLiteral: 0) - // Apple Park - static let DefaultLocation = CLLocation(latitude: 37.3346, longitude: -122.0090) - - static var currentLocation: CLLocation { + static var currentLocation: CLLocationCoordinate2D { guard let location = shared.locationManager.location else { - return DefaultLocation - } - return location - } - - /// Sats In View Estimator using horizontal and vertical accuracy since - /// CoreLocation does not have number of sats available - static var satsInView: Int { - // Invalid Coordinates - if shared.locationManager.location?.verticalAccuracy ?? 0 < 0 || shared.locationManager.location?.horizontalAccuracy ?? 0 < 0 { - return 0 + return DefaultLocation } + return location.coordinate + } + + static var currentAltitude: CLLocationDistance { + + guard let altitude = shared.locationManager.location?.altitude else { + return DefaultAltitude + } + return altitude + } + + static var currentSpeed: CLLocationSpeed { + + guard let speed = shared.locationManager.location?.speed else { + return DefaultSpeed + } + return speed + } + + static var currentHeading: CLLocationDirection { + + guard let heading = shared.locationManager.location?.course else { + return DefaultHeading + } + return heading + } + + static var currentTimestamp: Date { + + guard let timestamp = shared.locationManager.location?.timestamp else { + return Date.now + } + return timestamp + } + + static var satsInView: Int { // If we have a position we have a sat var sats = 1 - // If we have a 3D fix we have at least 4 sats if shared.locationManager.location?.verticalAccuracy ?? 0 > 0 { sats = 4 if 0...5 ~= shared.locationManager.location?.horizontalAccuracy ?? 0 { @@ -53,32 +66,32 @@ class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate { } else if 46...60 ~= shared.locationManager.location?.horizontalAccuracy ?? 0 { sats = 5 } - } else if shared.locationManager.location?.horizontalAccuracy ?? 0 <= 300 { - // Need at least 3 sats to be under 300, over that could be wifi or cell triangulation + } else if shared.locationManager.location?.verticalAccuracy ?? 0 < 0 && 60...300 ~= shared.locationManager.location?.horizontalAccuracy ?? 0 { sats = 3 - } else if shared.locationManager.location?.horizontalAccuracy ?? 0 > 300 { + } else if shared.locationManager.location?.verticalAccuracy ?? 0 < 0 && shared.locationManager.location?.horizontalAccuracy ?? 0 > 300 { sats = 2 } return sats } - - public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - guard let mostRecentLocation = locations.last else { - return - } - // Extra Smart positioning logic throwing out bad readings from the phone GPS - let age = -mostRecentLocation.timestamp.timeIntervalSinceNow - manager.stopUpdatingLocation() - if age > 10 || mostRecentLocation.horizontalAccuracy < 0 || mostRecentLocation.horizontalAccuracy > 100 { - print("Bad Location: HA-\(mostRecentLocation.horizontalAccuracy) VA-\(mostRecentLocation.verticalAccuracy) AGE-\(age)") - manager.startUpdatingLocation() - } else { - print("Good Location: HA-\(mostRecentLocation.horizontalAccuracy) VA-\(mostRecentLocation.verticalAccuracy) AGE-\(age)") - lastLocation = mostRecentLocation - } + private let locationManager = CLLocationManager() + + private override init() { + + super.init() + locationManager.delegate = self + locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters + locationManager.pausesLocationUpdatesAutomatically = true + locationManager.allowsBackgroundLocationUpdates = true + locationManager.activityType = .otherNavigation + locationManager.requestWhenInUseAuthorization() + locationManager.startUpdatingLocation() } - +} + +extension LocationHelper: CLLocationManagerDelegate { + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { } + public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print("Location manager failed with error: \(error.localizedDescription)") } diff --git a/Meshtastic/Persistence/PositionEntityExtension.swift b/Meshtastic/Persistence/PositionEntityExtension.swift index ecfe622f..75ba3f4b 100644 --- a/Meshtastic/Persistence/PositionEntityExtension.swift +++ b/Meshtastic/Persistence/PositionEntityExtension.swift @@ -58,7 +58,7 @@ extension PositionEntity { } extension PositionEntity: MKAnnotation { - public var coordinate: CLLocationCoordinate2D { nodeCoordinate ?? LocationHelper.DefaultLocation.coordinate } + public var coordinate: CLLocationCoordinate2D { nodeCoordinate ?? LocationHelper.DefaultLocation } public var title: String? { nodePosition?.user?.shortName ?? NSLocalizedString("unknown", comment: "Unknown") } public var subtitle: String? { time?.formatted() } } diff --git a/Meshtastic/Persistence/WaypointEntityExtension.swift b/Meshtastic/Persistence/WaypointEntityExtension.swift index f7531092..1744c30e 100644 --- a/Meshtastic/Persistence/WaypointEntityExtension.swift +++ b/Meshtastic/Persistence/WaypointEntityExtension.swift @@ -48,7 +48,7 @@ extension WaypointEntity { } extension WaypointEntity: MKAnnotation { - public var coordinate: CLLocationCoordinate2D { waypointCoordinate ?? LocationHelper.DefaultLocation.coordinate } + public var coordinate: CLLocationCoordinate2D { waypointCoordinate ?? LocationHelper.DefaultLocation } public var title: String? { name ?? "Dropped Pin" } public var subtitle: String? { (longDescription ?? "") + diff --git a/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift b/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift index fbac2b1c..28dcfeb7 100644 --- a/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift +++ b/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift @@ -210,7 +210,7 @@ struct NodeWeatherForecast { struct NodeWeatherForecastView_Previews: PreviewProvider { static var previews: some View { - NodeWeatherForecastView(location: CLLocation(latitude: LocationHelper.currentLocation.coordinate.latitude, longitude: LocationHelper.currentLocation.coordinate.longitude) ) + NodeWeatherForecastView(location: CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) ) .aspectRatio(2, contentMode: .fit) .padding() .previewLayout(.sizeThatFits) diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 971c478c..b44ce223 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -13,29 +13,23 @@ func degreesToRadians(_ number: Double) -> Double { struct MapViewSwiftUI: UIViewRepresentable { - var onLongPress: ((CLLocationCoordinate2D) -> Void) - var onWaypointEdit: ((Int) -> Void) + var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void + var onWaypointEdit: (_ waypointId: Int ) -> Void let mapView = MKMapView() // Parameters let positions: [PositionEntity] let waypoints: [WaypointEntity] let mapViewType: MKMapType let userTrackingMode: MKUserTrackingMode - // User Defaults Values + let showNodeHistory: Bool + let showRouteLines: Bool @AppStorage("meshMapRecentering") private var recenter: Bool = false - @AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false - @AppStorage("meshMapShowRouteLines") private var showRouteLines = false // Offline Map Tiles @AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0 @State private var loadedLastUpdatedLocalMapFile = 0 var customMapOverlay: CustomMapOverlay? @State private var presentCustomMapOverlayHash: CustomMapOverlay? - // Custom Tile Server - @AppStorage("meshMapCustomTileServer") private var tileServerUrl = "" - var tileRenderer: MKTileOverlayRenderer? - let tileServer: MapTileServerLinks = .openStreetMaps - func makeUIView(context: Context) -> MKMapView { // Map View Parameters mapView.mapType = mapViewType @@ -45,7 +39,7 @@ struct MapViewSwiftUI: UIViewRepresentable { .filter { $0.latest == true } .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 } let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003) - let center = (latest.count > 0 && userTrackingMode == MKUserTrackingMode.none) ? latest[0].coordinate : LocationHelper.currentLocation.coordinate + let center = (latest.count > 0 && userTrackingMode == MKUserTrackingMode.none) ? latest[0].coordinate : LocationHelper.currentLocation let region = MKCoordinateRegion(center: center, span: span) mapView.addAnnotations(showNodeHistory ? positions : latest) mapView.setRegion(region, animated: true) @@ -90,10 +84,6 @@ struct MapViewSwiftUI: UIViewRepresentable { #endif #endif - - if tileServerUrl.count > 0 { - context.coordinator.setupTileServerRenderer() - } mapView.delegate = context.coordinator return mapView } @@ -126,72 +116,54 @@ struct MapViewSwiftUI: UIViewRepresentable { } } - let latest = positions - .filter { $0.latest == true } - .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 } - let annotationCount = waypoints.count + (showNodeHistory ? positions.count : latest.count) - print("Waypoint Count: \(waypoints.count)") - print("Annotation Count: \(annotationCount) Map Annotations: \(mapView.annotations.count)") - mapView.removeAnnotations(mapView.annotations) - mapView.addAnnotations(waypoints) - mapView.addAnnotations(showNodeHistory ? positions : latest) - // Remove all existing PolyLine Overlays - for overlay in mapView.overlays { - if overlay is MKPolyline { - mapView.removeOverlay(overlay) - } - } - if showRouteLines { + DispatchQueue.main.async { + let latest = positions + .filter { $0.latest == true } + .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 } + let annotationCount = waypoints.count + (showNodeHistory ? positions.count : latest.count) - var lineIndex = 0 - for position in latest { - - let nodePositions = positions.filter { $0.nodePosition?.num ?? 0 == position.nodePosition?.num ?? -1 } - let lineCoords = nodePositions.map ({ - (position) -> CLLocationCoordinate2D in - return position.nodeCoordinate! - }) - let polyline = MKPolyline(coordinates: lineCoords, count: nodePositions.count) - polyline.title = "\(String(position.nodePosition?.num ?? 0))" - mapView.addOverlay(polyline) - lineIndex += 1 - // There are 18 colors for lines, start over if we are at index 17 - if lineIndex > 17 { - lineIndex = 0 + + if annotationCount != mapView.annotations.count { + print("Annotation Count: \(annotationCount) Map Annotations: \(mapView.annotations.count)") + mapView.removeAnnotations(mapView.annotations) + mapView.addAnnotations(waypoints) + if showRouteLines { + // Remove all existing PolyLine Overlays + for overlay in mapView.overlays { + if overlay is MKPolyline { + mapView.removeOverlay(overlay) + } + } + var lineIndex = 0 + for position in latest { + + let nodePositions = positions.filter { $0.nodePosition?.num ?? 0 == position.nodePosition?.num ?? -1 } + let lineCoords = nodePositions.map ({ + (position) -> CLLocationCoordinate2D in + return position.nodeCoordinate! + }) + let polyline = MKPolyline(coordinates: lineCoords, count: nodePositions.count) + polyline.title = "\(String(position.nodePosition?.num ?? 0))" + mapView.addOverlay(polyline) + lineIndex += 1 + // There are 18 colors for lines, start over if we are at index 17 + if lineIndex > 17 { + lineIndex = 0 + } + } } - } - } - if userTrackingMode == MKUserTrackingMode.none { - mapView.showsUserLocation = false - if recenter { - mapView.fit(annotations:showNodeHistory || showRouteLines ? positions : latest, andShow: false) - } - } else { - // Centering Done by tracking mode - mapView.showsUserLocation = true - } - mapView.setUserTrackingMode(userTrackingMode, animated: true) - - if tileServerUrl.count > 0 { - - tileRenderer?.alpha = 0.0 - let overlays = mapView.overlays - if mapView.mapType == .standard { - let overlay = MKTileOverlay(urlTemplate: tileServerUrl) - if overlays.contains(where: {$0 is MKPolyline}) { - mapView.addOverlay(overlay, level: .aboveLabels) - if let poly_overlay = overlays.filter({$0 is MKPolyline}).first { - mapView.addOverlay(poly_overlay, level: .aboveRoads) + if userTrackingMode == MKUserTrackingMode.none { + mapView.showsUserLocation = false + mapView.addAnnotations(showNodeHistory ? positions : latest) + if recenter { + mapView.fit(annotations:showNodeHistory || showRouteLines ? positions : latest, andShow: false) } } else { - mapView.addOverlay(overlay, level: .aboveRoads) - } - } else { - for overlay in overlays { - if let ove = overlay as? MKTileOverlay { - mapView.removeOverlay(ove) - } + // Centering Done by tracking mode + mapView.addAnnotations(showNodeHistory ? positions : latest) + mapView.showsUserLocation = true } + mapView.setUserTrackingMode(userTrackingMode, animated: true) } } } @@ -209,6 +181,9 @@ struct MapViewSwiftUI: UIViewRepresentable { self.parent = parent super.init() self.longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressHandler)) + self.longPressRecognizer.minimumPressDuration = 0.5 + self.longPressRecognizer.cancelsTouchesInView = true + self.longPressRecognizer.delegate = self self.parent.mapView.addGestureRecognizer(longPressRecognizer) } @@ -226,7 +201,7 @@ struct MapViewSwiftUI: UIViewRepresentable { annotationView.displayPriority = .required annotationView.titleVisibility = .visible } else { - annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)) + annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)) annotationView.displayPriority = .defaultHigh annotationView.titleVisibility = .adaptive } @@ -288,7 +263,7 @@ struct MapViewSwiftUI: UIViewRepresentable { annotationView.glyphImage = UIImage(systemName: "flipphone") } if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 { - let metersAway = positionAnnotation.coordinate.distance(from: LocationHelper.currentLocation.coordinate) + let metersAway = positionAnnotation.coordinate.distance(from: LocationHelper.currentLocation) subtitle.text! += NSLocalizedString("distance", comment: "") + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n" } subtitle.text! += positionAnnotation.time?.formatted() ?? "Unknown \n" @@ -321,7 +296,7 @@ struct MapViewSwiftUI: UIViewRepresentable { subtitle.text = "" } if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 { - let metersAway = waypointAnnotation.coordinate.distance(from: LocationHelper.currentLocation.coordinate) + let metersAway = waypointAnnotation.coordinate.distance(from: LocationHelper.currentLocation) let distanceFormatter = MKDistanceFormatter() subtitle.text! += NSLocalizedString("distance", comment: "") + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n" } @@ -351,20 +326,23 @@ struct MapViewSwiftUI: UIViewRepresentable { } } - @objc func longPressHandler(_ sender: UILongPressGestureRecognizer) { + @objc func longPressHandler(_ gesture: UILongPressGestureRecognizer) { - if sender.state == .began { - let point = sender.location(in: sender.view) - if let mapView = sender.view as? MKMapView { - let coordinate = mapView.convert(point, toCoordinateFrom: mapView) - let annotation = MKPointAnnotation() - print("Handler Coord - \(coordinate)") - annotation.title = "Dropped Pin" - annotation.coordinate = coordinate - parent.mapView.addAnnotation(annotation) - UINotificationFeedbackGenerator().notificationOccurred(.success) - parent.onLongPress(coordinate) - } + if gesture.state != UIGestureRecognizer.State.ended { + return + } else if gesture.state != UIGestureRecognizer.State.began { + + // Screen Position - CGPoint + let location = longPressRecognizer.location(in: self.parent.mapView) + + // Map Coordinate - CLLocationCoordinate2D + let coordinate = self.parent.mapView.convert(location, toCoordinateFrom: self.parent.mapView) + let annotation = MKPointAnnotation() + annotation.title = "Dropped Pin" + annotation.coordinate = coordinate + parent.mapView.addAnnotation(annotation) + UINotificationFeedbackGenerator().notificationOccurred(.success) + parent.onLongPress(coordinate) } } @@ -384,14 +362,6 @@ struct MapViewSwiftUI: UIViewRepresentable { return MKOverlayRenderer() } } - - func setupTileServerRenderer() { - //let template = "https://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}.jpg" - //let template = "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.jpg" - let overlay = MKTileOverlay(urlTemplate: parent.tileServerUrl) - parent.mapView.addOverlay(overlay, level: .aboveLabels) - parent.tileRenderer = MKTileOverlayRenderer(tileOverlay: overlay) - } } /// is supposed to be located in the folder with the map name diff --git a/Meshtastic/Views/Map/WaypointFormView.swift b/Meshtastic/Views/Map/WaypointFormView.swift index c53d26e5..0127e6f8 100644 --- a/Meshtastic/Views/Map/WaypointFormView.swift +++ b/Meshtastic/Views/Map/WaypointFormView.swift @@ -12,29 +12,32 @@ struct WaypointFormView: View { @EnvironmentObject var bleManager: BLEManager @Environment(\.dismiss) private var dismiss - @State var coordinate: WaypointCoordinate + @State var coordinate: CLLocationCoordinate2D + @State var waypointId: Int = 0 + @FocusState private var iconIsFocused: Bool + @State private var name: String = "" @State private var description: String = "" @State private var icon: String = "📍" @State private var latitude: Double = 0 @State private var longitude: Double = 0 @State private var expires: Bool = false - @State private var expire: Date = Date.now.addingTimeInterval(60 * 480) // 1 minute * 480 = 8 Hours + @State private var expire: Date = Date() // = Date.now.addingTimeInterval(60 * 120) // 1 minute * 120 = 2 Hours @State private var locked: Bool = false @State private var lockedTo: Int64 = 0 var body: some View { Form { - let distance = CLLocation(latitude: LocationHelper.currentLocation.coordinate.latitude, longitude: LocationHelper.currentLocation.coordinate.longitude).distance(from: CLLocation(latitude: coordinate.coordinate?.latitude ?? 0, longitude: coordinate.coordinate?.longitude ?? 0)) - Section(header: Text((coordinate.waypointId > 0) ? "Editing Waypoint" : "Create Waypoint")) { + let distance = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude).distance(from: CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)) + Section(header: Text((waypointId > 0) ? "Editing Waypoint" : "Create Waypoint")) { HStack { Text("Location: \(String(format: "%.5f", latitude) + "," + String(format: "%.5f", longitude))") .textSelection(.enabled) .foregroundColor(Color.gray) .font(.caption2) - if coordinate.coordinate?.latitude ?? 0 != 0 && coordinate.coordinate?.longitude ?? 0 != 0 { + if coordinate.latitude != LocationHelper.DefaultLocation.latitude && coordinate.longitude != LocationHelper.DefaultLocation.longitude { DistanceText(meters: distance) .foregroundColor(Color.gray) .font(.caption2) @@ -125,26 +128,23 @@ struct WaypointFormView: View { Button { var newWaypoint = Waypoint() - // Loading a waypoint from edit - if coordinate.waypointId > 0 { - newWaypoint.id = UInt32(coordinate.waypointId) - let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context!) - newWaypoint.latitudeI = waypoint.latitudeI - newWaypoint.longitudeI = waypoint.longitudeI + + if waypointId > 0 { + newWaypoint.id = UInt32(waypointId) } else { - // New waypoint newWaypoint.id = UInt32.random(in: UInt32(UInt8.max).. 0 ? name : "Dropped Pin" newWaypoint.description_p = description + newWaypoint.latitudeI = Int32(coordinate.latitude * 1e7) + newWaypoint.longitudeI = Int32(coordinate.longitude * 1e7) // Unicode scalar value for the icon emoji string let unicodeScalers = icon.unicodeScalars // First element as an UInt32 let unicode = unicodeScalers[unicodeScalers.startIndex].value newWaypoint.icon = unicode if locked { + if lockedTo == 0 { newWaypoint.lockedTo = UInt32(bleManager.connectedPeripheral!.num) } else { @@ -157,8 +157,10 @@ struct WaypointFormView: View { newWaypoint.expire = 0 } if bleManager.sendWaypoint(waypoint: newWaypoint) { + waypointId = 0 dismiss() } else { + waypointId = 0 dismiss() print("Send waypoint failed") } @@ -181,11 +183,11 @@ struct WaypointFormView: View { .controlSize(.large) .padding(.bottom) - if coordinate.waypointId > 0 { + if waypointId > 0 { Menu { Button("For me", action: { - let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context!) + let waypoint = getWaypoint(id: Int64(waypointId), context: bleManager.context!) bleManager.context!.delete(waypoint) do { try bleManager.context!.save() @@ -196,19 +198,20 @@ struct WaypointFormView: View { Button("For everyone", action: { var newWaypoint = Waypoint() - if coordinate.waypointId > 0 { - newWaypoint.id = UInt32(coordinate.waypointId) + if waypointId > 0 { + newWaypoint.id = UInt32(waypointId) } newWaypoint.name = name.count > 0 ? name : "Dropped Pin" newWaypoint.description_p = description - newWaypoint.latitudeI = Int32(coordinate.coordinate?.latitude ?? 0 * 1e7) - newWaypoint.longitudeI = Int32(coordinate.coordinate?.longitude ?? 0 * 1e7) + newWaypoint.latitudeI = Int32(coordinate.latitude * 1e7) + newWaypoint.longitudeI = Int32(coordinate.longitude * 1e7) // Unicode scalar value for the icon emoji string let unicodeScalers = icon.unicodeScalars // First element as an UInt32 let unicode = unicodeScalers[unicodeScalers.startIndex].value newWaypoint.icon = unicode if locked { + if lockedTo == 0 { newWaypoint.lockedTo = UInt32(bleManager.connectedPeripheral!.num) } else { @@ -217,8 +220,10 @@ struct WaypointFormView: View { } newWaypoint.expire = 1 if bleManager.sendWaypoint(waypoint: newWaypoint) { + waypointId = 0 dismiss() } else { + waypointId = 0 dismiss() print("Send waypoint failed") } @@ -234,9 +239,14 @@ struct WaypointFormView: View { .padding(.bottom) } } + .onChange(of: waypointId) { newId in + print(newId) + + } .onAppear { - if coordinate.waypointId > 0 { - let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context!) + if waypointId > 0 { + let waypoint = getWaypoint(id: Int64(waypointId), context: bleManager.context!) + waypointId = Int(waypoint.id) name = waypoint.name ?? "Dropped Pin" description = waypoint.longDescription ?? "" icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍") @@ -257,10 +267,10 @@ struct WaypointFormView: View { description = "" locked = false expires = false - expire = Date.now.addingTimeInterval(60 * 480) + expire = Date.now.addingTimeInterval(60 * 120) icon = "📍" - latitude = coordinate.coordinate?.latitude ?? 0 - longitude = coordinate.coordinate?.longitude ?? 0 + latitude = coordinate.latitude + longitude = coordinate.longitude } } } diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 3c4d0026..8508dfd7 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -14,8 +14,11 @@ struct NodeDetail: View { @EnvironmentObject var bleManager: BLEManager @Environment(\.colorScheme) var colorScheme: ColorScheme @AppStorage("meshMapType") private var meshMapType = "standard" + @AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false + @AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false @State private var mapType: MKMapType = .standard - @State var waypointCoordinate: WaypointCoordinate? + @State var waypointCoordinate: CLLocationCoordinate2D? + @State var editingWaypoint: Int = 0 @State private var loadedWeather: Bool = false @State private var showingDetailsPopover = false @State private var showingForecast = false @@ -61,14 +64,19 @@ struct NodeDetail: View { // let todaysPositions = positionArray.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) } ZStack { MapViewSwiftUI(onLongPress: { coord in - waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: coord, waypointId: 0) + waypointCoordinate = coord + editingWaypoint = 0 + presentingWaypointForm = true }, onWaypointEdit: { wpId in if wpId > 0 { - waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId)) + editingWaypoint = wpId + presentingWaypointForm = true } }, positions: lastTenThousand, waypoints: Array(waypoints), mapViewType: mapType, userTrackingMode: MKUserTrackingMode.none, + showNodeHistory: meshMapShowNodeHistory, + showRouteLines: meshMapShowRouteLines, customMapOverlay: self.customMapOverlay ) VStack(alignment: .leading) { @@ -100,7 +108,7 @@ struct NodeDetail: View { .font(.title) .padding() let nodeLocation = node.positions?.lastObject as? PositionEntity - NodeWeatherForecastView(location: CLLocation(latitude: nodeLocation?.nodeCoordinate!.latitude ?? LocationHelper.currentLocation.coordinate.latitude, longitude: nodeLocation?.nodeCoordinate!.longitude ?? LocationHelper.currentLocation.coordinate.longitude) ) + NodeWeatherForecastView(location: CLLocation(latitude: nodeLocation?.nodeCoordinate!.latitude ?? LocationHelper.currentLocation.latitude, longitude: nodeLocation?.nodeCoordinate!.longitude ?? LocationHelper.currentLocation.longitude) ) .frame(height: 250) } #else @@ -109,7 +117,7 @@ struct NodeDetail: View { .font(.title) .padding() let nodeLocation = node.positions?.lastObject as? PositionEntity - NodeWeatherForecastView(location: CLLocation(latitude: nodeLocation?.nodeCoordinate!.latitude ?? LocationHelper.currentLocation.coordinate.latitude, longitude: nodeLocation?.nodeCoordinate!.longitude ?? LocationHelper.currentLocation.coordinate.longitude) ).frame(height: 250) + NodeWeatherForecastView(location: CLLocation(latitude: nodeLocation?.nodeCoordinate!.latitude ?? LocationHelper.currentLocation.latitude, longitude: nodeLocation?.nodeCoordinate!.longitude ?? LocationHelper.currentLocation.longitude) ).frame(height: 250) .presentationDetents([.medium]) .presentationDragIndicator(.automatic) } @@ -202,11 +210,11 @@ struct NodeDetail: View { } } .edgesIgnoringSafeArea([.leading, .trailing]) - .sheet(item: $waypointCoordinate, content: { wpc in - WaypointFormView(coordinate: wpc) - .presentationDetents([.medium, .large]) - .presentationDragIndicator(.automatic) - }) + .sheet(isPresented: $presentingWaypointForm ) {// , onDismiss: didDismissSheet) { + WaypointFormView(coordinate: waypointCoordinate ?? LocationHelper.DefaultLocation, waypointId: editingWaypoint) + .presentationDetents([.medium, .large]) + .presentationDragIndicator(.automatic) + } .navigationBarTitle(String(node.user?.longName ?? NSLocalizedString("unknown", comment: "")), displayMode: .inline) .navigationBarItems(trailing: ZStack { @@ -217,8 +225,22 @@ struct NodeDetail: View { }) .onAppear { self.bleManager.context = context - let currentMapType = MeshMapType(rawValue: meshMapType) - mapType = currentMapType?.MKMapTypeValue() ?? .standard + switch meshMapType { + case "standard": + mapType = .standard + case "mutedStandard": + mapType = .mutedStandard + case "hybrid": + mapType = .hybrid + case "hybridFlyover": + mapType = .hybridFlyover + case "satellite": + mapType = .satellite + case "satelliteFlyover": + mapType = .satelliteFlyover + default: + mapType = .hybridFlyover + } } .task(id: node.num) { if !loadedWeather { @@ -228,7 +250,7 @@ struct NodeDetail: View { let mostRecent = node.positions?.lastObject as? PositionEntity - let weather = try await WeatherService.shared.weather(for: mostRecent?.nodeLocation ?? CLLocation(latitude: LocationHelper.currentLocation.coordinate.latitude, longitude: LocationHelper.currentLocation.coordinate.longitude)) + let weather = try await WeatherService.shared.weather(for: mostRecent?.nodeLocation ?? CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude)) condition = weather.currentWeather.condition temperature = weather.currentWeather.temperature humidity = Int(weather.currentWeather.humidity * 100) diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 3dac2efc..ba6922f1 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -25,10 +25,10 @@ struct NodeList: View { @State private var selection: NodeInfoEntity? // Nothing selected by default. - var body: some View { + var body: some View { NavigationSplitView { - List(nodes, id: \.self, selection: $selection) { node in + List(nodes, id: \.self, selection: $selection) { node in if nodes.count == 0 { Text("no.nodes").font(.title) } else { @@ -52,8 +52,8 @@ struct NodeList: View { if node.positions?.count ?? 0 > 0 && (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 != node.num) { HStack(alignment: .bottom) { let lastPostion = node.positions!.reversed()[0] as! PositionEntity - let myCoord = CLLocation(latitude: LocationHelper.currentLocation.coordinate.latitude, longitude: LocationHelper.currentLocation.coordinate.longitude) - if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.coordinate.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.coordinate.latitude { + let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.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") diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index 94bd7b96..e4e4f213 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -10,14 +10,6 @@ import MapKit import CoreLocation import CoreData -// A simple struct with waypoint data -struct WaypointCoordinate: Identifiable { - - let id: UUID - let coordinate: CLLocationCoordinate2D? - let waypointId: Int64 -} - struct NodeMap: View { @Environment(\.managedObjectContext) var context @@ -37,8 +29,10 @@ struct NodeMap: View { } } } - @AppStorage("meshMapType") private var meshMapType = "standard" + @AppStorage("meshMapType") private var meshMapType = "hybridFlyover" @AppStorage("meshMapUserTrackingMode") private var meshMapUserTrackingMode = 0 + @AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false + @AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)], predicate: NSPredicate(format: "time >= %@ && nodePosition != nil", Calendar.current.startOfDay(for: Date()) as NSDate), animation: .none) @@ -52,7 +46,9 @@ struct NodeMap: View { @State private var mapType: MKMapType = .standard @State private var userTrackingMode: MKUserTrackingMode = .none - @State var waypointCoordinate: WaypointCoordinate? + @State var waypointCoordinate: CLLocationCoordinate2D = LocationHelper.DefaultLocation + @State var editingWaypoint: Int = 0 + @State private var presentingWaypointForm = false @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay( mapName: "offlinemap", tileType: "png", @@ -64,17 +60,25 @@ struct NodeMap: View { NavigationStack { ZStack { - MapViewSwiftUI( - onLongPress: { coord in - waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: coord, waypointId: 0) + MapViewSwiftUI(onLongPress: { coord in + waypointCoordinate = coord + editingWaypoint = 0 + if waypointCoordinate.distance(from: LocationHelper.DefaultLocation) == 0.0 { + print("Apple Park") + } else { + presentingWaypointForm = true + } }, onWaypointEdit: { wpId in if wpId > 0 { - waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId)) + editingWaypoint = wpId + presentingWaypointForm = true } }, positions: Array(positions), waypoints: Array(waypoints), mapViewType: mapType, userTrackingMode: userTrackingMode, + showNodeHistory: meshMapShowNodeHistory, + showRouteLines: meshMapShowRouteLines, customMapOverlay: self.customMapOverlay ) VStack { @@ -91,11 +95,12 @@ struct NodeMap: View { } .ignoresSafeArea(.all, edges: [.top, .leading, .trailing]) .frame(maxHeight: .infinity) - .sheet(item: $waypointCoordinate, content: { wpc in - WaypointFormView(coordinate: wpc) + .sheet(isPresented: $presentingWaypointForm ) {// , onDismiss: didDismissSheet) { + WaypointFormView(coordinate: waypointCoordinate, waypointId: editingWaypoint) .presentationDetents([.medium, .large]) .presentationDragIndicator(.automatic) - }) + + } } .navigationBarItems(leading: MeshtasticLogo(), trailing: @@ -111,11 +116,25 @@ struct NodeMap: View { self.bleManager.context = context self.bleManager.userSettings = userSettings userTrackingMode = UserTrackingModes(rawValue: meshMapUserTrackingMode)?.MKUserTrackingModeValue() ?? MKUserTrackingMode.none - let currentMapType = MeshMapType(rawValue: meshMapType) - mapType = currentMapType?.MKMapTypeValue() ?? .standard + switch meshMapType { + case "standard": + mapType = .standard + case "mutedStandard": + mapType = .mutedStandard + case "hybrid": + mapType = .hybrid + case "hybridFlyover": + mapType = .hybridFlyover + case "satellite": + mapType = .satellite + case "satelliteFlyover": + mapType = .satelliteFlyover + default: + mapType = .hybridFlyover + } }) .onDisappear(perform: { UIApplication.shared.isIdleTimerDisabled = false }) - } + } } diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index fcfa98e7..35b2d22a 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -9,12 +9,11 @@ struct AppSettings: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @EnvironmentObject var userSettings: UserSettings - @StateObject var locationHelper = LocationHelper() @State private var isPresentingCoreDataResetConfirm = false @State private var preferredDeviceConnected = false - var body: some View { + var body: some View { VStack { Form { Section(header: Text("user.details")) { @@ -26,6 +25,7 @@ struct AppSettings: View { } .keyboardType(.asciiCapable) .disableAutocorrection(true) + .listRowSeparator(.visible) } Section(header: Text("options")) { @@ -39,30 +39,7 @@ struct AppSettings: View { } Section(header: Text("phone.gps")) { - let accuracy = Measurement(value: locationHelper.lastLocation?.horizontalAccuracy ?? 300, unit: UnitLength.meters) - let altitiude = Measurement(value: locationHelper.lastLocation?.altitude ?? 0, unit: UnitLength.meters) - let speed = Measurement(value: locationHelper.lastLocation?.speed ?? 0, unit: UnitSpeed.kilometersPerHour) - HStack { - Label("Accuracy \(accuracy.formatted())", systemImage: "scope") - .font(.callout) - Label("Sats \(LocationHelper.satsInView)", systemImage: "sparkles") - .font(.callout) - } - Label("Coordinates \(String(format: "%.5f", locationHelper.lastLocation?.coordinate.latitude ?? 0)), \(String(format: "%.5f", locationHelper.lastLocation?.coordinate.longitude ?? 0))", systemImage: "mappin") - .font(.callout) - .textSelection(.enabled) - if LocationHelper.currentLocation.verticalAccuracy > 0 { - Label("Altitude \(altitiude.formatted())", systemImage: "mountain.2") - .font(.callout) - } - if locationHelper.lastLocation?.courseAccuracy ?? 0 > 0 { - Label("Heading \(String(format: "%.2f", locationHelper.lastLocation?.course ?? 0))°", systemImage: "location.circle") - .font(.callout) - } - if locationHelper.lastLocation?.speedAccuracy ?? 0 > 0 { - Label("Speed \(speed.formatted())", systemImage: "speedometer") - .font(.callout) - } + Toggle(isOn: $userSettings.provideLocation) { Label("provide.location", systemImage: "location.circle.fill") @@ -119,19 +96,6 @@ struct AppSettings: View { Label("Show Route Lines", systemImage: "road.lanes") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - - HStack { - - Label("Tile Server", systemImage: "square.grid.3x2") - TextField( - "Tile Server", - text: $userSettings.meshMapCustomTileServer, - axis: .vertical - ) - .foregroundColor(.gray) - } - .keyboardType(.asciiCapable) - .disableAutocorrection(true) } } HStack { diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 3eb061da..0fcb3150 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -112,17 +112,11 @@ struct LoRaConfig: View { if !usePreset { HStack { - Picker("Bandwidth", selection: $spreadFactor) { - Text("31 kHz") - .tag(31) - Text("62 kHz") - .tag(62) - Text("125 kHz") - .tag(125) - Text("250 kHz") - .tag(0) - Text("500 kHz") - .tag(500) + Picker("Bandwidth", selection: $bandwidth) { + ForEach(Bandwidths.allCases) { bw in + Text(bw.description) + .tag(bw.rawValue == 250 ? 0 : bw.rawValue) + } } } HStack { @@ -143,7 +137,7 @@ struct LoRaConfig: View { } } - Picker("Number of hops", selection: $codingRate) { + Picker("Number of hops", selection: $hopLimit) { ForEach(1..<8) { Text("\($0)") .tag($0 == 3 ? 0 : $0) @@ -290,7 +284,7 @@ struct LoRaConfig: View { } } func setLoRaValues() { - self.hopLimit = Int(node?.loRaConfig?.hopLimit ?? 3) + self.hopLimit = Int(node?.loRaConfig?.hopLimit ?? 0) self.region = Int(node?.loRaConfig?.regionCode ?? 0) self.usePreset = node?.loRaConfig?.usePreset ?? true self.modemPreset = Int(node?.loRaConfig?.modemPreset ?? 0)