mirror of
https://github.com/bbara04/Purefin.git
synced 2026-03-31 17:10:08 +02:00
feature: add resume/pause functionality to PlayerGesturesLayer and update seek duration
This commit is contained in:
@@ -19,13 +19,14 @@ fun PurefinIconButton(
|
|||||||
icon: ImageVector,
|
icon: ImageVector,
|
||||||
contentDescription: String,
|
contentDescription: String,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier,
|
||||||
|
size: Int = 52
|
||||||
) {
|
) {
|
||||||
val scheme = MaterialTheme.colorScheme
|
val scheme = MaterialTheme.colorScheme
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.size(52.dp)
|
.size(size.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(scheme.secondary)
|
.background(scheme.secondary)
|
||||||
.clickable { onClick() },
|
.clickable { onClick() },
|
||||||
|
|||||||
@@ -11,24 +11,19 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.ArrowBack
|
import androidx.compose.material.icons.outlined.ArrowBack
|
||||||
import androidx.compose.material.icons.outlined.Cast
|
import androidx.compose.material.icons.outlined.Cast
|
||||||
import androidx.compose.material.icons.outlined.Forward10
|
|
||||||
import androidx.compose.material.icons.outlined.Forward30
|
import androidx.compose.material.icons.outlined.Forward30
|
||||||
import androidx.compose.material.icons.outlined.LiveTv
|
|
||||||
import androidx.compose.material.icons.outlined.MoreVert
|
import androidx.compose.material.icons.outlined.MoreVert
|
||||||
import androidx.compose.material.icons.outlined.Pause
|
import androidx.compose.material.icons.outlined.Pause
|
||||||
import androidx.compose.material.icons.outlined.PlayArrow
|
import androidx.compose.material.icons.outlined.PlayArrow
|
||||||
import androidx.compose.material.icons.outlined.PlaylistPlay
|
import androidx.compose.material.icons.outlined.PlaylistPlay
|
||||||
import androidx.compose.material.icons.outlined.Replay10
|
import androidx.compose.material.icons.outlined.Replay10
|
||||||
import androidx.compose.material.icons.outlined.Replay30
|
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material.icons.outlined.SkipNext
|
import androidx.compose.material.icons.outlined.SkipNext
|
||||||
import androidx.compose.material.icons.outlined.SkipPrevious
|
import androidx.compose.material.icons.outlined.SkipPrevious
|
||||||
import androidx.compose.material.icons.outlined.Subtitles
|
import androidx.compose.material.icons.outlined.Subtitles
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -38,7 +33,6 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
@@ -94,15 +88,6 @@ fun PlayerControlsOverlay(
|
|||||||
onOpenQueue = onOpenQueue,
|
onOpenQueue = onOpenQueue,
|
||||||
modifier = Modifier.align(Alignment.TopCenter)
|
modifier = Modifier.align(Alignment.TopCenter)
|
||||||
)
|
)
|
||||||
CenterControls(
|
|
||||||
isPlaying = uiState.isPlaying,
|
|
||||||
isLive = uiState.isLive,
|
|
||||||
onPlayPause = onPlayPause,
|
|
||||||
onSeekForward = { onSeekRelative(30_000) },
|
|
||||||
onSeekBackward = { onSeekRelative(-10_000) },
|
|
||||||
onSeekLiveEdge = onSeekLiveEdge,
|
|
||||||
modifier = Modifier.align(Alignment.Center)
|
|
||||||
)
|
|
||||||
BottomSection(
|
BottomSection(
|
||||||
uiState = uiState,
|
uiState = uiState,
|
||||||
scrubbing = scrubbing,
|
scrubbing = scrubbing,
|
||||||
@@ -111,6 +96,10 @@ fun PlayerControlsOverlay(
|
|||||||
onScrubFinished = { scrubbing = false },
|
onScrubFinished = { scrubbing = false },
|
||||||
onNext = onNext,
|
onNext = onNext,
|
||||||
onPrevious = onPrevious,
|
onPrevious = onPrevious,
|
||||||
|
onPlayPause = onPlayPause,
|
||||||
|
onSeekForward = { onSeekRelative(30_000) },
|
||||||
|
onSeekBackward = { onSeekRelative(-10_000) },
|
||||||
|
onSeekLiveEdge = onSeekLiveEdge,
|
||||||
onToggleCaptions = onToggleCaptions,
|
onToggleCaptions = onToggleCaptions,
|
||||||
onShowSettings = onShowSettings,
|
onShowSettings = onShowSettings,
|
||||||
onQueueSelected = onQueueSelected,
|
onQueueSelected = onQueueSelected,
|
||||||
@@ -161,80 +150,6 @@ private fun TopBar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun CenterControls(
|
|
||||||
isPlaying: Boolean,
|
|
||||||
isLive: Boolean,
|
|
||||||
onPlayPause: () -> Unit,
|
|
||||||
onSeekForward: () -> Unit,
|
|
||||||
onSeekBackward: () -> Unit,
|
|
||||||
onSeekLiveEdge: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
val scheme = MaterialTheme.colorScheme
|
|
||||||
Column(
|
|
||||||
modifier = modifier.fillMaxWidth(),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
OverlayActionButton(
|
|
||||||
icon = Icons.Outlined.Replay10,
|
|
||||||
label = "-10",
|
|
||||||
onClick = onSeekBackward
|
|
||||||
)
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(CircleShape)
|
|
||||||
.background(scheme.primary.copy(alpha = 0.9f))
|
|
||||||
) {
|
|
||||||
val icon = if (isPlaying) Icons.Outlined.Pause else Icons.Outlined.PlayArrow
|
|
||||||
GhostIconButton(
|
|
||||||
icon = icon,
|
|
||||||
contentDescription = "Play/Pause",
|
|
||||||
onClick = onPlayPause,
|
|
||||||
modifier = Modifier.padding(6.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
OverlayActionButton(
|
|
||||||
icon = Icons.Outlined.Forward30,
|
|
||||||
label = "+30",
|
|
||||||
onClick = onSeekForward
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
if (isLive) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
Icon(Icons.Outlined.LiveTv, contentDescription = null, tint = scheme.primary)
|
|
||||||
Text(
|
|
||||||
text = "Live",
|
|
||||||
color = scheme.primary,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(CircleShape)
|
|
||||||
.background(scheme.primary.copy(alpha = 0.15f))
|
|
||||||
.padding(horizontal = 10.dp, vertical = 6.dp)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = "Catch up",
|
|
||||||
color = scheme.onSurface,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(CircleShape)
|
|
||||||
.background(scheme.surfaceVariant.copy(alpha = 0.7f))
|
|
||||||
.clickable { onSeekLiveEdge() }
|
|
||||||
.padding(horizontal = 12.dp, vertical = 6.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BottomSection(
|
private fun BottomSection(
|
||||||
uiState: PlayerUiState,
|
uiState: PlayerUiState,
|
||||||
@@ -244,6 +159,10 @@ private fun BottomSection(
|
|||||||
onScrubFinished: () -> Unit,
|
onScrubFinished: () -> Unit,
|
||||||
onNext: () -> Unit,
|
onNext: () -> Unit,
|
||||||
onPrevious: () -> Unit,
|
onPrevious: () -> Unit,
|
||||||
|
onPlayPause: () -> Unit,
|
||||||
|
onSeekForward: () -> Unit,
|
||||||
|
onSeekBackward: () -> Unit,
|
||||||
|
onSeekLiveEdge: () -> Unit,
|
||||||
onToggleCaptions: () -> Unit,
|
onToggleCaptions: () -> Unit,
|
||||||
onShowSettings: () -> Unit,
|
onShowSettings: () -> Unit,
|
||||||
onQueueSelected: (String) -> Unit,
|
onQueueSelected: (String) -> Unit,
|
||||||
@@ -255,7 +174,8 @@ private fun BottomSection(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 8.dp),
|
.padding(horizontal = 8.dp),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = formatTime(uiState.positionMs),
|
text = formatTime(uiState.positionMs),
|
||||||
@@ -263,7 +183,17 @@ private fun BottomSection(
|
|||||||
style = MaterialTheme.typography.bodySmall
|
style = MaterialTheme.typography.bodySmall
|
||||||
)
|
)
|
||||||
if (uiState.isLive) {
|
if (uiState.isLive) {
|
||||||
Text(text = "LIVE", color = scheme.primary, fontWeight = FontWeight.Bold)
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(text = "LIVE", color = scheme.primary, fontWeight = FontWeight.Bold)
|
||||||
|
Text(
|
||||||
|
text = "Catch up",
|
||||||
|
color = scheme.onSurface,
|
||||||
|
modifier = Modifier.clickable { onSeekLiveEdge() }
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = formatTime(uiState.durationMs),
|
text = formatTime(uiState.durationMs),
|
||||||
@@ -283,26 +213,52 @@ private fun BottomSection(
|
|||||||
onScrubFinished = onScrubFinished
|
onScrubFinished = onScrubFinished
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Row(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 4.dp),
|
.padding(horizontal = 4.dp),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
) {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
Row(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
PurefinIconButton(
|
PurefinIconButton(
|
||||||
icon = Icons.Outlined.SkipPrevious,
|
icon = Icons.Outlined.SkipPrevious,
|
||||||
contentDescription = "Previous",
|
contentDescription = "Previous",
|
||||||
onClick = onPrevious
|
onClick = onPrevious,
|
||||||
|
size = 64
|
||||||
|
)
|
||||||
|
PurefinIconButton(
|
||||||
|
icon = Icons.Outlined.Replay10,
|
||||||
|
contentDescription = "Seek backward",
|
||||||
|
onClick = onSeekBackward,
|
||||||
|
size = 64
|
||||||
|
)
|
||||||
|
PurefinIconButton(
|
||||||
|
icon = if (uiState.isPlaying) Icons.Outlined.Pause else Icons.Outlined.PlayArrow,
|
||||||
|
contentDescription = "Play/Pause",
|
||||||
|
onClick = onPlayPause,
|
||||||
|
size = 64
|
||||||
|
)
|
||||||
|
PurefinIconButton(
|
||||||
|
icon = Icons.Outlined.Forward30,
|
||||||
|
contentDescription = "Seek forward",
|
||||||
|
onClick = onSeekForward,
|
||||||
|
size = 64
|
||||||
)
|
)
|
||||||
PurefinIconButton(
|
PurefinIconButton(
|
||||||
icon = Icons.Outlined.SkipNext,
|
icon = Icons.Outlined.SkipNext,
|
||||||
contentDescription = "Next",
|
contentDescription = "Next",
|
||||||
onClick = onNext
|
onClick = onNext,
|
||||||
|
size = 64
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
Row(
|
||||||
|
modifier = Modifier.align(Alignment.CenterEnd),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
PurefinIconButton(
|
PurefinIconButton(
|
||||||
icon = Icons.Outlined.Subtitles,
|
icon = Icons.Outlined.Subtitles,
|
||||||
contentDescription = "Captions",
|
contentDescription = "Captions",
|
||||||
@@ -319,23 +275,6 @@ private fun BottomSection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun OverlayActionButton(
|
|
||||||
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
|
||||||
label: String,
|
|
||||||
onClick: () -> Unit
|
|
||||||
) {
|
|
||||||
val scheme = MaterialTheme.colorScheme
|
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
|
||||||
GhostIconButton(
|
|
||||||
icon = icon,
|
|
||||||
contentDescription = label,
|
|
||||||
onClick = onClick
|
|
||||||
)
|
|
||||||
Text(text = label, color = scheme.onSurface, style = MaterialTheme.typography.labelSmall)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun formatTime(positionMs: Long): String {
|
private fun formatTime(positionMs: Long): String {
|
||||||
val totalSeconds = positionMs / 1000
|
val totalSeconds = positionMs / 1000
|
||||||
val seconds = (totalSeconds % 60).toInt()
|
val seconds = (totalSeconds % 60).toInt()
|
||||||
|
|||||||
Reference in New Issue
Block a user