diff --git a/app/src/main/java/app/revanced/manager/ui/model/SelectedSource.kt b/app/src/main/java/app/revanced/manager/ui/model/SelectedSource.kt index 8b01dd65..c91ba366 100644 --- a/app/src/main/java/app/revanced/manager/ui/model/SelectedSource.kt +++ b/app/src/main/java/app/revanced/manager/ui/model/SelectedSource.kt @@ -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 } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/screen/SelectedAppInfoScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/SelectedAppInfoScreen.kt index 7fe3991f..787231c0 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/SelectedAppInfoScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/SelectedAppInfoScreen.kt @@ -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) }, ) diff --git a/app/src/main/java/app/revanced/manager/ui/screen/SourceSelectorScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/SourceSelectorScreen.kt index a5eadc07..24d06d7a 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/SourceSelectorScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/SourceSelectorScreen.kt @@ -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( diff --git a/app/src/main/java/app/revanced/manager/ui/screen/VersionSelectorScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/VersionSelectorScreen.kt index 714c6759..aabca5ce 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/VersionSelectorScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/VersionSelectorScreen.kt @@ -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 ) } diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/SelectedAppInfoViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/SelectedAppInfoViewModel.kt index 4a56fa57..f6aee73f 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/SelectedAppInfoViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/SelectedAppInfoViewModel.kt @@ -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> { 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 } } diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/SourceSelectorViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/SourceSelectorViewModel.kt index d776dc8b..4dd45090 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/SourceSelectorViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/SourceSelectorViewModel.kt @@ -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(null) private set init { viewModelScope.launch { - installedApp = pm.getPackageInfo(input.packageName) + val packageInfo = pm.getPackageInfo(input.packageName) + + installedVersion = packageInfo?.versionName } } diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/VersionSelectorViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/VersionSelectorViewModel.kt index 5a30c755..e0db7584 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/VersionSelectorViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/VersionSelectorViewModel.kt @@ -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(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