mirror of
https://github.com/meowarex/rl-mobile.git
synced 2026-08-26 22:17:44 +10:00
Improve Swipe Gstures <3
This commit is contained in:
+9
-5
@@ -148,15 +148,15 @@ enum class KnownPatch(
|
||||
default = Disabled,
|
||||
defaultVariantIndex = 2,
|
||||
variants = listOf(
|
||||
PatchVariant( // 0: Floating — stock rounded pill (no patch; the progress border is an option)
|
||||
PatchVariant( // 0: Floating — stock rounded pill
|
||||
titleRes = R.string.patch_mini_player_variant_floating_title,
|
||||
fileNames = emptyList(),
|
||||
),
|
||||
PatchVariant( // 1: Grey — square, theme background (shown as "Legacy" when Dynamic BG is on)
|
||||
PatchVariant( // 1: Grey — square
|
||||
titleRes = R.string.patch_mini_player_variant_square_grey_title,
|
||||
fileNames = listOf("mini-player-grey.patch"),
|
||||
),
|
||||
PatchVariant( // 2: Black — square, forced black background (hidden when Dynamic BG is on)
|
||||
PatchVariant( // 2: Black — square black background
|
||||
titleRes = R.string.patch_mini_player_variant_square_black_title,
|
||||
fileNames = listOf("mini-player-black.patch"),
|
||||
),
|
||||
@@ -199,11 +199,15 @@ enum class KnownPatch(
|
||||
"radiant/MiniPlayerGestures\$ApplyPending.smali",
|
||||
),
|
||||
),
|
||||
PatchOption.Toggle(
|
||||
PatchOption.Choice(
|
||||
key = "swipe_up_drag",
|
||||
titleRes = R.string.patch_mini_player_drag_title,
|
||||
descRes = R.string.patch_mini_player_drag_desc,
|
||||
default = true,
|
||||
entries = listOf(
|
||||
ChoiceEntry(R.string.patch_mini_player_drag_static, value = "0x0"),
|
||||
ChoiceEntry(R.string.patch_mini_player_drag_drag, value = "0x1"),
|
||||
),
|
||||
defaultIndex = 1,
|
||||
requiresOption = "gestures",
|
||||
token = "RL_MINI_PLAYER_SWIPE_UP_DRAG",
|
||||
),
|
||||
|
||||
@@ -62,9 +62,12 @@ sealed interface PatchOption {
|
||||
@StringRes override val descRes: Int,
|
||||
val entries: List<ChoiceEntry>,
|
||||
val defaultIndex: Int = 0,
|
||||
val requiresOption: String? = null,
|
||||
val token: String? = null,
|
||||
) : PatchOption
|
||||
}
|
||||
|
||||
data class ChoiceEntry(
|
||||
@StringRes val labelRes: Int,
|
||||
val value: String? = null,
|
||||
)
|
||||
|
||||
+14
-1
@@ -52,6 +52,10 @@ data class PatchOptions(
|
||||
fun sliderValue(spec: PatchSpec, option: OptionSpec.Slider): Float =
|
||||
(optionFloats["${spec.id}/${option.key}"] ?: option.default).coerceIn(option.min, option.max)
|
||||
|
||||
fun choiceIndex(spec: PatchSpec, option: OptionSpec.Choice): Int =
|
||||
(optionInts["${spec.id}/${option.key}"] ?: option.defaultIndex)
|
||||
.coerceIn(0, option.entries.lastIndex.coerceAtLeast(0))
|
||||
|
||||
fun isToggleOn(spec: PatchSpec, option: OptionSpec.Toggle): Boolean =
|
||||
optionBools["${spec.id}/${option.key}"] ?: option.default
|
||||
|
||||
@@ -121,7 +125,16 @@ data class PatchOptions(
|
||||
val encode = option.encode ?: continue
|
||||
put("__${token}__", encode.encode(sliderValue(spec, option)))
|
||||
}
|
||||
is OptionSpec.Choice -> Unit
|
||||
is OptionSpec.Choice -> {
|
||||
val token = option.token ?: continue
|
||||
val gatedOff = option.requiresOption?.let { key ->
|
||||
spec.advancedOptions.filterIsInstance<OptionSpec.Toggle>()
|
||||
.firstOrNull { it.key == key }
|
||||
?.let { !isToggleOn(spec, it) }
|
||||
} ?: false
|
||||
val index = if (gatedOff) 0 else choiceIndex(spec, option)
|
||||
put("__${token}__", option.values.getOrNull(index) ?: continue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,10 +98,13 @@ sealed interface OptionSpec {
|
||||
@SerialName("choice")
|
||||
data class Choice(
|
||||
override val key: String,
|
||||
override val title: String,
|
||||
override val title: String = "",
|
||||
override val description: String = "",
|
||||
val entries: List<String> = emptyList(),
|
||||
val defaultIndex: Int = 0,
|
||||
val values: List<String> = emptyList(),
|
||||
val requiresOption: String? = null,
|
||||
val token: String? = null,
|
||||
) : OptionSpec
|
||||
}
|
||||
|
||||
@@ -187,6 +190,9 @@ private fun PatchOption.toSpec(resolve: (Int) -> String): OptionSpec = when (thi
|
||||
description = resolve(descRes),
|
||||
entries = entries.map { resolve(it.labelRes) },
|
||||
defaultIndex = defaultIndex,
|
||||
values = entries.map { it.value ?: "" },
|
||||
requiresOption = requiresOption,
|
||||
token = token,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+99
-12
@@ -1,5 +1,8 @@
|
||||
package com.meowarex.rlmobile.ui.screens.patchopts.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
@@ -11,6 +14,7 @@ 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.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
@@ -87,18 +91,91 @@ fun PatchAdvancedOptionsSheet(
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
val sheetOptions = patch.advancedOptions.filter { it !is OptionSpec.Toggle || !it.inline }
|
||||
val parentKeys = patch.advancedOptions
|
||||
.filterIsInstance<OptionSpec.Toggle>()
|
||||
.filter { !it.inline }
|
||||
.map { it.key }
|
||||
.toSet()
|
||||
val sheetOptions = patch.advancedOptions.filter { option ->
|
||||
when {
|
||||
option is OptionSpec.Toggle && option.inline -> false
|
||||
option is OptionSpec.Toggle && option.requiresOption in parentKeys -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
for (option in sheetOptions) key(option.key) {
|
||||
val lock = patch.optionLock(option, selectedVariant) { state.toggle(patch, it) }
|
||||
when (option) {
|
||||
is OptionSpec.Toggle -> ToggleOptionRow(
|
||||
is OptionSpec.Toggle -> {
|
||||
val toggleOn = state.toggle(patch, option)
|
||||
val gatedChoices = patch.advancedOptions.filterIsInstance<OptionSpec.Choice>()
|
||||
.filter { it.requiresOption == option.key }
|
||||
val gatedToggles = patch.advancedOptions.filterIsInstance<OptionSpec.Toggle>()
|
||||
.filter { it.requiresOption == option.key && !it.inline }
|
||||
val hasSubOptions = gatedChoices.isNotEmpty() || gatedToggles.isNotEmpty()
|
||||
val carded = hasSubOptions && toggleOn
|
||||
|
||||
Column(
|
||||
modifier = if (carded) {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.07f))
|
||||
.border(
|
||||
width = 1.5.dp,
|
||||
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.55f),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
)
|
||||
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||
} else {
|
||||
Modifier.fillMaxWidth()
|
||||
},
|
||||
) {
|
||||
ToggleOptionRow(
|
||||
title = option.title,
|
||||
description = option.description,
|
||||
checked = state.toggle(patch, option),
|
||||
checked = toggleOn,
|
||||
onCheckedChange = { state.setToggle(patch, option, it) },
|
||||
locked = lock != OptionLock.Free,
|
||||
onLockedTap = { lockDialog = lock },
|
||||
modifier = if (hasSubOptions) Modifier.padding(vertical = 6.dp) else Modifier,
|
||||
)
|
||||
if (hasSubOptions) {
|
||||
AnimatedVisibility(visible = toggleOn) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp),
|
||||
) {
|
||||
gatedChoices.forEach { choice ->
|
||||
key(choice.key) {
|
||||
ChoiceOptionRow(
|
||||
title = choice.title,
|
||||
entries = choice.entries,
|
||||
selectedIndex = state.choice(patch, choice),
|
||||
onSelect = { state.setChoice(patch, choice, it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
gatedToggles.forEach { sub ->
|
||||
key(sub.key) {
|
||||
val subLock = patch.optionLock(sub, selectedVariant) { state.toggle(patch, it) }
|
||||
ToggleOptionRow(
|
||||
title = sub.title,
|
||||
description = sub.description,
|
||||
checked = state.toggle(patch, sub),
|
||||
onCheckedChange = { state.setToggle(patch, sub, it) },
|
||||
locked = subLock != OptionLock.Free,
|
||||
onLockedTap = { lockDialog = subLock },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is OptionSpec.Slider -> SliderOptionRow(
|
||||
title = option.title,
|
||||
@@ -110,15 +187,17 @@ fun PatchAdvancedOptionsSheet(
|
||||
onValueChange = { state.setSlider(patch, option, it) },
|
||||
)
|
||||
|
||||
is OptionSpec.Choice -> ChoiceOptionRow(
|
||||
is OptionSpec.Choice ->
|
||||
if (option.requiresOption == null) {
|
||||
ChoiceOptionRow(
|
||||
title = option.title,
|
||||
description = option.description,
|
||||
entries = option.entries,
|
||||
selectedIndex = state.choice(patch, option),
|
||||
onSelect = { state.setChoice(patch, option, it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FilledTonalButton(
|
||||
onClick = { dismiss() },
|
||||
@@ -147,13 +226,14 @@ private fun ToggleOptionRow(
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
locked: Boolean = false,
|
||||
onLockedTap: () -> Unit = {},
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val interactionSource = remember(::MutableInteractionSource)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
@@ -279,18 +359,25 @@ private fun snapToNearestStep(
|
||||
|
||||
@Composable
|
||||
private fun ChoiceOptionRow(
|
||||
title: String,
|
||||
description: String,
|
||||
entries: List<String>,
|
||||
selectedIndex: Int,
|
||||
onSelect: (Int) -> Unit,
|
||||
title: String = "",
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
OptionText(title = title, description = description)
|
||||
|
||||
if (title.isNotEmpty()) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
|
||||
entries.forEachIndexed { index, entry ->
|
||||
SegmentedButton(
|
||||
|
||||
@@ -295,8 +295,10 @@
|
||||
<string name="patch_mini_player_redesign_desc">Integrates the Seekbar into the control panel at the bottom of the screen.</string>
|
||||
<string name="patch_mini_player_gestures_title">Mini-Player Gestures</string>
|
||||
<string name="patch_mini_player_gestures_desc">Swipe up on the mini-player to open the full player.</string>
|
||||
<string name="patch_mini_player_drag_title">Enable Drag</string>
|
||||
<string name="patch_mini_player_drag_desc">Makes player follow your finger when swiping up.</string>
|
||||
<string name="patch_mini_player_drag_title">Swipe Up Style</string>
|
||||
<string name="patch_mini_player_drag_desc">Whether the player follows your finger when swiping up to open it.</string>
|
||||
<string name="patch_mini_player_drag_static">Static</string>
|
||||
<string name="patch_mini_player_drag_drag">Drag</string>
|
||||
<string name="patch_mini_player_left_right_gestures_title">Next/Previous Gestures</string>
|
||||
<string name="patch_mini_player_left_right_gestures_desc">Swipe left or right on the mini-player to skip tracks.</string>
|
||||
<string name="patch_mini_player_dynamic_bg_title">Mini-Player Dynamic Background</string>
|
||||
|
||||
@@ -35,14 +35,14 @@
|
||||
"order": 30,
|
||||
"fileNames": ["player-backdrop.patch"],
|
||||
"title": "Player Backdrop",
|
||||
"description": "Restores the legacy translucent backdrop blur behind the player.",
|
||||
"description": "Restores the legacy translucent backdrop blur behind the player",
|
||||
"default": true,
|
||||
"advancedOptions": [
|
||||
{
|
||||
"type": "slider",
|
||||
"key": "blur_strength",
|
||||
"title": "Blur Strength",
|
||||
"description": "How strongly the album art behind the player is blurred. 0% is sharp, 50% matches the original look.",
|
||||
"description": "How strongly the album art behind the player is blurred",
|
||||
"default": 50,
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
@@ -55,7 +55,7 @@
|
||||
"type": "slider",
|
||||
"key": "dimming",
|
||||
"title": "Backdrop Dimming",
|
||||
"description": "Darkens the backdrop so the foreground controls stay legible.",
|
||||
"description": "Darkens the backdrop so the foreground controls stay legible",
|
||||
"default": 50,
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
@@ -163,7 +163,7 @@
|
||||
"type": "toggle",
|
||||
"key": "gestures",
|
||||
"title": "Mini-Player Gestures",
|
||||
"description": "Swipe up on the mini-player to open the full player.",
|
||||
"description": "Adds swipe up gesture to the mini-player to reopen the player",
|
||||
"default": true,
|
||||
"fileNames": ["mini-player-gestures.patch"],
|
||||
"extensionFiles": [
|
||||
@@ -176,11 +176,12 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "toggle",
|
||||
"type": "choice",
|
||||
"key": "swipe_up_drag",
|
||||
"title": "Enable Drag",
|
||||
"description": "Makes player follow your finger when swiping up.",
|
||||
"default": true,
|
||||
"title": "Swipe Up Style",
|
||||
"entries": ["Static", "Drag"],
|
||||
"values": ["0x0", "0x1"],
|
||||
"defaultIndex": 1,
|
||||
"requiresOption": "gestures",
|
||||
"token": "RL_MINI_PLAYER_SWIPE_UP_DRAG"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user