From 70b5b20aabdba5d98192478d2896abb2c089a230 Mon Sep 17 00:00:00 2001 From: theo Date: Fri, 15 May 2026 00:11:00 -0300 Subject: [PATCH] improve animations again (seek fix) --- .../components/WavySliderExpressive.kt | 85 +++++++------------ .../components/player/FullPlayerContent.kt | 7 +- .../components/subcomps/PlayerSeekBar.kt | 4 +- 3 files changed, 37 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/WavySliderExpressive.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/WavySliderExpressive.kt index f1ed8d1b4..37749b3b7 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/WavySliderExpressive.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/WavySliderExpressive.kt @@ -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 @@ -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 @@ -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 @@ -62,6 +59,7 @@ fun WavySliderExpressive( interactionSource: MutableInteractionSource? = null, valueRange: ClosedFloatingPointRange = 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, @@ -91,7 +89,6 @@ 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) @@ -99,11 +96,11 @@ fun WavySliderExpressive( 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, @@ -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 @@ -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) { @@ -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 } diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/player/FullPlayerContent.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/player/FullPlayerContent.kt index bd47be9c4..a9c21adbf 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/player/FullPlayerContent.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/player/FullPlayerContent.kt @@ -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) @@ -1838,7 +1837,7 @@ private fun PlayerProgressBarSection( private fun EfficientSlider( valueState: androidx.compose.runtime.State, onValueChange: (Float) -> Unit, - onValueChangeFinished: () -> Unit, + onValueCommit: (Float) -> Unit, thumbColor: Color, activeTrackColor: Color, inactiveTrackColor: Color, @@ -1864,7 +1863,7 @@ private fun EfficientSlider( WavySliderExpressive( value = valueState.value, onValueChange = onValueChangeWithHaptics, - onValueChangeFinished = onValueChangeFinished, + onValueCommit = onValueCommit, interactionSource = interactionSource, activeTrackColor = activeTrackColor, inactiveTrackColor = inactiveTrackColor, diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/subcomps/PlayerSeekBar.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/subcomps/PlayerSeekBar.kt index 428e82000..c3cbd115b 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/subcomps/PlayerSeekBar.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/subcomps/PlayerSeekBar.kt @@ -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 },