Loading packages/SystemUI/res/values/styles.xml +3 −0 Original line number Diff line number Diff line Loading @@ -570,6 +570,8 @@ <item name="trackCornerSize">12dp</item> <item name="trackInsideCornerSize">2dp</item> <item name="trackStopIndicatorSize">6dp</item> <item name="trackIconSize">20dp</item> <item name="labelBehavior">gone</item> </style> <style name="SystemUI.Material3.Slider" parent="@style/Widget.Material3.Slider"> Loading @@ -579,6 +581,7 @@ <item name="tickColorInactive">@androidprv:color/materialColorPrimary</item> <item name="trackColorActive">@androidprv:color/materialColorPrimary</item> <item name="trackColorInactive">@androidprv:color/materialColorSurfaceContainerHighest</item> <item name="trackIconActiveColor">@androidprv:color/materialColorSurfaceContainerHighest</item> </style> <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/> Loading packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt +42 −37 Original line number Diff line number Diff line Loading @@ -16,18 +16,15 @@ package com.android.systemui.volume.dialog.sliders.ui import android.animation.Animator import android.animation.ObjectAnimator import android.annotation.SuppressLint import android.view.View import android.view.animation.DecelerateInterpolator import androidx.dynamicanimation.animation.FloatPropertyCompat import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce import com.android.systemui.res.R import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory import com.android.systemui.volume.dialog.ui.utils.suspendAnimate import com.google.android.material.slider.LabelFormatter import com.google.android.material.slider.Slider import javax.inject.Inject import kotlin.math.roundToInt Loading @@ -35,53 +32,61 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach private const val PROGRESS_CHANGE_ANIMATION_DURATION_MS = 80L @VolumeDialogSliderScope class VolumeDialogSliderViewBinder @Inject constructor( private val viewModel: VolumeDialogSliderViewModel, private val jankListenerFactory: JankListenerFactory, ) { constructor(private val viewModel: VolumeDialogSliderViewModel) { fun CoroutineScope.bind(view: View) { val sliderView: Slider = view.requireViewById<Slider>(R.id.volume_dialog_slider).apply { labelBehavior = LabelFormatter.LABEL_GONE trackIconActiveColor = trackInactiveTintList private val sliderValueProperty = object : FloatPropertyCompat<Slider>("value") { override fun getValue(slider: Slider): Float = slider.value override fun setValue(slider: Slider, value: Float) { slider.value = value } } private val springForce = SpringForce().apply { stiffness = SpringForce.STIFFNESS_MEDIUM dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY } fun CoroutineScope.bind(view: View) { var isInitialUpdate = true val sliderView: Slider = view.requireViewById(R.id.volume_dialog_slider) val animation = SpringAnimation(sliderView, sliderValueProperty) animation.spring = springForce sliderView.addOnChangeListener { _, value, fromUser -> viewModel.setStreamVolume(value.roundToInt(), fromUser) } viewModel.state.onEach { sliderView.setModel(it) }.launchIn(this) viewModel.state .onEach { sliderView.setModel(it, animation, isInitialUpdate) isInitialUpdate = false } .launchIn(this) } @SuppressLint("UseCompatLoadingForDrawables") private suspend fun Slider.setModel(model: VolumeDialogSliderStateModel) { private fun Slider.setModel( model: VolumeDialogSliderStateModel, animation: SpringAnimation, isInitialUpdate: Boolean, ) { valueFrom = model.minValue animation.setMinValue(model.minValue) valueTo = model.maxValue animation.setMaxValue(model.maxValue) // coerce the current value to the new value range before animating it. This prevents // animating from the value that is outside of current [valueFrom, valueTo]. value = value.coerceIn(valueFrom, valueTo) setValueAnimated( model.value, jankListenerFactory.update(this, PROGRESS_CHANGE_ANIMATION_DURATION_MS), ) trackIconActiveEnd = context.getDrawable(model.iconRes) setTrackIconActiveStart(model.iconRes) if (isInitialUpdate) { value = model.value } else { animation.animateToFinalPosition(model.value) } } private suspend fun Slider.setValueAnimated( newValue: Float, jankListener: Animator.AnimatorListener, ) { ObjectAnimator.ofFloat(value, newValue) .apply { duration = PROGRESS_CHANGE_ANIMATION_DURATION_MS interpolator = DecelerateInterpolator() addListener(jankListener) } .suspendAnimate<Float> { value = it } } packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt +16 −13 Original line number Diff line number Diff line Loading @@ -73,7 +73,8 @@ constructor( .filterNotNull() val state: Flow<VolumeDialogSliderStateModel> = model.flatMapLatest { streamModel -> model .flatMapLatest { streamModel -> with(streamModel) { volumeDialogSliderIconProvider.getStreamIcon( stream = stream, Loading @@ -86,6 +87,8 @@ constructor( } .map { icon -> streamModel.toStateModel(icon) } } .stateIn(coroutineScope, SharingStarted.Eagerly, null) .filterNotNull() init { userVolumeUpdates Loading packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt +16 −7 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.volume.dialog.ui.utils import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.animation.ValueAnimator.AnimatorUpdateListener import android.view.ViewPropertyAnimator import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringAnimation Loading Loading @@ -69,17 +70,25 @@ suspend fun ViewPropertyAnimator.suspendAnimate( @Suppress("UNCHECKED_CAST") suspend fun <T> ValueAnimator.suspendAnimate(onValueChanged: (T) -> Unit) { suspendCancellableCoroutine { continuation -> addListener( val listener = object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) = continuation.resumeIfCan(Unit) override fun onAnimationEnd(animation: Animator) = continuation.resumeIfCan(Unit) override fun onAnimationCancel(animation: Animator) = continuation.resumeIfCan(Unit) override fun onAnimationCancel(animation: Animator) = continuation.resumeIfCan(Unit) } ) addUpdateListener { onValueChanged(it.animatedValue as T) } .also(::addListener) val updateListener = AnimatorUpdateListener { onValueChanged(it.animatedValue as T) } .also(::addUpdateListener) start() continuation.invokeOnCancellation { cancel() } continuation.invokeOnCancellation { removeUpdateListener(updateListener) removeListener(listener) cancel() } } } Loading Loading
packages/SystemUI/res/values/styles.xml +3 −0 Original line number Diff line number Diff line Loading @@ -570,6 +570,8 @@ <item name="trackCornerSize">12dp</item> <item name="trackInsideCornerSize">2dp</item> <item name="trackStopIndicatorSize">6dp</item> <item name="trackIconSize">20dp</item> <item name="labelBehavior">gone</item> </style> <style name="SystemUI.Material3.Slider" parent="@style/Widget.Material3.Slider"> Loading @@ -579,6 +581,7 @@ <item name="tickColorInactive">@androidprv:color/materialColorPrimary</item> <item name="trackColorActive">@androidprv:color/materialColorPrimary</item> <item name="trackColorInactive">@androidprv:color/materialColorSurfaceContainerHighest</item> <item name="trackIconActiveColor">@androidprv:color/materialColorSurfaceContainerHighest</item> </style> <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/> Loading
packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt +42 −37 Original line number Diff line number Diff line Loading @@ -16,18 +16,15 @@ package com.android.systemui.volume.dialog.sliders.ui import android.animation.Animator import android.animation.ObjectAnimator import android.annotation.SuppressLint import android.view.View import android.view.animation.DecelerateInterpolator import androidx.dynamicanimation.animation.FloatPropertyCompat import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce import com.android.systemui.res.R import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory import com.android.systemui.volume.dialog.ui.utils.suspendAnimate import com.google.android.material.slider.LabelFormatter import com.google.android.material.slider.Slider import javax.inject.Inject import kotlin.math.roundToInt Loading @@ -35,53 +32,61 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach private const val PROGRESS_CHANGE_ANIMATION_DURATION_MS = 80L @VolumeDialogSliderScope class VolumeDialogSliderViewBinder @Inject constructor( private val viewModel: VolumeDialogSliderViewModel, private val jankListenerFactory: JankListenerFactory, ) { constructor(private val viewModel: VolumeDialogSliderViewModel) { fun CoroutineScope.bind(view: View) { val sliderView: Slider = view.requireViewById<Slider>(R.id.volume_dialog_slider).apply { labelBehavior = LabelFormatter.LABEL_GONE trackIconActiveColor = trackInactiveTintList private val sliderValueProperty = object : FloatPropertyCompat<Slider>("value") { override fun getValue(slider: Slider): Float = slider.value override fun setValue(slider: Slider, value: Float) { slider.value = value } } private val springForce = SpringForce().apply { stiffness = SpringForce.STIFFNESS_MEDIUM dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY } fun CoroutineScope.bind(view: View) { var isInitialUpdate = true val sliderView: Slider = view.requireViewById(R.id.volume_dialog_slider) val animation = SpringAnimation(sliderView, sliderValueProperty) animation.spring = springForce sliderView.addOnChangeListener { _, value, fromUser -> viewModel.setStreamVolume(value.roundToInt(), fromUser) } viewModel.state.onEach { sliderView.setModel(it) }.launchIn(this) viewModel.state .onEach { sliderView.setModel(it, animation, isInitialUpdate) isInitialUpdate = false } .launchIn(this) } @SuppressLint("UseCompatLoadingForDrawables") private suspend fun Slider.setModel(model: VolumeDialogSliderStateModel) { private fun Slider.setModel( model: VolumeDialogSliderStateModel, animation: SpringAnimation, isInitialUpdate: Boolean, ) { valueFrom = model.minValue animation.setMinValue(model.minValue) valueTo = model.maxValue animation.setMaxValue(model.maxValue) // coerce the current value to the new value range before animating it. This prevents // animating from the value that is outside of current [valueFrom, valueTo]. value = value.coerceIn(valueFrom, valueTo) setValueAnimated( model.value, jankListenerFactory.update(this, PROGRESS_CHANGE_ANIMATION_DURATION_MS), ) trackIconActiveEnd = context.getDrawable(model.iconRes) setTrackIconActiveStart(model.iconRes) if (isInitialUpdate) { value = model.value } else { animation.animateToFinalPosition(model.value) } } private suspend fun Slider.setValueAnimated( newValue: Float, jankListener: Animator.AnimatorListener, ) { ObjectAnimator.ofFloat(value, newValue) .apply { duration = PROGRESS_CHANGE_ANIMATION_DURATION_MS interpolator = DecelerateInterpolator() addListener(jankListener) } .suspendAnimate<Float> { value = it } }
packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt +16 −13 Original line number Diff line number Diff line Loading @@ -73,7 +73,8 @@ constructor( .filterNotNull() val state: Flow<VolumeDialogSliderStateModel> = model.flatMapLatest { streamModel -> model .flatMapLatest { streamModel -> with(streamModel) { volumeDialogSliderIconProvider.getStreamIcon( stream = stream, Loading @@ -86,6 +87,8 @@ constructor( } .map { icon -> streamModel.toStateModel(icon) } } .stateIn(coroutineScope, SharingStarted.Eagerly, null) .filterNotNull() init { userVolumeUpdates Loading
packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt +16 −7 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.volume.dialog.ui.utils import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.animation.ValueAnimator.AnimatorUpdateListener import android.view.ViewPropertyAnimator import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringAnimation Loading Loading @@ -69,17 +70,25 @@ suspend fun ViewPropertyAnimator.suspendAnimate( @Suppress("UNCHECKED_CAST") suspend fun <T> ValueAnimator.suspendAnimate(onValueChanged: (T) -> Unit) { suspendCancellableCoroutine { continuation -> addListener( val listener = object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) = continuation.resumeIfCan(Unit) override fun onAnimationEnd(animation: Animator) = continuation.resumeIfCan(Unit) override fun onAnimationCancel(animation: Animator) = continuation.resumeIfCan(Unit) override fun onAnimationCancel(animation: Animator) = continuation.resumeIfCan(Unit) } ) addUpdateListener { onValueChanged(it.animatedValue as T) } .also(::addListener) val updateListener = AnimatorUpdateListener { onValueChanged(it.animatedValue as T) } .also(::addUpdateListener) start() continuation.invokeOnCancellation { cancel() } continuation.invokeOnCancellation { removeUpdateListener(updateListener) removeListener(listener) cancel() } } } Loading