summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Anton Potapov <apotapov@google.com> 2025-02-04 17:41:10 +0000
committer Anton Potapov <apotapov@google.com> 2025-02-05 15:41:47 +0000
commit251ae3eb26d0038b9d3618287d8d2956107e2d3c (patch)
treeadbaf816ba557007cd13952cf64bc47b1ccd65ad
parent55c5e8249fb033146639c4be28ecf6f3d249b090 (diff)
Use the new Material Slider API in the Volume Dialog
This fix also enables the immediate steps animation using the new API Flag: com.android.systemui.volume_redesign Fixes: 394315097 Test: manual on the phone. Open the Volume Dialog and adjust the Volume using buttons and dragging. Change-Id: I3c154ecddbda6b01884f3dd7ea136a8009e6e40d
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt110
1 files changed, 56 insertions, 54 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index ffafa065a7c8..a51e33ad05cc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -19,6 +19,7 @@ package com.android.systemui.volume.dialog.sliders.ui
import android.graphics.drawable.Drawable
import android.view.View
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@@ -39,8 +40,8 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
@@ -60,12 +61,11 @@ import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider
import javax.inject.Inject
import kotlin.math.round
import kotlin.math.roundToInt
+import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.currentCoroutineContext
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
@VolumeDialogSliderScope
class VolumeDialogSliderViewBinder
@@ -102,6 +102,7 @@ private fun VolumeDialogSlider(
hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
modifier: Modifier = Modifier,
) {
+ val coroutineScope = rememberCoroutineScope()
val colors =
SliderDefaults.colors(
thumbColor = MaterialTheme.colorScheme.primary,
@@ -110,8 +111,14 @@ private fun VolumeDialogSlider(
activeTrackColor = MaterialTheme.colorScheme.primary,
inactiveTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
)
- val collectedSliderState by viewModel.state.collectAsStateWithLifecycle(null)
- val sliderState = collectedSliderState ?: return
+ val collectedSliderStateModel by viewModel.state.collectAsStateWithLifecycle(null)
+ val sliderStateModel = collectedSliderStateModel ?: return
+
+ val steps = with(sliderStateModel.valueRange) { endInclusive - start - 1 }.toInt()
+
+ var animateJob: Job? = null
+ val animatedSliderValue =
+ remember(sliderStateModel.value) { Animatable(sliderStateModel.value) }
val interactionSource = remember { MutableInteractionSource() }
val hapticsViewModel: SliderHapticsViewModel? =
@@ -119,56 +126,63 @@ private fun VolumeDialogSlider(
rememberViewModel(traceName = "SliderHapticsViewModel") {
it.create(
interactionSource,
- sliderState.valueRange,
+ sliderStateModel.valueRange,
Orientation.Vertical,
- VolumeHapticsConfigsProvider.sliderHapticFeedbackConfig(sliderState.valueRange),
+ VolumeHapticsConfigsProvider.sliderHapticFeedbackConfig(
+ sliderStateModel.valueRange
+ ),
VolumeHapticsConfigsProvider.seekableSliderTrackerConfig,
)
}
}
- val state =
- remember(sliderState.valueRange) {
+ val sliderState =
+ remember(steps, sliderStateModel.valueRange) {
SliderState(
- value = sliderState.value,
- valueRange = sliderState.valueRange,
- steps =
- (sliderState.valueRange.endInclusive - sliderState.valueRange.start - 1)
- .toInt(),
+ value = sliderStateModel.value,
+ valueRange = sliderStateModel.valueRange,
+ steps = steps,
)
- .apply {
- onValueChangeFinished = {
- viewModel.onStreamChangeFinished(value.roundToInt())
+ .also { sliderState ->
+ sliderState.onValueChangeFinished = {
+ viewModel.onStreamChangeFinished(sliderState.value.roundToInt())
hapticsViewModel?.onValueChangeEnded()
}
- setOnValueChangeListener {
- value = it
- hapticsViewModel?.addVelocityDataPoint(it)
+ sliderState.onValueChange = { newValue ->
+ if (newValue != animatedSliderValue.targetValue) {
+ animateJob?.cancel()
+ animateJob =
+ coroutineScope.launch {
+ animatedSliderValue.animateTo(newValue) {
+ sliderState.value = value
+ }
+ }
+ }
+
+ hapticsViewModel?.addVelocityDataPoint(newValue)
overscrollViewModel.setSlider(
- value = value,
- min = valueRange.start,
- max = valueRange.endInclusive,
+ value = sliderState.value,
+ min = sliderState.valueRange.start,
+ max = sliderState.valueRange.endInclusive,
)
- viewModel.setStreamVolume(it, true)
+ viewModel.setStreamVolume(newValue, true)
}
}
}
- var lastDiscreteStep by remember { mutableFloatStateOf(round(sliderState.value)) }
- LaunchedEffect(sliderState.value) {
- state.value = sliderState.value
- snapshotFlow { sliderState.value }
- .map { round(it) }
- .filter { it != lastDiscreteStep }
- .distinctUntilChanged()
- .collect { discreteStep ->
- lastDiscreteStep = discreteStep
- hapticsViewModel?.onValueChange(discreteStep)
- }
+
+ var lastDiscreteStep by remember { mutableFloatStateOf(round(sliderStateModel.value)) }
+ LaunchedEffect(sliderStateModel.value) {
+ val value = sliderStateModel.value
+ launch { animatedSliderValue.animateTo(value) }
+ if (value != lastDiscreteStep) {
+ lastDiscreteStep = value
+ hapticsViewModel?.onValueChange(value)
+ }
}
VerticalSlider(
- state = state,
- enabled = !sliderState.isDisabled,
+ state = sliderState,
+ enabled = !sliderStateModel.isDisabled,
reverseDirection = true,
colors = colors,
interactionSource = interactionSource,
@@ -185,14 +199,14 @@ private fun VolumeDialogSlider(
},
track = {
VolumeDialogSliderTrack(
- state,
+ sliderState,
colors = colors,
- isEnabled = !sliderState.isDisabled,
+ isEnabled = !sliderStateModel.isDisabled,
activeTrackEndIcon = { iconsState ->
- VolumeIcon(sliderState.icon, iconsState.isActiveTrackEndIconVisible)
+ VolumeIcon(sliderStateModel.icon, iconsState.isActiveTrackEndIconVisible)
},
inactiveTrackEndIcon = { iconsState ->
- VolumeIcon(sliderState.icon, !iconsState.isActiveTrackEndIconVisible)
+ VolumeIcon(sliderStateModel.icon, !iconsState.isActiveTrackEndIconVisible)
},
)
},
@@ -214,15 +228,3 @@ private fun BoxScope.VolumeIcon(
Icon(painter = DrawablePainter(drawable), contentDescription = null)
}
}
-
-@OptIn(ExperimentalMaterial3Api::class)
-fun SliderState.setOnValueChangeListener(onValueChange: ((Float) -> Unit)?) {
- with(javaClass.getDeclaredField("onValueChange")) {
- val oldIsAccessible = isAccessible
- AutoCloseable { isAccessible = oldIsAccessible }
- .use {
- isAccessible = true
- set(this@setOnValueChangeListener, onValueChange)
- }
- }
-}