From a0e8caf6fbabbe6b2b6db28e272e9fb923a95233 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 20 Sep 2023 22:26:21 -0700 Subject: [PATCH] Janky waypoint popovers --- Meshtastic.xcodeproj/project.pbxproj | 8 +- Meshtastic/Helpers/BLEManager.swift | 17 +--- .../Views/Nodes/Helpers/NodeMapSwiftUI.swift | 51 ++++++++-- .../Views/Nodes/Helpers/WaypointPopover.swift | 98 +++++++++++++++++++ 4 files changed, 147 insertions(+), 27 deletions(-) create mode 100644 Meshtastic/Views/Nodes/Helpers/WaypointPopover.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index e7d6a2eb..968271ec 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -68,6 +68,7 @@ DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; }; DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; }; DD73FD1128750779000852D6 /* PositionLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FD1028750779000852D6 /* PositionLog.swift */; }; + DD760AAE2ABAC706002C022E /* WaypointPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD760AAD2ABAC706002C022E /* WaypointPopover.swift */; }; DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */; }; DD77093B2AA1ABB8007A8BF0 /* BluetoothTips.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD77093A2AA1ABB8007A8BF0 /* BluetoothTips.swift */; }; DD77093D2AA1AFA3007A8BF0 /* ChannelTips.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD77093C2AA1AFA3007A8BF0 /* ChannelTips.swift */; }; @@ -166,7 +167,6 @@ DDDE5A1329AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; }; DDDE5A1429AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; }; DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */; }; - DDE179302ABA2482005777A8 /* LocationDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1792F2ABA2482005777A8 /* LocationDataManager.swift */; }; DDF6B2482A9AEBF500BA6931 /* StoreForward.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */; }; @@ -277,6 +277,7 @@ DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = ""; }; DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = ""; }; DD73FD1028750779000852D6 /* PositionLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionLog.swift; sourceTree = ""; }; + DD760AAD2ABAC706002C022E /* WaypointPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointPopover.swift; sourceTree = ""; }; DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMetricsLog.swift; sourceTree = ""; }; DD77093A2AA1ABB8007A8BF0 /* BluetoothTips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothTips.swift; sourceTree = ""; }; DD77093C2AA1AFA3007A8BF0 /* ChannelTips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelTips.swift; sourceTree = ""; }; @@ -390,7 +391,6 @@ DDDE5A1229AFEAB900490C6C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; DDDEE5E229DBE43E00A8E078 /* MeshtasticDataModelV11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV11.xcdatamodel; sourceTree = ""; }; DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = ""; }; - DDE1792F2ABA2482005777A8 /* LocationDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDataManager.swift; sourceTree = ""; }; DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = ""; }; DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV17.xcdatamodel; sourceTree = ""; }; DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreForward.swift; sourceTree = ""; }; @@ -793,7 +793,6 @@ DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */, DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */, DDDB443C29F6592F00EE2349 /* NetworkManager.swift */, - DDE1792F2ABA2482005777A8 /* LocationDataManager.swift */, ); path = Helpers; sourceTree = ""; @@ -825,6 +824,7 @@ DDDB26472AACD6D1003AFCB7 /* NodeMapControl.swift */, DDB6CCFA2AAF805100945AF6 /* NodeMapSwiftUI.swift */, DD13AA482AB73BF400BA0C98 /* PositionPopover.swift */, + DD760AAD2ABAC706002C022E /* WaypointPopover.swift */, ); path = Helpers; sourceTree = ""; @@ -1075,6 +1075,7 @@ DDDB443629F6287000EE2349 /* MapButtons.swift in Sources */, DD5D0A9C2931B9F200F7EA61 /* EthernetModes.swift in Sources */, 6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */, + DD760AAE2ABAC706002C022E /* WaypointPopover.swift in Sources */, DD5E5203298EE33B00D21B61 /* config.pb.swift in Sources */, DD798B072915928D005217CD /* ChannelMessageList.swift in Sources */, DDA6B2EB28420A7B003E8C16 /* NodeAnnotation.swift in Sources */, @@ -1183,7 +1184,6 @@ DD5E520E298EE33B00D21B61 /* mqtt.pb.swift in Sources */, DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */, DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */, - DDE179302ABA2482005777A8 /* LocationDataManager.swift in Sources */, DDDB443D29F6592F00EE2349 /* NetworkManager.swift in Sources */, DDB6CCFB2AAF805100945AF6 /* NodeMapSwiftUI.swift in Sources */, DD73FD1128750779000852D6 /* PositionLog.swift in Sources */, diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 0251a62a..cc309864 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -841,22 +841,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if fromNodeNum <= 0 || LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) == 0.0 { return false } - -// if smartPosition { -// if lastPosition != nil { -// let connectedNode = getNodeInfo(id: connectedPeripheral?.num ?? 0, context: context!) -// if connectedNode?.positionConfig?.smartPositionEnabled ?? false { -// if lastPosition!.distance(from: LocationHelper.currentLocation) < Double(connectedNode?.positionConfig?.broadcastSmartMinimumDistance ?? 50) { -// return false -// } -// } -// } -// } -// lastPosition = LocationHelper.currentLocation -// var locationHelper = LocationHelper() var positionPacket = Position() - positionPacket.latitudeI = Int32((locationFetcher.lastKnownLocation?.latitude ?? 0) * 1e7) - positionPacket.longitudeI = Int32((locationFetcher.manager.location?.coordinate.longitude ?? 0) * 1e7) + 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) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift index bbef8357..f7fc6eed 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift @@ -11,7 +11,6 @@ import MapKit import WeatherKit @available(iOS 17.0, macOS 14.0, *) - struct NodeMapSwiftUI: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -19,7 +18,7 @@ struct NodeMapSwiftUI: View { @ObservedObject var node: NodeInfoEntity @State var showUserLocation: Bool = false @State var positions: [PositionEntity] = [] - @State var waypoints: [WaypointEntity] = [] + //@State var waypoints: [WaypointEntity] = [] /// Map State User Defaults @AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false @AppStorage("meshMapShowRouteLines") private var showRouteLines = false @@ -35,7 +34,18 @@ struct NodeMapSwiftUI: View { @State private var isLookingAround = false @State private var isEditingSettings = false @State private var selected: PositionEntity? + @State private var selectedWaypoint: WaypointEntity? + @State private var selectedWaypointRect: CGRect = .zero + @State private var selectedWaypointPoint: CGPoint = .zero @State private var showingPositionPopover = false + @State private var showingWaypointPopover = false + + @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], + predicate: NSPredicate( + format: "expire == nil || expire >= %@", Date() as NSDate + ), animation: .none) + private var waypoints: FetchedResults + @State var waypoiintSelectionRect: CGRect = .zero var body: some View { let positionArray = node.positions?.array as? [PositionEntity] ?? [] @@ -44,6 +54,7 @@ struct NodeMapSwiftUI: View { return position.nodeCoordinate ?? LocationHelper.DefaultLocation }) + if node.hasPositions { ZStack { Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) { @@ -71,13 +82,29 @@ struct NodeMapSwiftUI: View { .stroke(Color(nodeColor.darker()), lineWidth: 5) .foregroundStyle(Color(nodeColor).opacity(0.4)) } + /// Waypoint Annotations + ForEach(Array(waypoints), id: \.id) { waypoint in + Annotation(waypoint.name ?? "?", coordinate: waypoint.coordinate) { + ZStack { + CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "📍"), color: Color.orange, circleSize: 35) + .onTapGesture(coordinateSpace: .global) { location in + print("Tapped at \(location)") + let size = CGSize(width: 1, height: 1) + let rect = CGRect(origin: location, size: size) + selectedWaypointRect = rect + selectedWaypointPoint = location + showingWaypointPopover = true + selectedWaypoint = (selectedWaypoint == waypoint ? nil : waypoint) + } + } + } + } /// Node Annotations - ForEach(positionArray.reversed(), id: \.id) { position in + ForEach(positionArray, id: \.id) { position in let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 3)) let formatter = MeasurementFormatter() - let speedText = formatter.string(from: Measurement(value: Double(position.speed), unit: UnitSpeed.kilometersPerHour)) let headingDegrees = Angle.degrees(Double(position.heading)) - Annotation(position.latest ? node.user?.shortName ?? "?" : (pf.contains(.Speed) && position.speed > 2) ? speedText : "", coordinate: position.coordinate) { + Annotation(position.latest ? node.user?.shortName ?? "?": "", coordinate: position.coordinate) { ZStack { if position.latest { Circle() @@ -95,7 +122,7 @@ struct NodeMapSwiftUI: View { showingPositionPopover = true selected = (selected == position ? nil : position) // <-- here } - .popover(isPresented: $showingPositionPopover, arrowEdge: .bottom) { + .popover(isPresented: $showingPositionPopover) { PositionPopover(position: position) .padding() .opacity(0.8) @@ -114,6 +141,7 @@ struct NodeMapSwiftUI: View { } .popover(isPresented: $showingPositionPopover, arrowEdge: .bottom) { PositionPopover(position: position) + .tag(position.id) .padding() .opacity(0.8) .presentationCompactAdaptation(.popover) @@ -167,6 +195,13 @@ struct NodeMapSwiftUI: View { .padding(.horizontal, 20) } } + .popover(item: $selectedWaypoint, attachmentAnchor: .rect(.rect(selectedWaypointRect)), arrowEdge: .bottom) { selection in + //.popover(isPresented: $showingWaypointPopover, arrowEdge: .bottom) { + WaypointPopover(waypoint: selection) + .padding() + .opacity(0.8) + .presentationCompactAdaptation(.popover) + } .sheet(isPresented: $isEditingSettings) { VStack { Form { @@ -250,8 +285,8 @@ struct NodeMapSwiftUI: View { .padding() #endif } - //.presentationDetents([.fraction(0.5)]) - .presentationDetents([.medium]) + .presentationDetents([.fraction(0.46)]) + //.presentationDetents([.medium]) .presentationDragIndicator(.visible) } .onChange(of: node) { diff --git a/Meshtastic/Views/Nodes/Helpers/WaypointPopover.swift b/Meshtastic/Views/Nodes/Helpers/WaypointPopover.swift new file mode 100644 index 00000000..1c4d8f8d --- /dev/null +++ b/Meshtastic/Views/Nodes/Helpers/WaypointPopover.swift @@ -0,0 +1,98 @@ +// +// WaypointPopover.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen on 9/19/23. +// + +import SwiftUI +import MapKit + +struct WaypointPopover: View { + var waypoint: WaypointEntity + let distanceFormatter = MKDistanceFormatter() + var body: some View { + VStack { + HStack { + CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "📍"), color: Color.blue) + Text(waypoint.name ?? "?") + .font(.title3) + if waypoint.locked > 0 { + Image(systemName: "lock.fill" ) + .font(.title2) + } else { + // Edit Button + } + } + Divider() + VStack (alignment: .leading) { + // Description + if (waypoint.longDescription ?? "").count > 0 { + Label { + Text(waypoint.longDescription ?? "") + .foregroundColor(.primary) + .font(.footnote) + .multilineTextAlignment(.leading) + .fixedSize(horizontal: false, vertical: true) + } icon: { + Image(systemName: "doc.plaintext") + .symbolRenderingMode(.hierarchical) + .frame(width: 35) + } + .padding(.bottom, 5) + } + /// Created + Label { + Text("Created: \(waypoint.created?.formatted() ?? "?")") + .foregroundColor(.primary) + .font(.footnote) + } icon: { + Image(systemName: "clock.badge.checkmark") + .symbolRenderingMode(.hierarchical) + .frame(width: 35) + } + .padding(.bottom, 5) + /// Updated + if waypoint.lastUpdated != nil { + Label { + Text("Updated: \(waypoint.lastUpdated?.formatted() ?? "?")") + .foregroundColor(.primary) + .font(.footnote) + } icon: { + Image(systemName: "clock.arrow.circlepath") + .symbolRenderingMode(.hierarchical) + .frame(width: 35) + } + .padding(.bottom, 5) + } + /// Updated + if waypoint.expire != nil { + Label { + Text("Expires: \(waypoint.expire?.formatted() ?? "?")") + .foregroundColor(.primary) + .font(.footnote) + } icon: { + Image(systemName: "clock.badge.xmark") + .symbolRenderingMode(.hierarchical) + .frame(width: 35) + } + .padding(.bottom, 5) + } + /// Distance + if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 { + let metersAway = waypoint.coordinate.distance(from: LocationHelper.currentLocation) + Label { + Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway)))") + .foregroundColor(.primary) + .font(.footnote) + } icon: { + Image(systemName: "lines.measurement.horizontal") + .symbolRenderingMode(.hierarchical) + .frame(width: 35) + } + } + } + } + .tag(waypoint.id) + } +}