From 9bd37efe3d1d2378f1a8a372580eafacdcfe98a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montwe=CC=81?= Date: Thu, 1 Jun 2023 16:49:27 +0200 Subject: [PATCH] Add TextFieldOutlinedNumber atom and NumberInput molecule to the design system --- .../catalog/ui/atom/items/TextFieldItems.kt | 25 ++++++ .../catalog/ui/molecule/items/InputItems.kt | 26 ++++++ .../TextFieldOutlinedEmailAddress.kt | 4 +- .../atom/textfield/TextFieldOutlinedNumber.kt | 85 +++++++++++++++++++ .../molecule/input/EmailAddressInput.kt | 2 +- .../{TextInputLayout.kt => InputLayout.kt} | 2 +- .../molecule/input/NumberInput.kt | 73 ++++++++++++++++ .../molecule/input/PasswordInput.kt | 2 +- .../designsystem/molecule/input/TextInput.kt | 2 +- .../atom/textfield/CommonTextFieldTest.kt | 25 ++++++ .../TextFieldOutlinedNumberKtTest.kt | 85 +++++++++++++++++++ 11 files changed, 325 insertions(+), 6 deletions(-) create mode 100644 core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/textfield/TextFieldOutlinedNumber.kt rename core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/{TextInputLayout.kt => InputLayout.kt} (97%) create mode 100644 core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/NumberInput.kt create mode 100644 core/ui/compose/designsystem/src/test/kotlin/app/k9mail/core/ui/compose/designsystem/atom/textfield/TextFieldOutlinedNumberKtTest.kt diff --git a/app-ui-catalog/src/main/java/app/k9mail/ui/catalog/ui/atom/items/TextFieldItems.kt b/app-ui-catalog/src/main/java/app/k9mail/ui/catalog/ui/atom/items/TextFieldItems.kt index 223f2b434..c51963753 100644 --- a/app-ui-catalog/src/main/java/app/k9mail/ui/catalog/ui/atom/items/TextFieldItems.kt +++ b/app-ui-catalog/src/main/java/app/k9mail/ui/catalog/ui/atom/items/TextFieldItems.kt @@ -16,6 +16,7 @@ import app.k9mail.core.ui.compose.designsystem.atom.Icon import app.k9mail.core.ui.compose.designsystem.atom.text.TextSubtitle1 import app.k9mail.core.ui.compose.designsystem.atom.textfield.TextFieldOutlined import app.k9mail.core.ui.compose.designsystem.atom.textfield.TextFieldOutlinedEmailAddress +import app.k9mail.core.ui.compose.designsystem.atom.textfield.TextFieldOutlinedNumber import app.k9mail.core.ui.compose.designsystem.atom.textfield.TextFieldOutlinedPassword import app.k9mail.core.ui.compose.designsystem.atom.textfield.TextFieldOutlinedSelect import app.k9mail.core.ui.compose.designsystem.molecule.input.CheckboxInput @@ -36,6 +37,8 @@ fun LazyGridScope.textFieldItems() { passwordTextFieldOutlinedItems() sectionSubtitleItem(text = "Email address") emailTextFieldOutlinedItems() + sectionSubtitleItem(text = "Number") + numberTextFieldOutlinedItems() sectionSubtitleItem(text = "Selection") selectionTextFieldOutlinedItems() } @@ -215,6 +218,28 @@ private fun LazyGridScope.emailTextFieldOutlinedItems() { } } +private fun LazyGridScope.numberTextFieldOutlinedItems() { + item { + TextFieldDemo( + initialState = TextFieldState( + input = 123L, + label = "Number", + ), + ) { state -> + TextFieldOutlinedNumber( + value = state.value.input, + label = if (state.value.showLabel) state.value.label else null, + onValueChange = { state.value = state.value.copy(input = it) }, + isEnabled = !state.value.isDisabled, + isReadOnly = state.value.isReadOnly, + isRequired = state.value.isRequired, + hasError = state.value.hasError, + modifier = Modifier.fillMaxWidth(), + ) + } + } +} + private data class TextFieldSelectState( val options: ImmutableList = persistentListOf("Option 1", "Option 2", "Option 3"), val selectedOption: String = options.first(), diff --git a/app-ui-catalog/src/main/java/app/k9mail/ui/catalog/ui/molecule/items/InputItems.kt b/app-ui-catalog/src/main/java/app/k9mail/ui/catalog/ui/molecule/items/InputItems.kt index fa75f6019..30850f064 100644 --- a/app-ui-catalog/src/main/java/app/k9mail/ui/catalog/ui/molecule/items/InputItems.kt +++ b/app-ui-catalog/src/main/java/app/k9mail/ui/catalog/ui/molecule/items/InputItems.kt @@ -3,6 +3,7 @@ package app.k9mail.ui.catalog.ui.molecule.items import androidx.compose.foundation.lazy.grid.LazyGridScope import app.k9mail.core.ui.compose.designsystem.molecule.input.CheckboxInput import app.k9mail.core.ui.compose.designsystem.molecule.input.EmailAddressInput +import app.k9mail.core.ui.compose.designsystem.molecule.input.NumberInput import app.k9mail.core.ui.compose.designsystem.molecule.input.PasswordInput import app.k9mail.core.ui.compose.designsystem.molecule.input.SelectInput import app.k9mail.core.ui.compose.designsystem.molecule.input.SwitchInput @@ -65,6 +66,31 @@ fun LazyGridScope.inputItems() { } } + sectionHeaderItem(text = "NumberInput") + sectionSubtitleItem(text = "Default") + item { + ItemOutlined { + WithRememberedState(input = null) { state -> + NumberInput( + value = state.value, + onValueChange = { state.value = it }, + ) + } + } + } + sectionSubtitleItem(text = "With error") + item { + ItemOutlined { + WithRememberedState(input = 123L) { state -> + NumberInput( + value = state.value, + onValueChange = { state.value = it }, + errorMessage = "Invalid number", + ) + } + } + } + sectionHeaderItem(text = "PasswordInput") sectionSubtitleItem(text = "Default") item { diff --git a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/textfield/TextFieldOutlinedEmailAddress.kt b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/textfield/TextFieldOutlinedEmailAddress.kt index dddbf2cff..abf2d929b 100644 --- a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/textfield/TextFieldOutlinedEmailAddress.kt +++ b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/textfield/TextFieldOutlinedEmailAddress.kt @@ -39,7 +39,7 @@ fun TextFieldOutlinedEmailAddress( @Composable internal fun TextFieldOutlinedEmailAddressPreview() { PreviewWithThemes { - TextFieldOutlined( + TextFieldOutlinedEmailAddress( value = "Input text", onValueChange = {}, ) @@ -74,7 +74,7 @@ internal fun TextFieldOutlinedEmailDisabledPreview() { @Composable internal fun TextFieldOutlinedEmailErrorPreview() { PreviewWithThemes { - TextFieldOutlined( + TextFieldOutlinedEmailAddress( value = "Input text", onValueChange = {}, hasError = true, diff --git a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/textfield/TextFieldOutlinedNumber.kt b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/textfield/TextFieldOutlinedNumber.kt new file mode 100644 index 000000000..ad409301c --- /dev/null +++ b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/textfield/TextFieldOutlinedNumber.kt @@ -0,0 +1,85 @@ +package app.k9mail.core.ui.compose.designsystem.atom.textfield + +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import app.k9mail.core.ui.compose.theme.PreviewWithThemes +import androidx.compose.material.OutlinedTextField as MaterialOutlinedTextField + +@Suppress("LongParameterList") +@Composable +fun TextFieldOutlinedNumber( + value: Long?, + onValueChange: (Long?) -> Unit, + modifier: Modifier = Modifier, + label: String? = null, + isEnabled: Boolean = true, + isReadOnly: Boolean = false, + isRequired: Boolean = false, + hasError: Boolean = false, +) { + MaterialOutlinedTextField( + value = value?.toString() ?: "", + onValueChange = { + onValueChange(it.toLongOrNull()) + }, + modifier = modifier, + enabled = isEnabled, + label = selectLabel(label, isRequired), + readOnly = isReadOnly, + isError = hasError, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.NumberPassword, + ), + singleLine = true, + ) +} + +@Preview(showBackground = true) +@Composable +internal fun TextFieldOutlinedNumberPreview() { + PreviewWithThemes { + TextFieldOutlinedNumber( + value = 123L, + onValueChange = {}, + ) + } +} + +@Preview(showBackground = true) +@Composable +internal fun TextFieldOutlinedNumberWithLabelPreview() { + PreviewWithThemes { + TextFieldOutlinedNumber( + value = 123L, + label = "Label", + onValueChange = {}, + ) + } +} + +@Preview(showBackground = true) +@Composable +internal fun TextFieldOutlinedNumberDisabledPreview() { + PreviewWithThemes { + TextFieldOutlinedNumber( + value = 123L, + onValueChange = {}, + isEnabled = false, + ) + } +} + +@Preview(showBackground = true) +@Composable +internal fun TextFieldOutlinedNumberErrorPreview() { + PreviewWithThemes { + TextFieldOutlinedNumber( + value = 123L, + onValueChange = {}, + hasError = true, + ) + } +} diff --git a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/EmailAddressInput.kt b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/EmailAddressInput.kt index 7dcee2714..4401ee853 100644 --- a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/EmailAddressInput.kt +++ b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/EmailAddressInput.kt @@ -18,7 +18,7 @@ fun EmailAddressInput( errorMessage: String? = null, contentPadding: PaddingValues = inputContentPadding(), ) { - TextInputLayout( + InputLayout( modifier = modifier, contentPadding = contentPadding, errorMessage = errorMessage, diff --git a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/TextInputLayout.kt b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/InputLayout.kt similarity index 97% rename from core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/TextInputLayout.kt rename to core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/InputLayout.kt index 91cb1b5e4..466183c9d 100644 --- a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/TextInputLayout.kt +++ b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/InputLayout.kt @@ -11,7 +11,7 @@ import app.k9mail.core.ui.compose.designsystem.atom.text.TextCaption import app.k9mail.core.ui.compose.theme.MainTheme @Composable -internal fun TextInputLayout( +internal fun InputLayout( modifier: Modifier = Modifier, contentPadding: PaddingValues = inputContentPadding(), errorMessage: String? = null, diff --git a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/NumberInput.kt b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/NumberInput.kt new file mode 100644 index 000000000..fdde931a8 --- /dev/null +++ b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/NumberInput.kt @@ -0,0 +1,73 @@ +package app.k9mail.core.ui.compose.designsystem.molecule.input + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import app.k9mail.core.ui.compose.designsystem.atom.textfield.TextFieldOutlinedNumber +import app.k9mail.core.ui.compose.theme.PreviewWithThemes + +@Suppress("LongParameterList") +@Composable +fun NumberInput( + onValueChange: (Long?) -> Unit, + modifier: Modifier = Modifier, + value: Long? = null, + label: String? = null, + isRequired: Boolean = false, + errorMessage: String? = null, + contentPadding: PaddingValues = inputContentPadding(), +) { + val inputError = remember { mutableStateOf(null) } + + InputLayout( + modifier = modifier, + contentPadding = contentPadding, + errorMessage = errorMessage ?: inputError.value, + ) { + TextFieldOutlinedNumber( + value = value, + onValueChange = onValueChange, + label = label, + isRequired = isRequired, + hasError = errorMessage != null, + modifier = Modifier.fillMaxWidth(), + ) + } +} + +@Preview(showBackground = true) +@Composable +internal fun IntegerInputPreview() { + PreviewWithThemes { + NumberInput( + onValueChange = {}, + ) + } +} + +@Preview(showBackground = true) +@Composable +internal fun IntegerInputIsRequiredPreview() { + PreviewWithThemes { + NumberInput( + onValueChange = {}, + label = "Text input is required", + isRequired = true, + ) + } +} + +@Preview(showBackground = true) +@Composable +internal fun IntegerInputWithErrorPreview() { + PreviewWithThemes { + NumberInput( + onValueChange = {}, + errorMessage = "Text input error", + ) + } +} diff --git a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/PasswordInput.kt b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/PasswordInput.kt index 49f43f402..e7d17d83c 100644 --- a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/PasswordInput.kt +++ b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/PasswordInput.kt @@ -18,7 +18,7 @@ fun PasswordInput( errorMessage: String? = null, contentPadding: PaddingValues = inputContentPadding(), ) { - TextInputLayout( + InputLayout( modifier = modifier, contentPadding = contentPadding, errorMessage = errorMessage, diff --git a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/TextInput.kt b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/TextInput.kt index e78b3ef63..a78f1a65f 100644 --- a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/TextInput.kt +++ b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/molecule/input/TextInput.kt @@ -20,7 +20,7 @@ fun TextInput( contentPadding: PaddingValues = inputContentPadding(), isSingleLine: Boolean = true, ) { - TextInputLayout( + InputLayout( modifier = modifier, contentPadding = contentPadding, errorMessage = errorMessage, diff --git a/core/ui/compose/designsystem/src/test/kotlin/app/k9mail/core/ui/compose/designsystem/atom/textfield/CommonTextFieldTest.kt b/core/ui/compose/designsystem/src/test/kotlin/app/k9mail/core/ui/compose/designsystem/atom/textfield/CommonTextFieldTest.kt index fc4df6fb8..b8338ee59 100644 --- a/core/ui/compose/designsystem/src/test/kotlin/app/k9mail/core/ui/compose/designsystem/atom/textfield/CommonTextFieldTest.kt +++ b/core/ui/compose/designsystem/src/test/kotlin/app/k9mail/core/ui/compose/designsystem/atom/textfield/CommonTextFieldTest.kt @@ -227,6 +227,31 @@ class CommonTextFieldTest( } }, ), + CommonTextFieldTestData( + name = "TextFieldOutlinedNumber", + content = { modifier, config -> + if (config.isEnabled != null) { + TextFieldOutlinedNumber( + value = 123L, + onValueChange = {}, + modifier = modifier, + label = config.label, + isEnabled = config.isEnabled, + isReadOnly = config.isReadOnly, + isRequired = config.isRequired, + ) + } else { + TextFieldOutlinedNumber( + value = 123L, + onValueChange = {}, + modifier = modifier, + label = config.label, + isRequired = config.isRequired, + isReadOnly = config.isReadOnly, + ) + } + }, + ), CommonTextFieldTestData( name = "TextFieldOutlinedSelect", content = { modifier, config -> diff --git a/core/ui/compose/designsystem/src/test/kotlin/app/k9mail/core/ui/compose/designsystem/atom/textfield/TextFieldOutlinedNumberKtTest.kt b/core/ui/compose/designsystem/src/test/kotlin/app/k9mail/core/ui/compose/designsystem/atom/textfield/TextFieldOutlinedNumberKtTest.kt new file mode 100644 index 000000000..342db161f --- /dev/null +++ b/core/ui/compose/designsystem/src/test/kotlin/app/k9mail/core/ui/compose/designsystem/atom/textfield/TextFieldOutlinedNumberKtTest.kt @@ -0,0 +1,85 @@ +package app.k9mail.core.ui.compose.designsystem.atom.textfield + +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTextClearance +import androidx.compose.ui.test.performTextInput +import app.k9mail.core.ui.compose.testing.ComposeTest +import assertk.assertThat +import assertk.assertions.isEqualTo +import org.junit.Test + +private const val TEST_TAG = "TextFieldOutlinedNumber" + +class TextFieldOutlinedNumberKtTest : ComposeTest() { + + @Test + fun `should call onValueChange with null when input is empty`() = runComposeTest { + var value: Long? = 1L + setContent { + TextFieldOutlinedNumber( + value = value, + onValueChange = { value = it }, + modifier = Modifier.testTag(TEST_TAG), + ) + } + + onNodeWithTag(TEST_TAG).performClick() + onNodeWithTag(TEST_TAG).performTextClearance() + + assertThat(value).isEqualTo(null) + } + + @Test + fun `should call onValueChange when value changes`() = runComposeTest { + var value: Long? = 123L + setContent { + TextFieldOutlinedNumber( + value = value, + onValueChange = { value = it }, + modifier = Modifier.testTag(TEST_TAG), + ) + } + + onNodeWithTag(TEST_TAG).performClick() + onNodeWithTag(TEST_TAG).performTextInput("456") + + assertThat(value).isEqualTo(123456L) + } + + @Test + fun `should return null when no number`() = runComposeTest { + var value: Long? = 123L + setContent { + TextFieldOutlinedNumber( + value = value, + onValueChange = { value = it }, + modifier = Modifier.testTag(TEST_TAG), + ) + } + + onNodeWithTag(TEST_TAG).performClick() + onNodeWithTag(TEST_TAG).performTextInput(",") + + assertThat(value).isEqualTo(null) + } + + @Test + fun `should return null when input exceeds max long`() = runComposeTest { + var value: Long? = null + setContent { + TextFieldOutlinedNumber( + value = value, + onValueChange = { value = it }, + modifier = Modifier.testTag(TEST_TAG), + ) + } + + onNodeWithTag(TEST_TAG).performClick() + onNodeWithTag(TEST_TAG).performTextInput("9223372036854775808") + + assertThat(value).isEqualTo(null) + } +}