ui(features): dialogs & save button

This commit is contained in:
rhunk
2023-07-30 17:28:48 +02:00
parent 5278c912e0
commit d3434a4be2
3 changed files with 233 additions and 35 deletions

View File

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

View File

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

View File

@@ -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 ConfigStringValue -> {
Text(
text = configValueContainer.value().let {
it.substring(0, it.length.coerceAtMost(20))
is ConfigStateListValue, is ConfigStringValue, is ConfigIntegerValue -> {
dialogComposable.value = {
when (container) {
is ConfigStateListValue -> {
StateListDialog(item)
}
is ConfigStringValue, is ConfigIntegerValue -> {
KeyboardInputDialog(item) { showDialog.value = false }
}
}
)
}
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(