From 8bcf40f54302c72a17a7f3aedd9eb8d061941c47 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 6 May 2024 23:03:51 -0700 Subject: [PATCH] Deep Links Updated position precision --- Meshtastic.xcodeproj/project.pbxproj | 20 ++++ Meshtastic/DeepLinks/DeepLinkManager.swift | 100 ++++++++++++++++++ Meshtastic/Helpers/BLEManager.swift | 10 +- Meshtastic/Helpers/MeshPackets.swift | 2 +- Meshtastic/Info.plist | 2 +- Meshtastic/MeshtasticApp.swift | 11 +- Meshtastic/Persistence/UpdateCoreData.swift | 2 +- Meshtastic/Views/ContentView.swift | 26 +++-- Meshtastic/Views/Messages/Messages.swift | 6 ++ .../Map/MapContent/MeshMapContent.swift | 2 +- .../Map/MapContent/NodeMapContent.swift | 2 +- Meshtastic/Views/Nodes/NodeList.swift | 5 + 12 files changed, 173 insertions(+), 15 deletions(-) create mode 100644 Meshtastic/DeepLinks/DeepLinkManager.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index a96e3707..3ec5015c 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -128,6 +128,7 @@ DDA9515A2BC6624100CEA535 /* TelemetryWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA951592BC6624100CEA535 /* TelemetryWeather.swift */; }; DDA9515C2BC6631200CEA535 /* TelemetryEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */; }; DDA9515E2BC6F56F00CEA535 /* IndoorAirQuality.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */; }; + DDAA632E2BE7F84E00CC22D3 /* DeepLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAA632D2BE7F84E00CC22D3 /* DeepLinkManager.swift */; }; DDAB580D2B0DAA9E00147258 /* Routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAB580C2B0DAA9E00147258 /* Routes.swift */; }; DDAB580F2B0DAFBC00147258 /* LocationEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */; }; DDAD49ED2AFB39DC00B4425D /* MeshMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAD49EC2AFB39DC00B4425D /* MeshMap.swift */; }; @@ -386,6 +387,7 @@ DDA951592BC6624100CEA535 /* TelemetryWeather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelemetryWeather.swift; sourceTree = ""; }; DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryEnums.swift; sourceTree = ""; }; DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndoorAirQuality.swift; sourceTree = ""; }; + DDAA632D2BE7F84E00CC22D3 /* DeepLinkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkManager.swift; sourceTree = ""; }; DDAB580B2B0D913500147258 /* MeshtasticDataModelV20.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV20.xcdatamodel; sourceTree = ""; }; DDAB580C2B0DAA9E00147258 /* Routes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Routes.swift; sourceTree = ""; }; DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationEntityExtension.swift; sourceTree = ""; }; @@ -573,6 +575,13 @@ path = CoreData; sourceTree = ""; }; + DD17C4D42BE7EC2200D45AC7 /* SwiftData */ = { + isa = PBXGroup; + children = ( + ); + path = SwiftData; + sourceTree = ""; + }; DD47E3CA26F0E50300029299 /* Nodes */ = { isa = PBXGroup; children = ( @@ -757,6 +766,14 @@ path = Channels; sourceTree = ""; }; + DDAA632C2BE7F83400CC22D3 /* DeepLinks */ = { + isa = PBXGroup; + children = ( + DDAA632D2BE7F84E00CC22D3 /* DeepLinkManager.swift */, + ); + path = DeepLinks; + sourceTree = ""; + }; DDAD49EB2AFAE82500B4425D /* Map */ = { isa = PBXGroup; children = ( @@ -818,6 +835,7 @@ children = ( DD7709392AA1ABA1007A8BF0 /* Tips */, DD90860A26F645B700DC5189 /* Meshtastic.entitlements */, + DDAA632C2BE7F83400CC22D3 /* DeepLinks */, DD8ED9C6289CE4A100B3B0AB /* Enums */, DD86D40D2881BDB300BAEB7A /* Export */, DDDB443E29F79A9400EE2349 /* Extensions */, @@ -950,6 +968,7 @@ DDC4D5662754996200A4208E /* Persistence */ = { isa = PBXGroup; children = ( + DD17C4D42BE7EC2200D45AC7 /* SwiftData */, DDC4D567275499A500A4208E /* Persistence.swift */, DD964FC52975DBFD007C176F /* QueryCoreData.swift */, DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */, @@ -1234,6 +1253,7 @@ DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */, DD15E4F32B8BA56E00654F61 /* PaxCounterConfig.swift in Sources */, DDDB445229F8ACF900EE2349 /* Date.swift in Sources */, + DDAA632E2BE7F84E00CC22D3 /* DeepLinkManager.swift in Sources */, DDC4D568275499A500A4208E /* Persistence.swift in Sources */, DDD6EEAF29BC024700383354 /* Firmware.swift in Sources */, DD77093B2AA1ABB8007A8BF0 /* BluetoothTips.swift in Sources */, diff --git a/Meshtastic/DeepLinks/DeepLinkManager.swift b/Meshtastic/DeepLinks/DeepLinkManager.swift new file mode 100644 index 00000000..037f50a1 --- /dev/null +++ b/Meshtastic/DeepLinks/DeepLinkManager.swift @@ -0,0 +1,100 @@ +// +// DeepLinkManager.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 5/5/24. +// + +import Foundation + +protocol DeepLinkTabManager { + func handle(deepLink: String, selectedTab: inout Tab) -> Bool +} + +@available(iOS 17.0, *) +@Observable +class DeepLinkManager { + var selectedTab: Tab = .ble + var features: [DeepLinkTabManager] + + init() { + self.features = [ + DeepLinkManagerMessages(), + DeepLinkManagerBluetooth(), + DeepLinkManagerNodes(), + DeepLinkManagerMap(), + DeepLinkManagerSettings() + ] + } + + func handleDeepLink(deepLink: String) { + for handler in features { + if handler.handle(deepLink: deepLink, selectedTab: &selectedTab) { + return + } + } + } +} + +class DeepLinkManagerBluetooth: DeepLinkTabManager { + func handle(deepLink: String, selectedTab: inout Tab) -> Bool { + if deepLink.contains("bluetooth") { + selectedTab = .ble + return true + } + return false + } +} + +class DeepLinkManagerMessages: DeepLinkTabManager { + + var channel: String = "" + var messageId: String = "" + + func handle(deepLink: String, selectedTab: inout Tab) -> Bool { + if deepLink.contains("messages") { + selectedTab = .messages + extractData(from: deepLink) + return true + } + + return false + } + private func extractData(from deepLink: String) { + let temp = deepLink.replacingOccurrences(of: "meshtastic://messages?", with: "") + let params = temp.components(separatedBy: "&") + guard params.count == 2 else { return } + channel = params[0].replacingOccurrences(of: "channel=", with: "") + messageId = params[1].replacingOccurrences(of: "messageId=", with: "") + } +} + +class DeepLinkManagerMap: DeepLinkTabManager { + func handle(deepLink: String, selectedTab: inout Tab) -> Bool { + if deepLink.contains("map") { + selectedTab = .map + return true + } + return false + } +} + +class DeepLinkManagerNodes: DeepLinkTabManager { + func handle(deepLink: String, selectedTab: inout Tab) -> Bool { + if deepLink.contains("nodes") { + selectedTab = .nodes + return true + } + return false + } +} + +class DeepLinkManagerSettings: DeepLinkTabManager { + func handle(deepLink: String, selectedTab: inout Tab) -> Bool { + if deepLink.contains("settings") { + selectedTab = .settings + return true + } + return false + } +} diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 8fcae1e4..3c5b4d51 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -5,6 +5,14 @@ import SwiftUI import MapKit import CocoaMQTT +public protocol DeviceConnection: AnyObject { + var isSubscribed: Bool { get } + var invalidVersion: Bool { get } + //var handledDeepLinks: [DeepLink.Type] { get } + //func canHandle(deepLink: DeepLink) -> Bool + //func handle(deepLink: DeepLink) +} + // --------------------------------------------------------------------------------------- // Meshtastic BLE Device Manager // --------------------------------------------------------------------------------------- @@ -20,7 +28,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var context: NSManagedObjectContext? static let shared = BLEManager() - //var userSettings: UserSettings? private var centralManager: CBCentralManager! @Published var peripherals: [Peripheral] = [] @Published var connectedPeripheral: Peripheral! @@ -2633,6 +2640,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate newMessage.fromUser = fromUser newMessage.toUser = toUser + do { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) try context!.save() diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index f61d1994..cad532fa 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -905,7 +905,7 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")", content: messageText!, target: "message", - path: "meshtastic://messages/channel/\(newMessage.messageId)") + path: "meshtastic://messages?channel=\(newMessage.channel)&messageId=\(newMessage.messageId)") ] manager.schedule() print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)") diff --git a/Meshtastic/Info.plist b/Meshtastic/Info.plist index a2fde04b..2e319241 100644 --- a/Meshtastic/Info.plist +++ b/Meshtastic/Info.plist @@ -35,7 +35,7 @@ CFBundleTypeRole - Viewer + Editor CFBundleURLIconFile alpha CFBundleURLName diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index 0b8275dd..cd21ec67 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -6,9 +6,12 @@ import CoreData import TipKit #endif +@available(iOS 17.0, *) @main struct MeshtasticAppleApp: App { + let deepLinkManager = DeepLinkManager() + @UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) var appDelegate let persistenceController = PersistenceController.shared @ObservedObject private var bleManager: BLEManager = BLEManager.shared @@ -23,7 +26,7 @@ struct MeshtasticAppleApp: App { var body: some Scene { WindowGroup { - ContentView() + ContentView(deepLinkManager: deepLinkManager) .environment(\.managedObjectContext, persistenceController.container.viewContext) .environmentObject(bleManager) .sheet(isPresented: $saveChannels) { @@ -53,6 +56,10 @@ struct MeshtasticAppleApp: App { } } .onOpenURL(perform: { (url) in + + if url.absoluteString.lowercased().contains("meshtastic://") { + deepLinkManager.handleDeepLink(deepLink: url.absoluteString.lowercased()) + } print("Some sort of URL was received \(url)") self.incomingUrl = url @@ -62,6 +69,8 @@ struct MeshtasticAppleApp: App { } self.saveChannels = true print("User wants to open a Channel Settings URL: \(self.incomingUrl?.absoluteString ?? "No QR Code Link")") + } else if url.absoluteString.lowercased().contains("meshtastic://") { + deepLinkManager.handleDeepLink(deepLink: url.absoluteString.lowercased()) } else { saveChannels = false print("User wants to import a MBTILES offline map file: \(self.incomingUrl?.absoluteString ?? "No Tiles link")") diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index e9114b7d..87b11cb3 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -198,7 +198,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) subtitle: "\(newUser.longName ?? "unknown".localized)", content: "New Node has been discovered", target: "nodeInfo", - path: "meshtastic://nodeInfo" + path: "meshtastic://nodes?num=\(newUser.num)" ) ] manager.schedule() diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index 75e6fb8e..ffd8feee 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -4,11 +4,14 @@ import SwiftUI +@available(iOS 17.0, *) struct ContentView: View { + @State var deepLinkManager: DeepLinkManager + @StateObject var appState = AppState.shared var body: some View { TabView(selection: $appState.tabSelection) { - Messages() + Messages(deepLinkManager: deepLinkManager.features[0] as? DeepLinkManagerMessages) .tabItem { Label("messages", systemImage: "message") } @@ -19,7 +22,7 @@ struct ContentView: View { Label("bluetooth", systemImage: "antenna.radiowaves.left.and.right") } .tag(Tab.ble) - NodeList() + NodeList(deepLinkManager: deepLinkManager.features[2] as? DeepLinkManagerNodes) .tabItem { Label("nodes", systemImage: "flipphone") } @@ -54,14 +57,21 @@ struct ContentView: View { } } } +//#Preview { +// if #available(iOS 17.0, *) { +// // ContentView(deepLinkManager: .init()) +// } else { +// // Fallback on earlier versions +// } +//} -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} +//struct ContentView_Previews: PreviewProvider { +// static var previews: some View { +// ContentView() +// } +//} -enum Tab { +enum Tab: Hashable { case contacts case messages case map diff --git a/Meshtastic/Views/Messages/Messages.swift b/Meshtastic/Views/Messages/Messages.swift index 879a361c..871923ac 100644 --- a/Meshtastic/Views/Messages/Messages.swift +++ b/Meshtastic/Views/Messages/Messages.swift @@ -12,6 +12,8 @@ import TipKit #endif struct Messages: View { + + @State var deepLinkManager: DeepLinkManagerMessages @StateObject var appState = AppState.shared @Environment(\.managedObjectContext) var context @@ -27,6 +29,10 @@ struct Messages: View { case groupMessages case directMessages } + + init (deepLinkManager: DeepLinkManagerMessages? = nil) { + self.deepLinkManager = deepLinkManager ?? .init() + } var body: some View { diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift index 52d3a97c..cbb50828 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift @@ -142,7 +142,7 @@ struct MeshMapContent: MapContent { } } /// Reduced Precision Map Circles - if 2...24 ~= position.precisionBits { + if 10...19 ~= position.precisionBits { let pp = PositionPrecision(rawValue: Int(position.precisionBits)) let radius : CLLocationDistance = pp?.precisionMeters ?? 0 if radius > 0.0 { diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift index fd869c30..64cc43a8 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift @@ -49,7 +49,7 @@ struct NodeMapContent: MapContent { let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 771)) let headingDegrees = Angle.degrees(Double(position.heading)) /// Reduced Precision Map Circle - if position.latest && 2...24 ~= position.precisionBits { + if position.latest && 10...19 ~= position.precisionBits { let pp = PositionPrecision(rawValue: Int(position.precisionBits)) let radius : CLLocationDistance = pp?.precisionMeters ?? 0 if radius > 0.0 { diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 588fec7a..1f11b567 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -9,6 +9,7 @@ import CoreLocation struct NodeList: View { + @State var deepLinkManager: DeepLinkManagerNodes @State private var columnVisibility = NavigationSplitViewVisibility.all @State private var selectedNode: NodeInfoEntity? @State private var isPresentingTraceRouteSentAlert = false @@ -41,6 +42,10 @@ struct NodeList: View { var nodes: FetchedResults + init (deepLinkManager: DeepLinkManagerNodes? = nil) { + self.deepLinkManager = deepLinkManager ?? .init() + } + var body: some View { NavigationSplitView(columnVisibility: $columnVisibility) {