diff options
| author | Jon Petersson <jon.petersson@kvadrat.se> | 2023-05-22 13:25:38 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2023-06-22 12:43:58 +0200 |
| commit | 06fde66467f1ae9e0fc5805358c49d689d92d704 (patch) | |
| tree | 66bc18ba52d2e1c98c0ebcaa85d1c192f243a976 | |
| parent | 4e85e925966a1d1496eb1acf25207ecd0c80db77 (diff) | |
| download | mullvadvpn-06fde66467f1ae9e0fc5805358c49d689d92d704.tar.xz mullvadvpn-06fde66467f1ae9e0fc5805358c49d689d92d704.zip | |
Add info button next to device name in account view
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 } |
