mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2026-01-18 00:33:58 +00:00
feat: replace SelectedApp with packageName in selector
This commit is contained in:
@@ -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,
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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 -> {}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user