summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2023-04-28 16:47:01 +0200
committerAndrej Mihajlov <and@mullvad.net>2023-04-28 16:47:01 +0200
commite367a8a011b0875a565f69d8146aca5942e38e16 (patch)
tree4b2ab3341dbe97539a0c518f874ae6a2dc7b3c91
parent3595658d8f0f0f6851f113effcab0d2c93e75cb8 (diff)
parentd288c8742cc36d87edb5f3042130ee2633a20349 (diff)
downloadmullvadvpn-e367a8a011b0875a565f69d8146aca5942e38e16.tar.xz
mullvadvpn-e367a8a011b0875a565f69d8146aca5942e38e16.zip
Merge branch 'handle-account-expiry-transitions'
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj9
-rw-r--r--ios/MullvadVPN/AppDelegate.swift7
-rw-r--r--ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift34
-rw-r--r--ios/MullvadVPN/Coordinators/App/TunnelCoordinator.swift2
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift9
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift10
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/NotificationConfiguration.swift23
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotification.swift21
-rw-r--r--ios/MullvadVPN/Notifications/NotificationManager.swift15
-rw-r--r--ios/MullvadVPN/SceneDelegate.swift2
-rw-r--r--ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift13
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelBlockObserver.swift17
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManager.swift94
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelObserver.swift7
-rw-r--r--ios/MullvadVPN/View controllers/Account/AccountInteractor.swift2
-rw-r--r--ios/MullvadVPN/View controllers/Account/AccountViewController.swift4
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsInteractor.swift2
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift2
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/TunnelViewControllerInteractor.swift6
19 files changed, 205 insertions, 74 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index efb435cd82..d12da06daa 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -244,6 +244,7 @@
58C3F4F92964B08300D72515 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3F4F82964B08300D72515 /* MapViewController.swift */; };
58C3F4FB296C3AD500D72515 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3F4FA296C3AD500D72515 /* SettingsCoordinator.swift */; };
58C774BE29A7A249003A1A56 /* CustomNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C774BD29A7A249003A1A56 /* CustomNavigationController.swift */; };
+ 58C8191829FAA2C400DEB1B4 /* NotificationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C8191729FAA2C400DEB1B4 /* NotificationConfiguration.swift */; };
58CAF9F82983D36800BE19F7 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CAF9F72983D36800BE19F7 /* Coordinator.swift */; };
58CAF9FA2983E0C600BE19F7 /* LoginCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CAF9F92983E0C600BE19F7 /* LoginCoordinator.swift */; };
58CAFA002983FF0200BE19F7 /* LoginInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CAF9FF2983FF0200BE19F7 /* LoginInteractor.swift */; };
@@ -360,8 +361,8 @@
58FEEB58260B662E00A621A8 /* AutomaticKeyboardResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FEEB57260B662E00A621A8 /* AutomaticKeyboardResponder.swift */; };
58FF2C03281BDE02009EF542 /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF2C02281BDE02009EF542 /* SettingsManager.swift */; };
7A09C98129D99215000C2CAC /* String+FuzzyMatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */; };
- 7A7AD28F29DEDB1C00480EF1 /* SettingsContentBlockersHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7AD28E29DEDB1C00480EF1 /* SettingsContentBlockersHeaderView.swift */; };
7A7AD28D29DC677800480EF1 /* FirstTimeLaunch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */; };
+ 7A7AD28F29DEDB1C00480EF1 /* SettingsContentBlockersHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7AD28E29DEDB1C00480EF1 /* SettingsContentBlockersHeaderView.swift */; };
7A818F1F29F0305800C7F0F4 /* RootConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A818F1E29F0305800C7F0F4 /* RootConfiguration.swift */; };
7AD2DA1529DC4EB900250737 /* UISearchBar+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD2DA1429DC4EB900250737 /* UISearchBar+Appearance.swift */; };
7AF0419E29E957EB00D492DD /* AccountCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0419D29E957EB00D492DD /* AccountCoordinator.swift */; };
@@ -873,6 +874,7 @@
58C3F4F82964B08300D72515 /* MapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewController.swift; sourceTree = "<group>"; };
58C3F4FA296C3AD500D72515 /* SettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = "<group>"; };
58C774BD29A7A249003A1A56 /* CustomNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNavigationController.swift; sourceTree = "<group>"; };
+ 58C8191729FAA2C400DEB1B4 /* NotificationConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationConfiguration.swift; sourceTree = "<group>"; };
58CAF9F72983D36800BE19F7 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = "<group>"; };
58CAF9F92983E0C600BE19F7 /* LoginCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginCoordinator.swift; sourceTree = "<group>"; };
58CAF9FF2983FF0200BE19F7 /* LoginInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginInteractor.swift; sourceTree = "<group>"; };
@@ -949,8 +951,8 @@
58FEEB57260B662E00A621A8 /* AutomaticKeyboardResponder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomaticKeyboardResponder.swift; sourceTree = "<group>"; };
58FF2C02281BDE02009EF542 /* SettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsManager.swift; sourceTree = "<group>"; };
7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+FuzzyMatch.swift"; sourceTree = "<group>"; };
- 7A7AD28E29DEDB1C00480EF1 /* SettingsContentBlockersHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsContentBlockersHeaderView.swift; sourceTree = "<group>"; };
7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstTimeLaunch.swift; sourceTree = "<group>"; };
+ 7A7AD28E29DEDB1C00480EF1 /* SettingsContentBlockersHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsContentBlockersHeaderView.swift; sourceTree = "<group>"; };
7A818F1E29F0305800C7F0F4 /* RootConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootConfiguration.swift; sourceTree = "<group>"; };
7AD2DA1429DC4EB900250737 /* UISearchBar+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISearchBar+Appearance.swift"; sourceTree = "<group>"; };
7AF0419D29E957EB00D492DD /* AccountCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCoordinator.swift; sourceTree = "<group>"; };
@@ -1282,7 +1284,6 @@
583FE01829C19709006E85F9 /* Settings */ = {
isa = PBXGroup;
children = (
- 582BB1B2229574F40055B6EF /* SettingsAccountCell.swift */,
5819C2162729595500D6EC38 /* SettingsAddDNSEntryCell.swift */,
582BB1AE229566420055B6EF /* SettingsCell.swift */,
5864AF0029C7879B005B0CD9 /* SettingsCellFactory.swift */,
@@ -1582,6 +1583,7 @@
587B75422669034500DEF7E9 /* Notification Providers */ = {
isa = PBXGroup;
children = (
+ 58C8191729FAA2C400DEB1B4 /* NotificationConfiguration.swift */,
587B75402668FD7700DEF7E9 /* AccountExpirySystemNotificationProvider.swift */,
58607A4C2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift */,
F07CFF1F29F2720E008C0343 /* RegisteredDeviceInAppNotification.swift */,
@@ -2725,6 +2727,7 @@
580909D32876D09A0078138D /* RevokedDeviceViewController.swift in Sources */,
5835B7CC233B76CB0096D79F /* TunnelManager.swift in Sources */,
58607A4D2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift in Sources */,
+ 58C8191829FAA2C400DEB1B4 /* NotificationConfiguration.swift in Sources */,
06410E07292D108E00AFC18C /* SettingsStore.swift in Sources */,
586A950D290125F0007BAF2B /* PresentAlertOperation.swift in Sources */,
7A7AD28F29DEDB1C00480EF1 /* SettingsContentBlockersHeaderView.swift in Sources */,
diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift
index 29db4311c8..640d7fc86c 100644
--- a/ios/MullvadVPN/AppDelegate.swift
+++ b/ios/MullvadVPN/AppDelegate.swift
@@ -506,13 +506,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
- withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions)
- -> Void
+ withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
if #available(iOS 14.0, *) {
- completionHandler([.list])
+ completionHandler([.list, .banner, .sound])
} else {
- completionHandler([])
+ completionHandler([.sound, .alert])
}
}
}
diff --git a/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift b/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift
index 17b02b23cf..b4695072dd 100644
--- a/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift
@@ -616,8 +616,8 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
private func addTunnelObserver() {
let tunnelObserver =
- TunnelBlockObserver(didUpdateDeviceState: { [weak self] manager, deviceState in
- self?.deviceStateDidChange(deviceState)
+ TunnelBlockObserver(didUpdateDeviceState: { [weak self] manager, deviceState, previousDeviceState in
+ self?.deviceStateDidChange(deviceState, previousDeviceState: previousDeviceState)
})
tunnelManager.addObserver(tunnelObserver)
@@ -629,16 +629,28 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
splitViewController.preferredDisplayMode = tunnelManager.deviceState.splitViewMode
}
- private func deviceStateDidChange(_ deviceState: DeviceState) {
+ private func deviceStateDidChange(_ deviceState: DeviceState, previousDeviceState: DeviceState) {
splitViewController.preferredDisplayMode = deviceState.splitViewMode
switch deviceState {
case let .loggedIn(accountData, _):
- updateOutOfTimeTimer()
+ updateOutOfTimeTimer(accountData: accountData)
updateView(deviceState: deviceState, showDeviceInfo: false)
- if !accountData.isExpired {
+
+ // Handle transition to and from expired state.
+ let accountWasExpired = previousDeviceState.accountData?.isExpired ?? false
+ switch (accountWasExpired, accountData.isExpired) {
+ case (true, false):
+ continueFlow(animated: true)
router.dismiss(.outOfTime, animated: true)
+
+ case (false, true):
+ router.present(.outOfTime, animated: true)
+
+ default:
+ break
}
+
case .revoked:
cancelOutOfTimeTimer()
router.present(.revoked, animated: true)
@@ -663,13 +675,13 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
// MARK: - Out of time
- private func updateOutOfTimeTimer() {
+ private func updateOutOfTimeTimer(accountData: StoredAccountData) {
cancelOutOfTimeTimer()
- guard let expiry = tunnelManager.deviceState.accountData?.expiry else { return }
+ guard !accountData.isExpired else { return }
- let timer = Timer(fire: expiry, interval: 0, repeats: false, block: { [weak self] _ in
- self?.outOfTimeTimerDidFire()
+ let timer = Timer(fire: accountData.expiry, interval: 0, repeats: false, block: { [weak self] _ in
+ self?.router.present(.outOfTime, animated: true)
})
RunLoop.main.add(timer, forMode: .common)
@@ -677,10 +689,6 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
outOfTimeTimer = timer
}
- private func outOfTimeTimerDidFire() {
- router.present(.outOfTime, animated: true)
- }
-
private func cancelOutOfTimeTimer() {
outOfTimeTimer?.invalidate()
outOfTimeTimer = nil
diff --git a/ios/MullvadVPN/Coordinators/App/TunnelCoordinator.swift b/ios/MullvadVPN/Coordinators/App/TunnelCoordinator.swift
index d619f1ebc5..c63b0f80ef 100644
--- a/ios/MullvadVPN/Coordinators/App/TunnelCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/App/TunnelCoordinator.swift
@@ -35,7 +35,7 @@ class TunnelCoordinator: Coordinator, NotificationManagerDelegate {
func start() {
let tunnelObserver =
- TunnelBlockObserver(didUpdateDeviceState: { [weak self] _, deviceState in
+ TunnelBlockObserver(didUpdateDeviceState: { [weak self] _, deviceState, previousDeviceState in
self?.updateVisibility(animated: true)
})
diff --git a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift
index 350aa0dcb0..ddaa37b68b 100644
--- a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift
+++ b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift
@@ -8,9 +8,6 @@
import Foundation
-private let triggerInterval = 3
-private let refreshInterval = 60
-
final class AccountExpiryInAppNotificationProvider: NotificationProvider, InAppNotificationProvider {
private var accountExpiry: Date?
private var tunnelObserver: TunnelBlockObserver?
@@ -23,7 +20,7 @@ final class AccountExpiryInAppNotificationProvider: NotificationProvider, InAppN
didLoadConfiguration: { [weak self] tunnelManager in
self?.invalidate(deviceState: tunnelManager.deviceState)
},
- didUpdateDeviceState: { [weak self] tunnelManager, deviceState in
+ didUpdateDeviceState: { [weak self] tunnelManager, deviceState, previousDeviceState in
self?.invalidate(deviceState: deviceState)
}
)
@@ -89,7 +86,7 @@ final class AccountExpiryInAppNotificationProvider: NotificationProvider, InAppN
return Calendar.current.date(
byAdding: .day,
- value: -triggerInterval,
+ value: -NotificationConfiguration.closeToExpiryTriggerInterval,
to: accountExpiry
)
}
@@ -120,7 +117,7 @@ final class AccountExpiryInAppNotificationProvider: NotificationProvider, InAppN
}
timer.schedule(
wallDeadline: .now() + fireDate.timeIntervalSince(now),
- repeating: .seconds(refreshInterval)
+ repeating: .seconds(NotificationConfiguration.closeToExpiryInAppNotificationRefreshInterval)
)
timer.activate()
diff --git a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift
index c32bd24b28..b2edfb49f8 100644
--- a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift
+++ b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift
@@ -9,11 +9,7 @@
import Foundation
import UserNotifications
-private let triggerInterval = 3
-
-final class AccountExpirySystemNotificationProvider: NotificationProvider,
- SystemNotificationProvider
-{
+final class AccountExpirySystemNotificationProvider: NotificationProvider, SystemNotificationProvider {
private var accountExpiry: Date?
private var tunnelObserver: TunnelBlockObserver?
private var defaultActionHandler: (() -> Void)?
@@ -25,7 +21,7 @@ final class AccountExpirySystemNotificationProvider: NotificationProvider,
didLoadConfiguration: { [weak self] tunnelManager in
self?.invalidate(deviceState: tunnelManager.deviceState)
},
- didUpdateDeviceState: { [weak self] tunnelManager, deviceState in
+ didUpdateDeviceState: { [weak self] tunnelManager, deviceState, previousDeviceState in
self?.invalidate(deviceState: deviceState)
}
)
@@ -101,7 +97,7 @@ final class AccountExpirySystemNotificationProvider: NotificationProvider,
guard let triggerDate = Calendar.current.date(
byAdding: .day,
- value: -triggerInterval,
+ value: -NotificationConfiguration.closeToExpiryTriggerInterval,
to: accountExpiry
) else { return nil }
diff --git a/ios/MullvadVPN/Notifications/Notification Providers/NotificationConfiguration.swift b/ios/MullvadVPN/Notifications/Notification Providers/NotificationConfiguration.swift
new file mode 100644
index 0000000000..98e1913608
--- /dev/null
+++ b/ios/MullvadVPN/Notifications/Notification Providers/NotificationConfiguration.swift
@@ -0,0 +1,23 @@
+//
+// NotificationConfiguration.swift
+// MullvadVPN
+//
+// Created by pronebird on 27/04/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+enum NotificationConfiguration {
+ /**
+ Duration measured in days, before the account expiry, when notification is scheduled to remind user to add more
+ time on account.
+ */
+ static let closeToExpiryTriggerInterval = 3
+
+ /**
+ Time interval measured in seconds at which to refresh account expiry in-app notification, which reformats
+ the duration until account expiry over time.
+ */
+ static let closeToExpiryInAppNotificationRefreshInterval = 60
+}
diff --git a/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotification.swift b/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotification.swift
index d468e009c7..ae6007451f 100644
--- a/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotification.swift
+++ b/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotification.swift
@@ -1,5 +1,5 @@
//
-// AccountCreationInAppNotification.swift
+// RegisteredDeviceInAppNotification.swift
// MullvadVPN
//
// Created by Mojgan on 2023-04-21.
@@ -10,9 +10,7 @@ import Foundation
import UIKit.UIColor
import UIKit.UIFont
-final class RegisteredDeviceInAppNotification: NotificationProvider,
- InAppNotificationProvider
-{
+final class RegisteredDeviceInAppNotification: NotificationProvider, InAppNotificationProvider {
typealias CompletionHandler = (DeviceState) -> Void
// MARK: - private properties
@@ -78,13 +76,14 @@ final class RegisteredDeviceInAppNotification: NotificationProvider,
}
private func addObservers() {
- tunnelObserver = TunnelBlockObserver(didUpdateDeviceState: { [weak self] tunnelManager, deviceState in
- guard let self = self,
- case .loggedIn = deviceState else { return }
- self.shouldShowBanner = true
- self.deviceState = deviceState
- self.invalidate()
- })
+ tunnelObserver =
+ TunnelBlockObserver(didUpdateDeviceState: { [weak self] tunnelManager, deviceState, previousDeviceState in
+ guard let self = self, case .loggedIn = deviceState else { return }
+
+ self.shouldShowBanner = true
+ self.deviceState = deviceState
+ self.invalidate()
+ })
tunnelObserver.flatMap { tunnelManager.addObserver($0) }
}
}
diff --git a/ios/MullvadVPN/Notifications/NotificationManager.swift b/ios/MullvadVPN/Notifications/NotificationManager.swift
index 6536ae09fc..68c251e9c7 100644
--- a/ios/MullvadVPN/Notifications/NotificationManager.swift
+++ b/ios/MullvadVPN/Notifications/NotificationManager.swift
@@ -96,15 +96,12 @@ final class NotificationManager: NotificationProviderDelegate {
for newRequest in newSystemNotificationRequests {
notificationCenter.add(newRequest) { error in
- if let error = error {
- self.logger.error(
- error: error,
- message: """
- Failed to add notification request with identifier \
- \(newRequest.identifier).
- """
- )
- }
+ guard let error = error else { return }
+
+ self.logger.error(
+ error: error,
+ message: "Failed to add notification request with identifier \(newRequest.identifier)."
+ )
}
}
}
diff --git a/ios/MullvadVPN/SceneDelegate.swift b/ios/MullvadVPN/SceneDelegate.swift
index a256f1add1..eeadacfc13 100644
--- a/ios/MullvadVPN/SceneDelegate.swift
+++ b/ios/MullvadVPN/SceneDelegate.swift
@@ -51,7 +51,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, SettingsMigrationUIHand
didLoadConfiguration: { [weak self] _ in
self?.configureScene()
},
- didUpdateDeviceState: { [weak self] _, deviceState in
+ didUpdateDeviceState: { [weak self] _, deviceState, previousDeviceState in
self?.deviceStateDidChange(deviceState)
}
)
diff --git a/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift b/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift
index b9dca0bf21..7ee39c6934 100644
--- a/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift
+++ b/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift
@@ -85,6 +85,19 @@ enum DeviceState: Codable, Equatable {
return nil
}
}
+
+ /**
+ Mutates account and device data when in logged in state, otherwise does nothing.
+ */
+ mutating func updateData(_ body: (inout StoredAccountData, inout StoredDeviceData) -> Void) {
+ switch self {
+ case var .loggedIn(accountData, deviceData):
+ body(&accountData, &deviceData)
+ self = .loggedIn(accountData, deviceData)
+ case .revoked, .loggedOut:
+ break
+ }
+ }
}
struct StoredDeviceData: Codable, Equatable {
diff --git a/ios/MullvadVPN/TunnelManager/TunnelBlockObserver.swift b/ios/MullvadVPN/TunnelManager/TunnelBlockObserver.swift
index 43b74bd33e..90b90b5470 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelBlockObserver.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelBlockObserver.swift
@@ -11,7 +11,11 @@ import Foundation
final class TunnelBlockObserver: TunnelObserver {
typealias DidLoadConfigurationHandler = (TunnelManager) -> Void
typealias DidUpdateTunnelStatusHandler = (TunnelManager, TunnelStatus) -> Void
- typealias DidUpdateDeviceStateHandler = (TunnelManager, DeviceState) -> Void
+ typealias DidUpdateDeviceStateHandler = (
+ _ tunnelManager: TunnelManager,
+ _ deviceState: DeviceState,
+ _ previousDeviceStaate: DeviceState
+ ) -> Void
typealias DidUpdateTunnelSettingsHandler = (TunnelManager, TunnelSettingsV2) -> Void
typealias DidFailWithErrorHandler = (TunnelManager, Error) -> Void
@@ -43,14 +47,15 @@ final class TunnelBlockObserver: TunnelObserver {
didUpdateTunnelStatus?(manager, tunnelStatus)
}
- func tunnelManager(_ manager: TunnelManager, didUpdateDeviceState deviceState: DeviceState) {
- didUpdateDeviceState?(manager, deviceState)
- }
-
func tunnelManager(
_ manager: TunnelManager,
- didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2
+ didUpdateDeviceState deviceState: DeviceState,
+ previousDeviceState: DeviceState
) {
+ didUpdateDeviceState?(manager, deviceState, previousDeviceState)
+ }
+
+ func tunnelManager(_ manager: TunnelManager, didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2) {
didUpdateTunnelSettings?(manager, tunnelSettings)
}
diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
index 8b4bd53c84..9c9190200b 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
@@ -708,6 +708,7 @@ final class TunnelManager: StorePaymentObserver {
defer { nslock.unlock() }
let shouldCallDelegate = _deviceState != deviceState && _isConfigurationLoaded
+ let previousDeviceState = _deviceState
_deviceState = deviceState
@@ -725,7 +726,11 @@ final class TunnelManager: StorePaymentObserver {
if shouldCallDelegate {
DispatchQueue.main.async {
self.observerList.forEach { observer in
- observer.tunnelManager(self, didUpdateDeviceState: deviceState)
+ observer.tunnelManager(
+ self,
+ didUpdateDeviceState: deviceState,
+ previousDeviceState: previousDeviceState
+ )
}
}
}
@@ -955,6 +960,93 @@ final class TunnelManager: StorePaymentObserver {
}
}
+#if DEBUG
+
+// MARK: - Simulations
+
+extension TunnelManager {
+ enum AccountExpirySimulationOption {
+ case closeToExpiry
+ case expired
+ case active
+
+ fileprivate var date: Date? {
+ let calendar = Calendar.current
+ let now = Date()
+
+ switch self {
+ case .active:
+ return calendar.date(byAdding: .year, value: 1, to: now)
+
+ case .closeToExpiry:
+ return calendar.date(
+ byAdding: DateComponents(day: NotificationConfiguration.closeToExpiryTriggerInterval, second: 5),
+ to: now
+ )
+
+ case .expired:
+ return calendar.date(byAdding: .minute, value: -1, to: now)
+ }
+ }
+ }
+
+ /**
+
+ This function simulates account state transitions. The change is not permanent and any call to
+ `updateAccountData()` will overwrite it, but it's usually enough for quick testing.
+
+ It can be invoked somewhere in `initTunnelManagerOperation` (`AppDelegate`) after tunnel manager is fully
+ initialized. The following code snippet can be used to cycle through various states:
+
+ ```
+ func delay(seconds: UInt) async throws {
+ try await Task.sleep(nanoseconds: UInt64(seconds) * 1_000_000_000)
+ }
+
+ Task {
+ print("Wait 5 seconds")
+ try await delay(seconds: 5)
+
+ print("Simulate active account")
+ self.tunnelManager.simulateAccountExpiration(option: .active)
+ try await delay(seconds: 5)
+
+ print("Simulate close to expiry")
+ self.tunnelManager.simulateAccountExpiration(option: .closeToExpiry)
+ try await delay(seconds: 10)
+
+ print("Simulate expired account")
+ self.tunnelManager.simulateAccountExpiration(option: .expired)
+ try await delay(seconds: 5)
+
+ print("Simulate active account")
+ self.tunnelManager.simulateAccountExpiration(option: .active)
+ }
+ ```
+
+ Another way to invoke this code is to pause debugger and run it directly:
+
+ ```
+ command alias swift expression -l Swift -O --
+
+ swift import MullvadVPN
+ swift (UIApplication.shared.delegate as? AppDelegate)?.tunnelManager.simulateAccountExpiration(option: .closeToExpiry)
+ ```
+
+ */
+ func simulateAccountExpiration(option: AccountExpirySimulationOption) {
+ scheduleDeviceStateUpdate(taskName: "Simulating account expiry", reconnectTunnel: false) { deviceState in
+ deviceState.updateData { accountData, deviceData in
+ guard let date = option.date else { return }
+
+ accountData.expiry = date
+ }
+ }
+ }
+}
+
+#endif
+
private struct TunnelInteractorProxy: TunnelInteractor {
private let tunnelManager: TunnelManager
diff --git a/ios/MullvadVPN/TunnelManager/TunnelObserver.swift b/ios/MullvadVPN/TunnelManager/TunnelObserver.swift
index 148da699ae..f53461fb70 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelObserver.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelObserver.swift
@@ -11,12 +11,11 @@ import Foundation
protocol TunnelObserver: AnyObject {
func tunnelManagerDidLoadConfiguration(_ manager: TunnelManager)
func tunnelManager(_ manager: TunnelManager, didUpdateTunnelStatus tunnelStatus: TunnelStatus)
- func tunnelManager(_ manager: TunnelManager, didUpdateDeviceState deviceState: DeviceState)
-
func tunnelManager(
_ manager: TunnelManager,
- didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2
+ didUpdateDeviceState deviceState: DeviceState,
+ previousDeviceState: DeviceState
)
-
+ func tunnelManager(_ manager: TunnelManager, didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2)
func tunnelManager(_ manager: TunnelManager, didFailWithError error: Error)
}
diff --git a/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift b/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift
index c454181d95..6d8de2958b 100644
--- a/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift
+++ b/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift
@@ -27,7 +27,7 @@ final class AccountInteractor {
self.tunnelManager = tunnelManager
let tunnelObserver =
- TunnelBlockObserver(didUpdateDeviceState: { [weak self] manager, deviceState in
+ TunnelBlockObserver(didUpdateDeviceState: { [weak self] manager, deviceState, previousDeviceState in
self?.didReceiveDeviceState?(deviceState)
})
diff --git a/ios/MullvadVPN/View controllers/Account/AccountViewController.swift b/ios/MullvadVPN/View controllers/Account/AccountViewController.swift
index a7c4beb867..9edd3c85fc 100644
--- a/ios/MullvadVPN/View controllers/Account/AccountViewController.swift
+++ b/ios/MullvadVPN/View controllers/Account/AccountViewController.swift
@@ -102,8 +102,8 @@ class AccountViewController: UIViewController {
)
contentView.logoutButton.addTarget(self, action: #selector(doLogout), for: .touchUpInside)
- interactor.didReceiveDeviceState = { [weak self] newDeviceState in
- self?.updateView(from: newDeviceState)
+ interactor.didReceiveDeviceState = { [weak self] deviceState in
+ self?.updateView(from: deviceState)
}
interactor.didReceivePaymentEvent = { [weak self] event in
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsInteractor.swift b/ios/MullvadVPN/View controllers/Settings/SettingsInteractor.swift
index cd5c6e6e68..c695772823 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsInteractor.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsInteractor.swift
@@ -22,7 +22,7 @@ final class SettingsInteractor {
self.tunnelManager = tunnelManager
let tunnelObserver =
- TunnelBlockObserver(didUpdateDeviceState: { [weak self] manager, deviceState in
+ TunnelBlockObserver(didUpdateDeviceState: { [weak self] manager, deviceState, previousDeviceState in
self?.didUpdateDeviceState?(deviceState)
})
diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift
index 4f190995b6..4ec24fd400 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift
@@ -56,7 +56,7 @@ class TunnelViewController: UIViewController, RootContainment {
override func viewDidLoad() {
super.viewDidLoad()
- interactor.didUpdateDeviceState = { [weak self] deviceState in
+ interactor.didUpdateDeviceState = { [weak self] deviceState, previousDeviceState in
self?.setNeedsHeaderBarStyleAppearanceUpdate()
}
diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewControllerInteractor.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewControllerInteractor.swift
index b23bc214f2..e0eb61c8fb 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewControllerInteractor.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewControllerInteractor.swift
@@ -12,7 +12,7 @@ final class TunnelViewControllerInteractor {
private let tunnelManager: TunnelManager
private var tunnelObserver: TunnelObserver?
- var didUpdateDeviceState: ((DeviceState) -> Void)?
+ var didUpdateDeviceState: ((_ deviceState: DeviceState, _ previousDeviceState: DeviceState) -> Void)?
var didUpdateTunnelStatus: ((TunnelStatus) -> Void)?
var tunnelStatus: TunnelStatus {
@@ -30,8 +30,8 @@ final class TunnelViewControllerInteractor {
didUpdateTunnelStatus: { [weak self] tunnelManager, tunnelStatus in
self?.didUpdateTunnelStatus?(tunnelStatus)
},
- didUpdateDeviceState: { [weak self] tunnelManager, deviceState in
- self?.didUpdateDeviceState?(deviceState)
+ didUpdateDeviceState: { [weak self] tunnelManager, deviceState, previousDeviceState in
+ self?.didUpdateDeviceState?(deviceState, previousDeviceState)
}
)
tunnelManager.addObserver(tunnelObserver)