Use selected source in patcher

This commit is contained in:
Robert
2026-01-07 00:42:21 +01:00
parent 0169fd2109
commit 08662b2132
11 changed files with 140 additions and 134 deletions

View File

@@ -37,7 +37,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
@@ -84,7 +83,7 @@ class PatchBundleRepository(
.map(PatchInfo::toPatcherPatch)
}.toSet()
allPatches.mostCommonCompatibleVersions(countUnusedPatches = false)[packageName]
allPatches.mostCommonCompatibleVersions(countUnusedPatches = true)[packageName]
}
val suggestedVersions = bundleInfoFlow.map {

View File

@@ -39,7 +39,7 @@ import app.revanced.manager.patcher.toRemoteError
import app.revanced.manager.plugin.downloader.GetScope
import app.revanced.manager.plugin.downloader.PluginHostApi
import app.revanced.manager.plugin.downloader.UserInteractionException
import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.model.SelectedSource
import app.revanced.manager.util.Options
import app.revanced.manager.util.PM
import app.revanced.manager.util.PatchSelection
@@ -67,7 +67,9 @@ class PatcherWorker(
private val rootInstaller: RootInstaller by inject()
class Args(
val input: SelectedApp,
val packageName: String,
val version: String?,
val source: SelectedSource,
val output: String,
val selectedPatches: PatchSelection,
val options: Options,
@@ -75,9 +77,7 @@ class PatcherWorker(
val handleStartActivityRequest: suspend (LoadedDownloaderPlugin, Intent) -> ActivityResult,
val setInputFile: suspend (File) -> Unit,
val onEvent: (ProgressEvent) -> Unit,
) {
val packageName get() = input.packageName
}
)
override suspend fun getForegroundInfo() =
ForegroundInfo(
@@ -142,7 +142,7 @@ class PatcherWorker(
val patchedApk = fs.tempDir.resolve("patched.apk")
return try {
if (args.input is SelectedApp.Installed) {
if (args.source is SelectedSource.Installed) {
installedAppRepository.get(args.packageName)?.let {
if (it.installType == InstallType.MOUNT) {
rootInstaller.unmount(args.packageName)
@@ -155,7 +155,7 @@ class PatcherWorker(
plugin,
data,
args.packageName,
args.input.version,
args.version,
prefs.suggestedVersionSafeguard.get(),
!prefs.disablePatchVersionCompatCheck.get(),
onDownload = { progress ->
@@ -169,18 +169,10 @@ class PatcherWorker(
}
).also { args.setInputFile(it) }
val inputFile = when (val selectedApp = args.input) {
is SelectedApp.Download -> {
runStep(StepId.DownloadAPK, args.onEvent) {
val (plugin, data) = downloaderPluginRepository.unwrapParceledData(
selectedApp.data
)
val inputFile = when (val source = args.source) {
is SelectedSource.Auto -> throw Exception("Auto source is not supported in worker.")
download(plugin, data)
}
}
is SelectedApp.Search -> {
is SelectedSource.Plugin -> {
runStep(StepId.DownloadAPK, args.onEvent) {
downloaderPluginRepository.loadedPluginsFlow.first()
.firstNotNullOfOrNull { plugin ->
@@ -206,10 +198,10 @@ class PatcherWorker(
withContext(Dispatchers.IO) {
plugin.get(
getScope,
selectedApp.packageName,
selectedApp.version
args.packageName,
args.version
)
}?.takeIf { (_, version) -> selectedApp.version == null || version == selectedApp.version }
}?.takeIf { (_, version) -> args.version == null || version == args.version }
} catch (e: UserInteractionException.Activity.NotCompleted) {
throw e
} catch (_: UserInteractionException) {
@@ -219,8 +211,9 @@ class PatcherWorker(
}
}
is SelectedApp.Local -> selectedApp.file.also { args.setInputFile(it) }
is SelectedApp.Installed -> File(pm.getPackageInfo(selectedApp.packageName)!!.applicationInfo!!.sourceDir)
is SelectedSource.Downloaded -> File(source.path)
is SelectedSource.Installed -> File(pm.getPackageInfo(args.packageName)!!.applicationInfo!!.sourceDir)
}
val runtime = if (prefs.useProcessRuntime.get()) {
@@ -258,9 +251,10 @@ class PatcherWorker(
Result.failure()
} finally {
patchedApk.delete()
if (args.input is SelectedApp.Local && args.input.temporary) {
args.input.file.delete()
}
// TODO
// if (args.source is SelectedApp.Local && args.source.temporary) {
// args.source.file.delete()
// }
}
}

View File

@@ -8,5 +8,5 @@ sealed class SelectedSource : Parcelable {
data object Auto : SelectedSource()
data object Installed : SelectedSource()
data class Downloaded(val path: String, val version: String) : SelectedSource()
data class Plugin(val plugin: String) : SelectedSource() // TODO
data class Plugin(val packageName: String?) : SelectedSource()
}

View File

@@ -1,7 +1,6 @@
package app.revanced.manager.ui.model.navigation
import android.os.Parcelable
import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.model.SelectedSource
import app.revanced.manager.ui.model.SelectedVersion
import app.revanced.manager.util.Options
@@ -76,7 +75,9 @@ data object SelectedAppInfo : ComplexParameter<SelectedAppInfo.ViewModelParams>
data object Patcher : ComplexParameter<Patcher.ViewModelParams> {
@Parcelize
data class ViewModelParams(
val selectedApp: SelectedApp,
val packageName: String,
val version: String?,
val selectedSource: SelectedSource,
val selectedPatches: PatchSelection,
val options: @RawValue Options
) : Parcelable

View File

@@ -83,12 +83,12 @@ fun AppSelectorScreen(
}
}
vm.nonSuggestedVersionDialogSubject?.let {
NonSuggestedVersionDialog(
suggestedVersion = suggestedVersions[it.packageName].orEmpty(),
onDismiss = vm::dismissNonSuggestedVersionDialog
)
}
// vm.nonSuggestedVersionDialogSubject?.let {
// NonSuggestedVersionDialog(
// suggestedVersion = suggestedVersions[it.packageName].orEmpty(),
// onDismiss = vm::dismissNonSuggestedVersionDialog
// )
// }
if (search)
SearchView(

View File

@@ -182,25 +182,22 @@ fun SelectedAppInfoScreen(
PageItem(
R.string.apk_source_selector_item,
selectedSource.toString(),
onClick = { onSourceClick(packageName, versionText, selectedSource) },
onClick = { onSourceClick(packageName, resolvedVersion, selectedSource) },
)
error?.let {
Text(
stringResource(it.resourceId),
color = MaterialTheme.colorScheme.error,
modifier = Modifier.padding(horizontal = 16.dp)
)
}
// error?.let {
// Text(
// stringResource(it.resourceId),
// color = MaterialTheme.colorScheme.error,
// modifier = Modifier.padding(horizontal = 16.dp)
// )
// }
Column(
if (resolvedSource is SelectedSource.Plugin) Column(
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
val needsInternet = resolvedSource is SelectedSource.Plugin
when {
!needsInternet -> {}
!networkConnected -> {
NotificationCard(
isWarning = true,
@@ -209,7 +206,6 @@ fun SelectedAppInfoScreen(
onDismiss = null
)
}
networkMetered -> {
NotificationCard(
isWarning = true,

View File

@@ -1,8 +1,6 @@
package app.revanced.manager.ui.screen
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
@@ -16,8 +14,12 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextOverflow
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.network.downloader.DownloaderPluginState
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.model.SelectedSource
@@ -32,7 +34,10 @@ fun SourceSelectorScreen(
onSave: (source: SelectedSource) -> Unit,
viewModel: SourceSelectorViewModel,
) {
val context = LocalContext.current
val downloadedApps by viewModel.downloadedApps.collectAsStateWithLifecycle(emptyList())
val plugins by viewModel.plugins.collectAsStateWithLifecycle(emptyList())
Scaffold(
topBar = {
@@ -49,23 +54,27 @@ fun SourceSelectorScreen(
)
}
) { paddingValues ->
Column(
modifier = Modifier.padding(paddingValues)
LazyColumn (
contentPadding = paddingValues,
) {
item {
SourceOption(
isSelected = viewModel.selectedSource == SelectedSource.Auto,
onSelect = { viewModel.selectSource(SelectedSource.Auto) },
headlineContent = { Text("Auto (Recommended)") },
supportingContent = { Text("Automatically select the best available source") }
)
}
item {
SourceOption(
isSelected = viewModel.selectedSource == SelectedSource.Plugin("any"),
onSelect = { viewModel.selectSource(SelectedSource.Plugin("any")) },
isSelected = viewModel.selectedSource == SelectedSource.Plugin(null),
onSelect = { viewModel.selectSource(SelectedSource.Plugin(null)) },
headlineContent = { Text("Any available downloader") },
)
}
viewModel.installedVersion?.let { installedVersion ->
item {
HorizontalDivider()
SourceOption(
@@ -76,11 +85,10 @@ fun SourceSelectorScreen(
enabled = viewModel.input.version?.let { it == installedVersion } ?: true
)
}
}
if (downloadedApps.isNotEmpty()) {
HorizontalDivider()
if (downloadedApps.isNotEmpty()) item { HorizontalDivider() }
LazyColumn {
items(downloadedApps, key = { it.version }) { app ->
SourceOption(
isSelected = (viewModel.selectedSource as? SelectedSource.Downloaded)?.version == app.version,
@@ -89,27 +97,29 @@ fun SourceSelectorScreen(
overlineContent = { Text("Downloaded") },
)
}
}
if (plugins.isNotEmpty()) item { HorizontalDivider() }
items(plugins, key = { it.first }) {
val packageInfo = remember {
viewModel.getPackageInfo(it.first)
}
HorizontalDivider()
val label = remember {
packageInfo?.applicationInfo?.loadLabel(context.packageManager).toString()
}
SourceOption(
isSelected = viewModel.selectedSource == SelectedSource.Plugin("plugin-id"),
onSelect = { viewModel.selectSource(SelectedSource.Plugin("plugin-id")) },
headlineContent = { Text("APKMirror Downloader") },
isSelected = viewModel.selectedSource == SelectedSource.Plugin(it.first),
onSelect = { viewModel.selectSource(SelectedSource.Plugin(it.first)) },
headlineContent = { Text(label, maxLines = 1, overflow = TextOverflow.Ellipsis) },
overlineContent = { Text("Plugin") },
enabled = it.second is DownloaderPluginState.Loaded,
supportingContent = (it.second as? DownloaderPluginState.Untrusted)?.let { {
Text("Not trusted")
} }
)
SourceOption(
isSelected = viewModel.selectedSource == SelectedSource.Plugin("another-plugin-id"),
onSelect = { viewModel.selectSource(SelectedSource.Plugin("another-plugin-id")) },
headlineContent = { Text("Another Plugin") },
overlineContent = { Text("Plugin") },
supportingContent = { Text("Untrusted") },
enabled = false,
)
}
}
}
}

View File

@@ -3,9 +3,6 @@ package app.revanced.manager.ui.viewmodel
import android.app.Application
import android.content.pm.PackageInfo
import android.net.Uri
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@@ -14,7 +11,6 @@ import androidx.lifecycle.viewmodel.compose.saveable
import app.revanced.manager.R
import app.revanced.manager.data.platform.Filesystem
import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.util.PM
import app.revanced.manager.util.toast
import kotlinx.coroutines.Dispatchers
@@ -47,14 +43,14 @@ class AppSelectorViewModel(
val suggestedAppVersions = patchBundleRepository.suggestedVersions.flowOn(Dispatchers.Default)
var nonSuggestedVersionDialogSubject by mutableStateOf<SelectedApp.Local?>(null)
private set
// var nonSuggestedVersionDialogSubject by mutableStateOf<SelectedApp.Local?>(null)
// private set
fun loadLabel(app: PackageInfo?) = with(pm) { app?.label() ?: "Not installed" }
fun dismissNonSuggestedVersionDialog() {
nonSuggestedVersionDialogSubject = null
}
// fun dismissNonSuggestedVersionDialog() {
// nonSuggestedVersionDialogSubject = null
// }
fun handleStorageResult(uri: Uri) = viewModelScope.launch {
val selectedApp = withContext(Dispatchers.IO) {

View File

@@ -37,7 +37,7 @@ import app.revanced.manager.patcher.worker.PatcherWorker
import app.revanced.manager.plugin.downloader.PluginHostApi
import app.revanced.manager.plugin.downloader.UserInteractionException
import app.revanced.manager.ui.model.InstallerModel
import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.model.SelectedSource
import app.revanced.manager.ui.model.State
import app.revanced.manager.ui.model.StepCategory
import app.revanced.manager.ui.model.Step
@@ -93,9 +93,8 @@ class PatcherViewModel(
private val ackpineInstaller: PackageInstaller = get()
private var installedApp: InstalledApp? = null
private val selectedApp = input.selectedApp
val packageName = selectedApp.packageName
val version = selectedApp.version
val packageName = input.packageName
val version = input.version
var installedPackageName by savedStateHandle.saveable(
key = "installedPackageName",
@@ -160,7 +159,7 @@ class PatcherViewModel(
}
val steps by savedStateHandle.saveable(saver = snapshotStateListSaver()) {
generateSteps(app, input.selectedApp, input.selectedPatches).toMutableStateList()
generateSteps(app, input.selectedSource, input.selectedPatches).toMutableStateList()
}
val progress by derivedStateOf {
@@ -178,7 +177,9 @@ class PatcherViewModel(
ParcelUuid(
workerRepository.launchExpedited<PatcherWorker, PatcherWorker.Args>(
"patching", PatcherWorker.Args(
input.selectedApp,
input.packageName,
input.version,
input.selectedSource,
outputFile.path,
input.selectedPatches,
input.options,
@@ -257,7 +258,7 @@ class PatcherViewModel(
super.onCleared()
workManager.cancelWorkById(patcherWorkerId.uuid)
if (input.selectedApp is SelectedApp.Installed && installedApp?.installType == InstallType.MOUNT) {
if (input.selectedSource is SelectedSource.Installed && installedApp?.installType == InstallType.MOUNT) {
GlobalScope.launch(Dispatchers.Main) {
uiSafe(app, R.string.failed_to_mount, "Failed to mount") {
withTimeout(Duration.ofMinutes(1L)) {
@@ -381,7 +382,7 @@ class PatcherViewModel(
installedAppRepository.addOrUpdate(
installerPkgName,
packageName,
input.selectedApp.version
input.version
?: withContext(Dispatchers.IO) { pm.getPackageInfo(outputFile)?.versionName!! },
InstallType.DEFAULT,
input.selectedPatches
@@ -443,7 +444,7 @@ class PatcherViewModel(
}
}
val inputVersion = input.selectedApp.version
val inputVersion = input.version
?: withContext(Dispatchers.IO) { inputFile?.let(pm::getPackageInfo)?.versionName }
?: throw Exception("Failed to determine input APK version")
@@ -535,10 +536,10 @@ class PatcherViewModel(
fun generateSteps(
context: Context,
selectedApp: SelectedApp,
selectedSource: SelectedSource,
selectedPatches: PatchSelection
): List<Step> = buildList {
if (selectedApp is SelectedApp.Download || selectedApp is SelectedApp.Search)
if (selectedSource is SelectedSource.Plugin)
add(
Step(
StepId.DownloadAPK,

View File

@@ -14,7 +14,6 @@ import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi
import androidx.lifecycle.viewmodel.compose.saveable
import app.revanced.manager.R
import app.revanced.manager.data.room.apps.installed.InstalledApp
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.DownloaderPluginRepository
import app.revanced.manager.domain.repository.InstalledAppRepository
@@ -25,7 +24,6 @@ import app.revanced.manager.patcher.patch.PatchBundleInfo
import app.revanced.manager.patcher.patch.PatchBundleInfo.Extensions.requiredOptionsSet
import app.revanced.manager.patcher.patch.PatchBundleInfo.Extensions.toPatchSelection
import app.revanced.manager.plugin.downloader.PluginHostApi
import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.model.SelectedSource
import app.revanced.manager.ui.model.SelectedVersion
import app.revanced.manager.ui.model.navigation.Patcher
@@ -201,8 +199,8 @@ class SelectedAppInfoViewModel(
var installedAppData: Pair<SelectedApp.Installed, InstalledApp?>? by mutableStateOf(null)
private set
// var installedAppData: Pair<SelectedApp.Installed, InstalledApp?>? by mutableStateOf(null)
// private set
private var _selectedApp by savedStateHandle.saveable {
mutableStateOf(null)
@@ -229,7 +227,7 @@ class SelectedAppInfoViewModel(
val errorFlow = combine(plugins, snapshotFlow { selectedApp }) { pluginsList, app ->
when {
app is SelectedApp.Search && pluginsList.isEmpty() -> Error.NoPlugins
// app is SelectedApp.Search && pluginsList.isEmpty() -> Error.NoPlugins
else -> null
}
}
@@ -253,7 +251,9 @@ class SelectedAppInfoViewModel(
val allowIncompatible = prefs.disablePatchVersionCompatCheck.get()
val bundles = bundleInfoFlow.first()
return Patcher.ViewModelParams(
SelectedApp.Installed(packageName, version = "123"), // TODO
input.packageName,
resolvedVersion.first(),
resolvedSource.first(),
patchSelection.first(),
getOptionsFiltered(bundles)
)
@@ -287,13 +287,13 @@ class SelectedAppInfoViewModel(
val installedAppDeferred =
async(Dispatchers.IO) { installedAppRepository.get(packageName) }
installedAppData =
packageInfo.await()?.let {
SelectedApp.Installed(
packageName,
it.versionName!!
) to installedAppDeferred.await()
}
// installedAppData =
// packageInfo.await()?.let {
// SelectedApp.Installed(
// packageName,
// it.versionName!!
// ) to installedAppDeferred.await()
// }
}
}

View File

@@ -7,6 +7,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.data.room.apps.downloaded.DownloadedApp
import app.revanced.manager.domain.repository.DownloadedAppRepository
import app.revanced.manager.domain.repository.DownloaderPluginRepository
import app.revanced.manager.network.downloader.DownloaderPluginState
import app.revanced.manager.ui.model.SelectedSource
import app.revanced.manager.ui.model.navigation.SelectedAppInfo
import app.revanced.manager.util.PM
@@ -19,11 +21,18 @@ class SourceSelectorViewModel(
val input: SelectedAppInfo.SourceSelector.ViewModelParams
) : ViewModel(), KoinComponent {
private val downloadedAppRepository: DownloadedAppRepository = get()
private val pluginRepository: DownloaderPluginRepository = get()
private val pm: PM = get()
val downloadedApps = downloadedAppRepository.get(input.packageName)
.map { it.sortedByDescending { app -> app.version } }
val plugins = pluginRepository.pluginStates.map { plugins ->
plugins.toList().sortedByDescending { it.second is DownloaderPluginState.Loaded }
}
fun getPackageInfo(packageName: String) = pm.getPackageInfo(packageName)
var selectedSource by mutableStateOf(input.selectedSource)
private set