diff options
| author | Jon Petersson <jon.petersson@mullvad.net> | 2025-08-28 10:49:07 +0200 |
|---|---|---|
| committer | Jon Petersson <jon.petersson@mullvad.net> | 2025-08-28 10:49:07 +0200 |
| commit | fe878b2727f30d4120d32591d4285f8e986a8090 (patch) | |
| tree | 6793fc66809356379a5659336016256b153a6e11 /ios | |
| parent | 16685513c3f597498212f5add724f34171472a4a (diff) | |
| parent | 6f3137361452f1f69e74a4016dac3c59008eedfc (diff) | |
| download | mullvadvpn-fe878b2727f30d4120d32591d4285f8e986a8090.tar.xz mullvadvpn-fe878b2727f30d4120d32591d4285f8e986a8090.zip | |
Merge branch 'add-confirmation-dialogs-regarding-account-creation-and-ios-1232'
Diffstat (limited to 'ios')
8 files changed, 138 insertions, 5 deletions
diff --git a/ios/Assets/Localizable.xcstrings b/ios/Assets/Localizable.xcstrings index 3ed7c54ccd..57053d2910 100644 --- a/ios/Assets/Localizable.xcstrings +++ b/ios/Assets/Localizable.xcstrings @@ -1541,6 +1541,9 @@ } } }, + "Create new account" : { + + }, "Creating new account" : { "localizations" : { "de" : { @@ -5185,6 +5188,9 @@ } } }, + "Remove" : { + + }, "Remove last used account" : { "localizations" : { "de" : { @@ -5207,6 +5213,9 @@ } } }, + "Removing the saved account number from this device cannot be undone.\nDo you want to remove the saved account number?" : { + + }, "Rented" : { "localizations" : { "de" : { @@ -7373,6 +7382,9 @@ } } }, + "You already have a saved account number, by creating a new account the saved account number will be removed from this device. This cannot be undone.\nDo you want to create a new account?" : { + + }, "You are about to send the problem report without a way for us to get back to you. If you want an answer to your report you will have to enter an email address." : { "localizations" : { "de" : { diff --git a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift index 3819a755dd..f849db464b 100644 --- a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift +++ b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift @@ -27,6 +27,10 @@ public enum AccessibilityIdentifier: Equatable { case collapseButton case expandButton case createAccountButton + case createAccountConfirmationButton + case createAccountCancelButton + case removeLastUsedAccountButton + case cancelRemoveLastUsedAccountButton case deleteButton case deviceCellRemoveButton case disconnectButton diff --git a/ios/MullvadVPN/Coordinators/LoginCoordinator.swift b/ios/MullvadVPN/Coordinators/LoginCoordinator.swift index 662a094def..eaff6ab01e 100644 --- a/ios/MullvadVPN/Coordinators/LoginCoordinator.swift +++ b/ios/MullvadVPN/Coordinators/LoginCoordinator.swift @@ -44,7 +44,10 @@ final class LoginCoordinator: Coordinator, Presenting { func start(animated: Bool) { let interactor = LoginInteractor(tunnelManager: tunnelManager) - let loginController = LoginViewController(interactor: interactor) + let loginController = LoginViewController( + interactor: interactor, + alertPresenter: AlertPresenter(context: self) + ) loginController.didFinishLogin = { [weak self] action, error in self?.didFinishLogin(action: action, error: error) ?? .nothing diff --git a/ios/MullvadVPN/View controllers/Login/AccountInputGroupView.swift b/ios/MullvadVPN/View controllers/Login/AccountInputGroupView.swift index 221d07bc86..a5e51262ab 100644 --- a/ios/MullvadVPN/View controllers/Login/AccountInputGroupView.swift +++ b/ios/MullvadVPN/View controllers/Login/AccountInputGroupView.swift @@ -356,7 +356,6 @@ final class AccountInputGroupView: UIView { @objc private func didTapRemoveLastUsedAccount() { didRemoveLastUsedAccount?() - setLastUsedAccount(nil, animated: true) } // MARK: - Private diff --git a/ios/MullvadVPN/View controllers/Login/LoginInteractor.swift b/ios/MullvadVPN/View controllers/Login/LoginInteractor.swift index d16f571608..d993324892 100644 --- a/ios/MullvadVPN/View controllers/Login/LoginInteractor.swift +++ b/ios/MullvadVPN/View controllers/Login/LoginInteractor.swift @@ -16,6 +16,9 @@ final class LoginInteractor: @unchecked Sendable { private var tunnelObserver: TunnelObserver? var didCreateAccount: (@MainActor @Sendable () -> Void)? var suggestPreferredAccountNumber: (@Sendable (String) -> Void)? + var hasLastAccountNumber: Bool { + getLastUsedAccount() != nil + } init(tunnelManager: TunnelManager) { self.tunnelManager = tunnelManager diff --git a/ios/MullvadVPN/View controllers/Login/LoginViewController.swift b/ios/MullvadVPN/View controllers/Login/LoginViewController.swift index 6fb6ca9643..1a1da7b8b3 100644 --- a/ios/MullvadVPN/View controllers/Login/LoginViewController.swift +++ b/ios/MullvadVPN/View controllers/Login/LoginViewController.swift @@ -102,8 +102,11 @@ class LoginViewController: UIViewController, RootContainment { false } - init(interactor: LoginInteractor) { + private let alertPresenter: AlertPresenter + + init(interactor: LoginInteractor, alertPresenter: AlertPresenter) { self.interactor = interactor + self.alertPresenter = alertPresenter super.init(nibName: nil, bundle: nil) } @@ -124,7 +127,7 @@ class LoginViewController: UIViewController, RootContainment { updateLastUsedAccount() contentView.accountInputGroup.didRemoveLastUsedAccount = { [weak self] in - self?.interactor.removeLastUsedAccount() + self?.showLastUsedAccountRemovalWarning() } contentView.accountInputGroup.didEnterAccount = { [weak self] in @@ -227,11 +230,83 @@ class LoginViewController: UIViewController, RootContainment { } @objc private func createNewAccount() { - start(action: .createAccount) + if interactor.hasLastAccountNumber { + let message = NSMutableAttributedString( + markdownString: NSLocalizedString(""" + You already have a saved account number, by creating a new account the saved account number \ + will be removed from this device. This cannot be undone. + Do you want to create a new account? + """, comment: ""), + options: MarkdownStylingOptions(font: .preferredFont(forTextStyle: .body)) + ) + + let presentation = AlertPresentation( + id: "create-account-confirmation-dialog", + icon: .info, + attributedMessage: message, + buttons: [ + AlertAction( + title: NSLocalizedString("Create new account", comment: ""), + style: .default, + accessibilityId: .createAccountConfirmationButton, + handler: { + self.start(action: .createAccount) + } + ), + AlertAction( + title: NSLocalizedString("Cancel", comment: ""), + style: .default, + accessibilityId: .createAccountCancelButton + ), + ] + ) + alertPresenter.showAlert(presentation: presentation, animated: true) + } else { + start(action: .createAccount) + } } // MARK: - Private + private func showLastUsedAccountRemovalWarning() { + let message = NSMutableAttributedString( + markdownString: NSLocalizedString( + """ + Removing the saved account number from this device cannot be undone. + Do you want to remove the saved account number? + """, + comment: "" + ), + options: MarkdownStylingOptions(font: .preferredFont(forTextStyle: .body)) + ) + + let presentation = AlertPresentation( + id: "remove-saved-account-number-dialog", + icon: .info, + attributedMessage: message, + buttons: [ + AlertAction( + title: NSLocalizedString("Remove", comment: ""), + style: .destructive, + accessibilityId: .removeLastUsedAccountButton, + handler: { + self.interactor.removeLastUsedAccount() + self.contentView.accountInputGroup.setLastUsedAccount( + nil, + animated: true + ) + } + ), + AlertAction( + title: NSLocalizedString("Cancel", comment: ""), + style: .default, + accessibilityId: .cancelRemoveLastUsedAccountButton + ), + ] + ) + self.alertPresenter.showAlert(presentation: presentation, animated: true) + } + private func updateLastUsedAccount() { contentView.accountInputGroup.setLastUsedAccount( interactor.getLastUsedAccount(), diff --git a/ios/MullvadVPNUITests/AccountTests.swift b/ios/MullvadVPNUITests/AccountTests.swift index 9110c3dad6..ee4e6c6ce3 100644 --- a/ios/MullvadVPNUITests/AccountTests.swift +++ b/ios/MullvadVPNUITests/AccountTests.swift @@ -25,6 +25,38 @@ class AccountTests: LoggedOutUITestCase { mullvadAPIWrapper.deleteAccount(accountNumber) } + func testCreateAccountWithLastUsedAccount() throws { + // Setup + let temporaryAccountNumber = createTemporaryAccountWithoutTime() + + // Teardown + addTeardownBlock { + self.mullvadAPIWrapper.deleteAccount(temporaryAccountNumber) + } + + LoginPage(app) + .tapAccountNumberTextField() + .enterText(temporaryAccountNumber) + .tapAccountNumberSubmitButton() + + OutOfTimePage(app) + + HeaderBar(app) + .tapAccountButton() + + AccountPage(app) + .tapLogOutButton() + + LoginPage(app) + .tapCreateAccountButton() + .confirmAccountCreation() + + // Verify welcome page is shown and get account number from it + let accountNumber = WelcomePage(app).getAccountNumber() + + self.mullvadAPIWrapper.deleteAccount(accountNumber) + } + func testDeleteAccount() throws { let accountNumber = createTemporaryAccountWithoutTime() diff --git a/ios/MullvadVPNUITests/Pages/LoginPage.swift b/ios/MullvadVPNUITests/Pages/LoginPage.swift index 6035d844ca..42e78c71c3 100644 --- a/ios/MullvadVPNUITests/Pages/LoginPage.swift +++ b/ios/MullvadVPNUITests/Pages/LoginPage.swift @@ -47,6 +47,11 @@ class LoginPage: Page { return self } + @discardableResult public func confirmAccountCreation() -> Self { + app.buttons[AccessibilityIdentifier.createAccountConfirmationButton].tap() + return self + } + @discardableResult public func verifyFailIconShown() -> Self { let predicate = NSPredicate(format: "identifier == 'statusImageView' AND value == 'fail'") let elementQuery = app.images.containing(predicate) |
