Skip to content

Commit 15b43bf

Browse files
committed
feat: Use patch selector screen in patches tab
1 parent 8f05023 commit 15b43bf

File tree

9 files changed

+744
-349
lines changed

9 files changed

+744
-349
lines changed

app/src/main/java/app/revanced/manager/ui/component/bundle/BundlePatchesDialog.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,8 @@ fun PatchItem(
329329
fun PatchInfoChip(
330330
modifier: Modifier = Modifier,
331331
onClick: (() -> Unit)? = null,
332-
text: String
332+
text: String,
333+
wrapText: Boolean = false
333334
) {
334335
val shape = RoundedCornerShape(8.0.dp)
335336
val cardModifier = if (onClick != null) {
@@ -358,8 +359,9 @@ fun PatchInfoChip(
358359
) {
359360
Text(
360361
text,
361-
overflow = TextOverflow.Ellipsis,
362-
softWrap = false,
362+
overflow = if (wrapText) TextOverflow.Clip else TextOverflow.Ellipsis,
363+
softWrap = wrapText,
364+
maxLines = if (wrapText) Int.MAX_VALUE else 1,
363365
style = MaterialTheme.typography.labelLarge,
364366
color = MaterialTheme.colorScheme.onSurfaceVariant
365367
)

app/src/main/java/app/revanced/manager/ui/component/patches/OptionFields.kt

Lines changed: 194 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import androidx.compose.material.icons.outlined.Add
2828
import androidx.compose.material.icons.outlined.Edit
2929
import androidx.compose.material.icons.outlined.Folder
3030
import androidx.compose.material.icons.outlined.MoreVert
31+
import androidx.compose.material.icons.outlined.Visibility
3132
import androidx.compose.material3.AlertDialog
3233
import androidx.compose.material3.ButtonDefaults
3334
import 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+
230286
private 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

382468
private 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+
}

app/src/main/java/app/revanced/manager/ui/model/navigation/Nav.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ data object SelectedApplicationInfo : ComplexParameter<SelectedApplicationInfo.V
5353
val app: SelectedApp,
5454
val currentSelection: PatchSelection?,
5555
val options: @RawValue Options,
56+
val readOnly: Boolean = false,
57+
val browseAllBundles: Boolean = false,
5658
) : Parcelable
5759
}
5860

0 commit comments

Comments
 (0)