Keep the cursor position in room list search when going back (#5570)

Also, make sure disposing a `MessagesView` doesn't accidentally hide the keyboard once the transition animation is done
This commit is contained in:
Jorge Martin Espinosa 2025-10-21 11:25:46 +02:00 committed by GitHub
parent cd375cafba
commit cbeb58f00e
2 changed files with 13 additions and 10 deletions

View file

@ -24,6 +24,7 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect 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
@ -33,6 +34,8 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
@ -41,7 +44,6 @@ import io.element.android.features.home.impl.contentType
import io.element.android.features.home.impl.model.RoomListRoomSummary import io.element.android.features.home.impl.model.RoomListRoomSummary
import io.element.android.features.home.impl.roomlist.RoomListEvents import io.element.android.features.home.impl.roomlist.RoomListEvents
import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.FilledTextField import io.element.android.libraries.designsystem.theme.components.FilledTextField
@ -111,18 +113,18 @@ private fun RoomListSearchContent(
}, },
navigationIcon = { BackButton(onClick = ::onBackButtonClick) }, navigationIcon = { BackButton(onClick = ::onBackButtonClick) },
title = { title = {
var filter by textFieldState(state.query) var value by remember { mutableStateOf(TextFieldValue(state.query)) }
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
FilledTextField( FilledTextField(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.focusRequester(focusRequester), .focusRequester(focusRequester),
value = filter, value = value,
singleLine = true, singleLine = true,
onValueChange = { onValueChange = {
filter = it value = it
state.eventSink(RoomListSearchEvents.QueryChanged(it)) state.eventSink(RoomListSearchEvents.QueryChanged(it.text))
}, },
colors = TextFieldDefaults.colors( colors = TextFieldDefaults.colors(
focusedContainerColor = Color.Transparent, focusedContainerColor = Color.Transparent,
@ -134,7 +136,7 @@ private fun RoomListSearchContent(
errorIndicatorColor = Color.Transparent, errorIndicatorColor = Color.Transparent,
), ),
trailingIcon = { trailingIcon = {
if (filter.isNotEmpty()) { if (value.text.isNotEmpty()) {
IconButton(onClick = { IconButton(onClick = {
state.eventSink(RoomListSearchEvents.ClearQuery) state.eventSink(RoomListSearchEvents.ClearQuery)
}) { }) {
@ -148,7 +150,11 @@ private fun RoomListSearchContent(
) )
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
focusRequester.requestFocus() value = value.copy(selection = TextRange(value.text.length))
if (!focusRequester.restoreFocusedChild()) {
focusRequester.requestFocus()
}
focusRequester.saveFocusedChild()
} }
}, },
windowInsets = TopAppBarDefaults.windowInsets.copy(top = 0) windowInsets = TopAppBarDefaults.windowInsets.copy(top = 0)

View file

@ -84,7 +84,6 @@ import io.element.android.libraries.designsystem.text.toAnnotatedString
import io.element.android.libraries.designsystem.theme.components.BottomSheetDragHandle import io.element.android.libraries.designsystem.theme.components.BottomSheetDragHandle
import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.HideKeyboardWhenDisposed
import io.element.android.libraries.designsystem.utils.KeepScreenOn import io.element.android.libraries.designsystem.utils.KeepScreenOn
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
@ -124,8 +123,6 @@ fun MessagesView(
KeepScreenOn(state.voiceMessageComposerState.keepScreenOn) KeepScreenOn(state.voiceMessageComposerState.keepScreenOn)
HideKeyboardWhenDisposed()
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage) val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
// This is needed because the composer is inside an AndroidView that can't be affected by the FocusManager in Compose // This is needed because the composer is inside an AndroidView that can't be affected by the FocusManager in Compose