mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-17 15:32:01 +00:00
Merge d959596615 into 37203d1fc1
This commit is contained in:
commit
ae4306fb4f
8 changed files with 265 additions and 3 deletions
|
|
@ -56,6 +56,8 @@ actual object PlayerSettingsStorage {
|
||||||
private const val nextEpisodeThresholdMinutesBeforeEndKey = "next_episode_threshold_minutes_before_end_v2"
|
private const val nextEpisodeThresholdMinutesBeforeEndKey = "next_episode_threshold_minutes_before_end_v2"
|
||||||
private const val useLibassKey = "use_libass"
|
private const val useLibassKey = "use_libass"
|
||||||
private const val libassRenderTypeKey = "libass_render_type"
|
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(
|
private val syncKeys = listOf(
|
||||||
showLoadingOverlayKey,
|
showLoadingOverlayKey,
|
||||||
resizeModeKey,
|
resizeModeKey,
|
||||||
|
|
@ -92,6 +94,8 @@ actual object PlayerSettingsStorage {
|
||||||
nextEpisodeThresholdMinutesBeforeEndKey,
|
nextEpisodeThresholdMinutesBeforeEndKey,
|
||||||
useLibassKey,
|
useLibassKey,
|
||||||
libassRenderTypeKey,
|
libassRenderTypeKey,
|
||||||
|
swipeToSeekEnabledKey,
|
||||||
|
swipeToSeekSensitivityKey,
|
||||||
)
|
)
|
||||||
|
|
||||||
private var preferences: SharedPreferences? = null
|
private var preferences: SharedPreferences? = null
|
||||||
|
|
@ -688,6 +692,8 @@ actual object PlayerSettingsStorage {
|
||||||
loadNextEpisodeThresholdMinutesBeforeEnd()?.let { put(nextEpisodeThresholdMinutesBeforeEndKey, encodeSyncFloat(it)) }
|
loadNextEpisodeThresholdMinutesBeforeEnd()?.let { put(nextEpisodeThresholdMinutesBeforeEndKey, encodeSyncFloat(it)) }
|
||||||
loadUseLibass()?.let { put(useLibassKey, encodeSyncBoolean(it)) }
|
loadUseLibass()?.let { put(useLibassKey, encodeSyncBoolean(it)) }
|
||||||
loadLibassRenderType()?.let { put(libassRenderTypeKey, encodeSyncString(it)) }
|
loadLibassRenderType()?.let { put(libassRenderTypeKey, encodeSyncString(it)) }
|
||||||
|
loadSwipeToSeekEnabled()?.let { put(swipeToSeekEnabledKey, encodeSyncBoolean(it)) }
|
||||||
|
loadSwipeToSeekSensitivity()?.let { put(swipeToSeekSensitivityKey, encodeSyncString(it)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun replaceFromSyncPayload(payload: JsonObject) {
|
actual fun replaceFromSyncPayload(payload: JsonObject) {
|
||||||
|
|
@ -732,5 +738,34 @@ actual object PlayerSettingsStorage {
|
||||||
payload.decodeSyncFloat(nextEpisodeThresholdMinutesBeforeEndKey)?.let(::saveNextEpisodeThresholdMinutesBeforeEnd)
|
payload.decodeSyncFloat(nextEpisodeThresholdMinutesBeforeEndKey)?.let(::saveNextEpisodeThresholdMinutesBeforeEnd)
|
||||||
payload.decodeSyncBoolean(useLibassKey)?.let(::saveUseLibass)
|
payload.decodeSyncBoolean(useLibassKey)?.let(::saveUseLibass)
|
||||||
payload.decodeSyncString(libassRenderTypeKey)?.let(::saveLibassRenderType)
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -718,6 +718,13 @@
|
||||||
<string name="settings_playback_regex_matches_against">Matches against stream name/title/description/addon/url. Example: 4K|2160p|Remux</string>
|
<string name="settings_playback_regex_matches_against">Matches against stream name/title/description/addon/url. Example: 4K|2160p|Remux</string>
|
||||||
<string name="settings_playback_regex_pattern">Regex Pattern</string>
|
<string name="settings_playback_regex_pattern">Regex Pattern</string>
|
||||||
<string name="settings_playback_regex_placeholder">No pattern set. Example: 4K|2160p|Remux</string>
|
<string name="settings_playback_regex_placeholder">No pattern set. Example: 4K|2160p|Remux</string>
|
||||||
|
<string name="settings_playback_swipe_to_seek">Swipe to Seek</string>
|
||||||
|
<string name="settings_playback_swipe_to_seek_description">Swipe horizontally on the player surface to seek forward or backward.</string>
|
||||||
|
<string name="settings_playback_swipe_to_seek_sensitivity">Swipe to Seek Sensitivity</string>
|
||||||
|
<string name="settings_playback_swipe_to_seek_sensitivity_description">Adjust the drag distance required to trigger a seek.</string>
|
||||||
|
<string name="settings_playback_swipe_to_seek_sensitivity_low">Low</string>
|
||||||
|
<string name="settings_playback_swipe_to_seek_sensitivity_medium">Medium</string>
|
||||||
|
<string name="settings_playback_swipe_to_seek_sensitivity_high">High</string>
|
||||||
<string name="settings_playback_regex_preset_any_1080p">Any 1080p+</string>
|
<string name="settings_playback_regex_preset_any_1080p">Any 1080p+</string>
|
||||||
<string name="settings_playback_regex_preset_avc_x264">AVC / x264</string>
|
<string name="settings_playback_regex_preset_avc_x264">AVC / x264</string>
|
||||||
<string name="settings_playback_regex_preset_bluray_quality">BluRay Quality</string>
|
<string name="settings_playback_regex_preset_bluray_quality">BluRay Quality</string>
|
||||||
|
|
|
||||||
|
|
@ -1480,8 +1480,10 @@ fun PlayerScreen(
|
||||||
if (gestureMode == null) {
|
if (gestureMode == null) {
|
||||||
val holdToSpeedActive = isHoldToSpeedGestureActiveState.value
|
val holdToSpeedActive = isHoldToSpeedGestureActiveState.value
|
||||||
val horizontalDominant =
|
val horizontalDominant =
|
||||||
!holdToSpeedActive &&
|
playerSettingsUiState.swipeToSeekEnabled &&
|
||||||
abs(totalDx) > viewConfiguration.touchSlop &&
|
!holdToSpeedActive &&
|
||||||
|
abs(totalDx) > viewConfiguration.touchSlop *
|
||||||
|
playerSettingsUiState.swipeToSeekSensitivity.triggerMultiplier &&
|
||||||
abs(totalDx) > abs(totalDy)
|
abs(totalDx) > abs(totalDy)
|
||||||
val verticalDominant =
|
val verticalDominant =
|
||||||
!holdToSpeedActive &&
|
!holdToSpeedActive &&
|
||||||
|
|
@ -1518,7 +1520,7 @@ fun PlayerScreen(
|
||||||
else -> 60f
|
else -> 60f
|
||||||
}
|
}
|
||||||
val previewOffsetMs =
|
val previewOffsetMs =
|
||||||
((totalDx / width) * sensitivitySeconds * 1000f).roundToLong()
|
((totalDx / width) * sensitivitySeconds * playerSettingsUiState.swipeToSeekSensitivity.speedMultiplier * 1000f).roundToLong()
|
||||||
val unclampedPreviewMs = horizontalSeekBaselineMs + previewOffsetMs
|
val unclampedPreviewMs = horizontalSeekBaselineMs + previewOffsetMs
|
||||||
horizontalSeekPreviewMs = currentDurationMsState.value
|
horizontalSeekPreviewMs = currentDurationMsState.value
|
||||||
.takeIf { it > 0L }
|
.takeIf { it > 0L }
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,24 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
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(
|
data class PlayerSettingsUiState(
|
||||||
val showLoadingOverlay: Boolean = true,
|
val showLoadingOverlay: Boolean = true,
|
||||||
val resizeMode: PlayerResizeMode = PlayerResizeMode.Fit,
|
val resizeMode: PlayerResizeMode = PlayerResizeMode.Fit,
|
||||||
|
|
@ -31,6 +49,8 @@ data class PlayerSettingsUiState(
|
||||||
val streamAutoPlaySelectedPlugins: Set<String> = emptySet(),
|
val streamAutoPlaySelectedPlugins: Set<String> = emptySet(),
|
||||||
val streamAutoPlayRegex: String = "",
|
val streamAutoPlayRegex: String = "",
|
||||||
val streamAutoPlayTimeoutSeconds: Int = 3,
|
val streamAutoPlayTimeoutSeconds: Int = 3,
|
||||||
|
val swipeToSeekEnabled: Boolean = true,
|
||||||
|
val swipeToSeekSensitivity: SwipeToSeekSensitivity = SwipeToSeekSensitivity.MEDIUM,
|
||||||
val skipIntroEnabled: Boolean = true,
|
val skipIntroEnabled: Boolean = true,
|
||||||
val animeSkipEnabled: Boolean = false,
|
val animeSkipEnabled: Boolean = false,
|
||||||
val animeSkipClientId: String = "",
|
val animeSkipClientId: String = "",
|
||||||
|
|
@ -84,6 +104,8 @@ object PlayerSettingsRepository {
|
||||||
private var nextEpisodeThresholdMinutesBeforeEnd = 2f
|
private var nextEpisodeThresholdMinutesBeforeEnd = 2f
|
||||||
private var useLibass = false
|
private var useLibass = false
|
||||||
private var libassRenderType = "CUES"
|
private var libassRenderType = "CUES"
|
||||||
|
private var swipeToSeekEnabled = true
|
||||||
|
private var swipeToSeekSensitivity = SwipeToSeekSensitivity.MEDIUM
|
||||||
|
|
||||||
fun ensureLoaded() {
|
fun ensureLoaded() {
|
||||||
if (hasLoaded) return
|
if (hasLoaded) return
|
||||||
|
|
@ -204,6 +226,12 @@ object PlayerSettingsRepository {
|
||||||
nextEpisodeThresholdMinutesBeforeEnd = PlayerSettingsStorage.loadNextEpisodeThresholdMinutesBeforeEnd() ?: 2f
|
nextEpisodeThresholdMinutesBeforeEnd = PlayerSettingsStorage.loadNextEpisodeThresholdMinutesBeforeEnd() ?: 2f
|
||||||
useLibass = PlayerSettingsStorage.loadUseLibass() ?: false
|
useLibass = PlayerSettingsStorage.loadUseLibass() ?: false
|
||||||
libassRenderType = PlayerSettingsStorage.loadLibassRenderType() ?: "CUES"
|
libassRenderType = PlayerSettingsStorage.loadLibassRenderType() ?: "CUES"
|
||||||
|
swipeToSeekEnabled = PlayerSettingsStorage.loadSwipeToSeekEnabled() ?: true
|
||||||
|
swipeToSeekSensitivity = try {
|
||||||
|
SwipeToSeekSensitivity.valueOf(PlayerSettingsStorage.loadSwipeToSeekSensitivity() ?: "MEDIUM")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
SwipeToSeekSensitivity.MEDIUM
|
||||||
|
}
|
||||||
publish()
|
publish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -498,6 +526,22 @@ object PlayerSettingsRepository {
|
||||||
PlayerSettingsStorage.saveLibassRenderType(renderType)
|
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() {
|
private fun publish() {
|
||||||
_uiState.value = PlayerSettingsUiState(
|
_uiState.value = PlayerSettingsUiState(
|
||||||
showLoadingOverlay = showLoadingOverlay,
|
showLoadingOverlay = showLoadingOverlay,
|
||||||
|
|
@ -534,6 +578,8 @@ object PlayerSettingsRepository {
|
||||||
nextEpisodeThresholdMinutesBeforeEnd = nextEpisodeThresholdMinutesBeforeEnd,
|
nextEpisodeThresholdMinutesBeforeEnd = nextEpisodeThresholdMinutesBeforeEnd,
|
||||||
useLibass = useLibass,
|
useLibass = useLibass,
|
||||||
libassRenderType = libassRenderType,
|
libassRenderType = libassRenderType,
|
||||||
|
swipeToSeekEnabled = swipeToSeekEnabled,
|
||||||
|
swipeToSeekSensitivity = swipeToSeekSensitivity,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,10 @@ internal expect object PlayerSettingsStorage {
|
||||||
fun saveUseLibass(enabled: Boolean)
|
fun saveUseLibass(enabled: Boolean)
|
||||||
fun loadLibassRenderType(): String?
|
fun loadLibassRenderType(): String?
|
||||||
fun saveLibassRenderType(renderType: String)
|
fun saveLibassRenderType(renderType: String)
|
||||||
|
fun loadSwipeToSeekEnabled(): Boolean?
|
||||||
|
fun saveSwipeToSeekEnabled(enabled: Boolean)
|
||||||
|
fun loadSwipeToSeekSensitivity(): String?
|
||||||
|
fun saveSwipeToSeekSensitivity(sensitivity: String)
|
||||||
fun exportToSyncPayload(): JsonObject
|
fun exportToSyncPayload(): JsonObject
|
||||||
fun replaceFromSyncPayload(payload: JsonObject)
|
fun replaceFromSyncPayload(payload: JsonObject)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ import com.nuvio.app.features.player.AvailableLanguageOptions
|
||||||
import com.nuvio.app.features.player.ExternalPlayerApp
|
import com.nuvio.app.features.player.ExternalPlayerApp
|
||||||
import com.nuvio.app.features.player.ExternalPlayerPlatform
|
import com.nuvio.app.features.player.ExternalPlayerPlatform
|
||||||
import com.nuvio.app.features.player.PlayerSettingsRepository
|
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.SubtitleLanguageOption
|
||||||
import com.nuvio.app.features.player.formatPlaybackSpeedLabel
|
import com.nuvio.app.features.player.formatPlaybackSpeedLabel
|
||||||
import com.nuvio.app.features.player.languageLabelForCode
|
import com.nuvio.app.features.player.languageLabelForCode
|
||||||
|
|
@ -86,6 +87,8 @@ internal fun LazyListScope.playbackSettingsContent(
|
||||||
tunnelingEnabled: Boolean,
|
tunnelingEnabled: Boolean,
|
||||||
useLibass: Boolean,
|
useLibass: Boolean,
|
||||||
libassRenderType: String,
|
libassRenderType: String,
|
||||||
|
swipeToSeekEnabled: Boolean,
|
||||||
|
swipeToSeekSensitivity: SwipeToSeekSensitivity,
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
PlaybackSettingsSection(
|
PlaybackSettingsSection(
|
||||||
|
|
@ -104,6 +107,8 @@ internal fun LazyListScope.playbackSettingsContent(
|
||||||
tunnelingEnabled = tunnelingEnabled,
|
tunnelingEnabled = tunnelingEnabled,
|
||||||
useLibass = useLibass,
|
useLibass = useLibass,
|
||||||
libassRenderType = libassRenderType,
|
libassRenderType = libassRenderType,
|
||||||
|
swipeToSeekEnabled = swipeToSeekEnabled,
|
||||||
|
swipeToSeekSensitivity = swipeToSeekSensitivity,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -166,6 +171,8 @@ private fun PlaybackSettingsSection(
|
||||||
tunnelingEnabled: Boolean,
|
tunnelingEnabled: Boolean,
|
||||||
useLibass: Boolean,
|
useLibass: Boolean,
|
||||||
libassRenderType: String,
|
libassRenderType: String,
|
||||||
|
swipeToSeekEnabled: Boolean,
|
||||||
|
swipeToSeekSensitivity: SwipeToSeekSensitivity,
|
||||||
) {
|
) {
|
||||||
var showPreferredAudioDialog by remember { mutableStateOf(false) }
|
var showPreferredAudioDialog by remember { mutableStateOf(false) }
|
||||||
var showSecondaryAudioDialog by remember { mutableStateOf(false) }
|
var showSecondaryAudioDialog by remember { mutableStateOf(false) }
|
||||||
|
|
@ -181,6 +188,7 @@ private fun PlaybackSettingsSection(
|
||||||
var showAutoPlayAddonSelectionDialog by remember { mutableStateOf(false) }
|
var showAutoPlayAddonSelectionDialog by remember { mutableStateOf(false) }
|
||||||
var showAutoPlayPluginSelectionDialog by remember { mutableStateOf(false) }
|
var showAutoPlayPluginSelectionDialog by remember { mutableStateOf(false) }
|
||||||
var showAutoPlayRegexDialog by remember { mutableStateOf(false) }
|
var showAutoPlayRegexDialog by remember { mutableStateOf(false) }
|
||||||
|
var showSwipeSensitivityDialog by remember { mutableStateOf(false) }
|
||||||
val pluginsEnabled = AppFeaturePolicy.pluginsEnabled
|
val pluginsEnabled = AppFeaturePolicy.pluginsEnabled
|
||||||
val autoPlayPlayerSettings by PlayerSettingsRepository.uiState.collectAsStateWithLifecycle()
|
val autoPlayPlayerSettings by PlayerSettingsRepository.uiState.collectAsStateWithLifecycle()
|
||||||
val availableExternalPlayers = ExternalPlayerPlatform.availablePlayers()
|
val availableExternalPlayers = ExternalPlayerPlatform.availablePlayers()
|
||||||
|
|
@ -262,6 +270,27 @@ private fun PlaybackSettingsSection(
|
||||||
onClick = { showHoldToSpeedValueDialog = true },
|
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) {
|
if (showAutoPlayModeDialog) {
|
||||||
StreamAutoPlayModeDialog(
|
StreamAutoPlayModeDialog(
|
||||||
selectedMode = autoPlayPlayerSettings.streamAutoPlayMode,
|
selectedMode = autoPlayPlayerSettings.streamAutoPlayMode,
|
||||||
|
|
@ -2324,3 +2361,89 @@ private fun libassRenderTypeRes(renderType: String): StringResource = when (rend
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun libassRenderTypeLabel(renderType: String): String = stringResource(libassRenderTypeRes(renderType))
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,9 @@ import com.nuvio.app.features.mdblist.MdbListSettingsRepository
|
||||||
import com.nuvio.app.features.notifications.EpisodeReleaseNotificationsRepository
|
import com.nuvio.app.features.notifications.EpisodeReleaseNotificationsRepository
|
||||||
import com.nuvio.app.features.notifications.EpisodeReleaseNotificationsUiState
|
import com.nuvio.app.features.notifications.EpisodeReleaseNotificationsUiState
|
||||||
import com.nuvio.app.features.player.PlayerSettingsRepository
|
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.TraktAuthUiState
|
||||||
import com.nuvio.app.features.trakt.TraktAuthRepository
|
import com.nuvio.app.features.trakt.TraktAuthRepository
|
||||||
import com.nuvio.app.features.trakt.TraktCommentsSettings
|
import com.nuvio.app.features.trakt.TraktCommentsSettings
|
||||||
|
|
@ -220,6 +223,8 @@ fun SettingsScreen(
|
||||||
tunnelingEnabled = playerSettingsUiState.tunnelingEnabled,
|
tunnelingEnabled = playerSettingsUiState.tunnelingEnabled,
|
||||||
useLibass = playerSettingsUiState.useLibass,
|
useLibass = playerSettingsUiState.useLibass,
|
||||||
libassRenderType = playerSettingsUiState.libassRenderType,
|
libassRenderType = playerSettingsUiState.libassRenderType,
|
||||||
|
swipeToSeekEnabled = playerSettingsUiState.swipeToSeekEnabled,
|
||||||
|
swipeToSeekSensitivity = playerSettingsUiState.swipeToSeekSensitivity,
|
||||||
selectedTheme = selectedTheme,
|
selectedTheme = selectedTheme,
|
||||||
onThemeSelected = ThemeSettingsRepository::setTheme,
|
onThemeSelected = ThemeSettingsRepository::setTheme,
|
||||||
amoledEnabled = amoledEnabled,
|
amoledEnabled = amoledEnabled,
|
||||||
|
|
@ -267,6 +272,8 @@ fun SettingsScreen(
|
||||||
tunnelingEnabled = playerSettingsUiState.tunnelingEnabled,
|
tunnelingEnabled = playerSettingsUiState.tunnelingEnabled,
|
||||||
useLibass = playerSettingsUiState.useLibass,
|
useLibass = playerSettingsUiState.useLibass,
|
||||||
libassRenderType = playerSettingsUiState.libassRenderType,
|
libassRenderType = playerSettingsUiState.libassRenderType,
|
||||||
|
swipeToSeekEnabled = playerSettingsUiState.swipeToSeekEnabled,
|
||||||
|
swipeToSeekSensitivity = playerSettingsUiState.swipeToSeekSensitivity,
|
||||||
selectedTheme = selectedTheme,
|
selectedTheme = selectedTheme,
|
||||||
onThemeSelected = ThemeSettingsRepository::setTheme,
|
onThemeSelected = ThemeSettingsRepository::setTheme,
|
||||||
amoledEnabled = amoledEnabled,
|
amoledEnabled = amoledEnabled,
|
||||||
|
|
@ -324,6 +331,8 @@ private fun MobileSettingsScreen(
|
||||||
tunnelingEnabled: Boolean,
|
tunnelingEnabled: Boolean,
|
||||||
useLibass: Boolean,
|
useLibass: Boolean,
|
||||||
libassRenderType: String,
|
libassRenderType: String,
|
||||||
|
swipeToSeekEnabled: Boolean,
|
||||||
|
swipeToSeekSensitivity: SwipeToSeekSensitivity,
|
||||||
selectedTheme: AppTheme,
|
selectedTheme: AppTheme,
|
||||||
onThemeSelected: (AppTheme) -> Unit,
|
onThemeSelected: (AppTheme) -> Unit,
|
||||||
amoledEnabled: Boolean,
|
amoledEnabled: Boolean,
|
||||||
|
|
@ -484,6 +493,8 @@ private fun MobileSettingsScreen(
|
||||||
tunnelingEnabled = tunnelingEnabled,
|
tunnelingEnabled = tunnelingEnabled,
|
||||||
useLibass = useLibass,
|
useLibass = useLibass,
|
||||||
libassRenderType = libassRenderType,
|
libassRenderType = libassRenderType,
|
||||||
|
swipeToSeekEnabled = swipeToSeekEnabled,
|
||||||
|
swipeToSeekSensitivity = swipeToSeekSensitivity,
|
||||||
)
|
)
|
||||||
SettingsPage.Appearance -> appearanceSettingsContent(
|
SettingsPage.Appearance -> appearanceSettingsContent(
|
||||||
isTablet = false,
|
isTablet = false,
|
||||||
|
|
@ -625,6 +636,8 @@ private fun TabletSettingsScreen(
|
||||||
tunnelingEnabled: Boolean,
|
tunnelingEnabled: Boolean,
|
||||||
useLibass: Boolean,
|
useLibass: Boolean,
|
||||||
libassRenderType: String,
|
libassRenderType: String,
|
||||||
|
swipeToSeekEnabled: Boolean,
|
||||||
|
swipeToSeekSensitivity: SwipeToSeekSensitivity,
|
||||||
selectedTheme: AppTheme,
|
selectedTheme: AppTheme,
|
||||||
onThemeSelected: (AppTheme) -> Unit,
|
onThemeSelected: (AppTheme) -> Unit,
|
||||||
amoledEnabled: Boolean,
|
amoledEnabled: Boolean,
|
||||||
|
|
@ -844,6 +857,8 @@ private fun TabletSettingsScreen(
|
||||||
tunnelingEnabled = tunnelingEnabled,
|
tunnelingEnabled = tunnelingEnabled,
|
||||||
useLibass = useLibass,
|
useLibass = useLibass,
|
||||||
libassRenderType = libassRenderType,
|
libassRenderType = libassRenderType,
|
||||||
|
swipeToSeekEnabled = swipeToSeekEnabled,
|
||||||
|
swipeToSeekSensitivity = swipeToSeekSensitivity,
|
||||||
)
|
)
|
||||||
SettingsPage.Appearance -> appearanceSettingsContent(
|
SettingsPage.Appearance -> appearanceSettingsContent(
|
||||||
isTablet = true,
|
isTablet = true,
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,8 @@ actual object PlayerSettingsStorage {
|
||||||
private const val nextEpisodeThresholdMinutesBeforeEndKey = "next_episode_threshold_minutes_before_end_v2"
|
private const val nextEpisodeThresholdMinutesBeforeEndKey = "next_episode_threshold_minutes_before_end_v2"
|
||||||
private const val useLibassKey = "use_libass"
|
private const val useLibassKey = "use_libass"
|
||||||
private const val libassRenderTypeKey = "libass_render_type"
|
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(
|
private val syncKeys = listOf(
|
||||||
showLoadingOverlayKey,
|
showLoadingOverlayKey,
|
||||||
resizeModeKey,
|
resizeModeKey,
|
||||||
|
|
@ -90,6 +92,8 @@ actual object PlayerSettingsStorage {
|
||||||
nextEpisodeThresholdMinutesBeforeEndKey,
|
nextEpisodeThresholdMinutesBeforeEndKey,
|
||||||
useLibassKey,
|
useLibassKey,
|
||||||
libassRenderTypeKey,
|
libassRenderTypeKey,
|
||||||
|
swipeToSeekEnabledKey,
|
||||||
|
swipeToSeekSensitivityKey,
|
||||||
)
|
)
|
||||||
|
|
||||||
actual fun loadShowLoadingOverlay(): Boolean? {
|
actual fun loadShowLoadingOverlay(): Boolean? {
|
||||||
|
|
@ -588,6 +592,8 @@ actual object PlayerSettingsStorage {
|
||||||
loadNextEpisodeThresholdMinutesBeforeEnd()?.let { put(nextEpisodeThresholdMinutesBeforeEndKey, encodeSyncFloat(it)) }
|
loadNextEpisodeThresholdMinutesBeforeEnd()?.let { put(nextEpisodeThresholdMinutesBeforeEndKey, encodeSyncFloat(it)) }
|
||||||
loadUseLibass()?.let { put(useLibassKey, encodeSyncBoolean(it)) }
|
loadUseLibass()?.let { put(useLibassKey, encodeSyncBoolean(it)) }
|
||||||
loadLibassRenderType()?.let { put(libassRenderTypeKey, encodeSyncString(it)) }
|
loadLibassRenderType()?.let { put(libassRenderTypeKey, encodeSyncString(it)) }
|
||||||
|
loadSwipeToSeekEnabled()?.let { put(swipeToSeekEnabledKey, encodeSyncBoolean(it)) }
|
||||||
|
loadSwipeToSeekSensitivity()?.let { put(swipeToSeekSensitivityKey, encodeSyncString(it)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun replaceFromSyncPayload(payload: JsonObject) {
|
actual fun replaceFromSyncPayload(payload: JsonObject) {
|
||||||
|
|
@ -631,5 +637,29 @@ actual object PlayerSettingsStorage {
|
||||||
payload.decodeSyncFloat(nextEpisodeThresholdMinutesBeforeEndKey)?.let(::saveNextEpisodeThresholdMinutesBeforeEnd)
|
payload.decodeSyncFloat(nextEpisodeThresholdMinutesBeforeEndKey)?.let(::saveNextEpisodeThresholdMinutesBeforeEnd)
|
||||||
payload.decodeSyncBoolean(useLibassKey)?.let(::saveUseLibass)
|
payload.decodeSyncBoolean(useLibassKey)?.let(::saveUseLibass)
|
||||||
payload.decodeSyncString(libassRenderTypeKey)?.let(::saveLibassRenderType)
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue