AppDelegate.swift 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. //
  2. // AppDelegate.swift
  3. // Mastodon
  4. //
  5. // Created by MainasuK Cirno on 2021/1/22.
  6. //
  7. import os.log
  8. import UIKit
  9. import UserNotifications
  10. import AVFoundation
  11. import MastodonCore
  12. import MastodonUI
  13. @main
  14. class AppDelegate: UIResponder, UIApplicationDelegate {
  15. let appContext = AppContext()
  16. func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  17. AppSecret.default.register()
  18. // configure appearance
  19. ThemeService.shared.apply(theme: ThemeService.shared.currentTheme.value)
  20. // configure AudioSession
  21. try? AVAudioSession.sharedInstance().setCategory(.ambient)
  22. // Update app version info. See: `Settings.bundle`
  23. UserDefaults.standard.setValue(UIApplication.appVersion(), forKey: "Mastodon.appVersion")
  24. UserDefaults.standard.setValue(UIApplication.appBuild(), forKey: "Mastodon.appBundle")
  25. // Setup notification
  26. UNUserNotificationCenter.current().delegate = self
  27. application.registerForRemoteNotifications()
  28. // increase app process count
  29. var count = UserDefaults.shared.processCompletedCount
  30. count += 1 // Int64. could ignore overflow here
  31. UserDefaults.shared.processCompletedCount = count
  32. return true
  33. }
  34. // MARK: UISceneSession Lifecycle
  35. func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
  36. // Called when a new scene session is being created.
  37. // Use this method to select a configuration to create the new scene with.
  38. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
  39. }
  40. func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
  41. // Called when the user discards a scene session.
  42. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
  43. // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
  44. }
  45. func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
  46. return true
  47. }
  48. }
  49. extension AppDelegate {
  50. func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
  51. return UIDevice.current.userInterfaceIdiom == .phone ? .portrait : .all
  52. }
  53. }
  54. extension AppDelegate {
  55. func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
  56. appContext.notificationService.deviceToken.value = deviceToken
  57. }
  58. }
  59. // MARK: - UNUserNotificationCenterDelegate
  60. extension AppDelegate: UNUserNotificationCenterDelegate {
  61. // notification present in the foreground
  62. func userNotificationCenter(
  63. _ center: UNUserNotificationCenter,
  64. willPresent notification: UNNotification,
  65. withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
  66. ) {
  67. os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Push Notification]", ((#file as NSString).lastPathComponent), #line, #function)
  68. guard let pushNotification = AppDelegate.mastodonPushNotification(from: notification) else {
  69. completionHandler([])
  70. return
  71. }
  72. let notificationID = String(pushNotification.notificationID)
  73. os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Push Notification] notification %s", ((#file as NSString).lastPathComponent), #line, #function, notificationID)
  74. let accessToken = pushNotification.accessToken
  75. UserDefaults.shared.increaseNotificationCount(accessToken: accessToken)
  76. appContext.notificationService.applicationIconBadgeNeedsUpdate.send()
  77. appContext.notificationService.handle(pushNotification: pushNotification)
  78. completionHandler([.sound])
  79. }
  80. // notification present in the background (or resume from background)
  81. func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) async -> UIBackgroundFetchResult {
  82. let shortcutItems = try? await appContext.notificationService.unreadApplicationShortcutItems()
  83. UIApplication.shared.shortcutItems = shortcutItems
  84. return .noData
  85. }
  86. // response to user action for notification (e.g. redirect to post)
  87. func userNotificationCenter(
  88. _ center: UNUserNotificationCenter,
  89. didReceive response: UNNotificationResponse,
  90. withCompletionHandler completionHandler: @escaping () -> Void
  91. ) {
  92. os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Push Notification]", ((#file as NSString).lastPathComponent), #line, #function)
  93. guard let pushNotification = AppDelegate.mastodonPushNotification(from: response.notification) else {
  94. completionHandler()
  95. return
  96. }
  97. let notificationID = String(pushNotification.notificationID)
  98. os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Push Notification] notification %s", ((#file as NSString).lastPathComponent), #line, #function, notificationID)
  99. appContext.notificationService.handle(pushNotification: pushNotification)
  100. appContext.notificationService.requestRevealNotificationPublisher.send(pushNotification)
  101. completionHandler()
  102. }
  103. private static func mastodonPushNotification(from notification: UNNotification) -> MastodonPushNotification? {
  104. guard let plaintext = notification.request.content.userInfo["plaintext"] as? Data,
  105. let mastodonPushNotification = try? JSONDecoder().decode(MastodonPushNotification.self, from: plaintext) else {
  106. return nil
  107. }
  108. return mastodonPushNotification
  109. }
  110. }
  111. extension AppContext {
  112. static var shared: AppContext {
  113. let appDelegate = UIApplication.shared.delegate as! AppDelegate
  114. return appDelegate.appContext
  115. }
  116. }