Merge pull request #1918 from vitorpamplona/claude/draggable-video-progress-CQODi

feat: add drag-to-seek on video progress indicator
This commit is contained in:
Vitor Pamplona
2026-03-24 10:12:31 -04:00
committed by GitHub
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.service.playback.composable.controls
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@@ -30,7 +31,9 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -103,21 +106,43 @@ private fun HorizontalLinearProgressIndicator(
scrubberColor: Color = playedColor, scrubberColor: Color = playedColor,
rectHeightDp: Dp = 4.dp, rectHeightDp: Dp = 4.dp,
) { ) {
var isDragging by remember { mutableStateOf(false) }
var dragProgress by remember { mutableFloatStateOf(0f) }
Canvas( Canvas(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = rectHeightDp * 2.5f) .padding(horizontal = rectHeightDp * 2.5f)
.pointerInput(Unit) { .pointerInput(Unit) {
detectTapGestures { offset -> detectTapGestures { offset ->
// Capture the exact position
onSeek(offset.x / this.size.width.toFloat()) onSeek(offset.x / this.size.width.toFloat())
} }
}.pointerInput(Unit) {
detectDragGestures(
onDragStart = { offset ->
isDragging = true
dragProgress = (offset.x / this.size.width.toFloat()).coerceIn(0f, 1f)
},
onDrag = { change, _ ->
change.consume()
dragProgress = (change.position.x / this.size.width.toFloat()).coerceIn(0f, 1f)
},
onDragEnd = {
onSeek(dragProgress)
isDragging = false
},
onDragCancel = {
isDragging = false
},
)
}.padding(vertical = rectHeightDp * 2) }.padding(vertical = rectHeightDp * 2)
.height(rectHeightDp) .height(rectHeightDp)
.onSizeChanged { (w, _) -> onLayoutWidthChanged(w) }, .onSizeChanged { (w, _) -> onLayoutWidthChanged(w) },
) { ) {
val positionX = (currentPositionProgress() * size.width).coerceAtLeast(0f) val displayProgress = if (isDragging) dragProgress else currentPositionProgress()
val positionX = (displayProgress * size.width).coerceAtLeast(0f)
val bufferX = (bufferedPositionProgress() * size.width).coerceAtLeast(0f) val bufferX = (bufferedPositionProgress() * size.width).coerceAtLeast(0f)
val scrubberRadius = if (isDragging) size.height * 3f else size.height * 2f
drawRect(unplayedColor, size = Size(size.width, size.height)) drawRect(unplayedColor, size = Size(size.width, size.height))
drawRect(bufferedColor, size = Size(bufferX, size.height)) drawRect(bufferedColor, size = Size(bufferX, size.height))
@@ -125,7 +150,7 @@ private fun HorizontalLinearProgressIndicator(
drawCircle( drawCircle(
color = scrubberColor, color = scrubberColor,
radius = size.height * 2f, radius = scrubberRadius,
center = Offset(x = positionX, y = size.height / 2.0f), center = Offset(x = positionX, y = size.height / 2.0f),
) )
} }