summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2022-11-03 11:14:18 +0100
committerAndrej Mihajlov <and@mullvad.net>2022-11-03 11:14:18 +0100
commit4e3ce788f6e75a11ff6d22140adda6fdb1f0d716 (patch)
treea99b8c166b03a1cb7cba448d28388d2141f264c5
parentb8523b04e98e444f199ab3718adb48f8faf7bd55 (diff)
parent19aa248e6d5fd58ba224bfd2ed1db8214f19f67b (diff)
downloadmullvadvpn-4e3ce788f6e75a11ff6d22140adda6fdb1f0d716.tar.xz
mullvadvpn-4e3ce788f6e75a11ff6d22140adda6fdb1f0d716.zip
Merge branch 'add-settings-interactors'
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj20
-rw-r--r--ios/MullvadVPN/AccountInteractor.swift79
-rw-r--r--ios/MullvadVPN/AccountViewController.swift126
-rw-r--r--ios/MullvadVPN/PreferencesInteractor.swift32
-rw-r--r--ios/MullvadVPN/PreferencesViewController.swift43
-rw-r--r--ios/MullvadVPN/ProblemReportInteractor.swift64
-rw-r--r--ios/MullvadVPN/ProblemReportViewController.swift50
-rw-r--r--ios/MullvadVPN/SceneDelegate.swift8
-rw-r--r--ios/MullvadVPN/SettingsDataSource.swift40
-rw-r--r--ios/MullvadVPN/SettingsInteractor.swift33
-rw-r--r--ios/MullvadVPN/SettingsInteractorFactory.swift45
-rw-r--r--ios/MullvadVPN/SettingsNavigationController.swift29
-rw-r--r--ios/MullvadVPN/SettingsViewController.swift6
13 files changed, 400 insertions, 175 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 73b7b77a3e..b8d6b404c8 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -137,6 +137,8 @@
585E820327F3285E00939F0E /* SendStoreReceiptOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585E820227F3285E00939F0E /* SendStoreReceiptOperation.swift */; };
5862805422428EF100F5A6E1 /* TranslucentButtonBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */; };
5867770E29096984006F721F /* OutOfTimeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5867770D29096984006F721F /* OutOfTimeInteractor.swift */; };
+ 58677710290975E9006F721F /* SettingsInteractorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5867770F290975E8006F721F /* SettingsInteractorFactory.swift */; };
+ 58677712290976FB006F721F /* SettingsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58677711290976FB006F721F /* SettingsInteractor.swift */; };
5867771429097BCD006F721F /* PaymentState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5867771329097BCD006F721F /* PaymentState.swift */; };
5867771629097C5B006F721F /* ProductState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5867771529097C5B006F721F /* ProductState.swift */; };
5868585524054096000B8131 /* AppButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5868585424054096000B8131 /* AppButton.swift */; };
@@ -150,6 +152,7 @@
586A9516290133ED007BAF2B /* AnyIPAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584D26BE270C550B004EA533 /* AnyIPAddress.swift */; };
586A95172901344A007BAF2B /* IPAddress+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AC115628F848D00037AF9A /* IPAddress+Codable.swift */; };
586E54FB27A2DF6D0029B88B /* SendTunnelProviderMessageOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586E54FA27A2DF6D0029B88B /* SendTunnelProviderMessageOperation.swift */; };
+ 5871167F2910035700D41AAC /* PreferencesInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5871167E2910035700D41AAC /* PreferencesInteractor.swift */; };
5871FB96254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5871FB95254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift */; };
5871FBA0254C26C00051A0A4 /* NSRegularExpression+IPAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5871FB9F254C26BF0051A0A4 /* NSRegularExpression+IPAddress.swift */; };
5872631B283F6EAB00E14ADF /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 5872631A283F6EAB00E14ADF /* Intents.intentdefinition */; };
@@ -159,6 +162,8 @@
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 */; };
+ 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 */; };
5878A27529093A310096FC88 /* StorePaymentEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A27429093A310096FC88 /* StorePaymentEvent.swift */; };
5878A27729093A4F0096FC88 /* StorePaymentBlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A27629093A4F0096FC88 /* StorePaymentBlockObserver.swift */; };
@@ -625,6 +630,8 @@
5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslucentButtonBlurView.swift; sourceTree = "<group>"; };
5866F39B2243B82D00168AE5 /* MullvadVPN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MullvadVPN.entitlements; sourceTree = "<group>"; };
5867770D29096984006F721F /* OutOfTimeInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutOfTimeInteractor.swift; sourceTree = "<group>"; };
+ 5867770F290975E8006F721F /* SettingsInteractorFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInteractorFactory.swift; sourceTree = "<group>"; };
+ 58677711290976FB006F721F /* SettingsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInteractor.swift; sourceTree = "<group>"; };
5867771329097BCD006F721F /* PaymentState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentState.swift; sourceTree = "<group>"; };
5867771529097C5B006F721F /* ProductState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductState.swift; sourceTree = "<group>"; };
5868585424054096000B8131 /* AppButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppButton.swift; sourceTree = "<group>"; };
@@ -632,6 +639,7 @@
586A95112901321B007BAF2B /* IPv6Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPv6Endpoint.swift; sourceTree = "<group>"; };
586A951329013235007BAF2B /* AnyIPEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyIPEndpoint.swift; sourceTree = "<group>"; };
586E54FA27A2DF6D0029B88B /* SendTunnelProviderMessageOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendTunnelProviderMessageOperation.swift; sourceTree = "<group>"; };
+ 5871167E2910035700D41AAC /* PreferencesInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesInteractor.swift; sourceTree = "<group>"; };
5871FB95254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsolidatedApplicationLog.swift; sourceTree = "<group>"; };
5871FB9F254C26BF0051A0A4 /* NSRegularExpression+IPAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSRegularExpression+IPAddress.swift"; sourceTree = "<group>"; };
5872631A283F6EAB00E14ADF /* Intents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = Intents.intentdefinition; sourceTree = "<group>"; };
@@ -642,6 +650,8 @@
5875960926F371FC00BF6711 /* Tunnel+Messaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tunnel+Messaging.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>"; };
+ 5878A27029091CF20096FC88 /* AccountInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountInteractor.swift; sourceTree = "<group>"; };
5878A27229091D6D0096FC88 /* TunnelBlockObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelBlockObserver.swift; sourceTree = "<group>"; };
5878A27429093A310096FC88 /* StorePaymentEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorePaymentEvent.swift; sourceTree = "<group>"; };
5878A27629093A4F0096FC88 /* StorePaymentBlockObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorePaymentBlockObserver.swift; sourceTree = "<group>"; };
@@ -1223,6 +1233,7 @@
58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */,
58CCA01D2242787B004F3011 /* AccountTextField.swift */,
582AE30F2440A6CA00E6733A /* AccountTokenInput.swift */,
+ 5878A27029091CF20096FC88 /* AccountInteractor.swift */,
58CCA01722426713004F3011 /* AccountViewController.swift */,
5867771329097BCD006F721F /* PaymentState.swift */,
5867771529097C5B006F721F /* ProductState.swift */,
@@ -1288,11 +1299,13 @@
584D26C3270C855A004EA533 /* PreferencesDataSource.swift */,
587EB6732714520600123C75 /* PreferencesDataSourceDelegate.swift */,
58ACF6482655365700ACE4B7 /* PreferencesViewController.swift */,
+ 5871167E2910035700D41AAC /* PreferencesInteractor.swift */,
587EB671271451E300123C75 /* PreferencesViewModel.swift */,
58F8AC0D25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift */,
58EF580A25D69D7A00AEBA94 /* ProblemReportSubmissionOverlayView.swift */,
58293FAC2510CA58005D0BB5 /* ProblemReportViewController.swift */,
585DA87526B0249A00B8C587 /* RelayCache */,
+ 5878A26E2907E7E00096FC88 /* ProblemReportInteractor.swift */,
06FAE67828F83CA50033DD93 /* RESTCreateApplePaymentResponse+Localization.swift */,
58F1311427E0B2AB007AC5BC /* Result+Extensions.swift */,
580909D22876D09A0078138D /* RevokedDeviceViewController.swift */,
@@ -1311,9 +1324,11 @@
584D26C5270C8741004EA533 /* SettingsDNSTextCell.swift */,
580F8B88281A79A7002E0998 /* SettingsManager */,
58E6771E24ADFE7800AA26E7 /* SettingsNavigationController.swift */,
+ 5867770F290975E8006F721F /* SettingsInteractorFactory.swift */,
584D26C1270C8542004EA533 /* SettingsStaticTextFooterView.swift */,
58ACF64A26553C3F00ACE4B7 /* SettingsSwitchCell.swift */,
58CCA01122424D11004F3011 /* SettingsViewController.swift */,
+ 58677711290976FB006F721F /* SettingsInteractor.swift */,
75FD0C2028B108570021E33E /* ShortcutsDataSource.swift */,
75FD0C2228B109860021E33E /* ShortcutsDataSourceDelegate.swift */,
753D6C0B28B4BF3E0052D9E1 /* ShortcutsManager.swift */,
@@ -2081,6 +2096,7 @@
5867770E29096984006F721F /* OutOfTimeInteractor.swift in Sources */,
5872631B283F6EAB00E14ADF /* Intents.intentdefinition in Sources */,
58F8AC0E25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift in Sources */,
+ 5878A27129091CF20096FC88 /* AccountInteractor.swift in Sources */,
58CCA010224249A1004F3011 /* ConnectViewController.swift in Sources */,
5893716A28817A45004EE76C /* DeviceManagementViewController.swift in Sources */,
58BFA5C622A7C97F00A6173D /* RelayCacheTracker.swift in Sources */,
@@ -2148,7 +2164,9 @@
587EB66A270EFACB00123C75 /* CharacterSet+IPAddress.swift in Sources */,
5888AD83227B11080051EB06 /* SelectLocationCell.swift in Sources */,
5891BF1C25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift in Sources */,
+ 5878A26F2907E7E00096FC88 /* ProblemReportInteractor.swift in Sources */,
5807E2C02432038B00F5FF30 /* String+Split.swift in Sources */,
+ 58677712290976FB006F721F /* SettingsInteractor.swift in Sources */,
58CE5E66224146200008646E /* LoginViewController.swift in Sources */,
58EF580B25D69D7A00AEBA94 /* ProblemReportSubmissionOverlayView.swift in Sources */,
58FD5BE724192A2C00112C88 /* StoreReceipt.swift in Sources */,
@@ -2168,6 +2186,7 @@
58EE2E3A272FF814003BFF93 /* SettingsDataSource.swift in Sources */,
58421032282E42B000F24E46 /* UpdateDeviceDataOperation.swift in Sources */,
5878A27D2909657C0096FC88 /* RevokedDeviceInteractor.swift in Sources */,
+ 58677710290975E9006F721F /* SettingsInteractorFactory.swift in Sources */,
5872631D283F755900E14ADF /* IntentHandlers.swift in Sources */,
58CCA01222424D11004F3011 /* SettingsViewController.swift in Sources */,
580F8B8628197958002E0998 /* DNSSettings.swift in Sources */,
@@ -2190,6 +2209,7 @@
58293FB725138B88005D0BB5 /* CustomNavigationController.swift in Sources */,
587425C12299833500CA2045 /* RootContainerViewController.swift in Sources */,
5896AE84246D5889005B36CB /* CustomDateComponentsFormatting.swift in Sources */,
+ 5871167F2910035700D41AAC /* PreferencesInteractor.swift in Sources */,
587AD7C623421D7000E93A53 /* TunnelSettingsV1.swift in Sources */,
58E20771274672CA00DE5D77 /* LaunchViewController.swift in Sources */,
584D26C4270C855B004EA533 /* PreferencesDataSource.swift in Sources */,
diff --git a/ios/MullvadVPN/AccountInteractor.swift b/ios/MullvadVPN/AccountInteractor.swift
new file mode 100644
index 0000000000..298f73f0b5
--- /dev/null
+++ b/ios/MullvadVPN/AccountInteractor.swift
@@ -0,0 +1,79 @@
+//
+// AccountInteractor.swift
+// MullvadVPN
+//
+// Created by pronebird on 26/10/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadREST
+import MullvadTypes
+import Operations
+import StoreKit
+
+final class AccountInteractor {
+ private let storePaymentManager: StorePaymentManager
+ private let tunnelManager: TunnelManager
+
+ var didReceivePaymentEvent: ((StorePaymentEvent) -> Void)?
+ var didReceiveDeviceState: ((DeviceState) -> Void)?
+
+ private var tunnelObserver: TunnelObserver?
+ private var paymentObserver: StorePaymentObserver?
+
+ init(storePaymentManager: StorePaymentManager, tunnelManager: TunnelManager) {
+ self.storePaymentManager = storePaymentManager
+ self.tunnelManager = tunnelManager
+
+ let tunnelObserver =
+ TunnelBlockObserver(didUpdateDeviceState: { [weak self] manager, deviceState in
+ self?.didReceiveDeviceState?(deviceState)
+ })
+
+ let paymentObserver = StorePaymentBlockObserver { [weak self] manager, event in
+ self?.didReceivePaymentEvent?(event)
+ }
+
+ tunnelManager.addObserver(tunnelObserver)
+ storePaymentManager.addPaymentObserver(paymentObserver)
+
+ self.tunnelObserver = tunnelObserver
+ self.paymentObserver = paymentObserver
+ }
+
+ var deviceState: DeviceState {
+ return tunnelManager.deviceState
+ }
+
+ func logout(_ completion: @escaping () -> Void) {
+ tunnelManager.unsetAccount(completionHandler: completion)
+ }
+
+ func addPayment(_ payment: SKPayment, for accountNumber: String) {
+ storePaymentManager.addPayment(payment, for: accountNumber)
+ }
+
+ func restorePurchases(
+ for accountNumber: String,
+ completionHandler: @escaping (OperationCompletion<
+ REST.CreateApplePaymentResponse,
+ StorePaymentManagerError
+ >) -> Void
+ ) -> Cancellable {
+ return storePaymentManager.restorePurchases(
+ for: accountNumber,
+ completionHandler: completionHandler
+ )
+ }
+
+ func requestProducts(
+ with productIdentifiers: Set<StoreSubscription>,
+ completionHandler: @escaping (OperationCompletion<SKProductsResponse, Swift.Error>) -> Void
+ ) -> Cancellable {
+ return storePaymentManager.requestProducts(
+ with: productIdentifiers,
+ completionHandler: completionHandler
+ )
+ }
+}
diff --git a/ios/MullvadVPN/AccountViewController.swift b/ios/MullvadVPN/AccountViewController.swift
index 83ae252191..9aa1abd460 100644
--- a/ios/MullvadVPN/AccountViewController.swift
+++ b/ios/MullvadVPN/AccountViewController.swift
@@ -8,6 +8,7 @@
import MullvadLogging
import MullvadREST
+import MullvadTypes
import Operations
import StoreKit
import UIKit
@@ -16,7 +17,8 @@ protocol AccountViewControllerDelegate: AnyObject {
func accountViewControllerDidLogout(_ controller: AccountViewController)
}
-class AccountViewController: UIViewController, StorePaymentObserver, TunnelObserver {
+class AccountViewController: UIViewController {
+ private let interactor: AccountInteractor
private let alertPresenter = AlertPresenter()
private let contentView: AccountContentView = {
@@ -30,6 +32,16 @@ class AccountViewController: UIViewController, StorePaymentObserver, TunnelObser
weak var delegate: AccountViewControllerDelegate?
+ init(interactor: AccountInteractor) {
+ self.interactor = interactor
+
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
// MARK: - View lifecycle
override var preferredStatusBarStyle: UIStatusBarStyle {
@@ -83,10 +95,15 @@ class AccountViewController: UIViewController, StorePaymentObserver, TunnelObser
)
contentView.logoutButton.addTarget(self, action: #selector(doLogout), for: .touchUpInside)
- StorePaymentManager.shared.addPaymentObserver(self)
- TunnelManager.shared.addObserver(self)
+ interactor.didReceiveDeviceState = { [weak self] newDeviceState in
+ self?.updateView(from: newDeviceState)
+ }
+
+ interactor.didReceivePaymentEvent = { [weak self] event in
+ self?.didReceivePaymentEvent(event)
+ }
- updateView(from: TunnelManager.shared.deviceState)
+ updateView(from: interactor.deviceState)
applyViewState(animated: false)
if StorePaymentManager.canMakePayments {
@@ -96,20 +113,19 @@ class AccountViewController: UIViewController, StorePaymentObserver, TunnelObser
}
}
- // MARK: - Private methods
+ // MARK: - Private
private func requestStoreProducts() {
let productKind = StoreSubscription.thirtyDays
setProductState(.fetching(productKind), animated: true)
- _ = StorePaymentManager.shared
- .requestProducts(with: [productKind]) { [weak self] completion in
- let productState: ProductState = completion.value?.products.first
- .map { .received($0) } ?? .failed
+ _ = interactor.requestProducts(with: [productKind]) { [weak self] completion in
+ let productState: ProductState = completion.value?.products.first
+ .map { .received($0) } ?? .failed
- self?.setProductState(productState, animated: true)
- }
+ self?.setProductState(productState, animated: true)
+ }
}
private func setPaymentState(_ newState: PaymentState, animated: Bool) {
@@ -158,6 +174,27 @@ class AccountViewController: UIViewController, StorePaymentObserver, TunnelObser
navigationItem.setHidesBackButton(!isInteractionEnabled, animated: animated)
}
+ private func didReceivePaymentEvent(_ event: StorePaymentEvent) {
+ guard case let .makingPayment(payment) = paymentState,
+ payment == event.payment else { return }
+
+ switch event {
+ case let .finished(completion):
+ showTimeAddedConfirmationAlert(with: completion.serverResponse, context: .purchase)
+
+ case let .failure(paymentFailure):
+ switch paymentFailure.error {
+ case .storePayment(SKError.paymentCancelled):
+ break
+
+ default:
+ showPaymentErrorAlert(error: paymentFailure.error)
+ }
+ }
+
+ setPaymentState(.none, animated: true)
+ }
+
private func showPaymentErrorAlert(error: StorePaymentManagerError) {
let alertController = UIAlertController(
title: NSLocalizedString(
@@ -295,7 +332,7 @@ class AccountViewController: UIViewController, StorePaymentObserver, TunnelObser
)
alertPresenter.enqueue(alertController, presentingController: self) {
- TunnelManager.shared.unsetAccount {
+ self.interactor.logout {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
alertController.dismiss(animated: true) {
self.delegate?.accountViewControllerDidLogout(self)
@@ -305,59 +342,6 @@ class AccountViewController: UIViewController, StorePaymentObserver, TunnelObser
}
}
- // MARK: - TunnelObserver
-
- func tunnelManagerDidLoadConfiguration(_ manager: TunnelManager) {
- // no-op
- }
-
- func tunnelManager(_ manager: TunnelManager, didUpdateTunnelStatus tunnelStatus: TunnelStatus) {
- // no-op
- }
-
- func tunnelManager(_ manager: TunnelManager, didFailWithError error: Error) {
- // no-op
- }
-
- func tunnelManager(
- _ manager: TunnelManager,
- didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2
- ) {
- // no-op
- }
-
- func tunnelManager(_ manager: TunnelManager, didUpdateDeviceState deviceState: DeviceState) {
- updateView(from: deviceState)
- }
-
- // MARK: - StorePaymentObserver
-
- func storePaymentManager(
- _ manager: StorePaymentManager,
- didReceiveEvent event: StorePaymentEvent
- ) {
- guard case let .makingPayment(payment) = paymentState,
- payment == event.payment else { return }
-
- switch event {
- case let .finished(paymentCompletion):
- showTimeAddedConfirmationAlert(
- with: paymentCompletion.serverResponse,
- context: .purchase
- )
-
- case let .failure(paymentFailure):
- switch paymentFailure.error {
- case .storePayment(SKError.paymentCancelled):
- break
- default:
- showPaymentErrorAlert(error: paymentFailure.error)
- }
- }
-
- setPaymentState(.none, animated: true)
- }
-
// MARK: - Actions
@objc private func doLogout() {
@@ -369,7 +353,7 @@ class AccountViewController: UIViewController, StorePaymentObserver, TunnelObser
}
private func copyAccountToken() {
- guard let accountData = TunnelManager.shared.deviceState.accountData else {
+ guard let accountData = interactor.deviceState.accountData else {
return
}
@@ -378,25 +362,27 @@ class AccountViewController: UIViewController, StorePaymentObserver, TunnelObser
@objc private func doPurchase() {
guard case let .received(product) = productState,
- let accountData = TunnelManager.shared.deviceState.accountData
+ let accountData = interactor.deviceState.accountData
else {
return
}
let payment = SKPayment(product: product)
- StorePaymentManager.shared.addPayment(payment, for: accountData.number)
+ interactor.addPayment(payment, for: accountData.number)
setPaymentState(.makingPayment(payment), animated: true)
}
@objc private func restorePurchases() {
- guard let accountData = TunnelManager.shared.deviceState.accountData else {
+ guard let accountData = interactor.deviceState.accountData else {
return
}
setPaymentState(.restoringPurchases, animated: true)
- _ = StorePaymentManager.shared.restorePurchases(for: accountData.number) { completion in
+ _ = interactor.restorePurchases(for: accountData.number) { [weak self] completion in
+ guard let self = self else { return }
+
switch completion {
case let .success(response):
self.showTimeAddedConfirmationAlert(with: response, context: .restoration)
diff --git a/ios/MullvadVPN/PreferencesInteractor.swift b/ios/MullvadVPN/PreferencesInteractor.swift
new file mode 100644
index 0000000000..8029669841
--- /dev/null
+++ b/ios/MullvadVPN/PreferencesInteractor.swift
@@ -0,0 +1,32 @@
+//
+// PreferencesInteractor.swift
+// MullvadVPN
+//
+// Created by pronebird on 31/10/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+final class PreferencesInteractor {
+ private let tunnelManager: TunnelManager
+ private var tunnelObserver: TunnelObserver?
+
+ var dnsSettingsDidChange: ((DNSSettings) -> Void)?
+
+ init(tunnelManager: TunnelManager) {
+ self.tunnelManager = tunnelManager
+ tunnelObserver =
+ TunnelBlockObserver(didUpdateTunnelSettings: { [weak self] manager, newSettings in
+ self?.dnsSettingsDidChange?(newSettings.dnsSettings)
+ })
+ }
+
+ var dnsSettings: DNSSettings {
+ return tunnelManager.settings.dnsSettings
+ }
+
+ func setDNSSettings(_ newDNSSettings: DNSSettings, completion: (() -> Void)? = nil) {
+ tunnelManager.setDNSSettings(newDNSSettings, completionHandler: completion)
+ }
+}
diff --git a/ios/MullvadVPN/PreferencesViewController.swift b/ios/MullvadVPN/PreferencesViewController.swift
index 5d70790569..ea85b62b0f 100644
--- a/ios/MullvadVPN/PreferencesViewController.swift
+++ b/ios/MullvadVPN/PreferencesViewController.swift
@@ -6,19 +6,18 @@
// Copyright © 2021 Mullvad VPN AB. All rights reserved.
//
-import MullvadLogging
import UIKit
-class PreferencesViewController: UITableViewController, PreferencesDataSourceDelegate,
- TunnelObserver
-{
+class PreferencesViewController: UITableViewController, PreferencesDataSourceDelegate {
+ private let interactor: PreferencesInteractor
private let dataSource = PreferencesDataSource()
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
- init() {
+ init(interactor: PreferencesInteractor) {
+ self.interactor = interactor
super.init(style: .grouped)
}
@@ -45,8 +44,11 @@ class PreferencesViewController: UITableViewController, PreferencesDataSourceDel
)
navigationItem.rightBarButtonItem = editButtonItem
- TunnelManager.shared.addObserver(self)
- dataSource.update(from: TunnelManager.shared.settings.dnsSettings)
+ interactor.dnsSettingsDidChange = { [weak self] newDNSSettings in
+ self?.dataSource.update(from: newDNSSettings)
+ }
+
+ dataSource.update(from: interactor.dnsSettings)
}
override func setEditing(_ editing: Bool, animated: Bool) {
@@ -68,31 +70,6 @@ class PreferencesViewController: UITableViewController, PreferencesDataSourceDel
) {
let dnsSettings = dataModel.asDNSSettings()
- TunnelManager.shared.setDNSSettings(dnsSettings)
- }
-
- // MARK: - TunnelObserver
-
- func tunnelManagerDidLoadConfiguration(_ manager: TunnelManager) {
- // no-op
- }
-
- func tunnelManager(_ manager: TunnelManager, didUpdateTunnelStatus tunnelStatus: TunnelStatus) {
- // no-op
- }
-
- func tunnelManager(_ manager: TunnelManager, didFailWithError error: Error) {
- // no-op
- }
-
- func tunnelManager(
- _ manager: TunnelManager,
- didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2
- ) {
- dataSource.update(from: tunnelSettings.dnsSettings)
- }
-
- func tunnelManager(_ manager: TunnelManager, didUpdateDeviceState deviceState: DeviceState) {
- // no-op
+ interactor.setDNSSettings(dnsSettings)
}
}
diff --git a/ios/MullvadVPN/ProblemReportInteractor.swift b/ios/MullvadVPN/ProblemReportInteractor.swift
new file mode 100644
index 0000000000..744eedcbac
--- /dev/null
+++ b/ios/MullvadVPN/ProblemReportInteractor.swift
@@ -0,0 +1,64 @@
+//
+// ProblemReportInteractor.swift
+// MullvadVPN
+//
+// Created by pronebird on 25/10/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadREST
+import MullvadTypes
+import Operations
+
+final class ProblemReportInteractor {
+ private let apiProxy: REST.APIProxy
+ private let tunnelManager: TunnelManager
+
+ private lazy var consolidatedLog: ConsolidatedApplicationLog = {
+ let securityGroupIdentifier = ApplicationConfiguration.securityGroupIdentifier
+
+ // TODO: make sure we redact old tokens
+
+ let redactStrings = [tunnelManager.deviceState.accountData?.number].compactMap { $0 }
+
+ let report = ConsolidatedApplicationLog(
+ redactCustomStrings: redactStrings,
+ redactContainerPathsForSecurityGroupIdentifiers: [securityGroupIdentifier]
+ )
+
+ report.addLogFiles(fileURLs: ApplicationConfiguration.logFileURLs, includeLogBackups: true)
+
+ return report
+ }()
+
+ init(apiProxy: REST.APIProxy, tunnelManager: TunnelManager) {
+ self.apiProxy = apiProxy
+ self.tunnelManager = tunnelManager
+ }
+
+ func sendReport(
+ email: String,
+ message: String,
+ completion: @escaping (OperationCompletion<Void, REST.Error>) -> Void
+ ) -> Cancellable {
+ let request = REST.ProblemReportRequest(
+ address: email,
+ message: message,
+ log: consolidatedLog.string,
+ metadata: consolidatedLog.metadata.reduce(into: [:]) { output, entry in
+ output[entry.key.rawValue] = entry.value
+ }
+ )
+
+ return apiProxy.sendProblemReport(
+ request,
+ retryStrategy: .default,
+ completionHandler: completion
+ )
+ }
+
+ var reportString: String {
+ return consolidatedLog.string
+ }
+}
diff --git a/ios/MullvadVPN/ProblemReportViewController.swift b/ios/MullvadVPN/ProblemReportViewController.swift
index 5a543b5746..622cc1600c 100644
--- a/ios/MullvadVPN/ProblemReportViewController.swift
+++ b/ios/MullvadVPN/ProblemReportViewController.swift
@@ -7,33 +7,16 @@
//
import MullvadREST
+import MullvadTypes
import Operations
import UIKit
class ProblemReportViewController: UIViewController, UITextFieldDelegate, ConditionalNavigation {
- private let apiProxy = REST.ProxyFactory.shared.createAPIProxy()
+ private let interactor: ProblemReportInteractor
private var textViewKeyboardResponder: AutomaticKeyboardResponder?
private var scrollViewKeyboardResponder: AutomaticKeyboardResponder?
- private lazy var consolidatedLog: ConsolidatedApplicationLog = {
- let securityGroupIdentifier = ApplicationConfiguration.securityGroupIdentifier
-
- // TODO: make sure we redact old tokens
-
- let redactStrings = [TunnelManager.shared.deviceState.accountData?.number]
- .compactMap { $0 }
-
- let report = ConsolidatedApplicationLog(
- redactCustomStrings: redactStrings,
- redactContainerPathsForSecurityGroupIdentifiers: [securityGroupIdentifier]
- )
-
- report.addLogFiles(fileURLs: ApplicationConfiguration.logFileURLs, includeLogBackups: true)
-
- return report
- }()
-
/// Scroll view
private lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
@@ -204,6 +187,16 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit
return false
}
+ init(interactor: ProblemReportInteractor) {
+ self.interactor = interactor
+
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
override func viewDidLoad() {
super.viewDidLoad()
@@ -283,7 +276,7 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit
@objc func handleViewLogsButtonTap() {
let reviewController = ProblemReportReviewViewController(
- reportString: consolidatedLog.string
+ reportString: interactor.reportString
)
let navigationController = UINavigationController(rootViewController: reviewController)
@@ -681,21 +674,12 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit
private func sendProblemReport() {
let viewModel = Self.persistentViewModel
- let log = consolidatedLog.string
- let metadata = consolidatedLog.metadata.reduce(into: [:]) { output, entry in
- output[entry.key.rawValue] = entry.value
- }
-
- let request = REST.ProblemReportRequest(
- address: viewModel.email,
- message: viewModel.message,
- log: log,
- metadata: metadata
- )
-
willSendProblemReport()
- _ = apiProxy.sendProblemReport(request, retryStrategy: .default) { completion in
+ _ = interactor.sendReport(
+ email: viewModel.email,
+ message: viewModel.message
+ ) { completion in
self.didSendProblemReport(viewModel: viewModel, completion: completion)
}
}
diff --git a/ios/MullvadVPN/SceneDelegate.swift b/ios/MullvadVPN/SceneDelegate.swift
index 00ac40f33f..6016af5792 100644
--- a/ios/MullvadVPN/SceneDelegate.swift
+++ b/ios/MullvadVPN/SceneDelegate.swift
@@ -345,7 +345,13 @@ extension SceneDelegate {
private func makeSettingsNavigationController(route: SettingsNavigationRoute?)
-> SettingsNavigationController
{
- let navController = SettingsNavigationController()
+ let navController = SettingsNavigationController(
+ interactorFactory: SettingsInteractorFactory(
+ storePaymentManager: .shared,
+ tunnelManager: .shared,
+ apiProxy: REST.ProxyFactory.shared.createAPIProxy()
+ )
+ )
navController.settingsDelegate = self
if UIDevice.current.userInterfaceIdiom == .pad {
diff --git a/ios/MullvadVPN/SettingsDataSource.swift b/ios/MullvadVPN/SettingsDataSource.swift
index 0f0f9b6cd2..ed4bf23c3d 100644
--- a/ios/MullvadVPN/SettingsDataSource.swift
+++ b/ios/MullvadVPN/SettingsDataSource.swift
@@ -8,7 +8,7 @@
import UIKit
-class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITableViewDelegate {
+final class SettingsDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
private enum CellReuseIdentifiers: String, CaseIterable {
case accountCell
case basicCell
@@ -50,6 +50,7 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab
}
private var snapshot = DataSourceSnapshot<Section, Item>()
+ private let interactor: SettingsInteractor
private var storedAccountData: StoredAccountData?
weak var delegate: SettingsDataSourceDelegate?
@@ -63,11 +64,15 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab
}
}
- override init() {
+ init(interactor: SettingsInteractor) {
+ self.interactor = interactor
+
super.init()
- TunnelManager.shared.addObserver(self)
- storedAccountData = TunnelManager.shared.deviceState.accountData
+ interactor.didUpdateDeviceState = { [weak self] deviceState in
+ self?.didUpdateDeviceState(deviceState)
+ }
+ storedAccountData = interactor.deviceState.accountData
updateDataSnapshot()
}
@@ -91,7 +96,7 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab
private func updateDataSnapshot() {
var newSnapshot = DataSourceSnapshot<Section, Item>()
- if TunnelManager.shared.deviceState.isLoggedIn {
+ if interactor.deviceState.isLoggedIn {
newSnapshot.appendSections([.main])
newSnapshot.appendItems([.account, .preferences, .shortcuts], in: .main)
}
@@ -130,7 +135,7 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab
value: "Account",
comment: ""
)
- cell.accountExpiryDate = TunnelManager.shared.deviceState.accountData?.expiry
+ cell.accountExpiryDate = interactor.deviceState.accountData?.expiry
cell.accessibilityIdentifier = "AccountCell"
cell.disclosureType = .chevron
@@ -259,21 +264,9 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab
return 0
}
- // MARK: - TunnelObserver
-
- func tunnelManagerDidLoadConfiguration(_ manager: TunnelManager) {
- // no-op
- }
-
- func tunnelManager(_ manager: TunnelManager, didFailWithError error: Error) {
- // no-op
- }
-
- func tunnelManager(_ manager: TunnelManager, didUpdateTunnelStatus tunnelStatus: TunnelStatus) {
- // no-op
- }
+ // MARK: - Private
- func tunnelManager(_ manager: TunnelManager, didUpdateDeviceState deviceState: DeviceState) {
+ private func didUpdateDeviceState(_ deviceState: DeviceState) {
let newAccountData = deviceState.accountData
let oldAccountData = storedAccountData
@@ -295,11 +288,4 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab
updateDataSnapshot()
tableView?.reloadData()
}
-
- func tunnelManager(
- _ manager: TunnelManager,
- didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2
- ) {
- // no-op
- }
}
diff --git a/ios/MullvadVPN/SettingsInteractor.swift b/ios/MullvadVPN/SettingsInteractor.swift
new file mode 100644
index 0000000000..cd5c6e6e68
--- /dev/null
+++ b/ios/MullvadVPN/SettingsInteractor.swift
@@ -0,0 +1,33 @@
+//
+// SettingsInteractor.swift
+// MullvadVPN
+//
+// Created by pronebird on 26/10/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+final class SettingsInteractor {
+ private let tunnelManager: TunnelManager
+ private var tunnelObserver: TunnelObserver?
+
+ var didUpdateDeviceState: ((DeviceState) -> Void)?
+
+ var deviceState: DeviceState {
+ return tunnelManager.deviceState
+ }
+
+ init(tunnelManager: TunnelManager) {
+ self.tunnelManager = tunnelManager
+
+ let tunnelObserver =
+ TunnelBlockObserver(didUpdateDeviceState: { [weak self] manager, deviceState in
+ self?.didUpdateDeviceState?(deviceState)
+ })
+
+ tunnelManager.addObserver(tunnelObserver)
+
+ self.tunnelObserver = tunnelObserver
+ }
+}
diff --git a/ios/MullvadVPN/SettingsInteractorFactory.swift b/ios/MullvadVPN/SettingsInteractorFactory.swift
new file mode 100644
index 0000000000..521e587570
--- /dev/null
+++ b/ios/MullvadVPN/SettingsInteractorFactory.swift
@@ -0,0 +1,45 @@
+//
+// SettingsInteractorFactory.swift
+// MullvadVPN
+//
+// Created by pronebird on 26/10/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadREST
+
+final class SettingsInteractorFactory {
+ private let storePaymentManager: StorePaymentManager
+ private let tunnelManager: TunnelManager
+ private let apiProxy: REST.APIProxy
+
+ init(
+ storePaymentManager: StorePaymentManager,
+ tunnelManager: TunnelManager,
+ apiProxy: REST.APIProxy
+ ) {
+ self.storePaymentManager = storePaymentManager
+ self.tunnelManager = tunnelManager
+ self.apiProxy = apiProxy
+ }
+
+ func makeAccountInteractor() -> AccountInteractor {
+ return AccountInteractor(
+ storePaymentManager: storePaymentManager,
+ tunnelManager: tunnelManager
+ )
+ }
+
+ func makePreferencesInteractor() -> PreferencesInteractor {
+ return PreferencesInteractor(tunnelManager: tunnelManager)
+ }
+
+ func makeProblemReportInteractor() -> ProblemReportInteractor {
+ return ProblemReportInteractor(apiProxy: apiProxy, tunnelManager: tunnelManager)
+ }
+
+ func makeSettingsInteractor() -> SettingsInteractor {
+ return SettingsInteractor(tunnelManager: tunnelManager)
+ }
+}
diff --git a/ios/MullvadVPN/SettingsNavigationController.swift b/ios/MullvadVPN/SettingsNavigationController.swift
index 58993fc6c4..6656eb2acb 100644
--- a/ios/MullvadVPN/SettingsNavigationController.swift
+++ b/ios/MullvadVPN/SettingsNavigationController.swift
@@ -6,7 +6,6 @@
// Copyright © 2020 Mullvad VPN AB. All rights reserved.
//
-import Foundation
import UIKit
enum SettingsNavigationRoute {
@@ -37,6 +36,8 @@ protocol SettingsNavigationControllerDelegate: AnyObject {
class SettingsNavigationController: CustomNavigationController, SettingsViewControllerDelegate,
AccountViewControllerDelegate, UIAdaptivePresentationControllerDelegate
{
+ private let interactorFactory: SettingsInteractorFactory
+
weak var settingsDelegate: SettingsNavigationControllerDelegate?
override var childForStatusBarStyle: UIViewController? {
@@ -47,7 +48,9 @@ class SettingsNavigationController: CustomNavigationController, SettingsViewCont
return topViewController
}
- init() {
+ init(interactorFactory: SettingsInteractorFactory) {
+ self.interactorFactory = interactorFactory
+
super.init(navigationBarClass: CustomNavigationBar.self, toolbarClass: nil)
navigationBar.prefersLargeTitles = true
@@ -57,7 +60,7 @@ class SettingsNavigationController: CustomNavigationController, SettingsViewCont
}
required init?(coder aDecoder: NSCoder) {
- super.init(coder: aDecoder)
+ fatalError("init(coder:) has not been implemented")
}
override func willPop(navigationItem: UINavigationItem) {
@@ -102,23 +105,31 @@ class SettingsNavigationController: CustomNavigationController, SettingsViewCont
private func makeViewController(for route: SettingsNavigationRoute) -> UIViewController {
switch route {
case .root:
- let settingsController = SettingsViewController()
- settingsController.delegate = self
- return settingsController
+ let controller = SettingsViewController(
+ interactor: interactorFactory.makeSettingsInteractor()
+ )
+ controller.delegate = self
+ return controller
case .account:
- let controller = AccountViewController()
+ let controller = AccountViewController(
+ interactor: interactorFactory.makeAccountInteractor()
+ )
controller.delegate = self
return controller
case .preferences:
- return PreferencesViewController()
+ return PreferencesViewController(
+ interactor: interactorFactory.makePreferencesInteractor()
+ )
case .shortcuts:
return ShortcutsViewController()
case .problemReport:
- return ProblemReportViewController()
+ return ProblemReportViewController(
+ interactor: interactorFactory.makeProblemReportInteractor()
+ )
}
}
diff --git a/ios/MullvadVPN/SettingsViewController.swift b/ios/MullvadVPN/SettingsViewController.swift
index 0048c2eed3..3f5ce245df 100644
--- a/ios/MullvadVPN/SettingsViewController.swift
+++ b/ios/MullvadVPN/SettingsViewController.swift
@@ -23,9 +23,11 @@ class SettingsViewController: UITableViewController, SettingsDataSourceDelegate,
return .lightContent
}
- private let dataSource = SettingsDataSource()
+ private let dataSource: SettingsDataSource
+
+ init(interactor: SettingsInteractor) {
+ dataSource = SettingsDataSource(interactor: interactor)
- init() {
super.init(style: .grouped)
}