diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2023-05-09 17:02:14 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2023-05-09 17:02:14 +0200 |
| commit | 502385427686db7d57a86887c523a8b8457e99c1 (patch) | |
| tree | e05dc22973db4263a3448d3f0006571e27a8ef98 /ios | |
| parent | 63836bd9c5722a0901c3c91fda5fc70c468c4acf (diff) | |
| parent | 2b12f595e4911e55064c461807ee0b3e25fb14a5 (diff) | |
| download | mullvadvpn-502385427686db7d57a86887c523a8b8457e99c1.tar.xz mullvadvpn-502385427686db7d57a86887c523a8b8457e99c1.zip | |
Merge branch 'notification-action-handling'
Diffstat (limited to 'ios')
11 files changed, 111 insertions, 72 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 2dbb51e2b2..0b44c7f84a 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -143,6 +143,7 @@ 587425C12299833500CA2045 /* RootContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587425C02299833500CA2045 /* RootContainerViewController.swift */; }; 5875960A26F371FC00BF6711 /* Tunnel+Messaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5875960926F371FC00BF6711 /* Tunnel+Messaging.swift */; }; 5877D70F282137E8002FCFC7 /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF2C02281BDE02009EF542 /* SettingsManager.swift */; }; + 5877F94E2A0A59AA0052D9E9 /* NotificationResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5877F94D2A0A59AA0052D9E9 /* NotificationResponse.swift */; }; 5878A26F2907E7E00096FC88 /* ProblemReportInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A26E2907E7E00096FC88 /* ProblemReportInteractor.swift */; }; 5878A27129091CF20096FC88 /* AccountInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A27029091CF20096FC88 /* AccountInteractor.swift */; }; 5878A27329091D6D0096FC88 /* TunnelBlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A27229091D6D0096FC88 /* TunnelBlockObserver.swift */; }; @@ -782,6 +783,7 @@ 5872D6E7286304DE00DB5F4E /* TermsOfService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsOfService.swift; sourceTree = "<group>"; }; 587425C02299833500CA2045 /* RootContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootContainerViewController.swift; sourceTree = "<group>"; }; 5875960926F371FC00BF6711 /* Tunnel+Messaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tunnel+Messaging.swift"; sourceTree = "<group>"; }; + 5877F94D2A0A59AA0052D9E9 /* NotificationResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationResponse.swift; sourceTree = "<group>"; }; 58781CC822AE7CA8009B9D8E /* RelayConstraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConstraints.swift; sourceTree = "<group>"; }; 58781CD422AFBA39009B9D8E /* RelaySelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelector.swift; sourceTree = "<group>"; }; 5878A26E2907E7E00096FC88 /* ProblemReportInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportInteractor.swift; sourceTree = "<group>"; }; @@ -1696,6 +1698,7 @@ 58B26E1D2943514300D5980C /* InAppNotificationDescriptor.swift */, 58B26E21294351EA00D5980C /* InAppNotificationProvider.swift */, 58B26E272943527300D5980C /* SystemNotificationProvider.swift */, + 5877F94D2A0A59AA0052D9E9 /* NotificationResponse.swift */, 587B7535266528A200DEF7E9 /* NotificationManager.swift */, 58B26E292943545A00D5980C /* NotificationManagerDelegate.swift */, ); @@ -2731,6 +2734,7 @@ 58CAFA002983FF0200BE19F7 /* LoginInteractor.swift in Sources */, 5807E2C02432038B00F5FF30 /* String+Split.swift in Sources */, 58B26E242943520C00D5980C /* NotificationProviderProtocol.swift in Sources */, + 5877F94E2A0A59AA0052D9E9 /* NotificationResponse.swift in Sources */, 583FE01229C0F99A006E85F9 /* PresentationControllerDismissalInterceptor.swift in Sources */, 58677712290976FB006F721F /* SettingsInteractor.swift in Sources */, 58CE5E66224146200008646E /* LoginViewController.swift in Sources */, diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift index 233c17d73f..d7e54590c6 100644 --- a/ios/MullvadVPN/AppDelegate.swift +++ b/ios/MullvadVPN/AppDelegate.swift @@ -347,21 +347,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD private func setupNotificationHandler() { NotificationManager.shared.notificationProviders = [ - RegisteredDeviceInAppNotification(tunnelManager: tunnelManager, completionHandler: { deviceState in - let sceneDelegate = UIApplication.shared.connectedScenes - .first?.delegate as? SceneDelegate - sceneDelegate?.didDismissRegisteredDeviceInAppBanner(deviceState: deviceState) - }), + RegisteredDeviceInAppNotification(tunnelManager: tunnelManager), TunnelStatusNotificationProvider(tunnelManager: tunnelManager), - AccountExpirySystemNotificationProvider( - tunnelManager: tunnelManager, - defaultActionHandler: { - let sceneDelegate = UIApplication.shared.connectedScenes - .first?.delegate as? SceneDelegate - - sceneDelegate?.showUserAccount() - } - ), + AccountExpirySystemNotificationProvider(tunnelManager: tunnelManager), AccountExpiryInAppNotificationProvider(tunnelManager: tunnelManager), ] UNUserNotificationCenter.current().delegate = self diff --git a/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift b/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift index 53032583d1..eae7d81a3f 100644 --- a/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift +++ b/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift @@ -775,16 +775,6 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo return router.isPresenting(.settings) } - // MARK: - Deep link - - func showAccount() { - router.present(.account) - } - - func didDismissRegisteredDeviceInAppBanner(deviceState: DeviceState) { - updateView(deviceState: deviceState) - } - // MARK: - UISplitViewControllerDelegate func primaryViewController(forExpanding splitViewController: UISplitViewController) @@ -872,6 +862,14 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo notificationController.setNotifications(notifications, animated: true) } + func notificationManager(_ manager: NotificationManager, didReceiveResponse response: NotificationResponse) { + if response.providerIdentifier == AccountExpirySystemNotificationProvider.identifier { + router.present(.account) + } else if response.providerIdentifier == RegisteredDeviceInAppNotification.identifier { + updateView(deviceState: tunnelManager.deviceState) + } + } + // MARK: - Presenting var presentationContext: UIViewController { diff --git a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift index b2edfb49f8..4654c560d7 100644 --- a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift +++ b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift @@ -12,9 +12,10 @@ import UserNotifications final class AccountExpirySystemNotificationProvider: NotificationProvider, SystemNotificationProvider { private var accountExpiry: Date? private var tunnelObserver: TunnelBlockObserver? - private var defaultActionHandler: (() -> Void)? - init(tunnelManager: TunnelManager, defaultActionHandler: (() -> Void)? = nil) { + static let identifier = "net.mullvad.MullvadVPN.AccountExpiryNotification" + + init(tunnelManager: TunnelManager) { super.init() let tunnelObserver = TunnelBlockObserver( @@ -29,11 +30,10 @@ final class AccountExpirySystemNotificationProvider: NotificationProvider, Syste tunnelManager.addObserver(tunnelObserver) self.tunnelObserver = tunnelObserver - self.defaultActionHandler = defaultActionHandler } override var identifier: String { - return "net.mullvad.MullvadVPN.AccountExpiryNotification" + return Self.identifier } // MARK: - SystemNotificationProvider @@ -78,18 +78,6 @@ final class AccountExpirySystemNotificationProvider: NotificationProvider, Syste return shouldRemovePendingOrDeliveredRequests } - func handleResponse(_ response: UNNotificationResponse) -> Bool { - guard response.notification.request.identifier == identifier else { - return false - } - - if response.actionIdentifier == UNNotificationDefaultActionIdentifier { - defaultActionHandler?() - } - - return true - } - // MARK: - Private private var trigger: UNNotificationTrigger? { diff --git a/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotification.swift b/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotification.swift index ae6007451f..017d2aa1b9 100644 --- a/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotification.swift +++ b/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotification.swift @@ -11,12 +11,9 @@ import UIKit.UIColor import UIKit.UIFont final class RegisteredDeviceInAppNotification: NotificationProvider, InAppNotificationProvider { - typealias CompletionHandler = (DeviceState) -> Void - // MARK: - private properties private let tunnelManager: TunnelManager - private let completionHandler: CompletionHandler? private var shouldShowBanner = false private var deviceState: DeviceState @@ -53,9 +50,11 @@ final class RegisteredDeviceInAppNotification: NotificationProvider, InAppNotifi image: .init(named: "IconCloseSml"), handler: { [weak self] in guard let self = self else { return } + + self.sendAction() + self.shouldShowBanner = false self.invalidate() - self.completionHandler?(self.deviceState) } ) ) @@ -63,16 +62,17 @@ final class RegisteredDeviceInAppNotification: NotificationProvider, InAppNotifi // MARK: - initialize - init(tunnelManager: TunnelManager, completionHandler: CompletionHandler? = nil) { + static let identifier = "net.mullvad.MullvadVPN.RegisteredDeviceInAppNotification" + + init(tunnelManager: TunnelManager) { self.tunnelManager = tunnelManager - self.completionHandler = completionHandler deviceState = tunnelManager.deviceState super.init() addObservers() } override var identifier: String { - "net.mullvad.MullvadVPN.AccountCreationInAppNotification" + return Self.identifier } private func addObservers() { diff --git a/ios/MullvadVPN/Notifications/NotificationManager.swift b/ios/MullvadVPN/Notifications/NotificationManager.swift index 68c251e9c7..d6745793f2 100644 --- a/ios/MullvadVPN/Notifications/NotificationManager.swift +++ b/ios/MullvadVPN/Notifications/NotificationManager.swift @@ -115,11 +115,26 @@ final class NotificationManager: NotificationProviderDelegate { } func handleSystemNotificationResponse(_ response: UNNotificationResponse) { - for case let notificationProvider as SystemNotificationProvider in notificationProviders { - if notificationProvider.handleResponse(response) { - return - } + dispatchPrecondition(condition: .onQueue(.main)) + + guard let sourceProvider = notificationProviders.first(where: { notificationProvider in + guard let notificationProvider = notificationProvider as? SystemNotificationProvider else { return false } + + return response.notification.request.identifier == notificationProvider.identifier + }) else { + logger.warning( + "Received response with request identifier: \(response.notification.request.identifier) that didn't map to any notification provider" + ) + return } + + let notificationResponse = NotificationResponse( + providerIdentifier: sourceProvider.identifier, + actionIdentifier: response.actionIdentifier, + systemResponse: response + ) + + delegate?.notificationManager(self, didReceiveResponse: notificationResponse) } // MARK: - Private @@ -131,9 +146,7 @@ final class NotificationManager: NotificationProviderDelegate { userNotificationCenter.getNotificationSettings { notificationSettings in switch notificationSettings.authorizationStatus { case .notDetermined: - userNotificationCenter.requestAuthorization( - options: authorizationOptions - ) { granted, error in + userNotificationCenter.requestAuthorization(options: authorizationOptions) { granted, error in if let error = error { self.logger.error( error: error, @@ -223,4 +236,15 @@ final class NotificationManager: NotificationProviderDelegate { ) } } + + func notificationProvider(_ notificationProvider: NotificationProvider, didReceiveAction actionIdentifier: String) { + dispatchPrecondition(condition: .onQueue(.main)) + + let notificationResponse = NotificationResponse( + providerIdentifier: notificationProvider.identifier, + actionIdentifier: actionIdentifier + ) + + delegate?.notificationManager(self, didReceiveResponse: notificationResponse) + } } diff --git a/ios/MullvadVPN/Notifications/NotificationManagerDelegate.swift b/ios/MullvadVPN/Notifications/NotificationManagerDelegate.swift index 818d7deaad..26f1da2781 100644 --- a/ios/MullvadVPN/Notifications/NotificationManagerDelegate.swift +++ b/ios/MullvadVPN/Notifications/NotificationManagerDelegate.swift @@ -13,4 +13,6 @@ protocol NotificationManagerDelegate: AnyObject { _ manager: NotificationManager, notifications: [InAppNotificationDescriptor] ) + + func notificationManager(_ manager: NotificationManager, didReceiveResponse: NotificationResponse) } diff --git a/ios/MullvadVPN/Notifications/NotificationProvider.swift b/ios/MullvadVPN/Notifications/NotificationProvider.swift index 67299226c5..cdcc901a67 100644 --- a/ios/MullvadVPN/Notifications/NotificationProvider.swift +++ b/ios/MullvadVPN/Notifications/NotificationProvider.swift @@ -7,30 +7,55 @@ // import Foundation +import UserNotifications /// Notification provider delegate primarily used by `NotificationManager`. protocol NotificationProviderDelegate: AnyObject { func notificationProviderDidInvalidate(_ notificationProvider: NotificationProvider) + func notificationProvider(_ notificationProvider: NotificationProvider, didReceiveAction actionIdentifier: String) } /// Base class for all notification providers. class NotificationProvider: NotificationProviderProtocol { weak var delegate: NotificationProviderDelegate? + /** + Provider identifier. + + Override in subclasses and make sure each provider has unique identifier. It's preferred that identifiers use + reverse domain name, for instance: `com.example.app.ProviderName`. + */ var identifier: String { return "default" } + /** + Send action to notification manager delegate. + + Usually in response to user interacting with notification banner, i.e by tapping a button. Use different action + identifiers if notification offers more than one action that user can perform. + */ + func sendAction(_ actionIdentifier: String = UNNotificationDefaultActionIdentifier) { + dispatchOnMain { + self.delegate?.notificationProvider(self, didReceiveAction: actionIdentifier) + } + } + + /** + This method tells notification manager to re-evalute the notification content. + Call this method when notification provider wants to change the content it presents. + */ func invalidate() { - let executor = { + dispatchOnMain { self.delegate?.notificationProviderDidInvalidate(self) - return } + } + private func dispatchOnMain(_ block: @escaping () -> Void) { if Thread.isMainThread { - executor() + block() } else { - DispatchQueue.main.async(execute: executor) + DispatchQueue.main.async(execute: block) } } } diff --git a/ios/MullvadVPN/Notifications/NotificationResponse.swift b/ios/MullvadVPN/Notifications/NotificationResponse.swift new file mode 100644 index 0000000000..4b57401bec --- /dev/null +++ b/ios/MullvadVPN/Notifications/NotificationResponse.swift @@ -0,0 +1,24 @@ +// +// NotificationResponse.swift +// MullvadVPN +// +// Created by pronebird on 09/05/2023. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import UserNotifications + +/** + Struct holding system or in-app notification response. + */ +struct NotificationResponse { + /// Provider identifier. + var providerIdentifier: String + + /// Action identifier, i.e UNNotificationDefaultActionIdentifier or any custom. + var actionIdentifier: String + + /// System notification response. Unset for in-app notifications. + var systemResponse: UNNotificationResponse? +} diff --git a/ios/MullvadVPN/Notifications/SystemNotificationProvider.swift b/ios/MullvadVPN/Notifications/SystemNotificationProvider.swift index f346e3e230..110e078aba 100644 --- a/ios/MullvadVPN/Notifications/SystemNotificationProvider.swift +++ b/ios/MullvadVPN/Notifications/SystemNotificationProvider.swift @@ -19,8 +19,4 @@ protocol SystemNotificationProvider: NotificationProviderProtocol { /// Whether any delivered requests should be removed. var shouldRemoveDeliveredRequests: Bool { get } - - /// Handle system notification response. - /// Return `true` if response was handled by this provider, otherwise `false`. - func handleResponse(_ response: UNNotificationResponse) -> Bool } diff --git a/ios/MullvadVPN/SceneDelegate.swift b/ios/MullvadVPN/SceneDelegate.swift index eeadacfc13..1e61422dee 100644 --- a/ios/MullvadVPN/SceneDelegate.swift +++ b/ios/MullvadVPN/SceneDelegate.swift @@ -34,16 +34,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, SettingsMigrationUIHand return appDelegate.tunnelManager } - // MARK: - Deep link - - func showUserAccount() { - appCoordinator?.showAccount() - } - - func didDismissRegisteredDeviceInAppBanner(deviceState: DeviceState) { - appCoordinator?.didDismissRegisteredDeviceInAppBanner(deviceState: deviceState) - } - // MARK: - Private private func addTunnelObserver() { |
