ConfirmAccountProviderEvents -> ConfirmAccountProviderEvent

This commit is contained in:
Benoit Marty
2026-08-25 10:01:42 +02:00
parent 1fd102ffc3
commit 873843b669
6 changed files with 43 additions and 43 deletions
@@ -8,11 +8,11 @@
package io.element.android.features.login.impl.screens.confirmaccountprovider
sealed interface ConfirmAccountProviderEvents {
data class UserInputChanged(val accountProvider: String) : ConfirmAccountProviderEvents
sealed interface ConfirmAccountProviderEvent {
data class UserInputChanged(val accountProvider: String) : ConfirmAccountProviderEvent
// 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
data class Continue(val accountProvider: String) : ConfirmAccountProviderEvent
data object ClearError : ConfirmAccountProviderEvent
}
@@ -110,14 +110,14 @@ class ConfirmAccountProviderPresenter(
}
}
fun handleEvent(event: ConfirmAccountProviderEvents) {
fun handleEvent(event: ConfirmAccountProviderEvent) {
when (event) {
is ConfirmAccountProviderEvents.UserInputChanged -> {
is ConfirmAccountProviderEvent.UserInputChanged -> {
userInput = event.accountProvider
}
// 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.
is ConfirmAccountProviderEvents.Continue -> {
is ConfirmAccountProviderEvent.Continue -> {
// Apply the accepted account provider (any accepted completion) back into the field so it
// renders the full server rather than the typed prefix, and survives the OAuth round-trip.
val accountProvider = event.accountProvider.trim()
@@ -137,7 +137,7 @@ class ConfirmAccountProviderPresenter(
ChangeServerEvent.ChangeServer(AccountProvider(url = accountProviderUrl))
)
}
ConfirmAccountProviderEvents.ClearError -> {
ConfirmAccountProviderEvent.ClearError -> {
loginModeState.eventSink(LoginModeEvent.ClearError)
changeServerState.eventSink(ChangeServerEvent.ClearError)
}
@@ -19,7 +19,7 @@ data class ConfirmAccountProviderState(
val isAccountCreation: Boolean,
val loginModeState: LoginModeState,
val changeServerState: ChangeServerState,
val eventSink: (ConfirmAccountProviderEvents) -> Unit
val eventSink: (ConfirmAccountProviderEvent) -> Unit
) {
val submitEnabled: Boolean
get() = accountProviderInput.isNotBlank() &&
@@ -40,7 +40,7 @@ private fun aConfirmAccountProviderState(
isAccountCreation: Boolean = false,
loginModeState: LoginModeState = aLoginModeState(),
changeServerState: ChangeServerState = aChangeServerState(),
eventSink: (ConfirmAccountProviderEvents) -> Unit = {},
eventSink: (ConfirmAccountProviderEvent) -> Unit = {},
) = ConfirmAccountProviderState(
accountProviderInput = accountProviderInput,
accountProviderSuggestion = accountProviderSuggestion,
@@ -99,7 +99,7 @@ fun ConfirmAccountProviderView(
// Dismiss the keyboard and release focus while the account provider is being validated.
focusManager.clearFocus(force = true)
// Submit the exact field text (plus any accepted suggestion).
eventSink(ConfirmAccountProviderEvents.Continue(accountProviderToSubmit))
eventSink(ConfirmAccountProviderEvent.Continue(accountProviderToSubmit))
}
// Once a validation / login error has been dismissed, return focus and the keyboard to the field so
@@ -151,7 +151,7 @@ fun ConfirmAccountProviderView(
value = input,
onValueChange = {
input = it
eventSink(ConfirmAccountProviderEvents.UserInputChanged(it))
eventSink(ConfirmAccountProviderEvent.UserInputChanged(it))
},
modifier = Modifier
.fillMaxWidth()
@@ -181,7 +181,7 @@ fun ConfirmAccountProviderView(
// showing, Done submits directly.
val accepted = input + suggestionSuffix
input = accepted
eventSink(ConfirmAccountProviderEvents.UserInputChanged(accepted))
eventSink(ConfirmAccountProviderEvent.UserInputChanged(accepted))
focusManager.clearFocus(force = true)
} else {
submit()
@@ -197,7 +197,7 @@ fun ConfirmAccountProviderView(
role = Role.Button,
) {
input = ""
eventSink(ConfirmAccountProviderEvents.UserInputChanged(""))
eventSink(ConfirmAccountProviderEvent.UserInputChanged(""))
}
) {
Icon(
@@ -222,7 +222,7 @@ fun ConfirmAccountProviderView(
LoginModeView(
loginMode = state.loginModeState.loginMode,
onClearError = { eventSink(ConfirmAccountProviderEvents.ClearError) },
onClearError = { eventSink(ConfirmAccountProviderEvent.ClearError) },
onLearnMoreClick = onLearnMoreClick,
onOAuthDetails = onOAuthDetails,
onNeedLoginPassword = onNeedLoginPassword,
@@ -68,7 +68,7 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
val successState = awaitLoginMode { it is AsyncData.Success }
assertThat(successState.loginModeState.loginMode.dataOrNull()).isEqualTo(LoginMode.PasswordLogin)
cancelAndIgnoreRemainingEvents()
@@ -87,7 +87,7 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
val successState = awaitLoginMode { it is AsyncData.Success }
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
cancelAndIgnoreRemainingEvents()
@@ -108,7 +108,7 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
initialState.eventSink.invoke(ConfirmAccountProviderEvent.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)
@@ -133,7 +133,7 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
initialState.eventSink.invoke(ConfirmAccountProviderEvent.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())
@@ -157,7 +157,7 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
awaitLoginMode { it is AsyncData.Loading }
defaultOAuthActionFlow.post(OAuthAction.GoBack(toUnblock = true))
val cancelFinalState = awaitLoginMode { it is AsyncData.Uninitialized }
@@ -180,7 +180,7 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
initialState.eventSink.invoke(ConfirmAccountProviderEvent.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)
@@ -205,7 +205,7 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
initialState.eventSink.invoke(ConfirmAccountProviderEvent.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"))
@@ -227,7 +227,7 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
initialState.eventSink.invoke(ConfirmAccountProviderEvent.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)
@@ -249,13 +249,13 @@ class ConfirmAccountProviderPresenterTest {
val initialState = awaitItem()
// Submit will return an error while validating the account provider
initialState.eventSink(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
initialState.eventSink(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
// Check an error was returned
val submittedState = awaitState { it.changeServerState.changeServerAction is AsyncData.Failure }
// Assert the error is then cleared
submittedState.eventSink(ConfirmAccountProviderEvents.ClearError)
submittedState.eventSink(ConfirmAccountProviderEvent.ClearError)
val clearedState = awaitState { it.changeServerState.changeServerAction is AsyncData.Uninitialized }
assertThat(clearedState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized)
}
@@ -274,12 +274,12 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
initialState.eventSink(ConfirmAccountProviderEvent.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)
// Assert the error is then cleared
submittedState.eventSink(ConfirmAccountProviderEvents.ClearError)
submittedState.eventSink(ConfirmAccountProviderEvent.ClearError)
val clearedState = awaitLoginMode { it is AsyncData.Uninitialized }
assertThat(clearedState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized)
cancelAndIgnoreRemainingEvents()
@@ -299,7 +299,7 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
initialState.eventSink(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
val submittedState = awaitLoginMode { it is AsyncData.Success }
assertThat(submittedState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
cancelAndIgnoreRemainingEvents()
@@ -316,7 +316,7 @@ class ConfirmAccountProviderPresenterTest {
presenter.test {
val initialState = awaitItem()
assertThat(initialState.accountProviderSuggestion).isNull()
initialState.eventSink(ConfirmAccountProviderEvents.UserInputChanged("random"))
initialState.eventSink(ConfirmAccountProviderEvent.UserInputChanged("random"))
val suggestionState = awaitState { it.accountProviderInput == "random" }
// Completion is offered without the https:// scheme, even though history stores the full URL.
assertThat(suggestionState.accountProviderSuggestion).isEqualTo("randomcommunity.org")
@@ -340,9 +340,9 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(ConfirmAccountProviderEvents.UserInputChanged("random"))
initialState.eventSink(ConfirmAccountProviderEvent.UserInputChanged("random"))
val suggestionState = awaitState { it.accountProviderSuggestion != null }
suggestionState.eventSink(ConfirmAccountProviderEvents.Continue("randomcommunity.org"))
suggestionState.eventSink(ConfirmAccountProviderEvent.Continue("randomcommunity.org"))
awaitLoginMode { it is AsyncData.Success }
assertThat(submittedUrls.first()).isEqualTo("https://randomcommunity.org")
cancelAndIgnoreRemainingEvents()
@@ -360,9 +360,9 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(ConfirmAccountProviderEvents.UserInputChanged("random"))
initialState.eventSink(ConfirmAccountProviderEvent.UserInputChanged("random"))
val suggestionState = awaitState { it.accountProviderSuggestion != null }
suggestionState.eventSink(ConfirmAccountProviderEvents.Continue("randomcommunity.org"))
suggestionState.eventSink(ConfirmAccountProviderEvent.Continue("randomcommunity.org"))
// The field now renders the full accepted server rather than the typed prefix.
val appliedState = awaitState { it.accountProviderInput == "randomcommunity.org" }
assertThat(appliedState.accountProviderInput).isEqualTo("randomcommunity.org")
@@ -381,7 +381,7 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(ConfirmAccountProviderEvents.UserInputChanged("ele"))
initialState.eventSink(ConfirmAccountProviderEvent.UserInputChanged("ele"))
val suggestionState = awaitState { it.accountProviderInput == "ele" }
assertThat(suggestionState.accountProviderSuggestion).isEqualTo("element.io")
}
@@ -394,7 +394,7 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(ConfirmAccountProviderEvents.UserInputChanged("matr"))
initialState.eventSink(ConfirmAccountProviderEvent.UserInputChanged("matr"))
val suggestionState = awaitState { it.accountProviderInput == "matr" }
assertThat(suggestionState.accountProviderSuggestion).isEqualTo("matrix.org")
}
@@ -409,9 +409,9 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(ConfirmAccountProviderEvents.UserInputChanged("https://mat"))
initialState.eventSink(ConfirmAccountProviderEvent.UserInputChanged("https://mat"))
assertThat(awaitState { it.accountProviderInput == "https://mat" }.accountProviderSuggestion).isNull()
initialState.eventSink(ConfirmAccountProviderEvents.UserInputChanged("http://mat"))
initialState.eventSink(ConfirmAccountProviderEvent.UserInputChanged("http://mat"))
assertThat(awaitState { it.accountProviderInput == "http://mat" }.accountProviderSuggestion).isNull()
}
}
@@ -430,7 +430,7 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(ConfirmAccountProviderEvents.Continue("matrix.org"))
initialState.eventSink(ConfirmAccountProviderEvent.Continue("matrix.org"))
awaitLoginMode { it is AsyncData.Success }
assertThat(submittedUrls.first()).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
cancelAndIgnoreRemainingEvents()
@@ -453,7 +453,7 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL))
initialState.eventSink(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL))
awaitLoginMode { it is AsyncData.Success }
assertThat(submittedUrls).containsExactly(AuthenticationConfig.MATRIX_ORG_URL)
cancelAndIgnoreRemainingEvents()
@@ -472,7 +472,7 @@ class ConfirmAccountProviderPresenterTest {
val presenter = createConfirmAccountProviderPresenter(matrixAuthenticationService = authenticationService)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(ConfirmAccountProviderEvents.Continue("@alice:example.org"))
initialState.eventSink(ConfirmAccountProviderEvent.Continue("@alice:example.org"))
awaitLoginMode { it is AsyncData.Success }
assertThat(submittedUrls.first()).isEqualTo("https://example.org")
assertThat(authenticationService.getOAuthUrlLoginHint).isEqualTo("mxid:@alice:example.org")
@@ -492,7 +492,7 @@ class ConfirmAccountProviderPresenterTest {
val presenter = createConfirmAccountProviderPresenter(matrixAuthenticationService = authenticationService)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(ConfirmAccountProviderEvents.Continue("@alice:example.org:8448"))
initialState.eventSink(ConfirmAccountProviderEvent.Continue("@alice:example.org:8448"))
awaitLoginMode { it is AsyncData.Success }
assertThat(submittedUrls.first()).isEqualTo("https://example.org:8448")
cancelAndIgnoreRemainingEvents()
@@ -507,7 +507,7 @@ class ConfirmAccountProviderPresenterTest {
val presenter = createConfirmAccountProviderPresenter(matrixAuthenticationService = authenticationService)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(ConfirmAccountProviderEvents.Continue("matrix.org"))
initialState.eventSink(ConfirmAccountProviderEvent.Continue("matrix.org"))
awaitLoginMode { it is AsyncData.Success }
assertThat(authenticationService.getOAuthUrlLoginHint).isNull()
cancelAndIgnoreRemainingEvents()
@@ -525,7 +525,7 @@ class ConfirmAccountProviderPresenterTest {
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(ConfirmAccountProviderEvents.Continue("@alice:example.org"))
initialState.eventSink(ConfirmAccountProviderEvent.Continue("@alice:example.org"))
awaitLoginMode { it is AsyncData.Success }
assertThat(authenticationService.getOAuthUrlLoginHint).isNull()
cancelAndIgnoreRemainingEvents()