diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusEvent.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusEvent.kt index d78be1607b..80dd02e08a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusEvent.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusEvent.kt @@ -22,8 +22,6 @@ sealed interface UserStatusEvent { data object CancelCustomInput : UserStatusEvent /** User changed the emoji in the custom input row. */ data class UpdateCustomEmoji(val emoji: String) : UserStatusEvent - /** User changed the text in the custom input row. */ - data class UpdateCustomText(val text: String) : UserStatusEvent /** User tapped the clear action on an existing status. */ data object Clear : UserStatusEvent } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusPresenter.kt index d765f35800..0bb626b159 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusPresenter.kt @@ -7,6 +7,9 @@ package io.element.android.features.preferences.impl.userstatus +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.input.clearText +import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -31,6 +34,7 @@ class UserStatusPresenter( override fun present(): UserStatusState { val userProfile by matrixClient.userProfile.collectAsState() var pickerState by remember { mutableStateOf(UserStatusPickerState.Hidden) } + val customTextFieldState = remember { TextFieldState() } val coroutineScope = rememberCoroutineScope() fun handleEvent(event: UserStatusEvent) { @@ -39,9 +43,14 @@ class UserStatusPresenter( UserStatusEvent.Dismiss -> pickerState = UserStatusPickerState.Hidden UserStatusEvent.OpenCustomInput -> { val raw = userProfile.rawStatus + if (raw != null) { + customTextFieldState.setTextAndPlaceCursorAtEnd(raw.text) + } else { + customTextFieldState.clearText() + } pickerState = UserStatusPickerState.CustomInput( emoji = raw?.emoji ?: "😀", - text = raw?.text ?: "", + textFieldState = customTextFieldState, ) } is UserStatusEvent.Set -> { @@ -57,10 +66,6 @@ class UserStatusPresenter( val current = pickerState as? UserStatusPickerState.CustomInput ?: return pickerState = current.copy(emoji = event.emoji) } - is UserStatusEvent.UpdateCustomText -> { - val current = pickerState as? UserStatusPickerState.CustomInput ?: return - pickerState = current.copy(text = event.text) - } } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusState.kt index df1a97a5e3..b1fe11d1ed 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusState.kt @@ -8,6 +8,7 @@ package io.element.android.features.preferences.impl.userstatus import androidx.annotation.StringRes +import androidx.compose.foundation.text.input.TextFieldState import io.element.android.features.preferences.impl.R import io.element.android.libraries.matrix.api.user.DisplayedStatus @@ -22,7 +23,7 @@ sealed interface UserStatusPickerState { data object ShowingPicker : UserStatusPickerState data class CustomInput( val emoji: String = "😀", - val text: String = "", + val textFieldState: TextFieldState, ) : UserStatusPickerState } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusView.kt index 956400fa63..b63ae2630e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusView.kt @@ -15,8 +15,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.input.InputTransformation +import androidx.compose.foundation.text.input.TextFieldLineLimits +import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -32,6 +34,7 @@ import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.FilledTextField import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.IconSource @@ -40,7 +43,6 @@ import io.element.android.libraries.designsystem.theme.components.ModalBottomShe import io.element.android.libraries.designsystem.theme.components.Surface import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton -import io.element.android.libraries.designsystem.theme.components.TextField import io.element.android.libraries.matrix.api.user.DisplayedStatus import io.element.android.libraries.matrix.api.user.UserStatus import io.element.android.libraries.ui.strings.CommonStrings @@ -77,10 +79,13 @@ fun UserStatusRow( is UserStatusPickerState.CustomInput -> { CustomStatusInputRow( emoji = pickerState.emoji, - text = pickerState.text, + textFieldState = pickerState.textFieldState, onEmojiChange = { state.eventSink(UserStatusEvent.UpdateCustomEmoji(it)) }, - onTextChange = { state.eventSink(UserStatusEvent.UpdateCustomText(it)) }, - onConfirm = { state.eventSink(UserStatusEvent.Set(UserStatus(pickerState.emoji, pickerState.text))) }, + onConfirm = { + state.eventSink( + UserStatusEvent.Set(UserStatus(pickerState.emoji, pickerState.textFieldState.text.toString())) + ) + }, onCancel = { state.eventSink(UserStatusEvent.CancelCustomInput) }, modifier = modifier, ) @@ -158,24 +163,21 @@ private fun UserStatusPickerBottomSheet( @Composable private fun CustomStatusInputRow( emoji: String, - text: String, + textFieldState: TextFieldState, onEmojiChange: (String) -> Unit, - onTextChange: (String) -> Unit, onConfirm: () -> Unit, onCancel: () -> Unit, modifier: Modifier = Modifier, ) { ListItem( headlineContent = { - TextField( - value = text, - onValueChange = { if (it.length <= 30) onTextChange(it) }, - placeholder = stringResource(R.string.screen_preferences_user_status_custom_hint), + FilledTextField( + state = textFieldState, + placeholder = { Text(stringResource(R.string.screen_preferences_user_status_custom_hint)) }, + inputTransformation = InputTransformation { if (length > 30) revertAllChanges() }, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions( - onDone = { if (text.isNotBlank()) onConfirm() } - ), - singleLine = true, + keyboardActions = { if (textFieldState.text.isNotBlank()) onConfirm() }, + lineLimits = TextFieldLineLimits.SingleLine, ) }, trailingContent = ListItemContent.Custom({ @@ -261,7 +263,7 @@ internal fun UserStatusRowCustomInputPreview() = ElementPreview { UserStatusRow( state = UserStatusState( displayedStatus = null, - pickerState = UserStatusPickerState.CustomInput(emoji = "😀", text = ""), + pickerState = UserStatusPickerState.CustomInput(emoji = "😀", textFieldState = TextFieldState()), eventSink = {}, ) ) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusPresenterTest.kt index 42f1e53323..349361a61b 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusPresenterTest.kt @@ -16,6 +16,7 @@ import io.element.android.libraries.matrix.api.user.UserStatus import io.element.android.libraries.matrix.test.FakeMatrixClient import kotlinx.coroutines.test.runTest import org.junit.Test +import androidx.compose.foundation.text.input.TextFieldState class UserStatusPresenterTest { @@ -81,7 +82,7 @@ class UserStatusPresenterTest { val state = awaitItem() val pickerState = state.pickerState as UserStatusPickerState.CustomInput assertThat(pickerState.emoji).isEqualTo("😀") - assertThat(pickerState.text).isEqualTo("") + assertThat(pickerState.textFieldState.text.toString()).isEqualTo("") } } @@ -98,19 +99,7 @@ class UserStatusPresenterTest { val state = awaitItem() val pickerState = state.pickerState as UserStatusPickerState.CustomInput assertThat(pickerState.emoji).isEqualTo("🌴") - assertThat(pickerState.text).isEqualTo("Away") - } - } - - @Test - fun `UpdateCustomText updates text in CustomInput state`() = runTest { - moleculeFlow(RecompositionMode.Immediate) { - createPresenter().present() - }.test { - awaitItem().eventSink(UserStatusEvent.OpenCustomInput) - awaitItem().eventSink(UserStatusEvent.UpdateCustomText("Focusing")) - val state = awaitItem() - assertThat((state.pickerState as UserStatusPickerState.CustomInput).text).isEqualTo("Focusing") + assertThat(pickerState.textFieldState.text.toString()).isEqualTo("Away") } }