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
		
	
| 
								 
											7 years ago
										 
									 | 
							
								//
							 | 
						||
| 
								 | 
							
								//  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
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								    func notify(category: AppNotificationCategory, title: String?, body: String, userInfo: [AnyHashable: Any], sound: OWSSound?) {
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        AssertIsOnMainThread()
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        notify(category: category, title: title, body: body, userInfo: userInfo, sound: sound, replacingIdentifier: nil)
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								    func notify(category: AppNotificationCategory, title: String?, body: String, userInfo: [AnyHashable: Any], sound: OWSSound?, replacingIdentifier: String?) {
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        AssertIsOnMainThread()
							 | 
						||
| 
								 | 
							
								        guard UIApplication.shared.applicationState != .active else {
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								            if let sound = sound {
							 | 
						||
| 
								 | 
							
								                let soundId = OWSSounds.systemSoundID(for: sound, quiet: true)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                // Vibrate, respect silent switch, respect "Alert" volume, not media volume.
							 | 
						||
| 
								 | 
							
								                AudioServicesPlayAlertSound(soundId)
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								            return
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        let alertBody: String
							 | 
						||
| 
								 | 
							
								        if let title = title {
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								            // TODO - Make this a format string for better l10n
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								            alertBody = title.rtlSafeAppend(":").rtlSafeAppend(" ").rtlSafeAppend(body)
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								            alertBody = body
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        let notification = UILocalNotification()
							 | 
						||
| 
								 | 
							
								        notification.category = category.identifier
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        notification.alertBody = alertBody.filterForDisplay
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        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
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								        if checkForCancel {
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								            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)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        type(of: self).clearExistingNotifications()
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    public class func clearExistingNotifications() {
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								        // 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
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								@objc(OWSLegacyNotificationActionHandler)
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								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)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 
											7 years ago
										 
									 | 
							
								
							 | 
						||
| 
								 | 
							
								extension OWSSound {
							 | 
						||
| 
								 | 
							
								    var filename: String? {
							 | 
						||
| 
								 | 
							
								        return OWSSounds.filename(for: self, quiet: false)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 |