Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsDraggedAsState
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
Expand All @@ -18,8 +16,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.LinearWavyProgressIndicator
import androidx.compose.material3.ProgressIndicatorDefaults
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.WavyProgressIndicatorDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
Expand All @@ -28,6 +24,7 @@ import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Alignment
Expand Down Expand Up @@ -62,6 +59,7 @@ fun WavySliderExpressive(
interactionSource: MutableInteractionSource? = null,
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
onValueChangeFinished: (() -> Unit)? = null,
onValueCommit: ((Float) -> Unit)? = null,
activeTrackColor: Color = androidx.compose.material3.MaterialTheme.colorScheme.primary,
inactiveTrackColor: Color = androidx.compose.material3.MaterialTheme.colorScheme.surfaceVariant,
thumbColor: Color = androidx.compose.material3.MaterialTheme.colorScheme.primary,
Expand Down Expand Up @@ -91,19 +89,18 @@ fun WavySliderExpressive(
val normalizedValue = if (valueRange.endInclusive == valueRange.start) 0f
else ((value - valueRange.start) / (valueRange.endInclusive - valueRange.start)).coerceIn(0f, 1f)

val clampedValue = value.coerceIn(valueRange.start, valueRange.endInclusive)
val safeSemanticsStep = semanticsProgressStep.coerceIn(0.005f, 0.25f)
val semanticNormalizedValue = remember(normalizedValue, safeSemanticsStep) {
((normalizedValue / safeSemanticsStep).roundToInt() * safeSemanticsStep).coerceIn(0f, 1f)
}
val semanticSliderValue = remember(semanticNormalizedValue, valueRange) {
valueRange.start + semanticNormalizedValue * (valueRange.endInclusive - valueRange.start)
}
val resolvedInteractionSource = interactionSource ?: remember { MutableInteractionSource() }
val isDragged by resolvedInteractionSource.collectIsDraggedAsState()
val isPressed by resolvedInteractionSource.collectIsPressedAsState()
val latestOnValueChange by rememberUpdatedState(onValueChange)
val latestOnValueChangeFinished by rememberUpdatedState(onValueChangeFinished)
val latestOnValueCommit by rememberUpdatedState(onValueCommit)
var isPointerSeeking by remember { mutableStateOf(false) }
val isInteracting = isDragged || isPressed || isPointerSeeking
val isInteracting = isPointerSeeking

val thumbInteractionFraction by animateFloatAsState(
targetValue = if (isInteracting) 1f else 0f,
Expand Down Expand Up @@ -161,49 +158,28 @@ fun WavySliderExpressive(
Box(
modifier = modifier
.fillMaxWidth()
.height(containerHeight),
.height(containerHeight)
.clearAndSetSemantics {
if (!semanticsLabel.isNullOrBlank()) {
contentDescription = semanticsLabel
}
progressBarRangeInfo = ProgressBarRangeInfo(
current = semanticSliderValue,
range = valueRange.start..valueRange.endInclusive,
steps = 0
)
if (enabled) {
setProgress { requested ->
val coerced = requested.coerceIn(valueRange.start, valueRange.endInclusive)
latestOnValueChange(coerced)
latestOnValueCommit?.invoke(coerced)
?: latestOnValueChangeFinished?.invoke()
true
}
}
},
contentAlignment = Alignment.Center
) {
Slider(
value = clampedValue,
onValueChange = onValueChange,
modifier = Modifier
.fillMaxWidth()
.height(containerHeight)
// Keep slider accessible, but quantize semantic progress updates to reduce
// high-frequency semantics churn while visuals remain smooth.
.clearAndSetSemantics {
if (!semanticsLabel.isNullOrBlank()) {
contentDescription = semanticsLabel
}
progressBarRangeInfo = ProgressBarRangeInfo(
current = semanticSliderValue,
range = valueRange.start..valueRange.endInclusive,
steps = 0
)
if (enabled) {
setProgress { requested ->
val coerced = requested.coerceIn(valueRange.start, valueRange.endInclusive)
onValueChange(coerced)
onValueChangeFinished?.invoke()
true
}
}
},
enabled = enabled,
valueRange = valueRange,
onValueChangeFinished = onValueChangeFinished,
interactionSource = resolvedInteractionSource,
colors = SliderDefaults.colors(
thumbColor = Color.Transparent,
activeTrackColor = Color.Transparent,
inactiveTrackColor = Color.Transparent,
disabledThumbColor = Color.Transparent,
disabledActiveTrackColor = Color.Transparent,
disabledInactiveTrackColor = Color.Transparent
)
)

LinearWavyProgressIndicator(
progress = { renderedNormalizedProgress.floatValue },
modifier = Modifier
Expand Down Expand Up @@ -273,7 +249,8 @@ fun WavySliderExpressive(
val down = awaitFirstDown(requireUnconsumed = false)
isPointerSeeking = true
down.consume()
onValueChange(valueForX(down.position.x))
var latestGestureValue = valueForX(down.position.x)
latestOnValueChange(latestGestureValue)

var pointerId = down.id
while (true) {
Expand All @@ -290,11 +267,13 @@ fun WavySliderExpressive(

if (change.position != change.previousPosition) {
change.consume()
onValueChange(valueForX(change.position.x))
latestGestureValue = valueForX(change.position.x)
latestOnValueChange(latestGestureValue)
}
}

onValueChangeFinished?.invoke()
latestOnValueCommit?.invoke(latestGestureValue)
?: latestOnValueChangeFinished?.invoke()
} finally {
isPointerSeeking = false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1805,8 +1805,7 @@ private fun PlayerProgressBarSection(
EfficientSlider(
valueState = animatedProgressState,
onValueChange = { sliderDragValue = it },
onValueChangeFinished = {
val finalValue = sliderDragValue ?: animatedProgressState.value
onValueCommit = { finalValue ->
val targetMs = (finalValue * durationForCalc).roundToLong()
optimisticPosition = targetMs
onSeek(targetMs)
Expand Down Expand Up @@ -1838,7 +1837,7 @@ private fun PlayerProgressBarSection(
private fun EfficientSlider(
valueState: androidx.compose.runtime.State<Float>,
onValueChange: (Float) -> Unit,
onValueChangeFinished: () -> Unit,
onValueCommit: (Float) -> Unit,
thumbColor: Color,
activeTrackColor: Color,
inactiveTrackColor: Color,
Expand All @@ -1864,7 +1863,7 @@ private fun EfficientSlider(
WavySliderExpressive(
value = valueState.value,
onValueChange = onValueChangeWithHaptics,
onValueChangeFinished = onValueChangeFinished,
onValueCommit = onValueCommit,
interactionSource = interactionSource,
activeTrackColor = activeTrackColor,
inactiveTrackColor = inactiveTrackColor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ fun PlayerSeekBar(
hapticFeedback.performHapticFeedback(HapticFeedbackType.TextHandleMove)
}
},
onValueChangeFinished = {
onSeek((seekFraction * totalDuration).roundToLong())
onValueCommit = { finalFraction ->
onSeek((finalFraction * totalDuration).roundToLong())
onSeekPreview?.invoke(null)
isUserSeeking = false
},
Expand Down
Loading