diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressView.kt index ccc2fd6dbd..455f27c4bd 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/joinbyaddress/JoinRoomByAddressView.kt @@ -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, ) diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt index fbf91fa46d..04683d59f1 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt @@ -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)) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt index 2c118c3049..aedb962a64 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt @@ -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 { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt index 333716e6c2..ca56fbf19e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt @@ -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, ) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt index acd7723b87..fab4d153ec 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt @@ -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, ) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidity.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidity.kt index 25af0ab98d..d5813ce91f 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidity.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidity.kt @@ -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 - } }