feat(patch-selector): default patches selection (#1272)

This commit is contained in:
Ax333l 2023-10-01 20:56:16 +02:00 committed by GitHub
parent ca3c9af3b8
commit f78b56ef0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 364 additions and 69 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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