replacing async login/register state with separate failure view event and shared isLoading

This commit is contained in:
Adam Brown 2022-03-08 11:41:32 +00:00
parent e3df9c4cef
commit 2227df479c
4 changed files with 67 additions and 129 deletions

View file

@ -239,31 +239,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) {
_viewEvents.post(OnboardingViewEvents.Failure(failure))
setState {
copy(
asyncLoginAction = Fail(failure)
)
}
null
setState { copy(isLoading = false) }
}
?.let { onSessionCreated(it, isAccountCreated = false) }
}
}
}
@ -271,7 +259,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 +280,7 @@ class OnboardingViewModel @AssistedInject constructor(
}
}
)
setState { copy(asyncRegistration = Uninitialized) }
setState { copy(isLoading = false) }
}
}
@ -345,12 +333,7 @@ class OnboardingViewModel @AssistedInject constructor(
OnboardingAction.ResetLogin -> {
viewModelScope.launch {
authenticationService.cancelPendingLoginOrRegistration()
setState {
copy(
asyncLoginAction = Uninitialized,
asyncRegistration = Uninitialized
)
}
setState { copy(isLoading = false) }
}
}
OnboardingAction.ResetResetPassword -> {
@ -515,11 +498,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 +525,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))))
}
@ -587,18 +562,10 @@ class OnboardingViewModel @AssistedInject constructor(
is Failure.UnrecognizedCertificateFailure -> {
// Display this error in a dialog
_viewEvents.post(OnboardingViewEvents.Failure(failure))
setState {
copy(
asyncLoginAction = Uninitialized
)
}
setState { copy(isLoading = false) }
}
else -> {
setState {
copy(
asyncLoginAction = Fail(failure)
)
}
setState { copy(isLoading = false) }
}
}
}
@ -607,37 +574,22 @@ 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) }
}
?.let {
reAuthHelper.data = action.password
onSessionCreated(it, isAccountCreated = false)
}
}
}
}
@ -678,12 +630,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 +664,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) }
}
}
}

View file

@ -29,11 +29,9 @@ 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 isLoading: Boolean = false,
@PersistState
@ -73,11 +71,9 @@ data class OnboardingViewState(
) : MavericksState {
fun legacyIsLoading(): Boolean {
return asyncLoginAction is Loading ||
asyncHomeServerLoginFlowRequest is Loading ||
return asyncHomeServerLoginFlowRequest is Loading ||
asyncResetPassword is Loading ||
asyncResetMailConfirmed is Loading ||
asyncRegistration is Loading
asyncResetMailConfirmed is Loading
}
}

View file

@ -26,8 +26,6 @@ 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 im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.hidePassword
@ -74,6 +72,33 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.viewEvents
.stream()
.onEach {
when (it) {
is OnboardingViewEvents.Failure -> {
if (it.throwable is Failure.ServerError &&
it.throwable.error.code == MatrixError.M_FORBIDDEN &&
it.throwable.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 (it.throwable.isInvalidPassword() && spaceInPassword()) {
views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password)
} else {
views.passwordFieldTil.error = errorFormatter.toHumanReadable(it.throwable)
}
}
}
else -> {
// do nothing
}
}
}
.launchIn(lifecycleScope)
setupSubmitButton()
setupForgottenPasswordButton()
@ -274,39 +299,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

@ -128,8 +128,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()
@ -145,8 +145,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()
@ -175,8 +175,8 @@ class OnboardingViewModelTest {
test
.assertStatesChanges(
initialState,
{ copy(asyncRegistration = Loading()) },
{ copy(asyncRegistration = Uninitialized) }
{ copy(isLoading = true) },
{ copy(isLoading = false) }
)
.assertNoEvents()
.finish()
@ -193,9 +193,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()
@ -211,9 +210,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()