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:
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(