From 3529ae1b787e47c4540f94fc04c6c19f85e027d9 Mon Sep 17 00:00:00 2001 From: The-R0 <2861295907@qq.com> Date: Thu, 12 Nov 2026 10:55:20 +0800 Subject: [PATCH] feat(player,settings): add toggle for hold-speed dial (enabled by default) --- .../composeResources/values-en/strings.xml | 4 +++- .../composeResources/values/strings.xml | 4 +++- .../kotlin/dev/bilby/data/SettingsStore.kt | 7 +++++++ .../kotlin/dev/bilby/ui/BilbyApp.kt | 2 ++ .../kotlin/dev/bilby/ui/player/PlayerShell.kt | 8 ++++++-- .../bilby/ui/player/SpeedSelectionGestures.kt | 9 +++++---- .../bilby/ui/player/SpeedSelectionOverlay.kt | 19 +++++++++++-------- .../dev/bilby/ui/settings/SettingsPages.kt | 15 ++++++++++++++- .../bilby/ui/settings/SettingsViewModel.kt | 7 +++++++ .../kotlin/dev/bilby/ui/video/VideoScreen.kt | 10 ++++++---- 10 files changed, 64 insertions(+), 21 deletions(-) diff --git a/shared/src/commonMain/composeResources/values-en/strings.xml b/shared/src/commonMain/composeResources/values-en/strings.xml index 899e7eb..9365cad 100644 --- a/shared/src/commonMain/composeResources/values-en/strings.xml +++ b/shared/src/commonMain/composeResources/values-en/strings.xml @@ -427,8 +427,10 @@ Gestures Double-tap, hold speed and swipe controls Hold for temporary speed - Hold the picture to speed up, drag to adjust or lock + Hold the picture to speed up Default hold speed + Hold speed dial + Show the speed dial while holding to drag or lock Swipe horizontally to seek Drag left or right on the picture to scrub Swipe vertically for brightness and volume diff --git a/shared/src/commonMain/composeResources/values/strings.xml b/shared/src/commonMain/composeResources/values/strings.xml index 03e044f..e337c67 100644 --- a/shared/src/commonMain/composeResources/values/strings.xml +++ b/shared/src/commonMain/composeResources/values/strings.xml @@ -510,8 +510,10 @@ 快捷手势 双击进退、长按倍速与滑动调节 长按倍速 - 按住画面临时加速播放,滑动可切换倍速并锁定 + 按住画面临时加速播放 默认长按倍速 + 长按倍速条 + 长按时展开倍速选档条,滑动可切换倍速并锁定 横向滑动调节进度 在画面上左右拖动跳转播放位置 纵向滑动调节亮度与音量 diff --git a/shared/src/commonMain/kotlin/dev/bilby/data/SettingsStore.kt b/shared/src/commonMain/kotlin/dev/bilby/data/SettingsStore.kt index dba0194..e46d455 100644 --- a/shared/src/commonMain/kotlin/dev/bilby/data/SettingsStore.kt +++ b/shared/src/commonMain/kotlin/dev/bilby/data/SettingsStore.kt @@ -148,6 +148,7 @@ class SettingsStore(private val store: DataStore) { playerPickUpdatesDefault = p[KEY_PLAYER_PICK_UPDATES_DEFAULT] ?: false, doubleTapSeek = p[KEY_DOUBLE_TAP_SEEK] ?: true, longPressSpeed = p[KEY_LONG_PRESS_SPEED] ?: true, + longPressSpeedDial = p[KEY_LONG_PRESS_SPEED_DIAL] ?: true, defaultHoldSpeed = (p[KEY_DEFAULT_HOLD_SPEED] ?: p[KEY_LEGACY_HOLD_SPEED] ?: DEFAULT_HOLD_SPEED) .takeIf { it in HOLD_SPEED_OPTIONS } ?: DEFAULT_HOLD_SPEED, swipeSeek = p[KEY_SWIPE_SEEK] ?: true, @@ -180,6 +181,10 @@ class SettingsStore(private val store: DataStore) { store.edit { p -> p[KEY_LONG_PRESS_SPEED] = enabled } } + suspend fun saveLongPressSpeedDial(enabled: Boolean) { + store.edit { p -> p[KEY_LONG_PRESS_SPEED_DIAL] = enabled } + } + suspend fun saveDefaultHoldSpeed(speed: Float) { val valid = speed.takeIf { it in HOLD_SPEED_OPTIONS } ?: DEFAULT_HOLD_SPEED store.edit { p -> p[KEY_DEFAULT_HOLD_SPEED] = valid } @@ -538,6 +543,7 @@ class SettingsStore(private val store: DataStore) { private val KEY_DOUBLE_TAP_SEEK = booleanPreferencesKey("player_double_tap_seek") private val KEY_LONG_PRESS_SPEED = booleanPreferencesKey("player_long_press_speed") + private val KEY_LONG_PRESS_SPEED_DIAL = booleanPreferencesKey("player_long_press_speed_dial") // 沿用 v0.18.1 已发布的键名,升级时不丢用户已选的默认长按倍速。 private val KEY_DEFAULT_HOLD_SPEED = floatPreferencesKey("player_fast_forward_speed") private val KEY_LEGACY_HOLD_SPEED = floatPreferencesKey("player_default_hold_speed") @@ -758,6 +764,7 @@ data class PlayerPrefs( val playerPickUpdatesDefault: Boolean = false, val doubleTapSeek: Boolean = true, val longPressSpeed: Boolean = true, + val longPressSpeedDial: Boolean = true, val defaultHoldSpeed: Float = SettingsStore.DEFAULT_HOLD_SPEED, val swipeSeek: Boolean = true, val swipeBrightnessVolume: Boolean = true, diff --git a/shared/src/commonMain/kotlin/dev/bilby/ui/BilbyApp.kt b/shared/src/commonMain/kotlin/dev/bilby/ui/BilbyApp.kt index 56c574c..b3b8b3c 100644 --- a/shared/src/commonMain/kotlin/dev/bilby/ui/BilbyApp.kt +++ b/shared/src/commonMain/kotlin/dev/bilby/ui/BilbyApp.kt @@ -1791,6 +1791,7 @@ private fun SettingsPageRoute( state = state, onDoubleTapSeekChange = vm::setDoubleTapSeek, onLongPressSpeedChange = vm::setLongPressSpeed, + onLongPressSpeedDialChange = vm::setLongPressSpeedDial, onDefaultHoldSpeedChange = vm::setDefaultHoldSpeed, onSwipeSeekChange = vm::setSwipeSeek, onSwipeBrightnessVolumeChange = vm::setSwipeBrightnessVolume, @@ -3186,6 +3187,7 @@ private fun VideoPane( doubleTapSeek = playerPrefs.doubleTapSeek, brightnessAndVolume = playerPrefs.swipeBrightnessVolume, fastForward = playerPrefs.longPressSpeed, + fastForwardDial = playerPrefs.longPressSpeedDial, ), defaultHoldSpeed = playerPrefs.defaultHoldSpeed, panelPrefs = panelPrefs, diff --git a/shared/src/commonMain/kotlin/dev/bilby/ui/player/PlayerShell.kt b/shared/src/commonMain/kotlin/dev/bilby/ui/player/PlayerShell.kt index 3c020a6..f75cd8c 100644 --- a/shared/src/commonMain/kotlin/dev/bilby/ui/player/PlayerShell.kt +++ b/shared/src/commonMain/kotlin/dev/bilby/ui/player/PlayerShell.kt @@ -125,6 +125,8 @@ data class PlayerGestureOptions( val brightnessAndVolume: Boolean = true, /** 长按选择临时倍速。 */ val fastForward: Boolean = true, + /** 长按时展开倍速选档条并允许滑动切换或锁定倍速;关闭后仅按默认倍速临时加速并在画面顶部显示提示。 */ + val fastForwardDial: Boolean = true, ) /** @@ -932,7 +934,8 @@ fun PlayerShell( .speedSelectionLongPress( speedController, enabled = gestures.fastForward && !locked && !pip, - forceHorizontalBelow = !showSpeedOverlay, + dialEnabled = gestures.fastForwardDial, + forceHorizontalBelow = !showSpeedOverlay && gestures.fastForwardDial, onStart = { controlsVisible = false gesture = null @@ -1249,10 +1252,11 @@ fun PlayerShell( } } - if (showSpeedOverlay && speedController != null && !pip) { + if ((showSpeedOverlay || !gestures.fastForwardDial) && speedController != null && !pip) { SpeedSelectionOverlay( controller = speedController, isFullscreen = isFullscreen, + showDial = gestures.fastForwardDial, modifier = Modifier.matchParentSize(), ) } diff --git a/shared/src/commonMain/kotlin/dev/bilby/ui/player/SpeedSelectionGestures.kt b/shared/src/commonMain/kotlin/dev/bilby/ui/player/SpeedSelectionGestures.kt index edc97ac..903bb5d 100644 --- a/shared/src/commonMain/kotlin/dev/bilby/ui/player/SpeedSelectionGestures.kt +++ b/shared/src/commonMain/kotlin/dev/bilby/ui/player/SpeedSelectionGestures.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.platform.LocalWindowInfo internal fun Modifier.speedSelectionLongPress( controller: PlaybackSpeedController?, enabled: Boolean, + dialEnabled: Boolean = true, forceHorizontalBelow: Boolean = false, onStart: () -> Unit, ): Modifier { @@ -22,7 +23,7 @@ internal fun Modifier.speedSelectionLongPress( val haptics = LocalHapticFeedback.current val window = LocalWindowInfo.current.containerSize val vertical = !forceHorizontalBelow && window.width > window.height - return pointerInput(controller, enabled, vertical, forceHorizontalBelow) { + return pointerInput(controller, enabled, dialEnabled, vertical, forceHorizontalBelow) { if (controller == null || !enabled) return@pointerInput var owned = false var dockEnd = false @@ -31,10 +32,10 @@ internal fun Modifier.speedSelectionLongPress( try { detectDragGesturesAfterLongPress( onDragStart = { startOffset -> - dockEnd = vertical && startOffset.x < size.width / 2f + dockEnd = dialEnabled && vertical && startOffset.x < size.width / 2f // 竖屏内嵌时选档块画在画面下方(forceHorizontalBelow),一律下滑锁定; // 竖屏全屏时偏下长按把选档条放画面上沿(上滑锁定),偏上长按放画面下沿(下滑锁定)。 - dockTop = !vertical && !forceHorizontalBelow && startOffset.y >= size.height / 2f + dockTop = dialEnabled && !vertical && !forceHorizontalBelow && startOffset.y >= size.height / 2f controller.open(temporary = true, dockEnd = dockEnd, dockTop = dockTop) owned = controller.beginDrag(vertical = vertical) displacement = Offset.Zero @@ -51,7 +52,7 @@ internal fun Modifier.speedSelectionLongPress( }, ) { change, amount -> change.consume() - if (!owned) return@detectDragGesturesAfterLongPress + if (!owned || !dialEnabled) return@detectDragGesturesAfterLongPress displacement += amount val before = controller.selection val acrossPx = when { diff --git a/shared/src/commonMain/kotlin/dev/bilby/ui/player/SpeedSelectionOverlay.kt b/shared/src/commonMain/kotlin/dev/bilby/ui/player/SpeedSelectionOverlay.kt index 997b915..a61a6cc 100644 --- a/shared/src/commonMain/kotlin/dev/bilby/ui/player/SpeedSelectionOverlay.kt +++ b/shared/src/commonMain/kotlin/dev/bilby/ui/player/SpeedSelectionOverlay.kt @@ -119,6 +119,7 @@ private data class SpeedHintState( private fun rememberSpeedHintState( controller: PlaybackSpeedController, forceHorizontalBelow: Boolean, + showDial: Boolean = true, ): SpeedHintState { val selection = controller.selection val window = LocalWindowInfo.current.containerSize @@ -129,6 +130,7 @@ private fun rememberSpeedHintState( val dockTop = !vertical && !forceHorizontalBelow && selection?.dockTop == true val text = when { locked != null -> stringResource(Res.string.player_speed_locked, formatSpeed(locked / 100f)) + !showDial -> formatSpeed(selection?.speed ?: controller.speed) releaseToLock -> stringResource(Res.string.player_speed_release_to_lock) !vertical && dockTop -> stringResource(Res.string.player_speed_coarse_horizontal_top) !vertical -> stringResource(Res.string.player_speed_coarse_horizontal) @@ -185,6 +187,7 @@ private fun SpeedHintCapsule( internal fun SpeedSelectionOverlay( controller: PlaybackSpeedController, isFullscreen: Boolean = false, + showDial: Boolean = true, modifier: Modifier = Modifier, ) { var shownSelection by remember { mutableStateOf(null) } @@ -200,10 +203,10 @@ internal fun SpeedSelectionOverlay( val activeOrLast = controller.selection ?: shownSelection val dockEnd = vertical && activeOrLast?.dockEnd == true val dockTop = !vertical && activeOrLast?.dockTop == true - val hintState = rememberSpeedHintState(controller, forceHorizontalBelow = false) + val hintState = rememberSpeedHintState(controller, forceHorizontalBelow = false, showDial = showDial) val focus = remember { FocusRequester() } - BackHandler(enabled = controller.active) { controller.cancel() } - LaunchedEffect(controller.active) { if (controller.active) focus.requestFocus() } + BackHandler(enabled = controller.active && showDial) { controller.cancel() } + LaunchedEffect(controller.active, showDial) { if (controller.active && showDial) focus.requestFocus() } PlayerTheme { val motion = MaterialTheme.motionScheme val spatialOffset = motion.fastSpatialSpec() @@ -211,7 +214,7 @@ internal fun SpeedSelectionOverlay( Box( modifier.fillMaxSize() .onKeyEvent { event -> - if (event.type != KeyEventType.KeyDown || !controller.active) return@onKeyEvent false + if (event.type != KeyEventType.KeyDown || !controller.active || !showDial) return@onKeyEvent false when (event.key) { Key.Escape -> controller.cancel() Key.Enter -> { controller.lock(); controller.finish() } @@ -238,13 +241,13 @@ internal fun SpeedSelectionOverlay( else -> return@onKeyEvent false } true - }.focusRequester(focus).focusable(), + }.then(if (showDial) Modifier.focusRequester(focus).focusable() else Modifier), ) { - if (controller.active) { + if (controller.active && showDial) { Box(Modifier.fillMaxSize().pointerInput(controller) { detectTapGestures { controller.finish() } }) } AnimatedVisibility( - visible = controller.active, + visible = controller.active && showDial, enter = (when { !vertical && dockTop -> slideInVertically(spatialOffset) { -it / 2 } !vertical -> slideInVertically(spatialOffset) { it / 2 } @@ -308,7 +311,7 @@ internal fun SpeedSelectionOverlay( ) } } - val hintAtBottom = !vertical && dockTop + val hintAtBottom = showDial && !vertical && dockTop val hintInsetSides = WindowInsetsSides.Horizontal + if (hintAtBottom) WindowInsetsSides.Bottom else WindowInsetsSides.Top AnimatedVisibility( diff --git a/shared/src/commonMain/kotlin/dev/bilby/ui/settings/SettingsPages.kt b/shared/src/commonMain/kotlin/dev/bilby/ui/settings/SettingsPages.kt index 267bbbf..52d13a7 100644 --- a/shared/src/commonMain/kotlin/dev/bilby/ui/settings/SettingsPages.kt +++ b/shared/src/commonMain/kotlin/dev/bilby/ui/settings/SettingsPages.kt @@ -452,6 +452,7 @@ fun GestureSettingsPage( state: SettingsUiState, onDoubleTapSeekChange: (Boolean) -> Unit, onLongPressSpeedChange: (Boolean) -> Unit, + onLongPressSpeedDialChange: (Boolean) -> Unit = {}, onDefaultHoldSpeedChange: (Float) -> Unit, onSwipeSeekChange: (Boolean) -> Unit, onSwipeBrightnessVolumeChange: (Boolean) -> Unit, @@ -490,9 +491,21 @@ fun GestureSettingsPage( if (holdSpeedOnScreen) { row { position -> AnimatedVisibility(visibleState = holdSpeedShown, enter = enter, exit = exit) { - ChoiceRow( + ToggleSettingRow( position = position, icon = Icons.Outlined.Tune, + title = stringResource(Res.string.settings_long_press_speed_dial), + subtitle = stringResource(Res.string.settings_long_press_speed_dial_subtitle), + checked = state.longPressSpeedDial, + onCheckedChange = onLongPressSpeedDialChange, + ) + } + } + row { position -> + AnimatedVisibility(visibleState = holdSpeedShown, enter = enter, exit = exit) { + ChoiceRow( + position = position, + icon = Icons.Outlined.Speed, title = stringResource(Res.string.settings_default_hold_speed), options = SettingsStore.HOLD_SPEED_OPTIONS, selected = state.defaultHoldSpeed, diff --git a/shared/src/commonMain/kotlin/dev/bilby/ui/settings/SettingsViewModel.kt b/shared/src/commonMain/kotlin/dev/bilby/ui/settings/SettingsViewModel.kt index 59c78ba..be3f1f4 100644 --- a/shared/src/commonMain/kotlin/dev/bilby/ui/settings/SettingsViewModel.kt +++ b/shared/src/commonMain/kotlin/dev/bilby/ui/settings/SettingsViewModel.kt @@ -51,6 +51,7 @@ data class SettingsUiState( val hardwareCodecIds: Set = emptySet(), val doubleTapSeek: Boolean = true, val longPressSpeed: Boolean = true, + val longPressSpeedDial: Boolean = true, val defaultHoldSpeed: Float = SettingsStore.DEFAULT_HOLD_SPEED, val swipeSeek: Boolean = true, val swipeBrightnessVolume: Boolean = true, @@ -185,6 +186,7 @@ class SettingsViewModel( playerPickUpdatesDefault = prefs.playerPickUpdatesDefault, doubleTapSeek = prefs.doubleTapSeek, longPressSpeed = prefs.longPressSpeed, + longPressSpeedDial = prefs.longPressSpeedDial, defaultHoldSpeed = prefs.defaultHoldSpeed, swipeSeek = prefs.swipeSeek, swipeBrightnessVolume = prefs.swipeBrightnessVolume, @@ -314,6 +316,11 @@ class SettingsViewModel( persist { settings.saveLongPressSpeed(value) } } + fun setLongPressSpeedDial(value: Boolean) { + _state.update { it.copy(longPressSpeedDial = value) } + persist { settings.saveLongPressSpeedDial(value) } + } + fun setDefaultHoldSpeed(value: Float) { _state.update { it.copy(defaultHoldSpeed = value) } persist { settings.saveDefaultHoldSpeed(value) } diff --git a/shared/src/commonMain/kotlin/dev/bilby/ui/video/VideoScreen.kt b/shared/src/commonMain/kotlin/dev/bilby/ui/video/VideoScreen.kt index 95f370f..4d7b547 100644 --- a/shared/src/commonMain/kotlin/dev/bilby/ui/video/VideoScreen.kt +++ b/shared/src/commonMain/kotlin/dev/bilby/ui/video/VideoScreen.kt @@ -1324,10 +1324,12 @@ fun VideoScreen( .background(MaterialTheme.colorScheme.surface) .windowInsetsPadding(WindowInsets.horizontalCutout), ) - BelowVideoSpeedOverlay( - controller = speedController, - modifier = Modifier.fillMaxSize(), - ) + if (gestures.fastForwardDial) { + BelowVideoSpeedOverlay( + controller = speedController, + modifier = Modifier.fillMaxSize(), + ) + } } } }