From 85c0582649364a015927bf6cd8a77289ad8eba59 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 8 Feb 2023 09:42:07 -0800 Subject: [PATCH 1/6] HAMS --- Meshtastic/Views/Settings/UserConfig.swift | 81 ++++++++++++++++++++-- 1 file changed, 76 insertions(+), 5 deletions(-) diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index e645c370..d664da7d 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -16,10 +16,14 @@ struct UserConfig: View { @State private var isPresentingFactoryResetConfirm: Bool = false @State private var isPresentingSaveConfirm: Bool = false + @State private var isPresentatingHamSheet: Bool = false @State var hasChanges = false @State var shortName = "" @State var longName = "" @State var isLicensed = false + @State var overrideDutyCycle = false + @State var frequencyOverride = 0.0 + @State var txPower = 0 var body: some View { @@ -67,12 +71,15 @@ struct UserConfig: View { Text("The short name is used in maps and messaging and will be appended to the last 4 of the device MAC address to set the device's BLE Name. It can be up to 4 bytes long.") .font(.caption) - Toggle(isOn: $isLicensed) { - Label("Licensed User", systemImage: "person.text.rectangle") + // Only manage ham mode for the locally connected node + if node?.num ?? 0 > 0 && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { + Toggle(isOn: $isLicensed) { + Label("Licensed User", systemImage: "person.text.rectangle") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Text("Enable only if you are a licensed amateur radio user for your region.") + .font(.caption) } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - Text("Enable only if you are a licensed amateur radio user for your region.") - .font(.caption) } } .disabled(bleManager.connectedPeripheral == nil) @@ -113,6 +120,64 @@ struct UserConfig: View { } Spacer() } + .sheet(isPresented: $isPresentatingHamSheet) { + + VStack { + Form { + Section(header: Text("Licensed Amateur Radio Operators")) { + Text("Enable only if you are a licensed amateur radio user for your region.") + .font(.body) + Text("* Sets the node name to your call sign") + .font(.caption) + Text("* Override frequency, dutycycle and tx power") + .font(.caption) + Text("* Disables Device Encryption") + .font(.caption) + } + Section(header: Text("Licensed User Options")) { + + HStack { + Label("Call Sign", systemImage: "person.crop.rectangle.fill") + TextField("Call Sign", text: $longName) + .onChange(of: longName, perform: { value in + let totalBytes = longName.utf8.count + // Only mess with the value if it is too big + if totalBytes > 36 { + let firstNBytes = Data(longName.utf8.prefix(36)) + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + // Set the longName back to the last place where it was the right size + longName = maxBytesString + } + } + }) + } + .keyboardType(.default) + .disableAutocorrection(true) + Text("Call sign can be up to 36 bytes long.") + .font(.caption) + Toggle(isOn: $overrideDutyCycle) { + Label("Override Duty Cycle", systemImage: "figure.indoor.cycle") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + HStack { + Image(systemName: "waveform.path.ecg") + .foregroundColor(.accentColor) + Stepper("\(String(format: "Frequency: %.2f", frequencyOverride))", value: $frequencyOverride, in: 400...950, step: 0.1) + .padding(5) + } + HStack { + Image(systemName: "antenna.radiowaves.left.and.right") + .foregroundColor(.accentColor) + Stepper("\(txPower)db Transmit Power", value: $txPower, in: 0...30, step: 1) + .padding(5) + } + } + } + } + .presentationDetents([.large]) + .presentationDragIndicator(.automatic) + } + .navigationTitle("User Config") .navigationBarItems(trailing: ZStack { @@ -134,5 +199,11 @@ struct UserConfig: View { if newLong != node?.user!.longName { hasChanges = true } } } + .onChange(of: isLicensed) { newIsLicensed in + + if isLicensed { + isPresentatingHamSheet = true + } + } } } From 20822309298676d97f33f773d9c705af5cc60aab Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 9 Feb 2023 22:59:39 -0800 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=93=BB=20ham=20params?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Meshtastic/Helpers/BLEManager.swift | 24 +++ Meshtastic/Views/Settings/UserConfig.swift | 203 +++++++++++---------- 2 files changed, 129 insertions(+), 98 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 48a7fca4..e8b47d24 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1132,6 +1132,30 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { return 0 } + public func saveLicensedUser(ham: HamParameters, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { + + var adminPacket = AdminMessage() + adminPacket.setHamMode = ham + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(toUser.num) + meshPacket.from = UInt32(fromUser.num) + meshPacket.channel = UInt32(adminIndex) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { var adminPacket = AdminMessage() diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index d664da7d..966e1e75 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -5,6 +5,7 @@ // Copyright (c) Garth Vander Houwen 6/27/22. // import SwiftUI +import CoreData struct UserConfig: View { @@ -14,44 +15,56 @@ struct UserConfig: View { var node: NodeInfoEntity? + enum Field: Hashable { + case frequencyOverride + } + @State private var isPresentingFactoryResetConfirm: Bool = false @State private var isPresentingSaveConfirm: Bool = false - @State private var isPresentatingHamSheet: Bool = false @State var hasChanges = false @State var shortName = "" @State var longName = "" @State var isLicensed = false @State var overrideDutyCycle = false - @State var frequencyOverride = 0.0 + @State var overrideFrequency: Float = 0.0 @State var txPower = 0 + @FocusState var focusedField: Field? + + let floatFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + return formatter + }() + var body: some View { - + VStack { Form { Section(header: Text("User Details")) { - HStack { - Label("Long Name", systemImage: "person.crop.rectangle.fill") - TextField("Long Name", text: $longName) - .onChange(of: longName, perform: { value in - let totalBytes = longName.utf8.count - // Only mess with the value if it is too big - if totalBytes > 36 { - let firstNBytes = Data(longName.utf8.prefix(36)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the longName back to the last place where it was the right size - longName = maxBytesString + HStack { + Label(isLicensed ? "Call Sign" : "Long Name", systemImage: "person.crop.rectangle.fill") + TextField("Long Name", text: $longName) + .onChange(of: longName, perform: { value in + let totalBytes = longName.utf8.count + // Only mess with the value if it is too big + if totalBytes > 36 { + let firstNBytes = Data(longName.utf8.prefix(36)) + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + // Set the longName back to the last place where it was the right size + longName = maxBytesString + } } - } - }) - } - .keyboardType(.default) - .disableAutocorrection(true) - Text("Long name can be up to 36 bytes long.") - .font(.caption) + }) + } + .keyboardType(.default) + .disableAutocorrection(true) + Text("\(String(isLicensed ? "Call Sign" : "Long Name")) can be up to 36 bytes long.") + .font(.caption) + HStack { Label("Short Name", systemImage: "circlebadge.fill") - TextField("Long Name", text: $shortName) + TextField("Short Name", text: $shortName) .foregroundColor(.gray) .onChange(of: shortName, perform: { value in let totalBytes = shortName.utf8.count @@ -68,17 +81,45 @@ struct UserConfig: View { } .keyboardType(.asciiCapable) .disableAutocorrection(true) - Text("The short name is used in maps and messaging and will be appended to the last 4 of the device MAC address to set the device's BLE Name. It can be up to 4 bytes long.") + Text("The short name will be appended to the last 4 of the device MAC address to set the device's BLE Name. It can be up to 4 bytes long.") .font(.caption) // Only manage ham mode for the locally connected node if node?.num ?? 0 > 0 && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { Toggle(isOn: $isLicensed) { - Label("Licensed User", systemImage: "person.text.rectangle") + Label("Licensed Operator", systemImage: "person.text.rectangle") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - Text("Enable only if you are a licensed amateur radio user for your region.") - .font(.caption) + if isLicensed { + + Text("Onboarding for licensed operators requires firmware 2.0.20 or greater. Make sure to refer to your local regulations and contact the local amateur frequency coordinators with questions.") + .font(.caption2) + Text("What licensed operator mode does:\n* Sets the node name to your call sign \n* Broadcasts node info every 10 minutes \n* Overrides frequency, dutycycle and tx power \n* Disables encryption") + .font(.caption2) + + HStack { + Label("Frequency", systemImage: "waveform.path.ecg") + Spacer() + TextField("Frequency Override", value: $overrideFrequency, formatter: floatFormatter) + .toolbar { + ToolbarItemGroup(placement: .keyboard) { + Button("dismiss.keyboard") { + focusedField = nil + } + .font(.subheadline) + } + } + .keyboardType(.decimalPad) + .scrollDismissesKeyboard(.immediately) + .focused($focusedField, equals: .frequencyOverride) + } + HStack { + Image(systemName: "antenna.radiowaves.left.and.right") + .foregroundColor(.accentColor) + Stepper("\(txPower)db Transmit Power", value: $txPower, in: 0...30, step: 1) + .padding(5) + } + } } } } @@ -104,13 +145,27 @@ struct UserConfig: View { let connectedUser = getUser(id: bleManager.connectedPeripheral.num, context: context) let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) if connectedNode != nil { - var u = User() - u.shortName = shortName - u.longName = longName - let adminMessageId = bleManager.saveUser(config: u, fromUser: connectedUser, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) - if adminMessageId > 0 { - hasChanges = false - goBack() + + if !isLicensed { + var u = User() + u.shortName = shortName + u.longName = longName + let adminMessageId = bleManager.saveUser(config: u, fromUser: connectedUser, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + if adminMessageId > 0 { + hasChanges = false + goBack() + } + } else { + var ham = HamParameters() + //ham.shortName = shortName + ham.callSign = longName + ham.txPower = Int32(txPower) + ham.frequency = overrideFrequency + let adminMessageId = bleManager.saveLicensedUser(ham: ham, fromUser: connectedUser, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + if adminMessageId > 0 { + hasChanges = false + goBack() + } } } } @@ -120,73 +175,20 @@ struct UserConfig: View { } Spacer() } - .sheet(isPresented: $isPresentatingHamSheet) { - - VStack { - Form { - Section(header: Text("Licensed Amateur Radio Operators")) { - Text("Enable only if you are a licensed amateur radio user for your region.") - .font(.body) - Text("* Sets the node name to your call sign") - .font(.caption) - Text("* Override frequency, dutycycle and tx power") - .font(.caption) - Text("* Disables Device Encryption") - .font(.caption) - } - Section(header: Text("Licensed User Options")) { - - HStack { - Label("Call Sign", systemImage: "person.crop.rectangle.fill") - TextField("Call Sign", text: $longName) - .onChange(of: longName, perform: { value in - let totalBytes = longName.utf8.count - // Only mess with the value if it is too big - if totalBytes > 36 { - let firstNBytes = Data(longName.utf8.prefix(36)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the longName back to the last place where it was the right size - longName = maxBytesString - } - } - }) - } - .keyboardType(.default) - .disableAutocorrection(true) - Text("Call sign can be up to 36 bytes long.") - .font(.caption) - Toggle(isOn: $overrideDutyCycle) { - Label("Override Duty Cycle", systemImage: "figure.indoor.cycle") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - HStack { - Image(systemName: "waveform.path.ecg") - .foregroundColor(.accentColor) - Stepper("\(String(format: "Frequency: %.2f", frequencyOverride))", value: $frequencyOverride, in: 400...950, step: 0.1) - .padding(5) - } - HStack { - Image(systemName: "antenna.radiowaves.left.and.right") - .foregroundColor(.accentColor) - Stepper("\(txPower)db Transmit Power", value: $txPower, in: 0...30, step: 1) - .padding(5) - } - } - } - } - .presentationDetents([.large]) - .presentationDragIndicator(.automatic) - } - .navigationTitle("User Config") .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + ZStack { + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") }) .onAppear { self.bleManager.context = context - self.shortName = node?.user!.shortName ?? "" - self.longName = node?.user!.longName ?? "" + self.shortName = node?.user?.shortName ?? "" + self.longName = node?.user?.longName ?? "" + self.isLicensed = node?.user?.isLicensed ?? false + self.txPower = Int(node?.loRaConfig?.txPower ?? 0) + self.overrideFrequency = node?.loRaConfig?.overrideFrequency ?? 0.00 + + self.hasChanges = false } .onChange(of: shortName) { newShort in @@ -200,10 +202,15 @@ struct UserConfig: View { } } .onChange(of: isLicensed) { newIsLicensed in - - if isLicensed { - isPresentatingHamSheet = true + if node != nil && node!.user != nil { + if newIsLicensed != node?.user!.isLicensed { hasChanges = true } } } + .onChange(of: overrideFrequency) { newOverrideFrequency in + hasChanges = true + } + .onChange(of: txPower) { newTxPower in + hasChanges = true + } } } From f29c7048332696f6504e4311852f61fd083e8d0e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 10 Feb 2023 09:38:01 -0800 Subject: [PATCH 3/6] Cleanup ham mode view --- Meshtastic/Views/Settings/UserConfig.swift | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index 966e1e75..dc5a208e 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -60,7 +60,7 @@ struct UserConfig: View { .keyboardType(.default) .disableAutocorrection(true) Text("\(String(isLicensed ? "Call Sign" : "Long Name")) can be up to 36 bytes long.") - .font(.caption) + .font(.caption2) HStack { Label("Short Name", systemImage: "circlebadge.fill") @@ -81,8 +81,8 @@ struct UserConfig: View { } .keyboardType(.asciiCapable) .disableAutocorrection(true) - Text("The short name will be appended to the last 4 of the device MAC address to set the device's BLE Name. It can be up to 4 bytes long.") - .font(.caption) + Text("The last 4 of the device MAC address will be appended to the short name to set the device's BLE Name. Short name can be up to 4 bytes long.") + .font(.caption2) // Only manage ham mode for the locally connected node if node?.num ?? 0 > 0 && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { @@ -187,8 +187,6 @@ struct UserConfig: View { self.isLicensed = node?.user?.isLicensed ?? false self.txPower = Int(node?.loRaConfig?.txPower ?? 0) self.overrideFrequency = node?.loRaConfig?.overrideFrequency ?? 0.00 - - self.hasChanges = false } .onChange(of: shortName) { newShort in @@ -207,10 +205,10 @@ struct UserConfig: View { } } .onChange(of: overrideFrequency) { newOverrideFrequency in - hasChanges = true + //hasChanges = true } .onChange(of: txPower) { newTxPower in - hasChanges = true + //hasChanges = true } } } From a3c0b6f3771f11a9c2acd5ff69bc4be7a9187180 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 10 Feb 2023 09:38:36 -0800 Subject: [PATCH 4/6] Bump version --- Meshtastic.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index f431452c..ea2501f9 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1075,7 +1075,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.0.12; + MARKETING_VERSION = 2.0.14; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1108,7 +1108,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.0.12; + MARKETING_VERSION = 2.0.14; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; From 243dd75456591fd7f1ab969a350f5d5e0e3a9b51 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 10 Feb 2023 20:11:35 -0800 Subject: [PATCH 5/6] Fix empty telemetries crash Re-Enable map type picker in the mesh map Add red last position annotation pins to maps --- .../Views/Map/Custom/MapViewSwiftUI.swift | 22 ++++++++++++++----- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 2 +- Meshtastic/Views/Nodes/NodeMap.swift | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index fd68f7fe..44eca9a3 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -114,7 +114,7 @@ struct MapViewSwiftUI: UIViewRepresentable { switch annotation { case _ as MKClusterAnnotation: - let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "nodeGroup") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "nodeGroup") + let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "nodeGroup") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "NodeGroup") annotationView.markerTintColor = .brown//.systemRed annotationView.displayPriority = .defaultLow annotationView.tag = -1 @@ -124,9 +124,21 @@ struct MapViewSwiftUI: UIViewRepresentable { annotationView.tag = -1 annotationView.canShowCallout = true annotationView.glyphText = "📟" - annotationView.clusteringIdentifier = "nodeGroup" - annotationView.markerTintColor = UIColor(.indigo) - annotationView.displayPriority = .required + + let latest = parent.positions.first(where: { $0.nodePosition?.num ?? 0 == positionAnnotation.nodePosition?.num ?? -1 }) + + if latest == positionAnnotation { + annotationView.markerTintColor = .systemRed + annotationView.displayPriority = .required + annotationView.titleVisibility = .visible + } + else { + annotationView.markerTintColor = UIColor(.indigo) + annotationView.displayPriority = .defaultHigh + annotationView.titleVisibility = .adaptive + annotationView.clusteringIdentifier = "nodeGroup" + } + annotationView.titleVisibility = .adaptive let leftIcon = UIImageView(image: annotationView.glyphText?.image()) leftIcon.backgroundColor = UIColor(.indigo) @@ -172,7 +184,7 @@ struct MapViewSwiftUI: UIViewRepresentable { } annotationView.clusteringIdentifier = "waypointGroup" annotationView.markerTintColor = UIColor(.accentColor) - annotationView.displayPriority = .required + annotationView.displayPriority = .defaultHigh annotationView.titleVisibility = .adaptive let leftIcon = UIImageView(image: annotationView.glyphText?.image()) leftIcon.backgroundColor = UIColor(.accentColor) diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index b5ca5b32..5d27efa3 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -20,7 +20,7 @@ struct DeviceMetricsLog: View { var body: some View { NavigationStack { let oneDayAgo = Calendar.current.date(byAdding: .day, value: -3, to: Date()) - let data = node.telemetries!.filtered(using: NSPredicate(format: "metricsType == 0 && time !=nil && time >= %@", oneDayAgo! as CVarArg)) + let data = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0 && time !=nil && time >= %@", oneDayAgo! as CVarArg)) ?? [] if data.count > 0 { GroupBox(label: Label("battery.level.trend", systemImage: "battery.100")) { Chart(data.array as! [TelemetryEntity], id: \.self) { diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index 89f94eda..bfb451fc 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -40,7 +40,7 @@ struct NodeMap: View { ), animation: .none) private var waypoints: FetchedResults - @State private var mapType: MKMapType? + @State private var mapType: MKMapType = .standard @State var waypointCoordinate: CLLocationCoordinate2D = LocationHelper.DefaultLocation @State var editingWaypoint: Int = 0 @State private var presentingWaypointForm = false From f7f09c670520e4bb704a076fc2c3e2a6f9258610 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 10 Feb 2023 20:50:25 -0800 Subject: [PATCH 6/6] Sort positions the same way on both maps so we can get latest --- Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift | 2 +- Meshtastic/Views/Nodes/NodeMap.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 44eca9a3..70e8a9e0 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -125,7 +125,7 @@ struct MapViewSwiftUI: UIViewRepresentable { annotationView.canShowCallout = true annotationView.glyphText = "📟" - let latest = parent.positions.first(where: { $0.nodePosition?.num ?? 0 == positionAnnotation.nodePosition?.num ?? -1 }) + let latest = parent.positions.last(where: { $0.nodePosition?.num ?? 0 == positionAnnotation.nodePosition?.num ?? -1 }) if latest == positionAnnotation { annotationView.markerTintColor = .systemRed diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index bfb451fc..b13caa0c 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -30,7 +30,7 @@ struct NodeMap: View { } } //&& nodePosition != nil - @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: false)], + @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)], predicate: NSPredicate(format: "time >= %@ && nodePosition != nil", Calendar.current.startOfDay(for: Date()) as NSDate), animation: .none) private var positions: FetchedResults