mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2026-01-11 05:36:17 +00:00
Add local APK to version and source selector
This commit is contained in:
@@ -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,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user