summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2022-12-12 15:52:58 +0100
committerAndrej Mihajlov <and@mullvad.net>2022-12-12 15:52:58 +0100
commit41d084b2d94ad68390b60a5f200f001500c352d4 (patch)
tree87e7ace83ce32705e94a3b5b16cd41fdca6869f8
parentfe9fe00c2630efa49d2007dc5c38584aab81dabb (diff)
parent37645072fe534baa798a448aceb131802f911231 (diff)
downloadmullvadvpn-41d084b2d94ad68390b60a5f200f001500c352d4.tar.xz
mullvadvpn-41d084b2d94ad68390b60a5f200f001500c352d4.zip
Merge branch 'refactor-notifications'
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj54
-rw-r--r--ios/MullvadVPN/AppDelegate.swift20
-rw-r--r--ios/MullvadVPN/Notifications/InAppNotificationDescriptor.swift28
-rw-r--r--ios/MullvadVPN/Notifications/InAppNotificationProvider.swift15
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryNotificationProvider.swift (renamed from ios/MullvadVPN/Notifications/AccountExpiryNotificationProvider.swift)26
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/TunnelStatusNotificationProvider.swift (renamed from ios/MullvadVPN/Notifications/TunnelStatusNotificationProvider.swift)2
-rw-r--r--ios/MullvadVPN/Notifications/NotificationManager.swift (renamed from ios/MullvadVPN/NotificationManager.swift)157
-rw-r--r--ios/MullvadVPN/Notifications/NotificationManagerDelegate.swift16
-rw-r--r--ios/MullvadVPN/Notifications/NotificationProvider.swift36
-rw-r--r--ios/MullvadVPN/Notifications/NotificationProviderProtocol.swift19
-rw-r--r--ios/MullvadVPN/Notifications/SystemNotificationProvider.swift26
-rw-r--r--ios/MullvadVPN/Notifications/UI/NotificationBannerView.swift (renamed from ios/MullvadVPN/NotificationBannerView.swift)30
-rw-r--r--ios/MullvadVPN/Notifications/UI/NotificationContainerView.swift (renamed from ios/MullvadVPN/NotificationContainerView.swift)2
-rw-r--r--ios/MullvadVPN/Notifications/UI/NotificationController.swift (renamed from ios/MullvadVPN/NotificationController.swift)9
14 files changed, 294 insertions, 146 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 9c8eb65127..5661dcee7c 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -250,6 +250,12 @@
58ACF64D26567A5000ACE4B7 /* CustomSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ACF64C26567A4F00ACE4B7 /* CustomSwitch.swift */; };
58ACF64F26567A7100ACE4B7 /* CustomSwitchContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ACF64E26567A7100ACE4B7 /* CustomSwitchContainer.swift */; };
58B0A2A8238EE68200BC001D /* RelaySelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584B26F3237434D00073B10E /* RelaySelectorTests.swift */; };
+ 58B26E1E2943514300D5980C /* InAppNotificationDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E1D2943514300D5980C /* InAppNotificationDescriptor.swift */; };
+ 58B26E22294351EA00D5980C /* InAppNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E21294351EA00D5980C /* InAppNotificationProvider.swift */; };
+ 58B26E242943520C00D5980C /* NotificationProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E232943520C00D5980C /* NotificationProviderProtocol.swift */; };
+ 58B26E262943522400D5980C /* NotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E252943522400D5980C /* NotificationProvider.swift */; };
+ 58B26E282943527300D5980C /* SystemNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E272943527300D5980C /* SystemNotificationProvider.swift */; };
+ 58B26E2A2943545A00D5980C /* NotificationManagerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E292943545A00D5980C /* NotificationManagerDelegate.swift */; };
58B3F30F2742708B00A2DD38 /* HeaderBarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B3F30E2742708B00A2DD38 /* HeaderBarButton.swift */; };
58B43C1925F77DB60002C8C3 /* ConnectContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B43C1825F77DB60002C8C3 /* ConnectContentView.swift */; };
58B93A1326C3F13600A55733 /* TunnelState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B93A1226C3F13600A55733 /* TunnelState.swift */; };
@@ -747,6 +753,12 @@
58AEEF642344A36000C9BBD5 /* KeychainError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainError.swift; sourceTree = "<group>"; };
58B0A2A0238EE67E00BC001D /* MullvadVPNTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MullvadVPNTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
58B0A2A4238EE67E00BC001D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ 58B26E1D2943514300D5980C /* InAppNotificationDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppNotificationDescriptor.swift; sourceTree = "<group>"; };
+ 58B26E21294351EA00D5980C /* InAppNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppNotificationProvider.swift; sourceTree = "<group>"; };
+ 58B26E232943520C00D5980C /* NotificationProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationProviderProtocol.swift; sourceTree = "<group>"; };
+ 58B26E252943522400D5980C /* NotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationProvider.swift; sourceTree = "<group>"; };
+ 58B26E272943527300D5980C /* SystemNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemNotificationProvider.swift; sourceTree = "<group>"; };
+ 58B26E292943545A00D5980C /* NotificationManagerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerDelegate.swift; sourceTree = "<group>"; };
58B3F30E2742708B00A2DD38 /* HeaderBarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderBarButton.swift; sourceTree = "<group>"; };
58B43C1825F77DB60002C8C3 /* ConnectContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectContentView.swift; sourceTree = "<group>"; };
58B93A1226C3F13600A55733 /* TunnelState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelState.swift; sourceTree = "<group>"; };
@@ -1164,13 +1176,13 @@
path = Operations;
sourceTree = "<group>";
};
- 587B75422669034500DEF7E9 /* Notifications */ = {
+ 587B75422669034500DEF7E9 /* Notification Providers */ = {
isa = PBXGroup;
children = (
587B75402668FD7700DEF7E9 /* AccountExpiryNotificationProvider.swift */,
58A94AE326CFD945001CB97C /* TunnelStatusNotificationProvider.swift */,
);
- path = Notifications;
+ path = "Notification Providers";
sourceTree = "<group>";
};
5898D28A29017BD400EB5EBA /* TunnelProviderMessaging */ = {
@@ -1235,6 +1247,32 @@
path = MullvadVPNTests;
sourceTree = "<group>";
};
+ 58B26E1F2943516500D5980C /* Notifications */ = {
+ isa = PBXGroup;
+ children = (
+ 58B26E20294351A600D5980C /* UI */,
+ 587B75422669034500DEF7E9 /* Notification Providers */,
+ 58B26E232943520C00D5980C /* NotificationProviderProtocol.swift */,
+ 58B26E252943522400D5980C /* NotificationProvider.swift */,
+ 58B26E1D2943514300D5980C /* InAppNotificationDescriptor.swift */,
+ 58B26E21294351EA00D5980C /* InAppNotificationProvider.swift */,
+ 58B26E272943527300D5980C /* SystemNotificationProvider.swift */,
+ 587B7535266528A200DEF7E9 /* NotificationManager.swift */,
+ 58B26E292943545A00D5980C /* NotificationManagerDelegate.swift */,
+ );
+ path = Notifications;
+ sourceTree = "<group>";
+ };
+ 58B26E20294351A600D5980C /* UI */ = {
+ isa = PBXGroup;
+ children = (
+ 587B753C2666468F00DEF7E9 /* NotificationController.swift */,
+ 587B753E2668E5A700DEF7E9 /* NotificationContainerView.swift */,
+ 587B753A2666467500DEF7E9 /* NotificationBannerView.swift */,
+ );
+ path = UI;
+ sourceTree = "<group>";
+ };
58CE5E57224146200008646E = {
isa = PBXGroup;
children = (
@@ -1337,11 +1375,7 @@
5866F39B2243B82D00168AE5 /* MullvadVPN.entitlements */,
58906DDF2445C7A5002F0673 /* NEProviderStopReason+Debug.swift */,
5811DE4F239014550011EB53 /* NEVPNStatus+Debug.swift */,
- 587B753A2666467500DEF7E9 /* NotificationBannerView.swift */,
- 587B753E2668E5A700DEF7E9 /* NotificationContainerView.swift */,
- 587B753C2666468F00DEF7E9 /* NotificationController.swift */,
- 587B7535266528A200DEF7E9 /* NotificationManager.swift */,
- 587B75422669034500DEF7E9 /* Notifications */,
+ 58B26E1F2943516500D5980C /* Notifications */,
584EBDBC2747C98F00A0C9FD /* NSAttributedString+Markdown.swift */,
587D9675288989DB00CD8F1C /* NSLayoutConstraint+Helpers.swift */,
5871FB9F254C26BF0051A0A4 /* NSRegularExpression+IPAddress.swift */,
@@ -2178,6 +2212,7 @@
587B753B2666467500DEF7E9 /* NotificationBannerView.swift in Sources */,
58B993B12608A34500BA7811 /* LoginContentView.swift in Sources */,
5878A27529093A310096FC88 /* StorePaymentEvent.swift in Sources */,
+ 58B26E2A2943545A00D5980C /* NotificationManagerDelegate.swift in Sources */,
58E6771F24ADFE7800AA26E7 /* SettingsNavigationController.swift in Sources */,
58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */,
5878A27B2909649A0096FC88 /* CustomOverlayRenderer.swift in Sources */,
@@ -2189,6 +2224,7 @@
5878A27129091CF20096FC88 /* AccountInteractor.swift in Sources */,
068CE5742927B7A400A068BB /* Migration.swift in Sources */,
58CCA010224249A1004F3011 /* ConnectViewController.swift in Sources */,
+ 58B26E22294351EA00D5980C /* InAppNotificationProvider.swift in Sources */,
5893716A28817A45004EE76C /* DeviceManagementViewController.swift in Sources */,
58BFA5C622A7C97F00A6173D /* RelayCacheTracker.swift in Sources */,
E158B360285381C60002F069 /* StringFormatter.swift in Sources */,
@@ -2256,6 +2292,7 @@
5878A26F2907E7E00096FC88 /* ProblemReportInteractor.swift in Sources */,
58E11188292FA11F009FCA84 /* SettingsMigrationUIHandler.swift in Sources */,
5807E2C02432038B00F5FF30 /* String+Split.swift in Sources */,
+ 58B26E242943520C00D5980C /* NotificationProviderProtocol.swift in Sources */,
58677712290976FB006F721F /* SettingsInteractor.swift in Sources */,
58CE5E66224146200008646E /* LoginViewController.swift in Sources */,
58EF580B25D69D7A00AEBA94 /* ProblemReportSubmissionOverlayView.swift in Sources */,
@@ -2265,6 +2302,7 @@
06410E07292D108E00AFC18C /* SettingsStore.swift in Sources */,
586A950D290125F0007BAF2B /* PresentAlertOperation.swift in Sources */,
58B93A1326C3F13600A55733 /* TunnelState.swift in Sources */,
+ 58B26E262943522400D5980C /* NotificationProvider.swift in Sources */,
58FEEB46260A028D00A621A8 /* GeoJSON.swift in Sources */,
58CE5E64224146200008646E /* AppDelegate.swift in Sources */,
5878A27329091D6D0096FC88 /* TunnelBlockObserver.swift in Sources */,
@@ -2273,10 +2311,12 @@
58ACF64F26567A7100ACE4B7 /* CustomSwitchContainer.swift in Sources */,
5857F24324C8662600CF6F47 /* SelectLocationHeaderView.swift in Sources */,
58EE2E3A272FF814003BFF93 /* SettingsDataSource.swift in Sources */,
+ 58B26E1E2943514300D5980C /* InAppNotificationDescriptor.swift in Sources */,
58421032282E42B000F24E46 /* UpdateDeviceDataOperation.swift in Sources */,
06410E04292D0F7100AFC18C /* SettingsParser.swift in Sources */,
5878A27D2909657C0096FC88 /* RevokedDeviceInteractor.swift in Sources */,
58677710290975E9006F721F /* SettingsInteractorFactory.swift in Sources */,
+ 58B26E282943527300D5980C /* SystemNotificationProvider.swift in Sources */,
58CCA01222424D11004F3011 /* SettingsViewController.swift in Sources */,
06410DFE292CE18F00AFC18C /* KeychainSettingsStore.swift in Sources */,
580F8B8628197958002E0998 /* DNSSettings.swift in Sources */,
diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift
index eec04a0851..7f2cb40392 100644
--- a/ios/MullvadVPN/AppDelegate.swift
+++ b/ios/MullvadVPN/AppDelegate.swift
@@ -389,8 +389,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
private func setupNotificationHandler() {
+ let accountExpiryProvider = AccountExpiryNotificationProvider(tunnelManager: tunnelManager)
+
+ accountExpiryProvider.defaultActionHandler = {
+ let sceneDelegate = UIApplication.shared.connectedScenes
+ .first?.delegate as? SceneDelegate
+
+ sceneDelegate?.showUserAccount()
+ }
+
NotificationManager.shared.notificationProviders = [
- AccountExpiryNotificationProvider(tunnelManager: tunnelManager),
+ accountExpiryProvider,
TunnelStatusNotificationProvider(tunnelManager: tunnelManager),
]
UNUserNotificationCenter.current().delegate = self
@@ -416,14 +425,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
withCompletionHandler completionHandler: @escaping () -> Void
) {
let blockOperation = AsyncBlockOperation(dispatchQueue: .main) {
- if response.notification.request.identifier == accountExpiryNotificationIdentifier,
- response.actionIdentifier == UNNotificationDefaultActionIdentifier
- {
- let sceneDelegate = UIApplication.shared.connectedScenes
- .first?.delegate as? SceneDelegate
-
- sceneDelegate?.showUserAccount()
- }
+ NotificationManager.shared.handleSystemNotificationResponse(response)
completionHandler()
}
diff --git a/ios/MullvadVPN/Notifications/InAppNotificationDescriptor.swift b/ios/MullvadVPN/Notifications/InAppNotificationDescriptor.swift
new file mode 100644
index 0000000000..7d16791fc7
--- /dev/null
+++ b/ios/MullvadVPN/Notifications/InAppNotificationDescriptor.swift
@@ -0,0 +1,28 @@
+//
+// InAppNotificationDescriptor.swift
+// MullvadVPN
+//
+// Created by pronebird on 09/12/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+/// Struct describing in-app notification.
+struct InAppNotificationDescriptor: Equatable {
+ /// Notification identifier.
+ var identifier: String
+
+ /// Notification banner style.
+ var style: NotificationBannerStyle
+
+ /// Notification title.
+ var title: String
+
+ /// Notification body.
+ var body: String
+}
+
+enum NotificationBannerStyle {
+ case success, warning, error
+}
diff --git a/ios/MullvadVPN/Notifications/InAppNotificationProvider.swift b/ios/MullvadVPN/Notifications/InAppNotificationProvider.swift
new file mode 100644
index 0000000000..8410aef8b3
--- /dev/null
+++ b/ios/MullvadVPN/Notifications/InAppNotificationProvider.swift
@@ -0,0 +1,15 @@
+//
+// InAppNotificationProvider.swift
+// MullvadVPN
+//
+// Created by pronebird on 09/12/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+/// Protocol describing in-app notification provider.
+protocol InAppNotificationProvider: NotificationProviderProtocol {
+ /// In-app notification descriptor.
+ var notificationDescriptor: InAppNotificationDescriptor? { get }
+}
diff --git a/ios/MullvadVPN/Notifications/AccountExpiryNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryNotificationProvider.swift
index fc6a579661..80b7a06428 100644
--- a/ios/MullvadVPN/Notifications/AccountExpiryNotificationProvider.swift
+++ b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryNotificationProvider.swift
@@ -9,19 +9,19 @@
import Foundation
import UserNotifications
-let accountExpiryNotificationIdentifier = "net.mullvad.MullvadVPN.AccountExpiryNotification"
private let defaultTriggerInterval = 3
-class AccountExpiryNotificationProvider: NotificationProvider, SystemNotificationProvider,
+final class AccountExpiryNotificationProvider: NotificationProvider, SystemNotificationProvider,
InAppNotificationProvider, TunnelObserver
{
- private var accountExpiry: Date?
-
/// Interval prior to expiry used to calculate when to trigger notifications.
private let triggerInterval: Int
+ private var accountExpiry: Date?
+
+ var defaultActionHandler: (() -> Void)?
override var identifier: String {
- return accountExpiryNotificationIdentifier
+ return "net.mullvad.MullvadVPN.AccountExpiryNotification"
}
init(tunnelManager: TunnelManager, triggerInterval: Int = defaultTriggerInterval) {
@@ -85,7 +85,7 @@ class AccountExpiryNotificationProvider: NotificationProvider, SystemNotificatio
content.sound = UNNotificationSound.default
return UNNotificationRequest(
- identifier: accountExpiryNotificationIdentifier,
+ identifier: identifier,
content: content,
trigger: trigger
)
@@ -101,6 +101,18 @@ class AccountExpiryNotificationProvider: NotificationProvider, SystemNotificatio
return shouldRemovePendingOrDeliveredRequests
}
+ func handleResponse(_ response: UNNotificationResponse) -> Bool {
+ guard response.notification.request.identifier == identifier else {
+ return false
+ }
+
+ if response.actionIdentifier == UNNotificationDefaultActionIdentifier {
+ defaultActionHandler?()
+ }
+
+ return true
+ }
+
// MARK: - InAppNotificationProvider
var notificationDescriptor: InAppNotificationDescriptor? {
@@ -170,6 +182,6 @@ class AccountExpiryNotificationProvider: NotificationProvider, SystemNotificatio
}
func tunnelManager(_ manager: TunnelManager, didUpdateDeviceState deviceState: DeviceState) {
- invalidate(deviceState: manager.deviceState)
+ invalidate(deviceState: deviceState)
}
}
diff --git a/ios/MullvadVPN/Notifications/TunnelStatusNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/TunnelStatusNotificationProvider.swift
index 81c2709267..df59cc1433 100644
--- a/ios/MullvadVPN/Notifications/TunnelStatusNotificationProvider.swift
+++ b/ios/MullvadVPN/Notifications/Notification Providers/TunnelStatusNotificationProvider.swift
@@ -8,7 +8,7 @@
import Foundation
-class TunnelStatusNotificationProvider: NotificationProvider, InAppNotificationProvider,
+final class TunnelStatusNotificationProvider: NotificationProvider, InAppNotificationProvider,
TunnelObserver
{
private var isWaitingForConnectivity = false
diff --git a/ios/MullvadVPN/NotificationManager.swift b/ios/MullvadVPN/Notifications/NotificationManager.swift
index ca29eccabf..7cf48066cb 100644
--- a/ios/MullvadVPN/NotificationManager.swift
+++ b/ios/MullvadVPN/Notifications/NotificationManager.swift
@@ -8,86 +8,36 @@
import Foundation
import MullvadLogging
-import UIKit
import UserNotifications
-protocol NotificationManagerDelegate: AnyObject {
- func notificationManagerDidUpdateInAppNotifications(
- _ manager: NotificationManager,
- notifications: [InAppNotificationDescriptor]
- )
-}
-
-private protocol NotificationProviderDelegate: AnyObject {
- func notificationProviderDidInvalidate(_ notificationProvider: NotificationProvider)
-}
-
-class NotificationProvider {
- fileprivate weak var delegate: NotificationProviderDelegate?
-
- var identifier: String {
- return "default"
- }
-
- func invalidate() {
- let executor = {
- self.delegate?.notificationProviderDidInvalidate(self)
- return
- }
-
- if Thread.isMainThread {
- executor()
- } else {
- DispatchQueue.main.async(execute: executor)
- }
- }
-}
-
-protocol SystemNotificationProvider {
- /// Unique provider identifier used to identify requests
- var identifier: String { get }
-
- /// Notification request if available, otherwise `nil`
- var notificationRequest: UNNotificationRequest? { get }
-
- /// Whether any pending requests should be removed
- var shouldRemovePendingRequests: Bool { get }
-
- /// Whether any delivered requests should be removed
- var shouldRemoveDeliveredRequests: Bool { get }
-}
+final class NotificationManager: NotificationProviderDelegate {
+ private lazy var logger = Logger(label: "NotificationManager")
+ private var _notificationProviders: [NotificationProvider] = []
+ private var inAppNotificationDescriptors: [InAppNotificationDescriptor] = []
-protocol InAppNotificationProvider {
- /// Unique provider identifier used to identify notifications
- var identifier: String { get }
+ var notificationProviders: [NotificationProvider] {
+ set(newNotificationProviders) {
+ dispatchPrecondition(condition: .onQueue(.main))
- /// In-app notification descriptor
- var notificationDescriptor: InAppNotificationDescriptor? { get }
-}
+ for oldNotificationProvider in _notificationProviders {
+ oldNotificationProvider.delegate = nil
+ }
-class NotificationManager: NotificationProviderDelegate {
- private lazy var logger = Logger(label: "NotificationManager")
+ for newNotificationProvider in newNotificationProviders {
+ newNotificationProvider.delegate = self
+ }
- var notificationProviders: [NotificationProvider] = [] {
- willSet {
- assert(Thread.isMainThread)
+ _notificationProviders = newNotificationProviders
}
-
- didSet {
- for notificationProvider in notificationProviders {
- notificationProvider.delegate = self
- }
+ get {
+ return _notificationProviders
}
}
- private var inAppNotificationDescriptors: [InAppNotificationDescriptor] = []
-
weak var delegate: NotificationManagerDelegate? {
- willSet {
- assert(Thread.isMainThread)
- }
-
didSet {
+ dispatchPrecondition(condition: .onQueue(.main))
+
// Pump in-app notifications when changing delegate.
if !inAppNotificationDescriptors.isEmpty {
delegate?.notificationManagerDidUpdateInAppNotifications(
@@ -103,7 +53,7 @@ class NotificationManager: NotificationProviderDelegate {
private init() {}
func updateNotifications() {
- assert(Thread.isMainThread)
+ dispatchPrecondition(condition: .onQueue(.main))
var newSystemNotificationRequests = [UNNotificationRequest]()
var newInAppNotificationDescriptors = [InAppNotificationDescriptor]()
@@ -133,10 +83,13 @@ class NotificationManager: NotificationProviderDelegate {
}
let notificationCenter = UNUserNotificationCenter.current()
- notificationCenter
- .removePendingNotificationRequests(withIdentifiers: pendingRequestIdentifiersToRemove)
- notificationCenter
- .removeDeliveredNotifications(withIdentifiers: deliveredRequestIdentifiersToRemove)
+
+ notificationCenter.removePendingNotificationRequests(
+ withIdentifiers: pendingRequestIdentifiersToRemove
+ )
+ notificationCenter.removeDeliveredNotifications(
+ withIdentifiers: deliveredRequestIdentifiersToRemove
+ )
requestNotificationPermissions { granted in
guard granted else { return }
@@ -146,7 +99,10 @@ class NotificationManager: NotificationProviderDelegate {
if let error = error {
self.logger.error(
error: error,
- message: "Failed to add notification request with identifier \(newRequest.identifier)."
+ message: """
+ Failed to add notification request with identifier \
+ \(newRequest.identifier).
+ """
)
}
}
@@ -161,6 +117,14 @@ class NotificationManager: NotificationProviderDelegate {
)
}
+ func handleSystemNotificationResponse(_ response: UNNotificationResponse) {
+ for case let notificationProvider as SystemNotificationProvider in notificationProviders {
+ if notificationProvider.handleResponse(response) {
+ return
+ }
+ }
+ }
+
// MARK: - Private
private func requestNotificationPermissions(completion: @escaping (Bool) -> Void) {
@@ -170,16 +134,17 @@ class NotificationManager: NotificationProviderDelegate {
userNotificationCenter.getNotificationSettings { notificationSettings in
switch notificationSettings.authorizationStatus {
case .notDetermined:
- userNotificationCenter
- .requestAuthorization(options: authorizationOptions) { granted, error in
- if let error = error {
- self.logger.error(
- error: error,
- message: "Failed to obtain user notifications authorization"
- )
- }
- completion(granted)
+ userNotificationCenter.requestAuthorization(
+ options: authorizationOptions
+ ) { granted, error in
+ if let error = error {
+ self.logger.error(
+ error: error,
+ message: "Failed to obtain user notifications authorization"
+ )
}
+ completion(granted)
+ }
case .authorized, .provisional:
completion(true)
@@ -203,19 +168,15 @@ class NotificationManager: NotificationProviderDelegate {
let notificationCenter = UNUserNotificationCenter.current()
if notificationProvider.shouldRemovePendingRequests {
- notificationCenter
- .removePendingNotificationRequests(withIdentifiers: [
- notificationProvider
- .identifier,
- ])
+ notificationCenter.removePendingNotificationRequests(withIdentifiers: [
+ notificationProvider.identifier,
+ ])
}
if notificationProvider.shouldRemoveDeliveredRequests {
- notificationCenter
- .removeDeliveredNotifications(withIdentifiers: [
- notificationProvider
- .identifier,
- ])
+ notificationCenter.removeDeliveredNotifications(withIdentifiers: [
+ notificationProvider.identifier,
+ ])
}
if let request = notificationProvider.notificationRequest {
@@ -224,10 +185,12 @@ class NotificationManager: NotificationProviderDelegate {
notificationCenter.add(request) { error in
if let error = error {
- self.logger
- .error(
- "Failed to add notification request with identifier \(request.identifier). Error: \(error.localizedDescription)"
- )
+ self.logger.error(
+ """
+ Failed to add notification request with identifier \
+ \(request.identifier). Error: \(error.localizedDescription)
+ """
+ )
}
}
}
diff --git a/ios/MullvadVPN/Notifications/NotificationManagerDelegate.swift b/ios/MullvadVPN/Notifications/NotificationManagerDelegate.swift
new file mode 100644
index 0000000000..818d7deaad
--- /dev/null
+++ b/ios/MullvadVPN/Notifications/NotificationManagerDelegate.swift
@@ -0,0 +1,16 @@
+//
+// NotificationManagerDelegate.swift
+// MullvadVPN
+//
+// Created by pronebird on 09/12/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+protocol NotificationManagerDelegate: AnyObject {
+ func notificationManagerDidUpdateInAppNotifications(
+ _ manager: NotificationManager,
+ notifications: [InAppNotificationDescriptor]
+ )
+}
diff --git a/ios/MullvadVPN/Notifications/NotificationProvider.swift b/ios/MullvadVPN/Notifications/NotificationProvider.swift
new file mode 100644
index 0000000000..67299226c5
--- /dev/null
+++ b/ios/MullvadVPN/Notifications/NotificationProvider.swift
@@ -0,0 +1,36 @@
+//
+// NotificationProvider.swift
+// MullvadVPN
+//
+// Created by pronebird on 09/12/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+/// Notification provider delegate primarily used by `NotificationManager`.
+protocol NotificationProviderDelegate: AnyObject {
+ func notificationProviderDidInvalidate(_ notificationProvider: NotificationProvider)
+}
+
+/// Base class for all notification providers.
+class NotificationProvider: NotificationProviderProtocol {
+ weak var delegate: NotificationProviderDelegate?
+
+ var identifier: String {
+ return "default"
+ }
+
+ func invalidate() {
+ let executor = {
+ self.delegate?.notificationProviderDidInvalidate(self)
+ return
+ }
+
+ if Thread.isMainThread {
+ executor()
+ } else {
+ DispatchQueue.main.async(execute: executor)
+ }
+ }
+}
diff --git a/ios/MullvadVPN/Notifications/NotificationProviderProtocol.swift b/ios/MullvadVPN/Notifications/NotificationProviderProtocol.swift
new file mode 100644
index 0000000000..ee512e3961
--- /dev/null
+++ b/ios/MullvadVPN/Notifications/NotificationProviderProtocol.swift
@@ -0,0 +1,19 @@
+//
+// NotificationProviderProtocol.swift
+// MullvadVPN
+//
+// Created by pronebird on 09/12/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+/// Base protocol for notification providers.
+protocol NotificationProviderProtocol {
+ /// Unique provider identifier used to identify notification providers and notifications
+ /// produced by them.
+ var identifier: String { get }
+
+ /// Tell notifcation manager to update the associated notification.
+ func invalidate()
+}
diff --git a/ios/MullvadVPN/Notifications/SystemNotificationProvider.swift b/ios/MullvadVPN/Notifications/SystemNotificationProvider.swift
new file mode 100644
index 0000000000..f346e3e230
--- /dev/null
+++ b/ios/MullvadVPN/Notifications/SystemNotificationProvider.swift
@@ -0,0 +1,26 @@
+//
+// SystemNotificationProvider.swift
+// MullvadVPN
+//
+// Created by pronebird on 09/12/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import UserNotifications
+
+/// Protocol describing a system notification provider.
+protocol SystemNotificationProvider: NotificationProviderProtocol {
+ /// Notification request if available, otherwise `nil`.
+ var notificationRequest: UNNotificationRequest? { get }
+
+ /// Whether any pending requests should be removed.
+ var shouldRemovePendingRequests: Bool { get }
+
+ /// 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/NotificationBannerView.swift b/ios/MullvadVPN/Notifications/UI/NotificationBannerView.swift
index 325230d8a6..858d8a7f6b 100644
--- a/ios/MullvadVPN/NotificationBannerView.swift
+++ b/ios/MullvadVPN/Notifications/UI/NotificationBannerView.swift
@@ -8,22 +8,7 @@
import UIKit
-enum NotificationBannerStyle {
- case success, warning, error
-
- fileprivate var color: UIColor {
- switch self {
- case .success:
- return UIColor.InAppNotificationBanner.successIndicatorColor
- case .warning:
- return UIColor.InAppNotificationBanner.warningIndicatorColor
- case .error:
- return UIColor.InAppNotificationBanner.errorIndicatorColor
- }
- }
-}
-
-class NotificationBannerView: UIView {
+final class NotificationBannerView: UIView {
private static let indicatorViewSize = CGSize(width: 12, height: 12)
private let backgroundView: UIVisualEffectView = {
@@ -145,3 +130,16 @@ class NotificationBannerView: UIView {
fatalError("init(coder:) has not been implemented")
}
}
+
+private extension NotificationBannerStyle {
+ var color: UIColor {
+ switch self {
+ case .success:
+ return UIColor.InAppNotificationBanner.successIndicatorColor
+ case .warning:
+ return UIColor.InAppNotificationBanner.warningIndicatorColor
+ case .error:
+ return UIColor.InAppNotificationBanner.errorIndicatorColor
+ }
+ }
+}
diff --git a/ios/MullvadVPN/NotificationContainerView.swift b/ios/MullvadVPN/Notifications/UI/NotificationContainerView.swift
index 1a4ee8a5d5..8f77accff6 100644
--- a/ios/MullvadVPN/NotificationContainerView.swift
+++ b/ios/MullvadVPN/Notifications/UI/NotificationContainerView.swift
@@ -8,7 +8,7 @@
import UIKit
-class NotificationContainerView: UIView {
+final class NotificationContainerView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let hitView = super.hitTest(point, with: event)
diff --git a/ios/MullvadVPN/NotificationController.swift b/ios/MullvadVPN/Notifications/UI/NotificationController.swift
index 8d07dfefb9..aa325332ed 100644
--- a/ios/MullvadVPN/NotificationController.swift
+++ b/ios/MullvadVPN/Notifications/UI/NotificationController.swift
@@ -8,14 +8,7 @@
import UIKit
-struct InAppNotificationDescriptor: Equatable {
- var identifier: String
- var style: NotificationBannerStyle
- var title: String
- var body: String
-}
-
-class NotificationController: UIViewController {
+final class NotificationController: UIViewController {
let bannerView: NotificationBannerView = {
let bannerView = NotificationBannerView()
bannerView.translatesAutoresizingMaskIntoConstraints = false