feat: filter options for patches

This commit is contained in:
CnC-Robert 2023-06-15 22:20:17 +02:00
parent 56bc4ba7f1
commit 41c521876a
No known key found for this signature in database
GPG Key ID: C58ED617AEA8CB68
3 changed files with 248 additions and 118 deletions

View File

@ -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?") }
)

View File

@ -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
}
}

View File

@ -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>