// // MeshtasticAppDelegate.swift // Meshtastic // // Created by Ben on 8/20/23. // import SwiftUI import OSLog class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, ObservableObject { var router: Router? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { Logger.services.info("🚀 [App] Meshtstic Apple App launched!") // Default User Default Values UserDefaults.standard.register(defaults: ["meshMapRecentering": true]) UserDefaults.standard.register(defaults: ["meshMapShowNodeHistory": true]) UserDefaults.standard.register(defaults: ["meshMapShowRouteLines": true]) UNUserNotificationCenter.current().delegate = self let locationsHandler = LocationsHandler.shared locationsHandler.startLocationUpdates() // If a background activity session was previously active, reinstantiate it after the background launch. if locationsHandler.backgroundActivity { locationsHandler.backgroundActivity = true } return true } // Lets us show the notification in the app in the foreground func userNotificationCenter( _ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void ) { completionHandler([.list, .banner, .sound]) } // This method is called when a user clicks on the notification func userNotificationCenter( _ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void ) { let userInfo = response.notification.request.content.userInfo switch response.actionIdentifier { case UNNotificationDefaultActionIdentifier: break case "messageNotification.thumbsUpAction": if let channel = userInfo["channel"] as? Int32, let replyID = userInfo["messageId"] as? Int64 { let tapbackResponse = !BLEManager.shared.sendMessage( message: Tapbacks.thumbsUp.emojiString, toUserNum: userInfo["userNum"] as? Int64 ?? 0, channel: channel, isEmoji: true, replyID: replyID ) Logger.services.info("Tapback response sent") } else { Logger.services.error("Failed to retrieve channel or messageId from userInfo") } case "messageNotification.thumbsDownAction": if let channel = userInfo["channel"] as? Int32, let replyID = userInfo["messageId"] as? Int64 { let tapbackResponse = !BLEManager.shared.sendMessage( message: Tapbacks.thumbsDown.emojiString, toUserNum: userInfo["userNum"] as? Int64 ?? 0, channel: channel, isEmoji: true, replyID: replyID ) Logger.services.info("Tapback response sent") } else { Logger.services.error("Failed to retrieve channel or messageId from userInfo") } case "messageNotification.replyInputAction": if let userInput = (response as? UNTextInputNotificationResponse)?.userText, let channel = userInfo["channel"] as? Int32, let replyID = userInfo["messageId"] as? Int64 { let tapbackResponse = !BLEManager.shared.sendMessage( message: userInput, toUserNum: userInfo["userNum"] as? Int64 ?? 0, channel: channel, isEmoji: false, replyID: replyID ) Logger.services.info("Actionable notification reply sent") } else { Logger.services.error("Failed to retrieve user input, channel, or messageId from userInfo") } default: break } if let targetValue = userInfo["target"] as? String, let deepLink = userInfo["path"] as? String, let url = URL(string: deepLink) { Logger.services.info("userNotificationCenter didReceiveResponse handling deeplink: \(targetValue, privacy: .public) \(deepLink, privacy: .public)") // Handle TraceRoute notifications specially to ensure they navigate correctly if deepLink.contains("meshtastic:///nodes") && deepLink.contains("nodenum=") { // First extract the node number from the URL if let nodeNumString = deepLink.components(separatedBy: "nodenum=").last, let nodeNum = Int64(nodeNumString) { Logger.services.info("Navigation to specific node via notification: \(nodeNum, privacy: .public)") self.router?.navigationState.selectedTab = .nodes // Post a notification to trigger app-wide refresh NotificationCenter.default.post(name: NSNotification.Name("ForceNavigationRefresh"), object: nil, userInfo: ["nodeNum": nodeNum]) self.router?.navigationState.nodeListSelectedNodeNum = nodeNum } } // Still call the regular router in all cases router?.route(url: url) } else { Logger.services.error("Failed to handle notification response: \(userInfo, privacy: .public)") } completionHandler() } }