summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2022-11-01 06:35:00 +0100
committerAndrej Mihajlov <and@mullvad.net>2022-11-03 10:58:53 +0100
commitd63505854f5fb5c55ecf04811a845b77464b6027 (patch)
tree4ce5962b3e666bc4790618301b6f45ae9611fdff
parentd44ffae4df69e8203751e676e5e9ea29d223675c (diff)
downloadmullvadvpn-d63505854f5fb5c55ecf04811a845b77464b6027.tar.xz
mullvadvpn-d63505854f5fb5c55ecf04811a845b77464b6027.zip
Add account interactor
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/AccountInteractor.swift79
-rw-r--r--ios/MullvadVPN/AccountViewController.swift126
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)