Use TextFieldState in CustomInput state for proper MVI text handling
This commit is contained in:
-2
@@ -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
|
||||
}
|
||||
|
||||
+10
-5
@@ -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>(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+18
-16
@@ -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 = {},
|
||||
)
|
||||
)
|
||||
|
||||
+3
-14
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user