mirror of
https://github.com/revanced/revanced-manager
synced 2024-05-14 13:56:57 +02:00
feat: patch options UI (#80)
This commit is contained in:
parent
3f059d7748
commit
7aea9473de
@ -36,6 +36,13 @@ fun AppScaffold(
|
|||||||
fun AppTopBar(
|
fun AppTopBar(
|
||||||
title: String,
|
title: String,
|
||||||
onBackClick: (() -> Unit)? = null,
|
onBackClick: (() -> Unit)? = null,
|
||||||
|
backIcon: @Composable (() -> Unit) = @Composable {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ArrowBack, contentDescription = stringResource(
|
||||||
|
R.string.back
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
actions: @Composable (RowScope.() -> Unit) = {},
|
actions: @Composable (RowScope.() -> Unit) = {},
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||||
) {
|
) {
|
||||||
@ -47,10 +54,7 @@ fun AppTopBar(
|
|||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
if (onBackClick != null) {
|
if (onBackClick != null) {
|
||||||
IconButton(onClick = onBackClick) {
|
IconButton(onClick = onBackClick) {
|
||||||
Icon(
|
backIcon()
|
||||||
imageVector = Icons.Default.ArrowBack,
|
|
||||||
contentDescription = stringResource(R.string.back)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,38 +1,71 @@
|
|||||||
package app.revanced.manager.ui.component.patches
|
package app.revanced.manager.ui.component.patches
|
||||||
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.FileOpen
|
import androidx.compose.material.icons.outlined.Edit
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material.icons.outlined.Folder
|
||||||
|
import androidx.compose.material.icons.outlined.MoreVert
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextField
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.data.platform.FileSystem
|
import app.revanced.manager.data.platform.FileSystem
|
||||||
import app.revanced.manager.patcher.patch.Option
|
import app.revanced.manager.patcher.patch.Option
|
||||||
|
import app.revanced.manager.util.toast
|
||||||
import app.revanced.patcher.patch.PatchOption
|
import app.revanced.patcher.patch.PatchOption
|
||||||
import org.koin.compose.rememberKoinInject
|
import org.koin.compose.rememberKoinInject
|
||||||
|
|
||||||
/**
|
// Composable functions do not support function references, so we have to use composable lambdas instead.
|
||||||
* [Composable] functions do not support function references, so we have to use composable lambdas instead.
|
private typealias OptionImpl = @Composable (Option, Any?, (Any?) -> Unit) -> Unit
|
||||||
*/
|
|
||||||
private typealias OptionField = @Composable (Any?, (Any?) -> Unit) -> Unit
|
|
||||||
|
|
||||||
private val StringField: OptionField = { value, setValue ->
|
@Composable
|
||||||
val fs: FileSystem = rememberKoinInject()
|
private fun OptionListItem(
|
||||||
|
option: Option,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
trailingContent: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable(onClick = onClick),
|
||||||
|
headlineContent = { Text(option.title) },
|
||||||
|
supportingContent = { Text(option.description) },
|
||||||
|
trailingContent = trailingContent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun StringOptionDialog(
|
||||||
|
name: String,
|
||||||
|
value: String?,
|
||||||
|
onSubmit: (String) -> Unit,
|
||||||
|
onDismissRequest: () -> Unit
|
||||||
|
) {
|
||||||
var showFileDialog by rememberSaveable { mutableStateOf(false) }
|
var showFileDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
|
var fieldValue by rememberSaveable(value) {
|
||||||
|
mutableStateOf(value.orEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
val fs: FileSystem = rememberKoinInject()
|
||||||
val (contract, permissionName) = fs.permissionContract()
|
val (contract, permissionName) = fs.permissionContract()
|
||||||
val permissionLauncher = rememberLauncherForActivityResult(contract = contract) {
|
val permissionLauncher = rememberLauncherForActivityResult(contract = contract) {
|
||||||
showFileDialog = it
|
showFileDialog = it
|
||||||
}
|
}
|
||||||
val current = value as? String
|
|
||||||
|
|
||||||
if (showFileDialog) {
|
if (showFileDialog) {
|
||||||
PathSelectorDialog(
|
PathSelectorDialog(
|
||||||
@ -40,45 +73,133 @@ private val StringField: OptionField = { value, setValue ->
|
|||||||
) {
|
) {
|
||||||
showFileDialog = false
|
showFileDialog = false
|
||||||
it?.let { path ->
|
it?.let { path ->
|
||||||
setValue(path.toString())
|
fieldValue = path.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
AlertDialog(
|
||||||
TextField(value = current ?: "", onValueChange = setValue)
|
onDismissRequest = onDismissRequest,
|
||||||
Button(onClick = {
|
title = { Text(name) },
|
||||||
if (fs.hasStoragePermission()) {
|
text = {
|
||||||
showFileDialog = true
|
OutlinedTextField(
|
||||||
} else {
|
value = fieldValue,
|
||||||
permissionLauncher.launch(permissionName)
|
onValueChange = { fieldValue = it },
|
||||||
|
placeholder = {
|
||||||
|
Text(stringResource(R.string.string_option_placeholder))
|
||||||
|
},
|
||||||
|
trailingIcon = {
|
||||||
|
var showDropdownMenu by rememberSaveable { mutableStateOf(false) }
|
||||||
|
IconButton(
|
||||||
|
onClick = { showDropdownMenu = true }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Outlined.MoreVert,
|
||||||
|
contentDescription = stringResource(R.string.string_option_menu_description)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = showDropdownMenu,
|
||||||
|
onDismissRequest = { showDropdownMenu = false }
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(Icons.Outlined.Folder, null)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(stringResource(R.string.path_selector))
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
showDropdownMenu = false
|
||||||
|
if (fs.hasStoragePermission()) {
|
||||||
|
showFileDialog = true
|
||||||
|
} else {
|
||||||
|
permissionLauncher.launch(permissionName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { onSubmit(fieldValue) }) {
|
||||||
|
Text(stringResource(R.string.save))
|
||||||
}
|
}
|
||||||
}) {
|
},
|
||||||
Icon(Icons.Filled.FileOpen, null)
|
dismissButton = {
|
||||||
Text("Select file or folder")
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(stringResource(R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val StringOption: OptionImpl = { option, value, setValue ->
|
||||||
|
var showInputDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
|
fun showInputDialog() {
|
||||||
|
showInputDialog = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dismissInputDialog() {
|
||||||
|
showInputDialog = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showInputDialog) {
|
||||||
|
StringOptionDialog(
|
||||||
|
name = option.title,
|
||||||
|
value = value as? String,
|
||||||
|
onSubmit = {
|
||||||
|
dismissInputDialog()
|
||||||
|
setValue(it)
|
||||||
|
},
|
||||||
|
onDismissRequest = ::dismissInputDialog
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionListItem(
|
||||||
|
option = option,
|
||||||
|
onClick = ::showInputDialog
|
||||||
|
) {
|
||||||
|
IconButton(onClick = ::showInputDialog) {
|
||||||
|
Icon(
|
||||||
|
Icons.Outlined.Edit,
|
||||||
|
contentDescription = stringResource(R.string.string_option_icon_description)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val BooleanField: OptionField = { value, setValue ->
|
private val BooleanOption: OptionImpl = { option, value, setValue ->
|
||||||
val current = value as? Boolean
|
val current = (value as? Boolean) ?: false
|
||||||
Switch(checked = current ?: false, onCheckedChange = setValue)
|
|
||||||
|
OptionListItem(
|
||||||
|
option = option,
|
||||||
|
onClick = { setValue(!current) }
|
||||||
|
) {
|
||||||
|
Switch(checked = current, onCheckedChange = setValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val UnknownField: OptionField = { _, _ ->
|
private val UnknownOption: OptionImpl = { option, _, _ ->
|
||||||
Text("This type has not been implemented")
|
val context = LocalContext.current
|
||||||
|
OptionListItem(
|
||||||
|
option = option,
|
||||||
|
onClick = { context.toast("Unknown type: ${option.type.name}") },
|
||||||
|
trailingContent = {})
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun OptionField(option: Option, value: Any?, setValue: (Any?) -> Unit) {
|
fun OptionItem(option: Option, value: Any?, setValue: (Any?) -> Unit) {
|
||||||
val implementation = remember(option.type) {
|
val implementation = remember(option.type) {
|
||||||
when (option.type) {
|
when (option.type) {
|
||||||
// These are the only two types that are currently used by the official patches.
|
// These are the only two types that are currently used by the official patches.
|
||||||
PatchOption.StringOption::class.java -> StringField
|
PatchOption.StringOption::class.java -> StringOption
|
||||||
PatchOption.BooleanOption::class.java -> BooleanField
|
PatchOption.BooleanOption::class.java -> BooleanOption
|
||||||
else -> UnknownField
|
else -> UnknownOption
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation(value, setValue)
|
implementation(option, value, setValue)
|
||||||
}
|
}
|
@ -2,18 +2,20 @@ package app.revanced.manager.ui.component.patches
|
|||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.FileOpen
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.Folder
|
import androidx.compose.material.icons.outlined.ArrowBack
|
||||||
|
import androidx.compose.material.icons.outlined.DocumentScanner
|
||||||
|
import androidx.compose.material.icons.outlined.Folder
|
||||||
|
import androidx.compose.material.icons.outlined.InsertDriveFile
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@ -21,15 +23,18 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.ui.component.AppTopBar
|
import app.revanced.manager.ui.component.AppTopBar
|
||||||
|
import app.revanced.manager.ui.component.GroupHeader
|
||||||
import app.revanced.manager.util.saver.PathSaver
|
import app.revanced.manager.util.saver.PathSaver
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.absolutePathString
|
||||||
import kotlin.io.path.isDirectory
|
import kotlin.io.path.isDirectory
|
||||||
import kotlin.io.path.isRegularFile
|
import kotlin.io.path.isReadable
|
||||||
import kotlin.io.path.listDirectoryEntries
|
import kotlin.io.path.listDirectoryEntries
|
||||||
import kotlin.io.path.name
|
import kotlin.io.path.name
|
||||||
|
|
||||||
@ -40,14 +45,8 @@ fun PathSelectorDialog(root: Path, onSelect: (Path?) -> Unit) {
|
|||||||
val notAtRootDir = remember(currentDirectory) {
|
val notAtRootDir = remember(currentDirectory) {
|
||||||
currentDirectory != root
|
currentDirectory != root
|
||||||
}
|
}
|
||||||
val everything = remember(currentDirectory) {
|
val (directories, files) = remember(currentDirectory) {
|
||||||
currentDirectory.listDirectoryEntries()
|
currentDirectory.listDirectoryEntries().filter(Path::isReadable).partition(Path::isDirectory)
|
||||||
}
|
|
||||||
val directories = remember(everything) {
|
|
||||||
everything.filter { it.isDirectory() }
|
|
||||||
}
|
|
||||||
val files = remember(everything) {
|
|
||||||
everything.filter { it.isRegularFile() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialog(
|
Dialog(
|
||||||
@ -60,51 +59,78 @@ fun PathSelectorDialog(root: Path, onSelect: (Path?) -> Unit) {
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
AppTopBar(
|
AppTopBar(
|
||||||
title = stringResource(R.string.select_file),
|
title = stringResource(R.string.path_selector),
|
||||||
onBackClick = { onSelect(null) }
|
onBackClick = { onSelect(null) },
|
||||||
|
backIcon = {
|
||||||
|
Icon(Icons.Filled.Close, contentDescription = stringResource(R.string.close))
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
BackHandler(enabled = notAtRootDir) {
|
BackHandler(enabled = notAtRootDir) {
|
||||||
currentDirectory = currentDirectory.parent
|
currentDirectory = currentDirectory.parent
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(paddingValues)
|
||||||
.padding(paddingValues)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
) {
|
||||||
Text(text = currentDirectory.toString())
|
item(key = "current") {
|
||||||
Row(
|
PathItem(
|
||||||
modifier = Modifier.clickable { onSelect(currentDirectory) }
|
onClick = { onSelect(currentDirectory) },
|
||||||
) {
|
icon = Icons.Outlined.Folder,
|
||||||
Text("(Use this directory)")
|
name = currentDirectory.toString()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notAtRootDir) {
|
if (notAtRootDir) {
|
||||||
Row(
|
item(key = "parent") {
|
||||||
modifier = Modifier.clickable { currentDirectory = currentDirectory.parent }
|
PathItem(
|
||||||
) {
|
onClick = { currentDirectory = currentDirectory.parent },
|
||||||
Text("Previous directory")
|
icon = Icons.Outlined.ArrowBack,
|
||||||
|
name = stringResource(R.string.path_selector_parent_dir)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
directories.forEach {
|
if (directories.isNotEmpty()) {
|
||||||
Row(
|
item(key = "dirs_header") {
|
||||||
modifier = Modifier.clickable { currentDirectory = it }
|
GroupHeader(title = stringResource(R.string.path_selector_dirs))
|
||||||
) {
|
|
||||||
Icon(Icons.Filled.Folder, null)
|
|
||||||
Text(text = it.name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
files.forEach {
|
items(directories, key = { it.absolutePathString() }) {
|
||||||
Row(
|
PathItem(
|
||||||
modifier = Modifier.clickable { onSelect(it) }
|
onClick = { currentDirectory = it },
|
||||||
) {
|
icon = Icons.Outlined.Folder,
|
||||||
Icon(Icons.Filled.FileOpen, null)
|
name = it.name
|
||||||
Text(text = it.name)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files.isNotEmpty()) {
|
||||||
|
item(key = "files_header") {
|
||||||
|
GroupHeader(title = stringResource(R.string.path_selector_files))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
items(files, key = { it.absolutePathString() }) {
|
||||||
|
PathItem(
|
||||||
|
onClick = { onSelect(it) },
|
||||||
|
icon = Icons.Outlined.InsertDriveFile,
|
||||||
|
name = it.name
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PathItem(
|
||||||
|
onClick: () -> Unit,
|
||||||
|
icon: ImageVector,
|
||||||
|
name: String
|
||||||
|
) {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable(onClick = onClick),
|
||||||
|
headlineContent = { Text(name) },
|
||||||
|
leadingContent = { Icon(icon, contentDescription = null) }
|
||||||
|
)
|
||||||
}
|
}
|
@ -13,15 +13,13 @@ import androidx.compose.foundation.lazy.LazyListScope
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Build
|
import androidx.compose.material.icons.filled.Build
|
||||||
import androidx.compose.material.icons.outlined.HelpOutline
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
|
import androidx.compose.material.icons.outlined.Restore
|
||||||
import androidx.compose.material.icons.outlined.Search
|
import androidx.compose.material.icons.outlined.Search
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||||
@ -49,7 +47,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.patcher.patch.PatchInfo
|
import app.revanced.manager.patcher.patch.PatchInfo
|
||||||
import app.revanced.manager.ui.component.AppTopBar
|
import app.revanced.manager.ui.component.AppTopBar
|
||||||
import app.revanced.manager.ui.component.patches.OptionField
|
import app.revanced.manager.ui.component.patches.OptionItem
|
||||||
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
|
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
|
||||||
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_SUPPORTED
|
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_SUPPORTED
|
||||||
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_UNIVERSAL
|
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_UNIVERSAL
|
||||||
@ -82,8 +80,8 @@ fun PatchesSelectorScreen(
|
|||||||
onDismissRequest = vm::dismissDialogs,
|
onDismissRequest = vm::dismissDialogs,
|
||||||
patch = patch,
|
patch = patch,
|
||||||
values = vm.getOptions(bundle, patch),
|
values = vm.getOptions(bundle, patch),
|
||||||
set = { key, value -> vm.setOption(bundle, patch, key, value) },
|
reset = { vm.resetOptions(bundle, patch) },
|
||||||
unset = { vm.unsetOption(bundle, patch, it) }
|
set = { key, value -> vm.setOption(bundle, patch, key, value) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,7 +334,7 @@ fun UnsupportedDialog(
|
|||||||
fun OptionsDialog(
|
fun OptionsDialog(
|
||||||
patch: PatchInfo,
|
patch: PatchInfo,
|
||||||
values: Map<String, Any?>?,
|
values: Map<String, Any?>?,
|
||||||
unset: (String) -> Unit,
|
reset: () -> Unit,
|
||||||
set: (String, Any?) -> Unit,
|
set: (String, Any?) -> Unit,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
) = Dialog(
|
) = Dialog(
|
||||||
@ -350,36 +348,26 @@ fun OptionsDialog(
|
|||||||
topBar = {
|
topBar = {
|
||||||
AppTopBar(
|
AppTopBar(
|
||||||
title = patch.name,
|
title = patch.name,
|
||||||
onBackClick = onDismissRequest
|
onBackClick = onDismissRequest,
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = reset) {
|
||||||
|
Icon(Icons.Outlined.Restore, stringResource(R.string.reset))
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(paddingValues)
|
||||||
.padding(paddingValues)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
) {
|
||||||
patch.options?.forEach {
|
if (patch.options == null) return@LazyColumn
|
||||||
ListItem(
|
|
||||||
headlineContent = { Text(it.title) },
|
|
||||||
supportingContent = { Text(it.description) },
|
|
||||||
overlineContent = {
|
|
||||||
Button(onClick = { unset(it.key) }) {
|
|
||||||
Text("reset")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
trailingContent = {
|
|
||||||
val key = it.key
|
|
||||||
val value =
|
|
||||||
if (values == null || !values.contains(key)) it.defaultValue else values[key]
|
|
||||||
|
|
||||||
OptionField(option = it, value = value, setValue = { set(key, it) })
|
items(patch.options, key = { it.key }) { option ->
|
||||||
}
|
val key = option.key
|
||||||
)
|
val value =
|
||||||
}
|
if (values == null || !values.contains(key)) option.defaultValue else values[key]
|
||||||
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
OptionItem(option = option, value = value, setValue = { set(key, it) })
|
||||||
Text(stringResource(R.string.apply))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,8 +145,8 @@ class PatchesSelectorViewModel(
|
|||||||
patchOptions.getOrCreate(bundle).getOrCreate(patch.name)[key] = value
|
patchOptions.getOrCreate(bundle).getOrCreate(patch.name)[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unsetOption(bundle: Int, patch: PatchInfo, key: String) {
|
fun resetOptions(bundle: Int, patch: PatchInfo) {
|
||||||
patchOptions[bundle]?.get(patch.name)?.remove(key)
|
patchOptions[bundle]?.remove(patch.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dismissDialogs() {
|
fun dismissDialogs() {
|
||||||
|
@ -90,6 +90,7 @@
|
|||||||
|
|
||||||
<string name="options">Options</string>
|
<string name="options">Options</string>
|
||||||
<string name="ok">OK</string>
|
<string name="ok">OK</string>
|
||||||
|
<string name="reset">Reset</string>
|
||||||
<string name="patch">Patch</string>
|
<string name="patch">Patch</string>
|
||||||
<string name="select_from_storage">Select from storage</string>
|
<string name="select_from_storage">Select from storage</string>
|
||||||
<string name="search">Search</string>
|
<string name="search">Search</string>
|
||||||
@ -156,7 +157,14 @@
|
|||||||
<string name="error_occurred">An error occurred</string>
|
<string name="error_occurred">An error occurred</string>
|
||||||
<string name="already_downloaded">Already downloaded</string>
|
<string name="already_downloaded">Already downloaded</string>
|
||||||
|
|
||||||
<string name="select_file">Select file</string>
|
<string name="string_option_icon_description">Edit</string>
|
||||||
|
<string name="string_option_menu_description">More options</string>
|
||||||
|
<string name="string_option_placeholder">Value</string>
|
||||||
|
|
||||||
|
<string name="path_selector">Select from storage</string>
|
||||||
|
<string name="path_selector_parent_dir">Previous directory</string>
|
||||||
|
<string name="path_selector_dirs">Directories</string>
|
||||||
|
<string name="path_selector_files">Files</string>
|
||||||
|
|
||||||
<string name="show_password_field">Show password</string>
|
<string name="show_password_field">Show password</string>
|
||||||
<string name="hide_password_field">Hide password</string>
|
<string name="hide_password_field">Hide password</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user