feat(swipe to queue): add to playlist action + both directions for queue track removal + options UI rework

This commit is contained in:
Se7en-dev committed 2026-09-03 11:36:22 +02:00
1 parent 2cb51576df
commit 49693ca36b
36 files changed
+953 -151

No files matched your search

@@ -471,25 +471,27 @@
"radiant/swipe/SuggestionsResolver.smali"
],
"title": "Swipe to Queue",
"description": "Swipe on tracks/albums/playlists to add them to the play queue",
"description": "Swipe on tracks/albums/playlists to add them to the play queue (and more actions)",
"default": false,
"advancedOptions": [
{
"type": "choice",
"key": "left_action",
"title": "Left Swipe Action",
"description": "Choose the action for a left swipe",
"entries": [
"Disabled",
"Play next",
"Add to queue"
"Add to queue",
"Add to playlist"
],
"values": [
"0x0",
"0x1",
"0x2"
"0x2",
"0x3"
],
"defaultIndex": 2,
"inline": true,
"distinctFrom": "right_action",
"token": "RL_SWIPE_TO_QUEUE_LEFT_ACTION"
},
@@ -497,18 +499,20 @@
"type": "choice",
"key": "right_action",
"title": "Right Swipe Action",
"description": "Choose the action for a right swipe",
"entries": [
"Disabled",
"Play next",
"Add to queue"
"Add to queue",
"Add to playlist"
],
"values": [
"0x0",
"0x1",
"0x2"
"0x2",
"0x3"
],
"defaultIndex": 0,
"inline": true,
"distinctFrom": "left_action",
"token": "RL_SWIPE_TO_QUEUE_RIGHT_ACTION"
},
@@ -516,11 +520,10 @@
"type": "choice",
"key": "remove_direction",
"title": "Remove Track from Queue",
"description": "Choose the swipe direction used to remove items from the play queue",
"entries": [
"Left swipe",
"Right swipe",
"Both"
"Both directions"
],
"values": [
"0x4",
@@ -528,6 +531,8 @@
"0xc"
],
"defaultIndex": 0,
"forceDropdown": true,
"inline": true,
"token": "RL_SWIPE_TO_QUEUE_REMOVE_DIRECTION"
},
{
@@ -543,6 +548,13 @@
"title": "Play Next Color",
"default": -14408663,
"token": "RL_SWIPE_TO_QUEUE_PLAY_NEXT_COLOR"
},
{
"type": "color",
"key": "add_to_playlist_color",
"title": "Add to Playlist Color",
"default": -14408663,
"token": "RL_SWIPE_TO_QUEUE_ADD_TO_PLAYLIST_COLOR"
}
]
},
@@ -3,26 +3,49 @@ package com.meowarex.rlmobile.ui.components.radiant
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.rememberTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.selected
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import com.meowarex.rlmobile.R
import com.meowarex.rlmobile.ui.theme.*
/**
@@ -113,6 +136,151 @@ fun RadiantTextField(
}
}
/**
* Dropdown
*/
@Composable
fun RadiantDropdown(
options: List<String>,
selectedIndex: Int,
onSelect: (Int) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
disabledIndices: Set<Int> = emptySet(),
) {
var expanded by rememberSaveable { mutableStateOf(false) }
val visibilityState = remember { MutableTransitionState(false) }
visibilityState.targetState = expanded
val transition = rememberTransition(visibilityState, label = "Dropdown")
val progress by transition.animateFloat(
transitionSpec = { tween(durationMillis = 160, easing = FastOutSlowInEasing) },
label = "DropdownProgress",
) {
if (it) 1f else 0f
}
val scheme = MaterialTheme.colorScheme
val selectedLabel = options.getOrNull(selectedIndex).orEmpty()
val shape = if (radiantStyle.native) MaterialTheme.shapes.extraSmall else RoundedCornerShape(16.dp)
var anchorWidthPx by remember { mutableIntStateOf(0) }
val anchorWidth = with(LocalDensity.current) { anchorWidthPx.toDp() }
val positionProvider = MenuDefaults.rememberDropdownMenuPopupPositionProvider(
dropdownMenuAnchorPosition = MenuAnchorPosition.Below,
offset = DpOffset(0.dp, 6.dp),
)
Box(modifier = modifier.fillMaxWidth()) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier
.fillMaxWidth()
.onSizeChanged { anchorWidthPx = it.width }
.background(scheme.surfaceContainerLowest.copy(alpha = 0.8f), shape)
.then(
if (radiantStyle.native) Modifier.border(1.dp, scheme.outline, shape)
else Modifier.border(
RadiantDesign.Hairline,
hairlineBrush(scheme.onSurface, RadiantDesign.BORDER_ALPHA * 1.5f),
shape,
)
)
.clip(shape)
.clickable(enabled = enabled, role = Role.Button) { expanded = !expanded }
.alpha(if (enabled) 1f else 0.45f)
.heightIn(min = 52.dp)
.padding(horizontal = 16.dp, vertical = 10.dp),
) {
Text(
text = selectedLabel,
style = MaterialTheme.typography.bodyLarge,
color = scheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f),
)
Icon(
painter = painterResource(R.drawable.ic_arrow_down_small),
contentDescription = null,
modifier = Modifier.rotate(180f * progress),
)
}
if (
anchorWidthPx > 0 &&
(visibilityState.currentState || visibilityState.targetState || transition.isRunning)
) {
Popup(
popupPositionProvider = positionProvider,
onDismissRequest = { expanded = false },
properties = PopupProperties(focusable = true),
) {
Surface(
modifier = Modifier
.width(anchorWidth)
.graphicsLayer {
alpha = progress
scaleX = 0.92f + 0.08f * progress
scaleY = 0.92f + 0.08f * progress
transformOrigin = positionProvider.transformOrigin
},
shape = shape,
color = scheme.surfaceContainer,
tonalElevation = 0.dp,
shadowElevation = 8.dp,
border = BorderStroke(
RadiantDesign.Hairline,
scheme.onSurface.copy(alpha = RadiantDesign.BORDER_ALPHA * 2f),
),
) {
Column(
modifier = Modifier
.heightIn(max = 320.dp)
.verticalScroll(rememberScrollState())
.padding(vertical = 6.dp),
) {
options.forEachIndexed { index, option ->
val selected = index == selectedIndex
val itemEnabled = enabled && index !in disabledIndices
DropdownMenuItem(
text = {
Text(
text = option,
style = MaterialTheme.typography.bodyLarge,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
trailingIcon = {
RadiantRadio(
selected = selected,
onClick = null,
enabled = itemEnabled,
)
},
enabled = itemEnabled,
onClick = {
onSelect(index)
expanded = false
},
modifier = Modifier
.padding(horizontal = 6.dp, vertical = 2.dp)
.clip(RoundedCornerShape(12.dp))
.semantics {
this.selected = selected
role = Role.RadioButton
}
.then(
if (selected) Modifier.background(scheme.primary.copy(alpha = 0.1f))
else Modifier
),
)
}
}
}
}
}
}
}
/**
* Slider. (i prefer the old on tbh so i reverted it back <3)
*/
@@ -33,11 +33,13 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.meowarex.rlmobile.R
import com.meowarex.rlmobile.ui.components.radiant.RadiantDropdown
import com.meowarex.rlmobile.ui.legacy.components.ResetToDefaultButton
import com.meowarex.rlmobile.ui.screens.patchopts.OptionLock
import com.meowarex.rlmobile.ui.screens.patchopts.OptionSpec
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptionState
import com.meowarex.rlmobile.ui.screens.patchopts.hiddenForVariant
import com.meowarex.rlmobile.ui.screens.patchopts.isInline
import com.meowarex.rlmobile.ui.screens.patchopts.optionLock
import com.meowarex.rlmobile.ui.screens.patchopts.PatchSpec
import kotlinx.coroutines.launch
@@ -103,12 +105,12 @@ fun PatchAdvancedOptionsSheet(
val parentKeys = patch.advancedOptions
.filterIsInstance<OptionSpec.Toggle>()
.filter { !it.inline }
.filter { !it.isInline }
.map { it.key }
.toSet()
val sheetOptions = patch.advancedOptions.filter { option ->
when {
option is OptionSpec.Toggle && option.inline -> false
option.isInline -> false
option is OptionSpec.Toggle && option.requiresOption in parentKeys -> false
// Options belonging to another variant are hidden, not greyed out.
option.hiddenForVariant(selectedVariant) -> false
@@ -121,7 +123,7 @@ fun PatchAdvancedOptionsSheet(
is OptionSpec.Toggle -> {
val toggleOn = state.toggle(patch, option)
val gatedChoices = patch.advancedOptions.filterIsInstance<OptionSpec.Choice>()
.filter { it.requiresOption == option.key }
.filter { it.requiresOption == option.key && !it.isInline }
val gatedToggles = patch.advancedOptions.filterIsInstance<OptionSpec.Toggle>()
.filter { it.requiresOption == option.key && !it.inline }
val hasSubOptions = gatedChoices.isNotEmpty() || gatedToggles.isNotEmpty()
@@ -164,6 +166,7 @@ fun PatchAdvancedOptionsSheet(
key(choice.key) {
ChoiceOptionRow(
title = choice.title,
description = choice.description,
entries = choice.entries,
selectedIndex = state.choice(patch, choice),
forceDropdown = choice.forceDropdown,
@@ -208,6 +211,7 @@ fun PatchAdvancedOptionsSheet(
if (option.requiresOption == null) {
ChoiceOptionRow(
title = option.title,
description = option.description,
entries = option.entries,
selectedIndex = state.choice(patch, option),
forceDropdown = option.forceDropdown,
@@ -387,11 +391,12 @@ private fun snapToNearestStep(
}
@Composable
private fun ChoiceOptionRow(
internal fun ChoiceOptionRow(
entries: List<String>,
selectedIndex: Int,
onSelect: (Int) -> Unit,
title: String = "",
description: String = "",
forceDropdown: Boolean = false,
disabledIndices: Set<Int> = emptySet(),
modifier: Modifier = Modifier,
@@ -399,48 +404,28 @@ private fun ChoiceOptionRow(
Column(
modifier = modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(6.dp),
horizontalAlignment = Alignment.CenterHorizontally,
horizontalAlignment = Alignment.Start,
) {
if (title.isNotEmpty()) {
Text(
text = title,
style = MaterialTheme.typography.labelMedium,
style = MaterialTheme.typography.titleSmall,
)
}
if (description.isNotEmpty()) {
Text(
text = description,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
)
}
if (forceDropdown || entries.size > 3) {
var expanded by rememberSaveable { mutableStateOf(false) }
val selectedLabel = entries.getOrNull(selectedIndex).orEmpty()
Box(modifier = Modifier.fillMaxWidth()) {
OutlinedButton(
onClick = { expanded = true },
modifier = Modifier.fillMaxWidth(),
) {
Text(
text = selectedLabel,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier.fillMaxWidth(),
) {
entries.forEachIndexed { index, entry ->
DropdownMenuItem(
text = { Text(entry) },
enabled = index !in disabledIndices,
onClick = {
onSelect(index)
expanded = false
},
)
}
}
}
RadiantDropdown(
options = entries,
selectedIndex = selectedIndex,
onSelect = onSelect,
disabledIndices = disabledIndices,
)
return@Column
}
SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
@@ -36,6 +36,8 @@ import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptions
import com.meowarex.rlmobile.ui.screens.patchopts.PatchSpec
import com.meowarex.rlmobile.ui.screens.patchopts.effectiveVariants
import com.meowarex.rlmobile.ui.screens.patchopts.resolveVariantIndex
import com.meowarex.rlmobile.ui.screens.patchopts.conflictingChoices
import com.meowarex.rlmobile.ui.screens.patchopts.isInline
private data class LockInfo(val patch: PatchSpec, val lock: PatchLock)
@@ -131,11 +133,9 @@ fun PatchSelectionAccordion(
val checked = isEnabled(patch)
val lock = lockState(patch)
val inlineToggles = patch.advancedOptions
.filterIsInstance<OptionSpec.Toggle>()
.filter { it.inline }
val hasSheetOptions = patch.advancedOptions.any { it !is OptionSpec.Toggle || !it.inline }
val hasSettings = patch.variants.isNotEmpty() || inlineToggles.isNotEmpty() || hasSheetOptions
val inlineOptions = patch.advancedOptions.filter { it.isInline }
val hasSheetOptions = patch.advancedOptions.any { !it.isInline }
val hasSettings = patch.variants.isNotEmpty() || inlineOptions.isNotEmpty() || hasSheetOptions
// When a patch is enabled and has settings show a carded view of the settings
val carded = checked && hasSettings
@@ -191,16 +191,41 @@ fun PatchSelectionAccordion(
)
}
for (option in inlineToggles) key(option.key) {
val show = option.requiresVariant == null ||
option.requiresVariant == resolvedVariant
if (show) {
InlineToggleRow(
title = option.title,
description = option.description,
checked = optionState.toggle(patch, option),
onCheckedChange = { optionState.setToggle(patch, option, it) },
)
for (option in inlineOptions) key(option.key) {
when (option) {
is OptionSpec.Toggle -> {
val show = option.requiresVariant == null ||
option.requiresVariant == resolvedVariant
if (show) {
InlineToggleRow(
title = option.title,
description = option.description,
checked = optionState.toggle(patch, option),
onCheckedChange = { optionState.setToggle(patch, option, it) },
)
}
}
is OptionSpec.Choice -> {
val requiredToggle = option.requiresOption?.let { key ->
patch.advancedOptions.filterIsInstance<OptionSpec.Toggle>()
.firstOrNull { it.key == key }
}
if (requiredToggle == null || optionState.toggle(patch, requiredToggle)) {
ChoiceOptionRow(
title = option.title,
description = option.description,
entries = option.entries,
selectedIndex = optionState.choice(patch, option),
forceDropdown = option.forceDropdown,
disabledIndices = patch.conflictingChoices(option)
.map { optionState.choice(patch, it) }
.filter { it != 0 }
.toSet(),
onSelect = { optionState.setChoice(patch, option, it) },
)
}
}
else -> Unit
}
}
@@ -195,10 +195,9 @@ class PatchOptionsModel(
}
fun isAdvancedModified(spec: PatchSpec): Boolean = spec.advancedOptions.any { option ->
if (option.isInline) return@any false
when (option) {
// inline toggles sit next to the variant picker, not in the sheet, so they
// must not light up the sheet's "modified" dot
is OptionSpec.Toggle -> !option.inline && toggleValue(spec, option) != option.default
is OptionSpec.Toggle -> toggleValue(spec, option) != option.default
is OptionSpec.Slider -> sliderValue(spec, option) != option.default
is OptionSpec.Choice -> choiceValue(spec, option) != option.defaultIndex
is OptionSpec.Color -> colorValue(spec, option) != option.default
@@ -206,12 +205,17 @@ class PatchOptionsModel(
}
fun resetAdvanced(spec: PatchSpec) {
val prefix = "${spec.id}/"
optionBools = optionBools.filterKeys { !it.startsWith(prefix) }
optionFloats = optionFloats.filterKeys { !it.startsWith(prefix) }
val advancedOptions = spec.advancedOptions.filter { !it.isInline }
val boolKeys = advancedOptions.filterIsInstance<OptionSpec.Toggle>().map { keyOf(spec, it) }.toSet()
val floatKeys = advancedOptions.filterIsInstance<OptionSpec.Slider>().map { keyOf(spec, it) }.toSet()
val intKeys = advancedOptions.filter { it is OptionSpec.Choice || it is OptionSpec.Color }
.map { keyOf(spec, it) }
.toSet()
optionBools = optionBools.filterKeys { it !in boolKeys }
optionFloats = optionFloats.filterKeys { it !in floatKeys }
optionInts = normalizeChoiceSelections(
specs = listOf(spec),
values = optionInts.filterKeys { !it.startsWith(prefix) },
values = optionInts.filterKeys { it !in intKeys },
)
}
@@ -126,6 +126,8 @@ sealed interface OptionSpec {
val values: List<String> = emptyList(),
val requiresOption: String? = null,
val forceDropdown: Boolean = false,
/** Render inline in the patch card instead of inside the advanced sheet */
val inline: Boolean = false,
val distinctFrom: String? = null,
val token: String? = null,
) : OptionSpec
@@ -143,6 +145,13 @@ sealed interface OptionSpec {
) : OptionSpec
}
val OptionSpec.isInline: Boolean
get() = when (this) {
is OptionSpec.Toggle -> inline
is OptionSpec.Choice -> inline
is OptionSpec.Slider, is OptionSpec.Color -> false
}
@Serializable
enum class EncodeKind {
@SerialName("floatBits")
@@ -32,6 +32,7 @@ import androidx.compose.ui.unit.dp
import com.meowarex.rlmobile.R
import com.meowarex.rlmobile.ui.components.radiant.RadiantButton
import com.meowarex.rlmobile.ui.components.radiant.RadiantDialog
import com.meowarex.rlmobile.ui.components.radiant.RadiantDropdown
import com.meowarex.rlmobile.ui.components.radiant.RadiantButtonSize
import com.meowarex.rlmobile.ui.components.radiant.RadiantButtonStyle
import com.meowarex.rlmobile.ui.components.radiant.RadiantIconButton
@@ -45,6 +46,7 @@ import com.meowarex.rlmobile.ui.screens.patchopts.OptionSpec
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptionState
import com.meowarex.rlmobile.ui.screens.patchopts.conflictingChoices
import com.meowarex.rlmobile.ui.screens.patchopts.hiddenForVariant
import com.meowarex.rlmobile.ui.screens.patchopts.isInline
import com.meowarex.rlmobile.ui.screens.patchopts.optionLock
import com.meowarex.rlmobile.ui.screens.patchopts.PatchSpec
import kotlinx.coroutines.launch
@@ -110,12 +112,12 @@ fun PatchAdvancedOptionsSheet(
val parentKeys = patch.advancedOptions
.filterIsInstance<OptionSpec.Toggle>()
.filter { !it.inline }
.filter { !it.isInline }
.map { it.key }
.toSet()
val sheetOptions = patch.advancedOptions.filter { option ->
when {
option is OptionSpec.Toggle && option.inline -> false
option.isInline -> false
option is OptionSpec.Toggle && option.requiresOption in parentKeys -> false
// Options belonging to another variant are hidden, not greyed out.
option.hiddenForVariant(selectedVariant) -> false
@@ -128,7 +130,7 @@ fun PatchAdvancedOptionsSheet(
is OptionSpec.Toggle -> {
val toggleOn = state.toggle(patch, option)
val gatedChoices = patch.advancedOptions.filterIsInstance<OptionSpec.Choice>()
.filter { it.requiresOption == option.key }
.filter { it.requiresOption == option.key && !it.isInline }
val gatedToggles = patch.advancedOptions.filterIsInstance<OptionSpec.Toggle>()
.filter { it.requiresOption == option.key && !it.inline }
val hasSubOptions = gatedChoices.isNotEmpty() || gatedToggles.isNotEmpty()
@@ -171,6 +173,7 @@ fun PatchAdvancedOptionsSheet(
key(choice.key) {
ChoiceOptionRow(
title = choice.title,
description = choice.description,
entries = choice.entries,
selectedIndex = state.choice(patch, choice),
forceDropdown = choice.forceDropdown,
@@ -215,6 +218,7 @@ fun PatchAdvancedOptionsSheet(
if (option.requiresOption == null) {
ChoiceOptionRow(
title = option.title,
description = option.description,
entries = option.entries,
selectedIndex = state.choice(patch, option),
forceDropdown = option.forceDropdown,
@@ -388,11 +392,12 @@ private fun snapToNearestStep(
}
@Composable
private fun ChoiceOptionRow(
internal fun ChoiceOptionRow(
entries: List<String>,
selectedIndex: Int,
onSelect: (Int) -> Unit,
title: String = "",
description: String = "",
forceDropdown: Boolean = false,
disabledIndices: Set<Int> = emptySet(),
modifier: Modifier = Modifier,
@@ -400,44 +405,28 @@ private fun ChoiceOptionRow(
Column(
modifier = modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(6.dp),
horizontalAlignment = Alignment.CenterHorizontally,
horizontalAlignment = Alignment.Start,
) {
if (title.isNotEmpty()) {
Text(
text = title,
style = MaterialTheme.typography.labelMedium,
style = MaterialTheme.typography.titleSmall,
)
}
if (description.isNotEmpty()) {
Text(
text = description,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
)
}
if (forceDropdown || entries.size > 3) {
var expanded by rememberSaveable { mutableStateOf(false) }
val selectedLabel = entries.getOrNull(selectedIndex).orEmpty()
Box(modifier = Modifier.fillMaxWidth()) {
RadiantButton(
text = selectedLabel,
onClick = { expanded = true },
style = RadiantButtonStyle.Ghost,
fillWidth = true,
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier.fillMaxWidth(),
) {
entries.forEachIndexed { index, entry ->
DropdownMenuItem(
text = { Text(entry) },
enabled = index !in disabledIndices,
onClick = {
onSelect(index)
expanded = false
},
)
}
}
}
RadiantDropdown(
options = entries,
selectedIndex = selectedIndex,
onSelect = onSelect,
disabledIndices = disabledIndices,
)
return@Column
}
RadiantSegmented(
@@ -33,6 +33,8 @@ import com.meowarex.rlmobile.ui.components.radiant.RadiantDialog
import com.meowarex.rlmobile.ui.theme.radiantSurface
import com.meowarex.rlmobile.ui.theme.glassSurface
import com.meowarex.rlmobile.ui.screens.patchopts.OptionSpec
import com.meowarex.rlmobile.ui.screens.patchopts.conflictingChoices
import com.meowarex.rlmobile.ui.screens.patchopts.isInline
import com.meowarex.rlmobile.ui.screens.patchopts.PatchLock
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptionState
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptions
@@ -134,11 +136,9 @@ fun PatchSelectionAccordion(
val checked = isEnabled(patch)
val lock = lockState(patch)
val inlineToggles = patch.advancedOptions
.filterIsInstance<OptionSpec.Toggle>()
.filter { it.inline }
val hasSheetOptions = patch.advancedOptions.any { it !is OptionSpec.Toggle || !it.inline }
val hasSettings = patch.variants.isNotEmpty() || inlineToggles.isNotEmpty() || hasSheetOptions
val inlineOptions = patch.advancedOptions.filter { it.isInline }
val hasSheetOptions = patch.advancedOptions.any { !it.isInline }
val hasSettings = patch.variants.isNotEmpty() || inlineOptions.isNotEmpty() || hasSheetOptions
// When a patch is enabled and has settings show a carded view of the settings
val carded = checked && hasSettings
@@ -189,16 +189,41 @@ fun PatchSelectionAccordion(
)
}
for (option in inlineToggles) key(option.key) {
val show = option.requiresVariant == null ||
option.requiresVariant == resolvedVariant
if (show) {
InlineToggleRow(
title = option.title,
description = option.description,
checked = optionState.toggle(patch, option),
onCheckedChange = { optionState.setToggle(patch, option, it) },
)
for (option in inlineOptions) key(option.key) {
when (option) {
is OptionSpec.Toggle -> {
val show = option.requiresVariant == null ||
option.requiresVariant == resolvedVariant
if (show) {
InlineToggleRow(
title = option.title,
description = option.description,
checked = optionState.toggle(patch, option),
onCheckedChange = { optionState.setToggle(patch, option, it) },
)
}
}
is OptionSpec.Choice -> {
val requiredToggle = option.requiresOption?.let { key ->
patch.advancedOptions.filterIsInstance<OptionSpec.Toggle>()
.firstOrNull { it.key == key }
}
if (requiredToggle == null || optionState.toggle(patch, requiredToggle)) {
ChoiceOptionRow(
title = option.title,
description = option.description,
entries = option.entries,
selectedIndex = optionState.choice(patch, option),
forceDropdown = option.forceDropdown,
disabledIndices = patch.conflictingChoices(option)
.map { optionState.choice(patch, it) }
.filter { it != 0 }
.toSet(),
onSelect = { optionState.setChoice(patch, option, it) },
)
}
}
else -> Unit
}
}