feat: replace SelectedApp with packageName in selector

This commit is contained in:
Robert
2025-12-30 17:58:54 +01:00
parent 2d98923f50
commit fda0e1697b
8 changed files with 168 additions and 314 deletions

View File

@@ -25,7 +25,6 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.model.navigation.AppSelector
import app.revanced.manager.ui.model.navigation.ComplexParameter
import app.revanced.manager.ui.model.navigation.Dashboard
@@ -141,9 +140,7 @@ private fun ReVancedManager() {
onPatchClick = { packageName ->
navController.navigateComplex(
SelectedAppInfo,
SelectedAppInfo.ViewModelParams(
SelectedApp.Search(packageName, null) // TODO
)
SelectedAppInfo.ViewModelParams(packageName)
)
},
onBackClick = navController::popBackStack,
@@ -156,15 +153,15 @@ private fun ReVancedManager() {
onSelect = { packageName ->
navController.navigateComplex(
SelectedAppInfo,
SelectedAppInfo.ViewModelParams(
SelectedApp.Search(packageName, null) // TODO
)
SelectedAppInfo.ViewModelParams(packageName)
)
},
onStorageSelect = { app ->
onStorageSelect = { packageName, localFile ->
navController.navigateComplex(
SelectedAppInfo,
SelectedAppInfo.ViewModelParams(app)
SelectedAppInfo.ViewModelParams(
packageName, localFile
)
)
},
onBackClick = navController::popBackStack
@@ -214,23 +211,25 @@ private fun ReVancedManager() {
)
}
},
onPatchSelectorClick = { app, patches, options ->
onPatchSelectorClick = { packageName, version, patches, options ->
navController.navigateComplex(
SelectedAppInfo.PatchesSelector,
SelectedAppInfo.PatchesSelector.ViewModelParams(
app,
patches,
options
packageName,
version,
currentSelection = patches,
options = options,
)
)
},
onRequiredOptions = { app, patches, options ->
onRequiredOptions = { packageName, version, patches, options ->
navController.navigateComplex(
SelectedAppInfo.RequiredOptions,
SelectedAppInfo.PatchesSelector.ViewModelParams(
app,
patches,
options
packageName,
version,
currentSelection = patches,
options = options,
)
)
},

View File

@@ -8,6 +8,7 @@ import app.revanced.manager.util.PatchSelection
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.RawValue
import kotlinx.serialization.Serializable
import java.io.File
interface ComplexParameter<T : Parcelable>
@@ -27,7 +28,8 @@ data class Update(val downloadOnScreenEntry: Boolean = false)
data object SelectedAppInfo : ComplexParameter<SelectedAppInfo.ViewModelParams> {
@Parcelize
data class ViewModelParams(
val app: SelectedApp,
val packageName: String,
val localFile: File? = null,
val patches: PatchSelection? = null
) : Parcelable
@@ -38,7 +40,9 @@ data object SelectedAppInfo : ComplexParameter<SelectedAppInfo.ViewModelParams>
data object PatchesSelector : ComplexParameter<PatchesSelector.ViewModelParams> {
@Parcelize
data class ViewModelParams(
val app: SelectedApp,
val packageName: String,
val version: String?,
val restrictToVersion: Boolean = false,
val currentSelection: PatchSelection?,
val options: @RawValue Options,
) : Parcelable

View File

@@ -44,23 +44,23 @@ import app.revanced.manager.ui.component.LazyColumnWithScrollbar
import app.revanced.manager.ui.component.LoadingIndicator
import app.revanced.manager.ui.component.NonSuggestedVersionDialog
import app.revanced.manager.ui.component.SearchView
import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.viewmodel.AppSelectorViewModel
import app.revanced.manager.util.APK_MIMETYPE
import app.revanced.manager.util.EventEffect
import app.revanced.manager.util.transparentListItemColors
import org.koin.androidx.compose.koinViewModel
import java.io.File
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppSelectorScreen(
onSelect: (String) -> Unit,
onStorageSelect: (SelectedApp.Local) -> Unit,
onSelect: (packageName: String) -> Unit,
onStorageSelect: (packageName: String, File) -> Unit,
onBackClick: () -> Unit,
vm: AppSelectorViewModel = koinViewModel()
) {
EventEffect(flow = vm.storageSelectionFlow) {
onStorageSelect(it)
onStorageSelect(it.first, it.second)
}
val pickApkLauncher =

View File

@@ -36,7 +36,6 @@ import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.component.NotificationCard
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.model.SelectedVersion
import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel
import app.revanced.manager.util.Options
@@ -49,8 +48,8 @@ import org.koin.compose.koinInject
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SelectedAppInfoScreen(
onPatchSelectorClick: (SelectedApp, PatchSelection?, Options) -> Unit,
onRequiredOptions: (SelectedApp, PatchSelection?, Options) -> Unit,
onPatchSelectorClick: (packageName: String, version: String?, PatchSelection?, Options) -> Unit,
onRequiredOptions: (packageName: String, version: String?, PatchSelection?, Options) -> Unit,
onPatchClick: () -> Unit,
onVersionClick: (packageName: String, patchSelection: PatchSelection, currentSelection: SelectedVersion) -> Unit,
onSourceClick: (packageName: String, version: String?) -> Unit,
@@ -62,8 +61,8 @@ fun SelectedAppInfoScreen(
val networkConnected = remember { networkInfo.isConnected() }
val networkMetered = remember { !networkInfo.isUnmetered() }
val packageName = vm.selectedApp.packageName
val version = vm.selectedApp.version
val packageName = vm.input.packageName
val version = "123" // TODO
val bundles by vm.bundleInfoFlow.collectAsStateWithLifecycle(emptyList())
val allowIncompatiblePatches by vm.prefs.disablePatchVersionCompatCheck.getAsState()
@@ -74,16 +73,16 @@ fun SelectedAppInfoScreen(
}
val selectedPatchCount = patches.values.sumOf { it.size }
// val patches2 by remember {
// derivedStateOf {
// vm.patchSelection(bundles, allowIncompatiblePatches).collectAsStateWithLifecycle()
// }
// }
val composableScope = rememberCoroutineScope()
val error by vm.errorFlow.collectAsStateWithLifecycle(null)
val selectedVersion by vm.selectedVersion.collectAsStateWithLifecycle()
val resolvedVersion by vm.resolvedVersion.collectAsStateWithLifecycle(null)
val selectedSource by vm.selectedSource.collectAsStateWithLifecycle()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
@@ -115,7 +114,8 @@ fun SelectedAppInfoScreen(
composableScope.launch {
if (!vm.hasSetRequiredOptions(patches)) {
onRequiredOptions(
vm.selectedApp,
vm.packageName,
version,
vm.getCustomPatches(bundles, allowIncompatiblePatches),
vm.options
)
@@ -151,10 +151,11 @@ fun SelectedAppInfoScreen(
stringResource(
R.string.patch_selector_item_description,
selectedPatchCount
) + "\n\uFE0F 3 incompatible",
),
onClick = {
onPatchSelectorClick(
vm.selectedApp,
vm.packageName,
version,
vm.getCustomPatches(
bundles,
allowIncompatiblePatches
@@ -164,43 +165,39 @@ fun SelectedAppInfoScreen(
}
)
if (vm.selectedApp !is SelectedApp.Local || !(vm.selectedApp as SelectedApp.Local).temporary) {
val selectedVersion by vm.selectedVersion.collectAsStateWithLifecycle(SelectedVersion.Auto)
val resolvedVersion by vm.resolvedVersion.collectAsStateWithLifecycle(null)
if (vm.input.localFile == null) {
val version = resolvedVersion ?: "Any available version"
val description = if (selectedVersion is SelectedVersion.Auto)
"Auto ($version)" // stringResource(R.string.selected_app_meta_auto_version, actualVersion)
else version
PageItem(
R.string.version_selector_item,
description,
// "Auto (${requiredVersion ?: stringResource(R.string.selected_app_meta_any_version)})", // ⚠️ 1 Patch incompatible
onClick = { onVersionClick(packageName, patches, selectedVersion) },
)
}
PageItem(
R.string.apk_source_selector_item,
when (val app = vm.selectedApp) {
is SelectedApp.Search -> "Auto (Downloaded APK)" // stringResource(R.string.apk_source_auto)
is SelectedApp.Installed -> app.version + " (Installed)" // stringResource(R.string.apk_source_installed)
is SelectedApp.Download -> plugins.find { it.packageName == app.data.pluginPackageName }?.name ?: app.data.pluginPackageName
is SelectedApp.Local -> if (app.temporary) "${app.version} (Local File)" else "Downloaded APK"
// stringResource(
// R.string.apk_source_downloader,
// plugins.find { it.packageName == app.data.pluginPackageName }?.name
// ?: app.data.pluginPackageName
// )
// stringResource(R.string.apk_source_local)
},
// when (val app = vm.input.app) {
// is SelectedApp.Search -> "Auto (Downloaded APK)" // stringResource(R.string.apk_source_auto)
// is SelectedApp.Installed -> app.version + " (Installed)" // stringResource(R.string.apk_source_installed)
// is SelectedApp.Download -> plugins.find { it.packageName == app.data.pluginPackageName }?.name ?: app.data.pluginPackageName
// is SelectedApp.Local -> if (app.temporary) "${app.version} (Local File)" else "Downloaded APK"
//
//
//// stringResource(
//// R.string.apk_source_downloader,
//// plugins.find { it.packageName == app.data.pluginPackageName }?.name
//// ?: app.data.pluginPackageName
//// )
// // stringResource(R.string.apk_source_local)
// },
"Sourcing the source",
onClick = { onSourceClick(packageName, version) },
enabled = !vm.selectedApp.let { it is SelectedApp.Local && it.temporary }, // Disable for APK from storage
enabled = vm.input.localFile == null, // Disable for APK from storage
)
error?.let {
Text(
@@ -214,8 +211,8 @@ fun SelectedAppInfoScreen(
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
val needsInternet =
vm.selectedApp.let { it is SelectedApp.Search || it is SelectedApp.Download }
val needsInternet = false
// vm.input.app.let { it is SelectedApp.Search || it is SelectedApp.Download }
when {
!needsInternet -> {}

View File

@@ -2,7 +2,6 @@ package app.revanced.manager.ui.screen
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -15,27 +14,16 @@ import androidx.compose.material3.ListItem
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R
import app.revanced.manager.data.room.apps.downloaded.DownloadedApp
import app.revanced.manager.data.room.apps.installed.InstallType
import app.revanced.manager.data.room.apps.installed.InstalledApp
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
import app.revanced.manager.ui.component.AlertDialogExtended
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.LoadingIndicator
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.model.SelectedVersion
import app.revanced.manager.ui.viewmodel.VersionSelectorViewModel
import app.revanced.manager.util.enabled
import app.revanced.manager.util.transparentListItemColors
@OptIn(ExperimentalMaterial3Api::class)
@@ -128,132 +116,3 @@ private fun VersionOption(
)
}
@Composable
fun AppSourceSelectorDialog(
plugins: List<LoadedDownloaderPlugin>,
installedApp: Pair<SelectedApp.Installed, InstalledApp?>?,
downloadedApps: List<DownloadedApp>,
searchApp: SelectedApp.Search,
activeSearchJob: String?,
hasRoot: Boolean,
requiredVersion: String?,
onDismissRequest: () -> Unit,
onSelectPlugin: (LoadedDownloaderPlugin) -> Unit,
onSelect: (SelectedApp) -> Unit,
onSelectDownloaded: (DownloadedApp) -> Unit = {},
) {
val canSelect = activeSearchJob == null
AlertDialogExtended(
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(onClick = onDismissRequest) {
Text(stringResource(R.string.cancel))
}
},
title = { Text(stringResource(R.string.app_source_dialog_title)) },
textHorizontalPadding = PaddingValues(horizontal = 0.dp),
text = {
Column {
HorizontalDivider()
LazyColumn {
item(key = "auto") {
val hasPlugins = plugins.isNotEmpty()
ListItem(
modifier = Modifier
.clickable(enabled = canSelect && hasPlugins) { onSelect(searchApp) }
.enabled(hasPlugins),
headlineContent = { Text(stringResource(R.string.app_source_dialog_option_auto) + " (Recommended)") },
supportingContent = {
Text(
"Automatically choose a suitable source"
// if (hasPlugins)
// stringResource(R.string.app_source_dialog_option_auto_description)
//// "Automatically choose a suitable source"
// else
// stringResource(R.string.app_source_dialog_option_auto_unavailable)
)
},
colors = transparentListItemColors
)
}
installedApp?.let { (app, meta) ->
item(key = "installed") {
val (usable, text) = when {
// Mounted apps must be unpatched before patching, which cannot be done without root access.
meta?.installType == InstallType.MOUNT && !hasRoot -> false to stringResource(
R.string.app_source_dialog_option_installed_no_root
)
// Patching already patched apps is not allowed because patches expect unpatched apps.
meta?.installType == InstallType.DEFAULT -> false to stringResource(
R.string.already_patched
)
// Version does not match suggested version.
requiredVersion != null && app.version != requiredVersion -> false to "Does not match the selected version"
// stringResource(
// R.string.app_source_dialog_option_installed_version_not_suggested,
// app.version
// )
else -> true to null
}
ListItem(
modifier = Modifier
.clickable(enabled = canSelect && usable) { onSelect(app) }
.enabled(usable),
overlineContent = { Text("Installed") },
headlineContent = { Text(app.version) },
supportingContent = text?.let { { Text(text) } },
colors = transparentListItemColors
)
}
}
items(downloadedApps, key = { it.version }) { app ->
val (usable, text) = when {
// Version does not match suggested version.
requiredVersion != null && app.version != requiredVersion -> false to "Does not match the selected version"
// stringResource(
// R.string.app_source_dialog_option_installed_version_not_suggested,
// app.version
// )
else -> true to null // "Downloaded using downloader plugin"
}
ListItem(
modifier = Modifier
.clickable(enabled = usable) { onSelectDownloaded(app) }
.enabled(usable),
overlineContent = { Text("Downloaded") },
headlineContent = { Text(app.version) },
supportingContent = text?.let { { Text(text) } },
colors = transparentListItemColors
)
}
items(plugins, key = { "plugin_${it.packageName}" }) { plugin ->
ListItem(
modifier = Modifier.clickable(enabled = canSelect) {
onSelectPlugin(plugin)
},
overlineContent = { Text("Plugin") },
headlineContent = {
Text(
plugin.name,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
trailingContent = (@Composable { LoadingIndicator() }).takeIf { activeSearchJob == plugin.packageName },
colors = transparentListItemColors
)
}
}
HorizontalDivider()
}
}
)
}

View File

@@ -31,7 +31,7 @@ class AppSelectorViewModel(
private val app: Application,
private val pm: PM,
fs: Filesystem,
private val patchBundleRepository: PatchBundleRepository,
patchBundleRepository: PatchBundleRepository,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val inputFile = savedStateHandle.saveable(key = "inputFile") {
@@ -42,7 +42,7 @@ class AppSelectorViewModel(
}
val appList = pm.appList
private val storageSelectionChannel = Channel<SelectedApp.Local>()
private val storageSelectionChannel = Channel<Pair<String, File>>()
val storageSelectionFlow = storageSelectionChannel.receiveAsFlow()
val suggestedAppVersions = patchBundleRepository.suggestedVersions.flowOn(Dispatchers.Default)
@@ -66,11 +66,7 @@ class AppSelectorViewModel(
return@launch
}
if (patchBundleRepository.isVersionAllowed(selectedApp.packageName, selectedApp.version)) {
storageSelectionChannel.send(selectedApp)
} else {
nonSuggestedVersionDialogSubject = selectedApp
}
storageSelectionChannel.send(selectedApp)
}
private fun loadSelectedFile(uri: Uri) =
@@ -80,12 +76,7 @@ class AppSelectorViewModel(
Files.copy(stream, toPath())
pm.getPackageInfo(this)?.let { packageInfo ->
SelectedApp.Local(
packageName = packageInfo.packageName,
version = packageInfo.versionName!!,
file = this,
temporary = true
)
Pair(packageInfo.packageName, this)
}
}
}

View File

@@ -51,8 +51,8 @@ class PatchesSelectorViewModel(input: SelectedAppInfo.PatchesSelector.ViewModelP
private val savedStateHandle: SavedStateHandle = get()
private val prefs: PreferencesManager = get()
private val packageName = input.app.packageName
val appVersion = input.app.version
private val packageName = input.packageName
val appVersion = input.version
var selectionWarningEnabled by mutableStateOf(true)
private set
@@ -62,7 +62,7 @@ class PatchesSelectorViewModel(input: SelectedAppInfo.PatchesSelector.ViewModelP
val allowIncompatiblePatches =
get<PreferencesManager>().disablePatchVersionCompatCheck.getBlocking()
val bundlesFlow =
get<PatchBundleRepository>().scopedBundleInfoFlow(packageName, input.app.version)
get<PatchBundleRepository>().scopedBundleInfoFlow(packageName, input.version)
init {
viewModelScope.launch {

View File

@@ -50,7 +50,7 @@ import org.koin.core.component.get
@OptIn(SavedStateHandleSaveableApi::class, PluginHostApi::class)
class SelectedAppInfoViewModel(
input: SelectedAppInfo.ViewModelParams
val input: SelectedAppInfo.ViewModelParams
) : ViewModel(), KoinComponent {
private val bundleRepository: PatchBundleRepository = get()
private val selectionRepository: PatchSelectionRepository = get()
@@ -61,7 +61,7 @@ class SelectedAppInfoViewModel(
private val savedStateHandle: SavedStateHandle = get()
val prefs: PreferencesManager = get()
val plugins = pluginsRepository.loadedPluginsFlow
val packageName = input.app.packageName
val packageName = input.packageName
private val persistConfiguration = input.patches == null
@@ -105,7 +105,13 @@ class SelectedAppInfoViewModel(
// All patches for package
val bundles = bundleRepository.scopedBundleInfoFlow(packageName, null)
@OptIn(ExperimentalCoroutinesApi::class)
val bundles = selectedVersion.flatMapLatest { selectedVersion ->
val version = if (selectedVersion is SelectedVersion.Specific)
selectedVersion.version
else null
bundleRepository.scopedBundleInfoFlow(packageName, version)
}
// Selection derived from selectionFlow
val patchSelection = combine(
@@ -139,6 +145,98 @@ class SelectedAppInfoViewModel(
}
val bundleInfoFlow by derivedStateOf {
bundleRepository.scopedBundleInfoFlow(packageName, null)
}
var options: Options by savedStateHandle.saveable {
viewModelScope.launch {
if (!persistConfiguration) return@launch // TODO: save options for patched apps.
val bundlePatches = bundleInfoFlow.first()
.associate { it.uid to it.patches.associateBy { patch -> patch.name } }
options = withContext(Dispatchers.Default) {
optionsRepository.getOptions(packageName, bundlePatches)
}
}
mutableStateOf(emptyMap())
}
private set
var installedAppData: Pair<SelectedApp.Installed, InstalledApp?>? by mutableStateOf(null)
private set
private var _selectedApp by savedStateHandle.saveable {
mutableStateOf(null)
}
var selectedAppInfo: PackageInfo? by mutableStateOf(null)
private set
var selectedApp
get() = _selectedApp
set(value) {
_selectedApp = value
invalidateSelectedAppInfo()
}
// TODO: Remove
private var oldSelectionState: SelectionState by savedStateHandle.saveable { mutableStateOf(SelectionState.Default) }
val errorFlow = combine(plugins, snapshotFlow { selectedApp }) { pluginsList, app ->
when {
app is SelectedApp.Search && pluginsList.isEmpty() -> Error.NoPlugins
else -> null
}
}
// TODO: Load from local file or downloaded app
private fun invalidateSelectedAppInfo() = viewModelScope.launch {
selectedAppInfo = pm.getPackageInfo(packageName)
}
fun getOptionsFiltered(bundles: List<PatchBundleInfo.Scoped>) = options.filtered(bundles)
suspend fun hasSetRequiredOptions(patchSelection: PatchSelection) = bundleInfoFlow
.first()
.requiredOptionsSet(
allowIncompatible = prefs.disablePatchVersionCompatCheck.get(),
isSelected = { bundle, patch -> patch.name in patchSelection[bundle.uid]!! },
optionsForPatch = { bundle, patch -> options[bundle.uid]?.get(patch.name) },
)
suspend fun getPatcherParams(): Patcher.ViewModelParams {
val allowIncompatible = prefs.disablePatchVersionCompatCheck.get()
val bundles = bundleInfoFlow.first()
return Patcher.ViewModelParams(
SelectedApp.Installed(packageName, version = "123"), // TODO
getPatches(bundles, allowIncompatible),
getOptionsFiltered(bundles)
)
}
fun getPatches(bundles: List<PatchBundleInfo.Scoped>, allowIncompatible: Boolean) =
oldSelectionState.patches(bundles, allowIncompatible)
fun getCustomPatches(
bundles: List<PatchBundleInfo.Scoped>,
allowIncompatible: Boolean
): PatchSelection? =
(oldSelectionState as? SelectionState.Customized)?.patches(bundles, allowIncompatible)
init {
invalidateSelectedAppInfo()
@@ -167,100 +265,6 @@ class SelectedAppInfoViewModel(
}
}
var options: Options by savedStateHandle.saveable {
viewModelScope.launch {
if (!persistConfiguration) return@launch // TODO: save options for patched apps.
val bundlePatches = bundleInfoFlow.first()
.associate { it.uid to it.patches.associateBy { patch -> patch.name } }
options = withContext(Dispatchers.Default) {
optionsRepository.getOptions(packageName, bundlePatches)
}
}
mutableStateOf(emptyMap())
}
private set
var installedAppData: Pair<SelectedApp.Installed, InstalledApp?>? by mutableStateOf(null)
private set
private var _selectedApp by savedStateHandle.saveable {
mutableStateOf(input.app)
}
var selectedAppInfo: PackageInfo? by mutableStateOf(null)
private set
var selectedApp
get() = _selectedApp
set(value) {
_selectedApp = value
invalidateSelectedAppInfo()
}
val bundleInfoFlow by derivedStateOf {
bundleRepository.scopedBundleInfoFlow(packageName, selectedApp.version)
}
// TODO: Remove
private var oldSelectionState: SelectionState by savedStateHandle.saveable { mutableStateOf(SelectionState.Default) }
val errorFlow = combine(plugins, snapshotFlow { selectedApp }) { pluginsList, app ->
when {
app is SelectedApp.Search && pluginsList.isEmpty() -> Error.NoPlugins
else -> null
}
}
private fun invalidateSelectedAppInfo() = viewModelScope.launch {
selectedAppInfo = when (val app = selectedApp) {
is SelectedApp.Local -> withContext(Dispatchers.IO) { pm.getPackageInfo(app.file) }
else -> withContext(Dispatchers.IO) { pm.getPackageInfo(packageName) }
// else -> null
}
}
fun getOptionsFiltered(bundles: List<PatchBundleInfo.Scoped>) = options.filtered(bundles)
suspend fun hasSetRequiredOptions(patchSelection: PatchSelection) = bundleInfoFlow
.first()
.requiredOptionsSet(
allowIncompatible = prefs.disablePatchVersionCompatCheck.get(),
isSelected = { bundle, patch -> patch.name in patchSelection[bundle.uid]!! },
optionsForPatch = { bundle, patch -> options[bundle.uid]?.get(patch.name) },
)
suspend fun getPatcherParams(): Patcher.ViewModelParams {
val allowIncompatible = prefs.disablePatchVersionCompatCheck.get()
val bundles = bundleInfoFlow.first()
return Patcher.ViewModelParams(
selectedApp,
getPatches(bundles, allowIncompatible),
getOptionsFiltered(bundles)
)
}
fun getPatches(bundles: List<PatchBundleInfo.Scoped>, allowIncompatible: Boolean) =
oldSelectionState.patches(bundles, allowIncompatible)
fun getCustomPatches(
bundles: List<PatchBundleInfo.Scoped>,
allowIncompatible: Boolean
): PatchSelection? =
(oldSelectionState as? SelectionState.Customized)?.patches(bundles, allowIncompatible)
enum class Error(@param:StringRes val resourceId: Int) {
NoPlugins(R.string.downloader_no_plugins_available)
}