Skip to content

Commit

Permalink
feat: add required options screen
Browse files Browse the repository at this point in the history
  • Loading branch information
Axelen123 committed Jan 3, 2025
1 parent 9db3bd5 commit 7e4f565
Show file tree
Hide file tree
Showing 10 changed files with 337 additions and 51 deletions.
54 changes: 46 additions & 8 deletions app/src/main/java/app/revanced/manager/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ 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
Expand All @@ -29,6 +30,7 @@ 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
Expand Down Expand Up @@ -139,14 +141,20 @@ 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 = { app, patches, options ->
navController.navigateComplex(
Patcher,
Patcher.ViewModelParams(app, patches, options)
)
onPatchClick = {
it.lifecycleScope.launch {
navController.navigateComplex(
Patcher,
viewModel.getPatcherParams()
)
}
},
onPatchSelectorClick = { app, patches, options ->
navController.navigateComplex(
Expand All @@ -158,9 +166,17 @@ private fun ReVancedManager(vm: MainViewModel) {
)
)
},
vm = koinNavViewModel<SelectedAppInfoViewModel>(viewModelStoreOwner = parentBackStackEntry) {
parametersOf(data)
}
onRequiredOptions = { app, patches, options ->
navController.navigateComplex(
SelectedApplicationInfo.RequiredOptions,
SelectedApplicationInfo.PatchesSelector.ViewModelParams(
app,
patches,
options
)
)
},
vm = viewModel
)
}

Expand All @@ -180,6 +196,28 @@ 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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
Expand Down Expand Up @@ -141,21 +142,35 @@ 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
}

WithOptionEditor(editor, option, value, setValue) {
ListItem(
modifier = Modifier.clickable(onClick = ::clickAction),
headlineContent = { Text(option.title) },
supportingContent = { Text(option.description) },
supportingContent = {
Column {
Text(option.description)
if (option.required && value == null) Text(
stringResource(R.string.option_required),
color = MaterialTheme.colorScheme.error
)
}
},
trailingContent = { ListItemTrailingContent() }
)
}
Expand Down
49 changes: 37 additions & 12 deletions app/src/main/java/app/revanced/manager/ui/model/BundleInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,23 @@ 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(
Expand Down Expand Up @@ -78,6 +81,28 @@ 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
}
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ data object SelectedApplicationInfo : ComplexParameter<SelectedApplicationInfo.V
val options: @RawValue Options,
) : Parcelable
}

@Serializable
data object RequiredOptions : ComplexParameter<PatchesSelector.ViewModelParams>
}

@Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ fun PatchesSelectorScreen(
mutableStateOf(null)
}
var showBottomSheet by rememberSaveable { mutableStateOf(false) }
val showPatchButton by remember {
val showSaveButton by remember {
derivedStateOf { vm.selectionIsValid(bundles) }
}

Expand Down Expand Up @@ -298,7 +298,7 @@ fun PatchesSelectorScreen(
)
},
floatingActionButton = {
if (!showPatchButton) return@Scaffold
if (!showSaveButton) return@Scaffold

HapticExtendedFloatingActionButton(
text = { Text(stringResource(R.string.save)) },
Expand All @@ -311,7 +311,6 @@ 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())
}
)
Expand Down Expand Up @@ -464,7 +463,7 @@ private fun PatchItem(
)

@Composable
private fun ListHeader(
fun ListHeader(
title: String,
onHelpClick: (() -> Unit)? = null
) {
Expand Down
Loading

0 comments on commit 7e4f565

Please sign in to comment.