mirror of
https://github.com/revanced/revanced-manager-compose.git
synced 2025-04-29 22:04:25 +02:00
feat: filter options for patches
This commit is contained in:
parent
56bc4ba7f1
commit
41c521876a
@ -2,7 +2,12 @@ package app.revanced.manager.ui.screen
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
@ -12,7 +17,21 @@ import androidx.compose.material.icons.filled.Build
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.Search
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.ScrollableTabRow
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
@ -24,8 +43,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.patcher.patch.PatchInfo
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.GroupHeader
|
||||
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_UNIVERSAL
|
||||
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_UNSUPPORTED
|
||||
import app.revanced.manager.util.PatchesSelection
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@ -43,27 +64,43 @@ fun PatchesSelectorScreen(
|
||||
|
||||
val bundles by vm.bundlesFlow.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||
|
||||
if (vm.showUnsupportedDialog) UnsupportedDialog(onDismissRequest = vm::dismissDialogs)
|
||||
if (vm.compatibleVersions.isNotEmpty())
|
||||
UnsupportedDialog(
|
||||
appVersion = vm.appInfo.packageInfo!!.versionName,
|
||||
supportedVersions = vm.compatibleVersions,
|
||||
onDismissRequest = vm::dismissDialogs
|
||||
)
|
||||
|
||||
if (vm.showOptionsDialog) OptionsDialog(onDismissRequest = vm::dismissDialogs, onConfirm = {})
|
||||
|
||||
Scaffold(topBar = {
|
||||
AppTopBar(title = stringResource(R.string.select_patches), onBackClick = onBackClick, actions = {
|
||||
IconButton(onClick = { }) {
|
||||
Icon(Icons.Outlined.HelpOutline, stringResource(R.string.help))
|
||||
}
|
||||
IconButton(onClick = { }) {
|
||||
Icon(Icons.Outlined.Search, stringResource(R.string.search))
|
||||
}
|
||||
})
|
||||
}, floatingActionButton = {
|
||||
ExtendedFloatingActionButton(text = { Text(stringResource(R.string.patch)) },
|
||||
icon = { Icon(Icons.Default.Build, null) },
|
||||
onClick = { onPatchClick(vm.generateSelection()) })
|
||||
}) { paddingValues ->
|
||||
Column(Modifier.fillMaxSize().padding(paddingValues)) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = stringResource(R.string.select_patches),
|
||||
onBackClick = onBackClick,
|
||||
actions = {
|
||||
IconButton(onClick = { }) {
|
||||
Icon(Icons.Outlined.HelpOutline, stringResource(R.string.help))
|
||||
}
|
||||
IconButton(onClick = { }) {
|
||||
Icon(Icons.Outlined.Search, stringResource(R.string.search))
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
ExtendedFloatingActionButton(text = { Text(stringResource(R.string.patch)) },
|
||||
icon = { Icon(Icons.Default.Build, null) },
|
||||
onClick = { onPatchClick(vm.generateSelection()) })
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
if (bundles.size > 1) {
|
||||
TabRow(
|
||||
ScrollableTabRow(
|
||||
selectedTabIndex = pagerState.currentPage,
|
||||
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp)
|
||||
) {
|
||||
@ -85,54 +122,95 @@ fun PatchesSelectorScreen(
|
||||
userScrollEnabled = true,
|
||||
pageContent = { index ->
|
||||
|
||||
val (bundleName, supportedPatches, unsupportedPatches) = bundles[index]
|
||||
val (bundleName, supportedPatches, unsupportedPatches, universalPatches) = bundles[index]
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
items(
|
||||
items = supportedPatches
|
||||
) { patch ->
|
||||
PatchItem(
|
||||
patch = patch,
|
||||
onOptionsDialog = vm::openOptionsDialog,
|
||||
onToggle = {
|
||||
vm.togglePatch(bundleName, patch)
|
||||
},
|
||||
selected = vm.isSelected(bundleName, patch),
|
||||
supported = true
|
||||
Column {
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 10.dp, vertical = 2.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(5.dp)
|
||||
) {
|
||||
FilterChip(
|
||||
selected = vm.filter and SHOW_SUPPORTED != 0,
|
||||
onClick = { vm.toggleFlag(SHOW_SUPPORTED) },
|
||||
label = { Text(stringResource(R.string.supported)) }
|
||||
)
|
||||
|
||||
FilterChip(
|
||||
selected = vm.filter and SHOW_UNIVERSAL != 0,
|
||||
onClick = { vm.toggleFlag(SHOW_UNIVERSAL) },
|
||||
label = { Text(stringResource(R.string.universal)) }
|
||||
)
|
||||
|
||||
FilterChip(
|
||||
selected = vm.filter and SHOW_UNSUPPORTED != 0,
|
||||
onClick = { vm.toggleFlag(SHOW_UNSUPPORTED) },
|
||||
label = { Text(stringResource(R.string.unsupported)) }
|
||||
)
|
||||
}
|
||||
|
||||
if (unsupportedPatches.isNotEmpty()) {
|
||||
item {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp).padding(end = 10.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
GroupHeader(stringResource(R.string.unsupported_patches), Modifier.padding(0.dp))
|
||||
IconButton(onClick = vm::openUnsupportedDialog) {
|
||||
Icon(
|
||||
Icons.Outlined.HelpOutline, stringResource(R.string.help)
|
||||
)
|
||||
}
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
if (supportedPatches.isNotEmpty() && (vm.filter and SHOW_SUPPORTED != 0 || vm.filter == 0)) {
|
||||
|
||||
items(
|
||||
items = supportedPatches,
|
||||
key = { it.name }
|
||||
) { patch ->
|
||||
PatchItem(
|
||||
patch = patch,
|
||||
onOptionsDialog = vm::openOptionsDialog,
|
||||
onToggle = { vm.togglePatch(bundleName, patch) },
|
||||
selected = vm.isSelected(bundleName, patch)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items(
|
||||
items = unsupportedPatches,
|
||||
// key = { it.name }
|
||||
) { patch ->
|
||||
PatchItem(
|
||||
patch = patch,
|
||||
onOptionsDialog = vm::openOptionsDialog,
|
||||
onToggle = {
|
||||
vm.togglePatch(bundleName, patch)
|
||||
},
|
||||
selected = vm.isSelected(bundleName, patch),
|
||||
supported = allowUnsupported
|
||||
)
|
||||
if (universalPatches.isNotEmpty() && (vm.filter and SHOW_UNIVERSAL != 0 || vm.filter == 0)) {
|
||||
item {
|
||||
ListHeader(
|
||||
title = stringResource(R.string.universal_patches),
|
||||
onHelpClick = { }
|
||||
)
|
||||
}
|
||||
|
||||
items(
|
||||
items = universalPatches,
|
||||
key = { it.name }
|
||||
) { patch ->
|
||||
PatchItem(
|
||||
patch = patch,
|
||||
onOptionsDialog = vm::openOptionsDialog,
|
||||
onToggle = { vm.togglePatch(bundleName, patch) },
|
||||
selected = vm.isSelected(bundleName, patch)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (unsupportedPatches.isNotEmpty() && (vm.filter and SHOW_UNSUPPORTED != 0 || vm.filter == 0)) {
|
||||
item {
|
||||
ListHeader(
|
||||
title = stringResource(R.string.unsupported_patches),
|
||||
onHelpClick = { vm.openUnsupportedDialog(unsupportedPatches) }
|
||||
)
|
||||
}
|
||||
|
||||
items(
|
||||
items = unsupportedPatches,
|
||||
key = { it.name }
|
||||
) { patch ->
|
||||
PatchItem(
|
||||
patch = patch,
|
||||
onOptionsDialog = vm::openOptionsDialog,
|
||||
onToggle = { vm.togglePatch(bundleName, patch) },
|
||||
selected = vm.isSelected(bundleName, patch),
|
||||
supported = allowUnsupported
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -147,68 +225,88 @@ fun PatchItem(
|
||||
onOptionsDialog: () -> Unit,
|
||||
selected: Boolean,
|
||||
onToggle: () -> Unit,
|
||||
supported: Boolean
|
||||
) {
|
||||
ListItem(
|
||||
modifier = Modifier
|
||||
.let { if (!supported) it.alpha(0.5f) else it }
|
||||
.clickable(enabled = supported, onClick = onToggle),
|
||||
leadingContent = {
|
||||
Checkbox(
|
||||
checked = selected,
|
||||
onCheckedChange = {
|
||||
onToggle()
|
||||
},
|
||||
enabled = supported
|
||||
)
|
||||
},
|
||||
headlineContent = {
|
||||
Text(patch.name)
|
||||
},
|
||||
supportingContent = {
|
||||
Text(patch.description ?: "")
|
||||
},
|
||||
trailingContent = {
|
||||
if (patch.options?.isNotEmpty() == true) {
|
||||
IconButton(onClick = onOptionsDialog, enabled = supported) {
|
||||
Icon(Icons.Outlined.Settings, null)
|
||||
}
|
||||
supported: Boolean = true
|
||||
) = ListItem(
|
||||
modifier = Modifier
|
||||
.let { if (!supported) it.alpha(0.5f) else it }
|
||||
.clickable(enabled = supported, onClick = onToggle)
|
||||
.fillMaxSize(),
|
||||
leadingContent = {
|
||||
Checkbox(
|
||||
checked = selected,
|
||||
onCheckedChange = null,
|
||||
enabled = supported
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(patch.name) },
|
||||
supportingContent = patch.description?.let { { Text(it) } },
|
||||
trailingContent = {
|
||||
if (patch.options?.isNotEmpty() == true) {
|
||||
IconButton(onClick = onOptionsDialog, enabled = supported) {
|
||||
Icon(Icons.Outlined.Settings, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun ListHeader(
|
||||
title: String,
|
||||
onHelpClick: (() -> Unit)? = null
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = title,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
},
|
||||
trailingContent = onHelpClick?.let { {
|
||||
IconButton(onClick = onHelpClick) {
|
||||
Icon(
|
||||
Icons.Outlined.HelpOutline,
|
||||
stringResource(R.string.help)
|
||||
)
|
||||
}
|
||||
} }
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UnsupportedDialog(
|
||||
appVersion: String,
|
||||
supportedVersions: List<String>,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
val appVersion = "1.1.0"
|
||||
val supportedVersions =
|
||||
listOf("1.1.1", "1.2.0", "1.1.1", "1.2.0", "1.1.1", "1.2.0", "1.1.1", "1.2.0", "1.1.1", "1.2.0")
|
||||
|
||||
AlertDialog(modifier = Modifier.padding(vertical = 45.dp),
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(stringResource(R.string.ok))
|
||||
}
|
||||
},
|
||||
title = { Text(stringResource(R.string.unsupported_app)) },
|
||||
text = { Text(stringResource(R.string.app_not_supported, appVersion, supportedVersions.joinToString(", "))) })
|
||||
}
|
||||
) = AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(stringResource(R.string.ok))
|
||||
}
|
||||
},
|
||||
title = { Text(stringResource(R.string.unsupported_app)) },
|
||||
text = { Text(stringResource(R.string.app_not_supported, appVersion, supportedVersions.joinToString(", "))) }
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun OptionsDialog(
|
||||
onDismissRequest: () -> Unit, onConfirm: () -> Unit
|
||||
) {
|
||||
AlertDialog(onDismissRequest = onDismissRequest, confirmButton = {
|
||||
Button(onClick = {
|
||||
) = AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onConfirm()
|
||||
onDismissRequest()
|
||||
}) {
|
||||
Text(stringResource(R.string.apply))
|
||||
}
|
||||
}, title = { Text(stringResource(R.string.options)) }, text = {
|
||||
Text("You really thought these would exist?")
|
||||
})
|
||||
}
|
||||
},
|
||||
title = { Text(stringResource(R.string.options)) },
|
||||
text = { Text("You really thought these would exist?") }
|
||||
)
|
@ -15,19 +15,23 @@ import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
|
||||
@Stable
|
||||
class PatchesSelectorViewModel(appInfo: AppInfo) : ViewModel(), KoinComponent {
|
||||
class PatchesSelectorViewModel(
|
||||
val appInfo: AppInfo
|
||||
) : ViewModel(), KoinComponent {
|
||||
|
||||
val bundlesFlow = get<SourceRepository>().bundles.map { bundles ->
|
||||
bundles.mapValues { (_, bundle) -> bundle.patches }.map { (name, patches) ->
|
||||
val supported = mutableListOf<PatchInfo>()
|
||||
val unsupported = mutableListOf<PatchInfo>()
|
||||
val universal = mutableListOf<PatchInfo>()
|
||||
|
||||
patches.filter { it.compatibleWith(appInfo.packageName) }.forEach {
|
||||
val targetList = if (it.supportsVersion(appInfo.packageInfo!!.versionName)) supported else unsupported
|
||||
val targetList = if (it.compatiblePackages == null) universal else if (it.supportsVersion(appInfo.packageInfo!!.versionName)) supported else unsupported
|
||||
|
||||
targetList.add(it)
|
||||
}
|
||||
|
||||
Bundle(name, supported, unsupported)
|
||||
Bundle(name, supported, unsupported, universal)
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,24 +49,48 @@ class PatchesSelectorViewModel(appInfo: AppInfo) : ViewModel(), KoinComponent {
|
||||
}
|
||||
|
||||
data class Bundle(
|
||||
val name: String, val supported: List<PatchInfo>, val unsupported: List<PatchInfo>
|
||||
val name: String,
|
||||
val supported: List<PatchInfo>,
|
||||
val unsupported: List<PatchInfo>,
|
||||
val universal: List<PatchInfo>
|
||||
)
|
||||
|
||||
var showOptionsDialog by mutableStateOf(false)
|
||||
private set
|
||||
var showUnsupportedDialog by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
val compatibleVersions = mutableStateListOf<String>()
|
||||
|
||||
fun dismissDialogs() {
|
||||
showOptionsDialog = false
|
||||
showUnsupportedDialog = false
|
||||
compatibleVersions.clear()
|
||||
}
|
||||
|
||||
fun openOptionsDialog() {
|
||||
showOptionsDialog = true
|
||||
}
|
||||
|
||||
fun openUnsupportedDialog() {
|
||||
showUnsupportedDialog = true
|
||||
fun openUnsupportedDialog(unsupportedVersions: List<PatchInfo>) {
|
||||
val set = HashSet<String>()
|
||||
|
||||
unsupportedVersions.forEach { patch ->
|
||||
patch.compatiblePackages?.find { it.name == appInfo.packageName }?.let { compatiblePackage ->
|
||||
set.addAll(compatiblePackage.versions)
|
||||
}
|
||||
}
|
||||
|
||||
compatibleVersions.addAll(set)
|
||||
}
|
||||
|
||||
var filter by mutableStateOf(SHOW_SUPPORTED or SHOW_UNSUPPORTED)
|
||||
private set
|
||||
|
||||
fun toggleFlag(flag: Int) {
|
||||
filter = filter xor flag
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SHOW_SUPPORTED = 1 // 2^0
|
||||
const val SHOW_UNIVERSAL = 2 // 2^1
|
||||
const val SHOW_UNSUPPORTED = 4 // 2^2
|
||||
}
|
||||
}
|
@ -67,6 +67,10 @@
|
||||
<string name="no_patched_apps_found">No patched apps found</string>
|
||||
<string name="unsupported_app">Unsupported app</string>
|
||||
<string name="unsupported_patches">Unsupported patches</string>
|
||||
<string name="universal_patches">Universal patches</string>
|
||||
<string name="supported">Supported</string>
|
||||
<string name="universal">Universal</string>
|
||||
<string name="unsupported">Unsupported</string>
|
||||
<string name="app_not_supported">Some of the patches do not support this app version (%1$s). The patches only support the following versions: %2$s.</string>
|
||||
|
||||
<string name="select_file">Select file</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user