diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2022-11-01 06:35:00 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2022-11-03 10:58:53 +0100 |
| commit | d63505854f5fb5c55ecf04811a845b77464b6027 (patch) | |
| tree | 4ce5962b3e666bc4790618301b6f45ae9611fdff | |
| parent | d44ffae4df69e8203751e676e5e9ea29d223675c (diff) | |
| download | mullvadvpn-d63505854f5fb5c55ecf04811a845b77464b6027.tar.xz mullvadvpn-d63505854f5fb5c55ecf04811a845b77464b6027.zip | |
Add account interactor
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/AccountInteractor.swift | 79 | ||||
| -rw-r--r-- | ios/MullvadVPN/AccountViewController.swift | 126 |
3 files changed, 139 insertions, 70 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 95624211f7..a290b56022 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -161,6 +161,7 @@ 5877D70F282137E8002FCFC7 /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF2C02281BDE02009EF542 /* SettingsManager.swift */; }; 5878A27329091D6D0096FC88 /* TunnelBlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A27229091D6D0096FC88 /* TunnelBlockObserver.swift */; }; 5878A26F2907E7E00096FC88 /* ProblemReportInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A26E2907E7E00096FC88 /* ProblemReportInteractor.swift */; }; + 5878A27129091CF20096FC88 /* AccountInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A27029091CF20096FC88 /* AccountInteractor.swift */; }; 5878A27529093A310096FC88 /* StorePaymentEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A27429093A310096FC88 /* StorePaymentEvent.swift */; }; 5878A27729093A4F0096FC88 /* StorePaymentBlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A27629093A4F0096FC88 /* StorePaymentBlockObserver.swift */; }; 5878A279290954790096FC88 /* ConnectInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A278290954790096FC88 /* ConnectInteractor.swift */; }; @@ -645,6 +646,7 @@ 58781CD422AFBA39009B9D8E /* RelaySelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelector.swift; sourceTree = "<group>"; }; 5878A27229091D6D0096FC88 /* TunnelBlockObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelBlockObserver.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>"; }; 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>"; }; 5878A278290954790096FC88 /* ConnectInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectInteractor.swift; sourceTree = "<group>"; }; @@ -1225,6 +1227,7 @@ 58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */, 58CCA01D2242787B004F3011 /* AccountTextField.swift */, 582AE30F2440A6CA00E6733A /* AccountTokenInput.swift */, + 5878A27029091CF20096FC88 /* AccountInteractor.swift */, 58CCA01722426713004F3011 /* AccountViewController.swift */, 5867771329097BCD006F721F /* PaymentState.swift */, 5867771529097C5B006F721F /* ProductState.swift */, @@ -2084,6 +2087,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 */, 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) |
