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

Commit 011022d9 authored by Lucas Silva's avatar Lucas Silva
Browse files

Implement direct transition from dreaming -> hub

This handles the case where the dream is dismissed via tap by hooking
into the unocclusion remote animation to fade in the glanceable hub
before the dream fades out.

Known issues:
1. Notifications occassionally flicker during the dream -> hub
   transition. Not 100% reproducible, likely a timing issue with when
   the notifications are unhidden due to unocclusion and when they are
   re-hidden due to the animation running.
2. The screen dims when the dream is destroyed, and then brightens again
   when the dream is restarted underneath the hub. A follow-up could be
   to grab a wakelock during the transition to avoid this dimming while
   we are transitioning.

Test: atest SystemUiRoboTests
Bug: 325102385
Flag: ACONFIG com.android.systemui.communal_hub TEAMFOOD
Change-Id: I8859355dddf673c579110952a3a730735df71e1d
parent b6bab730
Loading
Loading
Loading
Loading
+2 −2
Original line number Original line Diff line number Diff line
@@ -8,7 +8,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.complication.ComplicationHostViewController
import com.android.systemui.complication.ComplicationHostViewController
import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel
import com.android.systemui.dreams.ui.viewmodel.DreamViewModel
import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.argumentCaptor
@@ -45,7 +45,7 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
    @Mock private lateinit var hostViewController: ComplicationHostViewController
    @Mock private lateinit var hostViewController: ComplicationHostViewController
    @Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
    @Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
    @Mock private lateinit var stateController: DreamOverlayStateController
    @Mock private lateinit var stateController: DreamOverlayStateController
    @Mock private lateinit var transitionViewModel: DreamOverlayViewModel
    @Mock private lateinit var transitionViewModel: DreamViewModel
    private val logBuffer = FakeLogBuffer.Factory.create()
    private val logBuffer = FakeLogBuffer.Factory.create()
    private lateinit var controller: DreamOverlayAnimationsController
    private lateinit var controller: DreamOverlayAnimationsController


+0 −36
Original line number Original line Diff line number Diff line
@@ -26,14 +26,9 @@ import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectValues
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -219,37 +214,6 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
            values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
            values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
        }
        }


    @Test
    fun transitionEnded() =
        testScope.runTest {
            val values by collectValues(underTest.transitionEnded)

            keyguardTransitionRepository.sendTransitionSteps(
                listOf(
                    TransitionStep(DOZING, DREAMING, 0.0f, STARTED),
                    TransitionStep(DOZING, DREAMING, 1.0f, FINISHED),
                    TransitionStep(DREAMING, LOCKSCREEN, 0.0f, STARTED),
                    TransitionStep(DREAMING, LOCKSCREEN, 0.1f, RUNNING),
                    TransitionStep(DREAMING, LOCKSCREEN, 1.0f, FINISHED),
                    TransitionStep(LOCKSCREEN, DREAMING, 0.0f, STARTED),
                    TransitionStep(LOCKSCREEN, DREAMING, 0.5f, RUNNING),
                    TransitionStep(LOCKSCREEN, DREAMING, 1.0f, FINISHED),
                    TransitionStep(DREAMING, GONE, 0.0f, STARTED),
                    TransitionStep(DREAMING, GONE, 0.5f, RUNNING),
                    TransitionStep(DREAMING, GONE, 1.0f, CANCELED),
                    TransitionStep(DREAMING, AOD, 0.0f, STARTED),
                    TransitionStep(DREAMING, AOD, 1.0f, FINISHED),
                ),
                testScope,
            )

            assertThat(values.size).isEqualTo(3)
            values.forEach {
                assertThat(it.transitionState == FINISHED || it.transitionState == CANCELED)
                    .isTrue()
            }
        }

    private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
    private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
        return TransitionStep(
        return TransitionStep(
            from = DREAMING,
            from = DREAMING,
+64 −17
Original line number Original line Diff line number Diff line
@@ -21,13 +21,10 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel


import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -79,13 +76,13 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
    init {
    init {
        kosmos.aodBurnInViewModel = aodBurnInViewModel
        kosmos.aodBurnInViewModel = aodBurnInViewModel
    }
    }

    val testScope = kosmos.testScope
    val testScope = kosmos.testScope
    val configurationRepository = kosmos.fakeConfigurationRepository
    val configurationRepository = kosmos.fakeConfigurationRepository
    val keyguardRepository = kosmos.fakeKeyguardRepository
    val keyguardRepository = kosmos.fakeKeyguardRepository
    val keyguardInteractor = kosmos.keyguardInteractor
    val keyguardInteractor = kosmos.keyguardInteractor
    val keyguardRootViewModel = kosmos.keyguardRootViewModel
    val keyguardRootViewModel = kosmos.keyguardRootViewModel
    val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
    val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
    val communalInteractor = kosmos.communalInteractor
    val shadeRepository = kosmos.shadeRepository
    val shadeRepository = kosmos.shadeRepository
    val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor
    val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor
    val largeScreenHeaderHelper = kosmos.mockLargeScreenHeaderHelper
    val largeScreenHeaderHelper = kosmos.mockLargeScreenHeaderHelper
@@ -238,7 +235,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
        }
        }


    @Test
    @Test
    fun glanceableHubAlpha() =
    fun glanceableHubAlpha_lockscreenToHub() =
        testScope.runTest {
        testScope.runTest {
            val alpha by collectLastValue(underTest.glanceableHubAlpha)
            val alpha by collectLastValue(underTest.glanceableHubAlpha)


@@ -277,12 +274,6 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
                    value = 1f,
                    value = 1f,
                )
                )
            )
            )
            val idleTransitionState =
                MutableStateFlow<ObservableTransitionState>(
                    ObservableTransitionState.Idle(CommunalScenes.Communal)
                )
            communalInteractor.setTransitionState(idleTransitionState)
            runCurrent()
            assertThat(alpha).isEqualTo(0f)
            assertThat(alpha).isEqualTo(0f)


            // While state is GLANCEABLE_HUB, verify alpha is restored to full if glanceable hub is
            // While state is GLANCEABLE_HUB, verify alpha is restored to full if glanceable hub is
@@ -291,6 +282,50 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
            assertThat(alpha).isEqualTo(1f)
            assertThat(alpha).isEqualTo(1f)
        }
        }


    @Test
    fun glanceableHubAlpha_dreamToHub() =
        testScope.runTest {
            val alpha by collectLastValue(underTest.glanceableHubAlpha)

            // Start on dream
            showDream()
            assertThat(alpha).isEqualTo(1f)

            // Start transitioning to glanceable hub
            val progress = 0.6f
            keyguardTransitionRepository.sendTransitionStep(
                TransitionStep(
                    transitionState = TransitionState.STARTED,
                    from = KeyguardState.DREAMING,
                    to = KeyguardState.GLANCEABLE_HUB,
                    value = 0f,
                )
            )
            runCurrent()
            keyguardTransitionRepository.sendTransitionStep(
                TransitionStep(
                    transitionState = TransitionState.RUNNING,
                    from = KeyguardState.DREAMING,
                    to = KeyguardState.GLANCEABLE_HUB,
                    value = progress,
                )
            )
            runCurrent()
            // Keep notifications hidden during the transition from dream to hub
            assertThat(alpha).isEqualTo(0)

            // Finish transition to glanceable hub
            keyguardTransitionRepository.sendTransitionStep(
                TransitionStep(
                    transitionState = TransitionState.FINISHED,
                    from = KeyguardState.DREAMING,
                    to = KeyguardState.GLANCEABLE_HUB,
                    value = 1f,
                )
            )
            assertThat(alpha).isEqualTo(0f)
        }

    @Test
    @Test
    fun validateMarginTop() =
    fun validateMarginTop() =
        testScope.runTest {
        testScope.runTest {
@@ -390,12 +425,11 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
            assertThat(isOnGlanceableHubWithoutShade).isFalse()
            assertThat(isOnGlanceableHubWithoutShade).isFalse()


            // Move to glanceable hub
            // Move to glanceable hub
            val idleTransitionState =
            keyguardTransitionRepository.sendTransitionSteps(
                MutableStateFlow<ObservableTransitionState>(
                from = KeyguardState.LOCKSCREEN,
                    ObservableTransitionState.Idle(CommunalScenes.Communal)
                to = KeyguardState.GLANCEABLE_HUB,
                testScope = this
            )
            )
            communalInteractor.setTransitionState(idleTransitionState)
            runCurrent()
            assertThat(isOnGlanceableHubWithoutShade).isTrue()
            assertThat(isOnGlanceableHubWithoutShade).isTrue()


            // While state is GLANCEABLE_HUB, validate variations of both shade and qs expansion
            // While state is GLANCEABLE_HUB, validate variations of both shade and qs expansion
@@ -725,6 +759,19 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
        )
        )
    }
    }


    private suspend fun TestScope.showDream() {
        shadeRepository.setLockscreenShadeExpansion(0f)
        shadeRepository.setQsExpansion(0f)
        runCurrent()
        keyguardRepository.setDreaming(true)
        runCurrent()
        keyguardTransitionRepository.sendTransitionSteps(
            from = KeyguardState.LOCKSCREEN,
            to = KeyguardState.DREAMING,
            testScope,
        )
    }

    private suspend fun TestScope.showLockscreenWithShadeExpanded() {
    private suspend fun TestScope.showLockscreenWithShadeExpanded() {
        shadeRepository.setLockscreenShadeExpansion(1f)
        shadeRepository.setLockscreenShadeExpansion(1f)
        shadeRepository.setQsExpansion(0f)
        shadeRepository.setQsExpansion(0f)
+6 −6
Original line number Original line Diff line number Diff line
@@ -33,7 +33,7 @@ import com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTO
import com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP
import com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP
import com.android.systemui.complication.ComplicationLayoutParams.Position
import com.android.systemui.complication.ComplicationLayoutParams.Position
import com.android.systemui.dreams.dagger.DreamOverlayModule
import com.android.systemui.dreams.dagger.DreamOverlayModule
import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel
import com.android.systemui.dreams.ui.viewmodel.DreamViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.core.Logger
@@ -53,7 +53,7 @@ constructor(
    private val mStatusBarViewController: DreamOverlayStatusBarViewController,
    private val mStatusBarViewController: DreamOverlayStatusBarViewController,
    private val mOverlayStateController: DreamOverlayStateController,
    private val mOverlayStateController: DreamOverlayStateController,
    @Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int,
    @Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int,
    private val dreamOverlayViewModel: DreamOverlayViewModel,
    private val dreamViewModel: DreamViewModel,
    @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
    @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
    private val mDreamInBlurAnimDurationMs: Long,
    private val mDreamInBlurAnimDurationMs: Long,
    @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
    @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
@@ -87,7 +87,7 @@ constructor(
        view.repeatWhenAttached {
        view.repeatWhenAttached {
            repeatOnLifecycle(Lifecycle.State.CREATED) {
            repeatOnLifecycle(Lifecycle.State.CREATED) {
                launch {
                launch {
                    dreamOverlayViewModel.dreamOverlayTranslationY.collect { px ->
                    dreamViewModel.dreamOverlayTranslationY.collect { px ->
                        ComplicationLayoutParams.iteratePositions(
                        ComplicationLayoutParams.iteratePositions(
                            { position: Int -> setElementsTranslationYAtPosition(px, position) },
                            { position: Int -> setElementsTranslationYAtPosition(px, position) },
                            POSITION_TOP or POSITION_BOTTOM
                            POSITION_TOP or POSITION_BOTTOM
@@ -96,7 +96,7 @@ constructor(
                }
                }


                launch {
                launch {
                    dreamOverlayViewModel.dreamOverlayTranslationX.collect { px ->
                    dreamViewModel.dreamOverlayTranslationX.collect { px ->
                        ComplicationLayoutParams.iteratePositions(
                        ComplicationLayoutParams.iteratePositions(
                            { position: Int -> setElementsTranslationXAtPosition(px, position) },
                            { position: Int -> setElementsTranslationXAtPosition(px, position) },
                            POSITION_TOP or POSITION_BOTTOM
                            POSITION_TOP or POSITION_BOTTOM
@@ -105,7 +105,7 @@ constructor(
                }
                }


                launch {
                launch {
                    dreamOverlayViewModel.dreamOverlayAlpha.collect { alpha ->
                    dreamViewModel.dreamOverlayAlpha.collect { alpha ->
                        ComplicationLayoutParams.iteratePositions(
                        ComplicationLayoutParams.iteratePositions(
                            { position: Int ->
                            { position: Int ->
                                setElementsAlphaAtPosition(
                                setElementsAlphaAtPosition(
@@ -120,7 +120,7 @@ constructor(
                }
                }


                launch {
                launch {
                    dreamOverlayViewModel.transitionEnded.collect { _ ->
                    dreamViewModel.transitionEnded.collect { _ ->
                        mOverlayStateController.setExitAnimationsRunning(false)
                        mOverlayStateController.setExitAnimationsRunning(false)
                    }
                    }
                }
                }
+103 −0
Original line number Original line Diff line number Diff line
@@ -16,34 +16,62 @@


package com.android.systemui.dreams.ui.viewmodel
package com.android.systemui.dreams.ui.viewmodel


import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dock.DockManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
import com.android.systemui.res.R
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.merge


@OptIn(ExperimentalCoroutinesApi::class)
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@SysUISingleton
class DreamOverlayViewModel
class DreamViewModel
@Inject
@Inject
constructor(
constructor(
    configurationInteractor: ConfigurationInteractor,
    configurationInteractor: ConfigurationInteractor,
    toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
    keyguardTransitionInteractor: KeyguardTransitionInteractor,
    fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel,
    fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel,
    private val toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
    private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
    private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
    private val dockManager: DockManager,
    private val communalInteractor: CommunalInteractor,
    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
    private val userTracker: UserTracker,
) {
) {


    fun startTransitionFromDream() {
        val showGlanceableHub =
            dockManager.isDocked &&
                communalInteractor.isCommunalEnabled.value &&
                !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
        if (showGlanceableHub) {
            toGlanceableHubTransitionViewModel.startTransition()
            communalInteractor.onSceneChanged(CommunalScenes.Communal)
        } else {
            toLockscreenTransitionViewModel.startTransition()
        }
    }

    val dreamOverlayTranslationX: Flow<Float> =
    val dreamOverlayTranslationX: Flow<Float> =
        merge(
        merge(
                toGlanceableHubTransitionViewModel.dreamOverlayTranslationX,
                toGlanceableHubTransitionViewModel.dreamOverlayTranslationX,
                fromGlanceableHubTransitionInteractor.dreamOverlayTranslationX,
                fromGlanceableHubTransitionInteractor.dreamOverlayTranslationX,
            )
            )
            .distinctUntilChanged()


    val dreamOverlayTranslationY: Flow<Float> =
    val dreamOverlayTranslationY: Flow<Float> =
        configurationInteractor
        configurationInteractor
@@ -52,12 +80,24 @@ constructor(
                toLockscreenTransitionViewModel.dreamOverlayTranslationY(px)
                toLockscreenTransitionViewModel.dreamOverlayTranslationY(px)
            }
            }


    val dreamAlpha: Flow<Float> =
        merge(
                toLockscreenTransitionViewModel.dreamOverlayAlpha,
                toGlanceableHubTransitionViewModel.dreamAlpha,
            )
            .distinctUntilChanged()

    val dreamOverlayAlpha: Flow<Float> =
    val dreamOverlayAlpha: Flow<Float> =
        merge(
        merge(
                toLockscreenTransitionViewModel.dreamOverlayAlpha,
                toLockscreenTransitionViewModel.dreamOverlayAlpha,
                toGlanceableHubTransitionViewModel.dreamOverlayAlpha,
                toGlanceableHubTransitionViewModel.dreamOverlayAlpha,
                fromGlanceableHubTransitionInteractor.dreamOverlayAlpha,
                fromGlanceableHubTransitionInteractor.dreamOverlayAlpha,
            )
            )
            .distinctUntilChanged()


    val transitionEnded = toLockscreenTransitionViewModel.transitionEnded
    val transitionEnded =
        keyguardTransitionInteractor.fromDreamingTransition.filter { step ->
            step.transitionState == TransitionState.FINISHED ||
                step.transitionState == TransitionState.CANCELED
        }
}
}
Loading