ui(features): dialogs & save button
This commit is contained in:
176
app/src/main/kotlin/me/rhunk/snapenhance/manager/Dialogs.kt
Normal file
176
app/src/main/kotlin/me/rhunk/snapenhance/manager/Dialogs.kt
Normal 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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user