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.ExperimentalFoundationApi
import androidx.compose.foundation.clickable 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.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.pager.HorizontalPager 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.HelpOutline
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.* 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.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@ -24,8 +43,10 @@ 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.GroupHeader
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_UNIVERSAL
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_UNSUPPORTED
import app.revanced.manager.util.PatchesSelection import app.revanced.manager.util.PatchesSelection
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -43,27 +64,43 @@ fun PatchesSelectorScreen(
val bundles by vm.bundlesFlow.collectAsStateWithLifecycle(initialValue = emptyList()) 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 = {}) if (vm.showOptionsDialog) OptionsDialog(onDismissRequest = vm::dismissDialogs, onConfirm = {})
Scaffold(topBar = { Scaffold(
AppTopBar(title = stringResource(R.string.select_patches), onBackClick = onBackClick, actions = { topBar = {
IconButton(onClick = { }) { AppTopBar(
Icon(Icons.Outlined.HelpOutline, stringResource(R.string.help)) title = stringResource(R.string.select_patches),
} onBackClick = onBackClick,
IconButton(onClick = { }) { actions = {
Icon(Icons.Outlined.Search, stringResource(R.string.search)) IconButton(onClick = { }) {
} Icon(Icons.Outlined.HelpOutline, stringResource(R.string.help))
}) }
}, floatingActionButton = { IconButton(onClick = { }) {
ExtendedFloatingActionButton(text = { Text(stringResource(R.string.patch)) }, Icon(Icons.Outlined.Search, stringResource(R.string.search))
icon = { Icon(Icons.Default.Build, null) }, }
onClick = { onPatchClick(vm.generateSelection()) }) }
}) { paddingValues -> )
Column(Modifier.fillMaxSize().padding(paddingValues)) { },
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) { if (bundles.size > 1) {
TabRow( ScrollableTabRow(
selectedTabIndex = pagerState.currentPage, selectedTabIndex = pagerState.currentPage,
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp) containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp)
) { ) {
@ -85,54 +122,95 @@ fun PatchesSelectorScreen(
userScrollEnabled = true, userScrollEnabled = true,
pageContent = { index -> pageContent = { index ->
val (bundleName, supportedPatches, unsupportedPatches) = bundles[index] val (bundleName, supportedPatches, unsupportedPatches, universalPatches) = bundles[index]
LazyColumn( Column {
modifier = Modifier.fillMaxSize()
) { Row(
items( modifier = Modifier
items = supportedPatches .fillMaxWidth()
) { patch -> .padding(horizontal = 10.dp, vertical = 2.dp),
PatchItem( horizontalArrangement = Arrangement.spacedBy(5.dp)
patch = patch, ) {
onOptionsDialog = vm::openOptionsDialog, FilterChip(
onToggle = { selected = vm.filter and SHOW_SUPPORTED != 0,
vm.togglePatch(bundleName, patch) onClick = { vm.toggleFlag(SHOW_SUPPORTED) },
}, label = { Text(stringResource(R.string.supported)) }
selected = vm.isSelected(bundleName, patch), )
supported = true
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()) { LazyColumn(
item { modifier = Modifier.fillMaxSize()
Row( ) {
modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp).padding(end = 10.dp), if (supportedPatches.isNotEmpty() && (vm.filter and SHOW_SUPPORTED != 0 || vm.filter == 0)) {
horizontalArrangement = Arrangement.SpaceBetween
) { items(
GroupHeader(stringResource(R.string.unsupported_patches), Modifier.padding(0.dp)) items = supportedPatches,
IconButton(onClick = vm::openUnsupportedDialog) { key = { it.name }
Icon( ) { patch ->
Icons.Outlined.HelpOutline, stringResource(R.string.help) PatchItem(
) patch = patch,
} onOptionsDialog = vm::openOptionsDialog,
onToggle = { vm.togglePatch(bundleName, patch) },
selected = vm.isSelected(bundleName, patch)
)
} }
} }
}
items( if (universalPatches.isNotEmpty() && (vm.filter and SHOW_UNIVERSAL != 0 || vm.filter == 0)) {
items = unsupportedPatches, item {
// key = { it.name } ListHeader(
) { patch -> title = stringResource(R.string.universal_patches),
PatchItem( onHelpClick = { }
patch = patch, )
onOptionsDialog = vm::openOptionsDialog, }
onToggle = {
vm.togglePatch(bundleName, patch) items(
}, items = universalPatches,
selected = vm.isSelected(bundleName, patch), key = { it.name }
supported = allowUnsupported ) { 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, onOptionsDialog: () -> Unit,
selected: Boolean, selected: Boolean,
onToggle: () -> Unit, onToggle: () -> Unit,
supported: Boolean supported: Boolean = true
) { ) = ListItem(
ListItem( modifier = Modifier
modifier = Modifier .let { if (!supported) it.alpha(0.5f) else it }
.let { if (!supported) it.alpha(0.5f) else it } .clickable(enabled = supported, onClick = onToggle)
.clickable(enabled = supported, onClick = onToggle), .fillMaxSize(),
leadingContent = { leadingContent = {
Checkbox( Checkbox(
checked = selected, checked = selected,
onCheckedChange = { onCheckedChange = null,
onToggle() enabled = supported
}, )
enabled = supported },
) headlineContent = { Text(patch.name) },
}, supportingContent = patch.description?.let { { Text(it) } },
headlineContent = { trailingContent = {
Text(patch.name) if (patch.options?.isNotEmpty() == true) {
}, IconButton(onClick = onOptionsDialog, enabled = supported) {
supportingContent = { Icon(Icons.Outlined.Settings, null)
Text(patch.description ?: "")
},
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 @Composable
fun UnsupportedDialog( fun UnsupportedDialog(
appVersion: String,
supportedVersions: List<String>,
onDismissRequest: () -> Unit onDismissRequest: () -> Unit
) { ) = AlertDialog(
val appVersion = "1.1.0" onDismissRequest = onDismissRequest,
val supportedVersions = confirmButton = {
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") TextButton(onClick = onDismissRequest) {
Text(stringResource(R.string.ok))
AlertDialog(modifier = Modifier.padding(vertical = 45.dp), }
onDismissRequest = onDismissRequest, },
confirmButton = { title = { Text(stringResource(R.string.unsupported_app)) },
TextButton(onClick = onDismissRequest) { text = { Text(stringResource(R.string.app_not_supported, appVersion, supportedVersions.joinToString(", "))) }
Text(stringResource(R.string.ok)) )
}
},
title = { Text(stringResource(R.string.unsupported_app)) },
text = { Text(stringResource(R.string.app_not_supported, appVersion, supportedVersions.joinToString(", "))) })
}
@Composable @Composable
fun OptionsDialog( fun OptionsDialog(
onDismissRequest: () -> Unit, onConfirm: () -> Unit onDismissRequest: () -> Unit, onConfirm: () -> Unit
) { ) = AlertDialog(
AlertDialog(onDismissRequest = onDismissRequest, confirmButton = { onDismissRequest = onDismissRequest,
Button(onClick = { dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(stringResource(R.string.cancel))
}
},
confirmButton = {
TextButton(onClick = {
onConfirm() onConfirm()
onDismissRequest() onDismissRequest()
}) { }) {
Text(stringResource(R.string.apply)) 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 import org.koin.core.component.get
@Stable @Stable
class PatchesSelectorViewModel(appInfo: AppInfo) : ViewModel(), KoinComponent { class PatchesSelectorViewModel(
val appInfo: AppInfo
) : ViewModel(), KoinComponent {
val bundlesFlow = get<SourceRepository>().bundles.map { bundles -> val bundlesFlow = get<SourceRepository>().bundles.map { bundles ->
bundles.mapValues { (_, bundle) -> bundle.patches }.map { (name, patches) -> bundles.mapValues { (_, bundle) -> bundle.patches }.map { (name, patches) ->
val supported = mutableListOf<PatchInfo>() val supported = mutableListOf<PatchInfo>()
val unsupported = mutableListOf<PatchInfo>() val unsupported = mutableListOf<PatchInfo>()
val universal = mutableListOf<PatchInfo>()
patches.filter { it.compatibleWith(appInfo.packageName) }.forEach { 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) 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( 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) var showOptionsDialog by mutableStateOf(false)
private set private set
var showUnsupportedDialog by mutableStateOf(false)
private set val compatibleVersions = mutableStateListOf<String>()
fun dismissDialogs() { fun dismissDialogs() {
showOptionsDialog = false showOptionsDialog = false
showUnsupportedDialog = false compatibleVersions.clear()
} }
fun openOptionsDialog() { fun openOptionsDialog() {
showOptionsDialog = true showOptionsDialog = true
} }
fun openUnsupportedDialog() { fun openUnsupportedDialog(unsupportedVersions: List<PatchInfo>) {
showUnsupportedDialog = true 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="no_patched_apps_found">No patched apps found</string>
<string name="unsupported_app">Unsupported app</string> <string name="unsupported_app">Unsupported app</string>
<string name="unsupported_patches">Unsupported patches</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="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> <string name="select_file">Select file</string>