From 345e3467e782527f348af18d3ff2a758f61c58a8 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 8 Oct 2023 13:06:10 -0700 Subject: [PATCH 01/13] Dummy up users if they are nil to prevent Unknown from being half the nodes on MQTT --- Meshtastic/Helpers/MeshPackets.swift | 22 ++++++++++++++++++++- Meshtastic/Persistence/UpdateCoreData.swift | 10 ++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index b01808fa..b2083e4f 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -265,6 +265,16 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newUser.shortName = nodeInfo.user.shortName newUser.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() newNode.user = newUser + } else { + let newUser = UserEntity(context: context) + newUser.num = Int64(nodeInfo.num) + let userId = String(format:"%2X", nodeInfo.num) + newUser.userId = "!\(userId)" + let last4 = String(userId.suffix(4)) + newUser.longName = "Meshtastic \(last4)" + newUser.shortName = last4 + newUser.hwModel = "UNSET" + newNode.user = newUser } if nodeInfo.position.longitudeI > 0 || nodeInfo.position.latitudeI > 0 && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) { @@ -306,7 +316,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje } catch { print("💥 Fetch MyInfo Error") } - } else if nodeInfo.hasUser && nodeInfo.num > 0 { + } else if nodeInfo.num > 0 { fetchedNode[0].id = Int64(nodeInfo.num) fetchedNode[0].num = Int64(nodeInfo.num) @@ -323,6 +333,16 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje fetchedNode[0].user!.longName = nodeInfo.user.longName fetchedNode[0].user!.shortName = nodeInfo.user.shortName fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() + } else { + let newUser = UserEntity(context: context) + newUser.num = Int64(nodeInfo.num) + let userId = String(format:"%2X", nodeInfo.num) + newUser.userId = "!\(userId)" + let last4 = String(userId.suffix(4)) + newUser.longName = "Meshtastic \(last4)" + newUser.shortName = last4 + newUser.hwModel = "UNSET" + fetchedNode[0].user = newUser } if nodeInfo.hasDeviceMetrics { diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 18c9463d..6ed295d5 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -178,6 +178,16 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].user!.longName = nodeInfoMessage.user.longName fetchedNode[0].user!.shortName = nodeInfoMessage.user.shortName fetchedNode[0].user!.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased() + } else { + let newUser = UserEntity(context: context) + newUser.num = Int64(nodeInfoMessage.num) + let userId = String(format:"%2X", nodeInfoMessage.num) + newUser.userId = "!\(userId)" + let last4 = String(userId.suffix(4)) + newUser.longName = "Meshtastic \(last4)" + newUser.shortName = last4 + newUser.hwModel = "UNSET" + fetchedNode[0].user! = newUser } } do { From 925f531c116beb670ffada0c30e6bfec1e4cda77 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 8 Oct 2023 13:07:17 -0700 Subject: [PATCH 02/13] Bump Version --- Meshtastic.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 4963df39..4b87f75d 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1423,7 +1423,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.8; + MARKETING_VERSION = 2.2.9; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1457,7 +1457,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.8; + MARKETING_VERSION = 2.2.9; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1579,7 +1579,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.8; + MARKETING_VERSION = 2.2.9; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1612,7 +1612,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.8; + MARKETING_VERSION = 2.2.9; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From 440fa683e3de3fa9f57ce329f7a4f4451a4a027e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 8 Oct 2023 13:25:34 -0700 Subject: [PATCH 03/13] Use formatted date instead of relative time on node list --- Meshtastic/Views/Nodes/Helpers/NodeListItem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index 7f4afcdb..c8f3eeec 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -54,7 +54,7 @@ struct NodeListItem: View { .font(.footnote) .symbolRenderingMode(.hierarchical) .foregroundColor(node.isOnline ? .green : .orange) - LastHeardText(lastHeard: node.lastHeard) + Text(node.lastHeard?.formatted() ?? "unknown".localized) .font(.caption) } if node.positions?.count ?? 0 > 0 && connectedNode != node.num { From 628edad09220cefa338d82c778bb00ef590f6208 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 8 Oct 2023 15:10:17 -0700 Subject: [PATCH 04/13] Fix position filtering --- Meshtastic/Helpers/MeshPackets.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index b2083e4f..eca33e91 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -277,7 +277,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newNode.user = newUser } - if nodeInfo.position.longitudeI > 0 || nodeInfo.position.latitudeI > 0 && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) { + if (nodeInfo.position.longitudeI != 0 && nodeInfo.position.latitudeI != 0) && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) { let position = PositionEntity(context: context) position.latest = true position.seqNo = Int32(nodeInfo.position.seqNumber) @@ -360,7 +360,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje if nodeInfo.hasPosition { - if nodeInfo.position.longitudeI > 0 || nodeInfo.position.latitudeI > 0 && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) { + if (nodeInfo.position.longitudeI != 0 && nodeInfo.position.latitudeI != 0) && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) { let position = PositionEntity(context: context) position.latitudeI = nodeInfo.position.latitudeI From 9799ed9330476f5b021ccbc55bcc91c8edf20aab Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 10 Oct 2023 21:41:15 -0700 Subject: [PATCH 05/13] Fix duplicate contact bug, reduce mqtt client proxy logging --- Meshtastic/Extensions/Date.swift | 3 --- Meshtastic/Helpers/BLEManager.swift | 6 ++--- Meshtastic/Helpers/MeshPackets.swift | 23 +++++++++++-------- .../Helpers/Mqtt/MqttClientProxyManager.swift | 23 +++++++++++++------ Meshtastic/Persistence/UpdateCoreData.swift | 21 +++++++++-------- 5 files changed, 42 insertions(+), 34 deletions(-) diff --git a/Meshtastic/Extensions/Date.swift b/Meshtastic/Extensions/Date.swift index 29a60320..224f0b03 100644 --- a/Meshtastic/Extensions/Date.swift +++ b/Meshtastic/Extensions/Date.swift @@ -8,9 +8,6 @@ import Foundation extension Date { - static var currentTimeStamp: Int64 { - return Int64(Date().timeIntervalSince1970 * 1000) - } func formattedDate(format: String) -> String { let dateformat = DateFormatter() diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 351c899e..30c83cd9 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -290,7 +290,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate func onMqttMessageReceived(message: CocoaMQTTMessage) { - print("📲 Mqtt Client Proxy onMqttMessageReceived for topic: \(message.topic)") + if message.topic.contains("/stat/") { return } @@ -305,7 +305,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let binaryData: Data = try! toRadio.serializedData() if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) - print("📲 Sent Mqtt client proxy message to the connected device.") + } } @@ -443,7 +443,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate payload: [UInt8](decodedInfo.mqttClientProxyMessage.data), retained: decodedInfo.mqttClientProxyMessage.retained ) - print("📲 Publish Mqtt client proxy message received on FromRadio to the Mqtt server \(message)") mqttManager.mqttClientProxy?.publish(message) } @@ -870,7 +869,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) success = true let logString = String.localizedStringWithFormat("mesh.log.sharelocation %@".localized, String(fromNodeNum)) - print(positionPacket) MeshLogger.log("📍 \(logString)") } return success diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index eca33e91..1ae63a83 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -258,6 +258,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard))) newNode.snr = nodeInfo.snr if nodeInfo.hasUser { + let newUser = UserEntity(context: context) newUser.userId = nodeInfo.user.id newUser.num = Int64(nodeInfo.num) @@ -333,16 +334,18 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje fetchedNode[0].user!.longName = nodeInfo.user.longName fetchedNode[0].user!.shortName = nodeInfo.user.shortName fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() - } else { - let newUser = UserEntity(context: context) - newUser.num = Int64(nodeInfo.num) - let userId = String(format:"%2X", nodeInfo.num) - newUser.userId = "!\(userId)" - let last4 = String(userId.suffix(4)) - newUser.longName = "Meshtastic \(last4)" - newUser.shortName = last4 - newUser.hwModel = "UNSET" - fetchedNode[0].user = newUser + } else { + if (fetchedNode[0].user == nil) { + let newUser = UserEntity(context: context) + newUser.num = Int64(nodeInfo.num) + let userId = String(format:"%2X", nodeInfo.num) + newUser.userId = "!\(userId)" + let last4 = String(userId.suffix(4)) + newUser.longName = "Meshtastic \(last4)" + newUser.shortName = last4 + newUser.hwModel = "UNSET" + fetchedNode[0].user = newUser + } } if nodeInfo.hasDeviceMetrics { diff --git a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift index 4df407b5..2d7dc6c1 100644 --- a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift +++ b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift @@ -22,6 +22,7 @@ class MqttClientProxyManager { weak var delegate: MqttClientProxyManagerDelegate? var mqttClientProxy: CocoaMQTT? var topic = "msh/2/c" + var debugLog = false func connectFromConfigSettings(node: NodeInfoEntity) { let defaultServerAddress = "mqtt.meshtastic.org" let useSsl = node.mqttConfig?.tlsEnabled == true @@ -58,9 +59,9 @@ class MqttClientProxyManager { mqttClient.password = password mqttClient.keepAlive = 60 mqttClient.cleanSession = cleanSession -#if DEBUG - mqttClient.logLevel = .debug -#endif + if debugLog { + mqttClient.logLevel = .debug + } mqttClient.willMessage = CocoaMQTTMessage(topic: "/will", string: "dieout") mqttClient.autoReconnect = true mqttClient.delegate = self @@ -82,7 +83,9 @@ class MqttClientProxyManager { } func publish(message: String, topic: String, qos: CocoaMQTTQoS) { mqttClientProxy?.publish(topic, withString: message, qos: qos) - print("📲 MQTT Client Proxy publish for: " + topic) + if debugLog { + print("📲 MQTT Client Proxy publish for: " + topic) + } } func disconnect() { if let client = mqttClientProxy { @@ -130,15 +133,21 @@ extension MqttClientProxyManager: CocoaMQTTDelegate { delegate?.onMqttDisconnected() } func mqtt(_ mqtt: CocoaMQTT, didPublishMessage message: CocoaMQTTMessage, id: UInt16) { - print("📲 MQTT Client Proxy didPublishMessage from MqttClientProxyManager: \(message)") + if debugLog { + print("📲 MQTT Client Proxy didPublishMessage from MqttClientProxyManager: \(message)") + } } func mqtt(_ mqtt: CocoaMQTT, didPublishAck id: UInt16) { - print("📲 MQTT Client Proxy didPublishAck from MqttClientProxyManager: \(id)") + if debugLog { + print("📲 MQTT Client Proxy didPublishAck from MqttClientProxyManager: \(id)") + } } public func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16) { delegate?.onMqttMessageReceived(message: message) - print("📲 MQTT Client Proxy message received on topic: \(message.topic)") + if debugLog { + print("📲 MQTT Client Proxy message received on topic: \(message.topic)") + } } func mqtt(_ mqtt: CocoaMQTT, didSubscribeTopics success: NSDictionary, failed: [String]) { print("📲 MQTT Client Proxy didSubscribeTopics: \(success.allKeys.count) topics. failed: \(failed.count) topics") diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 6ed295d5..7c79d265 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -179,15 +179,17 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].user!.shortName = nodeInfoMessage.user.shortName fetchedNode[0].user!.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased() } else { - let newUser = UserEntity(context: context) - newUser.num = Int64(nodeInfoMessage.num) - let userId = String(format:"%2X", nodeInfoMessage.num) - newUser.userId = "!\(userId)" - let last4 = String(userId.suffix(4)) - newUser.longName = "Meshtastic \(last4)" - newUser.shortName = last4 - newUser.hwModel = "UNSET" - fetchedNode[0].user! = newUser + if (fetchedNode[0].user == nil) { + let newUser = UserEntity(context: context) + newUser.num = Int64(nodeInfoMessage.num) + let userId = String(format:"%2X", nodeInfoMessage.num) + newUser.userId = "!\(userId)" + let last4 = String(userId.suffix(4)) + newUser.longName = "Meshtastic \(last4)" + newUser.shortName = last4 + newUser.hwModel = "UNSET" + fetchedNode[0].user! = newUser + } } } do { @@ -235,7 +237,6 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) position.latest = false } } - print("Incoming position message: \n \(positionMessage)") let position = PositionEntity(context: context) position.latest = true position.snr = packet.rxSnr From c1668577cc5ee0925806bbca6fa513f7b76b0d83 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 17 Oct 2023 19:21:45 -0700 Subject: [PATCH 06/13] Increase text size on node details --- Meshtastic/Views/Helpers/CircleText.swift | 2 +- .../Views/Helpers/LoRaSignalStrength.swift | 4 ++-- .../Views/Nodes/Helpers/NodeListItem.swift | 20 +++++++++---------- .../Views/Settings/AdminMessageList.swift | 4 +++- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Meshtastic/Views/Helpers/CircleText.swift b/Meshtastic/Views/Helpers/CircleText.swift index bd2f0b0d..da51cf8d 100644 --- a/Meshtastic/Views/Helpers/CircleText.swift +++ b/Meshtastic/Views/Helpers/CircleText.swift @@ -21,7 +21,7 @@ struct CircleText: View { .foregroundColor(color.isLight() ? .black : .white) .font(.system(size: 500)) .minimumScaleFactor(0.001) - .frame(width: circleSize * 0.94, height: circleSize * 0.94, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) + .frame(width: circleSize * 0.95, height: circleSize * 0.95, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) } .aspectRatio(1, contentMode: .fit) } diff --git a/Meshtastic/Views/Helpers/LoRaSignalStrength.swift b/Meshtastic/Views/Helpers/LoRaSignalStrength.swift index 444083ae..f1fee8bb 100644 --- a/Meshtastic/Views/Helpers/LoRaSignalStrength.swift +++ b/Meshtastic/Views/Helpers/LoRaSignalStrength.swift @@ -31,9 +31,9 @@ struct LoRaSignalStrengthMeter: View { Gauge(value: Double(signalStrength.rawValue), in: 0...3) { } currentValueLabel: { Image(systemName: "dot.radiowaves.left.and.right") - .font(.caption) + .font(.callout) Text("Signal \(signalStrength.description)") - .font(.caption) + .font(.callout) } .gaugeStyle(.accessoryLinear) .tint(gradient) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index c8f3eeec..ea49426e 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -26,14 +26,14 @@ struct NodeListItem: View { let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) if deviceMetrics?.count ?? 0 >= 1 { let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity - BatteryLevelCompact(batteryLevel: mostRecent?.batteryLevel, font: .caption2, iconFont: .callout, color: .accentColor) + BatteryLevelCompact(batteryLevel: mostRecent?.batteryLevel, font: .caption, iconFont: .callout, color: .accentColor) } } VStack(alignment: .leading) { HStack { Text(node.user?.longName ?? "unknown".localized) .fontWeight(.medium) - .font(.callout) + .font(.headline) if node.user?.vip ?? false { Spacer() Image(systemName: "star.fill") @@ -43,19 +43,19 @@ struct NodeListItem: View { if connected { HStack { Image(systemName: "antenna.radiowaves.left.and.right.circle.fill") - .font(.footnote) + .font(.callout) .symbolRenderingMode(.hierarchical) .foregroundColor(.green) - Text("connected").font(.caption) + Text("connected").font(.callout) } } HStack { Image(systemName: node.isOnline ? "checkmark.circle.fill" : "moon.circle.fill") - .font(.footnote) + .font(.callout) .symbolRenderingMode(.hierarchical) .foregroundColor(node.isOnline ? .green : .orange) Text(node.lastHeard?.formatted() ?? "unknown".localized) - .font(.caption) + .font(.callout) } if node.positions?.count ?? 0 > 0 && connectedNode != node.num { HStack { @@ -65,19 +65,19 @@ struct NodeListItem: View { let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude) let metersAway = nodeCoord.distance(from: myCoord) Image(systemName: "lines.measurement.horizontal") - .font(.footnote) + .font(.callout) .symbolRenderingMode(.hierarchical) - DistanceText(meters: metersAway).font(.caption) + DistanceText(meters: metersAway).font(.callout) } } } if node.channel > 0 { HStack { Image(systemName: "fibrechannel") - .font(.footnote) + .font(.callout) .symbolRenderingMode(.hierarchical) Text("Channel: \(node.channel)") - .font(.caption) + .font(.callout) } } diff --git a/Meshtastic/Views/Settings/AdminMessageList.swift b/Meshtastic/Views/Settings/AdminMessageList.swift index bb624dc0..190ff683 100644 --- a/Meshtastic/Views/Settings/AdminMessageList.swift +++ b/Meshtastic/Views/Settings/AdminMessageList.swift @@ -22,7 +22,9 @@ struct AdminMessageList: View { var body: some View { let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmmssa", options: 0, locale: Locale.current) + let localeTimeFormat = DateFormatter.dateFormat(fromTemplate: "h:mm:ss a", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mm:ss a") + let timeFormatString = (localeTimeFormat ?? "h:mm:ss a") List { if user != nil { @@ -55,7 +57,7 @@ struct AdminMessageList: View { } if am.receivedACK && am.ackTimestamp > 0 { - Text(" \(Date(timeIntervalSince1970: TimeInterval(am.ackTimestamp)).formattedDate(format: "h:mm:ss a"))") + Text(" \(Date(timeIntervalSince1970: TimeInterval(am.ackTimestamp)).formattedDate(format: timeFormatString))") .foregroundColor(am.realACK ? .gray : .orange) .font(.caption2) } From b6abd92543ebc42737d35d77e045343889646fdf Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 17 Oct 2023 20:35:29 -0700 Subject: [PATCH 07/13] Fix cases where location update interval is set to 0 --- Meshtastic/Enums/AppSettingsEnums.swift | 3 +++ Meshtastic/Helpers/BLEManager.swift | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Enums/AppSettingsEnums.swift b/Meshtastic/Enums/AppSettingsEnums.swift index dd0a94ed..d2d13979 100644 --- a/Meshtastic/Enums/AppSettingsEnums.swift +++ b/Meshtastic/Enums/AppSettingsEnums.swift @@ -85,6 +85,7 @@ enum UserTrackingModes: Int, CaseIterable, Identifiable { } enum LocationUpdateInterval: Int, CaseIterable, Identifiable { + case fiveSeconds = 5 case tenSeconds = 10 case fifteenSeconds = 15 case thirtySeconds = 30 @@ -96,6 +97,8 @@ enum LocationUpdateInterval: Int, CaseIterable, Identifiable { var id: Int { self.rawValue } var description: String { switch self { + case .fiveSeconds: + return "interval.five.seconds".localized case .tenSeconds: return "interval.ten.seconds".localized case .fifteenSeconds: diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 30c83cd9..68f65f92 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -272,6 +272,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } if ![FROMNUM_characteristic, TORADIO_characteristic].contains(nil) { + if mqttProxyConnected { + mqttManager.mqttClientProxy?.disconnect() + } sendWantConfig() } } @@ -634,10 +637,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // Use context to pass the radio name with the timer // Use a RunLoop to prevent the timer from running on the main UI thread if UserDefaults.provideLocation { + let interval = UserDefaults.provideLocationInterval > 0 ? UserDefaults.provideLocationInterval : 30 if positionTimer != nil { - positionTimer!.invalidate() } - positionTimer = Timer.scheduledTimer(timeInterval: TimeInterval((UserDefaults.provideLocationInterval )), target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true) + positionTimer = Timer.scheduledTimer(timeInterval: TimeInterval((UserDefaults.provideLocationInterval)), target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true) if positionTimer != nil { RunLoop.current.add(positionTimer!, forMode: .common) } From 9922e19fcedfd2852ea1974682925cc8067f3c71 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 20 Oct 2023 16:14:29 -0700 Subject: [PATCH 08/13] Check for admin channel on settings drop down, hide catalyst map controls that don't work --- Meshtastic/Extensions/UserDefaults.swift | 3 --- Meshtastic/Helpers/BLEManager.swift | 12 +++++++++++- Meshtastic/Protobufs/meshtastic/config.pb.swift | 11 +++++++++++ Meshtastic/Protobufs/meshtastic/mesh.pb.swift | 8 ++++++++ Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift | 9 +++++---- Meshtastic/Views/Settings/Settings.swift | 5 ++++- 6 files changed, 39 insertions(+), 9 deletions(-) diff --git a/Meshtastic/Extensions/UserDefaults.swift b/Meshtastic/Extensions/UserDefaults.swift index 48305399..97e99b98 100644 --- a/Meshtastic/Extensions/UserDefaults.swift +++ b/Meshtastic/Extensions/UserDefaults.swift @@ -123,9 +123,6 @@ extension UserDefaults { UserDefaults.standard.set(newValue, forKey: "enableMapPointsOfInterest") } } - - - static var enableOfflineMaps: Bool { get { UserDefaults.standard.bool(forKey: "enableOfflineMaps") diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 68f65f92..c724662e 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -144,6 +144,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate func disconnectPeripheral(reconnect: Bool = true) { guard let connectedPeripheral = connectedPeripheral else { return } + if mqttProxyConnected { + mqttManager.mqttClientProxy?.disconnect() + } automaticallyReconnect = reconnect centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral) FROMRADIO_characteristic = nil @@ -789,7 +792,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = fromNodeNum meshPacket.wantAck = true var dataMessage = DataMessage() - dataMessage.payload = try! waypoint.serializedData() + do { + dataMessage.payload = try waypoint.serializedData() + } + catch { + // Could not serialiaze the payload + return false + } + dataMessage.portnum = PortNum.waypointApp meshPacket.decoded = dataMessage var toRadio: ToRadio! diff --git a/Meshtastic/Protobufs/meshtastic/config.pb.swift b/Meshtastic/Protobufs/meshtastic/config.pb.swift index f33e0eb7..d04e9003 100644 --- a/Meshtastic/Protobufs/meshtastic/config.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/config.pb.swift @@ -237,6 +237,13 @@ struct Config { /// When used in conjunction with power.is_power_saving = true, nodes will wake up, /// send environment telemetry, and then sleep for telemetry.environment_update_interval seconds. case sensor // = 6 + + /// + /// TAK device role + /// Used for nodes dedicated for connection to an ATAK EUD. + /// Turns off many of the routine broadcasts to favor CoT packet stream + /// from the Meshtastic ATAK plugin -> IMeshService -> Node + case tak // = 7 case UNRECOGNIZED(Int) init() { @@ -252,6 +259,7 @@ struct Config { case 4: self = .repeater case 5: self = .tracker case 6: self = .sensor + case 7: self = .tak default: self = .UNRECOGNIZED(rawValue) } } @@ -265,6 +273,7 @@ struct Config { case .repeater: return 4 case .tracker: return 5 case .sensor: return 6 + case .tak: return 7 case .UNRECOGNIZED(let i): return i } } @@ -1285,6 +1294,7 @@ extension Config.DeviceConfig.Role: CaseIterable { .repeater, .tracker, .sensor, + .tak, ] } @@ -1692,6 +1702,7 @@ extension Config.DeviceConfig.Role: SwiftProtobuf._ProtoNameProviding { 4: .same(proto: "REPEATER"), 5: .same(proto: "TRACKER"), 6: .same(proto: "SENSOR"), + 7: .same(proto: "TAK"), ] } diff --git a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift index c4d94c49..cc2388a8 100644 --- a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift @@ -208,6 +208,10 @@ enum HardwareModel: SwiftProtobuf.Enum { /// Heltec HT-CT62 with ESP32-C3 CPU and SX1262 LoRa case heltecHt62 // = 53 + /// + /// EBYTE SPI LoRa module and ESP32-S3 + case ebyteEsp32S3 // = 54 + /// /// ------------------------------------------------------------------------------------------------------------------------------------------ /// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. @@ -265,6 +269,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case 51: self = .tWatchS3 case 52: self = .picomputerS3 case 53: self = .heltecHt62 + case 54: self = .ebyteEsp32S3 case 255: self = .privateHw default: self = .UNRECOGNIZED(rawValue) } @@ -316,6 +321,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case .tWatchS3: return 51 case .picomputerS3: return 52 case .heltecHt62: return 53 + case .ebyteEsp32S3: return 54 case .privateHw: return 255 case .UNRECOGNIZED(let i): return i } @@ -372,6 +378,7 @@ extension HardwareModel: CaseIterable { .tWatchS3, .picomputerS3, .heltecHt62, + .ebyteEsp32S3, .privateHw, ] } @@ -2534,6 +2541,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 51: .same(proto: "T_WATCH_S3"), 52: .same(proto: "PICOMPUTER_S3"), 53: .same(proto: "HELTEC_HT62"), + 54: .same(proto: "EBYTE_ESP32_S3"), 255: .same(proto: "PRIVATE_HW"), ] } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift index 584d3b53..3546f7ec 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift @@ -353,10 +353,11 @@ struct NodeMapSwiftUI: View { } #if targetEnvironment(macCatalyst) - MapZoomStepper(scope: mapScope) - .mapControlVisibility(.visible) - MapPitchSlider(scope: mapScope) - .mapControlVisibility(.visible) + /// Hide non fuctional catalyst controls +// MapZoomStepper(scope: mapScope) +// .mapControlVisibility(.visible) +// MapPitchSlider(scope: mapScope) +// .mapControlVisibility(.visible) #endif } .controlSize(.regular) diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 8a84f474..59b75425 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -59,6 +59,9 @@ struct Settings: View { } .tag(SettingsSidebar.appSettings) let node = nodes.first(where: { $0.num == connectedNodeNum }) + let hasAdmin = node?.myInfo?.adminIndex ?? 0 > 0 ? true : false + + if !(node?.deviceConfig?.isManaged ?? false) { Section("Configure") { Picker("Configuring Node", selection: $selectedNode) { @@ -72,7 +75,7 @@ struct Settings: View { } else if node.metadata != nil { Text("Remote Config: \(node.user?.longName ?? "unknown".localized)") .tag(Int(node.num)) - } else { + } else if hasAdmin { Text("Request Admin: \(node.user?.longName ?? "unknown".localized)") .tag(Int(node.num)) } From 7f1e2a2f5eec503e98e5779c88d6e9c1565d848f Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 20 Oct 2023 16:49:42 -0700 Subject: [PATCH 09/13] Add unknown age to node list timestamp --- Meshtastic/Views/Helpers/LastHeardText.swift | 2 +- .../Views/Nodes/Helpers/NodeListItem.swift | 2 +- .../Views/Nodes/Helpers/NodeMapSwiftUI.swift | 6 +- Meshtastic/Views/Settings/Settings.swift | 70 +++++++++---------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/Meshtastic/Views/Helpers/LastHeardText.swift b/Meshtastic/Views/Helpers/LastHeardText.swift index 97acce81..02c23f9f 100644 --- a/Meshtastic/Views/Helpers/LastHeardText.swift +++ b/Meshtastic/Views/Helpers/LastHeardText.swift @@ -17,7 +17,7 @@ struct LastHeardText: View { var body: some View { if lastHeard != nil && lastHeard! >= sixMonthsAgo! { - Text("heard")+Text(" \(LastHeardText.formatter.localizedString(for: lastHeard!, relativeTo: Date.now))") + Text(lastHeard?.formatted() ?? "unknown.age".localized) } else { Text("unknown.age") } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index ea49426e..a4b9f1e9 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -54,7 +54,7 @@ struct NodeListItem: View { .font(.callout) .symbolRenderingMode(.hierarchical) .foregroundColor(node.isOnline ? .green : .orange) - Text(node.lastHeard?.formatted() ?? "unknown".localized) + LastHeardText(lastHeard: node.lastHeard) .font(.callout) } if node.positions?.count ?? 0 > 0 && connectedNode != node.num { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift index 3546f7ec..066125e5 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift @@ -293,9 +293,9 @@ struct NodeMapSwiftUI: View { .padding() #endif } - .presentationDetents([.fraction(0.60)]) - //.presentationDetents([.medium, .large]) - .presentationDragIndicator(.automatic) + //.presentationDetents([.fraction(0.60)]) + .presentationDetents([.medium, .large]) + .presentationDragIndicator(.visible) } .onChange(of: node) { let mostRecent = node.positions?.lastObject as? PositionEntity diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 59b75425..a4972a90 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -14,7 +14,6 @@ struct Settings: View { private var nodes: FetchedResults @State private var selectedNode: Int = 0 @State private var connectedNodeNum: Int = 0 - @State private var initialLoad: Bool = true @State private var selection: SettingsSidebar = .about enum SettingsSidebar { case appSettings @@ -60,41 +59,43 @@ struct Settings: View { .tag(SettingsSidebar.appSettings) let node = nodes.first(where: { $0.num == connectedNodeNum }) let hasAdmin = node?.myInfo?.adminIndex ?? 0 > 0 ? true : false - - if !(node?.deviceConfig?.isManaged ?? false) { Section("Configure") { - Picker("Configuring Node", selection: $selectedNode) { - if selectedNode == 0 { - Text("Connect to a Node").tag(0) - } - ForEach(nodes) { node in - if node.num == bleManager.connectedPeripheral?.num ?? 0 { - Text("BLE Config: \(node.user?.longName ?? "unknown".localized)") - .tag(Int(node.num)) - } else if node.metadata != nil { - Text("Remote Config: \(node.user?.longName ?? "unknown".localized)") - .tag(Int(node.num)) - } else if hasAdmin { - Text("Request Admin: \(node.user?.longName ?? "unknown".localized)") - .tag(Int(node.num)) + if hasAdmin { + Picker("Configuring Node", selection: $selectedNode) { + if selectedNode == 0 { + Text("Connect to a Node").tag(0) } - } - } - .pickerStyle(.automatic) - .labelsHidden() - .onChange(of: selectedNode) { newValue in - if selectedNode > 0 { - let node = nodes.first(where: { $0.num == newValue }) - let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) - connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) - if connectedNode != nil && connectedNode?.user != nil && connectedNode?.myInfo != nil && node?.user != nil && node?.metadata == nil { - let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context) - if adminMessageId > 0 { - print("Sent node metadata request from node details") + ForEach(nodes) { node in + if node.num == bleManager.connectedPeripheral?.num ?? 0 { + Text("BLE Config: \(node.user?.longName ?? "unknown".localized)") + .tag(Int(node.num)) + } else if node.metadata != nil { + Text("Remote Config: \(node.user?.longName ?? "unknown".localized)") + .tag(Int(node.num)) + } else if hasAdmin { + Text("Request Admin: \(node.user?.longName ?? "unknown".localized)") + .tag(Int(node.num)) } } } + .pickerStyle(.automatic) + .labelsHidden() + .onChange(of: selectedNode) { newValue in + if selectedNode > 0 { + let node = nodes.first(where: { $0.num == newValue }) + let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) + connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) + if connectedNode != nil && connectedNode?.user != nil && connectedNode?.myInfo != nil && node?.user != nil && node?.metadata == nil { + let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context) + if adminMessageId > 0 { + print("Sent node metadata request from node details") + } + } + } + } + } else { + Text("Configuring Node \(node?.user?.longName ?? "unknown".localized)") } } Section("radio.configuration") { @@ -279,12 +280,11 @@ struct Settings: View { } } .onAppear { - self.bleManager.context = context - self.connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) - if initialLoad { - selectedNode = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) - initialLoad = false + if self.bleManager.context == nil { + self.bleManager.context = context } + self.connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) + selectedNode = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) } .listStyle(GroupedListStyle()) .navigationTitle("settings") From aedf8e363812f8ffa959e91d847353449a880455 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 20 Oct 2023 17:26:04 -0700 Subject: [PATCH 10/13] UI updates to licensed user flow --- Meshtastic/Views/Settings/UserConfig.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index ba7b5d74..47a108e8 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -23,7 +23,7 @@ struct UserConfig: View { @State private var isPresentingSaveConfirm: Bool = false @State var hasChanges = false @State var shortName = "" - @State var longName = "" + @State var longName: String = "" @State var isLicensed = false @State var overrideDutyCycle = false @State var overrideFrequency: Float = 0.0 @@ -157,10 +157,11 @@ struct UserConfig: View { } } else { var ham = HamParameters() - // ham.shortName = shortName + ham.shortName = shortName ham.callSign = longName ham.txPower = Int32(txPower) ham.frequency = overrideFrequency + print(ham) let adminMessageId = bleManager.saveLicensedUser(ham: ham, fromUser: connectedUser, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) if adminMessageId > 0 { hasChanges = false @@ -201,7 +202,14 @@ struct UserConfig: View { } .onChange(of: isLicensed) { newIsLicensed in if node != nil && node!.user != nil { - if newIsLicensed != node?.user!.isLicensed { hasChanges = true } + if newIsLicensed != node?.user!.isLicensed { + hasChanges = true + if newIsLicensed { + if node?.user?.longName?.count ?? 0 > 8 { + longName = "" + } + } + } } } .onChange(of: overrideFrequency) { _ in From d1ad0bfd20b4c3b583dafce322e0475c56acedd5 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 20 Oct 2023 18:55:28 -0700 Subject: [PATCH 11/13] Make waypoint popowers a sheet while I figure out how to position them properly --- .../Views/Nodes/Helpers/NodeMapSwiftUI.swift | 35 +++++++------------ .../Settings/Config/BluetoothConfig.swift | 2 +- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift index 066125e5..42f87496 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift @@ -20,7 +20,6 @@ struct NodeMapSwiftUI: View { @ObservedObject var node: NodeInfoEntity @State var showUserLocation: Bool = false @State var positions: [PositionEntity] = [] - //@State var waypoints: [WaypointEntity] = [] /// Map State User Defaults @AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false @AppStorage("meshMapShowRouteLines") private var showRouteLines = false @@ -30,24 +29,20 @@ struct NodeMapSwiftUI: View { @AppStorage("mapLayer") private var selectedMapLayer: MapLayer = .hybrid // Map Configuration @Namespace var mapScope - @State private var mapStyle: MapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: .all, showsTraffic: true) - @State private var position = MapCameraPosition.automatic - @State private var scene: MKLookAroundScene? - @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 + @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 isEditingSettings = false + @State var selected: PositionEntity? + @State var selectedWaypoint: WaypointEntity? + @State var showingPositionPopover = 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 { @@ -90,15 +85,10 @@ struct NodeMapSwiftUI: View { Annotation(waypoint.name ?? "?", coordinate: waypoint.coordinate) { ZStack { CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "📍"), color: Color.orange, circleSize: 35) - .onTapGesture(coordinateSpace: .global) { location in + + .onTapGesture(coordinateSpace: .named("nodemap")) { location in print("Tapped at \(location)") let pinLocation = reader.convert(location, from: .local) - print(pinLocation) - let size = CGSize(width: 1, height: 50) - let rect = CGRect(origin: location, size: size) - selectedWaypointRect = rect - selectedWaypointPoint = location - showingWaypointPopover = true selectedWaypoint = (selectedWaypoint == waypoint ? nil : waypoint) } } @@ -203,12 +193,11 @@ struct NodeMapSwiftUI: View { .padding(.horizontal, 20) } } - .popover(item: $selectedWaypoint, attachmentAnchor: .rect(.rect(selectedWaypointRect)), arrowEdge: .bottom) { selection in - //.popover(isPresented: $showingWaypointPopover, arrowEdge: .bottom) { + .popover(item: $selectedWaypoint) { selection in WaypointPopover(waypoint: selection) .padding() .opacity(0.8) - .presentationCompactAdaptation(.popover) + .presentationCompactAdaptation(.sheet) } .sheet(isPresented: $isEditingSettings) { VStack { diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 4370f95e..ed79dc5a 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -44,7 +44,7 @@ struct BluetoothConfig: View { setBluetoothValues() } } - } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { + } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? -1 { Text("Configuration for: \(node?.user?.longName ?? "Unknown")") .font(.title3) } else { From cd68873fb205c5242309d369bc6b86b8bf3f2f0e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 20 Oct 2023 19:34:20 -0700 Subject: [PATCH 12/13] Waypoint details updates --- .../Views/Nodes/Helpers/NodeMapSwiftUI.swift | 8 +++----- .../Views/Nodes/Helpers/WaypointPopover.swift | 14 +++++++++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift index 42f87496..aa9dd77f 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift @@ -193,11 +193,11 @@ struct NodeMapSwiftUI: View { .padding(.horizontal, 20) } } - .popover(item: $selectedWaypoint) { selection in + .sheet(item: $selectedWaypoint) { selection in WaypointPopover(waypoint: selection) + .presentationDetents([.fraction(0.2), .medium]) .padding() .opacity(0.8) - .presentationCompactAdaptation(.sheet) } .sheet(isPresented: $isEditingSettings) { VStack { @@ -282,9 +282,7 @@ struct NodeMapSwiftUI: View { .padding() #endif } - //.presentationDetents([.fraction(0.60)]) - .presentationDetents([.medium, .large]) - .presentationDragIndicator(.visible) + .presentationDetents([.fraction(0.4), .medium]) } .onChange(of: node) { let mostRecent = node.positions?.lastObject as? PositionEntity diff --git a/Meshtastic/Views/Nodes/Helpers/WaypointPopover.swift b/Meshtastic/Views/Nodes/Helpers/WaypointPopover.swift index 1c4d8f8d..19d5c309 100644 --- a/Meshtastic/Views/Nodes/Helpers/WaypointPopover.swift +++ b/Meshtastic/Views/Nodes/Helpers/WaypointPopover.swift @@ -9,12 +9,13 @@ import SwiftUI import MapKit struct WaypointPopover: View { + @Environment(\.dismiss) private var dismiss var waypoint: WaypointEntity let distanceFormatter = MKDistanceFormatter() var body: some View { VStack { HStack { - CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "📍"), color: Color.blue) + CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "📍"), color: Color.orange) Text(waypoint.name ?? "?") .font(.title3) if waypoint.locked > 0 { @@ -92,6 +93,17 @@ struct WaypointPopover: View { } } } + #if targetEnvironment(macCatalyst) + Button { + dismiss() + } label: { + Label("close", systemImage: "xmark") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + #endif } .tag(waypoint.id) } From ea4b7548a640321c90628b71af4cce3d41a4728c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 20 Oct 2023 19:59:26 -0700 Subject: [PATCH 13/13] More waypoint popover cleanup --- .../Views/Nodes/Helpers/NodeMapSwiftUI.swift | 2 +- .../Views/Nodes/Helpers/WaypointPopover.swift | 26 ++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift index aa9dd77f..44812c90 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift @@ -195,7 +195,7 @@ struct NodeMapSwiftUI: View { } .sheet(item: $selectedWaypoint) { selection in WaypointPopover(waypoint: selection) - .presentationDetents([.fraction(0.2), .medium]) + .presentationDetents([.fraction(0.3), .medium]) .padding() .opacity(0.8) } diff --git a/Meshtastic/Views/Nodes/Helpers/WaypointPopover.swift b/Meshtastic/Views/Nodes/Helpers/WaypointPopover.swift index 19d5c309..3eae5eb8 100644 --- a/Meshtastic/Views/Nodes/Helpers/WaypointPopover.swift +++ b/Meshtastic/Views/Nodes/Helpers/WaypointPopover.swift @@ -32,7 +32,6 @@ struct WaypointPopover: View { Label { Text(waypoint.longDescription ?? "") .foregroundColor(.primary) - .font(.footnote) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) } icon: { @@ -41,43 +40,57 @@ struct WaypointPopover: View { .frame(width: 35) } .padding(.bottom, 5) + Divider() } + /// Coordinate + Label { + Text("Coordinates: \(String(format: "%.6f", waypoint.coordinate.latitude)), \(String(format: "%.6f", waypoint.coordinate.longitude))") + //.font(.footnote) + .textSelection(.enabled) + .foregroundColor(.primary) + } icon: { + Image(systemName: "mappin.and.ellipse") + .symbolRenderingMode(.hierarchical) + .frame(width: 35) + } + .padding(.bottom, 5) + Divider() /// 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) + Divider() /// 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) + Divider() } - /// Updated + /// Expires 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) + Divider() } /// Distance if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 { @@ -85,12 +98,13 @@ struct WaypointPopover: View { Label { Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway)))") .foregroundColor(.primary) - .font(.footnote) } icon: { Image(systemName: "lines.measurement.horizontal") .symbolRenderingMode(.hierarchical) .frame(width: 35) } + .padding(.bottom, 5) + Divider() } } #if targetEnvironment(macCatalyst)