diff --git a/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt index ca26b172..bfe71e94 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt @@ -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, 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?") - }) -} \ No newline at end of file + }, + title = { Text(stringResource(R.string.options)) }, + text = { Text("You really thought these would exist?") } +) \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchesSelectorViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchesSelectorViewModel.kt index d1263a50..cbda928b 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchesSelectorViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchesSelectorViewModel.kt @@ -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().bundles.map { bundles -> bundles.mapValues { (_, bundle) -> bundle.patches }.map { (name, patches) -> val supported = mutableListOf() val unsupported = mutableListOf() + val universal = mutableListOf() 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, val unsupported: List + val name: String, + val supported: List, + val unsupported: List, + val universal: List ) var showOptionsDialog by mutableStateOf(false) private set - var showUnsupportedDialog by mutableStateOf(false) - private set + + val compatibleVersions = mutableStateListOf() fun dismissDialogs() { showOptionsDialog = false - showUnsupportedDialog = false + compatibleVersions.clear() } fun openOptionsDialog() { showOptionsDialog = true } - fun openUnsupportedDialog() { - showUnsupportedDialog = true + fun openUnsupportedDialog(unsupportedVersions: List) { + val set = HashSet() + + 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 } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8ae3fdd6..d315df69 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -67,6 +67,10 @@ No patched apps found Unsupported app Unsupported patches + Universal patches + Supported + Universal + Unsupported Some of the patches do not support this app version (%1$s). The patches only support the following versions: %2$s. Select file