From 4bed2f612be396cbaaedb3e1a438d7ed07f87e92 Mon Sep 17 00:00:00 2001 From: dkadrikj Date: Thu, 3 Feb 2022 10:42:25 +0100 Subject: [PATCH] Add localized message to the all API IO exceptions saying clearly to the user that the server is not available. --- auth/presentation/api/auth-presentation.api | 66 ++++++++++--------- .../confirmpass/ConfirmPasswordDialog.kt | 3 +- .../presentation/ui/ChooseAddressActivity.kt | 3 +- .../presentation/ui/CreateAddressActivity.kt | 3 +- .../auth/presentation/ui/LoginActivity.kt | 3 +- .../presentation/ui/SecondFactorActivity.kt | 4 +- .../presentation/ui/TwoPassModeActivity.kt | 4 +- .../ui/signup/ChooseUsernameFragment.kt | 3 +- .../ExternalValidationTokenCodeFragment.kt | 5 +- .../ui/signup/RecoveryMethodFragment.kt | 3 +- .../presentation/ui/signup/SignupActivity.kt | 3 +- .../viewmodel/ChooseAddressViewModel.kt | 6 +- .../ConfirmPasswordDialogViewModel.kt | 9 +-- .../viewmodel/CreateAddressViewModel.kt | 4 +- .../presentation/viewmodel/LoginViewModel.kt | 4 +- .../viewmodel/SecondFactorViewModel.kt | 4 +- .../viewmodel/TwoPassModeViewModel.kt | 4 +- .../signup/ChooseUsernameViewModel.kt | 6 +- .../ConfirmPasswordDialogViewModelTest.kt | 14 ++-- .../viewmodel/CreateAddressViewModelTest.kt | 5 +- .../viewmodel/LoginViewModelTest.kt | 11 ++-- .../viewmodel/SecondFactorViewModelTest.kt | 5 -- .../viewmodel/TwoPassModeViewModelTest.kt | 3 - .../signup/ChooseUsernameViewModelTest.kt | 7 +- .../viewmodel/signup/SignupViewModelTest.kt | 1 - .../HumanVerificationRepositoryImplTest.kt | 7 ++ ....kt => VerificationResponseGeneralTest.kt} | 2 +- .../presentation/api/payment-presentation.api | 39 +++++------ .../presentation/ui/BillingActivity.kt | 3 +- .../presentation/ui/PaymentOptionsActivity.kt | 5 +- .../ui/PaymentTokenApprovalActivity.kt | 4 +- .../viewmodel/BillingCommonViewModel.kt | 6 +- .../viewmodel/PaymentOptionsViewModel.kt | 4 +- .../PaymentTokenApprovalViewModel.kt | 6 +- .../viewmodel/BillingViewModelTest.kt | 5 +- .../viewmodel/PaymentOptionsViewModelTest.kt | 5 +- .../PaymentTokenApprovalViewModelTest.kt | 5 +- .../presentation/ui/SignupPlansFragment.kt | 3 +- .../presentation/ui/UpgradePlansFragment.kt | 6 +- .../viewmodel/BasePlansViewModel.kt | 4 +- .../viewmodel/SignupPlansViewModel.kt | 2 +- .../viewmodel/UpgradePlansViewModel.kt | 8 +-- presentation/api/presentation.api | 4 ++ presentation/build.gradle.kts | 1 + .../core/presentation/utils/ErrorUtils.kt | 51 ++++++++++++++ presentation/src/main/res/values/strings.xml | 1 + .../api/user-settings-presentation.api | 27 ++++---- .../ui/PasswordManagementFragment.kt | 3 +- .../ui/UpdateRecoveryEmailFragment.kt | 3 +- .../viewmodel/PasswordManagementViewModel.kt | 8 +-- .../viewmodel/UpdateRecoveryEmailViewModel.kt | 8 +-- .../UpdateRecoveryEmailViewModelTest.kt | 9 +-- 52 files changed, 243 insertions(+), 169 deletions(-) rename human-verification/presentation/src/test/kotlin/ui/{VerificationResponseMessageTest.kt => VerificationResponseGeneralTest.kt} (99%) create mode 100644 presentation/src/main/kotlin/me/proton/core/presentation/utils/ErrorUtils.kt diff --git a/auth/presentation/api/auth-presentation.api b/auth/presentation/api/auth-presentation.api index d60a19074..33c72633c 100644 --- a/auth/presentation/api/auth-presentation.api +++ b/auth/presentation/api/auth-presentation.api @@ -1542,12 +1542,12 @@ public final class me/proton/core/auth/presentation/viewmodel/ChooseAddressViewM } public final class me/proton/core/auth/presentation/viewmodel/ChooseAddressViewModel$State$Error$Message : me/proton/core/auth/presentation/viewmodel/ChooseAddressViewModel$State$Error { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lme/proton/core/auth/presentation/viewmodel/ChooseAddressViewModel$State$Error$Message; - public static synthetic fun copy$default (Lme/proton/core/auth/presentation/viewmodel/ChooseAddressViewModel$State$Error$Message;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/auth/presentation/viewmodel/ChooseAddressViewModel$State$Error$Message; + public fun (Ljava/lang/Throwable;)V + public final fun component1 ()Ljava/lang/Throwable; + public final fun copy (Ljava/lang/Throwable;)Lme/proton/core/auth/presentation/viewmodel/ChooseAddressViewModel$State$Error$Message; + public static synthetic fun copy$default (Lme/proton/core/auth/presentation/viewmodel/ChooseAddressViewModel$State$Error$Message;Ljava/lang/Throwable;ILjava/lang/Object;)Lme/proton/core/auth/presentation/viewmodel/ChooseAddressViewModel$State$Error$Message; public fun equals (Ljava/lang/Object;)Z - public final fun getMessage ()Ljava/lang/String; + public final fun getError ()Ljava/lang/Throwable; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1618,19 +1618,23 @@ public abstract class me/proton/core/auth/presentation/viewmodel/ConfirmPassword public abstract class me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error : me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State { } +public final class me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error$General : me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error { + public fun (Ljava/lang/Throwable;)V + public final fun component1 ()Ljava/lang/Throwable; + public final fun copy (Ljava/lang/Throwable;)Lme/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error$General; + public static synthetic fun copy$default (Lme/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error$General;Ljava/lang/Throwable;ILjava/lang/Object;)Lme/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error$General; + public fun equals (Ljava/lang/Object;)Z + public final fun getError ()Ljava/lang/Throwable; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error$InvalidAccount : me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error { public static final field INSTANCE Lme/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error$InvalidAccount; } -public final class me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error$Message : me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lme/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error$Message; - public static synthetic fun copy$default (Lme/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error$Message;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error$Message; - public fun equals (Ljava/lang/Object;)Z - public final fun getMessage ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; +public final class me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error$Unknown : me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error { + public static final field INSTANCE Lme/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Error$Unknown; } public final class me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State$Idle : me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel$State { @@ -1714,13 +1718,13 @@ public final class me/proton/core/auth/presentation/viewmodel/CreateAddressViewM public fun toString ()Ljava/lang/String; } -public final class me/proton/core/auth/presentation/viewmodel/CreateAddressViewModel$State$ErrorMessage : me/proton/core/auth/presentation/viewmodel/CreateAddressViewModel$State { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lme/proton/core/auth/presentation/viewmodel/CreateAddressViewModel$State$ErrorMessage; - public static synthetic fun copy$default (Lme/proton/core/auth/presentation/viewmodel/CreateAddressViewModel$State$ErrorMessage;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/auth/presentation/viewmodel/CreateAddressViewModel$State$ErrorMessage; +public final class me/proton/core/auth/presentation/viewmodel/CreateAddressViewModel$State$Error : me/proton/core/auth/presentation/viewmodel/CreateAddressViewModel$State { + public fun (Ljava/lang/Throwable;)V + public final fun component1 ()Ljava/lang/Throwable; + public final fun copy (Ljava/lang/Throwable;)Lme/proton/core/auth/presentation/viewmodel/CreateAddressViewModel$State$Error; + public static synthetic fun copy$default (Lme/proton/core/auth/presentation/viewmodel/CreateAddressViewModel$State$Error;Ljava/lang/Throwable;ILjava/lang/Object;)Lme/proton/core/auth/presentation/viewmodel/CreateAddressViewModel$State$Error; public fun equals (Ljava/lang/Object;)Z - public final fun getMessage ()Ljava/lang/String; + public final fun getError ()Ljava/lang/Throwable; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1818,12 +1822,12 @@ public abstract class me/proton/core/auth/presentation/viewmodel/SecondFactorVie } public final class me/proton/core/auth/presentation/viewmodel/SecondFactorViewModel$State$Error$Message : me/proton/core/auth/presentation/viewmodel/SecondFactorViewModel$State$Error { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lme/proton/core/auth/presentation/viewmodel/SecondFactorViewModel$State$Error$Message; - public static synthetic fun copy$default (Lme/proton/core/auth/presentation/viewmodel/SecondFactorViewModel$State$Error$Message;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/auth/presentation/viewmodel/SecondFactorViewModel$State$Error$Message; + public fun (Ljava/lang/Throwable;)V + public final fun component1 ()Ljava/lang/Throwable; + public final fun copy (Ljava/lang/Throwable;)Lme/proton/core/auth/presentation/viewmodel/SecondFactorViewModel$State$Error$Message; + public static synthetic fun copy$default (Lme/proton/core/auth/presentation/viewmodel/SecondFactorViewModel$State$Error$Message;Ljava/lang/Throwable;ILjava/lang/Object;)Lme/proton/core/auth/presentation/viewmodel/SecondFactorViewModel$State$Error$Message; public fun equals (Ljava/lang/Object;)Z - public final fun getMessage ()Ljava/lang/String; + public final fun getError ()Ljava/lang/Throwable; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1895,13 +1899,13 @@ public final class me/proton/core/auth/presentation/viewmodel/TwoPassModeViewMod public fun toString ()Ljava/lang/String; } -public final class me/proton/core/auth/presentation/viewmodel/TwoPassModeViewModel$State$ErrorMessage : me/proton/core/auth/presentation/viewmodel/TwoPassModeViewModel$State { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lme/proton/core/auth/presentation/viewmodel/TwoPassModeViewModel$State$ErrorMessage; - public static synthetic fun copy$default (Lme/proton/core/auth/presentation/viewmodel/TwoPassModeViewModel$State$ErrorMessage;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/auth/presentation/viewmodel/TwoPassModeViewModel$State$ErrorMessage; +public final class me/proton/core/auth/presentation/viewmodel/TwoPassModeViewModel$State$Error : me/proton/core/auth/presentation/viewmodel/TwoPassModeViewModel$State { + public fun (Ljava/lang/Throwable;)V + public final fun component1 ()Ljava/lang/Throwable; + public final fun copy (Ljava/lang/Throwable;)Lme/proton/core/auth/presentation/viewmodel/TwoPassModeViewModel$State$Error; + public static synthetic fun copy$default (Lme/proton/core/auth/presentation/viewmodel/TwoPassModeViewModel$State$Error;Ljava/lang/Throwable;ILjava/lang/Object;)Lme/proton/core/auth/presentation/viewmodel/TwoPassModeViewModel$State$Error; public fun equals (Ljava/lang/Object;)Z - public final fun getMessage ()Ljava/lang/String; + public final fun getError ()Ljava/lang/Throwable; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/alert/confirmpass/ConfirmPasswordDialog.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/alert/confirmpass/ConfirmPasswordDialog.kt index 112c43776..f91c8b850 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/alert/confirmpass/ConfirmPasswordDialog.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/alert/confirmpass/ConfirmPasswordDialog.kt @@ -105,7 +105,8 @@ class ConfirmPasswordDialog : DialogFragment() { is ConfirmPasswordDialogViewModel.State.ProcessingSecondFactor -> { // noop } - is ConfirmPasswordDialogViewModel.State.Error.Message -> { + is ConfirmPasswordDialogViewModel.State.Error.Unknown, + is ConfirmPasswordDialogViewModel.State.Error.General -> { setResultAndDismiss(MissingScopeState.ScopeObtainFailed) binding.enterButton.setIdle() } diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/ChooseAddressActivity.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/ChooseAddressActivity.kt index f979bce85..0bc665622 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/ChooseAddressActivity.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/ChooseAddressActivity.kt @@ -34,6 +34,7 @@ import me.proton.core.auth.presentation.entity.CreateAddressInput import me.proton.core.auth.presentation.entity.CreateAddressResult import me.proton.core.auth.presentation.viewmodel.ChooseAddressViewModel import me.proton.core.domain.entity.UserId +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.presentation.utils.hideKeyboard import me.proton.core.presentation.utils.onClick import me.proton.core.presentation.utils.onFailure @@ -86,7 +87,7 @@ class ChooseAddressActivity : AuthActivity(Activit is ChooseAddressViewModel.State.Processing -> showLoading(true) is ChooseAddressViewModel.State.Success -> onUsernameAvailable(it.username, it.domain) is ChooseAddressViewModel.State.Data -> onData(it.username, it.domains) - is ChooseAddressViewModel.State.Error.Message -> showError(it.message) + is ChooseAddressViewModel.State.Error.Message -> showError(it.error.getLocalizedMessage(resources)) is ChooseAddressViewModel.State.Error.DomainsNotAvailable -> showError(getString(R.string.auth_create_address_error_no_available_domain)) is ChooseAddressViewModel.State.Error.UsernameNotAvailable -> diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/CreateAddressActivity.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/CreateAddressActivity.kt index dd3f27da8..f14e40bfc 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/CreateAddressActivity.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/CreateAddressActivity.kt @@ -34,6 +34,7 @@ import me.proton.core.auth.presentation.entity.CreateAddressInput import me.proton.core.auth.presentation.entity.CreateAddressResult import me.proton.core.auth.presentation.viewmodel.CreateAddressViewModel import me.proton.core.domain.entity.UserId +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.presentation.utils.onClick import me.proton.core.util.kotlin.exhaustive @@ -77,7 +78,7 @@ class CreateAddressActivity : AuthActivity(Activit is CreateAddressViewModel.State.Idle -> showLoading(false) is CreateAddressViewModel.State.Processing -> showLoading(true) is CreateAddressViewModel.State.AccountSetupResult -> onAccountSetupResult(it.result) - is CreateAddressViewModel.State.ErrorMessage -> showError(it.message) + is CreateAddressViewModel.State.Error -> showError(it.error.getLocalizedMessage(resources)) }.exhaustive }.launchIn(lifecycleScope) } diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/LoginActivity.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/LoginActivity.kt index 81d7bd790..8dc296af9 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/LoginActivity.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/LoginActivity.kt @@ -37,6 +37,7 @@ import me.proton.core.auth.presentation.entity.LoginResult import me.proton.core.auth.presentation.entity.NextStep import me.proton.core.auth.presentation.viewmodel.LoginViewModel import me.proton.core.domain.entity.UserId +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.presentation.utils.hideKeyboard import me.proton.core.presentation.utils.onClick import me.proton.core.presentation.utils.onFailure @@ -106,7 +107,7 @@ class LoginActivity : AuthActivity(ActivityLoginBinding::i is LoginViewModel.State.Idle -> showLoading(false) is LoginViewModel.State.Processing -> showLoading(true) is LoginViewModel.State.AccountSetupResult -> onAccountSetupResult(it.result) - is LoginViewModel.State.ErrorMessage -> onError(true, it.message, it.isPotentialBlocking) + is LoginViewModel.State.Error -> onError(true, it.error.getLocalizedMessage(resources), it.isPotentialBlocking) }.exhaustive }.launchIn(lifecycleScope) } diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/SecondFactorActivity.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/SecondFactorActivity.kt index e7addfa08..0a04c0113 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/SecondFactorActivity.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/SecondFactorActivity.kt @@ -36,6 +36,7 @@ import me.proton.core.auth.presentation.entity.SecondFactorResult import me.proton.core.auth.presentation.viewmodel.SecondFactorViewModel import me.proton.core.domain.entity.UserId import me.proton.core.presentation.utils.errorToast +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.presentation.utils.hideKeyboard import me.proton.core.presentation.utils.onClick import me.proton.core.presentation.utils.onFailure @@ -88,7 +89,8 @@ class SecondFactorActivity : AuthActivity(Activity2faBinding is SecondFactorViewModel.State.Idle -> showLoading(false) is SecondFactorViewModel.State.Processing -> showLoading(true) is SecondFactorViewModel.State.AccountSetupResult -> onAccountSetupResult(it.result) - is SecondFactorViewModel.State.Error.Message -> onError(false, it.message) + is SecondFactorViewModel.State.Error.Message -> + onError(false, it.error.getLocalizedMessage(resources)) is SecondFactorViewModel.State.Error.Unrecoverable -> onUnrecoverableError(it.message) }.exhaustive }.launchIn(lifecycleScope) diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/TwoPassModeActivity.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/TwoPassModeActivity.kt index 2fd90623b..1da71fd50 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/TwoPassModeActivity.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/TwoPassModeActivity.kt @@ -33,6 +33,7 @@ import me.proton.core.auth.presentation.entity.TwoPassModeInput import me.proton.core.auth.presentation.entity.TwoPassModeResult import me.proton.core.auth.presentation.viewmodel.TwoPassModeViewModel import me.proton.core.domain.entity.UserId +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.presentation.utils.hideKeyboard import me.proton.core.presentation.utils.onClick import me.proton.core.presentation.utils.onFailure @@ -84,7 +85,8 @@ class TwoPassModeActivity : AuthActivity(ActivityMa is TwoPassModeViewModel.State.Idle -> showLoading(false) is TwoPassModeViewModel.State.Processing -> showLoading(true) is TwoPassModeViewModel.State.AccountSetupResult -> onAccountSetupResult(it.result) - is TwoPassModeViewModel.State.ErrorMessage -> onError(false, it.message) + is TwoPassModeViewModel.State.Error -> + onError(false, it.error.getLocalizedMessage(resources)) }.exhaustive }.launchIn(lifecycleScope) } diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/signup/ChooseUsernameFragment.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/signup/ChooseUsernameFragment.kt index 3f092c0b2..6ab549696 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/signup/ChooseUsernameFragment.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/signup/ChooseUsernameFragment.kt @@ -34,6 +34,7 @@ import me.proton.core.auth.presentation.entity.signup.SignUpInput import me.proton.core.auth.presentation.viewmodel.signup.ChooseUsernameViewModel import me.proton.core.auth.presentation.viewmodel.signup.SignupViewModel import me.proton.core.auth.presentation.viewmodel.signup.canSwitchToExternal +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.presentation.utils.hideKeyboard import me.proton.core.presentation.utils.onClick import me.proton.core.presentation.utils.onFailure @@ -88,7 +89,7 @@ class ChooseUsernameFragment : SignupFragment(R.layout.fragment_signup_choose_us is ChooseUsernameViewModel.State.Processing -> showLoading(true) is ChooseUsernameViewModel.State.UsernameAvailable -> onUsernameAvailable(it.username, it.domain) is ChooseUsernameViewModel.State.AvailableDomains -> onDomains(it.domains, it.currentAccountType) - is ChooseUsernameViewModel.State.Error.Message -> onError(it.message) + is ChooseUsernameViewModel.State.Error.Message -> onError(it.error.getLocalizedMessage(resources)) is ChooseUsernameViewModel.State.Error.DomainsNotAvailable -> onError(getString(R.string.auth_create_address_error_no_available_domain)) is ChooseUsernameViewModel.State.Error.UsernameNotAvailable -> diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/signup/ExternalValidationTokenCodeFragment.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/signup/ExternalValidationTokenCodeFragment.kt index 487ad5941..8b51a3481 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/signup/ExternalValidationTokenCodeFragment.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/signup/ExternalValidationTokenCodeFragment.kt @@ -32,6 +32,7 @@ import me.proton.core.auth.presentation.databinding.FragmentSignupValidationToke import me.proton.core.auth.presentation.viewmodel.signup.SignupViewModel import me.proton.core.humanverification.domain.entity.TokenType import me.proton.core.humanverification.presentation.viewmodel.verification.HumanVerificationEnterCodeViewModel +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.presentation.utils.hideKeyboard import me.proton.core.presentation.utils.onClick import me.proton.core.presentation.utils.onFailure @@ -91,7 +92,7 @@ class ExternalValidationTokenCodeFragment : SignupFragment(R.layout.fragment_sig when (it) { is ViewModelResult.None -> Unit is ViewModelResult.Processing -> showLoading() - is ViewModelResult.Error -> onValidationError(it.throwable?.message) + is ViewModelResult.Error -> onValidationError(it.throwable?.getLocalizedMessage(resources)) is ViewModelResult.Success -> onValidationSuccess() }.exhaustive }.launchIn(lifecycleScope) @@ -99,7 +100,7 @@ class ExternalValidationTokenCodeFragment : SignupFragment(R.layout.fragment_sig viewModel.verificationCodeResendState.onEach { when (it) { is ViewModelResult.None -> Unit - is ViewModelResult.Error -> showError(it.throwable?.message) + is ViewModelResult.Error -> showError(it.throwable?.getLocalizedMessage(resources)) is ViewModelResult.Processing -> showLoading(true) is ViewModelResult.Success -> { showLoading(false) diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/signup/RecoveryMethodFragment.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/signup/RecoveryMethodFragment.kt index 1a8c5f395..c9e982027 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/signup/RecoveryMethodFragment.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/signup/RecoveryMethodFragment.kt @@ -38,6 +38,7 @@ import me.proton.core.auth.presentation.entity.signup.RecoveryMethodType import me.proton.core.auth.presentation.viewmodel.signup.RecoveryMethodViewModel import me.proton.core.auth.presentation.viewmodel.signup.SignupViewModel import me.proton.core.presentation.ui.alert.FragmentDialogResultLauncher +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.presentation.utils.hideKeyboard import me.proton.core.presentation.utils.onClick import me.proton.core.presentation.utils.viewBinding @@ -91,7 +92,7 @@ class RecoveryMethodFragment : SignupFragment(R.layout.fragment_signup_recovery) viewModel.validationResult.onEach { when (it) { is ViewModelResult.None -> Unit - is ViewModelResult.Error -> showError(it.throwable?.message) + is ViewModelResult.Error -> showError(it.throwable?.getLocalizedMessage(resources)) is ViewModelResult.Processing -> showLoading(true) is ViewModelResult.Success -> { if (it.value) { diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/signup/SignupActivity.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/signup/SignupActivity.kt index 74917647c..f6763e7e5 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/signup/SignupActivity.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/ui/signup/SignupActivity.kt @@ -51,6 +51,7 @@ import me.proton.core.plan.presentation.ui.BasePlansFragment.Companion.BUNDLE_KE import me.proton.core.plan.presentation.ui.BasePlansFragment.Companion.KEY_PLAN_SELECTED import me.proton.core.plan.presentation.ui.removePlansSignup import me.proton.core.plan.presentation.ui.showPlansSignup +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.util.kotlin.exhaustive @AndroidEntryPoint @@ -106,7 +107,7 @@ class SignupActivity : AuthActivity(ActivitySignupBinding when (it) { is LoginViewModel.State.Idle -> showLoading(false) is LoginViewModel.State.Processing -> showLoading(true) - is LoginViewModel.State.ErrorMessage -> onLoginError(it.message) + is LoginViewModel.State.Error -> onLoginError(it.error.getLocalizedMessage(resources)) is LoginViewModel.State.AccountSetupResult -> onPostLoginAccountSetup(it.result) }.exhaustive }.launchIn(lifecycleScope) diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/ChooseAddressViewModel.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/ChooseAddressViewModel.kt index 791a31495..08433671c 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/ChooseAddressViewModel.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/ChooseAddressViewModel.kt @@ -59,7 +59,7 @@ class ChooseAddressViewModel @Inject constructor( sealed class Error : State() { object DomainsNotAvailable : Error() object UsernameNotAvailable : Error() - data class Message(val message: String?) : Error() + data class Message(val error: Throwable) : Error() } } @@ -74,7 +74,7 @@ class ChooseAddressViewModel @Inject constructor( val user = usernameDomainAvailability.getUser(userId) emit(State.Data(user.name, domains)) }.catch { error -> - emit(State.Error.Message(error.message)) + emit(State.Error.Message(error)) }.onEach { _state.tryEmit(it) }.launchIn(viewModelScope) @@ -97,7 +97,7 @@ class ChooseAddressViewModel @Inject constructor( emit(State.Error.UsernameNotAvailable) } }.catch { error -> - emit(State.Error.Message(error.message)) + emit(State.Error.Message(error)) }.onEach { _state.tryEmit(it) }.launchIn(viewModelScope) diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel.kt index b50e5ad44..36c21d5c0 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModel.kt @@ -66,7 +66,8 @@ class ConfirmPasswordDialogViewModel @Inject constructor( sealed class Error : State() { object InvalidAccount : Error() - data class Message(val message: String?) : Error() + object Unknown : Error() + data class General(val error: Throwable) : Error() } } @@ -86,7 +87,7 @@ class ConfirmPasswordDialogViewModel @Inject constructor( }.exhaustive emit(State.SecondFactorResult(isSecondFactorNeeded)) }.catch { error -> - emit(State.Error.Message(error.message)) + emit(State.Error.General(error)) }.onEach { _state.tryEmit(it) }.launchIn(viewModelScope) @@ -115,10 +116,10 @@ class ConfirmPasswordDialogViewModel @Inject constructor( ) ) } else { - emit(State.Error.Message(message = null)) + emit(State.Error.Unknown) } }.catch { error -> - emit(State.Error.Message(error.message)) + emit(State.Error.General(error)) }.onEach { _state.tryEmit(it) }.launchIn(viewModelScope) diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/CreateAddressViewModel.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/CreateAddressViewModel.kt index f931f5f28..59203ffb2 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/CreateAddressViewModel.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/CreateAddressViewModel.kt @@ -54,7 +54,7 @@ class CreateAddressViewModel @Inject constructor( object Idle : State() object Processing : State() data class AccountSetupResult(val result: PostLoginAccountSetup.Result) : State() - data class ErrorMessage(val message: String?) : State() + data class Error(val error: Throwable) : State() } fun upgradeAccount( @@ -81,7 +81,7 @@ class CreateAddressViewModel @Inject constructor( }.retryOnceWhen(Throwable::primaryKeyExists) { CoreLogger.e(LogTag.FLOW_ERROR_RETRY, it, "Retrying to upgrade an account") }.catch { error -> - emit(State.ErrorMessage(error.message)) + emit(State.Error(error)) }.onEach { _state.tryEmit(it) }.launchIn(viewModelScope) diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/LoginViewModel.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/LoginViewModel.kt index eb4d776da..6ad95cb79 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/LoginViewModel.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/LoginViewModel.kt @@ -67,7 +67,7 @@ internal class LoginViewModel @Inject constructor( object Idle : State() object Processing : State() data class AccountSetupResult(val result: PostLoginAccountSetup.Result) : State() - data class ErrorMessage(val message: String?, val isPotentialBlocking: Boolean) : State() + data class Error(val error: Throwable, val isPotentialBlocking: Boolean) : State() } override val recoveryEmailAddress: String? @@ -116,7 +116,7 @@ internal class LoginViewModel @Inject constructor( }.retryOnceWhen(Throwable::primaryKeyExists) { CoreLogger.e(LogTag.FLOW_ERROR_RETRY, it, "Retrying login flow") }.catch { error -> - emit(State.ErrorMessage(error.message, error.isPotentialBlocking())) + emit(State.Error(error, error.isPotentialBlocking())) }.onEach { state -> _state.tryEmit(state) }.launchIn(viewModelScope) diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/SecondFactorViewModel.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/SecondFactorViewModel.kt index 1c338ca71..ce712ca44 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/SecondFactorViewModel.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/SecondFactorViewModel.kt @@ -61,7 +61,7 @@ class SecondFactorViewModel @Inject constructor( sealed class Error : State() { data class Unrecoverable(val message: String?) : Error() - data class Message(val message: String?) : Error() + data class Message(val error: Throwable) : Error() } } @@ -102,7 +102,7 @@ class SecondFactorViewModel @Inject constructor( stopSecondFactorFlow(userId) emit(State.Error.Unrecoverable(error.message)) } else { - emit(State.Error.Message(error.message)) + emit(State.Error.Message(error)) } }.onEach { state -> _state.tryEmit(state) diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/TwoPassModeViewModel.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/TwoPassModeViewModel.kt index 2bc9c2c6d..9baeef9c5 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/TwoPassModeViewModel.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/TwoPassModeViewModel.kt @@ -52,7 +52,7 @@ class TwoPassModeViewModel @Inject constructor( object Idle : State() object Processing : State() data class AccountSetupResult(val result: PostLoginAccountSetup.Result) : State() - data class ErrorMessage(val message: String?) : State() + data class Error(val error: Throwable) : State() } fun stopMailboxLoginFlow( @@ -79,7 +79,7 @@ class TwoPassModeViewModel @Inject constructor( ) emit(State.AccountSetupResult(result)) }.catch { error -> - emit(State.ErrorMessage(error.message)) + emit(State.Error(error)) }.onEach { state -> _state.tryEmit(state) }.launchIn(viewModelScope) diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/signup/ChooseUsernameViewModel.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/signup/ChooseUsernameViewModel.kt index 27ba41eca..56d504679 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/signup/ChooseUsernameViewModel.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/signup/ChooseUsernameViewModel.kt @@ -68,7 +68,7 @@ internal class ChooseUsernameViewModel @Inject constructor( sealed class Error : State() { object DomainsNotAvailable : Error() object UsernameNotAvailable : Error() - data class Message(val message: String?) : Error() + data class Message(val error: Throwable) : Error() } } @@ -113,7 +113,7 @@ internal class ChooseUsernameViewModel @Inject constructor( domains = usernameDomainAvailability.getDomains() emit(State.AvailableDomains(domains!!, requireCurrentAccountType())) }.catch { error -> - emit(State.Error.Message(error.message)) + emit(State.Error.Message(error)) } /** @@ -159,7 +159,7 @@ internal class ChooseUsernameViewModel @Inject constructor( emit(State.Processing) emit(checkUsernameForAccountType(username, domain)) }.catch { error -> - emit(State.Error.Message(error.message)) + emit(State.Error.Message(error)) }.onEach { _state.tryEmit(it) }.launchIn(viewModelScope) diff --git a/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModelTest.kt b/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModelTest.kt index 3b4ff952c..d7a44288e 100644 --- a/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModelTest.kt +++ b/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/ConfirmPasswordDialogViewModelTest.kt @@ -30,7 +30,6 @@ import me.proton.core.account.domain.entity.AccountDetails import me.proton.core.account.domain.entity.AccountState import me.proton.core.account.domain.entity.SessionState import me.proton.core.accountmanager.domain.AccountManager -import me.proton.core.accountmanager.domain.getPrimaryAccount import me.proton.core.auth.domain.entity.AuthInfo import me.proton.core.auth.domain.usecase.scopes.GetAuthInfo import me.proton.core.auth.domain.usecase.scopes.ObtainLockedScope @@ -43,6 +42,7 @@ import me.proton.core.network.domain.ResponseCodes import me.proton.core.network.domain.scopes.MissingScopeListener import me.proton.core.network.domain.scopes.Scope import me.proton.core.network.domain.session.SessionId +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.test.android.ArchTest import me.proton.core.test.kotlin.CoroutinesTest import me.proton.core.test.kotlin.assertIs @@ -156,8 +156,8 @@ class ConfirmPasswordDialogViewModelTest : ArchTest, CoroutinesTest { assertIs(awaitItem()) assertIs(awaitItem()) val nextItem = awaitItem() - assertTrue(nextItem is ConfirmPasswordDialogViewModel.State.Error.Message) - assertEquals("Invalid input", nextItem.message) + assertTrue(nextItem is ConfirmPasswordDialogViewModel.State.Error.General) + assertEquals("Invalid input", nextItem.error.getLocalizedMessage(mockk())) cancelAndIgnoreRemainingEvents() } } @@ -204,8 +204,8 @@ class ConfirmPasswordDialogViewModelTest : ArchTest, CoroutinesTest { assertIs(awaitItem()) assertIs(awaitItem()) val nextItem = awaitItem() - assertTrue(nextItem is ConfirmPasswordDialogViewModel.State.Error.Message) - assertEquals("Invalid input", nextItem.message) + assertTrue(nextItem is ConfirmPasswordDialogViewModel.State.Error.General) + assertEquals("Invalid input", nextItem.error.getLocalizedMessage(mockk())) cancelAndIgnoreRemainingEvents() } @@ -260,8 +260,8 @@ class ConfirmPasswordDialogViewModelTest : ArchTest, CoroutinesTest { assertIs(awaitItem()) assertIs(awaitItem()) val nextItem = awaitItem() - assertTrue(nextItem is ConfirmPasswordDialogViewModel.State.Error.Message) - assertEquals("Invalid input", nextItem.message) + assertTrue(nextItem is ConfirmPasswordDialogViewModel.State.Error.General) + assertEquals("Invalid input", nextItem.error.getLocalizedMessage(mockk())) cancelAndIgnoreRemainingEvents() } diff --git a/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/CreateAddressViewModelTest.kt b/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/CreateAddressViewModelTest.kt index ff1048e27..35262e55b 100644 --- a/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/CreateAddressViewModelTest.kt +++ b/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/CreateAddressViewModelTest.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import me.proton.core.account.domain.entity.AccountType import me.proton.core.auth.domain.usecase.PostLoginAccountSetup import me.proton.core.domain.entity.UserId +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.test.android.ArchTest import me.proton.core.test.kotlin.CoroutinesTest import me.proton.core.usersettings.domain.usecase.SetupUsername @@ -185,8 +186,8 @@ class CreateAddressViewModelTest : ArchTest, CoroutinesTest { coVerify(exactly = 1) { setupUsername.invoke(any(), any()) } assertIs(awaitItem()) - val result = assertIs(awaitItem()) - assertEquals("Something went wrong", result.message) + val result = assertIs(awaitItem()) + assertEquals("Something went wrong", result.error.getLocalizedMessage(mockk())) cancelAndIgnoreRemainingEvents() } } diff --git a/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/LoginViewModelTest.kt b/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/LoginViewModelTest.kt index c784c5d43..d5ae32d50 100644 --- a/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/LoginViewModelTest.kt +++ b/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/LoginViewModelTest.kt @@ -37,6 +37,7 @@ import me.proton.core.humanverification.presentation.HumanVerificationOrchestrat import me.proton.core.network.domain.ApiException import me.proton.core.network.domain.ApiResult import me.proton.core.network.domain.ResponseCodes +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.test.android.ArchTest import me.proton.core.test.kotlin.CoroutinesTest import me.proton.core.test.kotlin.assertIs @@ -153,7 +154,7 @@ class LoginViewModelTest : ArchTest, CoroutinesTest { // THEN assertIs(awaitItem()) - assertTrue(awaitItem() is LoginViewModel.State.ErrorMessage) + assertTrue(awaitItem() is LoginViewModel.State.Error) verify { savedStateHandle.set(any(), any()) } @@ -183,8 +184,8 @@ class LoginViewModelTest : ArchTest, CoroutinesTest { assertIs(awaitItem()) val errorState = awaitItem() - assertTrue(errorState is LoginViewModel.State.ErrorMessage) - assertEquals("proton error", errorState.message) + assertTrue(errorState is LoginViewModel.State.Error) + assertEquals("proton error", errorState.error.getLocalizedMessage(mockk())) cancelAndIgnoreRemainingEvents() } @@ -239,8 +240,8 @@ class LoginViewModelTest : ArchTest, CoroutinesTest { assertIs(awaitItem()) // retried val errorState = awaitItem() - assertTrue(errorState is LoginViewModel.State.ErrorMessage) - assertEquals("Primary key exists", errorState.message) + assertTrue(errorState is LoginViewModel.State.Error) + assertEquals("Primary key exists", errorState.error.getLocalizedMessage(mockk())) cancelAndIgnoreRemainingEvents() } diff --git a/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/SecondFactorViewModelTest.kt b/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/SecondFactorViewModelTest.kt index 280f1192e..546345f30 100644 --- a/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/SecondFactorViewModelTest.kt +++ b/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/SecondFactorViewModelTest.kt @@ -29,10 +29,6 @@ import me.proton.core.auth.domain.AccountWorkflowHandler import me.proton.core.auth.domain.entity.ScopeInfo import me.proton.core.auth.domain.usecase.PerformSecondFactor import me.proton.core.auth.domain.usecase.PostLoginAccountSetup -import me.proton.core.auth.domain.usecase.SetupAccountCheck -import me.proton.core.auth.domain.usecase.SetupInternalAddress -import me.proton.core.auth.domain.usecase.SetupPrimaryKeys -import me.proton.core.auth.domain.usecase.UnlockUserPrimaryKey import me.proton.core.auth.presentation.entity.SessionResult import me.proton.core.domain.entity.UserId import me.proton.core.network.domain.session.SessionId @@ -40,7 +36,6 @@ import me.proton.core.network.domain.session.SessionProvider import me.proton.core.test.android.ArchTest import me.proton.core.test.kotlin.CoroutinesTest import me.proton.core.test.kotlin.assertIs -import me.proton.core.user.domain.UserManager import org.junit.Before import org.junit.Test import kotlin.test.assertEquals diff --git a/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/TwoPassModeViewModelTest.kt b/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/TwoPassModeViewModelTest.kt index 150cef260..736c1968d 100644 --- a/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/TwoPassModeViewModelTest.kt +++ b/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/TwoPassModeViewModelTest.kt @@ -36,9 +36,6 @@ import org.junit.Before import org.junit.Test import kotlin.test.assertTrue -/** - * @author Dino Kadrikj. - */ class TwoPassModeViewModelTest : ArchTest, CoroutinesTest { // region mocks diff --git a/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/signup/ChooseUsernameViewModelTest.kt b/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/signup/ChooseUsernameViewModelTest.kt index 894b2a44c..ce5858aeb 100644 --- a/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/signup/ChooseUsernameViewModelTest.kt +++ b/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/signup/ChooseUsernameViewModelTest.kt @@ -26,6 +26,7 @@ import me.proton.core.auth.domain.usecase.UsernameDomainAvailability import me.proton.core.humanverification.domain.usecase.SendVerificationCodeToEmailDestination import me.proton.core.network.domain.ApiException import me.proton.core.network.domain.ApiResult +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.test.android.ArchTest import me.proton.core.test.kotlin.CoroutinesTest import org.junit.Before @@ -95,7 +96,7 @@ class ChooseUsernameViewModelTest : ArchTest, CoroutinesTest { // THEN assertTrue(awaitItem() is ChooseUsernameViewModel.State.Processing) val errorItem = awaitItem() as ChooseUsernameViewModel.State.Error.Message - assertEquals("domains error", errorItem.message) + assertEquals("domains error", errorItem.error.getLocalizedMessage(mockk())) cancelAndConsumeRemainingEvents() } } @@ -246,7 +247,7 @@ class ChooseUsernameViewModelTest : ArchTest, CoroutinesTest { assertTrue(errorItem is ChooseUsernameViewModel.State.Error.Message) assertEquals( "currentAccountType is not set. Call setClientAppRequiredAccountType first.", - errorItem.message + errorItem.error.getLocalizedMessage(mockk()) ) cancelAndConsumeRemainingEvents() } @@ -354,7 +355,7 @@ class ChooseUsernameViewModelTest : ArchTest, CoroutinesTest { assertTrue(awaitItem() is ChooseUsernameViewModel.State.Processing) val errorItem = awaitItem() assertTrue(errorItem is ChooseUsernameViewModel.State.Error.Message) - assertEquals("Error with the email", errorItem.message) + assertEquals("Error with the email", errorItem.error.getLocalizedMessage(mockk())) cancelAndConsumeRemainingEvents() } } diff --git a/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/signup/SignupViewModelTest.kt b/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/signup/SignupViewModelTest.kt index 870463a36..1a8c21800 100644 --- a/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/signup/SignupViewModelTest.kt +++ b/auth/presentation/src/test/kotlin/me/proton/core/auth/presentation/viewmodel/signup/SignupViewModelTest.kt @@ -30,7 +30,6 @@ import me.proton.core.auth.domain.usecase.signup.PerformCreateUser import me.proton.core.auth.presentation.entity.signup.RecoveryMethod import me.proton.core.auth.presentation.entity.signup.RecoveryMethodType import me.proton.core.crypto.common.keystore.KeyStoreCrypto -import me.proton.core.domain.entity.Product import me.proton.core.domain.entity.UserId import me.proton.core.humanverification.domain.HumanVerificationManager import me.proton.core.humanverification.presentation.HumanVerificationManagerObserver diff --git a/human-verification/data/src/test/kotlin/me/proton/core/humanverification/data/repository/HumanVerificationRepositoryImplTest.kt b/human-verification/data/src/test/kotlin/me/proton/core/humanverification/data/repository/HumanVerificationRepositoryImplTest.kt index 9d80d3ea7..24a66b557 100644 --- a/human-verification/data/src/test/kotlin/me/proton/core/humanverification/data/repository/HumanVerificationRepositoryImplTest.kt +++ b/human-verification/data/src/test/kotlin/me/proton/core/humanverification/data/repository/HumanVerificationRepositoryImplTest.kt @@ -24,6 +24,7 @@ import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.slot +import io.mockk.unmockkStatic import kotlinx.coroutines.test.runBlockingTest import me.proton.core.crypto.common.keystore.EncryptedByteArray import me.proton.core.crypto.common.keystore.EncryptedString @@ -40,6 +41,7 @@ import me.proton.core.network.domain.client.ClientIdType import me.proton.core.network.domain.humanverification.VerificationMethod import me.proton.core.network.domain.session.Session import me.proton.core.network.domain.session.SessionId +import org.junit.After import org.junit.Before import org.junit.Test @@ -80,6 +82,11 @@ class HumanVerificationRepositoryImplTest { } } + @After + fun afterEveryTest() { + unmockkStatic("androidx.room.RoomDatabaseKt") + } + @Test fun `set human verification details`() = runBlockingTest { val humanVerificationDetails = HumanVerificationDetails( diff --git a/human-verification/presentation/src/test/kotlin/ui/VerificationResponseMessageTest.kt b/human-verification/presentation/src/test/kotlin/ui/VerificationResponseGeneralTest.kt similarity index 99% rename from human-verification/presentation/src/test/kotlin/ui/VerificationResponseMessageTest.kt rename to human-verification/presentation/src/test/kotlin/ui/VerificationResponseGeneralTest.kt index 5df838ed0..9a19b032e 100644 --- a/human-verification/presentation/src/test/kotlin/ui/VerificationResponseMessageTest.kt +++ b/human-verification/presentation/src/test/kotlin/ui/VerificationResponseGeneralTest.kt @@ -27,7 +27,7 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull -class VerificationResponseMessageTest { +class VerificationResponseGeneralTest { @Test fun `Deserializing a VerificationResponseMessage from JSON works`() { diff --git a/payment/presentation/api/payment-presentation.api b/payment/presentation/api/payment-presentation.api index ee30de6bb..98bb14765 100644 --- a/payment/presentation/api/payment-presentation.api +++ b/payment/presentation/api/payment-presentation.api @@ -520,13 +520,13 @@ public abstract class me/proton/core/payment/presentation/viewmodel/BillingCommo public abstract class me/proton/core/payment/presentation/viewmodel/BillingCommonViewModel$State$Error : me/proton/core/payment/presentation/viewmodel/BillingCommonViewModel$State { } -public final class me/proton/core/payment/presentation/viewmodel/BillingCommonViewModel$State$Error$Message : me/proton/core/payment/presentation/viewmodel/BillingCommonViewModel$State$Error { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lme/proton/core/payment/presentation/viewmodel/BillingCommonViewModel$State$Error$Message; - public static synthetic fun copy$default (Lme/proton/core/payment/presentation/viewmodel/BillingCommonViewModel$State$Error$Message;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/payment/presentation/viewmodel/BillingCommonViewModel$State$Error$Message; +public final class me/proton/core/payment/presentation/viewmodel/BillingCommonViewModel$State$Error$General : me/proton/core/payment/presentation/viewmodel/BillingCommonViewModel$State$Error { + public fun (Ljava/lang/Throwable;)V + public final fun component1 ()Ljava/lang/Throwable; + public final fun copy (Ljava/lang/Throwable;)Lme/proton/core/payment/presentation/viewmodel/BillingCommonViewModel$State$Error$General; + public static synthetic fun copy$default (Lme/proton/core/payment/presentation/viewmodel/BillingCommonViewModel$State$Error$General;Ljava/lang/Throwable;ILjava/lang/Object;)Lme/proton/core/payment/presentation/viewmodel/BillingCommonViewModel$State$Error$General; public fun equals (Ljava/lang/Object;)Z - public final fun getMessage ()Ljava/lang/String; + public final fun getError ()Ljava/lang/Throwable; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -691,13 +691,13 @@ public abstract class me/proton/core/payment/presentation/viewmodel/PaymentOptio public abstract class me/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModel$State$Error : me/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModel$State { } -public final class me/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModel$State$Error$Message : me/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModel$State$Error { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lme/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModel$State$Error$Message; - public static synthetic fun copy$default (Lme/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModel$State$Error$Message;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModel$State$Error$Message; +public final class me/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModel$State$Error$General : me/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModel$State$Error { + public fun (Ljava/lang/Throwable;)V + public final fun component1 ()Ljava/lang/Throwable; + public final fun copy (Ljava/lang/Throwable;)Lme/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModel$State$Error$General; + public static synthetic fun copy$default (Lme/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModel$State$Error$General;Ljava/lang/Throwable;ILjava/lang/Object;)Lme/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModel$State$Error$General; public fun equals (Ljava/lang/Object;)Z - public final fun getMessage ()Ljava/lang/String; + public final fun getError ()Ljava/lang/Throwable; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -771,16 +771,13 @@ public final class me/proton/core/payment/presentation/viewmodel/PaymentTokenApp public abstract class me/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel$State { } -public abstract class me/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel$State$Error : me/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel$State { -} - -public final class me/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel$State$Error$Message : me/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel$State$Error { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lme/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel$State$Error$Message; - public static synthetic fun copy$default (Lme/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel$State$Error$Message;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel$State$Error$Message; +public final class me/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel$State$Error : me/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel$State { + public fun (Ljava/lang/Throwable;)V + public final fun component1 ()Ljava/lang/Throwable; + public final fun copy (Ljava/lang/Throwable;)Lme/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel$State$Error; + public static synthetic fun copy$default (Lme/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel$State$Error;Ljava/lang/Throwable;ILjava/lang/Object;)Lme/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel$State$Error; public fun equals (Ljava/lang/Object;)Z - public final fun getMessage ()Ljava/lang/String; + public final fun getError ()Ljava/lang/Throwable; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/ui/BillingActivity.kt b/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/ui/BillingActivity.kt index b2dea3ad6..77d46dea5 100644 --- a/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/ui/BillingActivity.kt +++ b/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/ui/BillingActivity.kt @@ -40,6 +40,7 @@ import me.proton.core.payment.presentation.entity.BillingResult import me.proton.core.payment.presentation.viewmodel.BillingCommonViewModel import me.proton.core.payment.presentation.viewmodel.BillingViewModel import me.proton.core.presentation.ui.view.ProtonInput +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.presentation.utils.hideKeyboard import me.proton.core.presentation.utils.onClick import me.proton.core.presentation.utils.onTextChange @@ -124,7 +125,7 @@ class BillingActivity : PaymentsActivity(ActivityBilling ) is BillingCommonViewModel.State.Incomplete.TokenApprovalNeeded -> onTokenApprovalNeeded(input.userId, it.paymentToken, it.amount) - is BillingCommonViewModel.State.Error.Message -> showError(it.message) + is BillingCommonViewModel.State.Error.General -> showError(it.error.getLocalizedMessage(resources)) is BillingCommonViewModel.State.Error.SignUpWithPaymentMethodUnsupported -> showError(getString(R.string.payments_error_signup_paymentmethod)) else -> { diff --git a/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/ui/PaymentOptionsActivity.kt b/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/ui/PaymentOptionsActivity.kt index 1e9cbaddc..8ac28307a 100644 --- a/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/ui/PaymentOptionsActivity.kt +++ b/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/ui/PaymentOptionsActivity.kt @@ -39,6 +39,7 @@ import me.proton.core.payment.presentation.entity.PaymentOptionsInput import me.proton.core.payment.presentation.viewmodel.BillingCommonViewModel import me.proton.core.payment.presentation.viewmodel.PaymentOptionsViewModel import me.proton.core.presentation.ui.adapter.selectableProtonAdapter +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.presentation.utils.onClick import me.proton.core.util.kotlin.exhaustive @@ -120,7 +121,7 @@ class PaymentOptionsActivity : PaymentsActivity(A viewModel.availablePaymentMethodsState.onEach { when (it) { is PaymentOptionsViewModel.State.Success.PaymentMethodsSuccess -> onSuccess(it.availablePaymentMethods) - is PaymentOptionsViewModel.State.Error.Message -> showError(it.message) + is PaymentOptionsViewModel.State.Error.General -> showError(it.error.getLocalizedMessage(resources)) else -> { } }.exhaustive @@ -153,7 +154,7 @@ class PaymentOptionsActivity : PaymentsActivity(A ) is BillingCommonViewModel.State.Incomplete.TokenApprovalNeeded -> onTokenApprovalNeeded(input.userId, it.paymentToken, it.amount) - is BillingCommonViewModel.State.Error.Message -> showError(it.message) + is BillingCommonViewModel.State.Error.General -> showError(it.error.getLocalizedMessage(resources)) is BillingCommonViewModel.State.Error.SignUpWithPaymentMethodUnsupported -> showError(getString(R.string.payments_error_signup_paymentmethod)) else -> { diff --git a/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/ui/PaymentTokenApprovalActivity.kt b/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/ui/PaymentTokenApprovalActivity.kt index 0599cd8d6..b8736f8d2 100644 --- a/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/ui/PaymentTokenApprovalActivity.kt +++ b/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/ui/PaymentTokenApprovalActivity.kt @@ -40,6 +40,7 @@ import me.proton.core.payment.presentation.entity.PaymentTokenApprovalInput import me.proton.core.payment.presentation.entity.PaymentTokenApprovalResult import me.proton.core.payment.presentation.viewmodel.PaymentTokenApprovalViewModel import me.proton.core.presentation.utils.errorSnack +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.presentation.utils.onClick import me.proton.core.util.kotlin.exhaustive @@ -102,7 +103,8 @@ class PaymentTokenApprovalActivity : is PaymentTokenApprovalViewModel.State.Idle -> showLoading(false) is PaymentTokenApprovalViewModel.State.Processing -> showLoading(true) is PaymentTokenApprovalViewModel.State.Success -> onSuccess(it.paymentTokenStatus) - is PaymentTokenApprovalViewModel.State.Error.Message -> showError(it.message) + is PaymentTokenApprovalViewModel.State.Error -> + showError(it.error.getLocalizedMessage(resources)) }.exhaustive }.launchIn(lifecycleScope) } diff --git a/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/viewmodel/BillingCommonViewModel.kt b/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/viewmodel/BillingCommonViewModel.kt index d9e033275..36928db9a 100644 --- a/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/viewmodel/BillingCommonViewModel.kt +++ b/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/viewmodel/BillingCommonViewModel.kt @@ -97,7 +97,7 @@ class BillingCommonViewModel @Inject constructor( } sealed class Error : State() { - data class Message(val message: String?) : Error() + data class General(val error: Throwable) : Error() object SignUpWithPaymentMethodUnsupported : Error() } } @@ -186,7 +186,7 @@ class BillingCommonViewModel @Inject constructor( // endregion } }.catch { - _subscriptionState.tryEmit(State.Error.Message(it.message)) + _subscriptionState.tryEmit(State.Error.General(it)) }.onEach { _subscriptionState.tryEmit(it) }.launchIn(viewModelScope) @@ -206,7 +206,7 @@ class BillingCommonViewModel @Inject constructor( ) = flow { emit(onTokenApproved(userId, planIds, codes, amount, currency, cycle, token)) }.catch { - _subscriptionState.tryEmit(State.Error.Message(it.message)) + _subscriptionState.tryEmit(State.Error.General(it)) }.onEach { _subscriptionState.tryEmit(it) }.launchIn(viewModelScope) diff --git a/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModel.kt b/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModel.kt index 94210793f..39d798d8a 100644 --- a/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModel.kt +++ b/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModel.kt @@ -73,7 +73,7 @@ class PaymentOptionsViewModel @Inject constructor( sealed class Error : State() { object SubscriptionInRecoverableError : Error() - data class Message(val message: String?) : Error() + data class General(val error: Throwable) : Error() } } @@ -121,7 +121,7 @@ class PaymentOptionsViewModel @Inject constructor( } emit(State.Success.PaymentMethodsSuccess(paymentMethods)) }.catch { error -> - _availablePaymentMethodsState.tryEmit(State.Error.Message(error.message)) + _availablePaymentMethodsState.tryEmit(State.Error.General(error)) }.onEach { methods -> _availablePaymentMethodsState.tryEmit(methods) }.launchIn(viewModelScope) diff --git a/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel.kt b/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel.kt index 978125aaf..fe08d8a12 100644 --- a/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel.kt +++ b/payment/presentation/src/main/kotlin/me/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModel.kt @@ -55,9 +55,7 @@ class PaymentTokenApprovalViewModel @Inject constructor( object Idle : State() object Processing : State() data class Success(val paymentTokenStatus: PaymentTokenStatus) : State() - sealed class Error : State() { - data class Message(val message: String?) : Error() - } + data class Error(val error: Throwable) : State() } /** @@ -101,7 +99,7 @@ class PaymentTokenApprovalViewModel @Inject constructor( emit(State.Processing) emit(State.Success(getPaymentTokenStatus(userId, paymentToken).status)) }.catch { - _approvalState.tryEmit(State.Error.Message(it.message)) + _approvalState.tryEmit(State.Error(it)) }.onEach { _approvalState.tryEmit(it) }.launchIn(viewModelScope) diff --git a/payment/presentation/src/test/kotlin/me/proton/core/payment/presentation/viewmodel/BillingViewModelTest.kt b/payment/presentation/src/test/kotlin/me/proton/core/payment/presentation/viewmodel/BillingViewModelTest.kt index 6e617e850..f5498a2d1 100644 --- a/payment/presentation/src/test/kotlin/me/proton/core/payment/presentation/viewmodel/BillingViewModelTest.kt +++ b/payment/presentation/src/test/kotlin/me/proton/core/payment/presentation/viewmodel/BillingViewModelTest.kt @@ -39,6 +39,7 @@ import me.proton.core.payment.domain.usecase.CreatePaymentTokenWithNewCreditCard import me.proton.core.payment.domain.usecase.CreatePaymentTokenWithNewPayPal import me.proton.core.payment.domain.usecase.PerformSubscribe import me.proton.core.payment.domain.usecase.ValidateSubscriptionPlan +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.test.android.ArchTest import me.proton.core.test.kotlin.CoroutinesTest import me.proton.core.test.kotlin.assertIs @@ -471,8 +472,8 @@ class BillingViewModelTest : ArchTest, CoroutinesTest { assertIs(awaitItem()) assertIs(awaitItem()) val subscriptionPlanStatus = awaitItem() - assertTrue(subscriptionPlanStatus is BillingCommonViewModel.State.Error.Message) - assertEquals("proton error", subscriptionPlanStatus.message) + assertTrue(subscriptionPlanStatus is BillingCommonViewModel.State.Error.General) + assertEquals("proton error", subscriptionPlanStatus.error.getLocalizedMessage(mockk())) } } diff --git a/payment/presentation/src/test/kotlin/me/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModelTest.kt b/payment/presentation/src/test/kotlin/me/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModelTest.kt index 268153396..a700ea633 100644 --- a/payment/presentation/src/test/kotlin/me/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModelTest.kt +++ b/payment/presentation/src/test/kotlin/me/proton/core/payment/presentation/viewmodel/PaymentOptionsViewModelTest.kt @@ -48,6 +48,7 @@ import me.proton.core.payment.domain.usecase.GetCurrentSubscription import me.proton.core.payment.domain.usecase.PerformSubscribe import me.proton.core.payment.domain.usecase.ValidateSubscriptionPlan import me.proton.core.plan.domain.entity.Plan +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.test.android.ArchTest import me.proton.core.test.kotlin.CoroutinesTest import me.proton.core.test.kotlin.assertIs @@ -186,8 +187,8 @@ class PaymentOptionsViewModelTest : ArchTest, CoroutinesTest { assertIs(awaitItem()) assertIs(awaitItem()) val paymentMethodsStatus = awaitItem() - assertTrue(paymentMethodsStatus is PaymentOptionsViewModel.State.Error.Message) - assertEquals("proton error", paymentMethodsStatus.message) + assertTrue(paymentMethodsStatus is PaymentOptionsViewModel.State.Error.General) + assertEquals("proton error", paymentMethodsStatus.error.getLocalizedMessage(mockk())) } } diff --git a/payment/presentation/src/test/kotlin/me/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModelTest.kt b/payment/presentation/src/test/kotlin/me/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModelTest.kt index 16c8203f9..ac4e9b7a3 100644 --- a/payment/presentation/src/test/kotlin/me/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModelTest.kt +++ b/payment/presentation/src/test/kotlin/me/proton/core/payment/presentation/viewmodel/PaymentTokenApprovalViewModelTest.kt @@ -33,6 +33,7 @@ import me.proton.core.payment.domain.entity.PaymentToken import me.proton.core.payment.domain.entity.PaymentTokenStatus import me.proton.core.payment.domain.usecase.GetPaymentTokenStatus import me.proton.core.payment.presentation.entity.SecureEndpoint +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.test.android.ArchTest import me.proton.core.test.kotlin.CoroutinesTest import me.proton.core.test.kotlin.assertIs @@ -172,8 +173,8 @@ class PaymentTokenApprovalViewModelTest : ArchTest, CoroutinesTest { assertIs(awaitItem()) assertIs(awaitItem()) val approvalStatus = awaitItem() - assertTrue(approvalStatus is PaymentTokenApprovalViewModel.State.Error.Message) - assertEquals("proton error", approvalStatus.message) + assertTrue(approvalStatus is PaymentTokenApprovalViewModel.State.Error) + assertEquals("proton error", approvalStatus.error.getLocalizedMessage(mockk())) } } diff --git a/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/ui/SignupPlansFragment.kt b/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/ui/SignupPlansFragment.kt index ccedc8661..0673f6997 100644 --- a/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/ui/SignupPlansFragment.kt +++ b/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/ui/SignupPlansFragment.kt @@ -38,6 +38,7 @@ import me.proton.core.plan.presentation.viewmodel.BasePlansViewModel import me.proton.core.plan.presentation.viewmodel.SignupPlansViewModel import me.proton.core.presentation.utils.addOnBackPressedCallback import me.proton.core.presentation.utils.errorSnack +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.presentation.utils.viewBinding import me.proton.core.util.kotlin.exhaustive @@ -81,7 +82,7 @@ class SignupPlansFragment : BasePlansFragment(R.layout.fragment_plans) { } signupPlansViewModel.availablePlansState.onEach { when (it) { - is BasePlansViewModel.PlanState.Error.Message -> onError(it.message) + is BasePlansViewModel.PlanState.Error -> onError(it.error.getLocalizedMessage(resources)) is BasePlansViewModel.PlanState.Idle -> Unit is BasePlansViewModel.PlanState.Processing -> showLoading(true) is BasePlansViewModel.PlanState.Success.Plans -> { diff --git a/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/ui/UpgradePlansFragment.kt b/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/ui/UpgradePlansFragment.kt index 386b40471..d90fb3a90 100644 --- a/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/ui/UpgradePlansFragment.kt +++ b/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/ui/UpgradePlansFragment.kt @@ -41,6 +41,7 @@ import me.proton.core.plan.presentation.viewmodel.BasePlansViewModel import me.proton.core.plan.presentation.viewmodel.UpgradePlansViewModel import me.proton.core.presentation.utils.addOnBackPressedCallback import me.proton.core.presentation.utils.errorSnack +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.presentation.utils.viewBinding import me.proton.core.util.kotlin.exhaustive @@ -77,7 +78,8 @@ class UpgradePlansFragment : BasePlansFragment(R.layout.fragment_plans_upgrade) upgradePlanViewModel.subscribedPlansState.onEach { when (it) { - is UpgradePlansViewModel.SubscribedPlansState.Error.Message -> onError(it.message) + is UpgradePlansViewModel.SubscribedPlansState.Error -> + onError(it.error.getLocalizedMessage(resources)) is UpgradePlansViewModel.SubscribedPlansState.Idle -> Unit is UpgradePlansViewModel.SubscribedPlansState.Processing -> showLoading(true) is UpgradePlansViewModel.SubscribedPlansState.Success.SubscribedPlans -> { @@ -95,7 +97,7 @@ class UpgradePlansFragment : BasePlansFragment(R.layout.fragment_plans_upgrade) upgradePlanViewModel.availablePlansState.onEach { when (it) { - is BasePlansViewModel.PlanState.Error.Message -> onError(it.message) + is BasePlansViewModel.PlanState.Error -> onError(it.error.getLocalizedMessage(resources)) is BasePlansViewModel.PlanState.Idle -> Unit is BasePlansViewModel.PlanState.Processing -> showLoading(true) is BasePlansViewModel.PlanState.Success.Plans -> { diff --git a/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/viewmodel/BasePlansViewModel.kt b/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/viewmodel/BasePlansViewModel.kt index 57fa29eb6..6513141d0 100644 --- a/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/viewmodel/BasePlansViewModel.kt +++ b/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/viewmodel/BasePlansViewModel.kt @@ -55,9 +55,7 @@ internal abstract class BasePlansViewModel( data class PaidPlanPayment(val selectedPlan: SelectedPlan, val billing: BillingResult) : Success() } - sealed class Error : PlanState() { - data class Message(val message: String?) : Error() - } + data class Error(val error: Throwable) : PlanState() } fun register(context: Fragment) { diff --git a/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/viewmodel/SignupPlansViewModel.kt b/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/viewmodel/SignupPlansViewModel.kt index f8e39947b..a7d2c3387 100644 --- a/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/viewmodel/SignupPlansViewModel.kt +++ b/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/viewmodel/SignupPlansViewModel.kt @@ -55,7 +55,7 @@ internal class SignupPlansViewModel @Inject constructor( } emit(PlanState.Success.Plans(plans = plans)) }.catch { error -> - _availablePlansState.tryEmit(PlanState.Error.Message(error.message)) + _availablePlansState.tryEmit(PlanState.Error(error)) }.onEach { plans -> _availablePlansState.tryEmit(plans) }.launchIn(viewModelScope) diff --git a/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/viewmodel/UpgradePlansViewModel.kt b/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/viewmodel/UpgradePlansViewModel.kt index e2d542db8..7868d4ade 100644 --- a/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/viewmodel/UpgradePlansViewModel.kt +++ b/plan/presentation/src/main/kotlin/me/proton/core/plan/presentation/viewmodel/UpgradePlansViewModel.kt @@ -64,9 +64,7 @@ internal class UpgradePlansViewModel @Inject constructor( ) : Success() } - sealed class Error : SubscribedPlansState() { - data class Message(val message: String?) : Error() - } + data class Error(val error: Throwable) : SubscribedPlansState() } fun getCurrentSubscribedPlans(userId: UserId) = flow { @@ -102,7 +100,7 @@ internal class UpgradePlansViewModel @Inject constructor( getAvailablePlansForUpgrade(userId) emit(SubscribedPlansState.Success.SubscribedPlans(subscribedPlans)) }.catch { error -> - _subscribedPlansState.tryEmit(SubscribedPlansState.Error.Message(error.message)) + _subscribedPlansState.tryEmit(SubscribedPlansState.Error(error)) }.onEach { _subscribedPlansState.tryEmit(it) }.launchIn(viewModelScope) @@ -115,7 +113,7 @@ internal class UpgradePlansViewModel @Inject constructor( emit(PlanState.Success.Plans(plans = availablePlans)) }.catch { error -> - _availablePlansState.tryEmit(PlanState.Error.Message(error.message)) + _availablePlansState.tryEmit(PlanState.Error(error)) }.onEach { plans -> _availablePlansState.tryEmit(plans) }.launchIn(viewModelScope) diff --git a/presentation/api/presentation.api b/presentation/api/presentation.api index 1273b9078..3d56c3524 100644 --- a/presentation/api/presentation.api +++ b/presentation/api/presentation.api @@ -482,6 +482,10 @@ public class me/proton/core/presentation/utils/EmptyActivityLifecycleCallbacks : public fun onActivityStopped (Landroid/app/Activity;)V } +public final class me/proton/core/presentation/utils/ErrorUtilsKt { + public static final fun getLocalizedMessage (Ljava/lang/Throwable;Landroid/content/res/Resources;)Ljava/lang/String; +} + public final class me/proton/core/presentation/utils/FragmentScreenContentProtectionDelegate : kotlin/properties/ReadOnlyProperty { public fun (Landroidx/fragment/app/Fragment;Lme/proton/core/presentation/utils/ProtectScreenConfiguration;)V public fun getValue (Landroidx/fragment/app/Fragment;Lkotlin/reflect/KProperty;)Lme/proton/core/presentation/utils/ScreenContentProtector; diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index 61f21f067..31dd7c657 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -37,6 +37,7 @@ dependencies { project(Module.kotlinUtil), project(Module.domain), + project(Module.network), // Kotlin `kotlin-reflect`, diff --git a/presentation/src/main/kotlin/me/proton/core/presentation/utils/ErrorUtils.kt b/presentation/src/main/kotlin/me/proton/core/presentation/utils/ErrorUtils.kt new file mode 100644 index 000000000..1d44d41ec --- /dev/null +++ b/presentation/src/main/kotlin/me/proton/core/presentation/utils/ErrorUtils.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * This file is part of Proton AG and ProtonCore. + * + * ProtonCore is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ProtonCore is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ProtonCore. If not, see . + */ + +package me.proton.core.presentation.utils + +import android.content.res.Resources +import me.proton.core.network.domain.ApiException +import me.proton.core.network.domain.exception.ApiConnectionException +import me.proton.core.presentation.R +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import javax.net.ssl.SSLHandshakeException +import javax.net.ssl.SSLPeerUnverifiedException + +/** + * Return localised and user readable error message. + */ +fun Throwable.getLocalizedMessage(resources: Resources): String? = + when (this) { + // all connectivity errors are wrapped with ApiConnectionException under ApiException + is ApiException -> + if (cause is ApiConnectionException) { + (cause as ApiConnectionException).getLocalizedMessage(resources) + } else message + // currently all other errors return their original message + else -> message + } + +internal fun ApiConnectionException.getLocalizedMessage(resources: Resources): String? = + when (this.cause) { + is SSLHandshakeException, + is SSLPeerUnverifiedException, + is SocketTimeoutException, + is UnknownHostException -> resources.getString(R.string.presentation_general_connection_error) + else -> message + } diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index d2aab131d..d024ca076 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -44,4 +44,5 @@ Close Skip +%s + Could not connect to server. diff --git a/user-settings/presentation/api/user-settings-presentation.api b/user-settings/presentation/api/user-settings-presentation.api index 20adeb7e9..1c949a801 100644 --- a/user-settings/presentation/api/user-settings-presentation.api +++ b/user-settings/presentation/api/user-settings-presentation.api @@ -321,13 +321,13 @@ public abstract class me/proton/core/usersettings/presentation/viewmodel/Passwor public abstract class me/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel$State$Error : me/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel$State { } -public final class me/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel$State$Error$Message : me/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel$State$Error { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lme/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel$State$Error$Message; - public static synthetic fun copy$default (Lme/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel$State$Error$Message;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel$State$Error$Message; +public final class me/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel$State$Error$General : me/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel$State$Error { + public fun (Ljava/lang/Throwable;)V + public final fun component1 ()Ljava/lang/Throwable; + public final fun copy (Ljava/lang/Throwable;)Lme/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel$State$Error$General; + public static synthetic fun copy$default (Lme/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel$State$Error$General;Ljava/lang/Throwable;ILjava/lang/Object;)Lme/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel$State$Error$General; public fun equals (Ljava/lang/Object;)Z - public final fun getMessage ()Ljava/lang/String; + public final fun getError ()Ljava/lang/Throwable; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -428,16 +428,13 @@ public final class me/proton/core/usersettings/presentation/viewmodel/UpdateReco public abstract class me/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel$State { } -public abstract class me/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel$State$Error : me/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel$State { -} - -public final class me/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel$State$Error$Message : me/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel$State$Error { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lme/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel$State$Error$Message; - public static synthetic fun copy$default (Lme/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel$State$Error$Message;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel$State$Error$Message; +public final class me/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel$State$Error : me/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel$State { + public fun (Ljava/lang/Throwable;)V + public final fun component1 ()Ljava/lang/Throwable; + public final fun copy (Ljava/lang/Throwable;)Lme/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel$State$Error; + public static synthetic fun copy$default (Lme/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel$State$Error;Ljava/lang/Throwable;ILjava/lang/Object;)Lme/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel$State$Error; public fun equals (Ljava/lang/Object;)Z - public final fun getMessage ()Ljava/lang/String; + public final fun getError ()Ljava/lang/Throwable; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/user-settings/presentation/src/main/kotlin/me/proton/core/usersettings/presentation/ui/PasswordManagementFragment.kt b/user-settings/presentation/src/main/kotlin/me/proton/core/usersettings/presentation/ui/PasswordManagementFragment.kt index 37580a7ba..e061340e3 100644 --- a/user-settings/presentation/src/main/kotlin/me/proton/core/usersettings/presentation/ui/PasswordManagementFragment.kt +++ b/user-settings/presentation/src/main/kotlin/me/proton/core/usersettings/presentation/ui/PasswordManagementFragment.kt @@ -32,6 +32,7 @@ import me.proton.core.presentation.ui.view.ProtonInput import me.proton.core.presentation.ui.view.ProtonProgressButton import me.proton.core.presentation.utils.addOnBackPressedCallback import me.proton.core.presentation.utils.errorSnack +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.presentation.utils.hideKeyboard import me.proton.core.presentation.utils.onClick import me.proton.core.presentation.utils.onFailure @@ -97,7 +98,7 @@ class PasswordManagementFragment : ProtonSecureFragment(R.layout.fragment_passwo mailboxPasswordGroup.visibility = if (it.twoPasswordMode) View.VISIBLE else View.GONE } } - is PasswordManagementViewModel.State.Error.Message -> showError(it.message) + is PasswordManagementViewModel.State.Error.General -> showError(it.error.getLocalizedMessage(resources)) is PasswordManagementViewModel.State.Error.UpdatingSinglePassModePassword, is PasswordManagementViewModel.State.Error.UpdatingMailboxPassword -> showError(getString(R.string.settings_change_password_error)) diff --git a/user-settings/presentation/src/main/kotlin/me/proton/core/usersettings/presentation/ui/UpdateRecoveryEmailFragment.kt b/user-settings/presentation/src/main/kotlin/me/proton/core/usersettings/presentation/ui/UpdateRecoveryEmailFragment.kt index 6e8e0c7ea..a7ace84a5 100644 --- a/user-settings/presentation/src/main/kotlin/me/proton/core/usersettings/presentation/ui/UpdateRecoveryEmailFragment.kt +++ b/user-settings/presentation/src/main/kotlin/me/proton/core/usersettings/presentation/ui/UpdateRecoveryEmailFragment.kt @@ -31,6 +31,7 @@ import me.proton.core.presentation.ui.ProtonFragment import me.proton.core.presentation.ui.alert.FragmentDialogResultLauncher import me.proton.core.presentation.utils.addOnBackPressedCallback import me.proton.core.presentation.utils.errorSnack +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.presentation.utils.hideKeyboard import me.proton.core.presentation.utils.onClick import me.proton.core.presentation.utils.onFailure @@ -86,7 +87,7 @@ class UpdateRecoveryEmailFragment : ProtonFragment(R.layout.fragment_update_reco } viewModel.state.onEach { when (it) { - is UpdateRecoveryEmailViewModel.State.Error.Message -> showError(it.message) + is UpdateRecoveryEmailViewModel.State.Error -> showError(it.error.getLocalizedMessage(resources)) is UpdateRecoveryEmailViewModel.State.Idle -> Unit is UpdateRecoveryEmailViewModel.State.LoadingCurrent -> showLoading(true) is UpdateRecoveryEmailViewModel.State.UpdatingCurrent -> showLoading(true) diff --git a/user-settings/presentation/src/main/kotlin/me/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel.kt b/user-settings/presentation/src/main/kotlin/me/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel.kt index 1c64f723e..a6b6292f7 100644 --- a/user-settings/presentation/src/main/kotlin/me/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel.kt +++ b/user-settings/presentation/src/main/kotlin/me/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel.kt @@ -64,7 +64,7 @@ class PasswordManagementViewModel @Inject constructor( } sealed class Error : State() { - data class Message(val message: String?) : Error() + data class General(val error: Throwable) : Error() object UpdatingMailboxPassword : Error() object UpdatingSinglePassModePassword : Error() } @@ -76,7 +76,7 @@ class PasswordManagementViewModel @Inject constructor( secondFactorEnabled = currentSettings.twoFA?.enabled ?: false emit(State.Mode(twoPasswordMode!!)) }.catch { error -> - _state.tryEmit(State.Error.Message(error.message)) + _state.tryEmit(State.Error.General(error)) }.onEach { state -> _state.tryEmit(state) }.launchIn(viewModelScope) @@ -106,7 +106,7 @@ class PasswordManagementViewModel @Inject constructor( ) emit(State.Success.UpdatingLoginPassword(result)) }.catch { error -> - _state.tryEmit(State.Error.Message(error.message)) + _state.tryEmit(State.Error.General(error)) }.onEach { state -> _state.tryEmit(state) }.launchIn(viewModelScope) @@ -146,7 +146,7 @@ class PasswordManagementViewModel @Inject constructor( ) } }.catch { error -> - _state.tryEmit(State.Error.Message(error.message)) + _state.tryEmit(State.Error.General(error)) }.onEach { state -> _state.tryEmit(state) }.launchIn(viewModelScope) diff --git a/user-settings/presentation/src/main/kotlin/me/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel.kt b/user-settings/presentation/src/main/kotlin/me/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel.kt index 9f5de07d0..10f0816a3 100644 --- a/user-settings/presentation/src/main/kotlin/me/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel.kt +++ b/user-settings/presentation/src/main/kotlin/me/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModel.kt @@ -55,9 +55,7 @@ class UpdateRecoveryEmailViewModel @Inject constructor( object UpdatingCurrent : State() data class LoadingSuccess(val recoveryEmail: String?) : State() data class UpdatingSuccess(val recoveryEmail: String?) : State() - sealed class Error : State() { - data class Message(val message: String?) : Error() - } + data class Error(val error: Throwable) : State() } /** @@ -69,7 +67,7 @@ class UpdateRecoveryEmailViewModel @Inject constructor( secondFactorEnabled = currentSettings.twoFA?.enabled ?: false emit(State.LoadingSuccess(currentSettings.email?.value)) }.catch { error -> - _state.tryEmit(State.Error.Message(error.message)) + _state.tryEmit(State.Error(error)) }.onEach { state -> _state.tryEmit(state) }.launchIn(viewModelScope) @@ -97,7 +95,7 @@ class UpdateRecoveryEmailViewModel @Inject constructor( ) emit(State.UpdatingSuccess(updateRecoveryEmailResult.email?.value)) }.catch { error -> - _state.tryEmit(State.Error.Message(error.message)) + _state.tryEmit(State.Error(error)) }.onEach { state -> _state.tryEmit(state) }.launchIn(viewModelScope) diff --git a/user-settings/presentation/src/test/kotlin/me/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModelTest.kt b/user-settings/presentation/src/test/kotlin/me/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModelTest.kt index 2d90d2fc2..76364922d 100644 --- a/user-settings/presentation/src/test/kotlin/me/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModelTest.kt +++ b/user-settings/presentation/src/test/kotlin/me/proton/core/usersettings/presentation/viewmodel/UpdateRecoveryEmailViewModelTest.kt @@ -27,6 +27,7 @@ import me.proton.core.crypto.common.keystore.KeyStoreCrypto import me.proton.core.domain.entity.UserId import me.proton.core.network.domain.ApiException import me.proton.core.network.domain.ApiResult +import me.proton.core.presentation.utils.getLocalizedMessage import me.proton.core.test.android.ArchTest import me.proton.core.test.kotlin.CoroutinesTest import me.proton.core.test.kotlin.assertIs @@ -161,8 +162,8 @@ class UpdateRecoveryEmailViewModelTest : ArchTest, CoroutinesTest { assertIs(awaitItem()) assertIs(awaitItem()) val result = awaitItem() - assertTrue(result is UpdateRecoveryEmailViewModel.State.Error.Message) - assertEquals("proton error", result.message) + assertTrue(result is UpdateRecoveryEmailViewModel.State.Error) + assertEquals("proton error", result.error.getLocalizedMessage(mockk())) } } @@ -278,8 +279,8 @@ class UpdateRecoveryEmailViewModelTest : ArchTest, CoroutinesTest { assertIs(awaitItem()) assertIs(awaitItem()) val result = awaitItem() - assertTrue(result is UpdateRecoveryEmailViewModel.State.Error.Message) - assertEquals("proton error", result.message) + assertTrue(result is UpdateRecoveryEmailViewModel.State.Error) + assertEquals("proton error", result.error.getLocalizedMessage(mockk())) } } } \ No newline at end of file