@@ -28,6 +28,7 @@ import androidx.compose.material.icons.outlined.Add
2828import androidx.compose.material.icons.outlined.Edit
2929import androidx.compose.material.icons.outlined.Folder
3030import androidx.compose.material.icons.outlined.MoreVert
31+ import androidx.compose.material.icons.outlined.Visibility
3132import androidx.compose.material3.AlertDialog
3233import androidx.compose.material3.ButtonDefaults
3334import androidx.compose.material3.DropdownMenu
@@ -94,15 +95,18 @@ private class OptionEditorScope<T : Any>(
9495 val selectionWarningEnabled : Boolean ,
9596 val showSelectionWarning : () -> Unit ,
9697 val value : T ? ,
97- val setValue : (T ? ) -> Unit
98+ val setValue : (T ? ) -> Unit ,
99+ val readOnly : Boolean
98100) {
99101 fun submitDialog (value : T ? ) {
100102 setValue(value)
101103 dismissDialog()
102104 }
103105
104106 fun checkSafeguard (block : () -> Unit ) {
105- if (! option.required && selectionWarningEnabled)
107+ if (readOnly)
108+ block()
109+ else if (! option.required && selectionWarningEnabled)
106110 showSelectionWarning()
107111 else
108112 block()
@@ -131,7 +135,10 @@ private interface OptionEditor<T : Any> {
131135 onClick = { scope.checkSafeguard { clickAction(scope) } },
132136 shapes = IconButtonDefaults .shapes(),
133137 ) {
134- Icon (Icons .Outlined .Edit , stringResource(R .string.edit))
138+ Icon (
139+ if (scope.readOnly) Icons .Outlined .Visibility else Icons .Outlined .Edit ,
140+ stringResource(if (scope.readOnly) R .string.show else R .string.edit)
141+ )
135142 }
136143 }
137144
@@ -159,13 +166,14 @@ private inline fun <T : Any> WithOptionEditor(
159166 value : T ? ,
160167 noinline setValue : (T ? ) -> Unit ,
161168 selectionWarningEnabled : Boolean ,
169+ readOnly : Boolean ,
162170 crossinline onDismissDialog : @DisallowComposableCalls () -> Unit = {},
163171 block : OptionEditorScope <T >.() -> Unit
164172) {
165173 var showDialog by rememberSaveable { mutableStateOf(false ) }
166174 var showSelectionWarningDialog by rememberSaveable { mutableStateOf(false ) }
167175
168- val scope = remember(editor, option, value, setValue, selectionWarningEnabled) {
176+ val scope = remember(editor, option, value, setValue, selectionWarningEnabled, readOnly ) {
169177 OptionEditorScope (
170178 editor,
171179 option,
@@ -177,7 +185,8 @@ private inline fun <T : Any> WithOptionEditor(
177185 selectionWarningEnabled,
178186 showSelectionWarning = { showSelectionWarningDialog = true },
179187 value,
180- setValue
188+ setValue,
189+ readOnly
181190 )
182191 }
183192
@@ -196,7 +205,8 @@ fun <T : Any> OptionItem(
196205 option : Option <T >,
197206 value : T ? ,
198207 setValue : (T ? ) -> Unit ,
199- selectionWarningEnabled : Boolean
208+ selectionWarningEnabled : Boolean ,
209+ readOnly : Boolean = false
200210) {
201211 val editor = remember(option.type, option.presets) {
202212 @Suppress(" UNCHECKED_CAST" )
@@ -209,7 +219,7 @@ fun <T : Any> OptionItem(
209219 else baseOptionEditor
210220 }
211221
212- WithOptionEditor (editor, option, value, setValue, selectionWarningEnabled) {
222+ WithOptionEditor (editor, option, value, setValue, selectionWarningEnabled, readOnly ) {
213223 ListItem (
214224 modifier = Modifier .clickable(onClick = ::clickAction),
215225 headlineContent = { Text (option.name) },
@@ -227,10 +237,71 @@ fun <T : Any> OptionItem(
227237 }
228238}
229239
240+ private fun <T > optionValueLabelPlain (
241+ option : Option <T >,
242+ value : T ? ,
243+ fallBackToDefault : Boolean = true,
244+ unsetLabel : String
245+ ): String {
246+ val resolved = if (fallBackToDefault) value ? : option.default else value
247+ val presetLabel = option.presets?.entries?.firstOrNull { it.value == resolved }?.key
248+
249+ return when {
250+ presetLabel != null && resolved != null -> " $presetLabel ($resolved )"
251+ presetLabel != null -> presetLabel
252+ resolved == null -> unsetLabel
253+ else -> resolved.toString()
254+ }
255+ }
256+
257+ @Composable
258+ private fun <T > optionValueLabel (
259+ option : Option <T >,
260+ value : T ? ,
261+ fallBackToDefault : Boolean = true
262+ ) = optionValueLabelPlain(
263+ option = option,
264+ value = value,
265+ fallBackToDefault = fallBackToDefault,
266+ unsetLabel = stringResource(R .string.field_not_set)
267+ )
268+
269+ @OptIn(ExperimentalMaterial3ExpressiveApi ::class )
270+ @Composable
271+ private fun ReadonlyOptionDialog (
272+ title : String ,
273+ onDismissRequest : () -> Unit ,
274+ content : @Composable () -> Unit
275+ ) = AlertDialog (
276+ onDismissRequest = onDismissRequest,
277+ title = { Text (title) },
278+ text = content,
279+ confirmButton = {
280+ TextButton (onClick = onDismissRequest, shapes = ButtonDefaults .shapes()) {
281+ Text (stringResource(R .string.ok))
282+ }
283+ }
284+ )
285+
230286private object StringOptionEditor : OptionEditor<String> {
231287 @OptIn(ExperimentalMaterial3ExpressiveApi ::class )
232288 @Composable
233289 override fun Dialog (scope : OptionEditorScope <String >) {
290+ if (scope.readOnly) {
291+ ReadonlyOptionDialog (
292+ title = scope.option.name,
293+ onDismissRequest = scope.dismissDialog,
294+ ) {
295+ OutlinedTextField (
296+ value = optionValueLabel(scope.option, scope.value),
297+ onValueChange = {},
298+ enabled = false ,
299+ modifier = Modifier .fillMaxWidth()
300+ )
301+ }
302+ return
303+ }
304+
234305 var showFileDialog by rememberSaveable { mutableStateOf(false ) }
235306 var fieldValue by rememberSaveable(scope.value) {
236307 mutableStateOf(scope.value.orEmpty())
@@ -341,6 +412,21 @@ private abstract class NumberOptionEditor<T : Number> : OptionEditor<T> {
341412
342413 @Composable
343414 override fun Dialog (scope : OptionEditorScope <T >) {
415+ if (scope.readOnly) {
416+ ReadonlyOptionDialog (
417+ title = scope.option.name,
418+ onDismissRequest = scope.dismissDialog,
419+ ) {
420+ OutlinedTextField (
421+ value = optionValueLabel(scope.option, scope.value),
422+ onValueChange = {},
423+ enabled = false ,
424+ modifier = Modifier .fillMaxWidth()
425+ )
426+ }
427+ return
428+ }
429+
344430 NumberDialog (scope.option.name, scope.value, scope.option.validator) {
345431 if (it == null ) return @NumberDialog scope.dismissDialog()
346432
@@ -381,6 +467,7 @@ private object FloatOptionEditor : NumberOptionEditor<Float>() {
381467
382468private object BooleanOptionEditor : OptionEditor<Boolean> {
383469 override fun clickAction (scope : OptionEditorScope <Boolean >) {
470+ if (scope.readOnly) return
384471 scope.setValue(! scope.current)
385472 }
386473
@@ -392,7 +479,8 @@ private object BooleanOptionEditor : OptionEditor<Boolean> {
392479 scope.checkSafeguard {
393480 scope.setValue(value)
394481 }
395- }
482+ },
483+ enabled = ! scope.readOnly
396484 )
397485 }
398486
@@ -428,14 +516,64 @@ private class PresetOptionEditor<T : Any>(private val innerEditor: OptionEditor<
428516 mutableStateOf(presets.entries.find { it.value == scope.value }?.key)
429517 }
430518
519+ if (scope.readOnly) {
520+ AlertDialogExtended (
521+ onDismissRequest = scope.dismissDialog,
522+ confirmButton = {
523+ TextButton (onClick = scope.dismissDialog, shapes = ButtonDefaults .shapes()) {
524+ Text (stringResource(R .string.ok))
525+ }
526+ },
527+ title = { Text (scope.option.name) },
528+ textHorizontalPadding = PaddingValues (horizontal = 0 .dp),
529+ text = {
530+ val presets = remember(scope.option.presets) {
531+ scope.option.presets?.entries?.toList().orEmpty()
532+ }
533+
534+ LazyColumn {
535+ @Composable
536+ fun Item (title : String , value : Any? , presetKey : String? ) {
537+ ListItem (
538+ headlineContent = { Text (title) },
539+ supportingContent = value?.toString()?.let { { Text (it) } },
540+ leadingContent = {
541+ HapticRadioButton (
542+ selected = selectedPreset == presetKey,
543+ onClick = null ,
544+ enabled = false
545+ )
546+ },
547+ colors = transparentListItemColors
548+ )
549+ }
550+
551+ items(presets, key = { it.key }) {
552+ Item (it.key, it.value, it.key)
553+ }
554+
555+ item(key = null ) {
556+ Item (
557+ stringResource(R .string.option_preset_custom_value),
558+ scope.value,
559+ null
560+ )
561+ }
562+ }
563+ }
564+ )
565+ return
566+ }
567+
431568 WithOptionEditor (
432569 innerEditor,
433570 scope.option,
434571 scope.value,
435572 scope.setValue,
436573 scope.selectionWarningEnabled,
574+ readOnly = false ,
437575 onDismissDialog = scope.dismissDialog
438- ) inner@{
576+ ) inner@{
439577 var hidePresetsDialog by rememberSaveable {
440578 mutableStateOf(false )
441579 }
@@ -522,6 +660,50 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
522660 )
523661 @Composable
524662 override fun Dialog (scope : OptionEditorScope <List <T >>) {
663+ if (scope.readOnly) {
664+ FullscreenDialog (
665+ onDismissRequest = scope.dismissDialog,
666+ ) {
667+ Scaffold (
668+ topBar = {
669+ AppTopBar (
670+ title = scope.option.name,
671+ onBackClick = scope.dismissDialog,
672+ )
673+ }
674+ ) { paddingValues ->
675+ LazyColumn (
676+ modifier = Modifier
677+ .fillMaxHeight()
678+ .padding(paddingValues),
679+ ) {
680+ val items = scope.value.orEmpty()
681+ if (items.isEmpty()) {
682+ item {
683+ ListItem (
684+ headlineContent = {
685+ Text (
686+ stringResource(R .string.empty),
687+ fontStyle = FontStyle .Italic
688+ )
689+ },
690+ colors = transparentListItemColors
691+ )
692+ }
693+ } else {
694+ items(items) { item ->
695+ ListItem (
696+ headlineContent = { Text (item.toString()) },
697+ colors = transparentListItemColors
698+ )
699+ }
700+ }
701+ }
702+ }
703+ }
704+ return
705+ }
706+
525707 val items =
526708 rememberSaveable(scope.value, saver = snapshotStateListSaver()) {
527709 // We need a key for each element in order to support dragging.
@@ -542,9 +724,7 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
542724
543725 val lazyListState = rememberLazyListState()
544726 val reorderableLazyColumnState =
545- // Update the list
546727 rememberReorderableLazyListState(lazyListState) { from, to ->
547- // Update the list
548728 items.add(to.index, items.removeAt(from.index))
549729 }
550730
@@ -663,7 +843,8 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
663843 elementOption,
664844 value = item.value,
665845 setValue = { items[index] = item.copy(value = it) },
666- selectionWarningEnabled = scope.selectionWarningEnabled
846+ selectionWarningEnabled = scope.selectionWarningEnabled,
847+ readOnly = false
667848 ) {
668849 ListItem (
669850 modifier = Modifier .combinedClickable(
@@ -726,4 +907,4 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
726907 @Parcelize
727908 private data class Item <T : Serializable >(val value : T ? , val key : Int = Random .nextInt()) :
728909 Parcelable
729- }
910+ }
0 commit comments