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,
|
default = Disabled,
|
||||||
defaultVariantIndex = 2,
|
defaultVariantIndex = 2,
|
||||||
variants = listOf(
|
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,
|
titleRes = R.string.patch_mini_player_variant_floating_title,
|
||||||
fileNames = emptyList(),
|
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,
|
titleRes = R.string.patch_mini_player_variant_square_grey_title,
|
||||||
fileNames = listOf("mini-player-grey.patch"),
|
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,
|
titleRes = R.string.patch_mini_player_variant_square_black_title,
|
||||||
fileNames = listOf("mini-player-black.patch"),
|
fileNames = listOf("mini-player-black.patch"),
|
||||||
),
|
),
|
||||||
@@ -199,11 +199,15 @@ enum class KnownPatch(
|
|||||||
"radiant/MiniPlayerGestures\$ApplyPending.smali",
|
"radiant/MiniPlayerGestures\$ApplyPending.smali",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PatchOption.Toggle(
|
PatchOption.Choice(
|
||||||
key = "swipe_up_drag",
|
key = "swipe_up_drag",
|
||||||
titleRes = R.string.patch_mini_player_drag_title,
|
titleRes = R.string.patch_mini_player_drag_title,
|
||||||
descRes = R.string.patch_mini_player_drag_desc,
|
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",
|
requiresOption = "gestures",
|
||||||
token = "RL_MINI_PLAYER_SWIPE_UP_DRAG",
|
token = "RL_MINI_PLAYER_SWIPE_UP_DRAG",
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -62,9 +62,12 @@ sealed interface PatchOption {
|
|||||||
@StringRes override val descRes: Int,
|
@StringRes override val descRes: Int,
|
||||||
val entries: List<ChoiceEntry>,
|
val entries: List<ChoiceEntry>,
|
||||||
val defaultIndex: Int = 0,
|
val defaultIndex: Int = 0,
|
||||||
|
val requiresOption: String? = null,
|
||||||
|
val token: String? = null,
|
||||||
) : PatchOption
|
) : PatchOption
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ChoiceEntry(
|
data class ChoiceEntry(
|
||||||
@StringRes val labelRes: Int,
|
@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 =
|
fun sliderValue(spec: PatchSpec, option: OptionSpec.Slider): Float =
|
||||||
(optionFloats["${spec.id}/${option.key}"] ?: option.default).coerceIn(option.min, option.max)
|
(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 =
|
fun isToggleOn(spec: PatchSpec, option: OptionSpec.Toggle): Boolean =
|
||||||
optionBools["${spec.id}/${option.key}"] ?: option.default
|
optionBools["${spec.id}/${option.key}"] ?: option.default
|
||||||
|
|
||||||
@@ -121,7 +125,16 @@ data class PatchOptions(
|
|||||||
val encode = option.encode ?: continue
|
val encode = option.encode ?: continue
|
||||||
put("__${token}__", encode.encode(sliderValue(spec, option)))
|
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")
|
@SerialName("choice")
|
||||||
data class Choice(
|
data class Choice(
|
||||||
override val key: String,
|
override val key: String,
|
||||||
override val title: String,
|
override val title: String = "",
|
||||||
override val description: String = "",
|
override val description: String = "",
|
||||||
val entries: List<String> = emptyList(),
|
val entries: List<String> = emptyList(),
|
||||||
val defaultIndex: Int = 0,
|
val defaultIndex: Int = 0,
|
||||||
|
val values: List<String> = emptyList(),
|
||||||
|
val requiresOption: String? = null,
|
||||||
|
val token: String? = null,
|
||||||
) : OptionSpec
|
) : OptionSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +190,9 @@ private fun PatchOption.toSpec(resolve: (Int) -> String): OptionSpec = when (thi
|
|||||||
description = resolve(descRes),
|
description = resolve(descRes),
|
||||||
entries = entries.map { resolve(it.labelRes) },
|
entries = entries.map { resolve(it.labelRes) },
|
||||||
defaultIndex = defaultIndex,
|
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
|
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.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
@@ -11,6 +14,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
@@ -87,18 +91,91 @@ fun PatchAdvancedOptionsSheet(
|
|||||||
|
|
||||||
HorizontalDivider()
|
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) {
|
for (option in sheetOptions) key(option.key) {
|
||||||
val lock = patch.optionLock(option, selectedVariant) { state.toggle(patch, it) }
|
val lock = patch.optionLock(option, selectedVariant) { state.toggle(patch, it) }
|
||||||
when (option) {
|
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,
|
title = option.title,
|
||||||
description = option.description,
|
description = option.description,
|
||||||
checked = state.toggle(patch, option),
|
checked = toggleOn,
|
||||||
onCheckedChange = { state.setToggle(patch, option, it) },
|
onCheckedChange = { state.setToggle(patch, option, it) },
|
||||||
locked = lock != OptionLock.Free,
|
locked = lock != OptionLock.Free,
|
||||||
onLockedTap = { lockDialog = lock },
|
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(
|
is OptionSpec.Slider -> SliderOptionRow(
|
||||||
title = option.title,
|
title = option.title,
|
||||||
@@ -110,15 +187,17 @@ fun PatchAdvancedOptionsSheet(
|
|||||||
onValueChange = { state.setSlider(patch, option, it) },
|
onValueChange = { state.setSlider(patch, option, it) },
|
||||||
)
|
)
|
||||||
|
|
||||||
is OptionSpec.Choice -> ChoiceOptionRow(
|
is OptionSpec.Choice ->
|
||||||
|
if (option.requiresOption == null) {
|
||||||
|
ChoiceOptionRow(
|
||||||
title = option.title,
|
title = option.title,
|
||||||
description = option.description,
|
|
||||||
entries = option.entries,
|
entries = option.entries,
|
||||||
selectedIndex = state.choice(patch, option),
|
selectedIndex = state.choice(patch, option),
|
||||||
onSelect = { state.setChoice(patch, option, it) },
|
onSelect = { state.setChoice(patch, option, it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FilledTonalButton(
|
FilledTonalButton(
|
||||||
onClick = { dismiss() },
|
onClick = { dismiss() },
|
||||||
@@ -147,13 +226,14 @@ private fun ToggleOptionRow(
|
|||||||
onCheckedChange: (Boolean) -> Unit,
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
locked: Boolean = false,
|
locked: Boolean = false,
|
||||||
onLockedTap: () -> Unit = {},
|
onLockedTap: () -> Unit = {},
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val interactionSource = remember(::MutableInteractionSource)
|
val interactionSource = remember(::MutableInteractionSource)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(
|
.clickable(
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
@@ -279,18 +359,25 @@ private fun snapToNearestStep(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ChoiceOptionRow(
|
private fun ChoiceOptionRow(
|
||||||
title: String,
|
|
||||||
description: String,
|
|
||||||
entries: List<String>,
|
entries: List<String>,
|
||||||
selectedIndex: Int,
|
selectedIndex: Int,
|
||||||
onSelect: (Int) -> Unit,
|
onSelect: (Int) -> Unit,
|
||||||
|
title: String = "",
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
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()) {
|
SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
|
||||||
entries.forEachIndexed { index, entry ->
|
entries.forEachIndexed { index, entry ->
|
||||||
SegmentedButton(
|
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_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_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_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_title">Swipe Up Style</string>
|
||||||
<string name="patch_mini_player_drag_desc">Makes player follow your finger when swiping up.</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_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_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>
|
<string name="patch_mini_player_dynamic_bg_title">Mini-Player Dynamic Background</string>
|
||||||
|
|||||||
@@ -35,14 +35,14 @@
|
|||||||
"order": 30,
|
"order": 30,
|
||||||
"fileNames": ["player-backdrop.patch"],
|
"fileNames": ["player-backdrop.patch"],
|
||||||
"title": "Player Backdrop",
|
"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,
|
"default": true,
|
||||||
"advancedOptions": [
|
"advancedOptions": [
|
||||||
{
|
{
|
||||||
"type": "slider",
|
"type": "slider",
|
||||||
"key": "blur_strength",
|
"key": "blur_strength",
|
||||||
"title": "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,
|
"default": 50,
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"max": 100,
|
"max": 100,
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
"type": "slider",
|
"type": "slider",
|
||||||
"key": "dimming",
|
"key": "dimming",
|
||||||
"title": "Backdrop 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,
|
"default": 50,
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"max": 100,
|
"max": 100,
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
"type": "toggle",
|
"type": "toggle",
|
||||||
"key": "gestures",
|
"key": "gestures",
|
||||||
"title": "Mini-Player 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,
|
"default": true,
|
||||||
"fileNames": ["mini-player-gestures.patch"],
|
"fileNames": ["mini-player-gestures.patch"],
|
||||||
"extensionFiles": [
|
"extensionFiles": [
|
||||||
@@ -176,11 +176,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "toggle",
|
"type": "choice",
|
||||||
"key": "swipe_up_drag",
|
"key": "swipe_up_drag",
|
||||||
"title": "Enable Drag",
|
"title": "Swipe Up Style",
|
||||||
"description": "Makes player follow your finger when swiping up.",
|
"entries": ["Static", "Drag"],
|
||||||
"default": true,
|
"values": ["0x0", "0x1"],
|
||||||
|
"defaultIndex": 1,
|
||||||
"requiresOption": "gestures",
|
"requiresOption": "gestures",
|
||||||
"token": "RL_MINI_PLAYER_SWIPE_UP_DRAG"
|
"token": "RL_MINI_PLAYER_SWIPE_UP_DRAG"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user