summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@kvadrat.se>2023-05-22 13:25:38 +0200
committerAndrej Mihajlov <and@mullvad.net>2023-06-22 12:43:58 +0200
commit06fde66467f1ae9e0fc5805358c49d689d92d704 (patch)
tree66bc18ba52d2e1c98c0ebcaa85d1c192f243a976
parent4e85e925966a1d1496eb1acf25207ecd0c80db77 (diff)
downloadmullvadvpn-06fde66467f1ae9e0fc5805358c49d689d92d704.tar.xz
mullvadvpn-06fde66467f1ae9e0fc5805358c49d689d92d704.zip
Add info button next to device name in account view
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj6
-rw-r--r--ios/MullvadVPN/Classes/CustomAlertViewController.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/App/AccountCoordinator.swift123
-rw-r--r--ios/MullvadVPN/Coordinators/App/OutOfTimeCoordinator.swift9
-rw-r--r--ios/MullvadVPN/Extensions/RESTCreateApplePaymentResponse+Localization.swift21
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotificationProvider.swift2
-rw-r--r--ios/MullvadVPN/View controllers/Account/AccountContentView.swift43
-rw-r--r--ios/MullvadVPN/View controllers/Account/AccountViewController.swift143
-rw-r--r--ios/MullvadVPN/View controllers/Account/PaymentAlertPresenter.swift76
-rw-r--r--ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeViewController.swift102
10 files changed, 267 insertions, 260 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 4a248cc5d1..991c07062b 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -382,6 +382,7 @@
7A21DACF2A30AA3700A787A9 /* UITextField+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A21DACE2A30AA3700A787A9 /* UITextField+Appearance.swift */; };
7A42DEC92A05164100B209BE /* SettingsInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A42DEC82A05164100B209BE /* SettingsInputCell.swift */; };
7A42DECD2A09064C00B209BE /* SelectableSettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A42DECC2A09064C00B209BE /* SelectableSettingsCell.swift */; };
+ 7A1A26432A2612AE00B978AA /* PaymentAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1A26422A2612AE00B978AA /* PaymentAlertPresenter.swift */; };
7A7AD28D29DC677800480EF1 /* FirstTimeLaunch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */; };
7A7AD28F29DEDB1C00480EF1 /* SettingsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7AD28E29DEDB1C00480EF1 /* SettingsHeaderView.swift */; };
7A818F1F29F0305800C7F0F4 /* RootConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A818F1E29F0305800C7F0F4 /* RootConfiguration.swift */; };
@@ -1116,6 +1117,7 @@
7A21DACE2A30AA3700A787A9 /* UITextField+Appearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextField+Appearance.swift"; sourceTree = "<group>"; };
7A42DEC82A05164100B209BE /* SettingsInputCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInputCell.swift; sourceTree = "<group>"; };
7A42DECC2A09064C00B209BE /* SelectableSettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableSettingsCell.swift; sourceTree = "<group>"; };
+ 7A1A26422A2612AE00B978AA /* PaymentAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentAlertPresenter.swift; sourceTree = "<group>"; };
7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstTimeLaunch.swift; sourceTree = "<group>"; };
7A7AD28E29DEDB1C00480EF1 /* SettingsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHeaderView.swift; sourceTree = "<group>"; };
7A818F1E29F0305800C7F0F4 /* RootConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootConfiguration.swift; sourceTree = "<group>"; };
@@ -1599,9 +1601,10 @@
583FE02029C1A0B1006E85F9 /* Account */ = {
isa = PBXGroup;
children = (
- 58CCA01722426713004F3011 /* AccountViewController.swift */,
5896CEF126972DEB00B0FAE8 /* AccountContentView.swift */,
5878A27029091CF20096FC88 /* AccountInteractor.swift */,
+ 58CCA01722426713004F3011 /* AccountViewController.swift */,
+ 7A1A26422A2612AE00B978AA /* PaymentAlertPresenter.swift */,
5867771329097BCD006F721F /* PaymentState.swift */,
5867771529097C5B006F721F /* ProductState.swift */,
);
@@ -3016,6 +3019,7 @@
585CA70F25F8C44600B47C62 /* UIMetrics.swift in Sources */,
E1187ABD289BBB850024E748 /* OutOfTimeContentView.swift in Sources */,
58CC40EF24A601900019D96E /* ObserverList.swift in Sources */,
+ 7A1A26432A2612AE00B978AA /* PaymentAlertPresenter.swift in Sources */,
58CCA01822426713004F3011 /* AccountViewController.swift in Sources */,
5871FBA0254C26C00051A0A4 /* NSRegularExpression+IPAddress.swift in Sources */,
5878A27729093A4F0096FC88 /* StorePaymentBlockObserver.swift in Sources */,
diff --git a/ios/MullvadVPN/Classes/CustomAlertViewController.swift b/ios/MullvadVPN/Classes/CustomAlertViewController.swift
index 5a99673a0d..0720785419 100644
--- a/ios/MullvadVPN/Classes/CustomAlertViewController.swift
+++ b/ios/MullvadVPN/Classes/CustomAlertViewController.swift
@@ -175,7 +175,7 @@ class CustomAlertViewController: UIViewController {
@objc private func didTapButton(_ button: AppButton) {
dismiss(animated: true) { [self] in
- if let handler = handlers[button] {
+ if let handler = handlers.removeValue(forKey: button) {
handler()
}
didDismiss?()
diff --git a/ios/MullvadVPN/Coordinators/App/AccountCoordinator.swift b/ios/MullvadVPN/Coordinators/App/AccountCoordinator.swift
index cf2d0f4647..4425b624e7 100644
--- a/ios/MullvadVPN/Coordinators/App/AccountCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/App/AccountCoordinator.swift
@@ -16,6 +16,7 @@ enum AccountDismissReason: Equatable {
final class AccountCoordinator: Coordinator, Presentable, Presenting {
private let interactor: AccountInteractor
private var accountController: AccountViewController?
+ private let alertPresenter = AlertPresenter()
let navigationController: UINavigationController
var presentedViewController: UIViewController {
@@ -39,50 +40,104 @@ final class AccountCoordinator: Coordinator, Presentable, Presenting {
func start(animated: Bool) {
navigationController.navigationBar.prefersLargeTitles = true
- let accountController = AccountViewController(interactor: interactor)
- accountController.delegate = self
+ let accountController = AccountViewController(
+ interactor: interactor,
+ errorPresenter: PaymentAlertPresenter(
+ presentationController: presentationContext,
+ alertPresenter: alertPresenter
+ )
+ )
+
+ accountController.actionHandler = handleViewControllerAction
navigationController.pushViewController(accountController, animated: animated)
self.accountController = accountController
}
-}
-extension AccountCoordinator: AccountViewControllerDelegate {
- func accountViewController(
- _ controller: AccountViewController,
- didRequestRoutePresentation route: AccountsNavigationRoute
- ) {
- switch route {
- case .redeemVoucher:
- let coordinator = RedeemVoucherCoordinator(
- navigationController: CustomNavigationController(),
- interactor: RedeemVoucherInteractor(tunnelManager: interactor.tunnelManager)
- )
- coordinator.didFinish = { redeemVoucherCoordinator in
- redeemVoucherCoordinator.dismiss(animated: true)
- }
- coordinator.didCancel = { redeemVoucherCoordinator in
- redeemVoucherCoordinator.dismiss(animated: true)
- }
+ private func handleViewControllerAction(_ action: AccountViewControllerAction) {
+ switch action {
+ case .deviceInfo:
+ showAccountDeviceInfo()
+ case .finish:
+ didFinish?(self, .none)
+ case .logOut:
+ logOut()
+ case .navigateToVoucher:
+ navigateToRedeemVoucher()
+ }
+ }
- coordinator.start()
- presentChild(
- coordinator,
- animated: true,
- configuration: ModalPresentationConfiguration(
- preferredContentSize: UIMetrics.RedeemVoucher.preferredContentSize,
- modalPresentationStyle: .custom,
- transitioningDelegate: FormSheetTransitioningDelegate()
- )
- )
+ private func navigateToRedeemVoucher() {
+ let coordinator = RedeemVoucherCoordinator(
+ navigationController: CustomNavigationController(),
+ interactor: RedeemVoucherInteractor(tunnelManager: interactor.tunnelManager)
+ )
+ coordinator.didFinish = { redeemVoucherCoordinator in
+ redeemVoucherCoordinator.dismiss(animated: true)
+ }
+ coordinator.didCancel = { redeemVoucherCoordinator in
+ redeemVoucherCoordinator.dismiss(animated: true)
}
+
+ coordinator.start()
+ presentChild(
+ coordinator,
+ animated: true,
+ configuration: ModalPresentationConfiguration(
+ preferredContentSize: UIMetrics.RedeemVoucher.preferredContentSize,
+ modalPresentationStyle: .custom,
+ transitioningDelegate: FormSheetTransitioningDelegate()
+ )
+ )
}
- func accountViewControllerDidFinish(_ controller: AccountViewController) {
- didFinish?(self, .none)
+ // MARK: - Alerts
+
+ private func logOut() {
+ let alertController = CustomAlertViewController(icon: .spinner)
+
+ alertPresenter.enqueue(alertController, presentingController: presentationContext) {
+ self.interactor.logout {
+ DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) { [weak self] in
+ guard let self else { return }
+
+ alertController.dismiss(animated: true) {
+ self.didFinish?(self, .userLoggedOut)
+ }
+ }
+ }
+ }
}
- func accountViewControllerDidLogout(_ controller: AccountViewController) {
- didFinish?(self, .userLoggedOut)
+ private func showAccountDeviceInfo() {
+ let message = NSLocalizedString(
+ "DEVICE_INFO_DIALOG_MESSAGE_PART_1",
+ tableName: "Account",
+ value: """
+ This is the name assigned to the device. Each device logged in on a Mullvad account gets a unique name that helps you identify it when you manage your devices in the app or on the website.
+
+ You can have up to 5 devices logged in on one Mullvad account.
+
+ If you log out, the device and the device name is removed. When you log back in again, the device will get a new name.
+ """,
+ comment: ""
+ )
+
+ let alertController = CustomAlertViewController(
+ message: message,
+ icon: .info
+ )
+
+ alertController.addAction(
+ title: NSLocalizedString(
+ "DEVICE_INFO_DIALOG_OK_ACTION",
+ tableName: "Account",
+ value: "Got it!",
+ comment: ""
+ ),
+ style: .default
+ )
+
+ alertPresenter.enqueue(alertController, presentingController: presentationContext)
}
}
diff --git a/ios/MullvadVPN/Coordinators/App/OutOfTimeCoordinator.swift b/ios/MullvadVPN/Coordinators/App/OutOfTimeCoordinator.swift
index c9288a40cb..fa7ccd5ff2 100644
--- a/ios/MullvadVPN/Coordinators/App/OutOfTimeCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/App/OutOfTimeCoordinator.swift
@@ -33,7 +33,14 @@ class OutOfTimeCoordinator: Coordinator, OutOfTimeViewControllerDelegate {
storePaymentManager: storePaymentManager,
tunnelManager: tunnelManager
)
- let controller = OutOfTimeViewController(interactor: interactor)
+ let controller = OutOfTimeViewController(
+ interactor: interactor,
+ errorPresenter: PaymentAlertPresenter(
+ presentationController: navigationController,
+ alertPresenter: AlertPresenter()
+ )
+ )
+
controller.delegate = self
viewController = controller
diff --git a/ios/MullvadVPN/Extensions/RESTCreateApplePaymentResponse+Localization.swift b/ios/MullvadVPN/Extensions/RESTCreateApplePaymentResponse+Localization.swift
index 02b9eb06fd..2b887573e6 100644
--- a/ios/MullvadVPN/Extensions/RESTCreateApplePaymentResponse+Localization.swift
+++ b/ios/MullvadVPN/Extensions/RESTCreateApplePaymentResponse+Localization.swift
@@ -68,3 +68,24 @@ extension REST.CreateApplePaymentResponse {
}
}
}
+
+extension REST.CreateApplePaymentResponse.Context {
+ var errorTitle: String {
+ switch self {
+ case .purchase:
+ return NSLocalizedString(
+ "CANNOT_COMPLETE_PURCHASE_ALERT_TITLE",
+ tableName: "Payment",
+ value: "Cannot complete the purchase",
+ comment: ""
+ )
+ case .restoration:
+ return NSLocalizedString(
+ "RESTORE_PURCHASES_FAILURE_ALERT_TITLE",
+ tableName: "Payment",
+ value: "Cannot restore purchases",
+ comment: ""
+ )
+ }
+ }
+}
diff --git a/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotificationProvider.swift
index 345fe44a2c..67317e30ad 100644
--- a/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotificationProvider.swift
+++ b/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotificationProvider.swift
@@ -35,7 +35,7 @@ final class RegisteredDeviceInAppNotificationProvider: NotificationProvider,
let stylingOptions = NSAttributedString.MarkdownStylingOptions(font: .systemFont(ofSize: 14.0))
- return NSMutableAttributedString(markdownString: string, options: stylingOptions) { markdownType, string in
+ return NSAttributedString(markdownString: string, options: stylingOptions) { markdownType, string in
switch markdownType {
case .bold:
return [.foregroundColor: UIColor.InAppNotificationBanner.titleColor]
diff --git a/ios/MullvadVPN/View controllers/Account/AccountContentView.swift b/ios/MullvadVPN/View controllers/Account/AccountContentView.swift
index 127926dd5d..b450e378b4 100644
--- a/ios/MullvadVPN/View controllers/Account/AccountContentView.swift
+++ b/ios/MullvadVPN/View controllers/Account/AccountContentView.swift
@@ -131,9 +131,10 @@ class AccountDeviceRow: UIView {
}
}
+ var infoButtonAction: (() -> Void)?
+
private let titleLabel: UILabel = {
let label = UILabel()
- label.translatesAutoresizingMaskIntoConstraints = false
label.text = NSLocalizedString(
"DEVICE_NAME",
tableName: "Account",
@@ -147,36 +148,50 @@ class AccountDeviceRow: UIView {
private let deviceLabel: UILabel = {
let label = UILabel()
- label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 17)
label.textColor = .white
return label
}()
+ private let infoButton: UIButton = {
+ let button = IncreasedHitButton(type: .system)
+ button.accessibilityIdentifier = "InfoButton"
+ button.tintColor = .white
+ button.setImage(UIImage(named: "IconInfo"), for: .normal)
+ return button
+ }()
+
override init(frame: CGRect) {
super.init(frame: frame)
- addSubview(titleLabel)
- addSubview(deviceLabel)
-
- NSLayoutConstraint.activate([
- titleLabel.topAnchor.constraint(equalTo: topAnchor),
- titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
- titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
+ let contentContainerView = UIStackView(arrangedSubviews: [titleLabel, deviceLabel])
+ contentContainerView.axis = .vertical
+ contentContainerView.alignment = .leading
+ contentContainerView.spacing = 8
- deviceLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8),
- deviceLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
- deviceLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
- deviceLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
- ])
+ addConstrainedSubviews([contentContainerView, infoButton]) {
+ contentContainerView.pinEdgesToSuperview()
+ infoButton.leadingAnchor.constraint(equalToSystemSpacingAfter: deviceLabel.trailingAnchor, multiplier: 1)
+ infoButton.centerYAnchor.constraint(equalTo: deviceLabel.centerYAnchor)
+ }
isAccessibilityElement = true
accessibilityLabel = titleLabel.text
+
+ infoButton.addTarget(
+ self,
+ action: #selector(didTapInfoButton),
+ for: .touchUpInside
+ )
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
+
+ @objc private func didTapInfoButton() {
+ infoButtonAction?()
+ }
}
class AccountNumberRow: UIView {
diff --git a/ios/MullvadVPN/View controllers/Account/AccountViewController.swift b/ios/MullvadVPN/View controllers/Account/AccountViewController.swift
index 73d16ca854..7d6222a640 100644
--- a/ios/MullvadVPN/View controllers/Account/AccountViewController.swift
+++ b/ios/MullvadVPN/View controllers/Account/AccountViewController.swift
@@ -13,22 +13,18 @@ import Operations
import StoreKit
import UIKit
-enum AccountsNavigationRoute {
- case redeemVoucher
-}
-
-protocol AccountViewControllerDelegate: AnyObject {
- func accountViewControllerDidFinish(_ controller: AccountViewController)
- func accountViewControllerDidLogout(_ controller: AccountViewController)
- func accountViewController(
- _ controller: AccountViewController,
- didRequestRoutePresentation route: AccountsNavigationRoute
- )
+enum AccountViewControllerAction {
+ case deviceInfo
+ case finish
+ case logOut
+ case navigateToVoucher
}
class AccountViewController: UIViewController {
+ typealias ActionHandler = (AccountViewControllerAction) -> Void
+
private let interactor: AccountInteractor
- private let alertPresenter = AlertPresenter()
+ private let errorPresenter: PaymentAlertPresenter
private let contentView: AccountContentView = {
let contentView = AccountContentView()
@@ -39,10 +35,11 @@ class AccountViewController: UIViewController {
private var productState: ProductState = .none
private var paymentState: PaymentState = .none
- weak var delegate: AccountViewControllerDelegate?
+ var actionHandler: ActionHandler?
- init(interactor: AccountInteractor) {
+ init(interactor: AccountInteractor, errorPresenter: PaymentAlertPresenter) {
self.interactor = interactor
+ self.errorPresenter = errorPresenter
super.init(nibName: nil, bundle: nil)
}
@@ -104,6 +101,10 @@ class AccountViewController: UIViewController {
for: .touchUpInside
)
+ contentView.accountDeviceRow.infoButtonAction = { [weak self] in
+ self?.actionHandler?(.deviceInfo)
+ }
+
contentView.restorePurchasesButton.addTarget(
self,
action: #selector(restorePurchases),
@@ -114,7 +115,7 @@ class AccountViewController: UIViewController {
action: #selector(doPurchase),
for: .touchUpInside
)
- contentView.logoutButton.addTarget(self, action: #selector(logout), for: .touchUpInside)
+ contentView.logoutButton.addTarget(self, action: #selector(logOut), for: .touchUpInside)
interactor.didReceiveDeviceState = { [weak self] deviceState in
self?.updateView(from: deviceState)
@@ -136,10 +137,6 @@ class AccountViewController: UIViewController {
// MARK: - Private
- @objc private func handleDismiss() {
- delegate?.accountViewControllerDidFinish(self)
- }
-
private func requestStoreProducts() {
let productKind = StoreSubscription.thirtyDays
@@ -205,7 +202,7 @@ class AccountViewController: UIViewController {
switch event {
case let .finished(completion):
- showTimeAddedConfirmationAlert(with: completion.serverResponse, context: .purchase)
+ errorPresenter.showAlertForResponse(completion.serverResponse, context: .purchase)
case let .failure(paymentFailure):
switch paymentFailure.error {
@@ -213,111 +210,33 @@ class AccountViewController: UIViewController {
break
default:
- showPaymentErrorAlert(error: paymentFailure.error)
+ errorPresenter.showAlertForError(paymentFailure.error, context: .purchase)
}
}
setPaymentState(.none, animated: true)
}
- private func showPaymentErrorAlert(error: StorePaymentManagerError) {
- let alertController = CustomAlertViewController(
- title: NSLocalizedString(
- "CANNOT_COMPLETE_PURCHASE_ALERT_TITLE",
- tableName: "Account",
- value: "Cannot complete the purchase",
- comment: ""
- ),
- message: error.displayErrorDescription
- )
-
- alertController.addAction(
- title: NSLocalizedString(
- "CANNOT_COMPLETE_PURCHASE_ALERT_OK_ACTION",
- tableName: "Account",
- value: "Got it!",
- comment: ""
- ),
- style: .default
- )
-
- alertPresenter.enqueue(alertController, presentingController: self)
- }
-
- private func showRestorePurchasesErrorAlert(error: StorePaymentManagerError) {
- let alertController = CustomAlertViewController(
- title: NSLocalizedString(
- "RESTORE_PURCHASES_FAILURE_ALERT_TITLE",
- tableName: "Account",
- value: "Cannot restore purchases",
- comment: ""
- ),
- message: error.displayErrorDescription
- )
-
- alertController.addAction(
- title: NSLocalizedString(
- "RESTORE_PURCHASES_FAILURE_ALERT_OK_ACTION",
- tableName: "Account",
- value: "Got it!",
- comment: ""
- ),
- style: .default
- )
-
- alertPresenter.enqueue(alertController, presentingController: self)
- }
-
- private func showTimeAddedConfirmationAlert(
- with response: REST.CreateApplePaymentResponse,
- context: REST.CreateApplePaymentResponse.Context
- ) {
- let alertController = CustomAlertViewController(
- title: response.alertTitle(context: context),
- message: response.alertMessage(context: context)
- )
-
- alertController.addAction(
- title: NSLocalizedString(
- "TIME_ADDED_ALERT_OK_ACTION",
- tableName: "Account",
- value: "Got it!",
- comment: ""
- ),
- style: .default
- )
+ private func copyAccountToken() {
+ guard let accountData = interactor.deviceState.accountData else {
+ return
+ }
- alertPresenter.enqueue(alertController, presentingController: self)
+ UIPasteboard.general.string = accountData.number
}
// MARK: - Actions
- @objc private func redeemVoucher() {
- delegate?.accountViewController(self, didRequestRoutePresentation: .redeemVoucher)
+ @objc private func logOut() {
+ actionHandler?(.logOut)
}
- @objc private func logout() {
- let alertController = CustomAlertViewController(
- icon: .spinner
- )
-
- alertPresenter.enqueue(alertController, presentingController: self) {
- self.interactor.logout {
- DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
- alertController.dismiss(animated: true) {
- self.delegate?.accountViewControllerDidLogout(self)
- }
- }
- }
- }
+ @objc private func handleDismiss() {
+ actionHandler?(.finish)
}
- private func copyAccountToken() {
- guard let accountData = interactor.deviceState.accountData else {
- return
- }
-
- UIPasteboard.general.string = accountData.number
+ @objc private func redeemVoucher() {
+ actionHandler?(.navigateToVoucher)
}
@objc private func doPurchase() {
@@ -345,10 +264,10 @@ class AccountViewController: UIViewController {
switch completion {
case let .success(response):
- showTimeAddedConfirmationAlert(with: response, context: .restoration)
+ errorPresenter.showAlertForResponse(response, context: .restoration)
case let .failure(error as StorePaymentManagerError):
- showRestorePurchasesErrorAlert(error: error)
+ errorPresenter.showAlertForError(error, context: .restoration)
default:
break
diff --git a/ios/MullvadVPN/View controllers/Account/PaymentAlertPresenter.swift b/ios/MullvadVPN/View controllers/Account/PaymentAlertPresenter.swift
new file mode 100644
index 0000000000..64908b723d
--- /dev/null
+++ b/ios/MullvadVPN/View controllers/Account/PaymentAlertPresenter.swift
@@ -0,0 +1,76 @@
+//
+// PaymentErrorPresenter.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2023-05-30.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadREST
+import UIKit
+
+class PaymentAlertPresenter {
+ private let presentationController: UIViewController
+ private let alertPresenter: AlertPresenter
+
+ init(presentationController: UIViewController, alertPresenter: AlertPresenter) {
+ self.presentationController = presentationController
+ self.alertPresenter = alertPresenter
+ }
+
+ func showAlertForError(
+ _ error: StorePaymentManagerError,
+ context: REST.CreateApplePaymentResponse.Context,
+ completion: (() -> Void)? = nil
+ ) {
+ let alertController = CustomAlertViewController(
+ title: context.errorTitle,
+ message: error.displayErrorDescription
+ )
+
+ alertController.addAction(
+ title: okButtonTextForKey("PAYMENT_ERROR_ALERT_OK_ACTION"),
+ style: .default,
+ handler: {
+ completion?()
+ }
+ )
+
+ alertPresenter.enqueue(alertController, presentingController: presentationController)
+ }
+
+ func showAlertForResponse(
+ _ response: REST.CreateApplePaymentResponse,
+ context: REST.CreateApplePaymentResponse.Context,
+ completion: (() -> Void)? = nil
+ ) {
+ guard case .noTimeAdded = response else {
+ completion?()
+ return
+ }
+
+ let alertController = CustomAlertViewController(
+ title: response.alertTitle(context: context),
+ message: response.alertMessage(context: context)
+ )
+
+ alertController.addAction(
+ title: okButtonTextForKey("PAYMENT_RESPONSE_ALERT_OK_ACTION"),
+ style: .default,
+ handler: {
+ completion?()
+ }
+ )
+
+ alertPresenter.enqueue(alertController, presentingController: presentationController)
+ }
+
+ private func okButtonTextForKey(_ key: String) -> String {
+ return NSLocalizedString(
+ key,
+ tableName: "Payment",
+ value: "Got it!",
+ comment: ""
+ )
+ }
+}
diff --git a/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeViewController.swift b/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeViewController.swift
index ce0d4ae9d2..cb518c85d6 100644
--- a/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeViewController.swift
+++ b/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeViewController.swift
@@ -21,7 +21,7 @@ class OutOfTimeViewController: UIViewController, RootContainment {
weak var delegate: OutOfTimeViewControllerDelegate?
private let interactor: OutOfTimeInteractor
- private let alertPresenter = AlertPresenter()
+ private let errorPresenter: PaymentAlertPresenter
private var productState: ProductState = .none {
didSet {
@@ -55,8 +55,9 @@ class OutOfTimeViewController: UIViewController, RootContainment {
return false
}
- init(interactor: OutOfTimeInteractor) {
+ init(interactor: OutOfTimeInteractor, errorPresenter: PaymentAlertPresenter) {
self.interactor = interactor
+ self.errorPresenter = errorPresenter
super.init(nibName: nil, bundle: nil)
}
@@ -198,7 +199,7 @@ class OutOfTimeViewController: UIViewController, RootContainment {
break
default:
- showPaymentErrorAlert(error: paymentFailure.error) {
+ errorPresenter.showAlertForError(paymentFailure.error, context: .purchase) {
self.paymentState = .none
}
}
@@ -207,97 +208,6 @@ class OutOfTimeViewController: UIViewController, RootContainment {
paymentState = .none
}
- private func showPaymentErrorAlert(
- error: StorePaymentManagerError,
- completion: @escaping () -> Void
- ) {
- let alertController = CustomAlertViewController(
- title: NSLocalizedString(
- "CANNOT_COMPLETE_PURCHASE_ALERT_TITLE",
- tableName: "OutOfTime",
- value: "Cannot complete the purchase",
- comment: ""
- ),
- message: error.displayErrorDescription
- )
-
- alertController.addAction(
- title: NSLocalizedString(
- "CANNOT_COMPLETE_PURCHASE_ALERT_OK_ACTION",
- tableName: "OutOfTime",
- value: "Got it!",
- comment: ""
- ),
- style: .default,
- handler: {
- completion()
- }
- )
-
- alertPresenter.enqueue(alertController, presentingController: self)
- }
-
- private func showRestorePurchasesErrorAlert(
- error: StorePaymentManagerError,
- completion: @escaping () -> Void
- ) {
- let alertController = CustomAlertViewController(
- title: NSLocalizedString(
- "RESTORE_PURCHASES_FAILURE_ALERT_TITLE",
- tableName: "OutOfTime",
- value: "Cannot restore purchases",
- comment: ""
- ),
- message: error.displayErrorDescription
- )
-
- alertController.addAction(
- title: NSLocalizedString(
- "RESTORE_PURCHASES_FAILURE_ALERT_OK_ACTION",
- tableName: "OutOfTime",
- value: "Got it!",
- comment: ""
- ),
- style: .default,
- handler: {
- completion()
- }
- )
-
- alertPresenter.enqueue(alertController, presentingController: self)
- }
-
- private func showAlertIfNoTimeAdded(
- with response: REST.CreateApplePaymentResponse,
- context: REST.CreateApplePaymentResponse.Context,
- completion: @escaping () -> Void
- ) {
- guard case .noTimeAdded = response else {
- completion()
- return
- }
-
- let alertController = CustomAlertViewController(
- title: response.alertTitle(context: context),
- message: response.alertMessage(context: context)
- )
-
- alertController.addAction(
- title: NSLocalizedString(
- "TIME_ADDED_ALERT_OK_ACTION",
- tableName: "OutOfTime",
- value: "Got it!",
- comment: ""
- ),
- style: .default,
- handler: {
- completion()
- }
- )
-
- alertPresenter.enqueue(alertController, presentingController: self)
- }
-
// MARK: - Actions
@objc private func doPurchase() {
@@ -325,12 +235,12 @@ class OutOfTimeViewController: UIViewController, RootContainment {
switch result {
case let .success(response):
- showAlertIfNoTimeAdded(with: response, context: .restoration) {
+ errorPresenter.showAlertForResponse(response, context: .restoration) {
self.paymentState = .none
}
case let .failure(error as StorePaymentManagerError):
- showRestorePurchasesErrorAlert(error: error) {
+ errorPresenter.showAlertForError(error, context: .restoration) {
self.paymentState = .none
}