Show selected source in overview

This commit is contained in:
Robert
2026-01-03 20:56:45 +01:00
parent af8f2afa36
commit c53d0462d6
7 changed files with 138 additions and 55 deletions

View File

@@ -7,6 +7,6 @@ import kotlinx.parcelize.Parcelize
sealed class SelectedSource : Parcelable {
data object Auto : SelectedSource()
data object Installed : SelectedSource()
data class Downloaded(val path: String) : SelectedSource()
data class Downloaded(val path: String, val version: String) : SelectedSource()
data class Plugin(val plugin: String) : SelectedSource() // TODO
}

View File

@@ -77,6 +77,8 @@ fun SelectedAppInfoScreen(
val fullPatchSelection by vm.patchSelection.collectAsStateWithLifecycle(emptyMap())
val patchCount = fullPatchSelection.patchCount
val incompatibleCount by vm.incompatiblePatchCount.collectAsStateWithLifecycle(0)
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val plugins by vm.plugins.collectAsStateWithLifecycle(emptyList())
@@ -151,6 +153,15 @@ fun SelectedAppInfoScreen(
}
)
if (incompatibleCount > 0) {
Text(
"$incompatibleCount incompatible",
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(horizontal = 16.dp)
)
}
val versionText = resolvedVersion ?: "Any available version"
val versionDescription = if (selectedVersion is SelectedVersion.Auto)
"Auto ($versionText)" // stringResource(R.string.selected_app_meta_auto_version, actualVersion)
@@ -170,9 +181,7 @@ fun SelectedAppInfoScreen(
PageItem(
R.string.apk_source_selector_item,
when (selectedSource) {
else -> "Sourcing the source"
},
selectedSource.toString(),
onClick = { onSourceClick(packageName, versionText, selectedSource) },
)

View File

@@ -3,6 +3,8 @@ 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
import androidx.compose.material.icons.outlined.Save
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -13,7 +15,9 @@ import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.model.SelectedSource
@@ -28,6 +32,8 @@ fun SourceSelectorScreen(
onSave: (source: SelectedSource) -> Unit,
viewModel: SourceSelectorViewModel,
) {
val downloadedApps by viewModel.downloadedApps.collectAsStateWithLifecycle(emptyList())
Scaffold(
topBar = {
AppTopBar(
@@ -47,56 +53,71 @@ fun SourceSelectorScreen(
modifier = Modifier.padding(paddingValues)
) {
SourceOption(
source = SelectedSource.Auto,
isSelected = viewModel.selectedSource == SelectedSource.Auto,
onSelect = viewModel::selectSource,
onSelect = { viewModel.selectSource(SelectedSource.Auto) },
headlineContent = { Text("Auto (Recommended)") },
supportingContent = { Text("Automatically select the best available source") }
)
HorizontalDivider()
SourceOption(
source = SelectedSource.Installed,
isSelected = viewModel.selectedSource == SelectedSource.Installed,
onSelect = viewModel::selectSource,
headlineContent = { Text("20.14.43") },
supportingContent = { Text("Split APK's are not supported") },
overlineContent = { Text("Installed") },
enabled = false,
isSelected = viewModel.selectedSource == SelectedSource.Plugin("any"),
onSelect = { viewModel.selectSource(SelectedSource.Plugin("any")) },
headlineContent = { Text("Any available downloader") },
)
HorizontalDivider()
viewModel.installedVersion?.let { installedVersion ->
HorizontalDivider()
SourceOption(
source = SelectedSource.Downloaded("path"),
isSelected = viewModel.selectedSource == SelectedSource.Downloaded("path"),
onSelect = viewModel::selectSource,
headlineContent = { Text("20.14.43") },
// supportingContent = { Text("") },
overlineContent = { Text("Downloaded") },
SourceOption(
isSelected = viewModel.selectedSource == SelectedSource.Installed,
onSelect = { viewModel.selectSource(SelectedSource.Installed) },
headlineContent = { Text(installedVersion) },
overlineContent = { Text("Installed") },
enabled = viewModel.input.version?.let { it == installedVersion } ?: true
)
}
)
if (downloadedApps.isNotEmpty()) {
HorizontalDivider()
LazyColumn {
items(downloadedApps, key = { it.version }) { app ->
SourceOption(
isSelected = (viewModel.selectedSource as? SelectedSource.Downloaded)?.version == app.version,
onSelect = { viewModel.selectDownloadedApp(app) },
headlineContent = { Text(app.version) },
overlineContent = { Text("Downloaded") },
)
}
}
}
HorizontalDivider()
SourceOption(
source = SelectedSource.Plugin("plugin-id"),
isSelected = viewModel.selectedSource == SelectedSource.Plugin("plugin-id"),
onSelect = viewModel::selectSource,
onSelect = { viewModel.selectSource(SelectedSource.Plugin("plugin-id")) },
headlineContent = { Text("APKMirror Downloader") },
overlineContent = { Text("Plugin") },
)
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,
)
}
}
}
@Composable
private fun SourceOption(
source: SelectedSource,
isSelected: Boolean,
onSelect: (SelectedSource) -> Unit,
onSelect: () -> Unit,
headlineContent: @Composable (() -> Unit),
supportingContent: @Composable (() -> Unit)? = null,
overlineContent: @Composable (() -> Unit)? = null,
@@ -104,7 +125,7 @@ private fun SourceOption(
) {
ListItem(
modifier = Modifier
.clickable(enabled) { onSelect(source) }
.clickable(enabled) { onSelect() }
.enabled(enabled),
leadingContent = {
RadioButton(

View File

@@ -36,6 +36,7 @@ fun VersionSelectorScreen(
viewModel: VersionSelectorViewModel,
) {
val versions by viewModel.availableVersions.collectAsStateWithLifecycle(emptyList())
val downloadedVersions by viewModel.downloadedVersions.collectAsStateWithLifecycle(emptyList())
Scaffold(
topBar = {
@@ -64,24 +65,35 @@ fun VersionSelectorScreen(
version = SelectedVersion.Auto,
isSelected = viewModel.selectedVersion is SelectedVersion.Auto,
onSelect = viewModel::selectVersion,
title = { Text("Auto (Recommended)") },
description = { Text("Automatically select the best available version") }
headlineContent = { Text("Auto (Recommended)") },
supportingContent = { Text("Automatically select the best available version") }
)
HorizontalDivider()
if (versions.isNotEmpty()) {
LazyColumn {
items(versions, key = { it.first.version }) { version ->
val isDownloaded = downloadedVersions.contains(version.first.version)
val isInstalled = viewModel.installedAppVersion == version.first.version
val overlineText = when {
isDownloaded && isInstalled -> "Downloaded, Installed"
isDownloaded -> "Downloaded"
isInstalled -> "Installed"
else -> null
}
VersionOption(
version = version.first,
isSelected = viewModel.selectedVersion == version.first,
onSelect = viewModel::selectVersion,
title = { Text(version.first.version) },
description = {
headlineContent = { Text(version.first.version) },
supportingContent = {
Text(
"${version.second.let { if (it == 0) "No" else it }} incompatible patches"
)
}
},
overlineContent = overlineText?.let { { Text(it) } }
)
}
}
@@ -90,8 +102,8 @@ fun VersionSelectorScreen(
version = SelectedVersion.Any,
isSelected = viewModel.selectedVersion is SelectedVersion.Any,
onSelect = viewModel::selectVersion,
title = { Text("Any available version") },
description = { Text("Use any available version regardless of compatibility") }
headlineContent = { Text("Any available version") },
supportingContent = { Text("Use any available version regardless of compatibility") }
)
}
}
@@ -103,8 +115,9 @@ private fun VersionOption(
version: SelectedVersion,
isSelected: Boolean,
onSelect: (SelectedVersion) -> Unit,
title: @Composable (() -> Unit),
description: @Composable (() -> Unit)? = null,
headlineContent: @Composable (() -> Unit),
supportingContent: @Composable (() -> Unit)? = null,
overlineContent: @Composable (() -> Unit)? = null,
) {
ListItem(
modifier = Modifier
@@ -115,8 +128,9 @@ private fun VersionOption(
onClick = null
)
},
headlineContent = title,
supportingContent = description,
headlineContent = headlineContent,
supportingContent = supportingContent,
trailingContent = overlineContent,
colors = transparentListItemColors
)
}

View File

@@ -146,9 +146,21 @@ class SelectedAppInfoViewModel(
when (selected) {
is SelectedVersion.Specific -> selected.version
is SelectedVersion.Any -> null
is SelectedVersion.Auto -> {
mostCompatible?.maxByOrNull { it.value }?.key
}
is SelectedVersion.Auto -> mostCompatible?.maxWithOrNull(
compareBy<Map.Entry<String, Int>> { it.value }
.thenBy { it.key }
)?.key
}
}
@OptIn(ExperimentalCoroutinesApi::class)
val scopedBundles = resolvedVersion.flatMapLatest { version ->
bundleRepository.scopedBundleInfoFlow(packageName, version)
}
val incompatiblePatchCount = scopedBundles.map { bundles ->
bundles.sumOf { bundle ->
bundle.incompatible.size
}
}

View File

@@ -1,15 +1,16 @@
package app.revanced.manager.ui.viewmodel
import android.content.pm.PackageInfo
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
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.ui.model.SelectedSource
import app.revanced.manager.ui.model.navigation.SelectedAppInfo
import app.revanced.manager.util.PM
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
@@ -20,14 +21,8 @@ class SourceSelectorViewModel(
private val downloadedAppRepository: DownloadedAppRepository = get()
private val pm: PM = get()
// val downloadedApps = downloadedAppRepository.get(input.packageName)
// .map {
// it.map {
// SelectedSource.Downloaded(
//
// )
// }.sortedByDescending { app -> app.version }
// }
val downloadedApps = downloadedAppRepository.get(input.packageName)
.map { it.sortedByDescending { app -> app.version } }
var selectedSource by mutableStateOf(input.selectedSource)
private set
@@ -36,14 +31,20 @@ class SourceSelectorViewModel(
selectedSource = source
}
fun selectDownloadedApp(app: DownloadedApp) {
val file = downloadedAppRepository.getApkFileForApp(app)
selectedSource = SelectedSource.Downloaded(file.path, app.version)
}
var installedApp: PackageInfo? = null
var installedVersion by mutableStateOf<String?>(null)
private set
init {
viewModelScope.launch {
installedApp = pm.getPackageInfo(input.packageName)
val packageInfo = pm.getPackageInfo(input.packageName)
installedVersion = packageInfo?.versionName
}
}

View File

@@ -4,21 +4,34 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.domain.repository.DownloadedAppRepository
import app.revanced.manager.domain.repository.InstalledAppRepository
import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.ui.model.SelectedVersion
import app.revanced.manager.ui.model.navigation.SelectedAppInfo
import app.revanced.manager.util.PM
import app.revanced.manager.util.patchCount
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
class VersionSelectorViewModel(
val input: SelectedAppInfo.VersionSelector.ViewModelParams
) : ViewModel(), KoinComponent {
val patchBundleRepository: PatchBundleRepository = get()
private val patchBundleRepository: PatchBundleRepository = get()
private val downloadedAppsRepository: DownloadedAppRepository = get()
private val installedAppRepository: InstalledAppRepository = get()
private val pm: PM = get()
val patchCount = input.patchSelection.patchCount
val downloadedVersions = downloadedAppsRepository.get(input.packageName)
.map { apps ->
apps.map { it.version }
}
val availableVersions = patchBundleRepository.suggestedVersions(input.packageName, input.patchSelection)
.map { versions ->
versions.orEmpty()
@@ -29,6 +42,19 @@ class VersionSelectorViewModel(
)
}
var installedAppVersion by mutableStateOf<String?>(null)
init {
viewModelScope.launch {
val currentApp = pm.getPackageInfo(input.packageName)
val patchedApp = installedAppRepository.get(input.packageName)
// Skip if installed app is patched
if (patchedApp?.currentPackageName == input.packageName) return@launch
installedAppVersion = currentApp?.versionName
}
}
var selectedVersion by mutableStateOf(input.selectedVersion)
private set