diff --git a/Localizable.xcstrings b/Localizable.xcstrings index da00af423..3a3c9a4be 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -10878,9 +10878,6 @@ }, "Location:" : { - }, - "Location: %@" : { - }, "Locked" : { diff --git a/Meshtastic/Views/MapKitMap/Custom/MapButtons.swift b/Meshtastic/Views/MapKitMap/Custom/MapButtons.swift index 2d45b4f4a..89959f744 100644 --- a/Meshtastic/Views/MapKitMap/Custom/MapButtons.swift +++ b/Meshtastic/Views/MapKitMap/Custom/MapButtons.swift @@ -1,64 +1,64 @@ // -// MapButtons.swift -// Meshtastic +//// MapButtons.swift +//// Meshtastic +//// +//// Copyright © Garth Vander Houwen 4/23/23. +//// // -// Copyright © Garth Vander Houwen 4/23/23. +//import SwiftUI // - -import SwiftUI - -struct MapButtons: View { - let buttonWidth: CGFloat = 22 - let width: CGFloat = 45 - @Binding var tracking: UserTrackingModes - @Binding var isPresentingInfoSheet: Bool - var body: some View { - VStack { - let impactLight = UIImpactFeedbackGenerator(style: .light) - Button(action: { - self.isPresentingInfoSheet.toggle() - }) { - Image(systemName: isPresentingInfoSheet ? "info.circle.fill" : "info.circle") - .resizable() - .frame(width: buttonWidth, height: buttonWidth, alignment: .center) - .offset(y: -2) - } - Divider() - Button(action: { - switch self.tracking { - case .none: - self.tracking = .follow - case .follow: - self.tracking = .followWithHeading - case .followWithHeading: - self.tracking = .none - } - impactLight.impactOccurred() - }) { - Image(systemName: tracking.icon) - .frame(width: buttonWidth, height: buttonWidth, alignment: .center) - .offset(y: 3) - } - } - .frame(width: width, height: width*2, alignment: .center) - .background(Color(UIColor.systemBackground)) - .cornerRadius(8) - .shadow(radius: 1) - .offset(x: 3, y: 25) - } -} - -// MARK: Previews -// struct MapControl_Previews: PreviewProvider { -// @State static var tracking: UserTrackingModes = .none -// @State static var isPresentingInfoSheet = false -// static var previews: some View { -// Group { -// MapButtons(tracking: $tracking, isPresentingInfoSheet: $isPresentingInfoSheet) -// .environment(\.colorScheme, .light) -// MapButtons(tracking: $tracking, isPresentingInfoSheet: $isPresentingInfoSheet) -// .environment(\.colorScheme, .dark) +//struct MapButtons: View { +// let buttonWidth: CGFloat = 22 +// let width: CGFloat = 45 +// @Binding var tracking: UserTrackingModes +// @Binding var isPresentingInfoSheet: Bool +// var body: some View { +// VStack { +// let impactLight = UIImpactFeedbackGenerator(style: .light) +// Button(action: { +// self.isPresentingInfoSheet.toggle() +// }) { +// Image(systemName: isPresentingInfoSheet ? "info.circle.fill" : "info.circle") +// .resizable() +// .frame(width: buttonWidth, height: buttonWidth, alignment: .center) +// .offset(y: -2) +// } +// Divider() +// Button(action: { +// switch self.tracking { +// case .none: +// self.tracking = .follow +// case .follow: +// self.tracking = .followWithHeading +// case .followWithHeading: +// self.tracking = .none +// } +// impactLight.impactOccurred() +// }) { +// Image(systemName: tracking.icon) +// .frame(width: buttonWidth, height: buttonWidth, alignment: .center) +// .offset(y: 3) +// } // } -// .previewLayout(.fixed(width: 60, height: 100)) +// .frame(width: width, height: width*2, alignment: .center) +// .background(Color(UIColor.systemBackground)) +// .cornerRadius(8) +// .shadow(radius: 1) +// .offset(x: 3, y: 25) // } -// } +//} +// +//// MARK: Previews +//// struct MapControl_Previews: PreviewProvider { +//// @State static var tracking: UserTrackingModes = .none +//// @State static var isPresentingInfoSheet = false +//// static var previews: some View { +//// Group { +//// MapButtons(tracking: $tracking, isPresentingInfoSheet: $isPresentingInfoSheet) +//// .environment(\.colorScheme, .light) +//// MapButtons(tracking: $tracking, isPresentingInfoSheet: $isPresentingInfoSheet) +//// .environment(\.colorScheme, .dark) +//// } +//// .previewLayout(.fixed(width: 60, height: 100)) +//// } +//// } diff --git a/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift index b7629326d..8cdf6d84a 100644 --- a/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift @@ -1,434 +1,434 @@ +//// +//// MapViewSwitUI.swift +//// Meshtastic +//// +//// Copyright(c) Josh Pirihi & Garth Vander Houwen 1/16/22. // -// MapViewSwitUI.swift -// Meshtastic +//import Foundation +//import SwiftUI +//import MapKit +//import OSLog // -// Copyright(c) Josh Pirihi & Garth Vander Houwen 1/16/22. - -import Foundation -import SwiftUI -import MapKit -import OSLog - -struct PolygonInfo: Codable { - let stroke: String? - let strokeWidth, strokeOpacity: Int? - let fill: String? - let fillOpacity: Double? - let title, subtitle: String? -} - -func degreesToRadians(_ number: Double) -> Double { - return number * .pi / 180 -} -var currentMapLayer: MapLayer? - -struct MapViewSwiftUI: UIViewRepresentable { - var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void - var onWaypointEdit: (_ waypointId: Int ) -> Void - let mapView = MKMapView() - // Parameters - let selectedMapLayer: MapLayer - let selectedWeatherLayer: MapOverlayServer = UserDefaults.mapOverlayServer - let positions: [PositionEntity] - let waypoints: [WaypointEntity] - let userTrackingMode: MKUserTrackingMode - let showNodeHistory: Bool - let showRouteLines: Bool - let mapViewType: MKMapType = MKMapType.standard - // Offline Map Tiles - @AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0 - @State private var loadedLastUpdatedLocalMapFile = 0 - var customMapOverlay: CustomMapOverlay? - @State private var presentCustomMapOverlayHash: CustomMapOverlay? - // MARK: Private methods - private func configureMap(mapView: MKMapView) { - // Map View Parameters - mapView.mapType = mapViewType - mapView.addAnnotations(waypoints) - // Do the initial map centering - let latest = positions - .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 - let region = MKCoordinateRegion(center: center, span: span) - mapView.addAnnotations(showNodeHistory ? positions : latest) - mapView.setRegion(region, animated: true) - // Set user (phone gps) tracking options - mapView.setUserTrackingMode(userTrackingMode, animated: true) - if userTrackingMode == MKUserTrackingMode.none { - if latest.count == 1 { - mapView.fit(annotations: showNodeHistory ? positions: latest, andShow: false) - } else { - mapView.fitAllAnnotations() - } - mapView.showsUserLocation = false - } else { - mapView.showsUserLocation = true - } - // Other MKMapView Settings - mapView.preferredConfiguration.elevationStyle = .realistic// .flat - mapView.pointOfInterestFilter = MKPointOfInterestFilter.excludingAll - mapView.isPitchEnabled = true - mapView.isRotateEnabled = true - mapView.isScrollEnabled = true - mapView.isZoomEnabled = true - mapView.showsBuildings = true - mapView.showsScale = true - mapView.showsTraffic = true - - mapView.showsCompass = false - let compass = MKCompassButton(mapView: mapView) - compass.translatesAutoresizingMaskIntoConstraints = false - #if targetEnvironment(macCatalyst) - // Show the default always visible compass and the mac only controls - compass.compassVisibility = .visible - mapView.addSubview(compass) - mapView.showsZoomControls = true - mapView.showsPitchControl = true - compass.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -115).isActive = true - compass.bottomAnchor.constraint(equalTo: mapView.bottomAnchor, constant: -5).isActive = true - #else - compass.compassVisibility = .adaptive - mapView.addSubview(compass) - compass.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -5).isActive = true - compass.topAnchor.constraint(equalTo: mapView.topAnchor, constant: 145).isActive = true - #endif - } - private func setMapBaseLayer(mapView: MKMapView) { - // Avoid refreshing UI if selectedLayer has not changed - guard currentMapLayer != selectedMapLayer else { return } - currentMapLayer = selectedMapLayer - for overlay in mapView.overlays where overlay is MKTileOverlay { - mapView.removeOverlay(overlay) - } - switch selectedMapLayer { - case .offline: - mapView.mapType = .standard - let overlay = TileOverlay() - overlay.canReplaceMapContent = false - overlay.minimumZ = UserDefaults.mapTileServer.zoomRange.startIndex - overlay.maximumZ = UserDefaults.mapTileServer.zoomRange.endIndex - mapView.addOverlay(overlay, level: UserDefaults.mapTilesAboveLabels ? .aboveLabels : .aboveRoads) - case .satellite: - mapView.mapType = .satellite - case .hybrid: - mapView.mapType = .hybrid - default: - mapView.mapType = .standard - } - } - private func setMapOverlays(mapView: MKMapView) { - // Weather radar - if UserDefaults.enableOverlayServer { - let locale = Locale.current - if locale.region?.identifier ?? "no locale" == "US" { - let overlay = MKTileOverlay(urlTemplate: selectedWeatherLayer.tileUrl) - overlay.canReplaceMapContent = false - overlay.minimumZ = selectedWeatherLayer.zoomRange.startIndex - overlay.maximumZ = selectedWeatherLayer.zoomRange.endIndex - mapView.addOverlay(overlay, level: .aboveLabels) - } - } - } - - func makeUIView(context: Context) -> MKMapView { - currentMapLayer = nil - mapView.delegate = context.coordinator - self.configureMap(mapView: mapView) - return mapView - } - func updateUIView(_ mapView: MKMapView, context: Context) { - // Set selected map base layer - setMapBaseLayer(mapView: mapView) - // Set map tile server and weather overlay layers - setMapOverlays(mapView: mapView) - let latest = positions - .filter { $0.latest == true } - .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 } - // Node Route Lines - if showRouteLines { - // Remove all existing PolyLine Overlays - for overlay in mapView.overlays where overlay is MKPolyline { - mapView.removeOverlay(overlay) - } - var lineIndex = 0 - for position in latest { - let nodePositions = positions.filter { $0.nodeCoordinate != nil && $0.nodePosition?.num ?? 0 == position.nodePosition?.num ?? -1 } - let lineCoords = nodePositions.compactMap({(position) -> CLLocationCoordinate2D in - return position.nodeCoordinate ?? LocationHelper.DefaultLocation - }) - let polyline = MKPolyline(coordinates: lineCoords, count: nodePositions.count) - polyline.title = "\(String(position.nodePosition?.num ?? 0))" - mapView.addOverlay(polyline, level: .aboveLabels) - lineIndex += 1 - // There are 18 colors for lines, start over if we are at index 17 - if lineIndex > 17 { - lineIndex = 0 - } - } - } else { - // Remove all existing PolyLine Overlays - for overlay in mapView.overlays where overlay is MKPolyline { - mapView.removeOverlay(overlay) - } - } - let annotationCount = waypoints.count + (showNodeHistory ? positions.count : latest.count) - if annotationCount != mapView.annotations.count { - Logger.services.info("Annotation Count: \(annotationCount) Map Annotations: \(mapView.annotations.count)") - mapView.removeAnnotations(mapView.annotations) - mapView.addAnnotations(waypoints) - } - mapView.addAnnotations(showNodeHistory ? positions : latest) - if userTrackingMode == MKUserTrackingMode.none { - mapView.showsUserLocation = false - if UserDefaults.enableMapRecentering { - if latest.count == 1 { - mapView.fit(annotations: showNodeHistory ? positions : latest, andShow: true) - } else { - mapView.fitAllAnnotations() - } - } - } else { - mapView.showsUserLocation = true - } - mapView.setUserTrackingMode(userTrackingMode, animated: true) - } - func makeCoordinator() -> MapCoordinator { - return Coordinator(self) - } - final class MapCoordinator: NSObject, MKMapViewDelegate, UIGestureRecognizerDelegate { - var parent: MapViewSwiftUI - var longPressRecognizer = UILongPressGestureRecognizer() - init(_ parent: MapViewSwiftUI) { - 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) - } - func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { - switch annotation { - case let positionAnnotation as PositionEntity: - let reuseID = String(positionAnnotation.nodePosition?.num ?? 0) + "-" + String(positionAnnotation.time?.timeIntervalSince1970 ?? 0) - let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseID ) - annotationView.tag = -1 - annotationView.canShowCallout = true - if positionAnnotation.latest { - annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)).darker() - annotationView.displayPriority = .required - annotationView.titleVisibility = .visible - } else { - annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)).lighter() - annotationView.displayPriority = .defaultHigh - annotationView.titleVisibility = .adaptive - } - annotationView.tag = -1 - annotationView.canShowCallout = true - annotationView.titleVisibility = .adaptive - let leftIcon = UIImageView(image: annotationView.glyphText?.image()) - leftIcon.backgroundColor = UIColor(.indigo) - annotationView.leftCalloutAccessoryView = leftIcon - let subtitle = UILabel() - subtitle.text = "Long Name: \(positionAnnotation.nodePosition?.user?.longName ?? "Unknown") \n" - subtitle.text? += "Latitude: \(String(format: "%.5f", positionAnnotation.coordinate.latitude)) \n" - subtitle.text! += "Longitude: \(String(format: "%.5f", positionAnnotation.coordinate.longitude)) \n" - let distanceFormatter = MKDistanceFormatter() - subtitle.text! += "Altitude: \(distanceFormatter.string(fromDistance: Double(positionAnnotation.altitude))) \n" - if positionAnnotation.nodePosition?.metadata != nil { - if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.client || - DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.clientMute || - DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.routerClient { - annotationView.glyphImage = UIImage(systemName: "flipphone") - } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.repeater { - annotationView.glyphImage = UIImage(systemName: "repeat") - } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.router { - annotationView.glyphImage = UIImage(systemName: "wifi.router.fill") - } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.tracker { - annotationView.glyphImage = UIImage(systemName: "location.viewfinder") - } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.sensor { - annotationView.glyphImage = UIImage(systemName: "sensor") - } - let pf = PositionFlags(rawValue: Int(positionAnnotation.nodePosition?.metadata?.positionFlags ?? 3)) - if pf.contains(.Satsinview) { - subtitle.text! += "Sats in view: \(String(positionAnnotation.satsInView)) \n" - } - if pf.contains(.SeqNo) { - subtitle.text! += "Sequence: \(String(positionAnnotation.seqNo)) \n" - } - if pf.contains(.Heading) { - if parent.userTrackingMode != MKUserTrackingMode.followWithHeading { - annotationView.glyphImage = UIImage(systemName: "location.north.fill")?.rotate(radians: Float(degreesToRadians(Double(positionAnnotation.heading)))) - subtitle.text! += "Heading: \(String(positionAnnotation.heading)) \n" - } else { - annotationView.glyphImage = UIImage(systemName: "flipphone") - } - } - if pf.contains(.Speed) { - let formatter = MeasurementFormatter() - formatter.locale = Locale.current - if positionAnnotation.speed <= 1 { - annotationView.glyphImage = UIImage(systemName: "hexagon") - } - subtitle.text! += "Speed: \(formatter.string(from: Measurement(value: Double(positionAnnotation.speed), unit: UnitSpeed.kilometersPerHour))) \n" - } - } else { - // node metadata is nil - annotationView.glyphImage = UIImage(systemName: "flipphone") - } - if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 { - let metersAway = positionAnnotation.coordinate.distance(from: LocationHelper.currentLocation) - subtitle.text! += "distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n" - } - subtitle.text! += positionAnnotation.time?.formatted() ?? "Unknown \n" - subtitle.numberOfLines = 0 - annotationView.detailCalloutAccessoryView = subtitle - let detailsIcon = UIButton(type: .detailDisclosure) - detailsIcon.setImage(UIImage(systemName: "trash"), for: .normal) - annotationView.rightCalloutAccessoryView = detailsIcon - return annotationView - case let waypointAnnotation as WaypointEntity: - let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "waypoint") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: String(waypointAnnotation.id)) - annotationView.tag = Int(waypointAnnotation.id) - annotationView.isEnabled = true - annotationView.canShowCallout = true - if waypointAnnotation.icon == 0 { - annotationView.glyphText = "📍" - } else { - annotationView.glyphText = String(UnicodeScalar(Int(waypointAnnotation.icon)) ?? "📍") - } - annotationView.markerTintColor = UIColor(.accentColor) - annotationView.displayPriority = .required - annotationView.titleVisibility = .adaptive - let leftIcon = UIImageView(image: annotationView.glyphText?.image()) - leftIcon.backgroundColor = UIColor(.accentColor) - annotationView.leftCalloutAccessoryView = leftIcon - let subtitle = UILabel() - if waypointAnnotation.longDescription?.count ?? 0 > 0 { - subtitle.text = (waypointAnnotation.longDescription ?? "") + "\n" - } else { - subtitle.text = "" - } - if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 { - let metersAway = waypointAnnotation.coordinate.distance(from: LocationHelper.currentLocation) - let distanceFormatter = MKDistanceFormatter() - subtitle.text! += "distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n" - } - if waypointAnnotation.created != nil { - subtitle.text! += "Created: \(waypointAnnotation.created?.formatted() ?? "Unknown") \n" - } - if waypointAnnotation.lastUpdated != nil { - subtitle.text! += "Updated: \(waypointAnnotation.lastUpdated?.formatted() ?? "Unknown") \n" - } - if waypointAnnotation.expire != nil { - subtitle.text! += "Expires: \(waypointAnnotation.expire?.formatted() ?? "Unknown") \n" - } - subtitle.numberOfLines = 0 - annotationView.detailCalloutAccessoryView = subtitle - let editIcon = UIButton(type: .detailDisclosure) - editIcon.setImage(UIImage(systemName: "square.and.pencil"), for: .normal) - annotationView.rightCalloutAccessoryView = editIcon - return annotationView - default: return nil - } - } - func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) { - switch view.annotation { - case _ as WaypointEntity: - // Only Allow Edit for waypoint annotations with a id - if view.tag > 0 { - parent.onWaypointEdit(view.tag) - } - default: break - } - } - @objc func longPressHandler(_ gesture: UILongPressGestureRecognizer) { - 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) - } - } - public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { - if let tileOverlay = overlay as? MKTileOverlay { - return MKTileOverlayRenderer(tileOverlay: tileOverlay) - } else { - if let routePolyline = overlay as? MKPolyline { - let titleString = routePolyline.title ?? "0" - let renderer = MKPolylineRenderer(polyline: routePolyline) - renderer.strokeColor = UIColor(hex: UInt32(titleString) ?? 0).lighter() - renderer.lineWidth = 8 - return renderer - } - if let polygon = overlay as? MKPolygon { - let renderer = MKPolygonRenderer(polygon: polygon) - renderer.fillColor = UIColor.purple.withAlphaComponent(0.2) - renderer.strokeColor = .purple.withAlphaComponent(0.7) - return renderer - } - return MKOverlayRenderer(overlay: overlay) - } - } - } - /// is supposed to be located in the folder with the map name - public struct DefaultTile: Hashable { - let tileName: String - let tileType: String - public init(tileName: String, tileType: String) { - self.tileName = tileName - self.tileType = tileType - } - } - public struct CustomMapOverlay: Equatable, Hashable { - let mapName: String - let tileType: String - var canReplaceMapContent: Bool - var minimumZoomLevel: Int? - var maximumZoomLevel: Int? - let defaultTile: DefaultTile? - public init( - mapName: String, - tileType: String, - canReplaceMapContent: Bool = true, // false for transparent tiles - minimumZoomLevel: Int? = nil, - maximumZoomLevel: Int? = nil, - defaultTile: DefaultTile? = nil - ) { - self.mapName = mapName - self.tileType = tileType - self.canReplaceMapContent = canReplaceMapContent - self.minimumZoomLevel = minimumZoomLevel - self.maximumZoomLevel = maximumZoomLevel - self.defaultTile = defaultTile - } - public init?( - mapName: String?, - tileType: String, - canReplaceMapContent: Bool = true, // false for transparent tiles - minimumZoomLevel: Int? = nil, - maximumZoomLevel: Int? = nil, - defaultTile: DefaultTile? = nil - ) { - if mapName == nil || mapName! == "" { - return nil - } - self.mapName = mapName! - self.tileType = tileType - self.canReplaceMapContent = canReplaceMapContent - self.minimumZoomLevel = minimumZoomLevel - self.maximumZoomLevel = maximumZoomLevel - self.defaultTile = defaultTile - } - } -} +//struct PolygonInfo: Codable { +// let stroke: String? +// let strokeWidth, strokeOpacity: Int? +// let fill: String? +// let fillOpacity: Double? +// let title, subtitle: String? +//} +// +//func degreesToRadians(_ number: Double) -> Double { +// return number * .pi / 180 +//} +//var currentMapLayer: MapLayer? +// +//struct MapViewSwiftUI: UIViewRepresentable { +// var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void +// var onWaypointEdit: (_ waypointId: Int ) -> Void +// let mapView = MKMapView() +// // Parameters +// let selectedMapLayer: MapLayer +// let selectedWeatherLayer: MapOverlayServer = UserDefaults.mapOverlayServer +// let positions: [PositionEntity] +// let waypoints: [WaypointEntity] +// let userTrackingMode: MKUserTrackingMode +// let showNodeHistory: Bool +// let showRouteLines: Bool +// let mapViewType: MKMapType = MKMapType.standard +// // Offline Map Tiles +// @AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0 +// @State private var loadedLastUpdatedLocalMapFile = 0 +// var customMapOverlay: CustomMapOverlay? +// @State private var presentCustomMapOverlayHash: CustomMapOverlay? +// // MARK: Private methods +// private func configureMap(mapView: MKMapView) { +// // Map View Parameters +// mapView.mapType = mapViewType +// mapView.addAnnotations(waypoints) +// // Do the initial map centering +// let latest = positions +// .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 +// let region = MKCoordinateRegion(center: center, span: span) +// mapView.addAnnotations(showNodeHistory ? positions : latest) +// mapView.setRegion(region, animated: true) +// // Set user (phone gps) tracking options +// mapView.setUserTrackingMode(userTrackingMode, animated: true) +// if userTrackingMode == MKUserTrackingMode.none { +// if latest.count == 1 { +// mapView.fit(annotations: showNodeHistory ? positions: latest, andShow: false) +// } else { +// mapView.fitAllAnnotations() +// } +// mapView.showsUserLocation = false +// } else { +// mapView.showsUserLocation = true +// } +// // Other MKMapView Settings +// mapView.preferredConfiguration.elevationStyle = .realistic// .flat +// mapView.pointOfInterestFilter = MKPointOfInterestFilter.excludingAll +// mapView.isPitchEnabled = true +// mapView.isRotateEnabled = true +// mapView.isScrollEnabled = true +// mapView.isZoomEnabled = true +// mapView.showsBuildings = true +// mapView.showsScale = true +// mapView.showsTraffic = true +// +// mapView.showsCompass = false +// let compass = MKCompassButton(mapView: mapView) +// compass.translatesAutoresizingMaskIntoConstraints = false +// #if targetEnvironment(macCatalyst) +// // Show the default always visible compass and the mac only controls +// compass.compassVisibility = .visible +// mapView.addSubview(compass) +// mapView.showsZoomControls = true +// mapView.showsPitchControl = true +// compass.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -115).isActive = true +// compass.bottomAnchor.constraint(equalTo: mapView.bottomAnchor, constant: -5).isActive = true +// #else +// compass.compassVisibility = .adaptive +// mapView.addSubview(compass) +// compass.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -5).isActive = true +// compass.topAnchor.constraint(equalTo: mapView.topAnchor, constant: 145).isActive = true +// #endif +// } +// private func setMapBaseLayer(mapView: MKMapView) { +// // Avoid refreshing UI if selectedLayer has not changed +// guard currentMapLayer != selectedMapLayer else { return } +// currentMapLayer = selectedMapLayer +// for overlay in mapView.overlays where overlay is MKTileOverlay { +// mapView.removeOverlay(overlay) +// } +// switch selectedMapLayer { +// case .offline: +// mapView.mapType = .standard +// let overlay = TileOverlay() +// overlay.canReplaceMapContent = false +// overlay.minimumZ = UserDefaults.mapTileServer.zoomRange.startIndex +// overlay.maximumZ = UserDefaults.mapTileServer.zoomRange.endIndex +// mapView.addOverlay(overlay, level: UserDefaults.mapTilesAboveLabels ? .aboveLabels : .aboveRoads) +// case .satellite: +// mapView.mapType = .satellite +// case .hybrid: +// mapView.mapType = .hybrid +// default: +// mapView.mapType = .standard +// } +// } +// private func setMapOverlays(mapView: MKMapView) { +// // Weather radar +// if UserDefaults.enableOverlayServer { +// let locale = Locale.current +// if locale.region?.identifier ?? "no locale" == "US" { +// let overlay = MKTileOverlay(urlTemplate: selectedWeatherLayer.tileUrl) +// overlay.canReplaceMapContent = false +// overlay.minimumZ = selectedWeatherLayer.zoomRange.startIndex +// overlay.maximumZ = selectedWeatherLayer.zoomRange.endIndex +// mapView.addOverlay(overlay, level: .aboveLabels) +// } +// } +// } +// +// func makeUIView(context: Context) -> MKMapView { +// currentMapLayer = nil +// mapView.delegate = context.coordinator +// self.configureMap(mapView: mapView) +// return mapView +// } +// func updateUIView(_ mapView: MKMapView, context: Context) { +// // Set selected map base layer +// setMapBaseLayer(mapView: mapView) +// // Set map tile server and weather overlay layers +// setMapOverlays(mapView: mapView) +// let latest = positions +// .filter { $0.latest == true } +// .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 } +// // Node Route Lines +// if showRouteLines { +// // Remove all existing PolyLine Overlays +// for overlay in mapView.overlays where overlay is MKPolyline { +// mapView.removeOverlay(overlay) +// } +// var lineIndex = 0 +// for position in latest { +// let nodePositions = positions.filter { $0.nodeCoordinate != nil && $0.nodePosition?.num ?? 0 == position.nodePosition?.num ?? -1 } +// let lineCoords = nodePositions.compactMap({(position) -> CLLocationCoordinate2D in +// return position.nodeCoordinate ?? LocationHelper.DefaultLocation +// }) +// let polyline = MKPolyline(coordinates: lineCoords, count: nodePositions.count) +// polyline.title = "\(String(position.nodePosition?.num ?? 0))" +// mapView.addOverlay(polyline, level: .aboveLabels) +// lineIndex += 1 +// // There are 18 colors for lines, start over if we are at index 17 +// if lineIndex > 17 { +// lineIndex = 0 +// } +// } +// } else { +// // Remove all existing PolyLine Overlays +// for overlay in mapView.overlays where overlay is MKPolyline { +// mapView.removeOverlay(overlay) +// } +// } +// let annotationCount = waypoints.count + (showNodeHistory ? positions.count : latest.count) +// if annotationCount != mapView.annotations.count { +// Logger.services.info("Annotation Count: \(annotationCount) Map Annotations: \(mapView.annotations.count)") +// mapView.removeAnnotations(mapView.annotations) +// mapView.addAnnotations(waypoints) +// } +// mapView.addAnnotations(showNodeHistory ? positions : latest) +// if userTrackingMode == MKUserTrackingMode.none { +// mapView.showsUserLocation = false +// if UserDefaults.enableMapRecentering { +// if latest.count == 1 { +// mapView.fit(annotations: showNodeHistory ? positions : latest, andShow: true) +// } else { +// mapView.fitAllAnnotations() +// } +// } +// } else { +// mapView.showsUserLocation = true +// } +// mapView.setUserTrackingMode(userTrackingMode, animated: true) +// } +// func makeCoordinator() -> MapCoordinator { +// return Coordinator(self) +// } +// final class MapCoordinator: NSObject, MKMapViewDelegate, UIGestureRecognizerDelegate { +// var parent: MapViewSwiftUI +// var longPressRecognizer = UILongPressGestureRecognizer() +// init(_ parent: MapViewSwiftUI) { +// 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) +// } +// func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { +// switch annotation { +// case let positionAnnotation as PositionEntity: +// let reuseID = String(positionAnnotation.nodePosition?.num ?? 0) + "-" + String(positionAnnotation.time?.timeIntervalSince1970 ?? 0) +// let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseID ) +// annotationView.tag = -1 +// annotationView.canShowCallout = true +// if positionAnnotation.latest { +// annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)).darker() +// annotationView.displayPriority = .required +// annotationView.titleVisibility = .visible +// } else { +// annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)).lighter() +// annotationView.displayPriority = .defaultHigh +// annotationView.titleVisibility = .adaptive +// } +// annotationView.tag = -1 +// annotationView.canShowCallout = true +// annotationView.titleVisibility = .adaptive +// let leftIcon = UIImageView(image: annotationView.glyphText?.image()) +// leftIcon.backgroundColor = UIColor(.indigo) +// annotationView.leftCalloutAccessoryView = leftIcon +// let subtitle = UILabel() +// subtitle.text = "Long Name: \(positionAnnotation.nodePosition?.user?.longName ?? "Unknown") \n" +// subtitle.text? += "Latitude: \(String(format: "%.5f", positionAnnotation.coordinate.latitude)) \n" +// subtitle.text! += "Longitude: \(String(format: "%.5f", positionAnnotation.coordinate.longitude)) \n" +// let distanceFormatter = MKDistanceFormatter() +// subtitle.text! += "Altitude: \(distanceFormatter.string(fromDistance: Double(positionAnnotation.altitude))) \n" +// if positionAnnotation.nodePosition?.metadata != nil { +// if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.client || +// DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.clientMute || +// DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.routerClient { +// annotationView.glyphImage = UIImage(systemName: "flipphone") +// } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.repeater { +// annotationView.glyphImage = UIImage(systemName: "repeat") +// } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.router { +// annotationView.glyphImage = UIImage(systemName: "wifi.router.fill") +// } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.tracker { +// annotationView.glyphImage = UIImage(systemName: "location.viewfinder") +// } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.sensor { +// annotationView.glyphImage = UIImage(systemName: "sensor") +// } +// let pf = PositionFlags(rawValue: Int(positionAnnotation.nodePosition?.metadata?.positionFlags ?? 3)) +// if pf.contains(.Satsinview) { +// subtitle.text! += "Sats in view: \(String(positionAnnotation.satsInView)) \n" +// } +// if pf.contains(.SeqNo) { +// subtitle.text! += "Sequence: \(String(positionAnnotation.seqNo)) \n" +// } +// if pf.contains(.Heading) { +// if parent.userTrackingMode != MKUserTrackingMode.followWithHeading { +// annotationView.glyphImage = UIImage(systemName: "location.north.fill")?.rotate(radians: Float(degreesToRadians(Double(positionAnnotation.heading)))) +// subtitle.text! += "Heading: \(String(positionAnnotation.heading)) \n" +// } else { +// annotationView.glyphImage = UIImage(systemName: "flipphone") +// } +// } +// if pf.contains(.Speed) { +// let formatter = MeasurementFormatter() +// formatter.locale = Locale.current +// if positionAnnotation.speed <= 1 { +// annotationView.glyphImage = UIImage(systemName: "hexagon") +// } +// subtitle.text! += "Speed: \(formatter.string(from: Measurement(value: Double(positionAnnotation.speed), unit: UnitSpeed.kilometersPerHour))) \n" +// } +// } else { +// // node metadata is nil +// annotationView.glyphImage = UIImage(systemName: "flipphone") +// } +// if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 { +// let metersAway = positionAnnotation.coordinate.distance(from: LocationHelper.currentLocation) +// subtitle.text! += "distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n" +// } +// subtitle.text! += positionAnnotation.time?.formatted() ?? "Unknown \n" +// subtitle.numberOfLines = 0 +// annotationView.detailCalloutAccessoryView = subtitle +// let detailsIcon = UIButton(type: .detailDisclosure) +// detailsIcon.setImage(UIImage(systemName: "trash"), for: .normal) +// annotationView.rightCalloutAccessoryView = detailsIcon +// return annotationView +// case let waypointAnnotation as WaypointEntity: +// let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "waypoint") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: String(waypointAnnotation.id)) +// annotationView.tag = Int(waypointAnnotation.id) +// annotationView.isEnabled = true +// annotationView.canShowCallout = true +// if waypointAnnotation.icon == 0 { +// annotationView.glyphText = "📍" +// } else { +// annotationView.glyphText = String(UnicodeScalar(Int(waypointAnnotation.icon)) ?? "📍") +// } +// annotationView.markerTintColor = UIColor(.accentColor) +// annotationView.displayPriority = .required +// annotationView.titleVisibility = .adaptive +// let leftIcon = UIImageView(image: annotationView.glyphText?.image()) +// leftIcon.backgroundColor = UIColor(.accentColor) +// annotationView.leftCalloutAccessoryView = leftIcon +// let subtitle = UILabel() +// if waypointAnnotation.longDescription?.count ?? 0 > 0 { +// subtitle.text = (waypointAnnotation.longDescription ?? "") + "\n" +// } else { +// subtitle.text = "" +// } +// if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 { +// let metersAway = waypointAnnotation.coordinate.distance(from: LocationHelper.currentLocation) +// let distanceFormatter = MKDistanceFormatter() +// subtitle.text! += "distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n" +// } +// if waypointAnnotation.created != nil { +// subtitle.text! += "Created: \(waypointAnnotation.created?.formatted() ?? "Unknown") \n" +// } +// if waypointAnnotation.lastUpdated != nil { +// subtitle.text! += "Updated: \(waypointAnnotation.lastUpdated?.formatted() ?? "Unknown") \n" +// } +// if waypointAnnotation.expire != nil { +// subtitle.text! += "Expires: \(waypointAnnotation.expire?.formatted() ?? "Unknown") \n" +// } +// subtitle.numberOfLines = 0 +// annotationView.detailCalloutAccessoryView = subtitle +// let editIcon = UIButton(type: .detailDisclosure) +// editIcon.setImage(UIImage(systemName: "square.and.pencil"), for: .normal) +// annotationView.rightCalloutAccessoryView = editIcon +// return annotationView +// default: return nil +// } +// } +// func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) { +// switch view.annotation { +// case _ as WaypointEntity: +// // Only Allow Edit for waypoint annotations with a id +// if view.tag > 0 { +// parent.onWaypointEdit(view.tag) +// } +// default: break +// } +// } +// @objc func longPressHandler(_ gesture: UILongPressGestureRecognizer) { +// 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) +// } +// } +// public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { +// if let tileOverlay = overlay as? MKTileOverlay { +// return MKTileOverlayRenderer(tileOverlay: tileOverlay) +// } else { +// if let routePolyline = overlay as? MKPolyline { +// let titleString = routePolyline.title ?? "0" +// let renderer = MKPolylineRenderer(polyline: routePolyline) +// renderer.strokeColor = UIColor(hex: UInt32(titleString) ?? 0).lighter() +// renderer.lineWidth = 8 +// return renderer +// } +// if let polygon = overlay as? MKPolygon { +// let renderer = MKPolygonRenderer(polygon: polygon) +// renderer.fillColor = UIColor.purple.withAlphaComponent(0.2) +// renderer.strokeColor = .purple.withAlphaComponent(0.7) +// return renderer +// } +// return MKOverlayRenderer(overlay: overlay) +// } +// } +// } +// /// is supposed to be located in the folder with the map name +// public struct DefaultTile: Hashable { +// let tileName: String +// let tileType: String +// public init(tileName: String, tileType: String) { +// self.tileName = tileName +// self.tileType = tileType +// } +// } +// public struct CustomMapOverlay: Equatable, Hashable { +// let mapName: String +// let tileType: String +// var canReplaceMapContent: Bool +// var minimumZoomLevel: Int? +// var maximumZoomLevel: Int? +// let defaultTile: DefaultTile? +// public init( +// mapName: String, +// tileType: String, +// canReplaceMapContent: Bool = true, // false for transparent tiles +// minimumZoomLevel: Int? = nil, +// maximumZoomLevel: Int? = nil, +// defaultTile: DefaultTile? = nil +// ) { +// self.mapName = mapName +// self.tileType = tileType +// self.canReplaceMapContent = canReplaceMapContent +// self.minimumZoomLevel = minimumZoomLevel +// self.maximumZoomLevel = maximumZoomLevel +// self.defaultTile = defaultTile +// } +// public init?( +// mapName: String?, +// tileType: String, +// canReplaceMapContent: Bool = true, // false for transparent tiles +// minimumZoomLevel: Int? = nil, +// maximumZoomLevel: Int? = nil, +// defaultTile: DefaultTile? = nil +// ) { +// if mapName == nil || mapName! == "" { +// return nil +// } +// self.mapName = mapName! +// self.tileType = tileType +// self.canReplaceMapContent = canReplaceMapContent +// self.minimumZoomLevel = minimumZoomLevel +// self.maximumZoomLevel = maximumZoomLevel +// self.defaultTile = defaultTile +// } +// } +//} diff --git a/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift b/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift index ed48e1a4e..456472e0c 100644 --- a/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift +++ b/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift @@ -1,266 +1,266 @@ +//// +//// WaypointFormView.swift +//// Meshtastic +//// +//// Copyright Garth Vander Houwen 1/10/23. +//// // -// WaypointFormView.swift -// Meshtastic +//import CoreLocation +//import MeshtasticProtobufs +//import OSLog +//import SwiftUI // -// Copyright Garth Vander Houwen 1/10/23. +//struct WaypointFormMapKit: View { // - -import CoreLocation -import MeshtasticProtobufs -import OSLog -import SwiftUI - -struct WaypointFormMapKit: View { - - @EnvironmentObject var bleManager: BLEManager - @Environment(\.dismiss) private var dismiss - @State var coordinate: WaypointCoordinate - @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 locked: Bool = false - @State private var lockedTo: Int64 = 0 - - var body: some View { - - Form { - let distance = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude).distance(from: CLLocation(latitude: coordinate.coordinate?.latitude ?? 0, longitude: coordinate.coordinate?.longitude ?? 0)) - Section(header: Text((coordinate.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 { - DistanceText(meters: distance) - .foregroundColor(Color.gray) - .font(.caption2) - } - } - HStack { - Text("Name") - Spacer() - TextField( - "Name", - text: $name, - axis: .vertical - ) - .foregroundColor(Color.gray) - .onChange(of: name) { - var totalBytes = name.utf8.count - // Only mess with the value if it is too big - while totalBytes > 30 { - name = String(name.dropLast()) - totalBytes = name.utf8.count - } - if totalBytes > 30 { - name = String(name.dropLast()) - } - } - } - HStack { - Text("Description") - Spacer() - TextField( - "Description", - text: $description, - axis: .vertical - ) - .foregroundColor(Color.gray) - .onChange(of: description) { - var totalBytes = description.utf8.count - // Only mess with the value if it is too big - while totalBytes > 100 { - description = String(description.dropLast()) - totalBytes = description.utf8.count - } - } - } - HStack { - Text("Icon") - Spacer() - EmojiOnlyTextField(text: $icon, placeholder: "Select an emoji") - .font(.title) - .focused($iconIsFocused) - .onChange(of: icon) { _, value in - - // If you have anything other than emojis in your string make it empty - if !value.onlyEmojis() { - icon = "" - } - // If a second emoji is entered delete the first one - if value.count >= 1 { - - if value.count > 1 { - let index = value.index(value.startIndex, offsetBy: 1) - icon = String(value[index]) - } - iconIsFocused = false - } - } - - } - Toggle(isOn: $expires) { - Label("Expires", systemImage: "clock.badge.xmark") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - if expires { - DatePicker("Expire", selection: $expire, in: Date.now...) - .datePickerStyle(.compact) - .font(.callout) - } - Toggle(isOn: $locked) { - Label("Locked", systemImage: "lock") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - } - } - HStack { - 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 - } else { - // New waypoint - newWaypoint.id = UInt32.random(in: UInt32(UInt8.max).. 0 ? name : "Dropped Pin" - newWaypoint.description_p = description - // 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 { - newWaypoint.lockedTo = UInt32(lockedTo) - } - } - if expires { - newWaypoint.expire = UInt32(expire.timeIntervalSince1970) - } else { - newWaypoint.expire = 0 - } - if bleManager.sendWaypoint(waypoint: newWaypoint) { - dismiss() - } else { - dismiss() - Logger.mesh.error("Send waypoint failed") - } - } label: { - Label("Send", systemImage: "arrow.up") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.regular) - .disabled(bleManager.connectedPeripheral == nil) - .padding(.bottom) - - Button(role: .cancel) { - dismiss() - } label: { - Label("cancel", systemImage: "x.circle") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.regular) - .padding(.bottom) - - if coordinate.waypointId > 0 { - - Menu { - Button("For me", action: { - let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context) - bleManager.context.delete(waypoint) - do { - try bleManager.context.save() - } catch { - bleManager.context.rollback() - } - dismiss() }) - Button("For everyone", action: { - var newWaypoint = Waypoint() - - if coordinate.waypointId > 0 { - newWaypoint.id = UInt32(coordinate.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) - // 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 { - newWaypoint.lockedTo = UInt32(lockedTo) - } - } - newWaypoint.expire = 1 - if bleManager.sendWaypoint(waypoint: newWaypoint) { - dismiss() - } else { - dismiss() - Logger.mesh.error("Send waypoint failed") - } - }) - } - label: { - Label("delete", systemImage: "trash") - .foregroundColor(.red) - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.regular) - .padding(.bottom) - } - } - .onAppear { - if coordinate.waypointId > 0 { - let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context) - name = waypoint.name ?? "Dropped Pin" - description = waypoint.longDescription ?? "" - icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍") - latitude = Double(waypoint.latitudeI) / 1e7 - longitude = Double(waypoint.longitudeI) / 1e7 - if waypoint.expire != nil { - expires = true - expire = waypoint.expire ?? Date() - } else { - expires = false - } - if waypoint.locked > 0 { - locked = true - lockedTo = waypoint.locked - } - } else { - name = "" - description = "" - locked = false - expires = false - expire = Date.now.addingTimeInterval(60 * 480) - icon = "📍" - latitude = coordinate.coordinate?.latitude ?? 0 - longitude = coordinate.coordinate?.longitude ?? 0 - } - } - } -} +// @EnvironmentObject var bleManager: BLEManager +// @Environment(\.dismiss) private var dismiss +// @State var coordinate: WaypointCoordinate +// @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 locked: Bool = false +// @State private var lockedTo: Int64 = 0 +// +// var body: some View { +// +// Form { +// let distance = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude).distance(from: CLLocation(latitude: coordinate.coordinate?.latitude ?? 0, longitude: coordinate.coordinate?.longitude ?? 0)) +// Section(header: Text((coordinate.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 { +// DistanceText(meters: distance) +// .foregroundColor(Color.gray) +// .font(.caption2) +// } +// } +// HStack { +// Text("Name") +// Spacer() +// TextField( +// "Name", +// text: $name, +// axis: .vertical +// ) +// .foregroundColor(Color.gray) +// .onChange(of: name) { +// var totalBytes = name.utf8.count +// // Only mess with the value if it is too big +// while totalBytes > 30 { +// name = String(name.dropLast()) +// totalBytes = name.utf8.count +// } +// if totalBytes > 30 { +// name = String(name.dropLast()) +// } +// } +// } +// HStack { +// Text("Description") +// Spacer() +// TextField( +// "Description", +// text: $description, +// axis: .vertical +// ) +// .foregroundColor(Color.gray) +// .onChange(of: description) { +// var totalBytes = description.utf8.count +// // Only mess with the value if it is too big +// while totalBytes > 100 { +// description = String(description.dropLast()) +// totalBytes = description.utf8.count +// } +// } +// } +// HStack { +// Text("Icon") +// Spacer() +// EmojiOnlyTextField(text: $icon, placeholder: "Select an emoji") +// .font(.title) +// .focused($iconIsFocused) +// .onChange(of: icon) { _, value in +// +// // If you have anything other than emojis in your string make it empty +// if !value.onlyEmojis() { +// icon = "" +// } +// // If a second emoji is entered delete the first one +// if value.count >= 1 { +// +// if value.count > 1 { +// let index = value.index(value.startIndex, offsetBy: 1) +// icon = String(value[index]) +// } +// iconIsFocused = false +// } +// } +// +// } +// Toggle(isOn: $expires) { +// Label("Expires", systemImage: "clock.badge.xmark") +// } +// .toggleStyle(SwitchToggleStyle(tint: .accentColor)) +// if expires { +// DatePicker("Expire", selection: $expire, in: Date.now...) +// .datePickerStyle(.compact) +// .font(.callout) +// } +// Toggle(isOn: $locked) { +// Label("Locked", systemImage: "lock") +// } +// .toggleStyle(SwitchToggleStyle(tint: .accentColor)) +// } +// } +// HStack { +// 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 +// } else { +// // New waypoint +// newWaypoint.id = UInt32.random(in: UInt32(UInt8.max).. 0 ? name : "Dropped Pin" +// newWaypoint.description_p = description +// // 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 { +// newWaypoint.lockedTo = UInt32(lockedTo) +// } +// } +// if expires { +// newWaypoint.expire = UInt32(expire.timeIntervalSince1970) +// } else { +// newWaypoint.expire = 0 +// } +// if bleManager.sendWaypoint(waypoint: newWaypoint) { +// dismiss() +// } else { +// dismiss() +// Logger.mesh.error("Send waypoint failed") +// } +// } label: { +// Label("Send", systemImage: "arrow.up") +// } +// .buttonStyle(.bordered) +// .buttonBorderShape(.capsule) +// .controlSize(.regular) +// .disabled(bleManager.connectedPeripheral == nil) +// .padding(.bottom) +// +// Button(role: .cancel) { +// dismiss() +// } label: { +// Label("cancel", systemImage: "x.circle") +// } +// .buttonStyle(.bordered) +// .buttonBorderShape(.capsule) +// .controlSize(.regular) +// .padding(.bottom) +// +// if coordinate.waypointId > 0 { +// +// Menu { +// Button("For me", action: { +// let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context) +// bleManager.context.delete(waypoint) +// do { +// try bleManager.context.save() +// } catch { +// bleManager.context.rollback() +// } +// dismiss() }) +// Button("For everyone", action: { +// var newWaypoint = Waypoint() +// +// if coordinate.waypointId > 0 { +// newWaypoint.id = UInt32(coordinate.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) +// // 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 { +// newWaypoint.lockedTo = UInt32(lockedTo) +// } +// } +// newWaypoint.expire = 1 +// if bleManager.sendWaypoint(waypoint: newWaypoint) { +// dismiss() +// } else { +// dismiss() +// Logger.mesh.error("Send waypoint failed") +// } +// }) +// } +// label: { +// Label("delete", systemImage: "trash") +// .foregroundColor(.red) +// } +// .buttonStyle(.bordered) +// .buttonBorderShape(.capsule) +// .controlSize(.regular) +// .padding(.bottom) +// } +// } +// .onAppear { +// if coordinate.waypointId > 0 { +// let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context) +// name = waypoint.name ?? "Dropped Pin" +// description = waypoint.longDescription ?? "" +// icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍") +// latitude = Double(waypoint.latitudeI) / 1e7 +// longitude = Double(waypoint.longitudeI) / 1e7 +// if waypoint.expire != nil { +// expires = true +// expire = waypoint.expire ?? Date() +// } else { +// expires = false +// } +// if waypoint.locked > 0 { +// locked = true +// lockedTo = waypoint.locked +// } +// } else { +// name = "" +// description = "" +// locked = false +// expires = false +// expire = Date.now.addingTimeInterval(60 * 480) +// icon = "📍" +// latitude = coordinate.coordinate?.latitude ?? 0 +// longitude = coordinate.coordinate?.longitude ?? 0 +// } +// } +// } +//} diff --git a/Settings.bundle/Root.plist b/Settings.bundle/Root.plist index 7fb7c0ccc..e67ebad7a 100644 --- a/Settings.bundle/Root.plist +++ b/Settings.bundle/Root.plist @@ -68,16 +68,6 @@ DefaultValue - - Type - PSToggleSwitchSpecifier - Title - Use Legacy Mesh Map - Key - mapUseLegacy - DefaultValue - - Type PSGroupSpecifier