From 2db3cdabd2392e398e6c81bdd9bfc457e34950c9 Mon Sep 17 00:00:00 2001 From: MukeshCheekatla Date: Fri, 8 May 2026 12:59:33 +0530 Subject: [PATCH] feat: add setting to disable vertical swipe gestures in player (#955) --- .../player/PlayerSettingsStorage.android.kt | 21 +++++++++++++++++++ .../composeResources/values/strings.xml | 2 ++ .../nuvio/app/features/player/PlayerScreen.kt | 3 ++- .../player/PlayerSettingsRepository.kt | 13 ++++++++++++ .../features/player/PlayerSettingsStorage.kt | 2 ++ .../features/settings/PlaybackSettingsPage.kt | 11 ++++++++++ .../app/features/settings/SettingsScreen.kt | 6 ++++++ .../player/PlayerSettingsStorage.ios.kt | 19 +++++++++++++++++ 8 files changed, 76 insertions(+), 1 deletion(-) diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.android.kt index 4a589306..4eccfd77 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.android.kt @@ -54,6 +54,7 @@ actual object PlayerSettingsStorage { private const val nextEpisodeThresholdMinutesBeforeEndKey = "next_episode_threshold_minutes_before_end_v2" private const val useLibassKey = "use_libass" private const val libassRenderTypeKey = "libass_render_type" + private const val swipeGesturesEnabledKey = "swipe_gestures_enabled" private val syncKeys = listOf( showLoadingOverlayKey, resizeModeKey, @@ -88,6 +89,7 @@ actual object PlayerSettingsStorage { nextEpisodeThresholdMinutesBeforeEndKey, useLibassKey, libassRenderTypeKey, + swipeGesturesEnabledKey, ) private var preferences: SharedPreferences? = null @@ -614,6 +616,23 @@ actual object PlayerSettingsStorage { ?.apply() } + actual fun loadSwipeGesturesEnabled(): Boolean? = + preferences?.let { sharedPreferences -> + val key = ProfileScopedKey.of(swipeGesturesEnabledKey) + if (sharedPreferences.contains(key)) { + sharedPreferences.getBoolean(key, true) + } else { + null + } + } + + actual fun saveSwipeGesturesEnabled(enabled: Boolean) { + preferences + ?.edit() + ?.putBoolean(ProfileScopedKey.of(swipeGesturesEnabledKey), enabled) + ?.apply() + } + actual fun exportToSyncPayload(): JsonObject = buildJsonObject { loadShowLoadingOverlay()?.let { put(showLoadingOverlayKey, encodeSyncBoolean(it)) } loadResizeMode()?.let { put(resizeModeKey, encodeSyncString(it)) } @@ -648,6 +667,7 @@ actual object PlayerSettingsStorage { loadNextEpisodeThresholdMinutesBeforeEnd()?.let { put(nextEpisodeThresholdMinutesBeforeEndKey, encodeSyncFloat(it)) } loadUseLibass()?.let { put(useLibassKey, encodeSyncBoolean(it)) } loadLibassRenderType()?.let { put(libassRenderTypeKey, encodeSyncString(it)) } + loadSwipeGesturesEnabled()?.let { put(swipeGesturesEnabledKey, encodeSyncBoolean(it)) } } actual fun replaceFromSyncPayload(payload: JsonObject) { @@ -690,5 +710,6 @@ actual object PlayerSettingsStorage { payload.decodeSyncFloat(nextEpisodeThresholdMinutesBeforeEndKey)?.let(::saveNextEpisodeThresholdMinutesBeforeEnd) payload.decodeSyncBoolean(useLibassKey)?.let(::saveUseLibass) payload.decodeSyncString(libassRenderTypeKey)?.let(::saveLibassRenderType) + payload.decodeSyncBoolean(swipeGesturesEnabledKey)?.let(::saveSwipeGesturesEnabled) } } diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index f235570b..e9d7d4e6 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -657,6 +657,8 @@ Hold Speed Hold To Speed Long-press anywhere on the player surface to temporarily boost playback speed. + Swipe Gestures + Allow vertical swiping on the player surface to control volume and brightness. Invalid regex pattern Last Link Cache Duration DV7 - HEVC Fallback diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt index fc24fba4..ac24e7e6 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt @@ -1454,7 +1454,8 @@ fun PlayerScreen( abs(totalDx) > viewConfiguration.touchSlop && abs(totalDx) > abs(totalDy) val verticalDominant = - abs(totalDy) > viewConfiguration.touchSlop && abs(totalDy) > abs(totalDx) + playerSettingsUiState.swipeGesturesEnabled && + abs(totalDy) > viewConfiguration.touchSlop && abs(totalDy) > abs(totalDx) gestureMode = when { horizontalDominant -> { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt index ec58911e..f66d1753 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt @@ -41,6 +41,7 @@ data class PlayerSettingsUiState( val nextEpisodeThresholdMinutesBeforeEnd: Float = 2f, val useLibass: Boolean = false, val libassRenderType: String = "CUES", + val swipeGesturesEnabled: Boolean = true, ) object PlayerSettingsRepository { @@ -80,6 +81,7 @@ object PlayerSettingsRepository { private var nextEpisodeThresholdMinutesBeforeEnd = 2f private var useLibass = false private var libassRenderType = "CUES" + private var swipeGesturesEnabled = true fun ensureLoaded() { if (hasLoaded) return @@ -124,6 +126,7 @@ object PlayerSettingsRepository { nextEpisodeThresholdMinutesBeforeEnd = 2f useLibass = false libassRenderType = "CUES" + swipeGesturesEnabled = true publish() } @@ -195,6 +198,7 @@ object PlayerSettingsRepository { nextEpisodeThresholdMinutesBeforeEnd = PlayerSettingsStorage.loadNextEpisodeThresholdMinutesBeforeEnd() ?: 2f useLibass = PlayerSettingsStorage.loadUseLibass() ?: false libassRenderType = PlayerSettingsStorage.loadLibassRenderType() ?: "CUES" + swipeGesturesEnabled = PlayerSettingsStorage.loadSwipeGesturesEnabled() ?: true publish() } @@ -464,6 +468,14 @@ object PlayerSettingsRepository { PlayerSettingsStorage.saveLibassRenderType(renderType) } + fun setSwipeGesturesEnabled(enabled: Boolean) { + ensureLoaded() + if (swipeGesturesEnabled == enabled) return + swipeGesturesEnabled = enabled + publish() + PlayerSettingsStorage.saveSwipeGesturesEnabled(enabled) + } + private fun publish() { _uiState.value = PlayerSettingsUiState( showLoadingOverlay = showLoadingOverlay, @@ -498,6 +510,7 @@ object PlayerSettingsRepository { nextEpisodeThresholdMinutesBeforeEnd = nextEpisodeThresholdMinutesBeforeEnd, useLibass = useLibass, libassRenderType = libassRenderType, + swipeGesturesEnabled = swipeGesturesEnabled, ) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.kt index efc6b6c2..cc90eb72 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.kt @@ -74,6 +74,8 @@ internal expect object PlayerSettingsStorage { fun saveUseLibass(enabled: Boolean) fun loadLibassRenderType(): String? fun saveLibassRenderType(renderType: String) + fun loadSwipeGesturesEnabled(): Boolean? + fun saveSwipeGesturesEnabled(enabled: Boolean) fun exportToSyncPayload(): JsonObject fun replaceFromSyncPayload(payload: JsonObject) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt index 18a9c422..632c6203 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt @@ -84,6 +84,7 @@ internal fun LazyListScope.playbackSettingsContent( tunnelingEnabled: Boolean, useLibass: Boolean, libassRenderType: String, + swipeGesturesEnabled: Boolean, ) { item { PlaybackSettingsSection( @@ -102,6 +103,7 @@ internal fun LazyListScope.playbackSettingsContent( tunnelingEnabled = tunnelingEnabled, useLibass = useLibass, libassRenderType = libassRenderType, + swipeGesturesEnabled = swipeGesturesEnabled, ) } } @@ -164,6 +166,7 @@ private fun PlaybackSettingsSection( tunnelingEnabled: Boolean, useLibass: Boolean, libassRenderType: String, + swipeGesturesEnabled: Boolean, ) { var showPreferredAudioDialog by remember { mutableStateOf(false) } var showSecondaryAudioDialog by remember { mutableStateOf(false) } @@ -222,6 +225,14 @@ private fun PlaybackSettingsSection( onClick = { showHoldToSpeedValueDialog = true }, ) } + SettingsGroupDivider(isTablet = isTablet) + SettingsSwitchRow( + title = stringResource(Res.string.settings_playback_enable_swipe_gestures), + description = stringResource(Res.string.settings_playback_enable_swipe_gestures_description), + checked = swipeGesturesEnabled, + isTablet = isTablet, + onCheckedChange = PlayerSettingsRepository::setSwipeGesturesEnabled, + ) } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsScreen.kt index b625c9dc..974ba165 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsScreen.kt @@ -193,6 +193,7 @@ fun SettingsScreen( tunnelingEnabled = playerSettingsUiState.tunnelingEnabled, useLibass = playerSettingsUiState.useLibass, libassRenderType = playerSettingsUiState.libassRenderType, + swipeGesturesEnabled = playerSettingsUiState.swipeGesturesEnabled, selectedTheme = selectedTheme, onThemeSelected = ThemeSettingsRepository::setTheme, amoledEnabled = amoledEnabled, @@ -238,6 +239,7 @@ fun SettingsScreen( tunnelingEnabled = playerSettingsUiState.tunnelingEnabled, useLibass = playerSettingsUiState.useLibass, libassRenderType = playerSettingsUiState.libassRenderType, + swipeGesturesEnabled = playerSettingsUiState.swipeGesturesEnabled, selectedTheme = selectedTheme, onThemeSelected = ThemeSettingsRepository::setTheme, amoledEnabled = amoledEnabled, @@ -293,6 +295,7 @@ private fun MobileSettingsScreen( tunnelingEnabled: Boolean, useLibass: Boolean, libassRenderType: String, + swipeGesturesEnabled: Boolean, selectedTheme: AppTheme, onThemeSelected: (AppTheme) -> Unit, amoledEnabled: Boolean, @@ -374,6 +377,7 @@ private fun MobileSettingsScreen( tunnelingEnabled = tunnelingEnabled, useLibass = useLibass, libassRenderType = libassRenderType, + swipeGesturesEnabled = swipeGesturesEnabled, ) SettingsPage.Appearance -> appearanceSettingsContent( isTablet = false, @@ -471,6 +475,7 @@ private fun TabletSettingsScreen( tunnelingEnabled: Boolean, useLibass: Boolean, libassRenderType: String, + swipeGesturesEnabled: Boolean, selectedTheme: AppTheme, onThemeSelected: (AppTheme) -> Unit, amoledEnabled: Boolean, @@ -624,6 +629,7 @@ private fun TabletSettingsScreen( tunnelingEnabled = tunnelingEnabled, useLibass = useLibass, libassRenderType = libassRenderType, + swipeGesturesEnabled = swipeGesturesEnabled, ) SettingsPage.Appearance -> appearanceSettingsContent( isTablet = true, diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.ios.kt index 3f63f5db..d396c5d4 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.ios.kt @@ -52,6 +52,7 @@ actual object PlayerSettingsStorage { private const val nextEpisodeThresholdMinutesBeforeEndKey = "next_episode_threshold_minutes_before_end_v2" private const val useLibassKey = "use_libass" private const val libassRenderTypeKey = "libass_render_type" + private const val swipeGesturesEnabledKey = "swipe_gestures_enabled" private val syncKeys = listOf( showLoadingOverlayKey, resizeModeKey, @@ -86,6 +87,7 @@ actual object PlayerSettingsStorage { nextEpisodeThresholdMinutesBeforeEndKey, useLibassKey, libassRenderTypeKey, + swipeGesturesEnabledKey, ) actual fun loadShowLoadingOverlay(): Boolean? { @@ -98,6 +100,21 @@ actual object PlayerSettingsStorage { } } + actual fun loadSwipeGesturesEnabled(): Boolean? { + val defaults = NSUserDefaults.standardUserDefaults + val key = ProfileScopedKey.of(swipeGesturesEnabledKey) + return if (defaults.objectForKey(key) != null) { + defaults.boolForKey(key) + } else { + null + } + } + + actual fun saveSwipeGesturesEnabled(enabled: Boolean) { + val defaults = NSUserDefaults.standardUserDefaults + defaults.setBool(enabled, ProfileScopedKey.of(swipeGesturesEnabledKey)) + } + actual fun saveShowLoadingOverlay(enabled: Boolean) { NSUserDefaults.standardUserDefaults.setBool(enabled, forKey = ProfileScopedKey.of(showLoadingOverlayKey)) } @@ -552,6 +569,7 @@ actual object PlayerSettingsStorage { loadNextEpisodeThresholdMinutesBeforeEnd()?.let { put(nextEpisodeThresholdMinutesBeforeEndKey, encodeSyncFloat(it)) } loadUseLibass()?.let { put(useLibassKey, encodeSyncBoolean(it)) } loadLibassRenderType()?.let { put(libassRenderTypeKey, encodeSyncString(it)) } + loadSwipeGesturesEnabled()?.let { put(swipeGesturesEnabledKey, encodeSyncBoolean(it)) } } actual fun replaceFromSyncPayload(payload: JsonObject) { @@ -593,5 +611,6 @@ actual object PlayerSettingsStorage { payload.decodeSyncFloat(nextEpisodeThresholdMinutesBeforeEndKey)?.let(::saveNextEpisodeThresholdMinutesBeforeEnd) payload.decodeSyncBoolean(useLibassKey)?.let(::saveUseLibass) payload.decodeSyncString(libassRenderTypeKey)?.let(::saveLibassRenderType) + payload.decodeSyncBoolean(swipeGesturesEnabledKey)?.let(::saveSwipeGesturesEnabled) } }