commit d3434a4be26c7b894ace3914c5aa12afd6f7e430
parent 5278c912e0ada1080d3b37ba43ec9f65067a01bf
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Sun, 30 Jul 2023 17:28:48 +0200

ui(features): dialogs & save button

Diffstat:
Aapp/src/main/kotlin/me/rhunk/snapenhance/manager/Dialogs.kt | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/Navigation.kt | 3+--
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/sections/FeaturesSection.kt | 85++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
3 files changed, 232 insertions(+), 33 deletions(-)

diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/Dialogs.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/Dialogs.kt @@ -0,0 +1,176 @@ +package me.rhunk.snapenhance.manager + +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import me.rhunk.snapenhance.config.ConfigProperty +import me.rhunk.snapenhance.config.impl.ConfigIntegerValue +import me.rhunk.snapenhance.config.impl.ConfigStateListValue +import me.rhunk.snapenhance.config.impl.ConfigStateSelection +import me.rhunk.snapenhance.config.impl.ConfigStringValue + + +@Composable +@Preview +fun TestPreview() { + KeyboardInputDialog(config = ConfigProperty.SAVE_FOLDER) +} + +@Composable +fun DefaultDialogCard(content: @Composable ColumnScope.() -> Unit) { + Card( + shape = MaterialTheme.shapes.medium, + modifier = Modifier + .padding(10.dp, 5.dp, 10.dp, 10.dp), + ) { + Column( + modifier = Modifier + .padding(10.dp, 10.dp, 10.dp, 10.dp) + .verticalScroll(ScrollState(0)), + ) { content() } + } +} + +@Composable +fun DefaultEntryText(text: String, modifier: Modifier = Modifier) { + Text( + text = text, + modifier = Modifier + .padding(10.dp, 10.dp, 10.dp, 10.dp) + .then(modifier) + ) +} + +@Composable +fun StateSelectionDialog(config: ConfigProperty) { + assert(config.valueContainer is ConfigStateSelection) + val keys = (config.valueContainer as ConfigStateSelection).keys() + val selectedValue = remember { + mutableStateOf(config.valueContainer.value()) + } + DefaultDialogCard { + keys.forEach { item -> + fun select() { + selectedValue.value = item + config.valueContainer.writeFrom(item) + } + + Row( + modifier = Modifier.clickable { select() }, + verticalAlignment = Alignment.CenterVertically + ) { + DefaultEntryText( + text = item, + modifier = Modifier.weight(1f) + ) + RadioButton( + selected = selectedValue.value == item, + onClick = { select() } + ) + } + } + } +} + +@Composable +fun KeyboardInputDialog(config: ConfigProperty, dismiss: () -> Unit = {}) { + val focusRequester = remember { FocusRequester() } + + DefaultDialogCard { + val fieldValue = remember { mutableStateOf(config.valueContainer.read()) } + TextField( + modifier = Modifier.fillMaxWidth().padding(all = 10.dp).focusRequester(focusRequester), + value = fieldValue.value, onValueChange = { + fieldValue.value = it + }, + keyboardOptions = when (config.valueContainer) { + is ConfigIntegerValue -> { + KeyboardOptions(keyboardType = KeyboardType.Number) + } + else -> { + KeyboardOptions(keyboardType = KeyboardType.Text) + } + }, + singleLine = true + ) + + Row( + modifier = Modifier.padding(top = 10.dp).fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + Button(onClick = { dismiss() }) { + Text(text = "Cancel") + } + Button(onClick = { + config.valueContainer.writeFrom(fieldValue.value) + dismiss() + }) { + Text(text = "Ok") + } + } + } + + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } +} + +@Composable +fun StateListDialog(config: ConfigProperty) { + assert(config.valueContainer is ConfigStateListValue) + val stateList = (config.valueContainer as ConfigStateListValue).value() + DefaultDialogCard { + stateList.keys.forEach { key -> + val state = remember { + mutableStateOf(stateList[key] ?: false) + } + + fun toggle(value: Boolean? = null) { + state.value = value ?: !state.value + stateList[key] = state.value + } + + Row( + modifier = Modifier.clickable { toggle() }, + verticalAlignment = Alignment.CenterVertically + ) { + DefaultEntryText( + text = key, + modifier = Modifier + .weight(1f) + ) + Switch( + checked = state.value, + onCheckedChange = { + toggle(it) + } + ) + } + } + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/Navigation.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/Navigation.kt @@ -39,9 +39,8 @@ class Navigation( instance.manager = context instance.navController = navController } } - val homeSection = EnumSection.HOME - NavHost(navController, startDestination = homeSection.route, Modifier.padding(innerPadding)) { + NavHost(navController, startDestination = EnumSection.FEATURES.route, Modifier.padding(innerPadding)) { sections.forEach { (section, instance) -> composable(section.route) { instance.Content() diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/sections/FeaturesSection.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/sections/FeaturesSection.kt @@ -3,8 +3,7 @@ package me.rhunk.snapenhance.manager.sections import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -35,6 +34,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties import kotlinx.coroutines.launch import me.rhunk.snapenhance.config.ConfigProperty import me.rhunk.snapenhance.config.impl.ConfigIntegerValue @@ -42,60 +43,81 @@ import me.rhunk.snapenhance.config.impl.ConfigStateListValue import me.rhunk.snapenhance.config.impl.ConfigStateSelection import me.rhunk.snapenhance.config.impl.ConfigStateValue import me.rhunk.snapenhance.config.impl.ConfigStringValue +import me.rhunk.snapenhance.manager.StateListDialog import me.rhunk.snapenhance.manager.Section +import me.rhunk.snapenhance.manager.StateSelectionDialog +import me.rhunk.snapenhance.manager.KeyboardInputDialog typealias ClickCallback = (Boolean) -> Unit -typealias AddClickCallback = (ClickCallback) -> ClickCallback +typealias RegisterClickCallback = (ClickCallback) -> ClickCallback class FeaturesSection : Section() { @Composable - private fun PropertyAction(item: ConfigProperty, clickCallback: AddClickCallback) { - when (val configValueContainer = remember { item.valueContainer }) { - is ConfigStateValue -> { - val state = remember { - mutableStateOf(configValueContainer.value()) - } + private fun PropertyAction(item: ConfigProperty, registerClickCallback: RegisterClickCallback) { + val showDialog = remember { mutableStateOf(false) } + val dialogComposable = remember { mutableStateOf<@Composable () -> Unit>({})} + + fun registerDialogOnClickCallback() = registerClickCallback { + showDialog.value = true + } + if (showDialog.value) { + Dialog(onDismissRequest = { showDialog.value = false }, properties = DialogProperties()) { + dialogComposable.value() + } + } + + when (val container = remember { item.valueContainer }) { + is ConfigStateValue -> { + val state = remember { mutableStateOf(container.value()) } Switch( checked = state.value, - onCheckedChange = clickCallback { - state.value = !state.value - configValueContainer.writeFrom(state.value.toString()) + onCheckedChange = registerClickCallback { + state.value = state.value.not() + container.writeFrom(state.value.toString()) } ) } is ConfigStateSelection -> { + registerDialogOnClickCallback() + dialogComposable.value = { + StateSelectionDialog(item) + } Text( - text = configValueContainer.value().let { + text = container.value().let { it.substring(0, it.length.coerceAtMost(20)) } ) } - is ConfigStateListValue -> { - IconButton(onClick = { }) { - Icon(Icons.Filled.OpenInNew, contentDescription = null) - } - } - - is ConfigIntegerValue -> { - FilledIconButton(onClick = { }) { - Text(text = configValueContainer.value().toString()) + is ConfigStateListValue, is ConfigStringValue, is ConfigIntegerValue -> { + dialogComposable.value = { + when (container) { + is ConfigStateListValue -> { + StateListDialog(item) + } + is ConfigStringValue, is ConfigIntegerValue -> { + KeyboardInputDialog(item) { showDialog.value = false } + } + } } - } - is ConfigStringValue -> { - Text( - text = configValueContainer.value().let { - it.substring(0, it.length.coerceAtMost(20)) + registerDialogOnClickCallback().let { { it.invoke(true) } }.also { + if (container is ConfigIntegerValue) { + FilledIconButton(onClick = it) { + Text(text = container.value().toString()) + } + } else { + IconButton(onClick = it) { + Icon(Icons.Filled.OpenInNew, contentDescription = null) + } } - ) + } } } } - @OptIn(ExperimentalLayoutApi::class) @Composable private fun PropertyCard(item: ConfigProperty) { val clickCallback = remember { mutableStateOf<ClickCallback?>(null) } @@ -107,7 +129,7 @@ class FeaturesSection : Section() { } .padding(start = 10.dp, end = 10.dp, top = 5.dp, bottom = 5.dp) ) { - FlowRow( + Row( modifier = Modifier .fillMaxSize() .padding(all = 10.dp), @@ -132,7 +154,7 @@ class FeaturesSection : Section() { .align(Alignment.CenterVertically) .padding(all = 10.dp) ) { - PropertyAction(item, clickCallback = { callback -> + PropertyAction(item, registerClickCallback = { callback -> clickCallback.value = callback callback }) @@ -161,6 +183,7 @@ class FeaturesSection : Section() { } }, containerColor = MaterialTheme.colors.primary, + contentColor = MaterialTheme.colors.onPrimary, shape = RoundedCornerShape(16.dp), ) { Icon(