finish UI stuff (i think)

This commit is contained in:
Ax333l 2024-10-19 23:44:44 +02:00
parent 38fe7bf9fd
commit 11a2a140e6
No known key found for this signature in database
GPG Key ID: D2B4D85271127D23
6 changed files with 314 additions and 142 deletions

View File

@ -167,7 +167,7 @@ class PatcherWorker(
val inputFile = when (val selectedApp = args.input) { val inputFile = when (val selectedApp = args.input) {
is SelectedApp.Download -> { is SelectedApp.Download -> {
val (plugin, data) = downloaderPluginRepository.unwrapParceledData(selectedApp.app) val (plugin, data) = downloaderPluginRepository.unwrapParceledData(selectedApp.data)
download(plugin, data) download(plugin, data)
} }

View File

@ -13,7 +13,7 @@ sealed interface SelectedApp : Parcelable {
data class Download( data class Download(
override val packageName: String, override val packageName: String,
override val version: String, override val version: String,
val app: ParceledDownloaderData val data: ParceledDownloaderData
) : SelectedApp ) : SelectedApp
@Parcelize @Parcelize

View File

@ -1,6 +1,7 @@
package app.revanced.manager.ui.screen package app.revanced.manager.ui.screen
import android.content.pm.PackageInfo import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@ -16,34 +17,36 @@ import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.data.room.apps.installed.InstallType
import app.revanced.manager.data.room.apps.installed.InstalledApp
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
import app.revanced.manager.ui.component.AlertDialogExtended import app.revanced.manager.ui.component.AlertDialogExtended
import app.revanced.manager.ui.component.AppInfo import app.revanced.manager.ui.component.AppInfo
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.component.LoadingIndicator
import app.revanced.manager.ui.destination.SelectedAppInfoDestination import app.revanced.manager.ui.destination.SelectedAppInfoDestination
import app.revanced.manager.ui.model.BundleInfo.Extensions.bundleInfoFlow import app.revanced.manager.ui.model.BundleInfo.Extensions.bundleInfoFlow
import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel
import app.revanced.manager.util.EventEffect
import app.revanced.manager.util.Options import app.revanced.manager.util.Options
import app.revanced.manager.util.PatchSelection import app.revanced.manager.util.PatchSelection
import app.revanced.manager.util.enabled
import app.revanced.manager.util.toast import app.revanced.manager.util.toast
import app.revanced.manager.util.transparentListItemColors import app.revanced.manager.util.transparentListItemColors
import dev.olshevski.navigation.reimagined.AnimatedNavHost import dev.olshevski.navigation.reimagined.AnimatedNavHost
@ -54,6 +57,7 @@ import dev.olshevski.navigation.reimagined.rememberNavController
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SelectedAppInfoScreen( fun SelectedAppInfoScreen(
onPatchClick: (SelectedApp, PatchSelection, Options) -> Unit, onPatchClick: (SelectedApp, PatchSelection, Options) -> Unit,
@ -80,8 +84,12 @@ fun SelectedAppInfoScreen(
} }
} }
var showSourceSelectorDialog by rememberSaveable { val launcher = rememberLauncherForActivityResult(
mutableStateOf(false) contract = ActivityResultContracts.StartActivityForResult(),
onResult = vm::handlePluginActivityResult
)
EventEffect(flow = vm.launchActivityFlow) { intent ->
launcher.launch(intent)
} }
val navController = val navController =
@ -90,42 +98,120 @@ fun SelectedAppInfoScreen(
NavBackHandler(controller = navController) NavBackHandler(controller = navController)
AnimatedNavHost(controller = navController) { destination -> AnimatedNavHost(controller = navController) { destination ->
val error by vm.error.collectAsStateWithLifecycle(null)
when (destination) { when (destination) {
is SelectedAppInfoDestination.Main -> SelectedAppInfoScreen( is SelectedAppInfoDestination.Main -> Scaffold(
onPatchClick = patchClick@{ topBar = {
if (selectedPatchCount == 0) { AppTopBar(
context.toast(context.getString(R.string.no_patches_selected)) title = stringResource(R.string.app_info),
onBackClick = onBackClick
)
},
floatingActionButton = {
if (error != null) return@Scaffold
return@patchClick ExtendedFloatingActionButton(
} text = { Text(stringResource(R.string.patch)) },
onPatchClick( icon = {
vm.selectedApp, Icon(
patches, Icons.Default.AutoFixHigh,
vm.getOptionsFiltered(bundles) stringResource(R.string.patch)
)
},
onClick = patchClick@{
if (selectedPatchCount == 0) {
context.toast(context.getString(R.string.no_patches_selected))
return@patchClick
}
onPatchClick(
vm.selectedApp,
patches,
vm.getOptionsFiltered(bundles)
)
}
) )
}, }
onPatchSelectorClick = { ) { paddingValues ->
navController.navigate( val plugins by vm.plugins.collectAsStateWithLifecycle(emptyList())
SelectedAppInfoDestination.PatchesSelector(
vm.selectedApp, if (vm.showSourceSelector) {
vm.getCustomPatches( AppSourceSelectorDialog(
bundles, plugins = plugins,
allowIncompatiblePatches installedApp = vm.installedAppData,
), searchApp = SelectedApp.Search(
vm.options vm.packageName,
vm.desiredVersion
),
activeSearchJob = vm.activePluginAction,
hasRoot = vm.hasRoot,
onDismissRequest = vm::dismissSourceSelector,
onSelectPlugin = vm::searchInPlugin,
onSelect = {
vm.selectedApp = it
vm.dismissSourceSelector()
}
)
}
ColumnWithScrollbar(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
AppInfo(vm.selectedAppInfo, placeholderLabel = packageName) {
Text(
version ?: stringResource(R.string.selected_app_meta_any_version),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
) )
}
PageItem(
R.string.patch_selector_item,
stringResource(
R.string.patch_selector_item_description,
selectedPatchCount
),
onClick = {
navController.navigate(
SelectedAppInfoDestination.PatchesSelector(
vm.selectedApp,
vm.getCustomPatches(
bundles,
allowIncompatiblePatches
),
vm.options
)
)
}
) )
}, PageItem(
onSourceSelectorClick = { R.string.apk_source_selector_item,
showSourceSelectorDialog = true when (val app = vm.selectedApp) {
// navController.navigate(SelectedAppInfoDestination.VersionSelector) is SelectedApp.Search -> stringResource(R.string.apk_source_auto)
}, is SelectedApp.Installed -> stringResource(R.string.apk_source_installed)
onBackClick = onBackClick, is SelectedApp.Download -> stringResource(
selectedPatchCount = selectedPatchCount, R.string.apk_source_downloader,
packageName = packageName, plugins.find { it.packageName == app.data.pluginPackageName }?.name
version = version, ?: app.data.pluginPackageName
packageInfo = vm.selectedAppInfo, )
)
is SelectedApp.Local -> stringResource(R.string.apk_source_local)
},
onClick = {
vm.showSourceSelector()
}
)
error?.let {
Text(
stringResource(it.resourceId),
color = MaterialTheme.colorScheme.error,
modifier = Modifier.padding(horizontal = 24.dp)
)
}
}
}
is SelectedAppInfoDestination.PatchesSelector -> PatchesSelectorScreen( is SelectedAppInfoDestination.PatchesSelector -> PatchesSelectorScreen(
onSave = { patches, options -> onSave = { patches, options ->
@ -147,66 +233,6 @@ fun SelectedAppInfoScreen(
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SelectedAppInfoScreen(
onPatchClick: () -> Unit,
onPatchSelectorClick: () -> Unit,
onSourceSelectorClick: () -> Unit,
onBackClick: () -> Unit,
selectedPatchCount: Int,
packageName: String,
version: String?,
packageInfo: PackageInfo?,
) {
Scaffold(
topBar = {
AppTopBar(
title = stringResource(R.string.app_info),
onBackClick = onBackClick
)
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text(stringResource(R.string.patch)) },
icon = {
Icon(
Icons.Default.AutoFixHigh,
stringResource(R.string.patch)
)
},
onClick = onPatchClick
)
}
) { paddingValues ->
ColumnWithScrollbar(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
AppInfo(packageInfo, placeholderLabel = packageName) {
Text(
version ?: stringResource(R.string.selected_app_meta_any_version),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
)
}
PageItem(
R.string.patch_selector_item,
stringResource(R.string.patch_selector_item_description, selectedPatchCount),
onPatchSelectorClick
)
PageItem(
R.string.version_selector_item,
version?.let { stringResource(R.string.version_selector_item_description, it) }
?: stringResource(R.string.version_selector_item_description_auto),
onSourceSelectorClick
)
}
}
}
@Composable @Composable
private fun PageItem(@StringRes title: Int, description: String, onClick: () -> Unit) { private fun PageItem(@StringRes title: Int, description: String, onClick: () -> Unit) {
ListItem( ListItem(
@ -234,19 +260,21 @@ private fun PageItem(@StringRes title: Int, description: String, onClick: () ->
} }
@Composable @Composable
private fun AppSourceSelectorDialog(onDismissRequest: () -> Unit) { private fun AppSourceSelectorDialog(
plugins: List<LoadedDownloaderPlugin>,
installedApp: Pair<SelectedApp.Installed, InstalledApp?>?,
searchApp: SelectedApp.Search,
activeSearchJob: String?,
hasRoot: Boolean,
onDismissRequest: () -> Unit,
onSelectPlugin: (LoadedDownloaderPlugin) -> Unit,
onSelect: (SelectedApp) -> Unit,
) {
val canSelect = activeSearchJob == null
AlertDialogExtended( AlertDialogExtended(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
confirmButton = { confirmButton = {
TextButton(
onClick = {
}
) {
Text("Select")
}
},
dismissButton = {
TextButton(onClick = onDismissRequest) { TextButton(onClick = onDismissRequest) {
Text(stringResource(R.string.cancel)) Text(stringResource(R.string.cancel))
} }
@ -254,37 +282,49 @@ private fun AppSourceSelectorDialog(onDismissRequest: () -> Unit) {
title = { Text("Select source") }, title = { Text("Select source") },
textHorizontalPadding = PaddingValues(horizontal = 0.dp), textHorizontalPadding = PaddingValues(horizontal = 0.dp),
text = { text = {
/*
val presets = remember(scope.option.presets) {
scope.option.presets?.entries?.toList().orEmpty()
}
LazyColumn { LazyColumn {
@Composable item(key = "auto") {
fun Item(title: String, value: Any?, presetKey: String?) { val hasPlugins = plugins.isNotEmpty()
ListItem( ListItem(
modifier = Modifier.clickable { selectedPreset = presetKey }, modifier = Modifier
headlineContent = { Text(title) }, .clickable(enabled = canSelect && hasPlugins) { onSelect(searchApp) }
supportingContent = value?.toString()?.let { { Text(it) } }, .enabled(hasPlugins),
leadingContent = { headlineContent = { Text("Auto") },
RadioButton( supportingContent = { Text(if (hasPlugins) "Use all installed downloaders to find a suitable app." else "No plugins available") },
selected = selectedPreset == presetKey,
onClick = { selectedPreset = presetKey }
)
},
colors = transparentListItemColors colors = transparentListItemColors
) )
} }
items(presets, key = { it.key }) { installedApp?.let { (app, meta) ->
Item(it.key, it.value, it.key) item(key = "installed") {
val (usable, text) = when {
// Mounted apps must be unpatched before patching, which cannot be done without root access.
meta?.installType == InstallType.ROOT && !hasRoot -> false to "Mounted apps cannot be patched again without root access"
// Patching already patched apps is not allowed because patches expect unpatched apps.
meta?.installType == InstallType.DEFAULT -> false to stringResource(R.string.already_patched)
else -> true to app.version
}
ListItem(
modifier = Modifier
.clickable(enabled = canSelect && usable) { onSelect(app) }
.enabled(usable), // TODO: version safeguard
headlineContent = { Text(stringResource(R.string.installed)) },
supportingContent = { Text(text) },
colors = transparentListItemColors
)
}
} }
item(key = null) { items(plugins, key = { "plugin_${it.packageName}" }) { plugin ->
Item(stringResource(R.string.option_preset_custom_value), null, null) ListItem(
modifier = Modifier.clickable(enabled = canSelect) { onSelectPlugin(plugin) },
headlineContent = { Text(plugin.name) },
supportingContent = { Text("Try to find the app using ${plugin.name}") },
trailingContent = (@Composable { LoadingIndicator() }).takeIf { activeSearchJob == plugin.packageName },
colors = transparentListItemColors
)
} }
} }
*/
} }
) )
} }

View File

@ -1,46 +1,80 @@
package app.revanced.manager.ui.viewmodel package app.revanced.manager.ui.viewmodel
import android.app.Activity
import android.app.Application
import android.content.Intent
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.os.Parcelable import android.os.Parcelable
import androidx.activity.result.ActivityResult
import androidx.annotation.StringRes
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
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
import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi
import androidx.lifecycle.viewmodel.compose.saveable 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.installer.RootInstaller
import app.revanced.manager.domain.manager.PreferencesManager import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.DownloaderPluginRepository
import app.revanced.manager.domain.repository.InstalledAppRepository
import app.revanced.manager.domain.repository.PatchBundleRepository import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.domain.repository.PatchOptionsRepository import app.revanced.manager.domain.repository.PatchOptionsRepository
import app.revanced.manager.domain.repository.PatchSelectionRepository import app.revanced.manager.domain.repository.PatchSelectionRepository
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
import app.revanced.manager.network.downloader.ParceledDownloaderData
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.BundleInfo import app.revanced.manager.ui.model.BundleInfo
import app.revanced.manager.ui.model.BundleInfo.Extensions.toPatchSelection import app.revanced.manager.ui.model.BundleInfo.Extensions.toPatchSelection
import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.util.Options import app.revanced.manager.util.Options
import app.revanced.manager.util.PM import app.revanced.manager.util.PM
import app.revanced.manager.util.PatchSelection import app.revanced.manager.util.PatchSelection
import app.revanced.manager.util.toast
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext 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
@OptIn(SavedStateHandleSaveableApi::class) @OptIn(SavedStateHandleSaveableApi::class, PluginHostApi::class)
class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent { class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
private val app: Application = get()
val bundlesRepo: PatchBundleRepository = get() val bundlesRepo: PatchBundleRepository = get()
private val bundleRepository: PatchBundleRepository = get() private val bundleRepository: PatchBundleRepository = get()
private val selectionRepository: PatchSelectionRepository = get() private val selectionRepository: PatchSelectionRepository = get()
private val optionsRepository: PatchOptionsRepository = get() private val optionsRepository: PatchOptionsRepository = get()
private val pluginsRepository: DownloaderPluginRepository = get()
private val installedAppRepository: InstalledAppRepository = get()
private val rootInstaller: RootInstaller = get()
private val pm: PM = get() private val pm: PM = get()
private val savedStateHandle: SavedStateHandle = get() private val savedStateHandle: SavedStateHandle = get()
val prefs: PreferencesManager = get() val prefs: PreferencesManager = get()
val plugins = pluginsRepository.loadedPluginsFlow
val desiredVersion = input.app.version
val packageName = input.app.packageName
private val persistConfiguration = input.patches == null private val persistConfiguration = input.patches == null
val hasRoot = rootInstaller.hasRootAccess()
var installedAppData: Pair<SelectedApp.Installed, InstalledApp?>? by mutableStateOf(null)
private set
private var _selectedApp by savedStateHandle.saveable { private var _selectedApp by savedStateHandle.saveable {
mutableStateOf(input.app) mutableStateOf(input.app)
} }
@ -57,6 +91,19 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
init { init {
invalidateSelectedAppInfo() invalidateSelectedAppInfo()
viewModelScope.launch(Dispatchers.Main) {
val packageInfo = async(Dispatchers.IO) { pm.getPackageInfo(packageName) }
val installedAppDeferred =
async(Dispatchers.IO) { installedAppRepository.get(packageName) }
installedAppData =
packageInfo.await()?.let {
SelectedApp.Installed(
packageName,
it.versionName
) to installedAppDeferred.await()
}
}
} }
var options: Options by savedStateHandle.saveable { var options: Options by savedStateHandle.saveable {
@ -65,9 +112,6 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
viewModelScope.launch { viewModelScope.launch {
if (!persistConfiguration) return@launch // TODO: save options for patched apps. if (!persistConfiguration) return@launch // TODO: save options for patched apps.
// Accessing this from another thread may cause crashes.
val packageName = selectedApp.packageName
state.value = withContext(Dispatchers.Default) { state.value = withContext(Dispatchers.Default) {
val bundlePatches = bundleRepository.bundles.first() val bundlePatches = bundleRepository.bundles.first()
.mapValues { (_, bundle) -> bundle.patches.associateBy { it.name } } .mapValues { (_, bundle) -> bundle.patches.associateBy { it.name } }
@ -90,7 +134,7 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
viewModelScope.launch { viewModelScope.launch {
if (!prefs.disableSelectionWarning.get()) return@launch if (!prefs.disableSelectionWarning.get()) return@launch
val previous = selectionRepository.getSelection(selectedApp.packageName) val previous = selectionRepository.getSelection(packageName)
if (previous.values.sumOf { it.size } == 0) return@launch if (previous.values.sumOf { it.size } == 0) return@launch
selection.value = SelectionState.Customized(previous) selection.value = SelectionState.Customized(previous)
} }
@ -98,6 +142,86 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
selection selection
} }
var showSourceSelector by mutableStateOf(false)
private set
private var pluginAction: Pair<LoadedDownloaderPlugin, Job>? by mutableStateOf(null)
val activePluginAction get() = pluginAction?.first?.packageName
private var launchedActivity by mutableStateOf<CompletableDeferred<ActivityResult>?>(null)
private val launchActivityChannel = Channel<Intent>()
val launchActivityFlow = launchActivityChannel.receiveAsFlow()
val error = combine(plugins, snapshotFlow { selectedApp }) { pluginsList, app ->
when {
app is SelectedApp.Search && pluginsList.isEmpty() -> Error.NoPlugins
else -> null
}
}
fun showSourceSelector() {
dismissSourceSelector()
showSourceSelector = true
}
fun dismissSourceSelector() {
pluginAction?.second?.cancel()
pluginAction = null
showSourceSelector = false
}
fun searchInPlugin(plugin: LoadedDownloaderPlugin) {
pluginAction?.second?.cancel()
pluginAction = null
pluginAction = plugin to viewModelScope.launch {
try {
val scope = object : GetScope {
override suspend fun requestStartActivity(intent: Intent) =
withContext(Dispatchers.Main) {
if (launchedActivity != null) error("Previous activity has not finished")
try {
val result = with(CompletableDeferred<ActivityResult>()) {
launchedActivity = this
launchActivityChannel.send(intent)
await()
}
when (result.resultCode) {
Activity.RESULT_OK -> result.data
Activity.RESULT_CANCELED -> throw UserInteractionException.Activity.Cancelled()
else -> throw UserInteractionException.Activity.NotCompleted(
result.resultCode,
result.data
)
}
} finally {
launchedActivity = null
}
}
}
withContext(Dispatchers.IO) {
plugin.get(scope, packageName, desiredVersion)
}?.let { (data, version) ->
if (desiredVersion != null && version != desiredVersion) {
app.toast("Plugin returned a package with the wrong version")
return@launch
}
selectedApp = SelectedApp.Download(
packageName,
version
?: error("Umm, I guess I need to make the parameter nullable now?"),
ParceledDownloaderData(plugin, data)
)
} ?: app.toast("App was not found")
} finally {
pluginAction = null
dismissSourceSelector()
}
}
}
fun handlePluginActivityResult(result: ActivityResult) {
launchedActivity?.complete(result)
}
private fun invalidateSelectedAppInfo() = viewModelScope.launch { private fun invalidateSelectedAppInfo() = viewModelScope.launch {
val info = when (val app = selectedApp) { val info = when (val app = selectedApp) {
is SelectedApp.Local -> withContext(Dispatchers.IO) { pm.getPackageInfo(app.file) } is SelectedApp.Local -> withContext(Dispatchers.IO) { pm.getPackageInfo(app.file) }
@ -130,8 +254,6 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
this.options = filteredOptions this.options = filteredOptions
if (!persistConfiguration) return if (!persistConfiguration) return
val packageName = selectedApp.packageName
viewModelScope.launch(Dispatchers.Default) { viewModelScope.launch(Dispatchers.Default) {
selection?.let { selectionRepository.updateSelection(packageName, it) } selection?.let { selectionRepository.updateSelection(packageName, it) }
?: selectionRepository.clearSelection(packageName) ?: selectionRepository.clearSelection(packageName)
@ -145,6 +267,10 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
val patches: PatchSelection?, val patches: PatchSelection?,
) )
enum class Error(@StringRes val resourceId: Int) {
NoPlugins(R.string.downloader_no_plugins_available)
}
private companion object { private companion object {
/** /**
* Returns a copy with all nonexistent options removed. * Returns a copy with all nonexistent options removed.

View File

@ -24,6 +24,8 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@ -258,4 +260,6 @@ fun ScrollState.isScrollingUp(): State<Boolean> {
} }
val LazyListState.isScrollingUp: Boolean @Composable get() = this.isScrollingUp().value val LazyListState.isScrollingUp: Boolean @Composable get() = this.isScrollingUp().value
val ScrollState.isScrollingUp: Boolean @Composable get() = this.isScrollingUp().value val ScrollState.isScrollingUp: Boolean @Composable get() = this.isScrollingUp().value
fun Modifier.enabled(condition: Boolean) = if (condition) this else alpha(0.5f)

View File

@ -41,9 +41,11 @@
<string name="patch_selector_item_description">%d patches selected</string> <string name="patch_selector_item_description">%d patches selected</string>
<string name="no_patches_selected">No patches selected</string> <string name="no_patches_selected">No patches selected</string>
<string name="version_selector_item">Change version</string> <string name="apk_source_selector_item">Change source</string>
<string name="version_selector_item_description">%s selected</string> <string name="apk_source_auto">Current: All downloaders</string>
<string name="version_selector_item_description_auto">Automatically selected</string> <string name="apk_source_downloader">Current: %s</string>
<string name="apk_source_installed">Current: Installed</string>
<string name="apk_source_local">Current: File</string>
<string name="legacy_import_failed">Could not import legacy settings</string> <string name="legacy_import_failed">Could not import legacy settings</string>