import UIKit import AppCore import Defaults import XXClient import Dependencies import LaunchFeature import XXMessengerClient // MARK: - TO REMOVE FROM PRODUCTION: import Logging import PulseUI import AppNavigation import PulseLogHandler // MARK: - public class AppDelegate: UIResponder, UIApplicationDelegate { public var window: UIWindow? private var coverView: UIView? private var backgroundTimer: Timer? private var backgroundTask: UIBackgroundTaskIdentifier? @Dependency(\.app.log) var log @Dependency(\.navigator) var navigator @Dependency(\.app.messenger) var messenger @Dependency(\.pushNotificationRouter) var pushNotificationRouter @KeyObject(.hideAppList, defaultValue: false) var shouldHideAppInAppList @KeyObject(.pushNotifications, defaultValue: false) var isPushNotificationsEnabled public func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { LoggingSystem.bootstrap(PersistentLogHandler.init) UNUserNotificationCenter.current().delegate = self let navController = UINavigationController(rootViewController: LaunchController()) window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = RootViewController(navController) window?.makeKeyAndVisible() pushNotificationRouter.set(.live(navigationController: navController)) // #if DEBUG NotificationCenter.default.addObserver( forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: OperationQueue.main ) { [weak self] _ in guard let self else { return } let pulseViewController = PulseUI.MainViewController(store: .shared) self.navigator.perform( PresentModal( pulseViewController, from: navController.topViewController! ) ) } // #endif return true } public func applicationWillResignActive(_ application: UIApplication) { if shouldHideAppInAppList { coverView?.removeFromSuperview() coverView = UIVisualEffectView(effect: UIBlurEffect(style: .regular)) coverView?.frame = window?.bounds ?? .zero window?.addSubview(coverView!) } } public func applicationDidBecomeActive(_ application: UIApplication) { application.applicationIconBadgeNumber = 0 coverView?.removeFromSuperview() } public func applicationWillEnterForeground(_ application: UIApplication) { resumeMessenger(application) } public func applicationDidEnterBackground(_ application: UIApplication) { stopMessenger(application) } public func application( application: UIApplication, shouldAllowExtensionPointIdentifier identifier: String ) -> Bool { if identifier == UIApplication.ExtensionPointIdentifier.keyboard.rawValue { return false /// Disable custom keyboards } return true } public func application( _ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void ) -> Bool { guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, let incomingURL = userActivity.webpageURL, let username = getUsernameFromInvitationDeepLink(incomingURL), let router = pushNotificationRouter.get() else { return false } router.navigateTo(.search(username: username), {}) return true } } extension AppDelegate: UNUserNotificationCenterDelegate { public func application( _ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { if messenger.isConnected() { do { try messenger.registerForNotifications(token: deviceToken) isPushNotificationsEnabled = true } catch { isPushNotificationsEnabled = false log(.error(error as NSError)) print(error.localizedDescription) } } } public func userNotificationCenter( _ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void ) { let userInfo = response.notification.request.content.userInfo guard let string = userInfo["type"] as? String, let type = NotificationReport.ReportType(rawValue: string) else { completionHandler() return } var route: PushNotificationRouter.Route? switch type { case .e2e, .group: guard let source = userInfo["source"] as? Data else { completionHandler() return } if type == .e2e { route = .contactChat(id: source) } else { route = .groupChat(id: source) } default: break } if let route, let router = pushNotificationRouter.get() { router.navigateTo(route, completionHandler) } } public func application( _ application: UIApplication, didReceiveRemoteNotification notification: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void ) { if application.applicationState == .background, let csv = notification["notificationData"] as? String, let reports = try? messenger.getNotificationReports(notificationCSV: csv) { reports .filter { $0.forMe } .filter { $0.type != .silent } .filter { $0.type != .default } .map { let content = UNMutableNotificationContent() content.badge = 1 content.body = "" content.sound = .default content.userInfo["source"] = $0.source content.userInfo["type"] = $0.type.rawValue content.threadIdentifier = "new_message_identifier" return content }.map { UNNotificationRequest( identifier: Bundle.main.bundleIdentifier!, content: $0, trigger: UNTimeIntervalNotificationTrigger( timeInterval: 1, repeats: false ) ) }.forEach { UNUserNotificationCenter.current().add($0) { error in error == nil ? completionHandler(.newData) : completionHandler(.failed) } } } else { completionHandler(.noData) } } } extension AppDelegate { private func resumeMessenger(_ application: UIApplication) { backgroundTimer?.invalidate() backgroundTimer = nil if let backgroundTask { application.endBackgroundTask(backgroundTask) } do { if messenger.isLoaded() { try messenger.start() } } catch { log(.error(error as NSError)) print(error.localizedDescription) } } private func stopMessenger(_ application: UIApplication) { guard messenger.isLoaded() else { return } backgroundTask = application.beginBackgroundTask(withName: "STOPPING_NETWORK") backgroundTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in guard let self else { return } if application.backgroundTimeRemaining <= 5 { do { self.backgroundTimer?.invalidate() try self.messenger.stop() } catch { self.log(.error(error as NSError)) print(error.localizedDescription) } if let backgroundTask = self.backgroundTask { application.endBackgroundTask(backgroundTask) } } } } } func getUsernameFromInvitationDeepLink(_ url: URL) -> String? { if let components = URLComponents(url: url, resolvingAgainstBaseURL: false), components.scheme == "https", components.host == "elixxir.io", components.path == "/connect", let queryItem = components.queryItems?.first(where: { $0.name == "username" }), let username = queryItem.value { return username } return nil }