Dismiss the keyboard on continue and restore it after an error

This commit is contained in:
Jenna Vassar
2026-08-07 12:46:11 -07:00
parent 8c2cf9d95e
commit d7e6b64265
2 changed files with 32 additions and 5 deletions
@@ -29,4 +29,8 @@ data class ConfirmAccountProviderState(
val isLoading: Boolean val isLoading: Boolean
get() = loginModeState.loginMode is AsyncData.Loading || get() = loginModeState.loginMode is AsyncData.Loading ||
changeServerState.changeServerAction is AsyncData.Loading changeServerState.changeServerAction is AsyncData.Loading
val isShowingError: Boolean
get() = loginModeState.loginMode is AsyncData.Failure ||
changeServerState.changeServerAction is AsyncData.Failure
} }
@@ -15,12 +15,17 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
@@ -72,6 +77,26 @@ fun ConfirmAccountProviderView(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val eventSink = state.eventSink val eventSink = state.eventSink
val focusManager = LocalFocusManager.current
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
fun submit() {
// Dismiss the keyboard and release focus while the account provider is being validated.
focusManager.clearFocus(force = true)
eventSink(ConfirmAccountProviderEvents.Continue)
}
// Once a validation / login error has been dismissed, return focus and the keyboard to the field so
// the user can correct the account provider and retry.
var wasShowingError by remember { mutableStateOf(false) }
LaunchedEffect(state.isShowingError) {
if (wasShowingError && !state.isShowingError) {
focusRequester.requestFocus()
keyboardController?.show()
}
wasShowingError = state.isShowingError
}
HeaderFooterPage( HeaderFooterPage(
modifier = modifier, modifier = modifier,
@@ -94,7 +119,7 @@ fun ConfirmAccountProviderView(
Button( Button(
text = stringResource(id = CommonStrings.action_continue), text = stringResource(id = CommonStrings.action_continue),
showProgress = state.isLoading, showProgress = state.isLoading,
onClick = { eventSink(ConfirmAccountProviderEvents.Continue) }, onClick = ::submit,
enabled = state.submitEnabled && !state.isLoading, enabled = state.submitEnabled && !state.isLoading,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -104,7 +129,6 @@ fun ConfirmAccountProviderView(
} }
) { ) {
var input by textFieldState(stateValue = state.accountProviderInput) var input by textFieldState(stateValue = state.accountProviderInput)
val focusManager = LocalFocusManager.current
// Inline autocomplete: render the un-typed remainder of the suggested account provider in grey. // Inline autocomplete: render the un-typed remainder of the suggested account provider in grey.
val suggestionSuffix = state.accountProviderSuggestion val suggestionSuffix = state.accountProviderSuggestion
?.takeIf { it.length > input.length && it.startsWith(input, ignoreCase = true) } ?.takeIf { it.length > input.length && it.startsWith(input, ignoreCase = true) }
@@ -123,6 +147,7 @@ fun ConfirmAccountProviderView(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = 40.dp) .padding(top = 40.dp)
.focusRequester(focusRequester)
.onTabOrEnterKeyFocusNext(focusManager) .onTabOrEnterKeyFocusNext(focusManager)
.testTag(TestTags.changeServerServer), .testTag(TestTags.changeServerServer),
label = stringResource(id = CommonStrings.screen_select_server_textfield_header), label = stringResource(id = CommonStrings.screen_select_server_textfield_header),
@@ -139,9 +164,7 @@ fun ConfirmAccountProviderView(
keyboardType = KeyboardType.Uri, keyboardType = KeyboardType.Uri,
imeAction = ImeAction.Done, imeAction = ImeAction.Done,
), ),
keyboardActions = KeyboardActions(onDone = { keyboardActions = KeyboardActions(onDone = { submit() }),
eventSink(ConfirmAccountProviderEvents.Continue)
}),
singleLine = true, singleLine = true,
trailingIcon = if (input.isNotEmpty()) { trailingIcon = if (input.isNotEmpty()) {
{ {