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

Commit c0416c5c authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Always compose both swipeToScene() in STLImpl

This CL ensures that the ModifierNodeElement associated to each swipe
orientation is composed even if the underlying DraggableHandler is not
enabled (because there is no user action in that orientation). This
allows to prevent an active SwipeToScene modifier from being
updated/interrupted during a transition.

Bug: 378470603
Test: atest SwipeToSceneTest
Flag: com.android.systemui.scene_container
Change-Id: Ia53940b39864c255a3f9a8052ead3e074ba6de0f
parent 352d50b3
Loading
Loading
Loading
Loading
+37 −13
Original line number Diff line number Diff line
@@ -37,11 +37,7 @@ internal fun Modifier.swipeToScene(
    draggableHandler: DraggableHandlerImpl,
    swipeDetector: SwipeDetector,
): Modifier {
    return if (draggableHandler.enabled()) {
        this.then(SwipeToSceneElement(draggableHandler, swipeDetector))
    } else {
        this
    }
    return then(SwipeToSceneElement(draggableHandler, swipeDetector, draggableHandler.enabled()))
}

private fun DraggableHandlerImpl.enabled(): Boolean {
@@ -114,30 +110,58 @@ internal fun Content.findActionResultBestMatch(swipe: Swipe.Resolved): UserActio
private data class SwipeToSceneElement(
    val draggableHandler: DraggableHandlerImpl,
    val swipeDetector: SwipeDetector,
    val enabled: Boolean,
) : ModifierNodeElement<SwipeToSceneRootNode>() {
    override fun create(): SwipeToSceneRootNode =
        SwipeToSceneRootNode(draggableHandler, swipeDetector)
        SwipeToSceneRootNode(draggableHandler, swipeDetector, enabled)

    override fun update(node: SwipeToSceneRootNode) {
        node.update(draggableHandler, swipeDetector)
        node.update(draggableHandler, swipeDetector, enabled)
    }
}

private class SwipeToSceneRootNode(
    draggableHandler: DraggableHandlerImpl,
    swipeDetector: SwipeDetector,
    enabled: Boolean,
) : DelegatingNode() {
    private var delegateNode = delegate(SwipeToSceneNode(draggableHandler, swipeDetector))
    private var delegateNode = if (enabled) create(draggableHandler, swipeDetector) else null

    fun update(
        draggableHandler: DraggableHandlerImpl,
        swipeDetector: SwipeDetector,
        enabled: Boolean,
    ) {
        // Disabled.
        if (!enabled) {
            delegateNode?.let { undelegate(it) }
            delegateNode = null
            return
        }

    fun update(draggableHandler: DraggableHandlerImpl, swipeDetector: SwipeDetector) {
        if (draggableHandler == delegateNode.draggableHandler) {
        // Disabled => Enabled.
        val nullableDelegate = delegateNode
        if (nullableDelegate == null) {
            delegateNode = create(draggableHandler, swipeDetector)
            return
        }

        // Enabled => Enabled (update).
        if (draggableHandler == nullableDelegate.draggableHandler) {
            // Simple update, just update the swipe detector directly and keep the node.
            delegateNode.swipeDetector = swipeDetector
            nullableDelegate.swipeDetector = swipeDetector
        } else {
            // The draggableHandler changed, force recreate the underlying SwipeToSceneNode.
            undelegate(delegateNode)
            delegateNode = delegate(SwipeToSceneNode(draggableHandler, swipeDetector))
            undelegate(nullableDelegate)
            delegateNode = create(draggableHandler, swipeDetector)
        }
    }

    private fun create(
        draggableHandler: DraggableHandlerImpl,
        swipeDetector: SwipeDetector,
    ): SwipeToSceneNode {
        return delegate(SwipeToSceneNode(draggableHandler, swipeDetector))
    }
}

+41 −0
Original line number Diff line number Diff line
@@ -936,4 +936,45 @@ class SwipeToSceneTest {
        assertThat(state.transitionState).isIdle()
        assertThat(state.transitionState).hasCurrentScene(SceneC)
    }

    @Test
    fun swipeToSceneNodeIsKeptWhenDisabled() {
        var hasHorizontalActions by mutableStateOf(false)
        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
        var touchSlop = 0f
        rule.setContent {
            touchSlop = LocalViewConfiguration.current.touchSlop
            SceneTransitionLayout(state) {
                scene(
                    SceneA,
                    userActions =
                        buildList {
                                add(Swipe.Down to SceneB)

                                if (hasHorizontalActions) {
                                    add(Swipe.Left to SceneC)
                                }
                            }
                            .toMap(),
                ) {
                    Box(Modifier.fillMaxSize())
                }
                scene(SceneB) { Box(Modifier.fillMaxSize()) }
            }
        }

        // Swipe down to start a transition to B.
        rule.onRoot().performTouchInput {
            down(middle)
            moveBy(Offset(0f, touchSlop))
        }

        assertThat(state.transitionState).isSceneTransition()

        // Add new horizontal user actions. This should not stop the current transition, even if a
        // new horizontal Modifier.swipeToScene() handler is introduced where the vertical one was.
        hasHorizontalActions = true
        rule.waitForIdle()
        assertThat(state.transitionState).isSceneTransition()
    }
}