Add localized message to the all API IO exceptions saying clearly to the user that the server is not available.

This commit is contained in:
dkadrikj
2022-02-03 11:36:11 +00:00
committed by Dino Kadrikj
parent bc0e77aa80
commit 4bed2f612b
52 changed files with 243 additions and 169 deletions
+35 -31
View File
@@ -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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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;
}
@@ -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()
}
@@ -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<ActivityChooseAddressBinding>(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 ->
@@ -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<ActivityCreateAddressBinding>(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)
}
@@ -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>(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)
}
@@ -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>(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)
@@ -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<ActivityMailboxLoginBinding>(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)
}
@@ -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 ->
@@ -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)
@@ -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) {
@@ -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>(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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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<ConfirmPasswordDialogViewModel.State.Idle>(awaitItem())
assertIs<ConfirmPasswordDialogViewModel.State.ProcessingObtainScope>(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<ConfirmPasswordDialogViewModel.State.Idle>(awaitItem())
assertIs<ConfirmPasswordDialogViewModel.State.ProcessingObtainScope>(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<ConfirmPasswordDialogViewModel.State.Idle>(awaitItem())
assertIs<ConfirmPasswordDialogViewModel.State.ProcessingObtainScope>(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()
}
@@ -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<CreateAddressViewModel.State.Processing>(awaitItem())
val result = assertIs<CreateAddressViewModel.State.ErrorMessage>(awaitItem())
assertEquals("Something went wrong", result.message)
val result = assertIs<CreateAddressViewModel.State.Error>(awaitItem())
assertEquals("Something went wrong", result.error.getLocalizedMessage(mockk()))
cancelAndIgnoreRemainingEvents()
}
}
@@ -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<LoginViewModel.State.Processing>(awaitItem())
assertTrue(awaitItem() is LoginViewModel.State.ErrorMessage)
assertTrue(awaitItem() is LoginViewModel.State.Error)
verify { savedStateHandle.set(any(), any<String>()) }
@@ -183,8 +184,8 @@ class LoginViewModelTest : ArchTest, CoroutinesTest {
assertIs<LoginViewModel.State.Processing>(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<LoginViewModel.State.Processing>(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()
}
@@ -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
@@ -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
@@ -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()
}
}
@@ -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
@@ -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(
@@ -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`() {
@@ -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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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 <init> (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;
}
@@ -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<ActivityBillingBinding>(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 -> {
@@ -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<ActivityPaymentOptionsBinding>(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<ActivityPaymentOptionsBinding>(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 -> {
@@ -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)
}
@@ -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)
@@ -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)
@@ -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)
@@ -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<BillingCommonViewModel.State.Idle>(awaitItem())
assertIs<BillingCommonViewModel.State.Processing>(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()))
}
}
@@ -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<PaymentOptionsViewModel.State.Idle>(awaitItem())
assertIs<PaymentOptionsViewModel.State.Processing>(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()))
}
}
@@ -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<PaymentTokenApprovalViewModel.State.Idle>(awaitItem())
assertIs<PaymentTokenApprovalViewModel.State.Processing>(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()))
}
}
@@ -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 -> {
@@ -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 -> {
@@ -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) {
@@ -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)
@@ -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)
+4
View File
@@ -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 <init> (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;
+1
View File
@@ -37,6 +37,7 @@ dependencies {
project(Module.kotlinUtil),
project(Module.domain),
project(Module.network),
// Kotlin
`kotlin-reflect`,
@@ -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 <https://www.gnu.org/licenses/>.
*/
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
}
@@ -44,4 +44,5 @@
<string name="presentation_close">Close</string>
<string name="presentation_skip">Skip</string>
<string name="presentation_phone_calling_code_template">+%s</string>
<string name="presentation_general_connection_error">Could not connect to server.</string>
</resources>
@@ -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 <init> (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 <init> (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 <init> (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 <init> (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;
}
@@ -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))
@@ -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)
@@ -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)
@@ -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)
@@ -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<UpdateRecoveryEmailViewModel.State.Idle>(awaitItem())
assertIs<UpdateRecoveryEmailViewModel.State.LoadingCurrent>(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<UpdateRecoveryEmailViewModel.State.Idle>(awaitItem())
assertIs<UpdateRecoveryEmailViewModel.State.UpdatingCurrent>(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()))
}
}
}