design(text field) : allow setting validity (instead of just isError bool)
This commit is contained in:
parent
f1c5fa53e8
commit
f6b4ee945b
6 changed files with 59 additions and 31 deletions
|
|
@ -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.Button
|
||||||
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet
|
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.TextField
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TextFieldValidity
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
@ -98,7 +99,11 @@ private fun RoomAddressField(
|
||||||
"e.g. #room-name:matrix.org"
|
"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,
|
onValueChange = onAddressChange,
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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.preview.debugPlaceholderBackground
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
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.TextField
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TextFieldValidity
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -90,7 +91,7 @@ fun BugReportView(
|
||||||
keyboardController?.hide()
|
keyboardController?.hide()
|
||||||
}),
|
}),
|
||||||
minLines = 3,
|
minLines = 3,
|
||||||
isError = state.isDescriptionInError,
|
validity = if(state.isDescriptionInError) TextFieldValidity.Invalid else null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
|
||||||
|
|
@ -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.Button
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
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.TextField
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TextFieldValidity
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -82,8 +83,8 @@ private fun Content(text: String, onTextChange: (String) -> Unit, hasError: Bool
|
||||||
var showPassword by remember { mutableStateOf(false) }
|
var showPassword by remember { mutableStateOf(false) }
|
||||||
TextField(
|
TextField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.onTabOrEnterKeyFocusNext(LocalFocusManager.current),
|
.onTabOrEnterKeyFocusNext(LocalFocusManager.current),
|
||||||
value = text,
|
value = text,
|
||||||
onValueChange = onTextChange,
|
onValueChange = onTextChange,
|
||||||
placeholder = stringResource(CommonStrings.common_password),
|
placeholder = stringResource(CommonStrings.common_password),
|
||||||
|
|
@ -99,7 +100,7 @@ private fun Content(text: String, onTextChange: (String) -> Unit, hasError: Bool
|
||||||
Icon(imageVector = image, description)
|
Icon(imageVector = image, description)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isError = hasError,
|
validity = if (hasError) TextFieldValidity.Invalid else null,
|
||||||
supportingText = if (hasError) {
|
supportingText = if (hasError) {
|
||||||
stringResource(R.string.screen_reset_encryption_password_error)
|
stringResource(R.string.screen_reset_encryption_password_error)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ fun TextField(
|
||||||
placeholder: String? = null,
|
placeholder: String? = null,
|
||||||
leadingIcon: @Composable (() -> Unit)? = null,
|
leadingIcon: @Composable (() -> Unit)? = null,
|
||||||
trailingIcon: @Composable (() -> Unit)? = null,
|
trailingIcon: @Composable (() -> Unit)? = null,
|
||||||
isError: Boolean = false,
|
validity: TextFieldValidity? = null,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
readOnly: Boolean = false,
|
readOnly: Boolean = false,
|
||||||
singleLine: Boolean = false,
|
singleLine: Boolean = false,
|
||||||
|
|
@ -93,7 +93,7 @@ fun TextField(
|
||||||
readOnly = readOnly,
|
readOnly = readOnly,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
isFocused = isFocused,
|
isFocused = isFocused,
|
||||||
isError = isError,
|
validity = validity,
|
||||||
leadingIcon = leadingIcon,
|
leadingIcon = leadingIcon,
|
||||||
placeholder = placeholder,
|
placeholder = placeholder,
|
||||||
isTextEmpty = value.isEmpty(),
|
isTextEmpty = value.isEmpty(),
|
||||||
|
|
@ -114,7 +114,7 @@ fun TextField(
|
||||||
placeholder: String? = null,
|
placeholder: String? = null,
|
||||||
leadingIcon: @Composable (() -> Unit)? = null,
|
leadingIcon: @Composable (() -> Unit)? = null,
|
||||||
trailingIcon: @Composable (() -> Unit)? = null,
|
trailingIcon: @Composable (() -> Unit)? = null,
|
||||||
isError: Boolean = false,
|
validity: TextFieldValidity? = null,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
readOnly: Boolean = false,
|
readOnly: Boolean = false,
|
||||||
singleLine: Boolean = false,
|
singleLine: Boolean = false,
|
||||||
|
|
@ -149,7 +149,7 @@ fun TextField(
|
||||||
readOnly = readOnly,
|
readOnly = readOnly,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
isFocused = isFocused,
|
isFocused = isFocused,
|
||||||
isError = isError,
|
validity = validity,
|
||||||
leadingIcon = leadingIcon,
|
leadingIcon = leadingIcon,
|
||||||
placeholder = placeholder,
|
placeholder = placeholder,
|
||||||
isTextEmpty = value.text.isEmpty(),
|
isTextEmpty = value.text.isEmpty(),
|
||||||
|
|
@ -166,7 +166,7 @@ private fun DecorationBox(
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
readOnly: Boolean,
|
readOnly: Boolean,
|
||||||
isFocused: Boolean,
|
isFocused: Boolean,
|
||||||
isError: Boolean,
|
validity: TextFieldValidity?,
|
||||||
placeholder: String?,
|
placeholder: String?,
|
||||||
isTextEmpty: Boolean,
|
isTextEmpty: Boolean,
|
||||||
supportingText: String?,
|
supportingText: String?,
|
||||||
|
|
@ -187,7 +187,7 @@ private fun DecorationBox(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
readOnly = readOnly,
|
readOnly = readOnly,
|
||||||
isFocused = isFocused,
|
isFocused = isFocused,
|
||||||
isError = isError
|
isError = validity == TextFieldValidity.Invalid
|
||||||
) {
|
) {
|
||||||
Row(modifier = Modifier.padding(16.dp)) {
|
Row(modifier = Modifier.padding(16.dp)) {
|
||||||
if (leadingIcon != null) {
|
if (leadingIcon != null) {
|
||||||
|
|
@ -216,7 +216,7 @@ private fun DecorationBox(
|
||||||
}
|
}
|
||||||
if (supportingText != null) {
|
if (supportingText != null) {
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
SupportingTextLayout(isError, supportingText)
|
SupportingTextLayout(validity, supportingText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -254,24 +254,44 @@ private fun TextFieldContainer(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SupportingTextLayout(isError: Boolean, supportingText: String) {
|
private fun SupportingTextLayout(validity: TextFieldValidity?, supportingText: String) {
|
||||||
Row(horizontalArrangement = spacedBy(4.dp)) {
|
Row(horizontalArrangement = spacedBy(4.dp)) {
|
||||||
if (isError) {
|
when (validity) {
|
||||||
Icon(
|
TextFieldValidity.Invalid -> {
|
||||||
imageVector = CompoundIcons.Error(),
|
Icon(
|
||||||
contentDescription = null,
|
imageVector = CompoundIcons.Error(),
|
||||||
modifier = Modifier.size(16.dp),
|
contentDescription = null,
|
||||||
tint = ElementTheme.colors.iconCriticalPrimary
|
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(
|
||||||
text = supportingText,
|
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,
|
style = ElementTheme.typography.fontBodySmRegular,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class TextFieldValidity {
|
||||||
|
Invalid,
|
||||||
|
Valid
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun textFieldStyle(enabled: Boolean): TextStyle {
|
private fun textFieldStyle(enabled: Boolean): TextStyle {
|
||||||
return ElementTheme.typography.fontBodyLgRegular.copy(
|
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
|
@Composable
|
||||||
internal fun TextFieldsLightPreview() = ElementPreviewLight { ContentToPreview() }
|
internal fun TextFieldsLightPreview() = ElementPreviewLight { ContentToPreview() }
|
||||||
|
|
||||||
@Preview(group = PreviewGroup.TextFields)
|
@Preview(group = PreviewGroup.TextFields, heightDp = 1000)
|
||||||
@Composable
|
@Composable
|
||||||
internal fun TextFieldsDarkPreview() = ElementPreviewDark { ContentToPreview() }
|
internal fun TextFieldsDarkPreview() = ElementPreviewDark { ContentToPreview() }
|
||||||
|
|
||||||
|
|
@ -295,15 +315,15 @@ internal fun TextFieldsDarkPreview() = ElementPreviewDark { ContentToPreview() }
|
||||||
@ExcludeFromCoverage
|
@ExcludeFromCoverage
|
||||||
private fun ContentToPreview() {
|
private fun ContentToPreview() {
|
||||||
Column(modifier = Modifier.padding(4.dp)) {
|
Column(modifier = Modifier.padding(4.dp)) {
|
||||||
allBooleans.forEach { isError ->
|
TextFieldValidity.entries.forEach { validity ->
|
||||||
allBooleans.forEach { enabled ->
|
allBooleans.forEach { enabled ->
|
||||||
allBooleans.forEach { readonly ->
|
allBooleans.forEach { readonly ->
|
||||||
TextField(
|
TextField(
|
||||||
onValueChange = {},
|
onValueChange = {},
|
||||||
label = "Label",
|
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",
|
supportingText = "Supporting text",
|
||||||
isError = isError,
|
validity = validity,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
readOnly = readonly,
|
readOnly = readonly,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
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.TextField
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TextFieldValidity
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.libraries.testtags.testTag
|
import io.element.android.libraries.testtags.testTag
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -56,7 +57,10 @@ fun RoomAddressField(
|
||||||
}
|
}
|
||||||
else -> supportingText
|
else -> supportingText
|
||||||
},
|
},
|
||||||
isError = addressValidity.isError(),
|
validity = when (addressValidity) {
|
||||||
|
RoomAddressValidity.InvalidSymbols, RoomAddressValidity.NotAvailable -> TextFieldValidity.Invalid
|
||||||
|
else -> null
|
||||||
|
},
|
||||||
onValueChange = onAddressChange,
|
onValueChange = onAddressChange,
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
package io.element.android.libraries.matrix.ui.room.address
|
package io.element.android.libraries.matrix.ui.room.address
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TextFieldValidity
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the validity state of a room address.
|
* Represents the validity state of a room address.
|
||||||
|
|
@ -19,8 +20,4 @@ sealed interface RoomAddressValidity {
|
||||||
data object InvalidSymbols : RoomAddressValidity
|
data object InvalidSymbols : RoomAddressValidity
|
||||||
data object NotAvailable : RoomAddressValidity
|
data object NotAvailable : RoomAddressValidity
|
||||||
data object Valid : RoomAddressValidity
|
data object Valid : RoomAddressValidity
|
||||||
|
|
||||||
fun isError(): Boolean {
|
|
||||||
return this is InvalidSymbols || this is NotAvailable
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue