Compare commits

..

4 Commits

Author SHA1 Message Date
04774d9079 more cleanup 2024-12-23 14:12:51 +01:00
53c939d005 cleanup 2024-12-23 14:06:38 +01:00
28acf78156 i figured out how to make parcelables work 2024-12-23 13:06:19 +01:00
251b9eef69 not supporting parcelables is cringe 2024-12-22 23:23:05 +01:00
17 changed files with 76 additions and 473 deletions

1
.gitignore vendored
View File

@ -9,4 +9,3 @@
.cxx
local.properties
.kotlin/

View File

@ -12,7 +12,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
@ -30,7 +29,6 @@ import app.revanced.manager.ui.theme.Theme
import app.revanced.manager.ui.viewmodel.MainViewModel
import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel
import app.revanced.manager.util.EventEffect
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
import org.koin.androidx.compose.navigation.koinNavViewModel
import org.koin.core.parameter.parametersOf
@ -141,20 +139,14 @@ private fun ReVancedManager(vm: MainViewModel) {
val parentBackStackEntry = navController.navGraphEntry(it)
val data =
parentBackStackEntry.getComplexArg<SelectedApplicationInfo.ViewModelParams>()
val viewModel =
koinNavViewModel<SelectedAppInfoViewModel>(viewModelStoreOwner = parentBackStackEntry) {
parametersOf(data)
}
SelectedAppInfoScreen(
onBackClick = navController::popBackStack,
onPatchClick = {
it.lifecycleScope.launch {
navController.navigateComplex(
Patcher,
viewModel.getPatcherParams()
)
}
onPatchClick = { app, patches, options ->
navController.navigateComplex(
Patcher,
Patcher.ViewModelParams(app, patches, options)
)
},
onPatchSelectorClick = { app, patches, options ->
navController.navigateComplex(
@ -166,17 +158,9 @@ private fun ReVancedManager(vm: MainViewModel) {
)
)
},
onRequiredOptions = { app, patches, options ->
navController.navigateComplex(
SelectedApplicationInfo.RequiredOptions,
SelectedApplicationInfo.PatchesSelector.ViewModelParams(
app,
patches,
options
)
)
},
vm = viewModel
vm = koinNavViewModel<SelectedAppInfoViewModel>(viewModelStoreOwner = parentBackStackEntry) {
parametersOf(data)
}
)
}
@ -196,28 +180,6 @@ private fun ReVancedManager(vm: MainViewModel) {
vm = koinViewModel { parametersOf(data) }
)
}
composable<SelectedApplicationInfo.RequiredOptions> {
val data =
it.getComplexArg<SelectedApplicationInfo.PatchesSelector.ViewModelParams>()
val selectedAppInfoVm = koinNavViewModel<SelectedAppInfoViewModel>(
viewModelStoreOwner = navController.navGraphEntry(it)
)
RequiredOptionsScreen(
onBackClick = navController::popBackStack,
onContinue = { patches, options ->
selectedAppInfoVm.updateConfiguration(patches, options)
it.lifecycleScope.launch {
navController.navigateComplex(
Patcher,
selectedAppInfoVm.getPatcherParams()
)
}
},
vm = koinViewModel { parametersOf(data) }
)
}
}
navigation<Settings>(startDestination = Settings.Main) {

View File

@ -8,7 +8,6 @@ import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
@ -142,19 +141,13 @@ private inline fun <T : Any> WithOptionEditor(
}
@Composable
fun <T : Any> OptionItem(
option: Option<T>,
value: T?,
setValue: (T?) -> Unit,
) {
fun <T : Any> OptionItem(option: Option<T>, value: T?, setValue: (T?) -> Unit) {
val editor = remember(option.type, option.presets) {
@Suppress("UNCHECKED_CAST")
val baseOptionEditor =
optionEditors.getOrDefault(option.type, UnknownTypeEditor) as OptionEditor<T>
if (option.type != typeOf<Boolean>() && option.presets != null) PresetOptionEditor(
baseOptionEditor
)
if (option.type != typeOf<Boolean>() && option.presets != null) PresetOptionEditor(baseOptionEditor)
else baseOptionEditor
}
@ -162,15 +155,7 @@ fun <T : Any> OptionItem(
ListItem(
modifier = Modifier.clickable(onClick = ::clickAction),
headlineContent = { Text(option.title) },
supportingContent = {
Column {
Text(option.description)
if (option.required && value == null) Text(
stringResource(R.string.option_required),
color = MaterialTheme.colorScheme.error
)
}
},
supportingContent = { Text(option.description) },
trailingContent = { ListItemTrailingContent() }
)
}

View File

@ -1,54 +0,0 @@
package app.revanced.manager.ui.component.settings
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import app.revanced.manager.domain.manager.base.Preference
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@Composable
fun SafeguardBooleanItem(
modifier: Modifier = Modifier,
preference: Preference<Boolean>,
coroutineScope: CoroutineScope = rememberCoroutineScope(),
@StringRes headline: Int,
@StringRes description: Int,
@StringRes confirmationText: Int
) {
val value by preference.getAsState()
var showSafeguardWarning by rememberSaveable {
mutableStateOf(false)
}
if (showSafeguardWarning) {
SafeguardConfirmationDialog(
onDismiss = { showSafeguardWarning = false },
onConfirm = {
coroutineScope.launch { preference.update(!value) }
showSafeguardWarning = false
},
body = stringResource(confirmationText)
)
}
BooleanItem(
modifier = modifier,
value = value,
onValueChange = {
if (it != preference.default) {
showSafeguardWarning = true
} else {
coroutineScope.launch { preference.update(it) }
}
},
headline = headline,
description = description
)
}

View File

@ -1,46 +0,0 @@
package app.revanced.manager.ui.component.settings
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.WarningAmber
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import app.revanced.manager.R
@Composable
fun SafeguardConfirmationDialog(
onDismiss: () -> Unit,
onConfirm: () -> Unit,
body: String,
) {
AlertDialog(
onDismissRequest = onDismiss,
confirmButton = {
TextButton(onClick = onConfirm) {
Text(stringResource(R.string.yes))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.no))
}
},
icon = {
Icon(Icons.Outlined.WarningAmber, null)
},
title = {
Text(
text = stringResource(id = R.string.warning),
style = MaterialTheme.typography.headlineSmall.copy(textAlign = TextAlign.Center)
)
},
text = {
Text(body)
}
)
}

View File

@ -34,23 +34,20 @@ data class BundleInfo(
}
companion object Extensions {
inline fun Iterable<BundleInfo>.toPatchSelection(
allowUnsupported: Boolean,
condition: (Int, PatchInfo) -> Boolean
): PatchSelection = this.associate { bundle ->
val patches =
bundle.patchSequence(allowUnsupported)
.mapNotNullTo(mutableSetOf()) { patch ->
patch.name.takeIf {
condition(
bundle.uid,
patch
)
inline fun Iterable<BundleInfo>.toPatchSelection(allowUnsupported: Boolean, condition: (Int, PatchInfo) -> Boolean): PatchSelection = this.associate { bundle ->
val patches =
bundle.patchSequence(allowUnsupported)
.mapNotNullTo(mutableSetOf()) { patch ->
patch.name.takeIf {
condition(
bundle.uid,
patch
)
}
}
}
bundle.uid to patches
}
bundle.uid to patches
}
fun PatchBundleRepository.bundleInfoFlow(packageName: String, version: String?) =
sources.flatMapLatestAndCombine(
@ -81,28 +78,6 @@ data class BundleInfo(
BundleInfo(source.getName(), source.uid, supported, unsupported, universal)
}
}
/**
* Algorithm for determining whether all required options have been set.
*/
inline fun Iterable<BundleInfo>.requiredOptionsSet(
crossinline isSelected: (BundleInfo, PatchInfo) -> Boolean,
crossinline optionsForPatch: (BundleInfo, PatchInfo) -> Map<String, Any?>?
) = all bundle@{ bundle ->
bundle
.all
.filter { isSelected(bundle, it) }
.all patch@{
if (it.options.isNullOrEmpty()) return@patch true
val opts by lazy { optionsForPatch(bundle, it).orEmpty() }
it.options.all option@{ option ->
if (!option.required || option.default != null) return@option true
option.key in opts
}
}
}
}
}

View File

@ -42,9 +42,6 @@ data object SelectedApplicationInfo : ComplexParameter<SelectedApplicationInfo.V
val options: @RawValue Options,
) : Parcelable
}
@Serializable
data object RequiredOptions : ComplexParameter<PatchesSelector.ViewModelParams>
}
@Serializable

View File

@ -109,6 +109,7 @@ fun DashboardScreen(
)
}
val context = LocalContext.current
var showAndroid11Dialog by rememberSaveable { mutableStateOf(false) }
val installAppsPermissionLauncher =
rememberLauncherForActivityResult(RequestInstallAppsContract) { granted ->
@ -120,7 +121,7 @@ fun DashboardScreen(
showAndroid11Dialog = false
},
onContinue = {
installAppsPermissionLauncher.launch(androidContext.packageName)
installAppsPermissionLauncher.launch(context.packageName)
}
)
@ -238,7 +239,6 @@ fun DashboardScreen(
}
}
val showBatteryOptimizationsWarning by vm.showBatteryOptimizationsWarningFlow.collectAsStateWithLifecycle(false)
Notifications(
if (!Aapt.supportsDevice()) {
{
@ -250,7 +250,7 @@ fun DashboardScreen(
)
}
} else null,
if (showBatteryOptimizationsWarning) {
if (vm.showBatteryOptimizationsWarning) {
{
NotificationCard(
isWarning = true,

View File

@ -67,7 +67,7 @@ fun PatchesSelectorScreen(
mutableStateOf(null)
}
var showBottomSheet by rememberSaveable { mutableStateOf(false) }
val showSaveButton by remember {
val showPatchButton by remember {
derivedStateOf { vm.selectionIsValid(bundles) }
}
@ -298,7 +298,7 @@ fun PatchesSelectorScreen(
)
},
floatingActionButton = {
if (!showSaveButton) return@Scaffold
if (!showPatchButton) return@Scaffold
HapticExtendedFloatingActionButton(
text = { Text(stringResource(R.string.save)) },
@ -311,6 +311,7 @@ fun PatchesSelectorScreen(
expanded = patchLazyListStates.getOrNull(pagerState.currentPage)?.isScrollingUp
?: true,
onClick = {
// TODO: only allow this if all required options have been set.
onSave(vm.getCustomSelection(), vm.getOptions())
}
)
@ -463,7 +464,7 @@ private fun PatchItem(
)
@Composable
fun ListHeader(
private fun ListHeader(
title: String,
onHelpClick: (() -> Unit)? = null
) {

View File

@ -1,158 +0,0 @@
package app.revanced.manager.ui.screen
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AutoFixHigh
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Text
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R
import app.revanced.manager.patcher.patch.Option
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.HapticTab
import app.revanced.manager.ui.component.patches.OptionItem
import app.revanced.manager.ui.model.BundleInfo.Extensions.requiredOptionsSet
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
import app.revanced.manager.util.Options
import app.revanced.manager.util.PatchSelection
import app.revanced.manager.util.isScrollingUp
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RequiredOptionsScreen(
onContinue: (PatchSelection?, Options) -> Unit,
onBackClick: () -> Unit,
vm: PatchesSelectorViewModel
) {
val list by vm.requiredOptsPatches.collectAsStateWithLifecycle(emptyList())
val pagerState = rememberPagerState(
initialPage = 0,
initialPageOffsetFraction = 0f
) {
list.size
}
val patchLazyListStates = remember(list) { List(list.size, ::LazyListState) }
val bundles by vm.bundlesFlow.collectAsStateWithLifecycle(emptyList())
val showContinueButton by remember {
derivedStateOf {
bundles.requiredOptionsSet(
isSelected = { bundle, patch -> vm.isSelected(bundle.uid, patch) },
optionsForPatch = { bundle, patch -> vm.getOptions(bundle.uid, patch) }
)
}
}
val composableScope = rememberCoroutineScope()
Scaffold(
topBar = {
AppTopBar(
title = stringResource(R.string.required_options_screen),
onBackClick = onBackClick
)
},
floatingActionButton = {
if (!showContinueButton) return@Scaffold
HapticExtendedFloatingActionButton(
text = { Text(stringResource(R.string.patch)) },
icon = {
Icon(
Icons.Default.AutoFixHigh,
stringResource(R.string.patch)
)
},
expanded = patchLazyListStates.getOrNull(pagerState.currentPage)?.isScrollingUp
?: true,
onClick = {
onContinue(vm.getCustomSelection(), vm.getOptions())
}
)
}
) { paddingValues ->
Column(
Modifier
.fillMaxSize()
.padding(paddingValues)
) {
if (list.isEmpty()) return@Column
else if (list.size > 1) ScrollableTabRow(
selectedTabIndex = pagerState.currentPage,
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp)
) {
list.forEachIndexed { index, (bundle, _) ->
HapticTab(
selected = pagerState.currentPage == index,
onClick = {
composableScope.launch {
pagerState.animateScrollToPage(
index
)
}
},
text = { Text(bundle.name) },
selectedContentColor = MaterialTheme.colorScheme.primary,
unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
HorizontalPager(
state = pagerState,
userScrollEnabled = true,
pageContent = { index ->
// Avoid crashing if the lists have not been fully initialized yet.
if (index > list.lastIndex || list.size != patchLazyListStates.size) return@HorizontalPager
val (bundle, patches) = list[index]
LazyColumnWithScrollbar(
modifier = Modifier.fillMaxSize(),
state = patchLazyListStates[index]
) {
items(patches, key = { it.name }) {
ListHeader(it.name)
val values = vm.getOptions(bundle.uid, it)
it.options?.forEach { option ->
val key = option.key
val value =
if (values == null || key !in values) option.default else values[key]
@Suppress("UNCHECKED_CAST")
OptionItem(
option = option as Option<Any>,
value = value,
setValue = { new ->
vm.setOption(bundle.uid, it, key, new)
}
)
}
}
}
}
)
}
}
}

View File

@ -14,9 +14,9 @@ import androidx.compose.material.icons.automirrored.outlined.ArrowRight
import androidx.compose.material.icons.filled.AutoFixHigh
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@ -32,6 +32,7 @@ import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.component.LoadingIndicator
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.model.BundleInfo.Extensions.bundleInfoFlow
import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel
import app.revanced.manager.util.EventEffect
@ -40,14 +41,12 @@ import app.revanced.manager.util.PatchSelection
import app.revanced.manager.util.enabled
import app.revanced.manager.util.toast
import app.revanced.manager.util.transparentListItemColors
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SelectedAppInfoScreen(
onPatchSelectorClick: (SelectedApp, PatchSelection?, Options) -> Unit,
onRequiredOptions: (SelectedApp, PatchSelection?, Options) -> Unit,
onPatchClick: () -> Unit,
onPatchClick: (SelectedApp, PatchSelection, Options) -> Unit,
onBackClick: () -> Unit,
vm: SelectedAppInfoViewModel
) {
@ -55,14 +54,20 @@ fun SelectedAppInfoScreen(
val packageName = vm.selectedApp.packageName
val version = vm.selectedApp.version
val bundles by vm.bundleInfoFlow.collectAsStateWithLifecycle(emptyList())
val bundles by remember(packageName, version) {
vm.bundlesRepo.bundleInfoFlow(packageName, version)
}.collectAsStateWithLifecycle(initialValue = emptyList())
val allowIncompatiblePatches by vm.prefs.disablePatchVersionCompatCheck.getAsState()
val patches = remember(bundles, allowIncompatiblePatches) {
vm.getPatches(bundles, allowIncompatiblePatches)
val patches by remember {
derivedStateOf {
vm.getPatches(bundles, allowIncompatiblePatches)
}
}
val selectedPatchCount = remember(patches) {
patches.values.sumOf { it.size }
val selectedPatchCount by remember {
derivedStateOf {
patches.values.sumOf { it.size }
}
}
val launcher = rememberLauncherForActivityResult(
@ -72,7 +77,6 @@ fun SelectedAppInfoScreen(
EventEffect(flow = vm.launchActivityFlow) { intent ->
launcher.launch(intent)
}
val composableScope = rememberCoroutineScope()
val error by vm.errorFlow.collectAsStateWithLifecycle(null)
Scaffold(
@ -99,19 +103,11 @@ fun SelectedAppInfoScreen(
return@patchClick
}
composableScope.launch {
if (!vm.hasSetRequiredOptions(patches)) {
onRequiredOptions(
vm.selectedApp,
vm.getCustomPatches(bundles, allowIncompatiblePatches),
vm.options
)
return@launch
}
onPatchClick()
}
onPatchClick(
vm.selectedApp,
patches,
vm.getOptionsFiltered(bundles)
)
}
)
}

View File

@ -31,7 +31,6 @@ import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.component.GroupHeader
import app.revanced.manager.ui.component.settings.BooleanItem
import app.revanced.manager.ui.component.settings.IntegerItem
import app.revanced.manager.ui.component.settings.SafeguardBooleanItem
import app.revanced.manager.ui.component.settings.SettingsListItem
import app.revanced.manager.ui.viewmodel.AdvancedSettingsViewModel
import app.revanced.manager.util.toast
@ -105,33 +104,29 @@ fun AdvancedSettingsScreen(
)
GroupHeader(stringResource(R.string.safeguards))
SafeguardBooleanItem(
BooleanItem(
preference = vm.prefs.disablePatchVersionCompatCheck,
coroutineScope = vm.viewModelScope,
headline = R.string.patch_compat_check,
description = R.string.patch_compat_check_description,
confirmationText = R.string.patch_compat_check_confirmation
description = R.string.patch_compat_check_description
)
SafeguardBooleanItem(
BooleanItem(
preference = vm.prefs.disableUniversalPatchWarning,
coroutineScope = vm.viewModelScope,
headline = R.string.universal_patches_safeguard,
description = R.string.universal_patches_safeguard_description,
confirmationText = R.string.universal_patches_safeguard_confirmation
description = R.string.universal_patches_safeguard_description
)
SafeguardBooleanItem(
BooleanItem(
preference = vm.prefs.suggestedVersionSafeguard,
coroutineScope = vm.viewModelScope,
headline = R.string.suggested_version_safeguard,
description = R.string.suggested_version_safeguard_description,
confirmationText = R.string.suggested_version_safeguard_confirmation
description = R.string.suggested_version_safeguard_description
)
SafeguardBooleanItem(
BooleanItem(
preference = vm.prefs.disableSelectionWarning,
coroutineScope = vm.viewModelScope,
headline = R.string.patch_selection_safeguard,
description = R.string.patch_selection_safeguard_description,
confirmationText = R.string.patch_selection_safeguard_confirmation
description = R.string.patch_selection_safeguard_description
)
GroupHeader(stringResource(R.string.debugging))

View File

@ -1,5 +1,6 @@
package app.revanced.manager.ui.screen.settings
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@ -95,7 +96,7 @@ fun ContributorScreen(
}
}
@OptIn(ExperimentalLayoutApi::class)
@OptIn(ExperimentalLayoutApi::class, ExperimentalFoundationApi::class)
@Composable
fun ContributorsCard(
title: String,
@ -130,7 +131,7 @@ fun ContributorsCard(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = title,
text = processHeadlineText(title),
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Medium)
)
Text(
@ -198,4 +199,11 @@ fun ContributorsCard(
}
}
}
}
fun processHeadlineText(repositoryName: String): String {
return repositoryName.replace("revanced/revanced-", "")
.replace("-", " ")
.split(" ").joinToString(" ") { if (it.length > 3) it else it.uppercase() }
.replaceFirstChar { it.uppercase() }
}

View File

@ -24,9 +24,7 @@ import app.revanced.manager.network.api.ReVancedAPI
import app.revanced.manager.util.PM
import app.revanced.manager.util.toast
import app.revanced.manager.util.uiSafe
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@ -58,19 +56,14 @@ class DashboardViewModel(
var updatedManagerVersion: String? by mutableStateOf(null)
private set
val showBatteryOptimizationsWarningFlow = flow {
while (true) {
// There is no callback for this, so we have to poll it.
val result = !powerManager.isIgnoringBatteryOptimizations(app.packageName)
emit(result)
if (!result) return@flow
delay(500L)
}
}
var showBatteryOptimizationsWarning by mutableStateOf(false)
private set
init {
viewModelScope.launch {
checkForManagerUpdates()
showBatteryOptimizationsWarning =
!powerManager.isIgnoringBatteryOptimizations(app.packageName)
}
}

View File

@ -22,6 +22,7 @@ import app.revanced.manager.patcher.patch.PatchInfo
import app.revanced.manager.ui.model.BundleInfo
import app.revanced.manager.ui.model.BundleInfo.Extensions.bundleInfoFlow
import app.revanced.manager.ui.model.BundleInfo.Extensions.toPatchSelection
import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.model.navigation.SelectedApplicationInfo
import app.revanced.manager.util.Options
import app.revanced.manager.util.PatchSelection
@ -36,14 +37,11 @@ import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import kotlinx.collections.immutable.*
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
@Stable
@OptIn(SavedStateHandleSaveableApi::class)
class PatchesSelectorViewModel(input: SelectedApplicationInfo.PatchesSelector.ViewModelParams) :
ViewModel(), KoinComponent {
class PatchesSelectorViewModel(input: SelectedApplicationInfo.PatchesSelector.ViewModelParams) : ViewModel(), KoinComponent {
private val app: Application = get()
private val savedStateHandle: SavedStateHandle = get()
private val prefs: PreferencesManager = get()
@ -116,22 +114,6 @@ class PatchesSelectorViewModel(input: SelectedApplicationInfo.PatchesSelector.Vi
selection.values.sumOf { it.size }
}
// This is for the required options screen.
private val requiredOptsPatchesDeferred = viewModelScope.async(start = CoroutineStart.LAZY) {
bundlesFlow.first().map { bundle ->
bundle to bundle.all.filter { patch ->
val opts by lazy {
getOptions(bundle.uid, patch).orEmpty()
}
isSelected(
bundle.uid,
patch
) && patch.options?.any { it.required && it.default == null && it.key !in opts } ?: false
}.toList()
}.filter { (_, patches) -> patches.isNotEmpty() }
}
val requiredOptsPatches = flow { emit(requiredOptsPatchesDeferred.await()) }
fun selectionIsValid(bundles: List<BundleInfo>) = bundles.any { bundle ->
bundle.patchSequence(allowIncompatiblePatches).any { patch ->
isSelected(bundle.uid, patch)

View File

@ -9,7 +9,6 @@ import android.util.Log
import androidx.activity.result.ActivityResult
import androidx.annotation.StringRes
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@ -30,16 +29,13 @@ import app.revanced.manager.domain.repository.PatchOptionsRepository
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.patcher.patch.PatchInfo
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.Extensions.bundleInfoFlow
import app.revanced.manager.ui.model.BundleInfo.Extensions.toPatchSelection
import app.revanced.manager.ui.model.BundleInfo.Extensions.requiredOptionsSet
import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.model.navigation.Patcher
import app.revanced.manager.ui.model.navigation.SelectedApplicationInfo
import app.revanced.manager.util.Options
import app.revanced.manager.util.PM
@ -67,6 +63,7 @@ class SelectedAppInfoViewModel(
input: SelectedApplicationInfo.ViewModelParams
) : ViewModel(), KoinComponent {
private val app: Application = get()
val bundlesRepo: PatchBundleRepository = get()
private val bundleRepository: PatchBundleRepository = get()
private val selectionRepository: PatchSelectionRepository = get()
private val optionsRepository: PatchOptionsRepository = get()
@ -177,10 +174,6 @@ class SelectedAppInfoViewModel(
}
}
val bundleInfoFlow by derivedStateOf {
bundleRepository.bundleInfoFlow(packageName, selectedApp.version)
}
fun showSourceSelector() {
dismissSourceSelector()
showSourceSelector = true
@ -267,23 +260,6 @@ class SelectedAppInfoViewModel(
selectedAppInfo = info
}
suspend fun hasSetRequiredOptions(patchSelection: PatchSelection) = bundleInfoFlow
.first()
.requiredOptionsSet(
isSelected = { bundle, patch -> patch.name in patchSelection[bundle.uid]!! },
optionsForPatch = { bundle, patch -> options[bundle.uid]?.get(patch.name) },
)
suspend fun getPatcherParams(): Patcher.ViewModelParams {
val allowUnsupported = prefs.disablePatchVersionCompatCheck.get()
val bundles = bundleInfoFlow.first()
return Patcher.ViewModelParams(
selectedApp,
getPatches(bundles, allowUnsupported),
getOptionsFiltered(bundles)
)
}
fun getOptionsFiltered(bundles: List<BundleInfo>) = options.filtered(bundles)
fun getPatches(bundles: List<BundleInfo>, allowUnsupported: Boolean) =
@ -296,7 +272,7 @@ class SelectedAppInfoViewModel(
(selectionState as? SelectionState.Customized)?.patches(bundles, allowUnsupported)
fun updateConfiguration(selection: PatchSelection?, options: Options) = viewModelScope.launch {
val bundles = bundleInfoFlow.first()
val bundles = bundlesRepo.bundleInfoFlow(packageName, selectedApp.version).first()
selectionState = selection?.let(SelectionState::Customized) ?: SelectionState.Default

View File

@ -88,16 +88,12 @@
<string name="safeguards">Safeguards</string>
<string name="patch_compat_check">Disable version compatibility check</string>
<string name="patch_compat_check_description">The check restricts patches to supported app versions</string>
<string name="patch_compat_check_confirmation">Selecting incompatible patches can result in a broken app.\n\nDo you want to proceed anyways?</string>
<string name="suggested_version_safeguard">Require suggested app version</string>
<string name="suggested_version_safeguard_description">Enforce selection of the suggested app version</string>
<string name="suggested_version_safeguard_confirmation">Selecting an app that is not the suggested version may cause unexpected issues.\n\nDo you want to proceed anyways?</string>
<string name="patch_selection_safeguard">Allow changing patch selection</string>
<string name="patch_selection_safeguard_description">Do not prevent selecting or deselecting patches</string>
<string name="patch_selection_safeguard_confirmation">Changing the selection of patches may cause unexpected issues.\n\nEnable anyways?</string>
<string name="universal_patches_safeguard">Disable universal patch warning</string>
<string name="universal_patches_safeguard_description">Disables the warning that appears when you try to select universal patches</string>
<string name="universal_patches_safeguard_confirmation">Universal patches are not as well tested as those that target specific apps.\n\nEnable anyways?</string>
<string name="import_keystore">Import keystore</string>
<string name="import_keystore_description">Import a custom keystore</string>
<string name="import_keystore_dialog_title">Enter keystore credentials</string>
@ -146,8 +142,6 @@
<string name="options">Options</string>
<string name="ok">OK</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="edit">Edit</string>
<string name="dialog_input_placeholder">Value</string>
<string name="reset">Reset</string>
@ -365,8 +359,6 @@
<string name="invalid_date">Invalid date</string>
<string name="disable_battery_optimization">Disable battery optimization</string>
<string name="input_dialog_value_invalid">Invalid value</string>
<string name="option_required">This option is required</string>
<string name="required_options_screen">Required options</string>
<string name="failed_to_check_updates">Failed to check for updates: %s</string>
<string name="no_update_available">No update available</string>