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 5cb861a8..f4bcfb88 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 @@ -56,6 +56,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, @@ -92,6 +94,8 @@ actual object PlayerSettingsStorage { nextEpisodeThresholdMinutesBeforeEndKey, useLibassKey, libassRenderTypeKey, + swipeToSeekEnabledKey, + swipeToSeekSensitivityKey, ) private var preferences: SharedPreferences? = null @@ -688,6 +692,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) { @@ -732,5 +738,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 3c21777b..4ed0ebba 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -753,6 +753,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 279aae9f..241629ee 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 @@ -1552,8 +1552,10 @@ fun PlayerScreen( if (gestureMode == null) { val holdToSpeedActive = isHoldToSpeedGestureActiveState.value val horizontalDominant = - !holdToSpeedActive && - abs(totalDx) > viewConfiguration.touchSlop && + playerSettingsUiState.swipeToSeekEnabled && + !holdToSpeedActive && + abs(totalDx) > viewConfiguration.touchSlop * + playerSettingsUiState.swipeToSeekSensitivity.triggerMultiplier && abs(totalDx) > abs(totalDy) val verticalDominant = !holdToSpeedActive && @@ -1590,7 +1592,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 15f4f4d7..80475af9 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, @@ -31,6 +49,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 = "", @@ -84,6 +104,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 @@ -204,6 +226,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() } @@ -498,6 +526,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, @@ -534,6 +578,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 5c3b3756..6def2f56 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 @@ -78,6 +78,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 042d592d..1c42ed75 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 @@ -56,6 +56,7 @@ import com.nuvio.app.features.player.AvailableLanguageOptions import com.nuvio.app.features.player.ExternalPlayerApp import com.nuvio.app.features.player.ExternalPlayerPlatform 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 @@ -86,6 +87,8 @@ internal fun LazyListScope.playbackSettingsContent( tunnelingEnabled: Boolean, useLibass: Boolean, libassRenderType: String, + swipeToSeekEnabled: Boolean, + swipeToSeekSensitivity: SwipeToSeekSensitivity, ) { item { PlaybackSettingsSection( @@ -104,6 +107,8 @@ internal fun LazyListScope.playbackSettingsContent( tunnelingEnabled = tunnelingEnabled, useLibass = useLibass, libassRenderType = libassRenderType, + swipeToSeekEnabled = swipeToSeekEnabled, + swipeToSeekSensitivity = swipeToSeekSensitivity, ) } } @@ -166,6 +171,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) } @@ -181,6 +188,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 availableExternalPlayers = ExternalPlayerPlatform.availablePlayers() @@ -262,6 +270,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 }, + ) + } } } @@ -865,6 +894,14 @@ private fun PlaybackSettingsSection( ) } + if (showSwipeSensitivityDialog) { + SwipeToSeekSensitivityDialog( + selected = swipeToSeekSensitivity, + onSelect = PlayerSettingsRepository::setSwipeToSeekSensitivity, + onDismiss = { showSwipeSensitivityDialog = false }, + ) + } + if (showAutoPlayModeDialog) { StreamAutoPlayModeDialog( selectedMode = autoPlayPlayerSettings.streamAutoPlayMode, @@ -2324,3 +2361,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 519dc8d5..4018ce7a 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 @@ -68,6 +68,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 @@ -226,6 +229,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, @@ -274,6 +279,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, @@ -332,6 +339,8 @@ private fun MobileSettingsScreen( tunnelingEnabled: Boolean, useLibass: Boolean, libassRenderType: String, + swipeToSeekEnabled: Boolean, + swipeToSeekSensitivity: SwipeToSeekSensitivity, selectedTheme: AppTheme, onThemeSelected: (AppTheme) -> Unit, amoledEnabled: Boolean, @@ -493,6 +502,8 @@ private fun MobileSettingsScreen( tunnelingEnabled = tunnelingEnabled, useLibass = useLibass, libassRenderType = libassRenderType, + swipeToSeekEnabled = swipeToSeekEnabled, + swipeToSeekSensitivity = swipeToSeekSensitivity, ) SettingsPage.Appearance -> appearanceSettingsContent( isTablet = false, @@ -639,6 +650,8 @@ private fun TabletSettingsScreen( tunnelingEnabled: Boolean, useLibass: Boolean, libassRenderType: String, + swipeToSeekEnabled: Boolean, + swipeToSeekSensitivity: SwipeToSeekSensitivity, selectedTheme: AppTheme, onThemeSelected: (AppTheme) -> Unit, amoledEnabled: Boolean, @@ -859,6 +872,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 0aedbb30..5a5f5a08 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 @@ -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, @@ -90,6 +92,8 @@ actual object PlayerSettingsStorage { nextEpisodeThresholdMinutesBeforeEndKey, useLibassKey, libassRenderTypeKey, + swipeToSeekEnabledKey, + swipeToSeekSensitivityKey, ) actual fun loadShowLoadingOverlay(): Boolean? { @@ -588,6 +592,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) { @@ -631,5 +637,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)) } }