feature: add auto-pause playback feature
This commit is contained in:
+14
-4
@@ -186,11 +186,16 @@ internal class PlaybackRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts the configured silent break between spoken phrases: when continuous playback reaches
|
* Reacts to reaching the next spoken phrase during continuous playback, just before that phrase
|
||||||
* the next phrase, playback pauses just before that phrase starts, then resumes after the break.
|
* starts. Depending on preferences it either:
|
||||||
|
* - pauses and waits for the user to resume, when auto-pause is enabled, or
|
||||||
|
* - pauses for the configured silent break and then resumes on its own.
|
||||||
*
|
*
|
||||||
* Breaks only happen on automatic advancement — manual seeking and previous/next navigation are
|
* Auto-pause takes precedence: while it is on, the break between phrases is ignored.
|
||||||
* ignored via [suppressBreaks].
|
*
|
||||||
|
* This only happens on automatic advancement — manual seeking and previous/next navigation are
|
||||||
|
* ignored because they cancel the running break job and never re-enter [PlaybackState.PLAYING]
|
||||||
|
* through this path.
|
||||||
*/
|
*/
|
||||||
private suspend fun observePhraseBreaks() {
|
private suspend fun observePhraseBreaks() {
|
||||||
phraseBreakJob?.cancel()
|
phraseBreakJob?.cancel()
|
||||||
@@ -203,6 +208,11 @@ internal class PlaybackRepositoryImpl(
|
|||||||
if (playbackState.firstOrNull() != PlaybackState.PLAYING) return@onEach
|
if (playbackState.firstOrNull() != PlaybackState.PLAYING) return@onEach
|
||||||
|
|
||||||
val playbackPreferences = preferencesRepository.playbackPreferences.first()
|
val playbackPreferences = preferencesRepository.playbackPreferences.first()
|
||||||
|
if (playbackPreferences.autoPause) {
|
||||||
|
audioPlayer.pause()
|
||||||
|
return@onEach
|
||||||
|
}
|
||||||
|
|
||||||
val breakDuration = playbackPreferences.breakBetweenPhrases
|
val breakDuration = playbackPreferences.breakBetweenPhrases
|
||||||
if (breakDuration <= Duration.ZERO) return@onEach
|
if (breakDuration <= Duration.ZERO) return@onEach
|
||||||
|
|
||||||
|
|||||||
+4
@@ -43,4 +43,8 @@ internal class InMemoryPreferencesRepository : PreferencesRepository {
|
|||||||
override suspend fun setBreakBetweenPhrases(duration: Duration) {
|
override suspend fun setBreakBetweenPhrases(duration: Duration) {
|
||||||
playbackState.update { it.copy(breakBetweenPhrases = duration) }
|
playbackState.update { it.copy(breakBetweenPhrases = duration) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun setAutoPause(enabled: Boolean) {
|
||||||
|
playbackState.update { it.copy(autoPause = enabled) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -21,4 +21,6 @@ interface PreferencesRepository {
|
|||||||
suspend fun setPlaybackSpeed(speed: Float)
|
suspend fun setPlaybackSpeed(speed: Float)
|
||||||
|
|
||||||
suspend fun setBreakBetweenPhrases(duration: Duration)
|
suspend fun setBreakBetweenPhrases(duration: Duration)
|
||||||
|
|
||||||
|
suspend fun setAutoPause(enabled: Boolean)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -10,10 +10,13 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
* [SPEED_STEP] increments. [DEFAULT_SPEED] is the normal, unaltered speed.
|
* [SPEED_STEP] increments. [DEFAULT_SPEED] is the normal, unaltered speed.
|
||||||
* @property breakBetweenPhrases silent pause inserted when moving from one phrase to the next,
|
* @property breakBetweenPhrases silent pause inserted when moving from one phrase to the next,
|
||||||
* between [DEFAULT_BREAK_BETWEEN_PHRASES] and [MAX_BREAK_BETWEEN_PHRASES] in [BREAK_STEP] steps.
|
* between [DEFAULT_BREAK_BETWEEN_PHRASES] and [MAX_BREAK_BETWEEN_PHRASES] in [BREAK_STEP] steps.
|
||||||
|
* @property autoPause when enabled, playback stops at the end of every phrase and waits for the
|
||||||
|
* user to resume. This takes precedence over [breakBetweenPhrases], which is then ignored.
|
||||||
*/
|
*/
|
||||||
data class PlaybackPreferences(
|
data class PlaybackPreferences(
|
||||||
val speed: Float = DEFAULT_SPEED,
|
val speed: Float = DEFAULT_SPEED,
|
||||||
val breakBetweenPhrases: Duration = DEFAULT_BREAK_BETWEEN_PHRASES,
|
val breakBetweenPhrases: Duration = DEFAULT_BREAK_BETWEEN_PHRASES,
|
||||||
|
val autoPause: Boolean = DEFAULT_AUTO_PAUSE,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val DEFAULT_SPEED = 1f
|
const val DEFAULT_SPEED = 1f
|
||||||
@@ -24,5 +27,7 @@ data class PlaybackPreferences(
|
|||||||
val DEFAULT_BREAK_BETWEEN_PHRASES: Duration = Duration.ZERO
|
val DEFAULT_BREAK_BETWEEN_PHRASES: Duration = Duration.ZERO
|
||||||
val MAX_BREAK_BETWEEN_PHRASES: Duration = 5.seconds
|
val MAX_BREAK_BETWEEN_PHRASES: Duration = 5.seconds
|
||||||
val BREAK_STEP: Duration = 1.seconds
|
val BREAK_STEP: Duration = 1.seconds
|
||||||
|
|
||||||
|
const val DEFAULT_AUTO_PAUSE = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -44,6 +44,8 @@ internal class DataStorePreferencesRepository(
|
|||||||
override suspend fun setBreakBetweenPhrases(duration: Duration) =
|
override suspend fun setBreakBetweenPhrases(duration: Duration) =
|
||||||
edit { it[BreakBetweenPhrasesSecondsKey] = duration.inWholeSeconds.toInt() }
|
edit { it[BreakBetweenPhrasesSecondsKey] = duration.inWholeSeconds.toInt() }
|
||||||
|
|
||||||
|
override suspend fun setAutoPause(enabled: Boolean) = edit { it[AutoPauseKey] = enabled }
|
||||||
|
|
||||||
private suspend fun edit(transform: (MutablePreferences) -> Unit) {
|
private suspend fun edit(transform: (MutablePreferences) -> Unit) {
|
||||||
dataStore.edit(transform)
|
dataStore.edit(transform)
|
||||||
}
|
}
|
||||||
@@ -63,6 +65,7 @@ internal class DataStorePreferencesRepository(
|
|||||||
speed = this[PlaybackSpeedKey] ?: PlaybackPreferences.DEFAULT_SPEED,
|
speed = this[PlaybackSpeedKey] ?: PlaybackPreferences.DEFAULT_SPEED,
|
||||||
breakBetweenPhrases = this[BreakBetweenPhrasesSecondsKey]?.seconds
|
breakBetweenPhrases = this[BreakBetweenPhrasesSecondsKey]?.seconds
|
||||||
?: PlaybackPreferences.DEFAULT_BREAK_BETWEEN_PHRASES,
|
?: PlaybackPreferences.DEFAULT_BREAK_BETWEEN_PHRASES,
|
||||||
|
autoPause = this[AutoPauseKey] ?: PlaybackPreferences.DEFAULT_AUTO_PAUSE,
|
||||||
)
|
)
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
@@ -72,5 +75,6 @@ internal class DataStorePreferencesRepository(
|
|||||||
val ShowPhoneticMarkersKey = booleanPreferencesKey("show_phonetic_marker")
|
val ShowPhoneticMarkersKey = booleanPreferencesKey("show_phonetic_marker")
|
||||||
val PlaybackSpeedKey = floatPreferencesKey("playback_speed")
|
val PlaybackSpeedKey = floatPreferencesKey("playback_speed")
|
||||||
val BreakBetweenPhrasesSecondsKey = intPreferencesKey("break_between_phrases_seconds")
|
val BreakBetweenPhrasesSecondsKey = intPreferencesKey("break_between_phrases_seconds")
|
||||||
|
val AutoPauseKey = booleanPreferencesKey("auto_pause")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
<string name="playback_settings_action">Arventennoù al lenn</string>
|
<string name="playback_settings_action">Arventennoù al lenn</string>
|
||||||
<string name="break_between_phrases_title">Ehan etre ar frazennoù</string>
|
<string name="break_between_phrases_title">Ehan etre ar frazennoù</string>
|
||||||
<string name="break_between_phrases_value">%1$d s</string>
|
<string name="break_between_phrases_value">%1$d s</string>
|
||||||
|
<string name="auto_pause_title">Ehan emgefreek</string>
|
||||||
|
<string name="auto_pause_description">Ehanañ goude pep frazenn</string>
|
||||||
|
|
||||||
<string name="player_back">Distreiñ</string>
|
<string name="player_back">Distreiñ</string>
|
||||||
<string name="player_previous_sentence">Frazenn a-raok</string>
|
<string name="player_previous_sentence">Frazenn a-raok</string>
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
<string name="playback_settings_action">Réglages de lecture</string>
|
<string name="playback_settings_action">Réglages de lecture</string>
|
||||||
<string name="break_between_phrases_title">Pause entre les phrases</string>
|
<string name="break_between_phrases_title">Pause entre les phrases</string>
|
||||||
<string name="break_between_phrases_value">%1$d s</string>
|
<string name="break_between_phrases_value">%1$d s</string>
|
||||||
|
<string name="auto_pause_title">Pause automatique</string>
|
||||||
|
<string name="auto_pause_description">Mettre en pause après chaque phrase</string>
|
||||||
|
|
||||||
<string name="player_back">Retour</string>
|
<string name="player_back">Retour</string>
|
||||||
<string name="player_previous_sentence">Phrase précédente</string>
|
<string name="player_previous_sentence">Phrase précédente</string>
|
||||||
|
|||||||
+33
-2
@@ -15,7 +15,10 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import bzh.ajaury.chombev.player.ui.components.LabeledSlider
|
import bzh.ajaury.chombev.player.ui.components.LabeledSlider
|
||||||
import bzh.ajaury.chombev.preferences.model.PlaybackPreferences
|
import bzh.ajaury.chombev.preferences.model.PlaybackPreferences
|
||||||
|
import bzh.ajaury.chombev.preferences.ui.PreferenceToggle
|
||||||
import bzh.ajaury.chombev.resources.generated.resources.Res
|
import bzh.ajaury.chombev.resources.generated.resources.Res
|
||||||
|
import bzh.ajaury.chombev.resources.generated.resources.auto_pause_description
|
||||||
|
import bzh.ajaury.chombev.resources.generated.resources.auto_pause_title
|
||||||
import bzh.ajaury.chombev.resources.generated.resources.break_between_phrases_title
|
import bzh.ajaury.chombev.resources.generated.resources.break_between_phrases_title
|
||||||
import bzh.ajaury.chombev.resources.generated.resources.break_between_phrases_value
|
import bzh.ajaury.chombev.resources.generated.resources.break_between_phrases_value
|
||||||
import bzh.ajaury.chombev.resources.generated.resources.playback_speed_title
|
import bzh.ajaury.chombev.resources.generated.resources.playback_speed_title
|
||||||
@@ -23,16 +26,19 @@ import org.jetbrains.compose.resources.stringResource
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bottom sheet letting the user adjust playback: the audio [speed] and the [breakSeconds] silent
|
* Bottom sheet letting the user adjust playback: the audio [speed], whether playback stops after
|
||||||
* pause inserted between phrases.
|
* each phrase ([autoPause]), and the [breakSeconds] silent pause inserted between phrases. When
|
||||||
|
* [autoPause] is on, the break setting is ignored and shown as disabled.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun PlaybackSpeedBottomSheet(
|
fun PlaybackSpeedBottomSheet(
|
||||||
speed: Float,
|
speed: Float,
|
||||||
breakSeconds: Float,
|
breakSeconds: Float,
|
||||||
|
autoPause: Boolean,
|
||||||
onSpeedChange: (Float) -> Unit = {},
|
onSpeedChange: (Float) -> Unit = {},
|
||||||
onBreakChange: (Float) -> Unit = {},
|
onBreakChange: (Float) -> Unit = {},
|
||||||
|
onAutoPauseChange: (Boolean) -> Unit = {},
|
||||||
onDismiss: () -> Unit = {},
|
onDismiss: () -> Unit = {},
|
||||||
sheetState: SheetState = rememberModalBottomSheetState(),
|
sheetState: SheetState = rememberModalBottomSheetState(),
|
||||||
) {
|
) {
|
||||||
@@ -43,8 +49,10 @@ fun PlaybackSpeedBottomSheet(
|
|||||||
PlaybackSpeedContent(
|
PlaybackSpeedContent(
|
||||||
speed = speed,
|
speed = speed,
|
||||||
breakSeconds = breakSeconds,
|
breakSeconds = breakSeconds,
|
||||||
|
autoPause = autoPause,
|
||||||
onSpeedChange = onSpeedChange,
|
onSpeedChange = onSpeedChange,
|
||||||
onBreakChange = onBreakChange,
|
onBreakChange = onBreakChange,
|
||||||
|
onAutoPauseChange = onAutoPauseChange,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,8 +61,10 @@ fun PlaybackSpeedBottomSheet(
|
|||||||
private fun PlaybackSpeedContent(
|
private fun PlaybackSpeedContent(
|
||||||
speed: Float,
|
speed: Float,
|
||||||
breakSeconds: Float,
|
breakSeconds: Float,
|
||||||
|
autoPause: Boolean,
|
||||||
onSpeedChange: (Float) -> Unit = {},
|
onSpeedChange: (Float) -> Unit = {},
|
||||||
onBreakChange: (Float) -> Unit = {},
|
onBreakChange: (Float) -> Unit = {},
|
||||||
|
onAutoPauseChange: (Boolean) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
// Number of discrete stops strictly between the endpoints (e.g. 0.5..1.0 by 0.1 → 4).
|
// Number of discrete stops strictly between the endpoints (e.g. 0.5..1.0 by 0.1 → 4).
|
||||||
val speedSteps = with(PlaybackPreferences) {
|
val speedSteps = with(PlaybackPreferences) {
|
||||||
@@ -79,6 +89,12 @@ private fun PlaybackSpeedContent(
|
|||||||
steps = speedSteps,
|
steps = speedSteps,
|
||||||
onValueChange = onSpeedChange,
|
onValueChange = onSpeedChange,
|
||||||
)
|
)
|
||||||
|
PreferenceToggle(
|
||||||
|
title = stringResource(Res.string.auto_pause_title),
|
||||||
|
subtitle = stringResource(Res.string.auto_pause_description),
|
||||||
|
checked = autoPause,
|
||||||
|
onCheckedChange = onAutoPauseChange,
|
||||||
|
)
|
||||||
LabeledSlider(
|
LabeledSlider(
|
||||||
title = stringResource(Res.string.break_between_phrases_title),
|
title = stringResource(Res.string.break_between_phrases_title),
|
||||||
valueLabel = stringResource(
|
valueLabel = stringResource(
|
||||||
@@ -89,6 +105,8 @@ private fun PlaybackSpeedContent(
|
|||||||
valueRange = 0f..maxBreakSeconds.toFloat(),
|
valueRange = 0f..maxBreakSeconds.toFloat(),
|
||||||
steps = breakSteps,
|
steps = breakSteps,
|
||||||
onValueChange = onBreakChange,
|
onValueChange = onBreakChange,
|
||||||
|
// Auto-pause supersedes the break between phrases, so disable it while on.
|
||||||
|
enabled = !autoPause,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,6 +124,19 @@ private fun PlaybackSpeedContentPreview() {
|
|||||||
PlaybackSpeedContent(
|
PlaybackSpeedContent(
|
||||||
speed = 0.7f,
|
speed = 0.7f,
|
||||||
breakSeconds = 2f,
|
breakSeconds = 2f,
|
||||||
|
autoPause = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun PlaybackSpeedContentAutoPausePreview() {
|
||||||
|
MaterialTheme {
|
||||||
|
PlaybackSpeedContent(
|
||||||
|
speed = 0.7f,
|
||||||
|
breakSeconds = 2f,
|
||||||
|
autoPause = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ fun PlayerScreen(
|
|||||||
onTextSizeIncrease = viewModel::increaseSubtitleTextSize,
|
onTextSizeIncrease = viewModel::increaseSubtitleTextSize,
|
||||||
onSpeedChange = viewModel::setPlaybackSpeed,
|
onSpeedChange = viewModel::setPlaybackSpeed,
|
||||||
onBreakChange = viewModel::setBreakBetweenPhrases,
|
onBreakChange = viewModel::setBreakBetweenPhrases,
|
||||||
|
onAutoPauseChange = viewModel::setAutoPause,
|
||||||
onBackClicked = onBackClicked,
|
onBackClicked = onBackClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -102,6 +103,7 @@ fun PlayerScreen(
|
|||||||
onTextSizeIncrease: () -> Unit = {},
|
onTextSizeIncrease: () -> Unit = {},
|
||||||
onSpeedChange: (Float) -> Unit = {},
|
onSpeedChange: (Float) -> Unit = {},
|
||||||
onBreakChange: (Float) -> Unit = {},
|
onBreakChange: (Float) -> Unit = {},
|
||||||
|
onAutoPauseChange: (Boolean) -> Unit = {},
|
||||||
onBackClicked: () -> Unit = {},
|
onBackClicked: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
var showPreferences by remember { mutableStateOf(false) }
|
var showPreferences by remember { mutableStateOf(false) }
|
||||||
@@ -166,8 +168,10 @@ fun PlayerScreen(
|
|||||||
PlaybackSpeedBottomSheet(
|
PlaybackSpeedBottomSheet(
|
||||||
speed = uiState.playbackSpeed,
|
speed = uiState.playbackSpeed,
|
||||||
breakSeconds = uiState.breakBetweenPhrases.inWholeSeconds.toFloat(),
|
breakSeconds = uiState.breakBetweenPhrases.inWholeSeconds.toFloat(),
|
||||||
|
autoPause = uiState.autoPause,
|
||||||
onSpeedChange = onSpeedChange,
|
onSpeedChange = onSpeedChange,
|
||||||
onBreakChange = onBreakChange,
|
onBreakChange = onBreakChange,
|
||||||
|
onAutoPauseChange = onAutoPauseChange,
|
||||||
onDismiss = { showSpeed = false },
|
onDismiss = { showSpeed = false },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-1
@@ -21,7 +21,11 @@ fun LabeledSlider(
|
|||||||
valueRange: ClosedFloatingPointRange<Float>,
|
valueRange: ClosedFloatingPointRange<Float>,
|
||||||
steps: Int,
|
steps: Int,
|
||||||
onValueChange: (Float) -> Unit = {},
|
onValueChange: (Float) -> Unit = {},
|
||||||
|
enabled: Boolean = true,
|
||||||
) {
|
) {
|
||||||
|
// Dim the labels to match the Slider's disabled appearance.
|
||||||
|
val contentAlpha = if (enabled) 1f else 0.38f
|
||||||
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -31,11 +35,12 @@ fun LabeledSlider(
|
|||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = contentAlpha),
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = valueLabel,
|
text = valueLabel,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary.copy(alpha = contentAlpha),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Slider(
|
Slider(
|
||||||
@@ -43,6 +48,7 @@ fun LabeledSlider(
|
|||||||
onValueChange = onValueChange,
|
onValueChange = onValueChange,
|
||||||
valueRange = valueRange,
|
valueRange = valueRange,
|
||||||
steps = steps,
|
steps = steps,
|
||||||
|
enabled = enabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,3 +66,18 @@ private fun LabeledSliderPreview() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun LabeledSliderDisabledPreview() {
|
||||||
|
MaterialTheme {
|
||||||
|
LabeledSlider(
|
||||||
|
title = "Playback speed",
|
||||||
|
valueLabel = "x0.7",
|
||||||
|
value = 0.7f,
|
||||||
|
valueRange = 0.5f..2f,
|
||||||
|
steps = 10,
|
||||||
|
enabled = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+3
-2
@@ -14,6 +14,7 @@ data class PlayerUiState(
|
|||||||
val subtitlePreferences: SubtitlePreferences = SubtitlePreferences(),
|
val subtitlePreferences: SubtitlePreferences = SubtitlePreferences(),
|
||||||
val playbackSpeed: Float = PlaybackPreferences.DEFAULT_SPEED,
|
val playbackSpeed: Float = PlaybackPreferences.DEFAULT_SPEED,
|
||||||
val breakBetweenPhrases: Duration = PlaybackPreferences.DEFAULT_BREAK_BETWEEN_PHRASES,
|
val breakBetweenPhrases: Duration = PlaybackPreferences.DEFAULT_BREAK_BETWEEN_PHRASES,
|
||||||
|
val autoPause: Boolean = PlaybackPreferences.DEFAULT_AUTO_PAUSE,
|
||||||
) {
|
) {
|
||||||
val canGoToPreviousSentence: Boolean
|
val canGoToPreviousSentence: Boolean
|
||||||
get() = playerState.playbackTiming.position > 2.seconds
|
get() = playerState.playbackTiming.position > 2.seconds
|
||||||
@@ -21,8 +22,8 @@ data class PlayerUiState(
|
|||||||
val canGoToNextSentence: Boolean
|
val canGoToNextSentence: Boolean
|
||||||
get() = (playerState.currentSubtitleIndex ?: Int.MAX_VALUE) < playerState.subtitleLines.lastIndex
|
get() = (playerState.currentSubtitleIndex ?: Int.MAX_VALUE) < playerState.subtitleLines.lastIndex
|
||||||
|
|
||||||
/** True when either playback setting differs from its default, used to highlight the button. */
|
/** True when any playback setting differs from its default, used to highlight the button. */
|
||||||
val isPlaybackModified: Boolean
|
val isPlaybackModified: Boolean
|
||||||
get() = playbackSpeed != PlaybackPreferences.DEFAULT_SPEED ||
|
get() = playbackSpeed != PlaybackPreferences.DEFAULT_SPEED ||
|
||||||
breakBetweenPhrases != PlaybackPreferences.DEFAULT_BREAK_BETWEEN_PHRASES
|
breakBetweenPhrases != PlaybackPreferences.DEFAULT_BREAK_BETWEEN_PHRASES || autoPause
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -46,6 +46,7 @@ class PlayerViewModel(
|
|||||||
subtitlePreferences = subtitlePreferences,
|
subtitlePreferences = subtitlePreferences,
|
||||||
playbackSpeed = playbackPreferences.speed,
|
playbackSpeed = playbackPreferences.speed,
|
||||||
breakBetweenPhrases = playbackPreferences.breakBetweenPhrases,
|
breakBetweenPhrases = playbackPreferences.breakBetweenPhrases,
|
||||||
|
autoPause = playbackPreferences.autoPause,
|
||||||
)
|
)
|
||||||
}.stateIn(
|
}.stateIn(
|
||||||
scope = viewModelScope,
|
scope = viewModelScope,
|
||||||
@@ -136,6 +137,12 @@ class PlayerViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setAutoPause(enabled: Boolean) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
preferencesRepository.setAutoPause(enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun setShowTranscription(enabled: Boolean) {
|
fun setShowTranscription(enabled: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
preferencesRepository.setShowTranscription(enabled)
|
preferencesRepository.setShowTranscription(enabled)
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@ fun PreferenceToggle(
|
|||||||
Column(modifier = Modifier.weight(1f).padding(vertical = 8.dp)) {
|
Column(modifier = Modifier.weight(1f).padding(vertical = 8.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = subtitle,
|
text = subtitle,
|
||||||
|
|||||||
+1
-1
@@ -85,7 +85,7 @@ private fun SubtitlePreferencesContent(
|
|||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.subtitle_preferences_title),
|
text = stringResource(Res.string.subtitle_preferences_title),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
)
|
)
|
||||||
PreferenceToggle(
|
PreferenceToggle(
|
||||||
title = stringResource(Res.string.subtitle_transcription_label),
|
title = stringResource(Res.string.subtitle_transcription_label),
|
||||||
|
|||||||
Reference in New Issue
Block a user