Skip to content
Snippets Groups Projects
AppDelegate.swift 7.67 KiB
Newer Older
Bruno Muniz's avatar
Bruno Muniz committed
import UIKit
Bruno Muniz's avatar
Bruno Muniz committed
import Defaults
Bruno Muniz's avatar
Bruno Muniz committed
import XXClient
import Dependencies
Ahmed Shehata's avatar
Ahmed Shehata committed
import LaunchFeature
Bruno Muniz's avatar
Bruno Muniz committed
import XXMessengerClient

// MARK: - TO REMOVE FROM PRODUCTION:
import Logging
import PulseUI
import AppNavigation
import PulseLogHandler
// MARK: -
Bruno Muniz's avatar
Bruno Muniz committed
public class AppDelegate: UIResponder, UIApplicationDelegate {
Bruno Muniz's avatar
Bruno Muniz committed
  public var window: UIWindow?
Bruno Muniz's avatar
Bruno Muniz committed
  private var coverView: UIView?
  private var backgroundTimer: Timer?
  private var backgroundTask: UIBackgroundTaskIdentifier?

  @Dependency(\.app.log) var log
Bruno Muniz's avatar
Bruno Muniz committed
  @Dependency(\.navigator) var navigator
  @Dependency(\.app.messenger) var messenger
  @Dependency(\.pushNotificationRouter) var pushNotificationRouter
Bruno Muniz's avatar
Bruno Muniz committed

Bruno Muniz's avatar
Bruno Muniz committed
  @KeyObject(.hideAppList, defaultValue: false) var shouldHideAppInAppList
Bruno Muniz's avatar
Bruno Muniz committed
  @KeyObject(.pushNotifications, defaultValue: false) var isPushNotificationsEnabled
Bruno Muniz's avatar
Bruno Muniz committed
  public func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    LoggingSystem.bootstrap(PersistentLogHandler.init)

Bruno Muniz's avatar
Bruno Muniz committed
    UNUserNotificationCenter.current().delegate = self
    let navController = UINavigationController(rootViewController: LaunchController())
    window = UIWindow(frame: UIScreen.main.bounds)
    window?.rootViewController = RootViewController(navController)
    window?.makeKeyAndVisible()
Bruno Muniz's avatar
Bruno Muniz committed

    pushNotificationRouter.set(.live(navigationController: navController))

//    #if DEBUG
Bruno Muniz's avatar
Bruno Muniz committed
    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
Bruno Muniz's avatar
Bruno Muniz committed
    return true
  }
Bruno Muniz's avatar
Bruno Muniz committed

  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()
  }
Bruno Muniz's avatar
Bruno Muniz committed

  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))
Bruno Muniz's avatar
Bruno Muniz committed
        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))
Bruno Muniz's avatar
Bruno Muniz committed
      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))
Bruno Muniz's avatar
Bruno Muniz committed
          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