From 550add228c33fc33751151d81f90a7693aaa1b93 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 14 Mar 2024 23:05:05 -0700 Subject: [PATCH] Move node map into map content view Dont change channel for node infos directed at other nodes --- Meshtastic.xcodeproj/project.pbxproj | 4 + Meshtastic/Persistence/UpdateCoreData.swift | 9 +- .../Nodes/Helpers/Map/NodeMapContent.swift | 166 ++++++++++++++++++ .../Nodes/Helpers/Map/NodeMapSwiftUI.swift | 115 +----------- 4 files changed, 177 insertions(+), 117 deletions(-) create mode 100644 Meshtastic/Views/Nodes/Helpers/Map/NodeMapContent.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 4dac1356..6ae7e268 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -110,6 +110,7 @@ DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */; }; DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860D26F69BAE00DC5189 /* NodeMap.swift */; }; DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; }; + DD93800B2BA3F968008BEC06 /* NodeMapContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD93800A2BA3F968008BEC06 /* NodeMapContent.swift */; }; DD94B7402ACCE3BE00DCD1D1 /* MapSettingsForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */; }; DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */; }; DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */; }; @@ -360,6 +361,7 @@ DD90860A26F645B700DC5189 /* Meshtastic.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Meshtastic.entitlements; sourceTree = ""; }; DD90860D26F69BAE00DC5189 /* NodeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMap.swift; sourceTree = ""; }; DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = ""; }; + DD93800A2BA3F968008BEC06 /* NodeMapContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMapContent.swift; sourceTree = ""; }; DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSettingsForm.swift; sourceTree = ""; }; DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiOnlyTextField.swift; sourceTree = ""; }; DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointFormMapKit.swift; sourceTree = ""; }; @@ -748,6 +750,7 @@ DDAD49EB2AFAE82500B4425D /* Map */ = { isa = PBXGroup; children = ( + DD93800A2BA3F968008BEC06 /* NodeMapContent.swift */, DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */, DDB6CCFA2AAF805100945AF6 /* NodeMapSwiftUI.swift */, DD13AA482AB73BF400BA0C98 /* PositionPopover.swift */, @@ -1365,6 +1368,7 @@ DD5E5205298EE33B00D21B61 /* mesh.pb.swift in Sources */, DDF6B2482A9AEBF500BA6931 /* StoreForwardConfig.swift in Sources */, DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */, + DD93800B2BA3F968008BEC06 /* NodeMapContent.swift in Sources */, DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */, DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */, DDDB444429F8A8DD00EE2349 /* Float.swift in Sources */, diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index ee6ceac7..121cab33 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -144,9 +144,10 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) newNode.snr = packet.rxSnr newNode.rssi = packet.rxRssi newNode.viaMqtt = packet.viaMqtt - newNode.channel = Int32(packet.channel) + if packet.to == 4294967295 || packet.to == UserDefaults.preferredPeripheralNum { + newNode.channel = Int32(packet.channel) + } if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { - newNode.channel = Int32(nodeInfoMessage.channel) newNode.hopsAway = Int32(truncatingIfNeeded: nodeInfoMessage.hopsAway) } if let newUserMessage = try? User(serializedData: packet.decoded.payload) { @@ -212,7 +213,9 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].snr = packet.rxSnr fetchedNode[0].rssi = packet.rxRssi fetchedNode[0].viaMqtt = packet.viaMqtt - fetchedNode[0].channel = Int32(packet.channel) + if packet.to == 4294967295 || packet.to == UserDefaults.preferredPeripheralNum { + fetchedNode[0].channel = Int32(packet.channel) + } if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { fetchedNode[0].channel = Int32(nodeInfoMessage.channel) diff --git a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapContent.swift new file mode 100644 index 00000000..2c15e9de --- /dev/null +++ b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapContent.swift @@ -0,0 +1,166 @@ +// +// RouteLines.swift +// Meshtastic +// +// Created by Garth Vander Houwen on 3/14/24. +// +import SwiftUI +import MapKit + +@available(iOS 17.0, macOS 14.0, *) +struct NodeMapContent: MapContent { + + @ObservedObject var node: NodeInfoEntity + @State var showUserLocation: Bool = false + @State var positions: [PositionEntity] = [] + /// Map State User Defaults + @AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false + @AppStorage("meshMapShowRouteLines") private var showRouteLines = false + @AppStorage("enableMapConvexHull") private var showConvexHull = false + @AppStorage("enableMapTraffic") private var showTraffic: Bool = false + @AppStorage("enableMapPointsOfInterest") private var showPointsOfInterest: Bool = false + @AppStorage("mapLayer") private var selectedMapLayer: MapLayer = .hybrid + // Map Configuration + @Namespace var mapScope + @State var mapStyle: MapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: .all, showsTraffic: true) + @State var position = MapCameraPosition.automatic + @State var scene: MKLookAroundScene? + @State var isLookingAround = false + @State var isShowingAltitude = false + @State var isEditingSettings = false + @State var selectedPosition: PositionEntity? + @State var showWaypoints = false + @State var selectedWaypoint: WaypointEntity? + @State var isMeshMap = false + + //let region: MKCoordinateRegion + + + @MapContentBuilder + var nodeMap: some MapContent { + let positionArray = node.positions?.array as? [PositionEntity] ?? [] + let lineCoords = positionArray.compactMap({(position) -> CLLocationCoordinate2D in + return position.nodeCoordinate ?? LocationsHandler.DefaultLocation + }) + /// Node Color from node.num + let nodeColor = UIColor(hex: UInt32(node.num)) + + + /// Node Annotations + ForEach(positionArray, id: \.id) { position in + let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 771)) + let headingDegrees = Angle.degrees(Double(position.heading)) + /// Reduced Precision Map Circle + if position.latest && 11...16 ~= position.precisionBits { + let pp = PositionPrecision(rawValue: Int(position.precisionBits)) + let radius : CLLocationDistance = pp?.precisionMeters ?? 0 + if radius > 0.0 { + MapCircle(center: position.coordinate, radius: radius) + .foregroundStyle(Color(nodeColor).opacity(0.25)) + .stroke(.white, lineWidth: 2) + } + } + if showConvexHull { + if lineCoords.count > 0 { + let hull = lineCoords.getConvexHull() + MapPolygon(coordinates: hull) + .stroke(Color(nodeColor.darker()), lineWidth: 3) + .foregroundStyle(Color(nodeColor).opacity(0.4)) + } + } + /// Route Lines + if showRouteLines { + let gradient = LinearGradient( + colors: [Color(nodeColor.lighter().lighter().lighter()), Color(nodeColor.lighter()), Color(nodeColor)], + startPoint: .leading, endPoint: .trailing + ) + let dashed = StrokeStyle( + lineWidth: 3, + lineCap: .round, lineJoin: .round, dash: [10, 10] + ) + MapPolyline(coordinates: lineCoords) + .stroke(gradient, style: dashed) + } + + /// Node Annotations + ForEach(positionArray, id: \.id) { position in + Annotation(position.latest ? node.user?.shortName ?? "?": "", coordinate: position.coordinate) { + LazyVStack { + if position.latest { + ZStack { + Circle() + .fill(Color(nodeColor.lighter()).opacity(0.4).shadow(.drop(color: Color(nodeColor).isLight() ? .black : .white, radius: 5))) + .foregroundStyle(Color(nodeColor.lighter()).opacity(0.3)) + .frame(width: 50, height: 50) + if pf.contains(.Heading) { + Image(systemName: pf.contains(.Speed) && position.speed > 1 ? "location.north" : "octagon") + .symbolEffect(.pulse.byLayer) + .padding(5) + .foregroundStyle(Color(nodeColor).isLight() ? .black : .white) + .background(Color(nodeColor.darker())) + .clipShape(Circle()) + .rotationEffect(headingDegrees) + .onTapGesture { + selectedPosition = (selectedPosition == position ? nil : position) + } + .popover(item: $selectedPosition) { selection in + PositionPopover(position: selection) + .padding() + .opacity(0.8) + .presentationCompactAdaptation(.popover) + } + + } else { + Image(systemName: "flipphone") + .symbolEffect(.pulse.byLayer) + .padding(5) + .foregroundStyle(Color(nodeColor).isLight() ? .black : .white) + .background(Color(UIColor(hex: UInt32(node.num)).darker())) + .clipShape(Circle()) + .onTapGesture { + selectedPosition = (selectedPosition == position ? nil : position) + } + .popover(item: $selectedPosition) { selection in + PositionPopover(position: selection) + .padding() + .opacity(0.8) + .presentationCompactAdaptation(.popover) + } + } + } + } else { + if showNodeHistory { + if pf.contains(.Heading) { + Image(systemName: "location.north.circle") + .resizable() + .scaledToFit() + .foregroundStyle(Color(UIColor(hex: UInt32(node.num))).isLight() ? .black : .white) + .background(Color(UIColor(hex: UInt32(node.num)))) + .clipShape(Circle()) + .rotationEffect(headingDegrees) + .frame(width: 16, height: 16) + + } else { + Circle() + .fill(Color(UIColor(hex: UInt32(node.num)))) + .strokeBorder(Color(UIColor(hex: UInt32(node.num))).isLight() ? .black : .white ,lineWidth: 2) + .frame(width: 12, height: 12) + } + } + } + } + } + .tag(position.time) + .annotationTitles(.automatic) + .annotationSubtitles(.automatic) + } + } + } + + @MapContentBuilder + var body: some MapContent { + if node.positions?.count ?? 0 > 0 { + nodeMap + } + } +} diff --git a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift index 91ca6db4..3b2b5d31 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift @@ -57,120 +57,7 @@ struct NodeMapSwiftUI: View { ZStack { MapReader { reader in Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) { - /// Node Color from node.num - let nodeColor = UIColor(hex: UInt32(node.num)) - /// Convex Hull - if showConvexHull { - if lineCoords.count > 0 { - let hull = lineCoords.getConvexHull() - MapPolygon(coordinates: hull) - .stroke(Color(nodeColor.darker()), lineWidth: 3) - .foregroundStyle(Color(nodeColor).opacity(0.4)) - } - } - /// Route Lines - if showRouteLines { - let gradient = LinearGradient( - colors: [Color(nodeColor.lighter().lighter().lighter()), Color(nodeColor.lighter()), Color(nodeColor)], - startPoint: .leading, endPoint: .trailing - ) - let dashed = StrokeStyle( - lineWidth: 3, - lineCap: .round, lineJoin: .round, dash: [10, 10] - ) - MapPolyline(coordinates: lineCoords) - .stroke(gradient, style: dashed) - } - - - - /// Node Annotations - ForEach(positionArray, id: \.id) { position in - let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 771)) - let headingDegrees = Angle.degrees(Double(position.heading)) -// /// Reduced Precision Map Circle -// if position.latest && 11...16 ~= position.precisionBits { -// let pp = PositionPrecision(rawValue: Int(position.precisionBits)) -// let radius : CLLocationDistance = pp?.precisionMeters ?? 0 -// if radius > 0.0 { -// MapCircle(center: position.coordinate, radius: radius) -// .foregroundStyle(Color(nodeColor).opacity(0.25)) -// .stroke(.white, lineWidth: 2) -// } -// } -// Annotation(position.latest ? node.user?.shortName ?? "?": "", coordinate: position.coordinate) { -// LazyVStack { -// if position.latest { -// ZStack { -// Circle() -// .fill(Color(nodeColor.lighter()).opacity(0.4).shadow(.drop(color: Color(nodeColor).isLight() ? .black : .white, radius: 5))) -// .foregroundStyle(Color(nodeColor.lighter()).opacity(0.3)) -// .frame(width: 50, height: 50) -// if pf.contains(.Heading) { -// Image(systemName: pf.contains(.Speed) && position.speed > 1 ? "location.north" : "octagon") -// .symbolEffect(.pulse.byLayer) -// .padding(5) -// .foregroundStyle(Color(nodeColor).isLight() ? .black : .white) -// .background(Color(nodeColor.darker())) -// .clipShape(Circle()) -// .rotationEffect(headingDegrees) -// .onTapGesture { -// selectedPosition = (selectedPosition == position ? nil : position) -// } -// .popover(item: $selectedPosition) { selection in -// PositionPopover(position: selection) -// .padding() -// .opacity(0.8) -// .presentationCompactAdaptation(.popover) -// } -// -// } else { -// Image(systemName: "flipphone") -// .symbolEffect(.pulse.byLayer) -// .padding(5) -// .foregroundStyle(Color(nodeColor).isLight() ? .black : .white) -// .background(Color(UIColor(hex: UInt32(node.num)).darker())) -// .clipShape(Circle()) -// .onTapGesture { -// selectedPosition = (selectedPosition == position ? nil : position) -// } -// .popover(item: $selectedPosition) { selection in -// PositionPopover(position: selection) -// .padding() -// .opacity(0.8) -// .presentationCompactAdaptation(.popover) -// } -// -// } -// } -// } else { -// if showNodeHistory { -// if pf.contains(.Heading) { -// Image(systemName: "location.north.circle") -// .resizable() -// .scaledToFit() -// .foregroundStyle(Color(UIColor(hex: UInt32(node.num))).isLight() ? .black : .white) -// .background(Color(UIColor(hex: UInt32(node.num)))) -// .clipShape(Circle()) -// .rotationEffect(headingDegrees) -// .frame(width: 16, height: 16) -// -// } else { -// Circle() -// .fill(Color(UIColor(hex: UInt32(node.num)))) -// .strokeBorder(Color(UIColor(hex: UInt32(node.num))).isLight() ? .black : .white ,lineWidth: 2) -// .frame(width: 12, height: 12) -// } -// } -// } - - - } -// } -// .tag(position.time) -// .annotationTitles(.automatic) -// .annotationSubtitles(.automatic) -// } + NodeMapContent(node: node) } .mapScope(mapScope) .mapStyle(mapStyle)