summaryrefslogtreecommitdiffhomepage
path: root/ios
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2023-05-09 17:02:14 +0200
committerAndrej Mihajlov <and@mullvad.net>2023-05-09 17:02:14 +0200
commit502385427686db7d57a86887c523a8b8457e99c1 (patch)
treee05dc22973db4263a3448d3f0006571e27a8ef98 /ios
parent63836bd9c5722a0901c3c91fda5fc70c468c4acf (diff)
parent2b12f595e4911e55064c461807ee0b3e25fb14a5 (diff)
downloadmullvadvpn-502385427686db7d57a86887c523a8b8457e99c1.tar.xz
mullvadvpn-502385427686db7d57a86887c523a8b8457e99c1.zip
Merge branch 'notification-action-handling'
Diffstat (limited to 'ios')
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/AppDelegate.swift16
-rw-r--r--ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift18
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift20
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotification.swift14
-rw-r--r--ios/MullvadVPN/Notifications/NotificationManager.swift38
-rw-r--r--ios/MullvadVPN/Notifications/NotificationManagerDelegate.swift2
-rw-r--r--ios/MullvadVPN/Notifications/NotificationProvider.swift33
-rw-r--r--ios/MullvadVPN/Notifications/NotificationResponse.swift24
-rw-r--r--ios/MullvadVPN/Notifications/SystemNotificationProvider.swift4
-rw-r--r--ios/MullvadVPN/SceneDelegate.swift10
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() {