Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6d8cbd52 authored by Anton Potapov's avatar Anton Potapov
Browse files

Remove slider animation when set model first time

Flag: com.android.systemui.volume_redesign
Bug: 369994956
Test: manual on table. Open Volume Dialog
Change-Id: I64779238d14777fa8155deb3c9acea7781ddd6d8
parent 0246da22
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -561,6 +561,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">
@@ -570,6 +572,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"/>
+42 −37
Original line number Diff line number Diff line
@@ -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
@@ -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 }
}
+16 −13
Original line number Diff line number Diff line
@@ -73,7 +73,8 @@ constructor(
            .filterNotNull()

    val state: Flow<VolumeDialogSliderStateModel> =
        model.flatMapLatest { streamModel ->
        model
            .flatMapLatest { streamModel ->
                with(streamModel) {
                        volumeDialogSliderIconProvider.getStreamIcon(
                            stream = stream,
@@ -86,6 +87,8 @@ constructor(
                    }
                    .map { icon -> streamModel.toStateModel(icon) }
            }
            .stateIn(coroutineScope, SharingStarted.Eagerly, null)
            .filterNotNull()

    init {
        userVolumeUpdates
+16 −7
Original line number Diff line number Diff line
@@ -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
@@ -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()
        }
    }
}