Merge pull request #6948 from thundernest/add_number_input

Add TextFieldOutlinedNumber atom and NumberInput molecule to the design system
This commit is contained in:
Wolf-Martell Montwé 2023-06-01 16:40:35 +00:00 committed by GitHub
commit f99f6309ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 325 additions and 6 deletions

View file

@ -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<Long?>(
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<String> = persistentListOf("Option 1", "Option 2", "Option 3"),
val selectedOption: String = options.first(),

View file

@ -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<Long?>(input = null) { state ->
NumberInput(
value = state.value,
onValueChange = { state.value = it },
)
}
}
}
sectionSubtitleItem(text = "With error")
item {
ItemOutlined {
WithRememberedState<Long?>(input = 123L) { state ->
NumberInput(
value = state.value,
onValueChange = { state.value = it },
errorMessage = "Invalid number",
)
}
}
}
sectionHeaderItem(text = "PasswordInput")
sectionSubtitleItem(text = "Default")
item {

View file

@ -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,

View file

@ -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,
)
}
}

View file

@ -18,7 +18,7 @@ fun EmailAddressInput(
errorMessage: String? = null,
contentPadding: PaddingValues = inputContentPadding(),
) {
TextInputLayout(
InputLayout(
modifier = modifier,
contentPadding = contentPadding,
errorMessage = errorMessage,

View file

@ -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,

View file

@ -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<String?>(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",
)
}
}

View file

@ -18,7 +18,7 @@ fun PasswordInput(
errorMessage: String? = null,
contentPadding: PaddingValues = inputContentPadding(),
) {
TextInputLayout(
InputLayout(
modifier = modifier,
contentPadding = contentPadding,
errorMessage = errorMessage,

View file

@ -20,7 +20,7 @@ fun TextInput(
contentPadding: PaddingValues = inputContentPadding(),
isSingleLine: Boolean = true,
) {
TextInputLayout(
InputLayout(
modifier = modifier,
contentPadding = contentPadding,
errorMessage = errorMessage,

View file

@ -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 ->

View file

@ -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)
}
}