summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBug Magnet <marco.nikic@mullvad.net>2023-09-12 16:40:42 +0200
committerBug Magnet <marco.nikic@mullvad.net>2023-09-12 16:40:42 +0200
commitd7e598d6ff5993096cbc82c33d6d6d850b976429 (patch)
tree6123ced7e3f4432257d6bf0c812a701824c35c63
parentb952fff9354414c5aab011eaadf69f31466b1420 (diff)
parent5144389de1541b0565c9021d7a7542acdd227f75 (diff)
downloadmullvadvpn-d7e598d6ff5993096cbc82c33d6d6d850b976429.tar.xz
mullvadvpn-d7e598d6ff5993096cbc82c33d6d6d850b976429.zip
Merge branch 'account-number-as-voucher-input-error-in-account-flow-ios-207'
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj44
-rw-r--r--ios/MullvadVPN/Coordinators/AccountCoordinator.swift16
-rw-r--r--ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift24
-rw-r--r--ios/MullvadVPN/Coordinators/CreateAccountVoucherCoordinator.swift (renamed from ios/MullvadVPN/Coordinators/AccountRedeemingVoucherCoordinator.swift)21
-rw-r--r--ios/MullvadVPN/Coordinators/LoginCoordinator.swift12
-rw-r--r--ios/MullvadVPN/Coordinators/ProfileVoucherCoordinator.swift (renamed from ios/MullvadVPN/Coordinators/SettingsRedeemVoucherCoordinator.swift)17
-rw-r--r--ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift29
-rw-r--r--ios/MullvadVPN/SceneDelegate.swift1
-rw-r--r--ios/MullvadVPN/View controllers/Account/AccountInteractor.swift8
-rw-r--r--ios/MullvadVPN/View controllers/Login/LoginInteractor.swift1
-rw-r--r--ios/MullvadVPN/View controllers/Login/LoginViewController.swift4
-rw-r--r--ios/MullvadVPN/View controllers/RedeemVoucher/LogoutDialogueView.swift149
-rw-r--r--ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherContentView.swift35
-rw-r--r--ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherInteractor.swift64
-rw-r--r--ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherViewController.swift29
-rw-r--r--ios/MullvadVPN/View controllers/RedeemVoucher/VoucherTextField.swift4
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsInteractorFactory.swift7
17 files changed, 389 insertions, 76 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index e54ebb4eb0..1345a4e32d 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -422,13 +422,13 @@
7A9CCCB72A96302800DD6A34 /* RevokedCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9CCCA52A96302700DD6A34 /* RevokedCoordinator.swift */; };
7A9CCCB82A96302800DD6A34 /* SetupAccountCompletedCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9CCCA62A96302700DD6A34 /* SetupAccountCompletedCoordinator.swift */; };
7A9CCCB92A96302800DD6A34 /* SelectLocationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9CCCA72A96302700DD6A34 /* SelectLocationCoordinator.swift */; };
- 7A9CCCBA2A96302800DD6A34 /* AccountRedeemingVoucherCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9CCCA82A96302700DD6A34 /* AccountRedeemingVoucherCoordinator.swift */; };
+ 7A9CCCBA2A96302800DD6A34 /* CreateAccountVoucherCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9CCCA82A96302700DD6A34 /* CreateAccountVoucherCoordinator.swift */; };
7A9CCCBB2A96302800DD6A34 /* InAppPurchaseCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9CCCA92A96302700DD6A34 /* InAppPurchaseCoordinator.swift */; };
7A9CCCBC2A96302800DD6A34 /* ChangeLogCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9CCCAA2A96302700DD6A34 /* ChangeLogCoordinator.swift */; };
7A9CCCBD2A96302800DD6A34 /* LoginCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9CCCAB2A96302800DD6A34 /* LoginCoordinator.swift */; };
7A9CCCBE2A96302800DD6A34 /* AccountDeletionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9CCCAC2A96302800DD6A34 /* AccountDeletionCoordinator.swift */; };
7A9CCCBF2A96302800DD6A34 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9CCCAD2A96302800DD6A34 /* SettingsCoordinator.swift */; };
- 7A9CCCC02A96302800DD6A34 /* SettingsRedeemVoucherCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9CCCAE2A96302800DD6A34 /* SettingsRedeemVoucherCoordinator.swift */; };
+ 7A9CCCC02A96302800DD6A34 /* ProfileVoucherCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9CCCAE2A96302800DD6A34 /* ProfileVoucherCoordinator.swift */; };
7A9CCCC12A96302800DD6A34 /* AccountCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9CCCAF2A96302800DD6A34 /* AccountCoordinator.swift */; };
7A9CCCC22A96302800DD6A34 /* SafariCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9CCCB02A96302800DD6A34 /* SafariCoordinator.swift */; };
7A9CCCC32A96302800DD6A34 /* ApplicationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9CCCB12A96302800DD6A34 /* ApplicationCoordinator.swift */; };
@@ -483,16 +483,17 @@
E1187ABD289BBB850024E748 /* OutOfTimeContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1187ABB289BBB850024E748 /* OutOfTimeContentView.swift */; };
E158B360285381C60002F069 /* String+AccountFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = E158B35F285381C60002F069 /* String+AccountFormatting.swift */; };
E1FD0DF528AA7CE400299DB4 /* StatusActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FD0DF428AA7CE400299DB4 /* StatusActivityView.swift */; };
- F028A5492A336E8500C0CAA3 /* VoucherTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028A5482A336E8500C0CAA3 /* VoucherTextField.swift */; };
- F028A54B2A3370FA00C0CAA3 /* RedeemVoucherContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028A54A2A3370FA00C0CAA3 /* RedeemVoucherContentView.swift */; };
F028A56A2A34D4E700C0CAA3 /* RedeemVoucherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028A5692A34D4E700C0CAA3 /* RedeemVoucherViewController.swift */; };
F028A56C2A34D8E600C0CAA3 /* AddCreditSucceededViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028A56B2A34D8E600C0CAA3 /* AddCreditSucceededViewController.swift */; };
- F028A56E2A34DCC600C0CAA3 /* RedeemVoucherInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028A56D2A34DCC600C0CAA3 /* RedeemVoucherInteractor.swift */; };
F03580252A13842C00E5DAFD /* IncreasedHitButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03580242A13842C00E5DAFD /* IncreasedHitButton.swift */; };
F04FBE612A8379EE009278D7 /* AppPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04FBE602A8379EE009278D7 /* AppPreferences.swift */; };
F07BF2582A26112D00042943 /* InputTextFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */; };
F07BF2622A26279100042943 /* RedeemVoucherOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07BF2612A26279100042943 /* RedeemVoucherOperation.swift */; };
F07CFF2029F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07CFF1F29F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift */; };
+ F09A297B2A9F8A9B00EA3B6F /* LogoutDialogueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09A29782A9F8A9B00EA3B6F /* LogoutDialogueView.swift */; };
+ F09A297C2A9F8A9B00EA3B6F /* VoucherTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09A29792A9F8A9B00EA3B6F /* VoucherTextField.swift */; };
+ F09A297D2A9F8A9B00EA3B6F /* RedeemVoucherContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09A297A2A9F8A9B00EA3B6F /* RedeemVoucherContentView.swift */; };
+ F09A29822A9F8AD200EA3B6F /* RedeemVoucherInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09A297F2A9F8AD200EA3B6F /* RedeemVoucherInteractor.swift */; };
F0C2AEFD2A0BB5CC00986207 /* NotificationProviderIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C2AEFC2A0BB5CC00986207 /* NotificationProviderIdentifier.swift */; };
F0C6FA812A66E23300F521F0 /* DeleteAccountOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C6FA802A66E23300F521F0 /* DeleteAccountOperation.swift */; };
F0C6FA852A6A733700F521F0 /* InAppPurchaseInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C6FA842A6A733700F521F0 /* InAppPurchaseInteractor.swift */; };
@@ -1339,13 +1340,13 @@
7A9CCCA52A96302700DD6A34 /* RevokedCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RevokedCoordinator.swift; sourceTree = "<group>"; };
7A9CCCA62A96302700DD6A34 /* SetupAccountCompletedCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupAccountCompletedCoordinator.swift; sourceTree = "<group>"; };
7A9CCCA72A96302700DD6A34 /* SelectLocationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectLocationCoordinator.swift; sourceTree = "<group>"; };
- 7A9CCCA82A96302700DD6A34 /* AccountRedeemingVoucherCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountRedeemingVoucherCoordinator.swift; sourceTree = "<group>"; };
+ 7A9CCCA82A96302700DD6A34 /* CreateAccountVoucherCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateAccountVoucherCoordinator.swift; sourceTree = "<group>"; };
7A9CCCA92A96302700DD6A34 /* InAppPurchaseCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAppPurchaseCoordinator.swift; sourceTree = "<group>"; };
7A9CCCAA2A96302700DD6A34 /* ChangeLogCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeLogCoordinator.swift; sourceTree = "<group>"; };
7A9CCCAB2A96302800DD6A34 /* LoginCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginCoordinator.swift; sourceTree = "<group>"; };
7A9CCCAC2A96302800DD6A34 /* AccountDeletionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountDeletionCoordinator.swift; sourceTree = "<group>"; };
7A9CCCAD2A96302800DD6A34 /* SettingsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = "<group>"; };
- 7A9CCCAE2A96302800DD6A34 /* SettingsRedeemVoucherCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsRedeemVoucherCoordinator.swift; sourceTree = "<group>"; };
+ 7A9CCCAE2A96302800DD6A34 /* ProfileVoucherCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileVoucherCoordinator.swift; sourceTree = "<group>"; };
7A9CCCAF2A96302800DD6A34 /* AccountCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountCoordinator.swift; sourceTree = "<group>"; };
7A9CCCB02A96302800DD6A34 /* SafariCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SafariCoordinator.swift; sourceTree = "<group>"; };
7A9CCCB12A96302800DD6A34 /* ApplicationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationCoordinator.swift; sourceTree = "<group>"; };
@@ -1378,17 +1379,18 @@
E1187ABB289BBB850024E748 /* OutOfTimeContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeContentView.swift; sourceTree = "<group>"; };
E158B35F285381C60002F069 /* String+AccountFormatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+AccountFormatting.swift"; sourceTree = "<group>"; };
E1FD0DF428AA7CE400299DB4 /* StatusActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusActivityView.swift; sourceTree = "<group>"; };
- F028A5482A336E8500C0CAA3 /* VoucherTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoucherTextField.swift; sourceTree = "<group>"; };
- F028A54A2A3370FA00C0CAA3 /* RedeemVoucherContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemVoucherContentView.swift; sourceTree = "<group>"; };
F028A5692A34D4E700C0CAA3 /* RedeemVoucherViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemVoucherViewController.swift; sourceTree = "<group>"; };
F028A56B2A34D8E600C0CAA3 /* AddCreditSucceededViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddCreditSucceededViewController.swift; sourceTree = "<group>"; };
- F028A56D2A34DCC600C0CAA3 /* RedeemVoucherInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemVoucherInteractor.swift; sourceTree = "<group>"; };
F03580242A13842C00E5DAFD /* IncreasedHitButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncreasedHitButton.swift; sourceTree = "<group>"; };
F0465B5B2A7927B40004089E /* AddCreditSucceededCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCreditSucceededCoordinator.swift; sourceTree = "<group>"; };
F04FBE602A8379EE009278D7 /* AppPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPreferences.swift; sourceTree = "<group>"; };
F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputTextFormatterTests.swift; sourceTree = "<group>"; };
F07BF2612A26279100042943 /* RedeemVoucherOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemVoucherOperation.swift; sourceTree = "<group>"; };
F07CFF1F29F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisteredDeviceInAppNotificationProvider.swift; sourceTree = "<group>"; };
+ F09A29782A9F8A9B00EA3B6F /* LogoutDialogueView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogoutDialogueView.swift; sourceTree = "<group>"; };
+ F09A29792A9F8A9B00EA3B6F /* VoucherTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoucherTextField.swift; sourceTree = "<group>"; };
+ F09A297A2A9F8A9B00EA3B6F /* RedeemVoucherContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemVoucherContentView.swift; sourceTree = "<group>"; };
+ F09A297F2A9F8AD200EA3B6F /* RedeemVoucherInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemVoucherInteractor.swift; sourceTree = "<group>"; };
F0C2AEFC2A0BB5CC00986207 /* NotificationProviderIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationProviderIdentifier.swift; sourceTree = "<group>"; };
F0C6FA802A66E23300F521F0 /* DeleteAccountOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAccountOperation.swift; sourceTree = "<group>"; };
F0C6FA822A6A729500F521F0 /* InAppPurchaseCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseCoordinator.swift; sourceTree = "<group>"; };
@@ -2307,18 +2309,18 @@
children = (
7A9CCCAF2A96302800DD6A34 /* AccountCoordinator.swift */,
7A9CCCAC2A96302800DD6A34 /* AccountDeletionCoordinator.swift */,
- 7A9CCCA82A96302700DD6A34 /* AccountRedeemingVoucherCoordinator.swift */,
7A9CCCA32A96302700DD6A34 /* AddCreditSucceededCoordinator.swift */,
7A9CCCB12A96302800DD6A34 /* ApplicationCoordinator.swift */,
7A9CCCAA2A96302700DD6A34 /* ChangeLogCoordinator.swift */,
+ 7A9CCCA82A96302700DD6A34 /* CreateAccountVoucherCoordinator.swift */,
7A9CCCA92A96302700DD6A34 /* InAppPurchaseCoordinator.swift */,
7A9CCCAB2A96302800DD6A34 /* LoginCoordinator.swift */,
7A9CCCA42A96302700DD6A34 /* OutOfTimeCoordinator.swift */,
+ 7A9CCCAE2A96302800DD6A34 /* ProfileVoucherCoordinator.swift */,
7A9CCCA52A96302700DD6A34 /* RevokedCoordinator.swift */,
7A9CCCB02A96302800DD6A34 /* SafariCoordinator.swift */,
7A9CCCA72A96302700DD6A34 /* SelectLocationCoordinator.swift */,
7A9CCCAD2A96302800DD6A34 /* SettingsCoordinator.swift */,
- 7A9CCCAE2A96302800DD6A34 /* SettingsRedeemVoucherCoordinator.swift */,
7A9CCCA62A96302700DD6A34 /* SetupAccountCompletedCoordinator.swift */,
7A9CCCA22A96302700DD6A34 /* TermsOfServiceCoordinator.swift */,
7A9CCCB22A96302800DD6A34 /* TunnelCoordinator.swift */,
@@ -2617,11 +2619,12 @@
F028A5472A336E1900C0CAA3 /* RedeemVoucher */ = {
isa = PBXGroup;
children = (
- F028A54A2A3370FA00C0CAA3 /* RedeemVoucherContentView.swift */,
- F028A56D2A34DCC600C0CAA3 /* RedeemVoucherInteractor.swift */,
F028A56B2A34D8E600C0CAA3 /* AddCreditSucceededViewController.swift */,
+ F09A29782A9F8A9B00EA3B6F /* LogoutDialogueView.swift */,
+ F09A297A2A9F8A9B00EA3B6F /* RedeemVoucherContentView.swift */,
+ F09A297F2A9F8AD200EA3B6F /* RedeemVoucherInteractor.swift */,
F028A5692A34D4E700C0CAA3 /* RedeemVoucherViewController.swift */,
- F028A5482A336E8500C0CAA3 /* VoucherTextField.swift */,
+ F09A29792A9F8A9B00EA3B6F /* VoucherTextField.swift */,
);
path = RedeemVoucher;
sourceTree = "<group>";
@@ -3760,6 +3763,7 @@
F0E8E4C92A604E7400ED26A3 /* AccountDeletionInteractor.swift in Sources */,
58FF2C03281BDE02009EF542 /* SettingsManager.swift in Sources */,
A9F360342AAB626300F53531 /* VPNConnectionProtocol.swift in Sources */,
+ F09A297D2A9F8A9B00EA3B6F /* RedeemVoucherContentView.swift in Sources */,
5803B4B02940A47300C23744 /* TunnelConfiguration.swift in Sources */,
587EB672271451E300123C75 /* PreferencesViewModel.swift in Sources */,
586A950C290125EE007BAF2B /* AlertPresenter.swift in Sources */,
@@ -3781,7 +3785,6 @@
5867770E29096984006F721F /* OutOfTimeInteractor.swift in Sources */,
F03580252A13842C00E5DAFD /* IncreasedHitButton.swift in Sources */,
58F8AC0E25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift in Sources */,
- F028A56E2A34DCC600C0CAA3 /* RedeemVoucherInteractor.swift in Sources */,
5878A27129091CF20096FC88 /* AccountInteractor.swift in Sources */,
068CE5742927B7A400A068BB /* Migration.swift in Sources */,
A92ECC282A7802AB0052F1B1 /* StoredDeviceData.swift in Sources */,
@@ -3811,7 +3814,6 @@
5871FB96254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift in Sources */,
F0E8E4C52A60499100ED26A3 /* AccountDeletionViewController.swift in Sources */,
7A9CCCC12A96302800DD6A34 /* AccountCoordinator.swift in Sources */,
- F028A54B2A3370FA00C0CAA3 /* RedeemVoucherContentView.swift in Sources */,
58FEEB58260B662E00A621A8 /* AutomaticKeyboardResponder.swift in Sources */,
5846227326E22A160035F7C2 /* StorePaymentObserver.swift in Sources */,
F0E3618B2A4ADD2F00AEEF2B /* WelcomeContentView.swift in Sources */,
@@ -3819,12 +3821,13 @@
E1187ABC289BBB850024E748 /* OutOfTimeViewController.swift in Sources */,
7A9CCCBD2A96302800DD6A34 /* LoginCoordinator.swift in Sources */,
58293FB125124117005D0BB5 /* CustomTextField.swift in Sources */,
+ F09A29822A9F8AD200EA3B6F /* RedeemVoucherInteractor.swift in Sources */,
58138E61294871C600684F0C /* DeviceDataThrottling.swift in Sources */,
5878A279290954790096FC88 /* TunnelViewControllerInteractor.swift in Sources */,
7A818F1F29F0305800C7F0F4 /* RootConfiguration.swift in Sources */,
7A9CCCBF2A96302800DD6A34 /* SettingsCoordinator.swift in Sources */,
582AE3102440A6CA00E6733A /* InputTextFormatter.swift in Sources */,
- 7A9CCCBA2A96302800DD6A34 /* AccountRedeemingVoucherCoordinator.swift in Sources */,
+ 7A9CCCBA2A96302800DD6A34 /* CreateAccountVoucherCoordinator.swift in Sources */,
5820EDAB288FF0D2006BF4E4 /* DeviceRowView.swift in Sources */,
F0E8CC0C2A4EE672007ED3B4 /* SetupAccountCompletedController.swift in Sources */,
5846227726E22A7C0035F7C2 /* StorePaymentManagerDelegate.swift in Sources */,
@@ -3921,7 +3924,7 @@
5878A27D2909657C0096FC88 /* RevokedDeviceInteractor.swift in Sources */,
F0E8E4C32A602E0D00ED26A3 /* AccountDeletionViewModel.swift in Sources */,
58677710290975E9006F721F /* SettingsInteractorFactory.swift in Sources */,
- 7A9CCCC02A96302800DD6A34 /* SettingsRedeemVoucherCoordinator.swift in Sources */,
+ 7A9CCCC02A96302800DD6A34 /* ProfileVoucherCoordinator.swift in Sources */,
7A9CCCBC2A96302800DD6A34 /* ChangeLogCoordinator.swift in Sources */,
58B26E282943527300D5980C /* SystemNotificationProvider.swift in Sources */,
58CCA01222424D11004F3011 /* SettingsViewController.swift in Sources */,
@@ -3934,7 +3937,6 @@
587D9676288989DB00CD8F1C /* NSLayoutConstraint+Helpers.swift in Sources */,
F028A56C2A34D8E600C0CAA3 /* AddCreditSucceededViewController.swift in Sources */,
58293FAE2510CA58005D0BB5 /* ProblemReportViewController.swift in Sources */,
- F028A5492A336E8500C0CAA3 /* VoucherTextField.swift in Sources */,
58B9EB152489139B00095626 /* RESTError+Display.swift in Sources */,
587B753F2668E5A700DEF7E9 /* NotificationContainerView.swift in Sources */,
58F2E144276A13F300A79513 /* StartTunnelOperation.swift in Sources */,
@@ -3955,9 +3957,11 @@
7A9CCCB62A96302800DD6A34 /* OutOfTimeCoordinator.swift in Sources */,
58FD5BF024238EB300112C88 /* SKProduct+Formatting.swift in Sources */,
58B43C1925F77DB60002C8C3 /* TunnelControlView.swift in Sources */,
+ F09A297B2A9F8A9B00EA3B6F /* LogoutDialogueView.swift in Sources */,
5811DE50239014550011EB53 /* NEVPNStatus+Debug.swift in Sources */,
7A21DACF2A30AA3700A787A9 /* UITextField+Appearance.swift in Sources */,
58C3A4B222456F1B00340BDB /* AccountInputGroupView.swift in Sources */,
+ F09A297C2A9F8A9B00EA3B6F /* VoucherTextField.swift in Sources */,
58ACF64B26553C3F00ACE4B7 /* SettingsSwitchCell.swift in Sources */,
7A09C98129D99215000C2CAC /* String+FuzzyMatch.swift in Sources */,
58A8EE5E2976DB00009C0F8D /* StorePaymentManagerError+Display.swift in Sources */,
diff --git a/ios/MullvadVPN/Coordinators/AccountCoordinator.swift b/ios/MullvadVPN/Coordinators/AccountCoordinator.swift
index 516ade62a1..b1811001c2 100644
--- a/ios/MullvadVPN/Coordinators/AccountCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/AccountCoordinator.swift
@@ -77,15 +77,19 @@ final class AccountCoordinator: Coordinator, Presentable, Presenting {
}
private func navigateToRedeemVoucher() {
- let coordinator = SettingsRedeemVoucherCoordinator(
+ let coordinator = ProfileVoucherCoordinator(
navigationController: CustomNavigationController(),
- interactor: RedeemVoucherInteractor(tunnelManager: interactor.tunnelManager)
+ interactor: RedeemVoucherInteractor(
+ tunnelManager: interactor.tunnelManager,
+ accountsProxy: interactor.accountsProxy,
+ verifyVoucherAsAccount: false
+ )
)
- coordinator.didFinish = { redeemVoucherCoordinator in
- redeemVoucherCoordinator.dismiss(animated: true)
+ coordinator.didFinish = { coordinator in
+ coordinator.dismiss(animated: true)
}
- coordinator.didCancel = { redeemVoucherCoordinator in
- redeemVoucherCoordinator.dismiss(animated: true)
+ coordinator.didCancel = { coordinator in
+ coordinator.dismiss(animated: true)
}
coordinator.start()
diff --git a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
index ae302ea87d..bcc87d657a 100644
--- a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
@@ -6,6 +6,7 @@
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//
+import Combine
import MullvadREST
import MullvadTypes
import RelayCache
@@ -43,6 +44,9 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
*/
private let secondaryNavigationContainer = RootContainerViewController()
+ /// Posts `preferredAccountNumber` notification when user inputs the account number instead of voucher code
+ private let preferredAccountNumberSubject = PassthroughSubject<String, Never>()
+
private lazy var secondaryRootConfiguration = ModalPresentationConfiguration(
preferredContentSize: UIMetrics.preferredFormSheetContentSize,
modalPresentationStyle: .custom,
@@ -70,6 +74,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
private let apiProxy: REST.APIProxy
private let devicesProxy: REST.DevicesProxy
+ private let accountsProxy: REST.AccountsProxy
private var tunnelObserver: TunnelObserver?
private var appPreferences: AppPreferencesDataSource
@@ -85,6 +90,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
relayCacheTracker: RelayCacheTracker,
apiProxy: REST.APIProxy,
devicesProxy: REST.DevicesProxy,
+ accountsProxy: REST.AccountsProxy,
appPreferences: AppPreferencesDataSource
) {
self.tunnelManager = tunnelManager
@@ -92,6 +98,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
self.relayCacheTracker = relayCacheTracker
self.apiProxy = apiProxy
self.devicesProxy = devicesProxy
+ self.accountsProxy = accountsProxy
self.appPreferences = appPreferences
super.init()
@@ -593,15 +600,21 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
let coordinator = WelcomeCoordinator(
navigationController: horizontalFlowController,
storePaymentManager: storePaymentManager,
- tunnelManager: tunnelManager
+ tunnelManager: tunnelManager,
+ accountsProxy: accountsProxy
)
-
- coordinator.didFinish = { [weak self] _ in
+ coordinator.didFinish = { [weak self] in
guard let self else { return }
appPreferences.isShownOnboarding = true
router.dismiss(.welcome, animated: false)
continueFlow(animated: false)
}
+ coordinator.didLogout = { [weak self] preferredAccountNumber in
+ guard let self else { return }
+ router.dismissAll(.primary, animated: true)
+ continueFlow(animated: true)
+ preferredAccountNumberSubject.send(preferredAccountNumber)
+ }
addChild(coordinator)
coordinator.start(animated: animated)
@@ -634,6 +647,8 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
devicesProxy: devicesProxy
)
+ coordinator.preferredAccountNumberPublisher = preferredAccountNumberSubject.eraseToAnyPublisher()
+
coordinator.didFinish = { [weak self] _ in
self?.continueFlow(animated: true)
}
@@ -682,7 +697,8 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
private func presentAccount(animated: Bool, completion: @escaping (Coordinator) -> Void) {
let accountInteractor = AccountInteractor(
storePaymentManager: storePaymentManager,
- tunnelManager: tunnelManager
+ tunnelManager: tunnelManager,
+ accountsProxy: accountsProxy
)
let coordinator = AccountCoordinator(
diff --git a/ios/MullvadVPN/Coordinators/AccountRedeemingVoucherCoordinator.swift b/ios/MullvadVPN/Coordinators/CreateAccountVoucherCoordinator.swift
index 9998fd2d85..2234af0b03 100644
--- a/ios/MullvadVPN/Coordinators/AccountRedeemingVoucherCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/CreateAccountVoucherCoordinator.swift
@@ -1,5 +1,5 @@
//
-// AccountRedeemingVoucherCoordinator.swift
+// CreateAccountVoucherCoordinator.swift
// MullvadVPN
//
// Created by Mojgan on 2023-07-03.
@@ -10,32 +10,35 @@ import MullvadREST
import Routing
import UIKit
-public class AccountRedeemingVoucherCoordinator: Coordinator, Presentable {
+public class CreateAccountVoucherCoordinator: Coordinator {
private let navigationController: RootContainerViewController
private let viewController: RedeemVoucherViewController
+ private let interactor: RedeemVoucherInteractor
- var didFinish: ((AccountRedeemingVoucherCoordinator) -> Void)?
- var didCancel: ((AccountRedeemingVoucherCoordinator) -> Void)?
-
- public var presentedViewController: UIViewController {
- viewController
- }
+ var didFinish: ((CreateAccountVoucherCoordinator) -> Void)?
+ var didCancel: ((CreateAccountVoucherCoordinator) -> Void)?
+ var didLogout: ((CreateAccountVoucherCoordinator, String) -> Void)?
init(
navigationController: RootContainerViewController,
interactor: RedeemVoucherInteractor
) {
self.navigationController = navigationController
+ self.interactor = interactor
viewController = RedeemVoucherViewController(interactor: interactor)
}
func start() {
+ interactor.didLogout = { [weak self] accountNumber in
+ guard let self else { return }
+ didLogout?(self, accountNumber)
+ }
viewController.delegate = self
navigationController.pushViewController(viewController, animated: true)
}
}
-extension AccountRedeemingVoucherCoordinator: RedeemVoucherViewControllerDelegate {
+extension CreateAccountVoucherCoordinator: RedeemVoucherViewControllerDelegate {
func redeemVoucherDidSucceed(_ controller: RedeemVoucherViewController, with response: REST.SubmitVoucherResponse) {
let coordinator = AddCreditSucceededCoordinator(
purchaseType: .redeemingVoucher,
diff --git a/ios/MullvadVPN/Coordinators/LoginCoordinator.swift b/ios/MullvadVPN/Coordinators/LoginCoordinator.swift
index 444913b059..a85353c410 100644
--- a/ios/MullvadVPN/Coordinators/LoginCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/LoginCoordinator.swift
@@ -6,6 +6,7 @@
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//
+import Combine
import MullvadREST
import MullvadTypes
import Operations
@@ -18,10 +19,13 @@ final class LoginCoordinator: Coordinator, DeviceManagementViewControllerDelegat
private var loginController: LoginViewController?
private var lastLoginAction: LoginAction?
+ private var subscriptions = Set<Combine.AnyCancellable>()
var didFinish: ((LoginCoordinator) -> Void)?
var didCreateAccount: (() -> Void)?
+ var preferredAccountNumberPublisher: AnyPublisher<String, Never>?
+
let navigationController: RootContainerViewController
init(
@@ -41,6 +45,14 @@ final class LoginCoordinator: Coordinator, DeviceManagementViewControllerDelegat
loginController.didFinishLogin = { [weak self] action, error in
self?.didFinishLogin(action: action, error: error) ?? .nothing
}
+
+ preferredAccountNumberPublisher?
+ .compactMap { $0 }
+ .sink(receiveValue: { preferredAccountNumber in
+ interactor.suggestPreferredAccountNumber?(preferredAccountNumber)
+ })
+ .store(in: &subscriptions)
+
interactor.didCreateAccount = self.didCreateAccount
navigationController.pushViewController(loginController, animated: animated)
diff --git a/ios/MullvadVPN/Coordinators/SettingsRedeemVoucherCoordinator.swift b/ios/MullvadVPN/Coordinators/ProfileVoucherCoordinator.swift
index c44d670d63..02aed1dea2 100644
--- a/ios/MullvadVPN/Coordinators/SettingsRedeemVoucherCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/ProfileVoucherCoordinator.swift
@@ -1,5 +1,5 @@
//
-// SettingsRedeemVoucherCoordinator.swift
+// ProfileVoucherCoordinator.swift
// MullvadVPN
//
// Created by Mojgan on 2023-06-13.
@@ -11,11 +11,12 @@ import MullvadREST
import Routing
import UIKit
-final class SettingsRedeemVoucherCoordinator: Coordinator, Presentable {
+final class ProfileVoucherCoordinator: Coordinator, Presentable {
private let navigationController: UINavigationController
private let viewController: RedeemVoucherViewController
- var didFinish: ((SettingsRedeemVoucherCoordinator) -> Void)?
- var didCancel: ((SettingsRedeemVoucherCoordinator) -> Void)?
+
+ var didFinish: ((ProfileVoucherCoordinator) -> Void)?
+ var didCancel: ((ProfileVoucherCoordinator) -> Void)?
init(
navigationController: UINavigationController,
@@ -36,7 +37,7 @@ final class SettingsRedeemVoucherCoordinator: Coordinator, Presentable {
}
}
-extension SettingsRedeemVoucherCoordinator: RedeemVoucherViewControllerDelegate {
+extension ProfileVoucherCoordinator: RedeemVoucherViewControllerDelegate {
func redeemVoucherDidSucceed(
_ controller: RedeemVoucherViewController,
with response: REST.SubmitVoucherResponse
@@ -51,7 +52,7 @@ extension SettingsRedeemVoucherCoordinator: RedeemVoucherViewControllerDelegate
}
}
-extension SettingsRedeemVoucherCoordinator: AddCreditSucceededViewControllerDelegate {
+extension ProfileVoucherCoordinator: AddCreditSucceededViewControllerDelegate {
func addCreditSucceededViewControllerDidFinish(in controller: AddCreditSucceededViewController) {
didFinish?(self)
}
@@ -59,7 +60,7 @@ extension SettingsRedeemVoucherCoordinator: AddCreditSucceededViewControllerDele
func header(in controller: AddCreditSucceededViewController) -> String {
NSLocalizedString(
"REDEEM_VOUCHER_SUCCESS_TITLE",
- tableName: "RedeemVoucher",
+ tableName: "ProfileRedeemVoucher",
value: "Voucher was successfully redeemed.",
comment: ""
)
@@ -68,7 +69,7 @@ extension SettingsRedeemVoucherCoordinator: AddCreditSucceededViewControllerDele
func titleForAction(in controller: AddCreditSucceededViewController) -> String {
NSLocalizedString(
"REDEEM_VOUCHER_DISMISS_BUTTON",
- tableName: "RedeemVoucher",
+ tableName: "ProfileRedeemVoucher",
value: "Got it!",
comment: ""
)
diff --git a/ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift b/ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift
index de341fbce8..7a489478df 100644
--- a/ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift
@@ -17,10 +17,12 @@ final class WelcomeCoordinator: Coordinator, Presentable, Presenting {
private let storePaymentManager: StorePaymentManager
private let tunnelManager: TunnelManager
private let inAppPurchaseInteractor: InAppPurchaseInteractor
+ private let accountsProxy: REST.AccountsProxy
private var viewController: WelcomeViewController?
- var didFinish: ((WelcomeCoordinator) -> Void)?
+ var didFinish: (() -> Void)?
+ var didLogout: ((String) -> Void)?
var presentedViewController: UIViewController {
navigationController
@@ -33,11 +35,13 @@ final class WelcomeCoordinator: Coordinator, Presentable, Presenting {
init(
navigationController: RootContainerViewController,
storePaymentManager: StorePaymentManager,
- tunnelManager: TunnelManager
+ tunnelManager: TunnelManager,
+ accountsProxy: REST.AccountsProxy
) {
self.navigationController = navigationController
self.storePaymentManager = storePaymentManager
self.tunnelManager = tunnelManager
+ self.accountsProxy = accountsProxy
self.inAppPurchaseInteractor = InAppPurchaseInteractor(storePaymentManager: storePaymentManager)
}
@@ -52,8 +56,7 @@ final class WelcomeCoordinator: Coordinator, Presentable, Presenting {
let coordinator = SetupAccountCompletedCoordinator(navigationController: navigationController)
coordinator.didFinish = { [weak self] coordinator in
coordinator.removeFromParent()
- guard let self else { return }
- didFinish?(self)
+ self?.didFinish?()
}
addChild(coordinator)
coordinator.start(animated: true)
@@ -127,7 +130,7 @@ extension WelcomeCoordinator: WelcomeViewControllerDelegate {
coordinator.didFinish = { [weak self] coordinator in
guard let self else { return }
coordinator.removeFromParent()
- didFinish?(self)
+ didFinish?()
}
coordinator.didCancel = { coordinator in
@@ -140,9 +143,13 @@ extension WelcomeCoordinator: WelcomeViewControllerDelegate {
}
func didRequestToRedeemVoucher(controller: WelcomeViewController) {
- let coordinator = AccountRedeemingVoucherCoordinator(
+ let coordinator = CreateAccountVoucherCoordinator(
navigationController: navigationController,
- interactor: RedeemVoucherInteractor(tunnelManager: tunnelManager)
+ interactor: RedeemVoucherInteractor(
+ tunnelManager: tunnelManager,
+ accountsProxy: accountsProxy,
+ verifyVoucherAsAccount: true
+ )
)
coordinator.didCancel = { [weak self] coordinator in
@@ -152,9 +159,15 @@ extension WelcomeCoordinator: WelcomeViewControllerDelegate {
}
coordinator.didFinish = { [weak self] coordinator in
+ guard let self else { return }
coordinator.removeFromParent()
+ didFinish?()
+ }
+
+ coordinator.didLogout = { [weak self] coordinator, accountNumber in
guard let self else { return }
- didFinish?(self)
+ coordinator.removeFromParent()
+ didLogout?(accountNumber)
}
addChild(coordinator)
diff --git a/ios/MullvadVPN/SceneDelegate.swift b/ios/MullvadVPN/SceneDelegate.swift
index c1e9b91f9c..b8641b6845 100644
--- a/ios/MullvadVPN/SceneDelegate.swift
+++ b/ios/MullvadVPN/SceneDelegate.swift
@@ -67,6 +67,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, SettingsMigrationUIHand
relayCacheTracker: appDelegate.relayCacheTracker,
apiProxy: appDelegate.apiProxy,
devicesProxy: appDelegate.devicesProxy,
+ accountsProxy: appDelegate.accountsProxy,
appPreferences: AppPreferences()
)
diff --git a/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift b/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift
index 956703bff0..8ad2fb25fe 100644
--- a/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift
+++ b/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift
@@ -15,6 +15,7 @@ import StoreKit
final class AccountInteractor {
private let storePaymentManager: StorePaymentManager
let tunnelManager: TunnelManager
+ let accountsProxy: REST.AccountsProxy
var didReceivePaymentEvent: ((StorePaymentEvent) -> Void)?
var didReceiveDeviceState: ((DeviceState) -> Void)?
@@ -22,9 +23,14 @@ final class AccountInteractor {
private var tunnelObserver: TunnelObserver?
private var paymentObserver: StorePaymentObserver?
- init(storePaymentManager: StorePaymentManager, tunnelManager: TunnelManager) {
+ init(
+ storePaymentManager: StorePaymentManager,
+ tunnelManager: TunnelManager,
+ accountsProxy: REST.AccountsProxy
+ ) {
self.storePaymentManager = storePaymentManager
self.tunnelManager = tunnelManager
+ self.accountsProxy = accountsProxy
let tunnelObserver =
TunnelBlockObserver(didUpdateDeviceState: { [weak self] _, deviceState, _ in
diff --git a/ios/MullvadVPN/View controllers/Login/LoginInteractor.swift b/ios/MullvadVPN/View controllers/Login/LoginInteractor.swift
index 6db0a04b93..07a4ff1c4e 100644
--- a/ios/MullvadVPN/View controllers/Login/LoginInteractor.swift
+++ b/ios/MullvadVPN/View controllers/Login/LoginInteractor.swift
@@ -14,6 +14,7 @@ final class LoginInteractor {
private let logger = Logger(label: "LoginInteractor")
private var tunnelObserver: TunnelObserver?
var didCreateAccount: (() -> Void)?
+ var suggestPreferredAccountNumber: ((String) -> Void)?
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 94e51afb55..a2af008753 100644
--- a/ios/MullvadVPN/View controllers/Login/LoginViewController.swift
+++ b/ios/MullvadVPN/View controllers/Login/LoginViewController.swift
@@ -140,6 +140,10 @@ class LoginViewController: UIViewController, RootContainment {
self?.attemptLogin()
}
+ interactor.suggestPreferredAccountNumber = { [weak self] value in
+ self?.contentView.accountInputGroup.setAccount(value)
+ }
+
contentView.accountInputGroup.setOnReturnKey { [weak self] _ in
guard let self else { return true }
diff --git a/ios/MullvadVPN/View controllers/RedeemVoucher/LogoutDialogueView.swift b/ios/MullvadVPN/View controllers/RedeemVoucher/LogoutDialogueView.swift
new file mode 100644
index 0000000000..a6aee49cb5
--- /dev/null
+++ b/ios/MullvadVPN/View controllers/RedeemVoucher/LogoutDialogueView.swift
@@ -0,0 +1,149 @@
+//
+// LogoutDialogueView.swift
+// MullvadVPN
+//
+// Created by Mojgan on 2023-08-29.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import UIKit
+
+class LogoutDialogueView: UIView {
+ private let containerView: UIView = {
+ let view = UIView()
+ view.backgroundColor = .secondaryColor
+ view.layer.cornerRadius = 11
+ view.directionalLayoutMargins = UIMetrics.CustomAlert.containerMargins
+ view.clipsToBounds = true
+ return view
+ }()
+
+ private let messageLabel: UILabel = {
+ let label = UILabel()
+ label.font = .preferredFont(forTextStyle: .callout, weight: .light)
+ label.numberOfLines = .zero
+ label.lineBreakMode = .byWordWrapping
+ label.textColor = .white
+ label.text = NSLocalizedString(
+ "ACCOUNT_NUMBER_AS_VOUCHER_INPUT_ERROR_BODY",
+ tableName: "CreateAccountRedeemingVoucher",
+ value: """
+ It looks like you have entered a Mullvad account number instead of a voucher code. \
+ Do you want to log in to an existing account?
+ If so, click log out below to log in with the other account number.
+ """,
+ comment: ""
+ )
+ return label
+ }()
+
+ private let logoutButton: AppButton = {
+ let button = AppButton(style: .danger)
+ button.setTitle(NSLocalizedString(
+ "LOGOUT_BUTTON_TITLE",
+ tableName: "CreateAccountRedeemingVoucher",
+ value: "Log out",
+ comment: ""
+ ), for: .normal)
+ return button
+ }()
+
+ private var showConstraint: NSLayoutConstraint?
+ private var hideConstraint: NSLayoutConstraint?
+ private var didRequestToLogOut: (LogoutDialogueView) -> Void
+
+ var isLoading = true {
+ didSet {
+ logoutButton.isEnabled = !isLoading
+ }
+ }
+
+ override var isHidden: Bool {
+ willSet {
+ if newValue == true {
+ fadeOut()
+ } else {
+ fadeIn()
+ }
+ }
+ }
+
+ init(didRequestToLogOut: @escaping (LogoutDialogueView) -> Void) {
+ self.didRequestToLogOut = didRequestToLogOut
+ super.init(frame: .zero)
+ setupAppearance()
+ configureUI()
+ addActions()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func setupAppearance() {
+ containerView.layer.cornerRadius = 11
+ containerView.backgroundColor = .primaryColor
+ }
+
+ private func configureUI() {
+ addConstrainedSubviews([containerView]) {
+ containerView.pinEdgesToSuperview(.all().excluding(.bottom))
+ }
+
+ containerView.addConstrainedSubviews([messageLabel, logoutButton]) {
+ messageLabel.pinEdgesToSuperviewMargins(.all().excluding(.bottom))
+ logoutButton.pinEdgesToSuperviewMargins(.all().excluding(.top))
+ logoutButton.topAnchor.constraint(
+ equalTo: messageLabel.bottomAnchor,
+ constant: UIMetrics.padding16
+ ).withPriority(.defaultHigh)
+ }
+
+ showConstraint = containerView.bottomAnchor.constraint(equalTo: bottomAnchor)
+ hideConstraint = containerView.bottomAnchor.constraint(equalTo: topAnchor)
+ hideConstraint?.isActive = true
+ }
+
+ private func addActions() {
+ logoutButton.addTarget(self, action: #selector(logout), for: .touchUpInside)
+ }
+
+ @objc private func logout() {
+ didRequestToLogOut(self)
+ }
+
+ private func fadeIn() {
+ guard hideConstraint?.isActive == true else { return }
+ showConstraint?.isActive = true
+ hideConstraint?.isActive = false
+ animateWith(animations: {
+ self.containerView.alpha = 1.0
+ }, duration: 0.3, delay: 0.2)
+ }
+
+ private func fadeOut() {
+ guard showConstraint?.isActive == true else { return }
+ showConstraint?.isActive = false
+ hideConstraint?.isActive = true
+ animateWith(animations: {
+ self.containerView.alpha = 0.0
+ }, duration: 0.0, delay: 0.0)
+ }
+
+ private func animateWith(
+ animations: @escaping () -> Void,
+ duration: TimeInterval,
+ delay: TimeInterval
+ ) {
+ UIView.animate(
+ withDuration: duration,
+ delay: delay,
+ options: .curveEaseInOut,
+ animations: {
+ animations()
+ self.layoutIfNeeded()
+ },
+ completion: nil
+ )
+ }
+}
diff --git a/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherContentView.swift b/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherContentView.swift
index a24942bf3a..650ced232b 100644
--- a/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherContentView.swift
+++ b/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherContentView.swift
@@ -14,6 +14,7 @@ enum RedeemVoucherState {
case success
case verifying
case failure(Error)
+ case logout
}
final class RedeemVoucherContentView: UIView {
@@ -79,6 +80,13 @@ final class RedeemVoucherContentView: UIView {
return label
}()
+ private lazy var logoutViewForAccountNumberIsEntered: LogoutDialogueView = {
+ LogoutDialogueView { verifiedAccountView in
+ verifiedAccountView.isLoading = true
+ self.logoutAction?()
+ }
+ }()
+
private let redeemButton: AppButton = {
let button = AppButton(style: .success)
button.setTitle(NSLocalizedString(
@@ -114,12 +122,14 @@ final class RedeemVoucherContentView: UIView {
titleLabel,
textField,
statusStack,
+ logoutViewForAccountNumberIsEntered,
])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.setCustomSpacing(UIMetrics.padding16, after: titleLabel)
stackView.setCustomSpacing(UIMetrics.padding8, after: textField)
stackView.setCustomSpacing(UIMetrics.padding16, after: statusLabel)
+ stackView.setCustomSpacing(UIMetrics.padding24, after: statusStack)
stackView.setContentHuggingPriority(.defaultLow, for: .vertical)
return stackView
}()
@@ -147,6 +157,13 @@ final class RedeemVoucherContentView: UIView {
value: "Verifying voucher...",
comment: ""
)
+ case .logout:
+ return NSLocalizedString(
+ "REDEEM_VOUCHER_STATUS_WAITING",
+ tableName: "RedeemVoucher",
+ value: "Logging out...",
+ comment: ""
+ )
default: return ""
}
}
@@ -155,7 +172,7 @@ final class RedeemVoucherContentView: UIView {
switch state {
case .initial, .failure:
return true
- case .success, .verifying:
+ case .success, .verifying, .logout:
return false
}
}
@@ -171,7 +188,7 @@ final class RedeemVoucherContentView: UIView {
private var isLoading: Bool {
switch state {
- case .verifying:
+ case .verifying, .logout:
return true
default:
return false
@@ -185,6 +202,7 @@ final class RedeemVoucherContentView: UIView {
var redeemAction: ((String) -> Void)?
var cancelAction: (() -> Void)?
+ var logoutAction: (() -> Void)?
var state: RedeemVoucherState = .initial {
didSet {
@@ -206,6 +224,12 @@ final class RedeemVoucherContentView: UIView {
}
}
+ var isLogoutDialogHidden = true {
+ didSet {
+ logoutViewForAccountNumberIsEntered.isHidden = isLogoutDialogHidden
+ }
+ }
+
init() {
super.init(frame: .zero)
commonInit()
@@ -276,6 +300,7 @@ final class RedeemVoucherContentView: UIView {
redeemButton.isEnabled = isRedeemButtonEnabled && textField.isVoucherLengthSatisfied
statusLabel.text = text
statusLabel.textColor = textColor
+ logoutViewForAccountNumberIsEntered.isLoading = isLoading
}
private func addObservers() {
@@ -292,13 +317,17 @@ final class RedeemVoucherContentView: UIView {
}
@objc private func redeemButtonTapped(_ sender: AppButton) {
- guard let code = textField.text, !code.isEmpty else {
+ let code = textField.parsedToken
+ guard !code.isEmpty else {
return
}
redeemAction?(code)
}
@objc private func textDidChange() {
+ if textField.parsedToken.isEmpty {
+ isLogoutDialogHidden = true
+ }
updateUI()
}
diff --git a/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherInteractor.swift b/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherInteractor.swift
index 1fa5a2c6da..fa76391227 100644
--- a/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherInteractor.swift
+++ b/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherInteractor.swift
@@ -2,7 +2,7 @@
// RedeemVoucherInteractor.swift
// MullvadVPN
//
-// Created by Mojgan on 2023-05-24.
+// Created by Mojgan on 2023-08-30.
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//
@@ -12,15 +12,71 @@ import MullvadTypes
final class RedeemVoucherInteractor {
private let tunnelManager: TunnelManager
+ private let accountsProxy: REST.AccountsProxy
+ private let shouldVerifyVoucherAsAccount: Bool
- init(tunnelManager: TunnelManager) {
+ private var tasks: [Cancellable] = []
+ private var preferredAccountNumber: String?
+
+ var showLogoutDialog: (() -> Void)?
+ var didLogout: ((String) -> Void)?
+
+ init(
+ tunnelManager: TunnelManager,
+ accountsProxy: REST.AccountsProxy,
+ verifyVoucherAsAccount: Bool
+ ) {
self.tunnelManager = tunnelManager
+ self.accountsProxy = accountsProxy
+ self.shouldVerifyVoucherAsAccount = verifyVoucherAsAccount
}
func redeemVoucher(
code: String,
completion: @escaping ((Result<REST.SubmitVoucherResponse, Error>) -> Void)
- ) -> Cancellable {
- tunnelManager.redeemVoucher(code, completion: completion)
+ ) {
+ tasks.append(tunnelManager.redeemVoucher(code) { [weak self] result in
+ guard let self else { return }
+ completion(result)
+ guard shouldVerifyVoucherAsAccount,
+ result.error?.isInvalidVoucher ?? false else {
+ return
+ }
+ verifyVoucherAsAccount(code: code)
+ })
+ }
+
+ func logout(completionHandler: @escaping () -> Void) {
+ preferredAccountNumber.flatMap { accountNumber in
+ tunnelManager.unsetAccount { [weak self] in
+ guard let self else {
+ return
+ }
+ completionHandler()
+ didLogout?(accountNumber)
+ }
+ }
+ }
+
+ func cancelAll() {
+ tasks.forEach { $0.cancel() }
+ }
+
+ private func verifyVoucherAsAccount(code: String) {
+ let executer = accountsProxy.getAccountData(accountNumber: code)
+ tasks.append(executer.execute { [weak self] result in
+ guard let self,
+ case .success = result else {
+ return
+ }
+ showLogoutDialog?()
+ preferredAccountNumber = code
+ })
+ }
+}
+
+fileprivate extension Error {
+ var isInvalidVoucher: Bool {
+ (self as? REST.Error)?.compareErrorCode(.invalidVoucher) ?? false
}
}
diff --git a/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherViewController.swift b/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherViewController.swift
index cf96e55ae8..f7921f2c6e 100644
--- a/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherViewController.swift
+++ b/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherViewController.swift
@@ -20,14 +20,13 @@ protocol RedeemVoucherViewControllerDelegate: AnyObject {
class RedeemVoucherViewController: UIViewController, UINavigationControllerDelegate, RootContainment {
private let contentView = RedeemVoucherContentView()
- private var voucherTask: Cancellable?
- private var interactor: RedeemVoucherInteractor?
+ private var interactor: RedeemVoucherInteractor
weak var delegate: RedeemVoucherViewControllerDelegate?
init(interactor: RedeemVoucherInteractor) {
- super.init(nibName: nil, bundle: nil)
self.interactor = interactor
+ super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
@@ -87,6 +86,14 @@ class RedeemVoucherViewController: UIViewController, UINavigationControllerDeleg
contentView.cancelAction = { [weak self] in
self?.cancel()
}
+
+ contentView.logoutAction = { [weak self] in
+ self?.logout()
+ }
+
+ interactor.showLogoutDialog = { [weak self] in
+ self?.contentView.isLogoutDialogHidden = false
+ }
}
private func configureUI() {
@@ -97,12 +104,12 @@ class RedeemVoucherViewController: UIViewController, UINavigationControllerDeleg
private func submit(code: String) {
contentView.state = .verifying
- voucherTask = interactor?.redeemVoucher(code: code, completion: { [weak self] result in
+ contentView.isEditing = false
+ interactor.redeemVoucher(code: code, completion: { [weak self] result in
guard let self else { return }
switch result {
case let .success(value):
contentView.state = .success
- contentView.isEditing = false
delegate?.redeemVoucherDidSucceed(self, with: value)
case let .failure(error):
contentView.state = .failure(error)
@@ -113,8 +120,18 @@ class RedeemVoucherViewController: UIViewController, UINavigationControllerDeleg
private func cancel() {
contentView.isEditing = false
- voucherTask?.cancel()
+ interactor.cancelAll()
delegate?.redeemVoucherDidCancel(self)
}
+
+ private func logout() {
+ contentView.isEditing = false
+
+ contentView.state = .logout
+
+ interactor.logout { [weak self] in
+ self?.contentView.state = .initial
+ }
+ }
}
diff --git a/ios/MullvadVPN/View controllers/RedeemVoucher/VoucherTextField.swift b/ios/MullvadVPN/View controllers/RedeemVoucher/VoucherTextField.swift
index 89d72228ac..1475fc9397 100644
--- a/ios/MullvadVPN/View controllers/RedeemVoucher/VoucherTextField.swift
+++ b/ios/MullvadVPN/View controllers/RedeemVoucher/VoucherTextField.swift
@@ -23,6 +23,10 @@ class VoucherTextField: CustomTextField, UITextFieldDelegate {
return maxGroups * groupSize + (maxGroups - 1)
}
+ var parsedToken: String {
+ inputFormatter.string
+ }
+
var isVoucherLengthSatisfied: Bool {
let length = text?.count ?? 0
return length >= voucherLength
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsInteractorFactory.swift b/ios/MullvadVPN/View controllers/Settings/SettingsInteractorFactory.swift
index eee79da6a7..2af17ec147 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsInteractorFactory.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsInteractorFactory.swift
@@ -28,13 +28,6 @@ final class SettingsInteractorFactory {
self.relayCacheTracker = relayCacheTracker
}
- func makeAccountInteractor() -> AccountInteractor {
- AccountInteractor(
- storePaymentManager: storePaymentManager,
- tunnelManager: tunnelManager
- )
- }
-
func makePreferencesInteractor() -> PreferencesInteractor {
PreferencesInteractor(tunnelManager: tunnelManager, relayCacheTracker: relayCacheTracker)
}