Improve TextFieldDialog (#4512)
* Extract TextFieldDialog to its own file (no other change). * Add TextFieldDialogPreview Enhance TextFieldDialog * Let RoomMembersModerationView use TextFieldDialog * Update screenshots * Konsist. * Add modifier parameter. --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
5c2a069c95
commit
1fdb590ece
35 changed files with 291 additions and 264 deletions
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.components.dialogs
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.components.list.TextFieldListItem
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
fun TextFieldDialog(
|
||||
title: String,
|
||||
onSubmit: (String) -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
value: String?,
|
||||
placeholder: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
validation: (String?) -> Boolean = { true },
|
||||
onValidationErrorMessage: String? = null,
|
||||
autoSelectOnDisplay: Boolean = true,
|
||||
maxLines: Int = 1,
|
||||
content: String? = null,
|
||||
label: String? = null,
|
||||
withBorder: Boolean = false,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
submitText: String = stringResource(CommonStrings.action_ok),
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
var textFieldContents by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
||||
mutableStateOf(
|
||||
TextFieldValue(
|
||||
value.orEmpty(),
|
||||
selection = TextRange(value.orEmpty().length)
|
||||
)
|
||||
)
|
||||
}
|
||||
var error by rememberSaveable { mutableStateOf(if (!validation(value.orEmpty())) onValidationErrorMessage else null) }
|
||||
var canRequestFocus by rememberSaveable { mutableStateOf(false) }
|
||||
val canSubmit by remember { derivedStateOf { validation(textFieldContents.text) } }
|
||||
ListDialog(
|
||||
title = title,
|
||||
onSubmit = { onSubmit(textFieldContents.text) },
|
||||
onDismissRequest = onDismissRequest,
|
||||
enabled = canSubmit,
|
||||
applyPaddingToContents = content.isNullOrEmpty().not(),
|
||||
submitText = submitText,
|
||||
modifier = modifier,
|
||||
) {
|
||||
if (content != null) {
|
||||
item {
|
||||
Text(
|
||||
text = content,
|
||||
style = ElementTheme.materialTypography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
TextFieldListItem(
|
||||
placeholder = placeholder.orEmpty(),
|
||||
label = label,
|
||||
withBorder = withBorder,
|
||||
text = textFieldContents,
|
||||
onTextChange = {
|
||||
error = if (!validation(it.text)) onValidationErrorMessage else null
|
||||
textFieldContents = it
|
||||
},
|
||||
error = error,
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = KeyboardActions(onAny = {
|
||||
if (validation(textFieldContents.text)) {
|
||||
onSubmit(textFieldContents.text)
|
||||
}
|
||||
}),
|
||||
maxLines = maxLines,
|
||||
modifier = Modifier.focusRequester(focusRequester),
|
||||
)
|
||||
canRequestFocus = true
|
||||
}
|
||||
}
|
||||
|
||||
if (autoSelectOnDisplay && canRequestFocus) {
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TextFieldDialogPreview() = ElementPreview {
|
||||
TextFieldDialog(
|
||||
title = "Title",
|
||||
value = "",
|
||||
placeholder = "Placeholder",
|
||||
onSubmit = {},
|
||||
onDismissRequest = {},
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TextFieldDialogWithBorderPreview() = ElementPreview {
|
||||
TextFieldDialog(
|
||||
title = "Title",
|
||||
content = "Some content",
|
||||
onSubmit = {},
|
||||
onDismissRequest = {},
|
||||
value = "Value",
|
||||
placeholder = "Placeholder",
|
||||
label = "Label",
|
||||
withBorder = true,
|
||||
onValidationErrorMessage = "Error message",
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TextFieldDialogWithErrorPreview() = ElementPreview {
|
||||
TextFieldDialog(
|
||||
title = "Title",
|
||||
content = "Some content",
|
||||
onSubmit = {},
|
||||
validation = { false },
|
||||
onDismissRequest = {},
|
||||
value = "Value",
|
||||
placeholder = "Placeholder",
|
||||
label = "Label",
|
||||
withBorder = true,
|
||||
onValidationErrorMessage = "Error message",
|
||||
)
|
||||
}
|
||||
|
|
@ -70,6 +70,8 @@ fun TextFieldListItem(
|
|||
modifier: Modifier = Modifier,
|
||||
error: String? = null,
|
||||
maxLines: Int = 1,
|
||||
withBorder: Boolean = false,
|
||||
label: String? = null,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||
) {
|
||||
|
|
@ -79,12 +81,17 @@ fun TextFieldListItem(
|
|||
value = text,
|
||||
onValueChange = { onTextChange(it) },
|
||||
placeholder = placeholder?.let { @Composable { Text(it) } },
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
disabledBorderColor = Color.Transparent,
|
||||
errorBorderColor = Color.Transparent,
|
||||
focusedBorderColor = Color.Transparent,
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
),
|
||||
label = label?.let { @Composable { Text(it) } },
|
||||
colors = if (withBorder) {
|
||||
OutlinedTextFieldDefaults.colors()
|
||||
} else {
|
||||
OutlinedTextFieldDefaults.colors(
|
||||
disabledBorderColor = Color.Transparent,
|
||||
errorBorderColor = Color.Transparent,
|
||||
focusedBorderColor = Color.Transparent,
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
)
|
||||
},
|
||||
isError = error != null,
|
||||
supportingText = error?.let { @Composable { Text(it) } },
|
||||
keyboardOptions = keyboardOptions,
|
||||
|
|
|
|||
|
|
@ -7,24 +7,15 @@
|
|||
|
||||
package io.element.android.libraries.designsystem.components.preferences
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ListDialog
|
||||
import io.element.android.libraries.designsystem.components.dialogs.TextFieldDialog
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.components.list.TextFieldListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItemStyle
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
|
|
@ -74,58 +65,3 @@ fun PreferenceTextField(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TextFieldDialog(
|
||||
title: String,
|
||||
onSubmit: (String) -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
value: String?,
|
||||
placeholder: String?,
|
||||
validation: (String?) -> Boolean = { true },
|
||||
onValidationErrorMessage: String? = null,
|
||||
autoSelectOnDisplay: Boolean = true,
|
||||
maxLines: Int = 1,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
var textFieldContents by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
||||
mutableStateOf(TextFieldValue(value.orEmpty(), selection = TextRange(value.orEmpty().length)))
|
||||
}
|
||||
var error by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
var canRequestFocus by rememberSaveable { mutableStateOf(false) }
|
||||
val canSubmit by remember { derivedStateOf { validation(textFieldContents.text) } }
|
||||
ListDialog(
|
||||
title = title,
|
||||
onSubmit = { onSubmit(textFieldContents.text) },
|
||||
onDismissRequest = onDismissRequest,
|
||||
enabled = canSubmit,
|
||||
) {
|
||||
item {
|
||||
TextFieldListItem(
|
||||
placeholder = placeholder.orEmpty(),
|
||||
text = textFieldContents,
|
||||
onTextChange = {
|
||||
error = if (!validation(it.text)) onValidationErrorMessage else null
|
||||
textFieldContents = it
|
||||
},
|
||||
error = error,
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = KeyboardActions(onAny = {
|
||||
if (validation(textFieldContents.text)) {
|
||||
onSubmit(textFieldContents.text)
|
||||
}
|
||||
}),
|
||||
maxLines = maxLines,
|
||||
modifier = Modifier.focusRequester(focusRequester),
|
||||
)
|
||||
canRequestFocus = true
|
||||
}
|
||||
}
|
||||
|
||||
if (autoSelectOnDisplay && canRequestFocus) {
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue