summaryrefslogtreecommitdiffhomepage
path: root/ios
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@mullvad.net>2025-08-28 10:49:07 +0200
committerJon Petersson <jon.petersson@mullvad.net>2025-08-28 10:49:07 +0200
commitfe878b2727f30d4120d32591d4285f8e986a8090 (patch)
tree6793fc66809356379a5659336016256b153a6e11 /ios
parent16685513c3f597498212f5add724f34171472a4a (diff)
parent6f3137361452f1f69e74a4016dac3c59008eedfc (diff)
downloadmullvadvpn-fe878b2727f30d4120d32591d4285f8e986a8090.tar.xz
mullvadvpn-fe878b2727f30d4120d32591d4285f8e986a8090.zip
Merge branch 'add-confirmation-dialogs-regarding-account-creation-and-ios-1232'
Diffstat (limited to 'ios')
-rw-r--r--ios/Assets/Localizable.xcstrings12
-rw-r--r--ios/MullvadVPN/Classes/AccessbilityIdentifier.swift4
-rw-r--r--ios/MullvadVPN/Coordinators/LoginCoordinator.swift5
-rw-r--r--ios/MullvadVPN/View controllers/Login/AccountInputGroupView.swift1
-rw-r--r--ios/MullvadVPN/View controllers/Login/LoginInteractor.swift3
-rw-r--r--ios/MullvadVPN/View controllers/Login/LoginViewController.swift81
-rw-r--r--ios/MullvadVPNUITests/AccountTests.swift32
-rw-r--r--ios/MullvadVPNUITests/Pages/LoginPage.swift5
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)