mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			281 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			281 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Swift
		
	
| //
 | |
| //  Copyright (c) 2019 Open Whisper Systems. All rights reserved.
 | |
| //
 | |
| 
 | |
| import Foundation
 | |
| import PromiseKit
 | |
| 
 | |
| struct LegacyNotificationConfig {
 | |
| 
 | |
|     static var allNotificationCategories: Set<UIUserNotificationCategory> {
 | |
|         let categories = AppNotificationCategory.allCases.map { notificationCategory($0) }
 | |
|         return Set(categories)
 | |
|     }
 | |
| 
 | |
|     static func notificationActions(for category: AppNotificationCategory) -> [UIUserNotificationAction] {
 | |
|         return category.actions.map { notificationAction($0) }
 | |
|     }
 | |
| 
 | |
|     static func notificationAction(_ action: AppNotificationAction) -> UIUserNotificationAction {
 | |
|         switch action {
 | |
|         case .markAsRead:
 | |
|             let mutableAction = UIMutableUserNotificationAction()
 | |
|             mutableAction.identifier = action.identifier
 | |
|             mutableAction.title = MessageStrings.markAsReadNotificationAction
 | |
|             mutableAction.activationMode = .background
 | |
|             mutableAction.isDestructive = false
 | |
|             mutableAction.isAuthenticationRequired = false
 | |
|             return mutableAction
 | |
|         case .reply:
 | |
|             let mutableAction = UIMutableUserNotificationAction()
 | |
|             mutableAction.identifier = action.identifier
 | |
|             mutableAction.title = MessageStrings.replyNotificationAction
 | |
|             mutableAction.activationMode = .background
 | |
|             mutableAction.isDestructive = false
 | |
|             mutableAction.isAuthenticationRequired = false
 | |
|             mutableAction.behavior = .textInput
 | |
|             return mutableAction
 | |
|         case .showThread:
 | |
|             let mutableAction = UIMutableUserNotificationAction()
 | |
|             mutableAction.identifier = action.identifier
 | |
|             mutableAction.title = CallStrings.showThreadButtonTitle
 | |
|             mutableAction.activationMode = .foreground
 | |
|             mutableAction.isDestructive = false
 | |
|             mutableAction.isAuthenticationRequired = true
 | |
|             return mutableAction
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     static func action(identifier: String) -> AppNotificationAction? {
 | |
|         return AppNotificationAction.allCases.first { notificationAction($0).identifier == identifier }
 | |
|     }
 | |
| 
 | |
|     static func notificationActions(category: AppNotificationCategory) -> [UIUserNotificationAction] {
 | |
|         return category.actions.map { notificationAction($0) }
 | |
|     }
 | |
| 
 | |
|     static func notificationCategory(_ category: AppNotificationCategory) -> UIUserNotificationCategory {
 | |
|         let notificationCategory = UIMutableUserNotificationCategory()
 | |
|         notificationCategory.identifier = category.identifier
 | |
| 
 | |
|         let actions = notificationActions(category: category)
 | |
|         notificationCategory.setActions(actions, for: .minimal)
 | |
|         notificationCategory.setActions(actions, for: .default)
 | |
| 
 | |
|         return notificationCategory
 | |
|     }
 | |
| }
 | |
| 
 | |
| class LegacyNotificationPresenterAdaptee {
 | |
| 
 | |
|     private var notifications: [String: UILocalNotification] = [:]
 | |
|     private var userNotificationSettingsPromise: Promise<Void>?
 | |
|     private var userNotificationSettingsResolver: Resolver<Void>?
 | |
| 
 | |
|     // Notification registration is confirmed via AppDelegate
 | |
|     // Before this occurs, it is not safe to assume push token requests will be acknowledged.
 | |
|     //
 | |
|     // e.g. in the case that Background Fetch is disabled, token requests will be ignored until
 | |
|     // we register user notification settings.
 | |
|     @objc
 | |
|     public func didRegisterUserNotificationSettings() {
 | |
|         AssertIsOnMainThread()
 | |
|         guard let userNotificationSettingsResolver = self.userNotificationSettingsResolver else {
 | |
|             owsFailDebug("promise completion in \(#function) unexpectedly nil")
 | |
|             return
 | |
|         }
 | |
| 
 | |
|         userNotificationSettingsResolver.fulfill(())
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| extension LegacyNotificationPresenterAdaptee: NotificationPresenterAdaptee {
 | |
| 
 | |
|     func registerNotificationSettings() -> Promise<Void> {
 | |
|         AssertIsOnMainThread()
 | |
|         Logger.debug("")
 | |
| 
 | |
|         guard self.userNotificationSettingsPromise == nil else {
 | |
|             let promise = self.userNotificationSettingsPromise!
 | |
|             Logger.info("already registered user notification settings")
 | |
|             return promise
 | |
|         }
 | |
| 
 | |
|         let (promise, resolver) = Promise<Void>.pending()
 | |
|         self.userNotificationSettingsPromise = promise
 | |
|         self.userNotificationSettingsResolver = resolver
 | |
| 
 | |
|         let settings = UIUserNotificationSettings(types: [.alert, .sound, .badge],
 | |
|                                                   categories: LegacyNotificationConfig.allNotificationCategories)
 | |
|         UIApplication.shared.registerUserNotificationSettings(settings)
 | |
| 
 | |
|         return promise
 | |
|     }
 | |
| 
 | |
|     func notify(category: AppNotificationCategory, title: String?, body: String, userInfo: [AnyHashable: Any], sound: OWSSound?) {
 | |
|         AssertIsOnMainThread()
 | |
|         notify(category: category, title: title, body: body, userInfo: userInfo, sound: sound, replacingIdentifier: nil)
 | |
|     }
 | |
| 
 | |
|     func notify(category: AppNotificationCategory, title: String?, body: String, userInfo: [AnyHashable: Any], sound: OWSSound?, replacingIdentifier: String?) {
 | |
|         AssertIsOnMainThread()
 | |
|         guard UIApplication.shared.applicationState != .active else {
 | |
|             if let sound = sound {
 | |
|                 let soundId = OWSSounds.systemSoundID(for: sound, quiet: true)
 | |
| 
 | |
|                 // Vibrate, respect silent switch, respect "Alert" volume, not media volume.
 | |
|                 AudioServicesPlayAlertSound(soundId)
 | |
|             }
 | |
|             return
 | |
|         }
 | |
| 
 | |
|         let alertBody: String
 | |
|         if let title = title {
 | |
|             // TODO - Make this a format string for better l10n
 | |
|             alertBody = title.rtlSafeAppend(":").rtlSafeAppend(" ").rtlSafeAppend(body)
 | |
|         } else {
 | |
|             alertBody = body
 | |
|         }
 | |
| 
 | |
|         let notification = UILocalNotification()
 | |
|         notification.category = category.identifier
 | |
|         notification.alertBody = alertBody.filterForDisplay
 | |
|         notification.userInfo = userInfo
 | |
|         notification.soundName = sound?.filename
 | |
| 
 | |
|         var notificationIdentifier: String = UUID().uuidString
 | |
|         if let replacingIdentifier = replacingIdentifier {
 | |
|             notificationIdentifier = replacingIdentifier
 | |
|             Logger.debug("replacing notification with identifier: \(notificationIdentifier)")
 | |
|             cancelNotification(identifier: notificationIdentifier)
 | |
|         }
 | |
| 
 | |
|         let checkForCancel = category == .incomingMessage
 | |
|         if checkForCancel {
 | |
|             assert(userInfo[AppNotificationUserInfoKey.threadId] != nil)
 | |
|             notification.fireDate = Date(timeIntervalSinceNow: kNotificationDelayForRemoteRead)
 | |
|             notification.timeZone = NSTimeZone.local
 | |
|         }
 | |
| 
 | |
|         Logger.debug("presenting notification with identifier: \(notificationIdentifier)")
 | |
|         UIApplication.shared.scheduleLocalNotification(notification)
 | |
|         notifications[notificationIdentifier] = notification
 | |
|     }
 | |
| 
 | |
|     func cancelNotification(_ notification: UILocalNotification) {
 | |
|         AssertIsOnMainThread()
 | |
|         UIApplication.shared.cancelLocalNotification(notification)
 | |
|     }
 | |
| 
 | |
|     func cancelNotification(identifier: String) {
 | |
|         AssertIsOnMainThread()
 | |
|         guard let notification = notifications.removeValue(forKey: identifier) else {
 | |
|             Logger.debug("no notification to cancel with identifier: \(identifier)")
 | |
|             return
 | |
|         }
 | |
| 
 | |
|         cancelNotification(notification)
 | |
|     }
 | |
| 
 | |
|     func cancelNotifications(threadId: String) {
 | |
|         AssertIsOnMainThread()
 | |
|         for notification in notifications.values {
 | |
|             guard let notificationThreadId = notification.userInfo?[AppNotificationUserInfoKey.threadId] as? String else {
 | |
|                 continue
 | |
|             }
 | |
| 
 | |
|             guard notificationThreadId == threadId else {
 | |
|                 continue
 | |
|             }
 | |
| 
 | |
|             cancelNotification(notification)
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     func clearAllNotifications() {
 | |
|         AssertIsOnMainThread()
 | |
|         for (_, notification) in notifications {
 | |
|             cancelNotification(notification)
 | |
|         }
 | |
|         type(of: self).clearExistingNotifications()
 | |
|     }
 | |
| 
 | |
|     public class func clearExistingNotifications() {
 | |
|         // This will cancel all "scheduled" local notifications that haven't
 | |
|         // been presented yet.
 | |
|         UIApplication.shared.cancelAllLocalNotifications()
 | |
|         // To clear all already presented local notifications, we need to
 | |
|         // set the app badge number to zero after setting it to a non-zero value.
 | |
|         UIApplication.shared.applicationIconBadgeNumber = 1
 | |
|         UIApplication.shared.applicationIconBadgeNumber = 0
 | |
|     }
 | |
| }
 | |
| 
 | |
| @objc(OWSLegacyNotificationActionHandler)
 | |
| public class LegacyNotificationActionHandler: NSObject {
 | |
| 
 | |
|     @objc
 | |
|     public static let kDefaultActionIdentifier = "LegacyNotificationActionHandler.kDefaultActionIdentifier"
 | |
| 
 | |
|     var actionHandler: NotificationActionHandler {
 | |
|         return NotificationActionHandler.shared
 | |
|     }
 | |
| 
 | |
|     @objc
 | |
|     func handleNotificationResponse(actionIdentifier: String,
 | |
|                                     notification: UILocalNotification,
 | |
|                                     responseInfo: [AnyHashable: Any],
 | |
|                                     completionHandler: @escaping () -> Void) {
 | |
|         firstly {
 | |
|             try handleNotificationResponse(actionIdentifier: actionIdentifier, notification: notification, responseInfo: responseInfo)
 | |
|         }.done {
 | |
|             completionHandler()
 | |
|         }.catch { error in
 | |
|             completionHandler()
 | |
|             owsFailDebug("error: \(error)")
 | |
|             Logger.error("error: \(error)")
 | |
|         }.retainUntilComplete()
 | |
|     }
 | |
| 
 | |
|     func handleNotificationResponse(actionIdentifier: String,
 | |
|                                     notification: UILocalNotification,
 | |
|                                     responseInfo: [AnyHashable: Any]) throws -> Promise<Void> {
 | |
|         assert(AppReadiness.isAppReady())
 | |
| 
 | |
|         let userInfo = notification.userInfo ?? [:]
 | |
| 
 | |
|         switch actionIdentifier {
 | |
|         case type(of: self).kDefaultActionIdentifier:
 | |
|             Logger.debug("default action")
 | |
|             return try actionHandler.showThread(userInfo: userInfo)
 | |
|         default:
 | |
|             // proceed
 | |
|             break
 | |
|         }
 | |
| 
 | |
|         guard let action = LegacyNotificationConfig.action(identifier: actionIdentifier) else {
 | |
|             throw NotificationError.failDebug("unable to find action for actionIdentifier: \(actionIdentifier)")
 | |
|         }
 | |
| 
 | |
|         switch action {
 | |
|         case .markAsRead:
 | |
|             return try actionHandler.markAsRead(userInfo: userInfo)
 | |
|         case .reply:
 | |
|             guard let replyText = responseInfo[UIUserNotificationActionResponseTypedTextKey] as? String else {
 | |
|                 throw NotificationError.failDebug("replyText was unexpectedly nil")
 | |
|             }
 | |
| 
 | |
|             return try actionHandler.reply(userInfo: userInfo, replyText: replyText)
 | |
|         case .showThread:
 | |
|             return try actionHandler.showThread(userInfo: userInfo)
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| extension OWSSound {
 | |
|     var filename: String? {
 | |
|         return OWSSounds.filename(for: self, quiet: false)
 | |
|     }
 | |
| }
 |