Add local APK to version and source selector

This commit is contained in:
Robert
2026-01-08 15:30:16 +01:00
parent 9fc2b4fdef
commit b28e9a15be
10 changed files with 200 additions and 82 deletions

View File

@@ -156,11 +156,11 @@ private fun ReVancedManager() {
SelectedAppInfo.ViewModelParams(packageName) SelectedAppInfo.ViewModelParams(packageName)
) )
}, },
onStorageSelect = { packageName, localFile -> onStorageSelect = { packageName, localPath ->
navController.navigateComplex( navController.navigateComplex(
SelectedAppInfo, SelectedAppInfo,
SelectedAppInfo.ViewModelParams( SelectedAppInfo.ViewModelParams(
packageName, localFile packageName, localPath
) )
) )
}, },
@@ -233,23 +233,25 @@ private fun ReVancedManager() {
) )
) )
}, },
onVersionClick = { packageName, patchSelection, selectedVersion -> onVersionClick = { packageName, patchSelection, selectedVersion, local ->
navController.navigateComplex( navController.navigateComplex(
SelectedAppInfo.VersionSelector, SelectedAppInfo.VersionSelector,
SelectedAppInfo.VersionSelector.ViewModelParams( SelectedAppInfo.VersionSelector.ViewModelParams(
packageName, packageName,
patchSelection, patchSelection,
selectedVersion, selectedVersion,
local,
) )
) )
}, },
onSourceClick = { packageName, version, selectedSource -> onSourceClick = { packageName, version, selectedSource, local ->
navController.navigateComplex( navController.navigateComplex(
SelectedAppInfo.SourceSelector, SelectedAppInfo.SourceSelector,
SelectedAppInfo.SourceSelector.ViewModelParams( SelectedAppInfo.SourceSelector.ViewModelParams(
packageName, packageName,
version, version,
selectedSource, selectedSource,
local,
) )
) )
}, },

View File

@@ -8,7 +8,6 @@ import app.revanced.manager.util.PatchSelection
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.RawValue import kotlinx.parcelize.RawValue
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.io.File
interface ComplexParameter<T : Parcelable> interface ComplexParameter<T : Parcelable>
@@ -29,7 +28,7 @@ data object SelectedAppInfo : ComplexParameter<SelectedAppInfo.ViewModelParams>
@Parcelize @Parcelize
data class ViewModelParams( data class ViewModelParams(
val packageName: String, val packageName: String,
val localFile: File? = null, val localPath: String? = null,
val patches: PatchSelection? = null val patches: PatchSelection? = null
) : Parcelable ) : Parcelable
@@ -54,6 +53,7 @@ data object SelectedAppInfo : ComplexParameter<SelectedAppInfo.ViewModelParams>
val packageName: String, val packageName: String,
val patchSelection: PatchSelection, val patchSelection: PatchSelection,
val selectedVersion: SelectedVersion, val selectedVersion: SelectedVersion,
val localPath: String? = null,
) : Parcelable ) : Parcelable
} }
@@ -64,6 +64,7 @@ data object SelectedAppInfo : ComplexParameter<SelectedAppInfo.ViewModelParams>
val packageName: String, val packageName: String,
val version: String?, val version: String?,
val selectedSource: SelectedSource, val selectedSource: SelectedSource,
val localPath: String? = null,
) : Parcelable ) : Parcelable
} }

View File

@@ -49,13 +49,12 @@ import app.revanced.manager.util.APK_MIMETYPE
import app.revanced.manager.util.EventEffect import app.revanced.manager.util.EventEffect
import app.revanced.manager.util.transparentListItemColors import app.revanced.manager.util.transparentListItemColors
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import java.io.File
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AppSelectorScreen( fun AppSelectorScreen(
onSelect: (packageName: String) -> Unit, onSelect: (packageName: String) -> Unit,
onStorageSelect: (packageName: String, File) -> Unit, onStorageSelect: (packageName: String, path: String) -> Unit,
onBackClick: () -> Unit, onBackClick: () -> Unit,
vm: AppSelectorViewModel = koinViewModel() vm: AppSelectorViewModel = koinViewModel()
) { ) {

View File

@@ -53,8 +53,8 @@ fun SelectedAppInfoScreen(
onPatchSelectorClick: (packageName: String, version: String?, PatchSelection?, Options) -> Unit, onPatchSelectorClick: (packageName: String, version: String?, PatchSelection?, Options) -> Unit,
onRequiredOptions: (packageName: String, version: String?, PatchSelection?, Options) -> Unit, onRequiredOptions: (packageName: String, version: String?, PatchSelection?, Options) -> Unit,
onPatchClick: () -> Unit, onPatchClick: () -> Unit,
onVersionClick: (packageName: String, patchSelection: PatchSelection, selectedVersion: SelectedVersion) -> Unit, onVersionClick: (packageName: String, patchSelection: PatchSelection, selectedVersion: SelectedVersion, localPath: String?) -> Unit,
onSourceClick: (packageName: String, version: String?, SelectedSource) -> Unit, onSourceClick: (packageName: String, version: String?, SelectedSource, localPath: String?) -> Unit,
onBackClick: () -> Unit, onBackClick: () -> Unit,
vm: SelectedAppInfoViewModel vm: SelectedAppInfoViewModel
) { ) {
@@ -173,6 +173,7 @@ fun SelectedAppInfoScreen(
packageName, packageName,
fullPatchSelection, fullPatchSelection,
selectedVersion, selectedVersion,
vm.localPath,
) )
}, },
) )
@@ -193,7 +194,12 @@ fun SelectedAppInfoScreen(
PageItem( PageItem(
R.string.apk_source_selector_item, R.string.apk_source_selector_item,
sourceDescription, sourceDescription,
onClick = { onSourceClick(packageName, resolvedVersion, selectedSource) }, onClick = { onSourceClick(
packageName,
resolvedVersion,
selectedSource,
vm.localPath,
) },
) )
error?.let { error?.let {

View File

@@ -1,7 +1,6 @@
package app.revanced.manager.ui.screen package app.revanced.manager.ui.screen
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Save import androidx.compose.material.icons.outlined.Save
@@ -14,13 +13,13 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.network.downloader.DownloaderPluginState import app.revanced.manager.R
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.model.SelectedSource import app.revanced.manager.ui.model.SelectedSource
import app.revanced.manager.ui.viewmodel.SourceSelectorViewModel import app.revanced.manager.ui.viewmodel.SourceSelectorViewModel
@@ -34,14 +33,9 @@ fun SourceSelectorScreen(
onSave: (source: SelectedSource) -> Unit, onSave: (source: SelectedSource) -> Unit,
viewModel: SourceSelectorViewModel, viewModel: SourceSelectorViewModel,
) { ) {
val context = LocalContext.current
val downloadedApps by viewModel.downloadedApps.collectAsStateWithLifecycle(emptyList()) val downloadedApps by viewModel.downloadedApps.collectAsStateWithLifecycle(emptyList())
val plugins by viewModel.plugins.collectAsStateWithLifecycle(emptyList()) val plugins by viewModel.plugins.collectAsStateWithLifecycle(emptyList())
val version = viewModel.input.version
fun matchesVersion(appVersion: String) = version == null || version == appVersion
Scaffold( Scaffold(
topBar = { topBar = {
AppTopBar( AppTopBar(
@@ -51,13 +45,13 @@ fun SourceSelectorScreen(
}, },
floatingActionButton = { floatingActionButton = {
HapticExtendedFloatingActionButton( HapticExtendedFloatingActionButton(
text = { Text("Save") }, text = { Text(stringResource(R.string.save)) },
icon = { Icon(Icons.Outlined.Save, null) }, icon = { Icon(Icons.Outlined.Save, null) },
onClick = { onSave(viewModel.selectedSource) }, onClick = { onSave(viewModel.selectedSource) },
) )
} }
) { paddingValues -> ) { paddingValues ->
LazyColumn ( LazyColumnWithScrollbar (
contentPadding = paddingValues, contentPadding = paddingValues,
) { ) {
item { item {
@@ -76,58 +70,63 @@ fun SourceSelectorScreen(
) )
} }
viewModel.installedVersion?.let { installedVersion -> viewModel.localApp?.let { option ->
item { item {
HorizontalDivider() HorizontalDivider()
SourceOption( SourceOption(
isSelected = viewModel.selectedSource == SelectedSource.Installed, sourceOption = option,
onSelect = { viewModel.selectSource(SelectedSource.Installed) }, isSelected = viewModel.selectedSource == option.source,
headlineContent = { Text(installedVersion) }, onSelect = viewModel::selectSource,
overlineContent = { Text("Installed") }, )
enabled = matchesVersion(installedVersion) }
}
viewModel.installedSource?.let { option ->
item {
HorizontalDivider()
SourceOption(
sourceOption = option,
isSelected = viewModel.selectedSource == option.source,
onSelect = viewModel::selectSource,
) )
} }
} }
if (downloadedApps.isNotEmpty()) item { HorizontalDivider() } if (downloadedApps.isNotEmpty()) item { HorizontalDivider() }
items(downloadedApps, key = { it.key }) { option ->
items(downloadedApps, key = { it.version }) { app ->
SourceOption( SourceOption(
isSelected = (viewModel.selectedSource as? SelectedSource.Downloaded)?.version == app.version, sourceOption = option,
onSelect = { viewModel.selectDownloadedApp(app) }, isSelected = viewModel.selectedSource == option.source,
headlineContent = { Text(app.version) }, onSelect = viewModel::selectSource,
overlineContent = { Text("Downloaded") },
enabled = matchesVersion(app.version)
) )
} }
if (plugins.isNotEmpty()) item { HorizontalDivider() } if (plugins.isNotEmpty()) item { HorizontalDivider() }
items(plugins, key = { it.key }) { option ->
items(plugins, key = { it.first }) {
val packageInfo = remember {
viewModel.getPackageInfo(it.first)
}
val label = remember {
packageInfo?.applicationInfo?.loadLabel(context.packageManager).toString()
}
SourceOption( SourceOption(
isSelected = viewModel.selectedSource == SelectedSource.Plugin(it.first), sourceOption = option,
onSelect = { viewModel.selectSource(SelectedSource.Plugin(it.first)) }, isSelected = viewModel.selectedSource == option.source,
headlineContent = { Text(label, maxLines = 1, overflow = TextOverflow.Ellipsis) }, onSelect = viewModel::selectSource,
overlineContent = { Text("Plugin") },
enabled = it.second is DownloaderPluginState.Loaded,
supportingContent = (it.second as? DownloaderPluginState.Untrusted)?.let { {
Text("Not trusted")
} }
) )
} }
} }
} }
} }
@Composable
private fun SourceOption(
sourceOption: SourceSelectorViewModel.SourceOption,
isSelected: Boolean,
onSelect: (SelectedSource) -> Unit,
) = SourceOption(
isSelected = isSelected,
onSelect = { onSelect(sourceOption.source) },
overlineContent = sourceOption.category?.let {{ Text(it) }},
headlineContent = { Text(sourceOption.title, maxLines = 1, overflow = TextOverflow.Ellipsis) },
supportingContent = sourceOption.disableReason?.let {{ Text(it.message) }},
enabled = sourceOption.disableReason == null,
)
@Composable @Composable
private fun SourceOption( private fun SourceOption(
isSelected: Boolean, isSelected: Boolean,

View File

@@ -37,6 +37,7 @@ fun VersionSelectorScreen(
) { ) {
val versions by viewModel.availableVersions.collectAsStateWithLifecycle(emptyList()) val versions by viewModel.availableVersions.collectAsStateWithLifecycle(emptyList())
val downloadedVersions by viewModel.downloadedVersions.collectAsStateWithLifecycle(emptyList()) val downloadedVersions by viewModel.downloadedVersions.collectAsStateWithLifecycle(emptyList())
val localVersion by viewModel.localVersion.collectAsStateWithLifecycle(null)
Scaffold( Scaffold(
topBar = { topBar = {
@@ -75,8 +76,10 @@ fun VersionSelectorScreen(
items(versions, key = { it.first.version }) { version -> items(versions, key = { it.first.version }) { version ->
val isDownloaded = downloadedVersions.contains(version.first.version) val isDownloaded = downloadedVersions.contains(version.first.version)
val isInstalled = viewModel.installedAppVersion == version.first.version val isInstalled = viewModel.installedAppVersion == version.first.version
val isLocal = localVersion == version.first.version
val overlineText = when { val overlineText = when {
isLocal -> "Local"
isDownloaded && isInstalled -> "Downloaded, Installed" isDownloaded && isInstalled -> "Downloaded, Installed"
isDownloaded -> "Downloaded" isDownloaded -> "Downloaded"
isInstalled -> "Installed" isInstalled -> "Installed"

View File

@@ -38,7 +38,7 @@ class AppSelectorViewModel(
} }
val appList = pm.appList val appList = pm.appList
private val storageSelectionChannel = Channel<Pair<String, File>>() private val storageSelectionChannel = Channel<Pair<String, String>>()
val storageSelectionFlow = storageSelectionChannel.receiveAsFlow() val storageSelectionFlow = storageSelectionChannel.receiveAsFlow()
val suggestedAppVersions = patchBundleRepository.suggestedVersions.flowOn(Dispatchers.Default) val suggestedAppVersions = patchBundleRepository.suggestedVersions.flowOn(Dispatchers.Default)
@@ -73,7 +73,7 @@ class AppSelectorViewModel(
Files.copy(stream, toPath()) Files.copy(stream, toPath())
pm.getPackageInfo(this)?.let { packageInfo -> pm.getPackageInfo(this)?.let { packageInfo ->
Pair(packageInfo.packageName, this) Pair(packageInfo.packageName, path)
} }
} }
} }

View File

@@ -7,7 +7,6 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@@ -47,6 +46,7 @@ import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.get import org.koin.core.component.get
import java.io.File
@OptIn(SavedStateHandleSaveableApi::class, PluginHostApi::class) @OptIn(SavedStateHandleSaveableApi::class, PluginHostApi::class)
class SelectedAppInfoViewModel( class SelectedAppInfoViewModel(
@@ -63,6 +63,7 @@ class SelectedAppInfoViewModel(
private val prefs: PreferencesManager = get() private val prefs: PreferencesManager = get()
val plugins = pluginsRepository.loadedPluginsFlow val plugins = pluginsRepository.loadedPluginsFlow
val packageName = input.packageName val packageName = input.packageName
val localPath = input.localPath
private val persistConfiguration = input.patches == null private val persistConfiguration = input.patches == null
@@ -261,6 +262,17 @@ class SelectedAppInfoViewModel(
init { init {
invalidateSelectedAppInfo() invalidateSelectedAppInfo()
input.localPath?.let { local ->
viewModelScope.launch {
val packageInfo = pm.getPackageInfo(File(local))
_selectedVersion.value = SelectedVersion.Specific(
packageInfo?.versionName ?: return@launch
)
_selectedSource.value = SelectedSource.Local(local)
}
}
// Get the previous selection if customization is enabled. // Get the previous selection if customization is enabled.
viewModelScope.launch { viewModelScope.launch {
if (prefs.disableSelectionWarning.get()) { if (prefs.disableSelectionWarning.get()) {

View File

@@ -1,13 +1,14 @@
package app.revanced.manager.ui.viewmodel package app.revanced.manager.ui.viewmodel
import android.app.Application
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope 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.DownloadedAppRepository
import app.revanced.manager.domain.repository.DownloaderPluginRepository import app.revanced.manager.domain.repository.DownloaderPluginRepository
import app.revanced.manager.domain.repository.InstalledAppRepository
import app.revanced.manager.network.downloader.DownloaderPluginState import app.revanced.manager.network.downloader.DownloaderPluginState
import app.revanced.manager.ui.model.SelectedSource import app.revanced.manager.ui.model.SelectedSource
import app.revanced.manager.ui.model.navigation.SelectedAppInfo import app.revanced.manager.ui.model.navigation.SelectedAppInfo
@@ -16,23 +17,17 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.get import org.koin.core.component.get
import java.io.File
class SourceSelectorViewModel( class SourceSelectorViewModel(
val input: SelectedAppInfo.SourceSelector.ViewModelParams val input: SelectedAppInfo.SourceSelector.ViewModelParams
) : ViewModel(), KoinComponent { ) : ViewModel(), KoinComponent {
private val app: Application = get()
private val downloadedAppRepository: DownloadedAppRepository = get() private val downloadedAppRepository: DownloadedAppRepository = get()
private val pluginRepository: DownloaderPluginRepository = get() private val pluginRepository: DownloaderPluginRepository = get()
private val installedAppRepository: InstalledAppRepository = get()
private val pm: PM = 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) var selectedSource by mutableStateOf(input.selectedSource)
private set private set
@@ -40,21 +35,102 @@ class SourceSelectorViewModel(
selectedSource = source selectedSource = source
} }
fun selectDownloadedApp(app: DownloadedApp) { var localApp by mutableStateOf<SourceOption?>(null)
val file = downloadedAppRepository.getApkFileForApp(app) private set
selectedSource = SelectedSource.Downloaded(file.path, app.version) val downloadedApps = downloadedAppRepository.get(input.packageName)
.map { apps ->
apps.sortedByDescending { app -> app.version }
.map {
SourceOption(
source = SelectedSource.Downloaded(
path = downloadedAppRepository.getApkFileForApp(it).path,
version = it.version
),
title = it.version,
category = "Downloaded",
key = it.version,
disableReason = if (input.version != null && it.version != input.version) {
DisableReason.VERSION_NOT_MATCHING
} else null
)
}
} }
var installedVersion by mutableStateOf<String?>(null) val plugins = pluginRepository.pluginStates.map { plugins ->
plugins.toList().sortedByDescending { it.second is DownloaderPluginState.Loaded }
.map {
val packageInfo = pm.getPackageInfo(it.first)
val label = packageInfo?.applicationInfo?.loadLabel(app.packageManager)
?.toString()
SourceOption(
source = SelectedSource.Plugin(it.first),
title = label ?: it.first,
category = "Plugin",
key = it.first,
disableReason = when (it.second) {
is DownloaderPluginState.Loaded -> null
is DownloaderPluginState.Untrusted -> DisableReason.NOT_TRUSTED
is DownloaderPluginState.Failed -> DisableReason.FAILED_TO_LOAD
}
)
}
}
fun getPackageInfo(packageName: String) = pm.getPackageInfo(packageName)
var installedSource by mutableStateOf<SourceOption?>(null)
private set private set
init { init {
viewModelScope.launch { viewModelScope.launch {
val packageInfo = pm.getPackageInfo(input.packageName) val packageInfo = pm.getPackageInfo(input.packageName) ?: return@launch
installedVersion = packageInfo?.versionName val installedApp = installedAppRepository.get(input.packageName)
installedSource = SourceOption(
source = SelectedSource.Installed,
title = packageInfo.versionName.toString(),
category = "Installed",
key = input.packageName,
disableReason = when {
installedApp != null -> DisableReason.ALREADY_PATCHED
input.version != null && packageInfo.versionName != input.version -> DisableReason.VERSION_NOT_MATCHING
else -> null
}
)
}
input.localPath?.let { local ->
viewModelScope.launch {
val packageInfo = pm.getPackageInfo(File(local))
?: return@launch
localApp = SourceOption(
source = SelectedSource.Local(local),
title = packageInfo.versionName.toString(),
category = "Local",
key = "local",
disableReason = if (input.version != null && packageInfo.versionName != input.version) {
DisableReason.VERSION_NOT_MATCHING
} else null
)
}
} }
} }
enum class DisableReason(val message: String) {
VERSION_NOT_MATCHING("Does not match the selected version"),
ALREADY_PATCHED("Already patched"),
NOT_TRUSTED("Not trusted"),
FAILED_TO_LOAD("Failed to load"),
}
data class SourceOption(
val source: SelectedSource,
val title: String,
val category: String? = null,
val key: String,
val disableReason: DisableReason? = null
)
} }

View File

@@ -12,10 +12,14 @@ import app.revanced.manager.ui.model.SelectedVersion
import app.revanced.manager.ui.model.navigation.SelectedAppInfo import app.revanced.manager.ui.model.navigation.SelectedAppInfo
import app.revanced.manager.util.PM import app.revanced.manager.util.PM
import app.revanced.manager.util.patchCount import app.revanced.manager.util.patchCount
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.get import org.koin.core.component.get
import java.io.File
class VersionSelectorViewModel( class VersionSelectorViewModel(
val input: SelectedAppInfo.VersionSelector.ViewModelParams val input: SelectedAppInfo.VersionSelector.ViewModelParams
@@ -32,9 +36,19 @@ class VersionSelectorViewModel(
apps.map { it.version } apps.map { it.version }
} }
val availableVersions = patchBundleRepository.suggestedVersions(input.packageName, input.patchSelection) private val _localVersion = MutableStateFlow<String?>(null)
.map { versions -> val localVersion: StateFlow<String?> = _localVersion
val availableVersions = combine(
patchBundleRepository.suggestedVersions(input.packageName, input.patchSelection),
_localVersion,
) { versions, local ->
versions.orEmpty() versions.orEmpty()
.let { versions ->
local?.let {
versions.toMutableMap().also { it.putIfAbsent(local, 0) }
} ?: versions
}
.map { (key, value) -> SelectedVersion.Specific(key) to patchCount - value } .map { (key, value) -> SelectedVersion.Specific(key) to patchCount - value }
.sortedWith( .sortedWith(
compareBy<Pair<SelectedVersion.Specific, Int>>{ it.second } compareBy<Pair<SelectedVersion.Specific, Int>>{ it.second }
@@ -54,6 +68,12 @@ class VersionSelectorViewModel(
installedAppVersion = currentApp?.versionName installedAppVersion = currentApp?.versionName
} }
input.localPath?.let { local ->
viewModelScope.launch {
val packageInfo = pm.getPackageInfo(File(local))
_localVersion.value = packageInfo?.versionName
}
}
} }
var selectedVersion by mutableStateOf(input.selectedVersion) var selectedVersion by mutableStateOf(input.selectedVersion)