Skip to content

Instantly share code, notes, and snippets.

@yoloroy
Last active September 7, 2022 20:42
Show Gist options
  • Select an option

  • Save yoloroy/a24a8455e8bee059d77afb2a8595ac22 to your computer and use it in GitHub Desktop.

Select an option

Save yoloroy/a24a8455e8bee059d77afb2a8595ac22 to your computer and use it in GitHub Desktop.
ListInputComponent
package components
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AddCircle
import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@Composable
fun <T : Presentable> ListInput(stateHolder: ListInputStateHolder<T>) {
val items = stateHolder.state.presentationList
val errorMessage = stateHolder.state.errorText
var currentEditItemIndex: Int by remember { mutableStateOf(-1) }
var currentEditedText: String by remember { mutableStateOf("null") }
fun onEditItem(index: Int, text: String): () -> Unit = {
currentEditItemIndex = index
currentEditedText = text
}
fun onStopEditing(index: Int): () -> Unit = {
currentEditItemIndex = -1
if (currentEditedText.isBlank()) stateHolder.remove(index)
else stateHolder.update(index, currentEditedText)
}
val onAppend: () -> Unit = {
currentEditItemIndex = items.size
currentEditedText = stateHolder.append()
}
Column {
errorMessage?.let { Text(it) }
Spacer(modifier = Modifier.height(12.dp))
LazyRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
itemsIndexed(items) { index, text ->
if (index == currentEditItemIndex) {
OutlinedTextField(
value = currentEditedText,
onValueChange = { currentEditedText = it },
modifier = Modifier.widthIn(1.dp, Dp.Infinity),
trailingIcon = {
IconButton(onClick = onStopEditing(index)) {
Icon(Icons.Default.Close, "Cancel")
}
}
)
} else {
Text(
text = text,
modifier = Modifier
.padding(vertical = 8.dp, horizontal = 4.dp)
.clickable(onClick = onEditItem(index, text))
)
}
}
item {
IconButton(onClick = onAppend) {
Icon(Icons.Default.AddCircle, "Append")
}
}
}
}
}
@Composable
@Preview
fun ListInputPreview() {
val presentables = "hello".map { c -> Presentable.String(c.toString()) }
val stateHolder = ListInputStateHolder(presentables, Presentable.String.Creator, "Bad input")
Box(
modifier = Modifier.size(400.dp),
contentAlignment = Alignment.Center
) {
ListInput(stateHolder)
}
}
class ListInputStateHolder<T : Presentable>(
initial: List<T>,
private val creator: Presentable.Creator<T>,
private val errorTextBadValue: String
) {
var state: ListInputState<T> by mutableStateOf(ListInputState(initial))
private set
/**
* @return [creator].defaultValue
*/
fun append(): String {
val value = creator.defaultValue
state = state.copy(list = state.list + value, errorText = null)
return value.toText()
}
fun remove(index: Int) {
val previousValues = state.list.take(index)
val postValues = state.list.takeLast(state.list.size - index - 1)
state = state.copy(list = previousValues + postValues, errorText = null)
}
fun update(index: Int, stringValue: String) {
val value = creator.valueOfOrNull(stringValue)
if (value == null) {
state = state.copy(errorText = errorTextBadValue)
return
}
val previousValues = state.list.take(index)
val postValues = state.list.takeLast(state.list.size - index - 1)
state = state.copy(list = previousValues + value + postValues, errorText = null)
}
}
data class ListInputState<T : Presentable>(
val list: List<T>,
val errorText: String? = null
) {
val presentationList: List<String> get() = list.map { it.toText() }
}
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import components.ListInput
import components.ListInputStateHolder
import components.Presentable
@Composable
@Preview
fun App() {
val presentables = listOf(1, 2, 3, 4, 5).map { Presentable.Int(it) }
val stateHolder = ListInputStateHolder(presentables, Presentable.Int.Creator, "Bad input")
MaterialTheme {
ListInput(stateHolder)
}
}
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
App()
}
}
package components
interface Presentable {
fun toText(): kotlin.String
interface Creator<T : Presentable> {
val defaultValue: T
fun valueOfOrNull(string: kotlin.String): T?
}
class Int(val int: kotlin.Int) : Presentable {
override fun toText() = int.toString()
object Creator : Presentable.Creator<Int> {
override val defaultValue: Int get() = Int(0)
override fun valueOfOrNull(string: kotlin.String) = string.toIntOrNull()?.let { Int(it) }
}
}
class String(val string: kotlin.String) : Presentable {
override fun toText() = string
object Creator : Presentable.Creator<String> {
override val defaultValue: String get() = String("+")
override fun valueOfOrNull(string: kotlin.String) = String(string)
}
}
}
@yoloroy
Copy link
Author

yoloroy commented Sep 7, 2022

2022-09-07.23.01.05.mov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment