new: add previous and next sentence navigation in PlayerControl and PlayerViewModel
This commit is contained in:
@@ -3,14 +3,18 @@ package fr.ajaury.gwenedeg.player.ui
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Pause
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.filled.Replay
|
||||
import androidx.compose.material.icons.filled.SkipNext
|
||||
import androidx.compose.material.icons.filled.SkipPrevious
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -20,9 +24,11 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import fr.ajaury.gwenedeg.player.model.PlaybackState
|
||||
import fr.ajaury.gwenedeg.player.model.PlaybackTiming
|
||||
import fr.ajaury.gwenedeg.theme.GwenedegTheme
|
||||
|
||||
@Composable
|
||||
fun PlayerControl(
|
||||
@@ -30,6 +36,8 @@ fun PlayerControl(
|
||||
playbackTiming: PlaybackTiming,
|
||||
onSeek: (Float) -> Unit = {},
|
||||
onMainPlayActionButtonClicked: () -> Unit = {},
|
||||
onPreviousSentenceClicked: () -> Unit = {},
|
||||
onNextSentenceClicked: () -> Unit = {},
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -51,15 +59,40 @@ fun PlayerControl(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Button(
|
||||
modifier = Modifier.size(60.dp),
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
onClick = onMainPlayActionButtonClicked,
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = playbackState.toActionIcon(),
|
||||
contentDescription = playbackState.toActionContentDescription(),
|
||||
)
|
||||
IconButton(
|
||||
modifier = Modifier.size(60.dp),
|
||||
onClick = onPreviousSentenceClicked,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.SkipPrevious,
|
||||
contentDescription = "Previous sentence",
|
||||
)
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier.size(60.dp),
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
onClick = onMainPlayActionButtonClicked,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = playbackState.toActionIcon(),
|
||||
contentDescription = playbackState.toActionContentDescription(),
|
||||
)
|
||||
}
|
||||
|
||||
IconButton(
|
||||
modifier = Modifier.size(60.dp),
|
||||
onClick = onNextSentenceClicked,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.SkipNext,
|
||||
contentDescription = "Next sentence",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,3 +110,14 @@ private fun PlaybackState.toActionContentDescription(): String =
|
||||
PlaybackState.ENDED -> "Replay"
|
||||
PlaybackState.PAUSED, PlaybackState.IDLE -> "Play"
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PlayerControlPreview() {
|
||||
GwenedegTheme {
|
||||
PlayerControl(
|
||||
playbackState = PlaybackState.PLAYING,
|
||||
playbackTiming = PlaybackTiming(positionMs = 1234, durationMs = 5000),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ fun PlayerScreen(
|
||||
onMainPlayActionButtonClicked = viewModel::performMainPlayAction,
|
||||
onSeekToTimePart = viewModel::seekToTimePart,
|
||||
onSeekToSentence = viewModel::seekToSentence,
|
||||
onPreviousSentenceClicked = viewModel::goToPreviousSentence,
|
||||
onNextSentenceClicked = viewModel::goToNextSentence,
|
||||
onBackClicked = onBackClicked,
|
||||
)
|
||||
}
|
||||
@@ -61,6 +63,8 @@ fun PlayerScreen(
|
||||
onMainPlayActionButtonClicked: () -> Unit = {},
|
||||
onSeekToTimePart: (progress: Float) -> Unit = {},
|
||||
onSeekToSentence: (position: Int) -> Unit = {},
|
||||
onPreviousSentenceClicked: () -> Unit = {},
|
||||
onNextSentenceClicked: () -> Unit = {},
|
||||
onBackClicked: () -> Unit = {},
|
||||
) {
|
||||
Scaffold(
|
||||
@@ -106,6 +110,8 @@ fun PlayerScreen(
|
||||
playbackTiming = uiState.playbackTiming,
|
||||
onSeek = onSeekToTimePart,
|
||||
onMainPlayActionButtonClicked = onMainPlayActionButtonClicked,
|
||||
onPreviousSentenceClicked = onPreviousSentenceClicked,
|
||||
onNextSentenceClicked = onNextSentenceClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+28
@@ -165,6 +165,33 @@ class PlayerViewModel(
|
||||
audioPlayer.seekTo(matchingSubtitleLine.startMs.milliseconds)
|
||||
}
|
||||
|
||||
fun goToPreviousSentence() {
|
||||
val lines = subtitle.value.lines
|
||||
val currentIndex = currentSentenceIndex() ?: return
|
||||
val currentLine = lines.getOrNull(currentIndex) ?: return
|
||||
val elapsedInSentence =
|
||||
audioPlayer.currentPosition.inWholeMilliseconds - currentLine.startMs
|
||||
val targetIndex = if (elapsedInSentence < PREVIOUS_SENTENCE_THRESHOLD.inWholeMilliseconds) {
|
||||
(currentIndex - 1)
|
||||
} else {
|
||||
currentIndex
|
||||
}.coerceAtLeast(0)
|
||||
|
||||
seekToSentence(targetIndex)
|
||||
}
|
||||
|
||||
fun goToNextSentence() {
|
||||
val lines = subtitle.value.lines
|
||||
val currentIndex = currentSentenceIndex() ?: return
|
||||
seekToSentence((currentIndex + 1).coerceAtMost(lines.lastIndex))
|
||||
}
|
||||
|
||||
private fun currentSentenceIndex(): Int? =
|
||||
getCurrentSubtitleIndex(
|
||||
subtitle = subtitle.value,
|
||||
progressMs = audioPlayer.currentPosition.inWholeMilliseconds,
|
||||
)
|
||||
|
||||
fun stop() {
|
||||
audioPlayer.stop()
|
||||
}
|
||||
@@ -177,5 +204,6 @@ class PlayerViewModel(
|
||||
|
||||
companion object {
|
||||
private val POSITION_POLL_INTERVAL = 50.milliseconds
|
||||
private val PREVIOUS_SENTENCE_THRESHOLD = 4.seconds
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user