Merge pull request #5517 from vector-im/feature/adm/ftue-single-loading-state

FTUE - Single loading state
This commit is contained in:
Adam Brown 2022-03-24 10:36:07 +00:00 committed by GitHub
commit 84b34f75de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 157 additions and 271 deletions

1
changelog.d/5517.misc Normal file
View file

@ -0,0 +1 @@
Flattening the asynchronous onboarding state and passing all errors through the same pipeline

View file

@ -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)
}
/**

View file

@ -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))
}
}

View file

@ -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<Unit> = Uninitialized,
val asyncHomeServerLoginFlowRequest: Async<Unit> = Uninitialized,
val asyncResetPassword: Async<Unit> = Uninitialized,
val asyncResetMailConfirmed: Async<Unit> = Uninitialized,
val asyncRegistration: Async<Unit> = Uninitialized,
val asyncDisplayName: Async<Unit> = Uninitialized,
val asyncProfilePicture: Async<Unit> = 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,

View file

@ -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<VB : ViewBinding> : VectorBaseFragment<V
is CancellationException ->
/* 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)
}
}

View file

@ -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()
}
}

View file

@ -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()
}
}
}

View file

@ -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()
}
}

View file

@ -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

View file

@ -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.() -> 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)) }
)
}