From 766970bce71340096f5a21f1f315d105342b25b1 Mon Sep 17 00:00:00 2001 From: MukeshCheekatla Date: Fri, 8 May 2026 14:14:32 +0530 Subject: [PATCH] feat: implement swipe-to-seek toggle and sensitivity settings (#936) --- .../player/PlayerSettingsStorage.android.kt | 35 +++++ .../composeResources/values/strings.xml | 7 + .../nuvio/app/features/player/PlayerScreen.kt | 7 +- .../player/PlayerSettingsRepository.kt | 46 +++++++ .../features/player/PlayerSettingsStorage.kt | 4 + .../features/settings/PlaybackSettingsPage.kt | 123 ++++++++++++++++++ .../app/features/settings/SettingsScreen.kt | 15 +++ .../player/PlayerSettingsStorage.ios.kt | 30 +++++ 8 files changed, 264 insertions(+), 3 deletions(-) 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..58bdd413 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,8 @@ 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 swipeToSeekEnabledKey = "swipe_to_seek_enabled" + private const val swipeToSeekSensitivityKey = "swipe_to_seek_sensitivity" private val syncKeys = listOf( showLoadingOverlayKey, resizeModeKey, @@ -88,6 +90,8 @@ actual object PlayerSettingsStorage { nextEpisodeThresholdMinutesBeforeEndKey, useLibassKey, libassRenderTypeKey, + swipeToSeekEnabledKey, + swipeToSeekSensitivityKey, ) private var preferences: SharedPreferences? = null @@ -648,6 +652,8 @@ actual object PlayerSettingsStorage { loadNextEpisodeThresholdMinutesBeforeEnd()?.let { put(nextEpisodeThresholdMinutesBeforeEndKey, encodeSyncFloat(it)) } loadUseLibass()?.let { put(useLibassKey, encodeSyncBoolean(it)) } loadLibassRenderType()?.let { put(libassRenderTypeKey, encodeSyncString(it)) } + loadSwipeToSeekEnabled()?.let { put(swipeToSeekEnabledKey, encodeSyncBoolean(it)) } + loadSwipeToSeekSensitivity()?.let { put(swipeToSeekSensitivityKey, encodeSyncString(it)) } } actual fun replaceFromSyncPayload(payload: JsonObject) { @@ -690,5 +696,34 @@ actual object PlayerSettingsStorage { payload.decodeSyncFloat(nextEpisodeThresholdMinutesBeforeEndKey)?.let(::saveNextEpisodeThresholdMinutesBeforeEnd) payload.decodeSyncBoolean(useLibassKey)?.let(::saveUseLibass) payload.decodeSyncString(libassRenderTypeKey)?.let(::saveLibassRenderType) + payload.decodeSyncBoolean(swipeToSeekEnabledKey)?.let(::saveSwipeToSeekEnabled) + payload.decodeSyncString(swipeToSeekSensitivityKey)?.let(::saveSwipeToSeekSensitivity) + } + + actual fun loadSwipeToSeekEnabled(): Boolean? = + preferences?.let { sharedPreferences -> + val key = ProfileScopedKey.of(swipeToSeekEnabledKey) + if (sharedPreferences.contains(key)) { + sharedPreferences.getBoolean(key, true) + } else { + null + } + } + + actual fun saveSwipeToSeekEnabled(enabled: Boolean) { + preferences + ?.edit() + ?.putBoolean(ProfileScopedKey.of(swipeToSeekEnabledKey), enabled) + ?.apply() + } + + actual fun loadSwipeToSeekSensitivity(): String? = + preferences?.getString(ProfileScopedKey.of(swipeToSeekSensitivityKey), null) + + actual fun saveSwipeToSeekSensitivity(sensitivity: String) { + preferences + ?.edit() + ?.putString(ProfileScopedKey.of(swipeToSeekSensitivityKey), sensitivity) + ?.apply() } } diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index f235570b..3ed33b85 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -678,6 +678,13 @@ Matches against stream name/title/description/addon/url. Example: 4K|2160p|Remux Regex Pattern No pattern set. Example: 4K|2160p|Remux + Swipe to Seek + Swipe horizontally on the player surface to seek forward or backward. + Swipe to Seek Sensitivity + Adjust the drag distance required to trigger a seek. + Low + Medium + High Any 1080p+ AVC / x264 BluRay Quality 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..b6320e8a 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 @@ -1450,8 +1450,9 @@ fun PlayerScreen( if (gestureMode == null) { val horizontalDominant = - !isHoldToSpeedGestureActiveState.value && - abs(totalDx) > viewConfiguration.touchSlop && + playerSettingsUiState.swipeToSeekEnabled && + !isHoldToSpeedGestureActiveState.value && + abs(totalDx) > viewConfiguration.touchSlop * playerSettingsUiState.swipeToSeekSensitivity.triggerMultiplier && abs(totalDx) > abs(totalDy) val verticalDominant = abs(totalDy) > viewConfiguration.touchSlop && abs(totalDy) > abs(totalDx) @@ -1486,7 +1487,7 @@ fun PlayerScreen( else -> 60f } val previewOffsetMs = - ((totalDx / width) * sensitivitySeconds * 1000f).roundToLong() + ((totalDx / width) * sensitivitySeconds * playerSettingsUiState.swipeToSeekSensitivity.speedMultiplier * 1000f).roundToLong() val unclampedPreviewMs = horizontalSeekBaselineMs + previewOffsetMs horizontalSeekPreviewMs = currentDurationMsState.value .takeIf { it > 0L } 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..27019ed7 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 @@ -8,6 +8,24 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +enum class SwipeToSeekSensitivity { + LOW, MEDIUM, HIGH; + + val triggerMultiplier: Float + get() = when (this) { + LOW -> 4.0f + MEDIUM -> 1.0f + HIGH -> 0.5f + } + + val speedMultiplier: Float + get() = when (this) { + LOW -> 0.5f + MEDIUM -> 1.0f + HIGH -> 2.0f + } +} + data class PlayerSettingsUiState( val showLoadingOverlay: Boolean = true, val resizeMode: PlayerResizeMode = PlayerResizeMode.Fit, @@ -29,6 +47,8 @@ data class PlayerSettingsUiState( val streamAutoPlaySelectedPlugins: Set = emptySet(), val streamAutoPlayRegex: String = "", val streamAutoPlayTimeoutSeconds: Int = 3, + val swipeToSeekEnabled: Boolean = true, + val swipeToSeekSensitivity: SwipeToSeekSensitivity = SwipeToSeekSensitivity.MEDIUM, val skipIntroEnabled: Boolean = true, val animeSkipEnabled: Boolean = false, val animeSkipClientId: String = "", @@ -80,6 +100,8 @@ object PlayerSettingsRepository { private var nextEpisodeThresholdMinutesBeforeEnd = 2f private var useLibass = false private var libassRenderType = "CUES" + private var swipeToSeekEnabled = true + private var swipeToSeekSensitivity = SwipeToSeekSensitivity.MEDIUM fun ensureLoaded() { if (hasLoaded) return @@ -195,6 +217,12 @@ object PlayerSettingsRepository { nextEpisodeThresholdMinutesBeforeEnd = PlayerSettingsStorage.loadNextEpisodeThresholdMinutesBeforeEnd() ?: 2f useLibass = PlayerSettingsStorage.loadUseLibass() ?: false libassRenderType = PlayerSettingsStorage.loadLibassRenderType() ?: "CUES" + swipeToSeekEnabled = PlayerSettingsStorage.loadSwipeToSeekEnabled() ?: true + swipeToSeekSensitivity = try { + SwipeToSeekSensitivity.valueOf(PlayerSettingsStorage.loadSwipeToSeekSensitivity() ?: "MEDIUM") + } catch (e: Exception) { + SwipeToSeekSensitivity.MEDIUM + } publish() } @@ -464,6 +492,22 @@ object PlayerSettingsRepository { PlayerSettingsStorage.saveLibassRenderType(renderType) } + fun setSwipeToSeekEnabled(enabled: Boolean) { + ensureLoaded() + if (swipeToSeekEnabled == enabled) return + swipeToSeekEnabled = enabled + publish() + PlayerSettingsStorage.saveSwipeToSeekEnabled(enabled) + } + + fun setSwipeToSeekSensitivity(sensitivity: SwipeToSeekSensitivity) { + ensureLoaded() + if (swipeToSeekSensitivity == sensitivity) return + swipeToSeekSensitivity = sensitivity + publish() + PlayerSettingsStorage.saveSwipeToSeekSensitivity(sensitivity.name) + } + private fun publish() { _uiState.value = PlayerSettingsUiState( showLoadingOverlay = showLoadingOverlay, @@ -498,6 +542,8 @@ object PlayerSettingsRepository { nextEpisodeThresholdMinutesBeforeEnd = nextEpisodeThresholdMinutesBeforeEnd, useLibass = useLibass, libassRenderType = libassRenderType, + swipeToSeekEnabled = swipeToSeekEnabled, + swipeToSeekSensitivity = swipeToSeekSensitivity, ) } 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..30a42997 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,10 @@ internal expect object PlayerSettingsStorage { fun saveUseLibass(enabled: Boolean) fun loadLibassRenderType(): String? fun saveLibassRenderType(renderType: String) + fun loadSwipeToSeekEnabled(): Boolean? + fun saveSwipeToSeekEnabled(enabled: Boolean) + fun loadSwipeToSeekSensitivity(): String? + fun saveSwipeToSeekSensitivity(sensitivity: String) 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..445e722b 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 @@ -54,6 +54,7 @@ import com.nuvio.app.features.addons.AddonRepository import com.nuvio.app.features.player.AudioLanguageOption import com.nuvio.app.features.player.AvailableLanguageOptions import com.nuvio.app.features.player.PlayerSettingsRepository +import com.nuvio.app.features.player.SwipeToSeekSensitivity import com.nuvio.app.features.player.SubtitleLanguageOption import com.nuvio.app.features.player.formatPlaybackSpeedLabel import com.nuvio.app.features.player.languageLabelForCode @@ -84,6 +85,8 @@ internal fun LazyListScope.playbackSettingsContent( tunnelingEnabled: Boolean, useLibass: Boolean, libassRenderType: String, + swipeToSeekEnabled: Boolean, + swipeToSeekSensitivity: SwipeToSeekSensitivity, ) { item { PlaybackSettingsSection( @@ -102,6 +105,8 @@ internal fun LazyListScope.playbackSettingsContent( tunnelingEnabled = tunnelingEnabled, useLibass = useLibass, libassRenderType = libassRenderType, + swipeToSeekEnabled = swipeToSeekEnabled, + swipeToSeekSensitivity = swipeToSeekSensitivity, ) } } @@ -164,6 +169,8 @@ private fun PlaybackSettingsSection( tunnelingEnabled: Boolean, useLibass: Boolean, libassRenderType: String, + swipeToSeekEnabled: Boolean, + swipeToSeekSensitivity: SwipeToSeekSensitivity, ) { var showPreferredAudioDialog by remember { mutableStateOf(false) } var showSecondaryAudioDialog by remember { mutableStateOf(false) } @@ -178,6 +185,7 @@ private fun PlaybackSettingsSection( var showAutoPlayAddonSelectionDialog by remember { mutableStateOf(false) } var showAutoPlayPluginSelectionDialog by remember { mutableStateOf(false) } var showAutoPlayRegexDialog by remember { mutableStateOf(false) } + var showSwipeSensitivityDialog by remember { mutableStateOf(false) } val pluginsEnabled = AppFeaturePolicy.pluginsEnabled val autoPlayPlayerSettings by PlayerSettingsRepository.uiState.collectAsStateWithLifecycle() val addonUiState by AddonRepository.uiState.collectAsStateWithLifecycle() @@ -222,6 +230,27 @@ private fun PlaybackSettingsSection( onClick = { showHoldToSpeedValueDialog = true }, ) } + SettingsGroupDivider(isTablet = isTablet) + SettingsSwitchRow( + title = stringResource(Res.string.settings_playback_swipe_to_seek), + description = stringResource(Res.string.settings_playback_swipe_to_seek_description), + checked = swipeToSeekEnabled, + isTablet = isTablet, + onCheckedChange = PlayerSettingsRepository::setSwipeToSeekEnabled, + ) + if (swipeToSeekEnabled) { + SettingsGroupDivider(isTablet = isTablet) + SettingsNavigationRow( + title = stringResource(Res.string.settings_playback_swipe_to_seek_sensitivity), + description = when (swipeToSeekSensitivity) { + SwipeToSeekSensitivity.LOW -> stringResource(Res.string.settings_playback_swipe_to_seek_sensitivity_low) + SwipeToSeekSensitivity.MEDIUM -> stringResource(Res.string.settings_playback_swipe_to_seek_sensitivity_medium) + SwipeToSeekSensitivity.HIGH -> stringResource(Res.string.settings_playback_swipe_to_seek_sensitivity_high) + }, + isTablet = isTablet, + onClick = { showSwipeSensitivityDialog = true }, + ) + } } } @@ -813,6 +842,14 @@ private fun PlaybackSettingsSection( ) } + if (showSwipeSensitivityDialog) { + SwipeToSeekSensitivityDialog( + selected = swipeToSeekSensitivity, + onSelect = PlayerSettingsRepository::setSwipeToSeekSensitivity, + onDismiss = { showSwipeSensitivityDialog = false }, + ) + } + if (showAutoPlayModeDialog) { StreamAutoPlayModeDialog( selectedMode = autoPlayPlayerSettings.streamAutoPlayMode, @@ -2178,3 +2215,89 @@ private fun libassRenderTypeRes(renderType: String): StringResource = when (rend @Composable private fun libassRenderTypeLabel(renderType: String): String = stringResource(libassRenderTypeRes(renderType)) + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +private fun SwipeToSeekSensitivityDialog( + selected: SwipeToSeekSensitivity, + onSelect: (SwipeToSeekSensitivity) -> Unit, + onDismiss: () -> Unit, +) { + val options = SwipeToSeekSensitivity.entries + + BasicAlertDialog(onDismissRequest = onDismiss) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(20.dp), + color = MaterialTheme.colorScheme.surface, + ) { + Column( + modifier = Modifier.padding(20.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Text( + text = stringResource(Res.string.settings_playback_swipe_to_seek_sensitivity), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.SemiBold, + ) + Text( + text = stringResource(Res.string.settings_playback_swipe_to_seek_sensitivity_description), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + options.forEach { option -> + val isSelected = option == selected + val containerColor = if (isSelected) { + MaterialTheme.colorScheme.primary.copy(alpha = 0.14f) + } else { + MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f) + } + + Surface( + modifier = Modifier + .fillMaxWidth() + .clickable { + onSelect(option) + onDismiss() + }, + shape = RoundedCornerShape(12.dp), + color = containerColor, + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 14.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = when (option) { + SwipeToSeekSensitivity.LOW -> stringResource(Res.string.settings_playback_swipe_to_seek_sensitivity_low) + SwipeToSeekSensitivity.MEDIUM -> stringResource(Res.string.settings_playback_swipe_to_seek_sensitivity_medium) + SwipeToSeekSensitivity.HIGH -> stringResource(Res.string.settings_playback_swipe_to_seek_sensitivity_high) + else -> stringResource(Res.string.settings_playback_swipe_to_seek_sensitivity_medium) + }, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.weight(1f), + ) + if (isSelected) { + Icon( + imageVector = Icons.Rounded.Check, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + ) + } + } + } + } + } + } + } + } +} 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..7cee50f7 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 @@ -55,6 +55,9 @@ import com.nuvio.app.features.mdblist.MdbListSettingsRepository import com.nuvio.app.features.notifications.EpisodeReleaseNotificationsRepository import com.nuvio.app.features.notifications.EpisodeReleaseNotificationsUiState import com.nuvio.app.features.player.PlayerSettingsRepository +import com.nuvio.app.features.player.PlayerResizeMode +import com.nuvio.app.features.player.SwipeToSeekSensitivity +import com.nuvio.app.features.player.skip.NextEpisodeThresholdMode import com.nuvio.app.features.trakt.TraktAuthUiState import com.nuvio.app.features.trakt.TraktAuthRepository import com.nuvio.app.features.trakt.TraktCommentsSettings @@ -193,6 +196,8 @@ fun SettingsScreen( tunnelingEnabled = playerSettingsUiState.tunnelingEnabled, useLibass = playerSettingsUiState.useLibass, libassRenderType = playerSettingsUiState.libassRenderType, + swipeToSeekEnabled = playerSettingsUiState.swipeToSeekEnabled, + swipeToSeekSensitivity = playerSettingsUiState.swipeToSeekSensitivity, selectedTheme = selectedTheme, onThemeSelected = ThemeSettingsRepository::setTheme, amoledEnabled = amoledEnabled, @@ -238,6 +243,8 @@ fun SettingsScreen( tunnelingEnabled = playerSettingsUiState.tunnelingEnabled, useLibass = playerSettingsUiState.useLibass, libassRenderType = playerSettingsUiState.libassRenderType, + swipeToSeekEnabled = playerSettingsUiState.swipeToSeekEnabled, + swipeToSeekSensitivity = playerSettingsUiState.swipeToSeekSensitivity, selectedTheme = selectedTheme, onThemeSelected = ThemeSettingsRepository::setTheme, amoledEnabled = amoledEnabled, @@ -293,6 +300,8 @@ private fun MobileSettingsScreen( tunnelingEnabled: Boolean, useLibass: Boolean, libassRenderType: String, + swipeToSeekEnabled: Boolean, + swipeToSeekSensitivity: SwipeToSeekSensitivity, selectedTheme: AppTheme, onThemeSelected: (AppTheme) -> Unit, amoledEnabled: Boolean, @@ -374,6 +383,8 @@ private fun MobileSettingsScreen( tunnelingEnabled = tunnelingEnabled, useLibass = useLibass, libassRenderType = libassRenderType, + swipeToSeekEnabled = swipeToSeekEnabled, + swipeToSeekSensitivity = swipeToSeekSensitivity, ) SettingsPage.Appearance -> appearanceSettingsContent( isTablet = false, @@ -471,6 +482,8 @@ private fun TabletSettingsScreen( tunnelingEnabled: Boolean, useLibass: Boolean, libassRenderType: String, + swipeToSeekEnabled: Boolean, + swipeToSeekSensitivity: SwipeToSeekSensitivity, selectedTheme: AppTheme, onThemeSelected: (AppTheme) -> Unit, amoledEnabled: Boolean, @@ -624,6 +637,8 @@ private fun TabletSettingsScreen( tunnelingEnabled = tunnelingEnabled, useLibass = useLibass, libassRenderType = libassRenderType, + swipeToSeekEnabled = swipeToSeekEnabled, + swipeToSeekSensitivity = swipeToSeekSensitivity, ) 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..3f133e4b 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,8 @@ 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 swipeToSeekEnabledKey = "swipe_to_seek_enabled" + private const val swipeToSeekSensitivityKey = "swipe_to_seek_sensitivity" private val syncKeys = listOf( showLoadingOverlayKey, resizeModeKey, @@ -86,6 +88,8 @@ actual object PlayerSettingsStorage { nextEpisodeThresholdMinutesBeforeEndKey, useLibassKey, libassRenderTypeKey, + swipeToSeekEnabledKey, + swipeToSeekSensitivityKey, ) actual fun loadShowLoadingOverlay(): Boolean? { @@ -552,6 +556,8 @@ actual object PlayerSettingsStorage { loadNextEpisodeThresholdMinutesBeforeEnd()?.let { put(nextEpisodeThresholdMinutesBeforeEndKey, encodeSyncFloat(it)) } loadUseLibass()?.let { put(useLibassKey, encodeSyncBoolean(it)) } loadLibassRenderType()?.let { put(libassRenderTypeKey, encodeSyncString(it)) } + loadSwipeToSeekEnabled()?.let { put(swipeToSeekEnabledKey, encodeSyncBoolean(it)) } + loadSwipeToSeekSensitivity()?.let { put(swipeToSeekSensitivityKey, encodeSyncString(it)) } } actual fun replaceFromSyncPayload(payload: JsonObject) { @@ -593,5 +599,29 @@ actual object PlayerSettingsStorage { payload.decodeSyncFloat(nextEpisodeThresholdMinutesBeforeEndKey)?.let(::saveNextEpisodeThresholdMinutesBeforeEnd) payload.decodeSyncBoolean(useLibassKey)?.let(::saveUseLibass) payload.decodeSyncString(libassRenderTypeKey)?.let(::saveLibassRenderType) + payload.decodeSyncBoolean(swipeToSeekEnabledKey)?.let(::saveSwipeToSeekEnabled) + payload.decodeSyncString(swipeToSeekSensitivityKey)?.let(::saveSwipeToSeekSensitivity) + } + + actual fun loadSwipeToSeekEnabled(): Boolean? { + val defaults = NSUserDefaults.standardUserDefaults + val key = ProfileScopedKey.of(swipeToSeekEnabledKey) + return if (defaults.objectForKey(key) != null) { + defaults.boolForKey(key) + } else { + null + } + } + + actual fun saveSwipeToSeekEnabled(enabled: Boolean) { + val defaults = NSUserDefaults.standardUserDefaults + defaults.setBool(enabled, ProfileScopedKey.of(swipeToSeekEnabledKey)) + } + + actual fun loadSwipeToSeekSensitivity(): String? = + NSUserDefaults.standardUserDefaults.stringForKey(ProfileScopedKey.of(swipeToSeekSensitivityKey)) + + actual fun saveSwipeToSeekSensitivity(sensitivity: String) { + NSUserDefaults.standardUserDefaults.setObject(sensitivity, ProfileScopedKey.of(swipeToSeekSensitivityKey)) } }