mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Submit the exact account provider field text on continue
This commit is contained in:
+4
-1
@@ -10,6 +10,9 @@ package io.element.android.features.login.impl.screens.confirmaccountprovider
|
||||
|
||||
sealed interface ConfirmAccountProviderEvents {
|
||||
data class UserInputChanged(val accountProvider: String) : ConfirmAccountProviderEvents
|
||||
data object Continue : ConfirmAccountProviderEvents
|
||||
|
||||
// Carries the account provider to submit (the current field text, or the accepted autocomplete suggestion),
|
||||
// captured from the view at click time so it can't lag behind fast input (e.g. keyboard autofill).
|
||||
data class Continue(val accountProvider: String) : ConfirmAccountProviderEvents
|
||||
data object ClearError : ConfirmAccountProviderEvents
|
||||
}
|
||||
|
||||
+11
-7
@@ -67,9 +67,10 @@ class ConfirmAccountProviderPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
// Continue accepts the suggestion when one is offered, otherwise the raw input.
|
||||
val accountProviderToSubmit = accountProviderSuggestion ?: accountProviderInput
|
||||
val latestAccountProviderToSubmit by rememberUpdatedState(accountProviderToSubmit)
|
||||
// The account provider submitted via Continue (carried by the event, so it reflects the exact field
|
||||
// text at click time); used to actually sign in once validation succeeds.
|
||||
var submittedAccountProvider by remember { mutableStateOf<String?>(null) }
|
||||
val latestSubmittedAccountProvider by rememberUpdatedState(submittedAccountProvider)
|
||||
|
||||
// Once the chosen account provider has been validated and persisted, proceed with the actual sign in / sign up.
|
||||
LaunchedEffect(changeServerState.changeServerAction) {
|
||||
@@ -77,7 +78,7 @@ class ConfirmAccountProviderPresenter(
|
||||
loginModeState.eventSink(
|
||||
LoginModeEvent.Submit(
|
||||
isAccountCreation = params.isAccountCreation,
|
||||
homeserverUrl = latestAccountProviderToSubmit.trim(),
|
||||
homeserverUrl = latestSubmittedAccountProvider.orEmpty().trim(),
|
||||
resolvedHomeserverUrl = null,
|
||||
loginHint = null,
|
||||
)
|
||||
@@ -92,9 +93,12 @@ class ConfirmAccountProviderPresenter(
|
||||
}
|
||||
// Validate (and persist) the chosen account provider before proceeding. This also enforces the
|
||||
// account-provider access control, which the login submit does not run on its own.
|
||||
ConfirmAccountProviderEvents.Continue -> changeServerState.eventSink(
|
||||
ChangeServerEvents.ChangeServer(AccountProvider(url = accountProviderToSubmit.trim()))
|
||||
)
|
||||
is ConfirmAccountProviderEvents.Continue -> {
|
||||
submittedAccountProvider = event.accountProvider
|
||||
changeServerState.eventSink(
|
||||
ChangeServerEvents.ChangeServer(AccountProvider(url = event.accountProvider.trim()))
|
||||
)
|
||||
}
|
||||
ConfirmAccountProviderEvents.ClearError -> {
|
||||
loginModeState.eventSink(LoginModeEvent.ClearError)
|
||||
changeServerState.eventSink(ChangeServerEvents.ClearError)
|
||||
|
||||
+11
-7
@@ -81,10 +81,20 @@ fun ConfirmAccountProviderView(
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
|
||||
var input by textFieldState(stateValue = state.accountProviderInput)
|
||||
// Inline autocomplete: the un-typed remainder of the suggested account provider (rendered in grey and
|
||||
// accepted on Continue). Only valid while the suggestion still starts with the current input.
|
||||
val suggestionSuffix = state.accountProviderSuggestion
|
||||
?.takeIf { it.length > input.length && it.startsWith(input, ignoreCase = true) }
|
||||
?.substring(input.length)
|
||||
.orEmpty()
|
||||
|
||||
fun submit() {
|
||||
// Dismiss the keyboard and release focus while the account provider is being validated.
|
||||
focusManager.clearFocus(force = true)
|
||||
eventSink(ConfirmAccountProviderEvents.Continue)
|
||||
// Submit the exact field text (plus any accepted suggestion), read here so it can't lag behind
|
||||
// fast input such as keyboard autofill.
|
||||
eventSink(ConfirmAccountProviderEvents.Continue(input + suggestionSuffix))
|
||||
}
|
||||
|
||||
// Once a validation / login error has been dismissed, return focus and the keyboard to the field so
|
||||
@@ -128,12 +138,6 @@ fun ConfirmAccountProviderView(
|
||||
}
|
||||
}
|
||||
) {
|
||||
var input by textFieldState(stateValue = state.accountProviderInput)
|
||||
// Inline autocomplete: render the un-typed remainder of the suggested account provider in grey.
|
||||
val suggestionSuffix = state.accountProviderSuggestion
|
||||
?.takeIf { it.length > input.length && it.startsWith(input, ignoreCase = true) }
|
||||
?.substring(input.length)
|
||||
.orEmpty()
|
||||
val ghostColor = ElementTheme.colors.textSecondary
|
||||
val ghostTransformation = remember(suggestionSuffix, ghostColor) {
|
||||
GhostSuffixVisualTransformation(suggestionSuffix, ghostColor)
|
||||
|
||||
+14
-14
@@ -70,7 +70,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
||||
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isEqualTo(LoginMode.PasswordLogin)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
@@ -89,7 +89,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
||||
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
@@ -110,7 +110,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
||||
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||
authenticationService.givenOAuthCancelError(AN_EXCEPTION)
|
||||
@@ -135,7 +135,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
||||
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||
defaultOAuthActionFlow.post(OAuthAction.GoBack())
|
||||
@@ -159,7 +159,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
||||
awaitLoginMode { it is AsyncData.Loading }
|
||||
defaultOAuthActionFlow.post(OAuthAction.GoBack(toUnblock = true))
|
||||
val cancelFinalState = awaitLoginMode { it is AsyncData.Uninitialized }
|
||||
@@ -182,7 +182,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
||||
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||
authenticationService.givenLoginError(AN_EXCEPTION)
|
||||
@@ -207,7 +207,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
||||
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||
defaultOidcActionFlow.post(OAuthAction.Success("aUrl"))
|
||||
@@ -229,7 +229,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
||||
// The account provider validation fails, so the login is never attempted.
|
||||
val failureState = awaitState { it.changeServerState.changeServerAction is AsyncData.Failure }
|
||||
assertThat(failureState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized)
|
||||
@@ -251,7 +251,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||
val initialState = awaitItem()
|
||||
|
||||
// Submit will return an error while validating the account provider
|
||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
||||
|
||||
// Check an error was returned
|
||||
val submittedState = awaitState { it.changeServerState.changeServerAction is AsyncData.Failure }
|
||||
@@ -279,7 +279,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
||||
// Check an error was returned
|
||||
val submittedState = awaitLoginMode { it is AsyncData.Failure }
|
||||
assertThat(submittedState.loginModeState.loginMode.errorOrNull()).isInstanceOf(AccountCreationNotSupported::class.java)
|
||||
@@ -304,7 +304,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
||||
val submittedState = awaitLoginMode { it is AsyncData.Success }
|
||||
assertThat(submittedState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
@@ -326,7 +326,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
||||
val submittedState = awaitLoginMode { it is AsyncData.Success }
|
||||
assertThat(submittedState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
@@ -348,7 +348,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
|
||||
val submittedState = awaitLoginMode { it is AsyncData.Success }
|
||||
assertThat(submittedState.loginModeState.loginMode.dataOrNull()).isEqualTo(LoginMode.AccountCreation(aUrl))
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
@@ -390,7 +390,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(ConfirmAccountProviderEvents.UserInputChanged("https://random"))
|
||||
val suggestionState = awaitState { it.accountProviderSuggestion != null }
|
||||
suggestionState.eventSink(ConfirmAccountProviderEvents.Continue)
|
||||
suggestionState.eventSink(ConfirmAccountProviderEvents.Continue("https://randomcommunity.org"))
|
||||
awaitLoginMode { it is AsyncData.Success }
|
||||
assertThat(submittedUrls.first()).isEqualTo("https://randomcommunity.org")
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
|
||||
Reference in New Issue
Block a user