design(text field) : allow setting validity (instead of just isError bool)

This commit is contained in:
ganfra
2025-02-20 21:49:50 +01:00
parent f1c5fa53e8
commit f6b4ee945b
6 changed files with 59 additions and 31 deletions

View File

@@ -29,6 +29,7 @@ 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.ModalBottomSheet
import io.element.android.libraries.designsystem.theme.components.TextField
import io.element.android.libraries.designsystem.theme.components.TextFieldValidity
import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class)
@@ -98,7 +99,11 @@ private fun RoomAddressField(
"e.g. #room-name:matrix.org"
}
},
isError = addressState is RoomAddressState.Invalid,
validity = when (addressState) {
RoomAddressState.Unknown -> null
RoomAddressState.Invalid -> TextFieldValidity.Invalid
is RoomAddressState.Valid -> if (addressState.matchingRoomFound) TextFieldValidity.Valid else null
},
onValueChange = onAddressChange,
singleLine = true,
)

View File

@@ -45,6 +45,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.debugPlaceholderBackground
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.TextField
import io.element.android.libraries.designsystem.theme.components.TextFieldValidity
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
@@ -90,7 +91,7 @@ fun BugReportView(
keyboardController?.hide()
}),
minLines = 3,
isError = state.isDescriptionInError,
validity = if(state.isDescriptionInError) TextFieldValidity.Invalid else null,
)
}
Spacer(modifier = Modifier.height(16.dp))

View File

@@ -33,6 +33,7 @@ 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.Icon
import io.element.android.libraries.designsystem.theme.components.TextField
import io.element.android.libraries.designsystem.theme.components.TextFieldValidity
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
@@ -82,8 +83,8 @@ private fun Content(text: String, onTextChange: (String) -> Unit, hasError: Bool
var showPassword by remember { mutableStateOf(false) }
TextField(
modifier = Modifier
.fillMaxWidth()
.onTabOrEnterKeyFocusNext(LocalFocusManager.current),
.fillMaxWidth()
.onTabOrEnterKeyFocusNext(LocalFocusManager.current),
value = text,
onValueChange = onTextChange,
placeholder = stringResource(CommonStrings.common_password),
@@ -99,7 +100,7 @@ private fun Content(text: String, onTextChange: (String) -> Unit, hasError: Bool
Icon(imageVector = image, description)
}
},
isError = hasError,
validity = if (hasError) TextFieldValidity.Invalid else null,
supportingText = if (hasError) {
stringResource(R.string.screen_reset_encryption_password_error)
} else {

View File

@@ -58,7 +58,7 @@ fun TextField(
placeholder: String? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
isError: Boolean = false,
validity: TextFieldValidity? = null,
enabled: Boolean = true,
readOnly: Boolean = false,
singleLine: Boolean = false,
@@ -93,7 +93,7 @@ fun TextField(
readOnly = readOnly,
enabled = enabled,
isFocused = isFocused,
isError = isError,
validity = validity,
leadingIcon = leadingIcon,
placeholder = placeholder,
isTextEmpty = value.isEmpty(),
@@ -114,7 +114,7 @@ fun TextField(
placeholder: String? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
isError: Boolean = false,
validity: TextFieldValidity? = null,
enabled: Boolean = true,
readOnly: Boolean = false,
singleLine: Boolean = false,
@@ -149,7 +149,7 @@ fun TextField(
readOnly = readOnly,
enabled = enabled,
isFocused = isFocused,
isError = isError,
validity = validity,
leadingIcon = leadingIcon,
placeholder = placeholder,
isTextEmpty = value.text.isEmpty(),
@@ -166,7 +166,7 @@ private fun DecorationBox(
enabled: Boolean,
readOnly: Boolean,
isFocused: Boolean,
isError: Boolean,
validity: TextFieldValidity?,
placeholder: String?,
isTextEmpty: Boolean,
supportingText: String?,
@@ -187,7 +187,7 @@ private fun DecorationBox(
enabled = enabled,
readOnly = readOnly,
isFocused = isFocused,
isError = isError
isError = validity == TextFieldValidity.Invalid
) {
Row(modifier = Modifier.padding(16.dp)) {
if (leadingIcon != null) {
@@ -216,7 +216,7 @@ private fun DecorationBox(
}
if (supportingText != null) {
Spacer(modifier = Modifier.height(4.dp))
SupportingTextLayout(isError, supportingText)
SupportingTextLayout(validity, supportingText)
}
}
}
@@ -254,24 +254,44 @@ private fun TextFieldContainer(
}
@Composable
private fun SupportingTextLayout(isError: Boolean, supportingText: String) {
private fun SupportingTextLayout(validity: TextFieldValidity?, supportingText: String) {
Row(horizontalArrangement = spacedBy(4.dp)) {
if (isError) {
Icon(
imageVector = CompoundIcons.Error(),
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = ElementTheme.colors.iconCriticalPrimary
)
when (validity) {
TextFieldValidity.Invalid -> {
Icon(
imageVector = CompoundIcons.Error(),
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = ElementTheme.colors.iconCriticalPrimary
)
}
TextFieldValidity.Valid -> {
Icon(
imageVector = CompoundIcons.CheckCircleSolid(),
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = ElementTheme.colors.iconSuccessPrimary
)
}
else -> Unit
}
Text(
text = supportingText,
color = if (isError) ElementTheme.colors.textCriticalPrimary else ElementTheme.colors.textSecondary,
color = when (validity) {
TextFieldValidity.Invalid -> ElementTheme.colors.textCriticalPrimary
TextFieldValidity.Valid -> ElementTheme.colors.textSuccessPrimary
else -> ElementTheme.colors.textSecondary
},
style = ElementTheme.typography.fontBodySmRegular,
)
}
}
enum class TextFieldValidity {
Invalid,
Valid
}
@Composable
private fun textFieldStyle(enabled: Boolean): TextStyle {
return ElementTheme.typography.fontBodyLgRegular.copy(
@@ -283,11 +303,11 @@ private fun textFieldStyle(enabled: Boolean): TextStyle {
)
}
@Preview(group = PreviewGroup.TextFields)
@Preview(group = PreviewGroup.TextFields, heightDp = 1000)
@Composable
internal fun TextFieldsLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview(group = PreviewGroup.TextFields)
@Preview(group = PreviewGroup.TextFields, heightDp = 1000)
@Composable
internal fun TextFieldsDarkPreview() = ElementPreviewDark { ContentToPreview() }
@@ -295,15 +315,15 @@ internal fun TextFieldsDarkPreview() = ElementPreviewDark { ContentToPreview() }
@ExcludeFromCoverage
private fun ContentToPreview() {
Column(modifier = Modifier.padding(4.dp)) {
allBooleans.forEach { isError ->
TextFieldValidity.entries.forEach { validity ->
allBooleans.forEach { enabled ->
allBooleans.forEach { readonly ->
TextField(
onValueChange = {},
label = "Label",
value = "Hello er=${isError.asInt()}, en=${enabled.asInt()}, ro=${readonly.asInt()}",
value = "Hello val=${validity}, en=${enabled.asInt()}, ro=${readonly.asInt()}",
supportingText = "Supporting text",
isError = isError,
validity = validity,
enabled = enabled,
readOnly = readonly,
)

View File

@@ -15,6 +15,7 @@ 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.designsystem.theme.components.TextField
import io.element.android.libraries.designsystem.theme.components.TextFieldValidity
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings
@@ -56,7 +57,10 @@ fun RoomAddressField(
}
else -> supportingText
},
isError = addressValidity.isError(),
validity = when (addressValidity) {
RoomAddressValidity.InvalidSymbols, RoomAddressValidity.NotAvailable -> TextFieldValidity.Invalid
else -> null
},
onValueChange = onAddressChange,
singleLine = true,
)

View File

@@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.ui.room.address
import androidx.compose.runtime.Immutable
import io.element.android.libraries.designsystem.theme.components.TextFieldValidity
/**
* Represents the validity state of a room address.
@@ -19,8 +20,4 @@ sealed interface RoomAddressValidity {
data object InvalidSymbols : RoomAddressValidity
data object NotAvailable : RoomAddressValidity
data object Valid : RoomAddressValidity
fun isError(): Boolean {
return this is InvalidSymbols || this is NotAvailable
}
}