From 4014c807582c400bfce48e899cd1f4c42c9d4f52 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 12 Sep 2023 00:02:31 -0700 Subject: [PATCH] Mapkit for swiftui --- Meshtastic.xcodeproj/project.pbxproj | 8 +- .../Views/Nodes/Helpers/NodeDetail.swift | 7 +- .../Views/Nodes/Helpers/NodeMapSwiftUI.swift | 110 ++++++++++++++++++ Meshtastic/Views/Nodes/NodeList.swift | 10 -- 4 files changed, 122 insertions(+), 13 deletions(-) create mode 100644 Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 720af1f1..1cb3e9a1 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -105,6 +105,7 @@ DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABE128B13FB500384BA1 /* PositionConfigEnums.swift */; }; DDB6ABE428B13FFF00384BA1 /* DisplayEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABE328B13FFF00384BA1 /* DisplayEnums.swift */; }; DDB6ABE628B1406100384BA1 /* LoraConfigEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABE528B1406100384BA1 /* LoraConfigEnums.swift */; }; + DDB6CCFB2AAF805100945AF6 /* NodeMapSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6CCFA2AAF805100945AF6 /* NodeMapSwiftUI.swift */; }; DDB75A0F2A05920E006ED576 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A0E2A05920E006ED576 /* FileManager.swift */; }; DDB75A112A059258006ED576 /* Url.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A102A059258006ED576 /* Url.swift */; }; DDB75A142A0593E2006ED576 /* OfflineTileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A132A0593E2006ED576 /* OfflineTileManager.swift */; }; @@ -313,6 +314,7 @@ DDB6ABE128B13FB500384BA1 /* PositionConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionConfigEnums.swift; sourceTree = ""; }; DDB6ABE328B13FFF00384BA1 /* DisplayEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayEnums.swift; sourceTree = ""; }; DDB6ABE528B1406100384BA1 /* LoraConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoraConfigEnums.swift; sourceTree = ""; }; + DDB6CCFA2AAF805100945AF6 /* NodeMapSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMapSwiftUI.swift; sourceTree = ""; }; DDB759E12A04B264006ED576 /* MeshtasticDataModelV12.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV12.xcdatamodel; sourceTree = ""; }; DDB75A0E2A05920E006ED576 /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = ""; }; DDB75A102A059258006ED576 /* Url.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Url.swift; sourceTree = ""; }; @@ -823,6 +825,7 @@ DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */, DDDB26412AABF655003AFCB7 /* NodeListItem.swift */, DDDB26472AACD6D1003AFCB7 /* NodeMapControl.swift */, + DDB6CCFA2AAF805100945AF6 /* NodeMapSwiftUI.swift */, ); path = Helpers; sourceTree = ""; @@ -1181,6 +1184,7 @@ DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */, DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */, DDDB443D29F6592F00EE2349 /* NetworkManager.swift in Sources */, + DDB6CCFB2AAF805100945AF6 /* NodeMapSwiftUI.swift in Sources */, DD73FD1128750779000852D6 /* PositionLog.swift in Sources */, DD5E5206298EE33B00D21B61 /* localonly.pb.swift in Sources */, DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */, @@ -1402,7 +1406,7 @@ CODE_SIGN_ENTITLEMENTS = Meshtastic/Meshtastic.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 655; DEVELOPMENT_ASSET_PATHS = "\"Meshtastic/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; @@ -1436,7 +1440,7 @@ CODE_SIGN_ENTITLEMENTS = Meshtastic/Meshtastic.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 655; DEVELOPMENT_ASSET_PATHS = "\"Meshtastic/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 3ee69c79..a5b45aa9 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -41,7 +41,12 @@ struct NodeDetail: View { .disabled(!node.hasDeviceMetrics) Divider() NavigationLink { - NodeMapControl(node: node) + if #available (iOS 17, macOS 14, *) { + NodeMapSwiftUI(node: node) + } else { + NodeMapControl(node: node) + } + } label: { Image(systemName: "map") .symbolRenderingMode(.hierarchical) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift new file mode 100644 index 00000000..d430c170 --- /dev/null +++ b/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift @@ -0,0 +1,110 @@ +// +// NodeMapSwiftUI.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 9/11/23. +// + +import SwiftUI +import CoreLocation +import MapKit +import WeatherKit + +@available(iOS 17.0, *) +struct NodeMapSwiftUI: View { + + @Namespace var mapScope + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + @AppStorage("meshMapType") private var meshMapType = 0 + @AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false + @AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false + @State private var selectedMapLayer: MapLayer = .standard + @State var waypointCoordinate: WaypointCoordinate? + @State var editingWaypoint: Int = 0 + @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay( + mapName: "offlinemap", + tileType: "png", + canReplaceMapContent: true + ) + @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], + predicate: NSPredicate( + format: "expire == nil || expire >= %@", Date() as NSDate + ), animation: .none) + private var waypoints: FetchedResults + @ObservedObject var node: NodeInfoEntity + + var body: some View { + let positionArray = node.positions?.array as? [PositionEntity] ?? [] + let mostRecent = node.positions?.lastObject as? PositionEntity + if mostRecent != nil { + NavigationStack { + ZStack { + + Map(initialPosition: .camera(MapCamera(centerCoordinate: mostRecent!.coordinate, distance: 500, heading: 90, pitch: 60)), + bounds: MapCameraBounds(minimumDistance: 100, maximumDistance: 3500), + scope: mapScope) { + ForEach(positionArray, id: \.id) { position in + Annotation(position.latest ? node.user?.shortName ?? "?" : "", coordinate: position.coordinate) { + ZStack { + if position.latest { + Circle() + .foregroundStyle(Color(UIColor(hex: UInt32(node.num)).darker()).opacity(0.4)) + .frame(width: 60, height: 60) + Image(systemName: "flipphone") + .symbolEffect(.pulse.byLayer) + .padding(7) + .foregroundStyle(Color(UIColor(hex: UInt32(node.num)).lighter()).isLight() ? .black : .white) + .background(Color(UIColor(hex: UInt32(node.num)).darker())) + .clipShape(Circle()) + } else { + if showNodeHistory { + Image(systemName: "mappin.circle") + .padding(2) + .foregroundStyle(Color(UIColor(hex: UInt32(node.num)).lighter()).isLight() ? .black : .white) + .background(Color(UIColor(hex: UInt32(node.num)).lighter())) + .clipShape(Circle()) + } + } + + } + } + .tag(node.num) + +// Marker(node.user?.shortName ?? "?", systemImage: "mappin.and.ellipse", coordinate: position.coordinate) +// .tint(position.latest ? Color(UIColor(hex: UInt32(node.num)).darker()) : Color(UIColor(hex: UInt32(node.num)).lighter())) +// .tag(node.num) + } + } + .mapScope(mapScope) + .mapStyle(.imagery(elevation: .realistic)) + .mapControls { + MapScaleView(scope: mapScope) + .mapControlVisibility(.visible) + MapUserLocationButton(scope: mapScope) + .mapControlVisibility(.visible) + MapPitchToggle(scope: mapScope) + .mapControlVisibility(.visible) + #if targetEnvironment(macCatalyst) + MapZoomStepper(scope: mapScope) + .mapControlVisibility(.visible) + MapPitchSlider(scope: mapScope) + .mapControlVisibility(.visible) + #endif + MapCompass(scope: mapScope) + .mapControlVisibility(.visible) + } + .controlSize(.large) + } + .navigationBarTitle(String("Node Map " + (node.user?.shortName ?? "unknown".localized)), displayMode: .inline) + .navigationBarItems(trailing: + ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") + }) + } + } + } +} diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index acae0cfc..59108fa4 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -7,16 +7,6 @@ import SwiftUI import CoreLocation -enum SelectedDetail { - case positionLog - case nodeMap - case deviceMetricsLog - case environmentMetricsLog - case detectionSensorLog -} - - - struct NodeList: View { @State private var columnVisibility = NavigationSplitViewVisibility.all