diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index b54367e9..bf5f71ba 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */; }; C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */; }; C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; }; DD0D3D222A55CEB10066DB71 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = DD0D3D212A55CEB10066DB71 /* CocoaMQTT */; }; @@ -195,6 +196,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAppDelegate.swift; sourceTree = ""; }; A65FA974296876BF00A97686 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMBTileOverlay.swift; sourceTree = ""; }; DD0E9C222A30CE3A00580CBB /* MeshtasticDataModelV14.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV14.xcdatamodel; sourceTree = ""; }; @@ -625,6 +627,7 @@ DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */, DDC2E16526CE248F0042C5E4 /* Info.plist */, DDC2E15D26CE248F0042C5E4 /* Preview Content */, + 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */, ); path = Meshtastic; sourceTree = ""; @@ -1058,6 +1061,7 @@ DD5E520D298EE33B00D21B61 /* storeforward.pb.swift in Sources */, DD882F5D2772E4640005BF05 /* Contacts.swift in Sources */, DD964FC2297272AE007C176F /* WaypointEntityExtension.swift in Sources */, + 6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */, DD47E3CE26F103C600029299 /* NodeList.swift in Sources */, DD5E520A298EE33B00D21B61 /* channel.pb.swift in Sources */, DDDEE5E129DA3E1100A8E078 /* NodeInfoView.swift in Sources */, diff --git a/Meshtastic/Helpers/LocalNotificationManager.swift b/Meshtastic/Helpers/LocalNotificationManager.swift index d6bea32b..c5efe48e 100644 --- a/Meshtastic/Helpers/LocalNotificationManager.swift +++ b/Meshtastic/Helpers/LocalNotificationManager.swift @@ -10,16 +10,16 @@ class LocalNotificationManager { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in if granted == true && error == nil { - self.scheduleNotifications() + self.scheduleNotifications() } } } - func schedule() { + func schedule() { UNUserNotificationCenter.current().getNotificationSettings { settings in switch settings.authorizationStatus { case .notDetermined: - self.requestAuthorization() + self.requestAuthorization() case .authorized, .provisional: self.scheduleNotifications() default: @@ -37,6 +37,9 @@ class LocalNotificationManager { content.body = notification.content content.sound = .default content.interruptionLevel = .timeSensitive + if notification.target != nil { + content.userInfo["target"] = notification.target + } let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: trigger) @@ -65,4 +68,5 @@ struct Notification { var title: String var subtitle: String var content: String + var target: String? } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 062bddbd..c3384193 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -877,7 +877,9 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) { id: ("notification.id.\(waypoint.id)"), title: "New Waypoint Received", subtitle: "\(icon) \(waypoint.name ?? "Dropped Pin")", - content: "\(waypoint.longDescription ?? "\(latitude), \(longitude)")") + content: "\(waypoint.longDescription ?? "\(latitude), \(longitude)")", + target: "waypoint" + ) ] manager.schedule() } catch { diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index 49046122..3325a45b 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -4,8 +4,8 @@ import SwiftUI import CoreData @main -struct MeshtasticAppleApp: App { - +struct MeshtasticAppleApp : App { + @UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) var appDelegate let persistenceController = PersistenceController.shared @ObservedObject private var bleManager: BLEManager = BLEManager() @Environment(\.scenePhase) var scenePhase @@ -13,10 +13,11 @@ struct MeshtasticAppleApp: App { @State var saveChannels = false @State var incomingUrl: URL? @State var channelSettings: String? - + @StateObject var appState = AppState.shared + var body: some Scene { WindowGroup { - ContentView() + ContentView() .environment(\.managedObjectContext, persistenceController.container.viewContext) .environmentObject(bleManager) .sheet(isPresented: $saveChannels) { @@ -45,7 +46,6 @@ struct MeshtasticAppleApp: App { print("Some sort of URL was received \(url)") self.incomingUrl = url - if url.absoluteString.lowercased().contains("meshtastic.org/e/#") { if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") { self.channelSettings = components.last! @@ -115,5 +115,11 @@ struct MeshtasticAppleApp: App { print("💥 Apple must have changed something") } } - } + } +} + +class AppState: ObservableObject { + static let shared = AppState() + + @Published var tabSelection: Tab = .ble } diff --git a/Meshtastic/MeshtasticAppDelegate.swift b/Meshtastic/MeshtasticAppDelegate.swift new file mode 100644 index 00000000..c04a1b51 --- /dev/null +++ b/Meshtastic/MeshtasticAppDelegate.swift @@ -0,0 +1,37 @@ +// +// MeshtasticAppDelegate.swift +// Meshtastic +// +// Created by Ben on 8/20/23. +// + +import SwiftUI + +class MeshtasticAppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + print("App launched!") + UNUserNotificationCenter.current().delegate = self + return true + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + } + + // This method is called when user clicked on the notification + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) + { + let userInfo = response.notification.request.content.userInfo + if let targetValue = userInfo["target"] as? String, targetValue == "waypoint" + { + openWaypoint() + } + + completionHandler() + } + + private func openWaypoint() + { + AppState.shared.tabSelection = Tab.map + } +} diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index 9ca1e2b7..f630d195 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -5,22 +5,11 @@ import SwiftUI struct ContentView: View { - - @State private var selection: Tab = .ble - - enum Tab { - case contacts - case messages - case map - case ble - case nodes - case settings - } - + @StateObject var appState = AppState.shared + var body: some View { - - TabView(selection: $selection) { - + + TabView(selection: $appState.tabSelection) { Contacts() .tabItem { Label("messages", systemImage: "message") @@ -55,3 +44,12 @@ struct ContentView_Previews: PreviewProvider { ContentView() } } + +enum Tab { + case contacts + case messages + case map + case ble + case nodes + case settings +}