mirror of
https://github.com/revanced/revanced-manager.git
synced 2025-04-30 05:54:26 +02:00
feat(patch-selector): default patches selection (#1272)
This commit is contained in:
parent
ca3c9af3b8
commit
f78b56ef0a
@ -35,6 +35,9 @@ abstract class SelectionDao {
|
|||||||
@Query("DELETE FROM patch_selections WHERE patch_bundle = :uid")
|
@Query("DELETE FROM patch_selections WHERE patch_bundle = :uid")
|
||||||
abstract suspend fun clearForPatchBundle(uid: Int)
|
abstract suspend fun clearForPatchBundle(uid: Int)
|
||||||
|
|
||||||
|
@Query("DELETE FROM patch_selections WHERE package_name = :packageName")
|
||||||
|
abstract suspend fun clearForPackage(packageName: String)
|
||||||
|
|
||||||
@Query("DELETE FROM patch_selections")
|
@Query("DELETE FROM patch_selections")
|
||||||
abstract suspend fun reset()
|
abstract suspend fun reset()
|
||||||
|
|
||||||
|
@ -21,4 +21,7 @@ class PreferencesManager(
|
|||||||
|
|
||||||
val showAutoUpdatesDialog = booleanPreference("show_auto_updates_dialog", true)
|
val showAutoUpdatesDialog = booleanPreference("show_auto_updates_dialog", true)
|
||||||
val managerAutoUpdates = booleanPreference("manager_auto_updates", false)
|
val managerAutoUpdates = booleanPreference("manager_auto_updates", false)
|
||||||
|
|
||||||
|
val disableSelectionWarning = booleanPreference("disable_selection_warning", false)
|
||||||
|
val enableSelectionWarningCountdown = booleanPreference("enable_selection_warning_countdown", true)
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,10 @@ class PatchSelectionRepository(db: AppDatabase) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
suspend fun clearSelection(packageName: String) {
|
||||||
|
dao.clearForPackage(packageName)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun reset() = dao.reset()
|
suspend fun reset() = dao.reset()
|
||||||
|
|
||||||
suspend fun export(bundleUid: Int): SerializedSelection = dao.exportSelection(bundleUid)
|
suspend fun export(bundleUid: Int): SerializedSelection = dao.exportSelection(bundleUid)
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package app.revanced.manager.ui.component
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Countdown(start: Int, content: @Composable (Int) -> Unit) {
|
||||||
|
var timer by rememberSaveable(start) {
|
||||||
|
mutableStateOf(start)
|
||||||
|
}
|
||||||
|
LaunchedEffect(timer) {
|
||||||
|
if (timer == 0) {
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(1000L)
|
||||||
|
timer -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
content(timer)
|
||||||
|
}
|
@ -16,11 +16,15 @@ import androidx.compose.foundation.pager.rememberPagerState
|
|||||||
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.MoreVert
|
||||||
import androidx.compose.material.icons.outlined.Restore
|
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.material.icons.outlined.WarningAmber
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||||
import androidx.compose.material3.FilterChip
|
import androidx.compose.material3.FilterChip
|
||||||
@ -35,26 +39,36 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
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 androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
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.Countdown
|
||||||
import app.revanced.manager.ui.component.patches.OptionItem
|
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.BaseSelectionMode
|
||||||
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
|
||||||
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_UNSUPPORTED
|
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_UNSUPPORTED
|
||||||
import app.revanced.manager.util.Options
|
import app.revanced.manager.util.Options
|
||||||
import app.revanced.manager.util.PatchesSelection
|
import app.revanced.manager.util.PatchesSelection
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.koin.compose.rememberKoinInject
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@ -85,12 +99,49 @@ fun PatchesSelectorScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vm.pendingSelectionAction?.let {
|
||||||
|
SelectionWarningDialog(
|
||||||
|
onCancel = vm::dismissSelectionWarning,
|
||||||
|
onConfirm = vm::confirmSelectionWarning
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
AppTopBar(
|
AppTopBar(
|
||||||
title = stringResource(R.string.select_patches),
|
title = stringResource(R.string.select_patches),
|
||||||
onBackClick = onBackClick,
|
onBackClick = onBackClick,
|
||||||
actions = {
|
actions = {
|
||||||
|
IconButton(onClick = vm::reset) {
|
||||||
|
Icon(Icons.Outlined.Restore, stringResource(R.string.reset))
|
||||||
|
}
|
||||||
|
|
||||||
|
var dropdownActive by rememberSaveable {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
// This part should probably be changed
|
||||||
|
IconButton(onClick = { dropdownActive = true }) {
|
||||||
|
Icon(Icons.Outlined.MoreVert, stringResource(R.string.more))
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = dropdownActive,
|
||||||
|
onDismissRequest = { dropdownActive = false }
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
val id =
|
||||||
|
if (vm.baseSelectionMode == BaseSelectionMode.DEFAULT)
|
||||||
|
R.string.menu_opt_selection_mode_previous else R.string.menu_opt_selection_mode_default
|
||||||
|
|
||||||
|
Text(stringResource(id))
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
dropdownActive = false
|
||||||
|
vm.switchBaseSelectionMode()
|
||||||
|
},
|
||||||
|
enabled = vm.hasPreviousSelection
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
IconButton(onClick = { }) {
|
IconButton(onClick = { }) {
|
||||||
Icon(Icons.Outlined.Search, stringResource(R.string.search))
|
Icon(Icons.Outlined.Search, stringResource(R.string.search))
|
||||||
}
|
}
|
||||||
@ -102,9 +153,11 @@ fun PatchesSelectorScreen(
|
|||||||
text = { Text(stringResource(R.string.patch)) },
|
text = { Text(stringResource(R.string.patch)) },
|
||||||
icon = { Icon(Icons.Default.Build, null) },
|
icon = { Icon(Icons.Default.Build, null) },
|
||||||
onClick = {
|
onClick = {
|
||||||
|
// TODO: only allow this if all required options have been set.
|
||||||
composableScope.launch {
|
composableScope.launch {
|
||||||
// TODO: only allow this if all required options have been set.
|
val selection = vm.getSelection()
|
||||||
onPatchClick(vm.getAndSaveSelection(), vm.getOptions())
|
vm.saveSelection(selection).join()
|
||||||
|
onPatchClick(selection, vm.getOptions())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -206,7 +259,15 @@ fun PatchesSelectorScreen(
|
|||||||
bundle.uid,
|
bundle.uid,
|
||||||
patch
|
patch
|
||||||
),
|
),
|
||||||
onToggle = { vm.togglePatch(bundle.uid, patch) },
|
onToggle = {
|
||||||
|
if (vm.selectionWarningEnabled) {
|
||||||
|
vm.pendingSelectionAction = {
|
||||||
|
vm.togglePatch(bundle.uid, patch)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vm.togglePatch(bundle.uid, patch)
|
||||||
|
}
|
||||||
|
},
|
||||||
supported = supported
|
supported = supported
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -246,6 +307,84 @@ fun PatchesSelectorScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SelectionWarningDialog(
|
||||||
|
onCancel: () -> Unit,
|
||||||
|
onConfirm: (Boolean) -> Unit
|
||||||
|
) {
|
||||||
|
val prefs: PreferencesManager = rememberKoinInject()
|
||||||
|
var dismissPermanently by rememberSaveable {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onCancel,
|
||||||
|
confirmButton = {
|
||||||
|
val enableCountdown by prefs.enableSelectionWarningCountdown.getAsState()
|
||||||
|
|
||||||
|
Countdown(start = if (enableCountdown) 3 else 0) { timer ->
|
||||||
|
LaunchedEffect(timer) {
|
||||||
|
if (timer == 0) prefs.enableSelectionWarningCountdown.update(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
onClick = { onConfirm(dismissPermanently) },
|
||||||
|
enabled = timer == 0
|
||||||
|
) {
|
||||||
|
val text =
|
||||||
|
if (timer == 0) stringResource(R.string.continue_) else stringResource(
|
||||||
|
R.string.selection_warning_continue_countdown, timer
|
||||||
|
)
|
||||||
|
Text(text, color = MaterialTheme.colorScheme.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onCancel) {
|
||||||
|
Text(stringResource(R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(Icons.Outlined.WarningAmber, null)
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.selection_warning_title),
|
||||||
|
style = MaterialTheme.typography.headlineSmall.copy(textAlign = TextAlign.Center),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.selection_warning_description),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(0.dp),
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
dismissPermanently = !dismissPermanently
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
checked = dismissPermanently,
|
||||||
|
onCheckedChange = {
|
||||||
|
dismissPermanently = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Text(stringResource(R.string.permanent_dismiss))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PatchItem(
|
fun PatchItem(
|
||||||
patch: PatchInfo,
|
patch: PatchInfo,
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
package app.revanced.manager.ui.viewmodel
|
package app.revanced.manager.ui.viewmodel
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateMapOf
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.saveable.Saver
|
import androidx.compose.runtime.saveable.Saver
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi
|
import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi
|
||||||
import androidx.lifecycle.viewmodel.compose.saveable
|
import androidx.lifecycle.viewmodel.compose.saveable
|
||||||
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.domain.manager.PreferencesManager
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
import app.revanced.manager.domain.repository.PatchSelectionRepository
|
import app.revanced.manager.domain.repository.PatchSelectionRepository
|
||||||
import app.revanced.manager.domain.repository.PatchBundleRepository
|
import app.revanced.manager.domain.repository.PatchBundleRepository
|
||||||
@ -20,12 +23,9 @@ import app.revanced.manager.patcher.patch.PatchInfo
|
|||||||
import app.revanced.manager.ui.destination.Destination
|
import app.revanced.manager.ui.destination.Destination
|
||||||
import app.revanced.manager.util.Options
|
import app.revanced.manager.util.Options
|
||||||
import app.revanced.manager.util.PatchesSelection
|
import app.revanced.manager.util.PatchesSelection
|
||||||
import app.revanced.manager.util.SnapshotStateSet
|
|
||||||
import app.revanced.manager.util.flatMapLatestAndCombine
|
import app.revanced.manager.util.flatMapLatestAndCombine
|
||||||
import app.revanced.manager.util.mutableStateSetOf
|
|
||||||
import app.revanced.manager.util.saver.snapshotStateMapSaver
|
import app.revanced.manager.util.saver.snapshotStateMapSaver
|
||||||
import app.revanced.manager.util.saver.snapshotStateSetSaver
|
import app.revanced.manager.util.toast
|
||||||
import app.revanced.manager.util.toMutableStateSet
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@ -39,8 +39,17 @@ import org.koin.core.component.get
|
|||||||
class PatchesSelectorViewModel(
|
class PatchesSelectorViewModel(
|
||||||
val input: Destination.PatchesSelector
|
val input: Destination.PatchesSelector
|
||||||
) : ViewModel(), KoinComponent {
|
) : ViewModel(), KoinComponent {
|
||||||
|
private val app: Application = get()
|
||||||
private val selectionRepository: PatchSelectionRepository = get()
|
private val selectionRepository: PatchSelectionRepository = get()
|
||||||
private val savedStateHandle: SavedStateHandle = get()
|
private val savedStateHandle: SavedStateHandle = get()
|
||||||
|
private val prefs: PreferencesManager = get()
|
||||||
|
|
||||||
|
private val packageName = input.selectedApp.packageName
|
||||||
|
|
||||||
|
var pendingSelectionAction by mutableStateOf<(() -> Unit)?>(null)
|
||||||
|
|
||||||
|
var selectionWarningEnabled by mutableStateOf(true)
|
||||||
|
private set
|
||||||
|
|
||||||
val allowExperimental = get<PreferencesManager>().allowExperimental
|
val allowExperimental = get<PreferencesManager>().allowExperimental
|
||||||
val bundlesFlow = get<PatchBundleRepository>().sources.flatMapLatestAndCombine(
|
val bundlesFlow = get<PatchBundleRepository>().sources.flatMapLatestAndCombine(
|
||||||
@ -54,7 +63,7 @@ class PatchesSelectorViewModel(
|
|||||||
val unsupported = mutableListOf<PatchInfo>()
|
val unsupported = mutableListOf<PatchInfo>()
|
||||||
val universal = mutableListOf<PatchInfo>()
|
val universal = mutableListOf<PatchInfo>()
|
||||||
|
|
||||||
bundle.patches.filter { it.compatibleWith(input.selectedApp.packageName) }.forEach {
|
bundle.patches.filter { it.compatibleWith(packageName) }.forEach {
|
||||||
val targetList = when {
|
val targetList = when {
|
||||||
it.compatiblePackages == null -> universal
|
it.compatiblePackages == null -> universal
|
||||||
it.supportsVersion(input.selectedApp.version) -> supported
|
it.supportsVersion(input.selectedApp.version) -> supported
|
||||||
@ -68,38 +77,72 @@ class PatchesSelectorViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val selectedPatches: SnapshotStatePatchesSelection by savedStateHandle.saveable(
|
init {
|
||||||
saver = patchesSelectionSaver,
|
viewModelScope.launch {
|
||||||
init = {
|
if (prefs.disableSelectionWarning.get()) {
|
||||||
val map: SnapshotStatePatchesSelection = mutableStateMapOf()
|
selectionWarningEnabled = false
|
||||||
viewModelScope.launch(Dispatchers.Default) {
|
return@launch
|
||||||
val bundles = bundlesFlow.first()
|
|
||||||
val filteredSelection =
|
|
||||||
(input.patchesSelection
|
|
||||||
?: selectionRepository.getSelection(input.selectedApp.packageName))
|
|
||||||
.mapValues { (uid, patches) ->
|
|
||||||
// Filter out patches that don't exist.
|
|
||||||
val filteredPatches = bundles.singleOrNull { it.uid == uid }
|
|
||||||
?.let { bundle ->
|
|
||||||
val allPatches = bundle.all.map { it.name }
|
|
||||||
patches.filter { allPatches.contains(it) }
|
|
||||||
}
|
|
||||||
?: patches
|
|
||||||
|
|
||||||
filteredPatches.toMutableStateSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
map.putAll(filteredSelection)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return@saveable map
|
|
||||||
})
|
val experimental = allowExperimental.get()
|
||||||
private val patchOptions: SnapshotStateOptions by savedStateHandle.saveable(
|
fun BundleInfo.hasDefaultPatches(): Boolean {
|
||||||
|
return if (experimental) {
|
||||||
|
all.asSequence()
|
||||||
|
} else {
|
||||||
|
sequence {
|
||||||
|
yieldAll(supported)
|
||||||
|
yieldAll(universal)
|
||||||
|
}
|
||||||
|
}.any { it.include }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't show the warning if there are no default patches.
|
||||||
|
selectionWarningEnabled = bundlesFlow.first().any(BundleInfo::hasDefaultPatches)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseSelectionMode by mutableStateOf(BaseSelectionMode.DEFAULT)
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val previousPatchesSelection: SnapshotStateMap<Int, Set<String>> = mutableStateMapOf()
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch(Dispatchers.Default) { loadPreviousSelection() }
|
||||||
|
}
|
||||||
|
|
||||||
|
val hasPreviousSelection by derivedStateOf {
|
||||||
|
previousPatchesSelection.filterValues(Set<String>::isNotEmpty).isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var hasModifiedSelection = false
|
||||||
|
|
||||||
|
private val explicitPatchesSelection: SnapshotExplicitPatchesSelection by savedStateHandle.saveable(
|
||||||
|
saver = explicitPatchesSelectionSaver,
|
||||||
|
init = ::mutableStateMapOf
|
||||||
|
)
|
||||||
|
|
||||||
|
private val patchOptions: SnapshotOptions by savedStateHandle.saveable(
|
||||||
saver = optionsSaver,
|
saver = optionsSaver,
|
||||||
init = ::mutableStateMapOf
|
init = ::mutableStateMapOf
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val selectors by derivedStateOf<Array<Selector>> {
|
||||||
|
arrayOf(
|
||||||
|
// Patches that were explicitly selected
|
||||||
|
{ bundle, patch ->
|
||||||
|
explicitPatchesSelection[bundle]?.get(patch.name)
|
||||||
|
},
|
||||||
|
// The fallback selection.
|
||||||
|
when (baseSelectionMode) {
|
||||||
|
BaseSelectionMode.DEFAULT -> ({ _, patch -> patch.include })
|
||||||
|
|
||||||
|
BaseSelectionMode.PREVIOUS -> ({ bundle, patch ->
|
||||||
|
previousPatchesSelection[bundle]?.contains(patch.name) ?: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the patch options dialog for this patch.
|
* Show the patch options dialog for this patch.
|
||||||
*/
|
*/
|
||||||
@ -107,35 +150,91 @@ class PatchesSelectorViewModel(
|
|||||||
|
|
||||||
val compatibleVersions = mutableStateListOf<String>()
|
val compatibleVersions = mutableStateListOf<String>()
|
||||||
|
|
||||||
var filter by mutableStateOf(SHOW_SUPPORTED or SHOW_UNSUPPORTED)
|
var filter by mutableStateOf(SHOW_SUPPORTED or SHOW_UNIVERSAL or SHOW_UNSUPPORTED)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private fun getOrCreateSelection(bundle: Int) =
|
private suspend fun loadPreviousSelection() {
|
||||||
selectedPatches.getOrPut(bundle, ::mutableStateSetOf)
|
val selection = (input.patchesSelection ?: selectionRepository.getSelection(
|
||||||
|
packageName
|
||||||
|
)).mapValues { (_, value) -> value.toSet() }
|
||||||
|
|
||||||
fun isSelected(bundle: Int, patch: PatchInfo) =
|
withContext(Dispatchers.Main) {
|
||||||
selectedPatches[bundle]?.contains(patch.name) ?: false
|
previousPatchesSelection.putAll(selection)
|
||||||
|
}
|
||||||
fun togglePatch(bundle: Int, patch: PatchInfo) {
|
|
||||||
val name = patch.name
|
|
||||||
val patches = getOrCreateSelection(bundle)
|
|
||||||
|
|
||||||
if (patches.contains(name)) patches.remove(name) else patches.add(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getAndSaveSelection(): PatchesSelection =
|
fun switchBaseSelectionMode() = viewModelScope.launch {
|
||||||
selectedPatches.also {
|
baseSelectionMode = if (baseSelectionMode == BaseSelectionMode.DEFAULT) {
|
||||||
withContext(Dispatchers.Default) {
|
BaseSelectionMode.PREVIOUS
|
||||||
selectionRepository.updateSelection(input.selectedApp.packageName, it)
|
} else {
|
||||||
}
|
BaseSelectionMode.DEFAULT
|
||||||
}.mapValues { it.value.toMutableSet() }.apply {
|
}
|
||||||
if (allowExperimental.get()) {
|
}
|
||||||
return@apply
|
|
||||||
|
private fun getOrCreateSelection(bundle: Int) =
|
||||||
|
explicitPatchesSelection.getOrPut(bundle, ::mutableStateMapOf)
|
||||||
|
|
||||||
|
fun isSelected(bundle: Int, patch: PatchInfo) =
|
||||||
|
selectors.firstNotNullOf { fn -> fn(bundle, patch) }
|
||||||
|
|
||||||
|
fun togglePatch(bundle: Int, patch: PatchInfo) {
|
||||||
|
val patches = getOrCreateSelection(bundle)
|
||||||
|
|
||||||
|
hasModifiedSelection = true
|
||||||
|
patches[patch.name] = !isSelected(bundle, patch)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun confirmSelectionWarning(dismissPermanently: Boolean) {
|
||||||
|
selectionWarningEnabled = false
|
||||||
|
|
||||||
|
pendingSelectionAction?.invoke()
|
||||||
|
pendingSelectionAction = null
|
||||||
|
|
||||||
|
if (!dismissPermanently) return
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
prefs.disableSelectionWarning.update(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dismissSelectionWarning() {
|
||||||
|
pendingSelectionAction = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
patchOptions.clear()
|
||||||
|
baseSelectionMode = BaseSelectionMode.DEFAULT
|
||||||
|
explicitPatchesSelection.clear()
|
||||||
|
hasModifiedSelection = false
|
||||||
|
app.toast(app.getString(R.string.patch_selection_reset_toast))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSelection(): PatchesSelection {
|
||||||
|
val bundles = bundlesFlow.first()
|
||||||
|
val removeUnsupported = !allowExperimental.get()
|
||||||
|
|
||||||
|
return bundles.associate { bundle ->
|
||||||
|
val included =
|
||||||
|
bundle.all.filter { isSelected(bundle.uid, it) }.map { it.name }.toMutableSet()
|
||||||
|
|
||||||
|
if (removeUnsupported) {
|
||||||
|
val unsupported = bundle.unsupported.map { it.name }.toSet()
|
||||||
|
included.removeAll(unsupported)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out unsupported patches that may have gotten selected through the database if the setting is not enabled.
|
bundle.uid to included
|
||||||
bundlesFlow.first().forEach {
|
}
|
||||||
this[it.uid]?.removeAll(it.unsupported.map { patch -> patch.name }.toSet())
|
}
|
||||||
|
|
||||||
|
suspend fun saveSelection(selection: PatchesSelection) =
|
||||||
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
when {
|
||||||
|
hasModifiedSelection -> selectionRepository.updateSelection(packageName, selection)
|
||||||
|
baseSelectionMode == BaseSelectionMode.DEFAULT -> selectionRepository.clearSelection(
|
||||||
|
packageName
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +279,7 @@ class PatchesSelectorViewModel(
|
|||||||
private fun <K, K2, V> SnapshotStateMap<K, SnapshotStateMap<K2, V>>.getOrCreate(key: K) =
|
private fun <K, K2, V> SnapshotStateMap<K, SnapshotStateMap<K2, V>>.getOrCreate(key: K) =
|
||||||
getOrPut(key, ::mutableStateMapOf)
|
getOrPut(key, ::mutableStateMapOf)
|
||||||
|
|
||||||
private val optionsSaver: Saver<SnapshotStateOptions, Options> = snapshotStateMapSaver(
|
private val optionsSaver: Saver<SnapshotOptions, Options> = snapshotStateMapSaver(
|
||||||
// Patch name -> Options
|
// Patch name -> Options
|
||||||
valueSaver = snapshotStateMapSaver(
|
valueSaver = snapshotStateMapSaver(
|
||||||
// Option key -> Option value
|
// Option key -> Option value
|
||||||
@ -188,8 +287,24 @@ class PatchesSelectorViewModel(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
private val patchesSelectionSaver: Saver<SnapshotStatePatchesSelection, PatchesSelection> =
|
private val explicitPatchesSelectionSaver: Saver<SnapshotExplicitPatchesSelection, ExplicitPatchesSelection> =
|
||||||
snapshotStateMapSaver(valueSaver = snapshotStateSetSaver())
|
snapshotStateMapSaver(valueSaver = snapshotStateMapSaver())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enum for controlling the behavior of the selector.
|
||||||
|
*/
|
||||||
|
enum class BaseSelectionMode {
|
||||||
|
/**
|
||||||
|
* Selection is determined by the [PatchInfo.include] field.
|
||||||
|
*/
|
||||||
|
DEFAULT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selection is determined by what the user selected previously.
|
||||||
|
* Any patch that is not part of the previous selection will be deselected.
|
||||||
|
*/
|
||||||
|
PREVIOUS
|
||||||
}
|
}
|
||||||
|
|
||||||
data class BundleInfo(
|
data class BundleInfo(
|
||||||
@ -202,12 +317,9 @@ class PatchesSelectorViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private typealias Selector = (Int, PatchInfo) -> Boolean?
|
||||||
* [Options] but with observable collection types.
|
private typealias ExplicitPatchesSelection = Map<Int, Map<String, Boolean>>
|
||||||
*/
|
|
||||||
private typealias SnapshotStateOptions = SnapshotStateMap<Int, SnapshotStateMap<String, SnapshotStateMap<String, Any?>>>
|
|
||||||
|
|
||||||
/**
|
// Versions of other types, but utilizing observable collection types instead.
|
||||||
* [PatchesSelection] but with observable collection types.
|
private typealias SnapshotOptions = SnapshotStateMap<Int, SnapshotStateMap<String, SnapshotStateMap<String, Any?>>>
|
||||||
*/
|
private typealias SnapshotExplicitPatchesSelection = SnapshotStateMap<Int, SnapshotStateMap<String, Boolean>>
|
||||||
private typealias SnapshotStatePatchesSelection = SnapshotStateMap<Int, SnapshotStateSet<String>>
|
|
@ -143,6 +143,12 @@
|
|||||||
<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="universal_patches">Universal patches</string>
|
||||||
|
<string name="menu_opt_selection_mode_default">Use default selection</string>
|
||||||
|
<string name="menu_opt_selection_mode_previous">Use previous selection</string>
|
||||||
|
<string name="patch_selection_reset_toast">Patch selection and options has been reset to recommended defaults</string>
|
||||||
|
<string name="selection_warning_title">Stop using defaults?</string>
|
||||||
|
<string name="selection_warning_description">You may encounter issues when not using the default patch selection and options.</string>
|
||||||
|
<string name="selection_warning_continue_countdown">Continue (%ds)</string>
|
||||||
<string name="supported">Supported</string>
|
<string name="supported">Supported</string>
|
||||||
<string name="universal">Universal</string>
|
<string name="universal">Universal</string>
|
||||||
<string name="unsupported">Unsupported</string>
|
<string name="unsupported">Unsupported</string>
|
||||||
@ -223,6 +229,8 @@
|
|||||||
<string name="collapse_content">collapse</string>
|
<string name="collapse_content">collapse</string>
|
||||||
|
|
||||||
<string name="more">More</string>
|
<string name="more">More</string>
|
||||||
|
<string name="continue_">Continue</string>
|
||||||
|
<string name="permanent_dismiss">Do not show this again</string>
|
||||||
<string name="donate">Donate</string>
|
<string name="donate">Donate</string>
|
||||||
<string name="website">Website</string>
|
<string name="website">Website</string>
|
||||||
<string name="github">GitHub</string>
|
<string name="github">GitHub</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user