Add TextFieldOutlinedSelect for selecting entries from a set of options
This commit is contained in:
parent
520dc4049c
commit
7d18529e73
3 changed files with 185 additions and 2 deletions
|
@ -0,0 +1,141 @@
|
|||
package app.k9mail.core.ui.compose.designsystem.atom.textfield
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.ExposedDropdownMenuBox
|
||||
import androidx.compose.material.Text
|
||||
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.Icon
|
||||
import app.k9mail.core.ui.compose.theme.Icons
|
||||
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun <T> TextFieldOutlinedSelect(
|
||||
options: ImmutableList<T>,
|
||||
selectedOption: T,
|
||||
onValueChange: (T) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
label: String? = null,
|
||||
isEnabled: Boolean = true,
|
||||
isReadOnly: Boolean = false,
|
||||
isRequired: Boolean = false,
|
||||
hasError: Boolean = false,
|
||||
) {
|
||||
TextFieldDropDownWrapper(
|
||||
isReadOnlyOrDisabled = isReadOnly || !isEnabled,
|
||||
options = options,
|
||||
onValueChange = onValueChange,
|
||||
) { expanded ->
|
||||
TextFieldOutlined(
|
||||
value = selectedOption.toString(),
|
||||
onValueChange = { },
|
||||
label = label,
|
||||
trailingIcon = selectTrailingIcon(expanded),
|
||||
isEnabled = isEnabled,
|
||||
isReadOnly = true,
|
||||
isRequired = isRequired,
|
||||
hasError = hasError,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.then(modifier),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectTrailingIcon(
|
||||
isExpanded: Boolean,
|
||||
): @Composable () -> Unit {
|
||||
return if (isExpanded) {
|
||||
{ Icon(Icons.Outlined.arrowDropUp) }
|
||||
} else {
|
||||
{ Icon(Icons.Outlined.arrowDropDown) }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun <T> TextFieldDropDownWrapper(
|
||||
isReadOnlyOrDisabled: Boolean,
|
||||
options: ImmutableList<T>,
|
||||
onValueChange: (T) -> Unit,
|
||||
content: @Composable (expanded: Boolean) -> Unit,
|
||||
) {
|
||||
if (isReadOnlyOrDisabled) {
|
||||
content(false)
|
||||
} else {
|
||||
DropDownMenu(
|
||||
options = options,
|
||||
onValueChange = onValueChange,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun <T> DropDownMenu(
|
||||
options: ImmutableList<T>,
|
||||
onValueChange: (T) -> Unit,
|
||||
content: @Composable (expanded: Boolean) -> Unit,
|
||||
) {
|
||||
var expanded = remember { mutableStateOf(false) }
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded.value,
|
||||
onExpandedChange = {
|
||||
expanded.value = expanded.value.not()
|
||||
},
|
||||
) {
|
||||
content(expanded.value)
|
||||
|
||||
ExposedDropdownMenu(
|
||||
expanded = expanded.value,
|
||||
onDismissRequest = {
|
||||
expanded.value = false
|
||||
},
|
||||
) {
|
||||
options.forEach { option ->
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onValueChange(option)
|
||||
expanded.value = false
|
||||
},
|
||||
) {
|
||||
Text(text = option.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun TextFieldOutlinedSelectPreview() {
|
||||
PreviewWithThemes {
|
||||
TextFieldOutlinedSelect(
|
||||
options = persistentListOf("Option 1", "Option 2", "Option 3"),
|
||||
selectedOption = "Option 1",
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun TextFieldOutlinedSelectPreviewWithLabel() {
|
||||
PreviewWithThemes {
|
||||
TextFieldOutlinedSelect(
|
||||
options = persistentListOf("Option 1", "Option 2", "Option 3"),
|
||||
selectedOption = "Option 1",
|
||||
onValueChange = {},
|
||||
label = "Label",
|
||||
)
|
||||
}
|
||||
}
|
|
@ -14,6 +14,8 @@ import app.k9mail.core.ui.compose.testing.ComposeTest
|
|||
import assertk.assertFailure
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner
|
||||
|
@ -36,6 +38,7 @@ data class TextFieldTestData<INPUT, VALUE>(
|
|||
modifier: Modifier,
|
||||
textFieldConfig: TextFieldConfig,
|
||||
) -> Unit,
|
||||
val isSelectionTest: Boolean = false,
|
||||
)
|
||||
|
||||
@RunWith(ParameterizedRobolectricTestRunner::class)
|
||||
|
@ -46,6 +49,7 @@ class TextFieldKtTest(
|
|||
private val testSubjectName = data.name
|
||||
private val testSubject = data.content
|
||||
private val testInput = data.input
|
||||
private val isSelectionTest = data.isSelectionTest
|
||||
|
||||
@Test
|
||||
fun `should call onValueChange when value changes`() = runComposeTest {
|
||||
|
@ -65,9 +69,14 @@ class TextFieldKtTest(
|
|||
}
|
||||
|
||||
onNodeWithTag(testSubjectName).performClick()
|
||||
onNodeWithTag(testSubjectName).performTextInput(" + added text")
|
||||
|
||||
assertThat(value).isEqualTo("$testInput + added text")
|
||||
if (isSelectionTest) {
|
||||
onNodeWithText((testInput as ImmutableList<*>)[1].toString()).performClick()
|
||||
assertThat(value).isEqualTo(testInput[1])
|
||||
} else {
|
||||
onNodeWithTag(testSubjectName).performTextInput(" + added text")
|
||||
assertThat(value).isEqualTo("$testInput + added text")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -270,6 +279,37 @@ class TextFieldKtTest(
|
|||
}
|
||||
},
|
||||
),
|
||||
TextFieldTestData(
|
||||
name = "TextFieldOutlinedSelect",
|
||||
input = persistentListOf("option1", "option2"),
|
||||
isSelectionTest = true,
|
||||
content = { value, onValueChange: (Any) -> Unit, modifier, config ->
|
||||
if (config.isEnabled != null) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
TextFieldOutlinedSelect(
|
||||
options = value as ImmutableList<Any>,
|
||||
selectedOption = value.first(),
|
||||
onValueChange = onValueChange,
|
||||
modifier = modifier,
|
||||
label = config.label,
|
||||
isEnabled = config.isEnabled,
|
||||
isReadOnly = config.isReadOnly,
|
||||
isRequired = config.isRequired,
|
||||
)
|
||||
} else {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
TextFieldOutlinedSelect(
|
||||
options = value as ImmutableList<Any>,
|
||||
selectedOption = value.first(),
|
||||
onValueChange = onValueChange,
|
||||
modifier = modifier,
|
||||
label = config.label,
|
||||
isRequired = config.isRequired,
|
||||
isReadOnly = config.isReadOnly,
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-c
|
|||
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
|
||||
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
|
||||
kotlinx-datetime = "org.jetbrains.kotlinx:kotlinx-datetime:0.4.0"
|
||||
kotlinx-collections-immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5"
|
||||
jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrainsAnnotations" }
|
||||
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidxAppCompat" }
|
||||
androidx-activity = { module = "androidx.activity:activity", version.ref = "androidxActivity" }
|
||||
|
@ -166,6 +167,7 @@ shared-jvm-android-app = [
|
|||
"android-material",
|
||||
]
|
||||
shared-jvm-android-compose = [
|
||||
"kotlinx-collections-immutable",
|
||||
"androidx-compose-foundation",
|
||||
"androidx-compose-ui-tooling-preview",
|
||||
"androidx-compose-lifecycle-runtime",
|
||||
|
|
Loading…
Reference in a new issue