diff --git a/changelog.d/5517.misc b/changelog.d/5517.misc new file mode 100644 index 00000000000..18269afcc6a --- /dev/null +++ b/changelog.d/5517.misc @@ -0,0 +1 @@ +Flattening the asynchronous onboarding state and passing all errors through the same pipeline \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt index aabe6e0d069..89b4a343dd4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt @@ -58,12 +58,36 @@ fun Throwable.getRetryDelay(defaultValue: Long): Long { ?: defaultValue } +fun Throwable.isUsernameInUse(): Boolean { + return this is Failure.ServerError && error.code == MatrixError.M_USER_IN_USE +} + +fun Throwable.isInvalidUsername(): Boolean { + return this is Failure.ServerError && + error.code == MatrixError.M_INVALID_USERNAME +} + fun Throwable.isInvalidPassword(): Boolean { return this is Failure.ServerError && error.code == MatrixError.M_FORBIDDEN && error.message == "Invalid password" } +fun Throwable.isRegistrationDisabled(): Boolean { + return this is Failure.ServerError && error.code == MatrixError.M_FORBIDDEN && + httpCode == HttpsURLConnection.HTTP_FORBIDDEN +} + +fun Throwable.isWeakPassword(): Boolean { + return this is Failure.ServerError && error.code == MatrixError.M_WEAK_PASSWORD +} + +fun Throwable.isLoginEmailUnknown(): Boolean { + return this is Failure.ServerError && + error.code == MatrixError.M_FORBIDDEN && + error.message.isEmpty() +} + fun Throwable.isInvalidUIAAuth(): Boolean { return this is Failure.ServerError && error.code == MatrixError.M_FORBIDDEN && @@ -104,8 +128,8 @@ fun Throwable.isRegistrationAvailabilityError(): Boolean { return this is Failure.ServerError && httpCode == HttpsURLConnection.HTTP_BAD_REQUEST && /* 400 */ (error.code == MatrixError.M_USER_IN_USE || - error.code == MatrixError.M_INVALID_USERNAME || - error.code == MatrixError.M_EXCLUSIVE) + error.code == MatrixError.M_INVALID_USERNAME || + error.code == MatrixError.M_EXCLUSIVE) } /** diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index e7302cb1e21..a18b9e62e0d 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -18,11 +18,7 @@ package im.vector.app.features.onboarding import android.content.Context import android.net.Uri -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksViewModelFactory -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -239,31 +235,19 @@ class OnboardingViewModel @AssistedInject constructor( val safeLoginWizard = loginWizard if (safeLoginWizard == null) { - setState { - copy( - asyncLoginAction = Fail(Throwable("Bad configuration")) - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration"))) } else { - setState { - copy( - asyncLoginAction = Loading() - ) - } + setState { copy(isLoading = true) } currentJob = viewModelScope.launch { try { - safeLoginWizard.loginWithToken(action.loginToken) + val result = safeLoginWizard.loginWithToken(action.loginToken) + onSessionCreated(result, isAccountCreated = false) } catch (failure: Throwable) { + setState { copy(isLoading = false) } _viewEvents.post(OnboardingViewEvents.Failure(failure)) - setState { - copy( - asyncLoginAction = Fail(failure) - ) - } - null } - ?.let { onSessionCreated(it, isAccountCreated = false) } } } } @@ -271,7 +255,7 @@ class OnboardingViewModel @AssistedInject constructor( private fun handleRegisterAction(action: RegisterAction) { currentJob = viewModelScope.launch { if (action.hasLoadingState()) { - setState { copy(asyncRegistration = Loading()) } + setState { copy(isLoading = true) } } runCatching { registrationActionHandler.handleRegisterAction(registrationWizard, action) } .fold( @@ -292,7 +276,7 @@ class OnboardingViewModel @AssistedInject constructor( } } ) - setState { copy(asyncRegistration = Uninitialized) } + setState { copy(isLoading = false) } } } @@ -322,7 +306,7 @@ class OnboardingViewModel @AssistedInject constructor( authenticationService.reset() setState { copy( - asyncHomeServerLoginFlowRequest = Uninitialized, + isLoading = false, homeServerUrlFromUser = null, homeServerUrl = null, loginMode = LoginMode.Unknown, @@ -335,7 +319,7 @@ class OnboardingViewModel @AssistedInject constructor( OnboardingAction.ResetSignMode -> { setState { copy( - asyncHomeServerLoginFlowRequest = Uninitialized, + isLoading = false, signMode = SignMode.Unknown, loginMode = LoginMode.Unknown, loginModeSupportedTypes = emptyList() @@ -345,19 +329,13 @@ class OnboardingViewModel @AssistedInject constructor( OnboardingAction.ResetLogin -> { viewModelScope.launch { authenticationService.cancelPendingLoginOrRegistration() - setState { - copy( - asyncLoginAction = Uninitialized, - asyncRegistration = Uninitialized - ) - } + setState { copy(isLoading = false) } } } OnboardingAction.ResetResetPassword -> { setState { copy( - asyncResetPassword = Uninitialized, - asyncResetMailConfirmed = Uninitialized, + isLoading = false, resetPasswordEmail = null ) } @@ -426,35 +404,23 @@ class OnboardingViewModel @AssistedInject constructor( val safeLoginWizard = loginWizard if (safeLoginWizard == null) { - setState { - copy( - asyncResetPassword = Fail(Throwable("Bad configuration")), - asyncResetMailConfirmed = Uninitialized - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration"))) } else { - setState { - copy( - asyncResetPassword = Loading(), - asyncResetMailConfirmed = Uninitialized - ) - } + setState { copy(isLoading = true) } currentJob = viewModelScope.launch { try { safeLoginWizard.resetPassword(action.email, action.newPassword) } catch (failure: Throwable) { - setState { - copy( - asyncResetPassword = Fail(failure) - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(failure)) return@launch } setState { copy( - asyncResetPassword = Success(Unit), + isLoading = false, resetPasswordEmail = action.email ) } @@ -468,34 +434,22 @@ class OnboardingViewModel @AssistedInject constructor( val safeLoginWizard = loginWizard if (safeLoginWizard == null) { - setState { - copy( - asyncResetPassword = Uninitialized, - asyncResetMailConfirmed = Fail(Throwable("Bad configuration")) - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration"))) } else { - setState { - copy( - asyncResetPassword = Uninitialized, - asyncResetMailConfirmed = Loading() - ) - } + setState { copy(isLoading = false) } currentJob = viewModelScope.launch { try { safeLoginWizard.resetPasswordMailConfirmed() } catch (failure: Throwable) { - setState { - copy( - asyncResetMailConfirmed = Fail(failure) - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(failure)) return@launch } setState { copy( - asyncResetMailConfirmed = Success(Unit), + isLoading = false, resetPasswordEmail = null ) } @@ -515,11 +469,7 @@ class OnboardingViewModel @AssistedInject constructor( } private fun handleDirectLogin(action: OnboardingAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) { - setState { - copy( - asyncLoginAction = Loading() - ) - } + setState { copy(isLoading = true) } currentJob = viewModelScope.launch { val data = try { @@ -546,11 +496,7 @@ class OnboardingViewModel @AssistedInject constructor( } private fun onWellKnownError() { - setState { - copy( - asyncLoginAction = Uninitialized - ) - } + setState { copy(isLoading = false) } _viewEvents.post(OnboardingViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error)))) } @@ -585,20 +531,12 @@ class OnboardingViewModel @AssistedInject constructor( when (failure) { is MatrixIdFailure.InvalidMatrixId, is Failure.UnrecognizedCertificateFailure -> { + setState { copy(isLoading = false) } // Display this error in a dialog _viewEvents.post(OnboardingViewEvents.Failure(failure)) - setState { - copy( - asyncLoginAction = Uninitialized - ) - } } else -> { - setState { - copy( - asyncLoginAction = Fail(failure) - ) - } + setState { copy(isLoading = false) } } } } @@ -607,37 +545,23 @@ class OnboardingViewModel @AssistedInject constructor( val safeLoginWizard = loginWizard if (safeLoginWizard == null) { - setState { - copy( - asyncLoginAction = Fail(Throwable("Bad configuration")) - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration"))) } else { - setState { - copy( - asyncLoginAction = Loading() - ) - } - + setState { copy(isLoading = true) } currentJob = viewModelScope.launch { try { - safeLoginWizard.login( + val result = safeLoginWizard.login( action.username, action.password, action.initialDeviceName ) + reAuthHelper.data = action.password + onSessionCreated(result, isAccountCreated = false) } catch (failure: Throwable) { - setState { - copy( - asyncLoginAction = Fail(failure) - ) - } - null + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(failure)) } - ?.let { - reAuthHelper.data = action.password - onSessionCreated(it, isAccountCreated = false) - } } } } @@ -678,12 +602,12 @@ class OnboardingViewModel @AssistedInject constructor( true -> { val personalizationState = createPersonalizationState(session, state) setState { - copy(asyncLoginAction = Success(Unit), personalizationState = personalizationState) + copy(isLoading = false, personalizationState = personalizationState) } _viewEvents.post(OnboardingViewEvents.OnAccountCreated) } false -> { - setState { copy(asyncLoginAction = Success(Unit)) } + setState { copy(isLoading = false) } _viewEvents.post(OnboardingViewEvents.OnAccountSignedIn) } } @@ -712,14 +636,11 @@ class OnboardingViewModel @AssistedInject constructor( } else { currentJob = viewModelScope.launch { try { - authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials) + val result = authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials) + onSessionCreated(result, isAccountCreated = false) } catch (failure: Throwable) { - setState { - copy(asyncLoginAction = Fail(failure)) - } - null + setState { copy(isLoading = false) } } - ?.let { onSessionCreated(it, isAccountCreated = false) } } } } @@ -743,7 +664,7 @@ class OnboardingViewModel @AssistedInject constructor( setState { copy( - asyncHomeServerLoginFlowRequest = Loading(), + isLoading = true, // If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg // It is also useful to set the value again in the case of a certificate error on matrix.org serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) { @@ -757,14 +678,14 @@ class OnboardingViewModel @AssistedInject constructor( val data = try { authenticationService.getLoginFlow(homeServerConnectionConfig) } catch (failure: Throwable) { - _viewEvents.post(OnboardingViewEvents.Failure(failure)) setState { copy( - asyncHomeServerLoginFlowRequest = Uninitialized, + isLoading = false, // If we were trying to retrieve matrix.org login flow, also reset the serverType serverType = if (serverType == ServerType.MatrixOrg) ServerType.Unknown else serverType ) } + _viewEvents.post(OnboardingViewEvents.Failure(failure)) null } @@ -785,7 +706,7 @@ class OnboardingViewModel @AssistedInject constructor( setState { copy( - asyncHomeServerLoginFlowRequest = Uninitialized, + isLoading = false, homeServerUrlFromUser = homeServerConnectionConfig.homeServerUri.toString(), homeServerUrl = data.homeServerUrl, loginMode = loginMode, @@ -828,20 +749,20 @@ class OnboardingViewModel @AssistedInject constructor( } private fun updateDisplayName(displayName: String) { - setState { copy(asyncDisplayName = Loading()) } + setState { copy(isLoading = true) } viewModelScope.launch { val activeSession = activeSessionHolder.getActiveSession() try { activeSession.setDisplayName(activeSession.myUserId, displayName) setState { copy( - asyncDisplayName = Success(Unit), + isLoading = false, personalizationState = personalizationState.copy(displayName = displayName) ) } handleDisplayNameStepComplete() } catch (error: Throwable) { - setState { copy(asyncDisplayName = Fail(error)) } + setState { copy(isLoading = false) } _viewEvents.post(OnboardingViewEvents.Failure(error)) } } @@ -883,7 +804,7 @@ class OnboardingViewModel @AssistedInject constructor( when (val pictureUri = state.personalizationState.selectedPictureUri) { null -> _viewEvents.post(OnboardingViewEvents.Failure(NullPointerException("picture uri is missing from state"))) else -> { - setState { copy(asyncProfilePicture = Loading()) } + setState { copy(isLoading = true) } viewModelScope.launch { val activeSession = activeSessionHolder.getActiveSession() try { @@ -894,12 +815,12 @@ class OnboardingViewModel @AssistedInject constructor( ) setState { copy( - asyncProfilePicture = Success(Unit), + isLoading = false, ) } onProfilePictureSaved() } catch (error: Throwable) { - setState { copy(asyncProfilePicture = Fail(error)) } + setState { copy(isLoading = false) } _viewEvents.post(OnboardingViewEvents.Failure(error)) } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt index 8747de6da89..b98e8116798 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt @@ -18,24 +18,15 @@ package im.vector.app.features.onboarding import android.net.Uri import android.os.Parcelable -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.PersistState -import com.airbnb.mvrx.Uninitialized import im.vector.app.features.login.LoginMode import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import kotlinx.parcelize.Parcelize data class OnboardingViewState( - val asyncLoginAction: Async = Uninitialized, - val asyncHomeServerLoginFlowRequest: Async = Uninitialized, - val asyncResetPassword: Async = Uninitialized, - val asyncResetMailConfirmed: Async = Uninitialized, - val asyncRegistration: Async = Uninitialized, - val asyncDisplayName: Async = Uninitialized, - val asyncProfilePicture: Async = Uninitialized, + val isLoading: Boolean = false, @PersistState val onboardingFlow: OnboardingFlow? = null, @@ -71,18 +62,7 @@ data class OnboardingViewState( @PersistState val personalizationState: PersonalizationState = PersonalizationState() -) : MavericksState { - - fun isLoading(): Boolean { - return asyncLoginAction is Loading || - asyncHomeServerLoginFlowRequest is Loading || - asyncResetPassword is Loading || - asyncResetMailConfirmed is Loading || - asyncRegistration is Loading || - asyncDisplayName is Loading || - asyncProfilePicture is Loading - } -} +) : MavericksState enum class OnboardingFlow { SignIn, diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt index f8f6f6cefac..64e29766c5e 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt @@ -34,8 +34,6 @@ import im.vector.app.features.onboarding.OnboardingViewModel import im.vector.app.features.onboarding.OnboardingViewState import kotlinx.coroutines.CancellationException import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixError -import javax.net.ssl.HttpsURLConnection /** * Parent Fragment for all the login/registration screens @@ -85,21 +83,8 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment /* Ignore this error, user has cancelled the action */ Unit - is Failure.ServerError -> - if (throwable.error.code == MatrixError.M_FORBIDDEN && - throwable.httpCode == HttpsURLConnection.HTTP_FORBIDDEN /* 403 */) { - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.dialog_title_error) - .setMessage(getString(R.string.login_registration_disabled)) - .setPositiveButton(R.string.ok, null) - .show() - } else { - onError(throwable) - } - is Failure.UnrecognizedCertificateFailure -> - showUnrecognizedCertificateFailure(throwable) - else -> - onError(throwable) + is Failure.UnrecognizedCertificateFailure -> showUnrecognizedCertificateFailure(throwable) + else -> onError(throwable) } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt index dacd8feab32..53c9fd4fcce 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt @@ -26,8 +26,7 @@ import androidx.autofill.HintConstants import androidx.core.text.isDigitsOnly import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading +import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hidePassword @@ -45,9 +44,12 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.isInvalidPassword +import org.matrix.android.sdk.api.failure.isInvalidUsername +import org.matrix.android.sdk.api.failure.isLoginEmailUnknown +import org.matrix.android.sdk.api.failure.isRegistrationDisabled +import org.matrix.android.sdk.api.failure.isUsernameInUse +import org.matrix.android.sdk.api.failure.isWeakPassword import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject @@ -256,12 +258,31 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< } override fun onError(throwable: Throwable) { - // Show M_WEAK_PASSWORD error in the password field - if (throwable is Failure.ServerError && - throwable.error.code == MatrixError.M_WEAK_PASSWORD) { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) - } else { - views.loginFieldTil.error = errorFormatter.toHumanReadable(throwable) + // Trick to display the error without text. + views.loginFieldTil.error = " " + when { + throwable.isUsernameInUse() || throwable.isInvalidUsername() -> { + views.loginFieldTil.error = errorFormatter.toHumanReadable(throwable) + } + throwable.isLoginEmailUnknown() -> { + views.loginFieldTil.error = getString(R.string.login_login_with_email_error) + } + throwable.isInvalidPassword() && spaceInPassword() -> { + views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) + } + throwable.isWeakPassword() || throwable.isInvalidPassword() -> { + views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) + } + throwable.isRegistrationDisabled() -> { + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.dialog_title_error) + .setMessage(getString(R.string.login_registration_disabled)) + .setPositiveButton(R.string.ok, null) + .show() + } + else -> { + super.onError(throwable) + } } } @@ -274,39 +295,9 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< setupSocialLoginButtons(state) setupButtons(state) - when (state.asyncLoginAction) { - is Loading -> { - // Ensure password is hidden - views.passwordField.hidePassword() - } - is Fail -> { - val error = state.asyncLoginAction.error - if (error is Failure.ServerError && - error.error.code == MatrixError.M_FORBIDDEN && - error.error.message.isEmpty()) { - // Login with email, but email unknown - views.loginFieldTil.error = getString(R.string.login_login_with_email_error) - } else { - // Trick to display the error without text. - views.loginFieldTil.error = " " - if (error.isInvalidPassword() && spaceInPassword()) { - views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) - } else { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(error) - } - } - } - // Success is handled by the LoginActivity - else -> Unit - } - - when (state.asyncRegistration) { - is Loading -> { - // Ensure password is hidden - views.passwordField.hidePassword() - } - // Success is handled by the LoginActivity - else -> Unit + if (state.isLoading) { + // Ensure password is hidden + views.passwordField.hidePassword() } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt index 073801c9203..b612ec34b57 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt @@ -21,8 +21,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.lifecycle.lifecycleScope -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard @@ -53,10 +51,13 @@ class FtueAuthResetPasswordFragment @Inject constructor() : AbstractFtueAuthFrag override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupSubmitButton() } + override fun onError(throwable: Throwable) { + views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(throwable) + } + private fun setupUi(state: OnboardingViewState) { views.resetPasswordTitle.text = getString(R.string.login_reset_password_on, state.homeServerUrlFromUser.toReducedUrl()) } @@ -115,16 +116,9 @@ class FtueAuthResetPasswordFragment @Inject constructor() : AbstractFtueAuthFrag override fun updateWithState(state: OnboardingViewState) { setupUi(state) - - when (state.asyncResetPassword) { - is Loading -> { - // Ensure new password is hidden - views.passwordField.hidePassword() - } - is Fail -> { - views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error) - } - else -> Unit + if (state.isLoading) { + // Ensure new password is hidden + views.passwordField.hidePassword() } } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt index f8b3266d377..f6141a4900d 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt @@ -20,7 +20,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.airbnb.mvrx.Fail import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.databinding.FragmentLoginResetPasswordMailConfirmationBinding @@ -58,23 +57,20 @@ class FtueAuthResetPasswordMailConfirmationFragment @Inject constructor() : Abst override fun updateWithState(state: OnboardingViewState) { setupUi(state) + } - when (state.asyncResetMailConfirmed) { - is Fail -> { - // Link in email not yet clicked ? - val message = if (state.asyncResetMailConfirmed.error.is401()) { - getString(R.string.auth_reset_password_error_unauthorized) - } else { - errorFormatter.toHumanReadable(state.asyncResetMailConfirmed.error) - } - - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.dialog_title_error) - .setMessage(message) - .setPositiveButton(R.string.ok, null) - .show() - } - else -> Unit + override fun onError(throwable: Throwable) { + // Link in email not yet clicked ? + val message = if (throwable.is401()) { + getString(R.string.auth_reset_password_error_unauthorized) + } else { + errorFormatter.toHumanReadable(throwable) } + + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.dialog_title_error) + .setMessage(message) + .setPositiveButton(R.string.ok, null) + .show() } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index 13b5f610100..2b3a43df5d4 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -121,7 +121,7 @@ class FtueAuthVariant( private fun updateWithState(viewState: OnboardingViewState) { isForceLoginFallbackEnabled = viewState.isForceLoginFallbackEnabled - views.loginLoading.isVisible = viewState.isLoading() + views.loginLoading.isVisible = viewState.isLoading } override fun setIsLoading(isLoading: Boolean) = Unit diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index 4fd079611d9..df4e0de65e6 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -17,10 +17,6 @@ package im.vector.app.features.onboarding import android.net.Uri -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.test.MvRxTestRule import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.SignMode @@ -129,8 +125,8 @@ class OnboardingViewModelTest { .assertStatesChanges( initialState, { copy(signMode = SignMode.SignUp) }, - { copy(asyncRegistration = Loading()) }, - { copy(asyncRegistration = Uninitialized) } + { copy(isLoading = true) }, + { copy(isLoading = false) } ) .assertEvents(OnboardingViewEvents.RegistrationFlowResult(ANY_CONTINUING_REGISTRATION_RESULT.flowResult, isRegistrationStarted = true)) .finish() @@ -146,8 +142,8 @@ class OnboardingViewModelTest { test .assertStatesChanges( initialState, - { copy(asyncRegistration = Loading()) }, - { copy(asyncRegistration = Uninitialized) } + { copy(isLoading = true) }, + { copy(isLoading = false) } ) .assertEvents(OnboardingViewEvents.RegistrationFlowResult(ANY_CONTINUING_REGISTRATION_RESULT.flowResult, isRegistrationStarted = true)) .finish() @@ -176,8 +172,8 @@ class OnboardingViewModelTest { test .assertStatesChanges( initialState, - { copy(asyncRegistration = Loading()) }, - { copy(asyncRegistration = Uninitialized) } + { copy(isLoading = true) }, + { copy(isLoading = false) } ) .assertNoEvents() .finish() @@ -194,9 +190,8 @@ class OnboardingViewModelTest { test .assertStatesChanges( initialState, - { copy(asyncRegistration = Loading()) }, - { copy(asyncLoginAction = Success(Unit), personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState()) }, - { copy(asyncLoginAction = Success(Unit), asyncRegistration = Uninitialized) } + { copy(isLoading = true) }, + { copy(isLoading = false, personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState()) } ) .assertEvents(OnboardingViewEvents.OnAccountCreated) .finish() @@ -212,9 +207,8 @@ class OnboardingViewModelTest { test .assertStatesChanges( initialState, - { copy(asyncRegistration = Loading()) }, - { copy(asyncLoginAction = Success(Unit), personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState()) }, - { copy(asyncRegistration = Uninitialized) } + { copy(isLoading = true) }, + { copy(isLoading = false, personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState()) } ) .assertEvents(OnboardingViewEvents.OnAccountCreated) .finish() @@ -260,8 +254,8 @@ class OnboardingViewModelTest { test .assertStatesChanges( initialState, - { copy(asyncDisplayName = Loading()) }, - { copy(asyncDisplayName = Fail(AN_ERROR)) }, + { copy(isLoading = true) }, + { copy(isLoading = false) }, ) .assertEvents(OnboardingViewEvents.Failure(AN_ERROR)) .finish() @@ -307,7 +301,7 @@ class OnboardingViewModelTest { viewModel.handle(OnboardingAction.SaveSelectedProfilePicture) test - .assertStates(expectedProfilePictureFailureStates(initialStateWithPicture, AN_ERROR)) + .assertStates(expectedProfilePictureFailureStates(initialStateWithPicture)) .assertEvents(OnboardingViewEvents.Failure(AN_ERROR)) .finish() } @@ -362,20 +356,20 @@ class OnboardingViewModelTest { private fun expectedProfilePictureSuccessStates(state: OnboardingViewState) = listOf( state, - state.copy(asyncProfilePicture = Loading()), - state.copy(asyncProfilePicture = Success(Unit)) + state.copy(isLoading = true), + state.copy(isLoading = false) ) - private fun expectedProfilePictureFailureStates(state: OnboardingViewState, cause: Exception) = listOf( + private fun expectedProfilePictureFailureStates(state: OnboardingViewState) = listOf( state, - state.copy(asyncProfilePicture = Loading()), - state.copy(asyncProfilePicture = Fail(cause)) + state.copy(isLoading = true), + state.copy(isLoading = false) ) private fun expectedSuccessfulDisplayNameUpdateStates(): List OnboardingViewState> { return listOf( - { copy(asyncDisplayName = Loading()) }, - { copy(asyncDisplayName = Success(Unit), personalizationState = personalizationState.copy(displayName = A_DISPLAY_NAME)) } + { copy(isLoading = true) }, + { copy(isLoading = false, personalizationState = personalizationState.copy(displayName = A_DISPLAY_NAME)) } ) }