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

Commit 606145ec authored by Jordan Demeulenaere's avatar Jordan Demeulenaere Committed by Android (Google) Code Review
Browse files

Merge "Support RTL layouts in STL" into main

parents 27ca2a9a 9c4966a4
Loading
Loading
Loading
Loading
+27 −23
Original line number Diff line number Diff line
@@ -184,31 +184,33 @@ internal class DraggableHandlerImpl(
    ): Swipes {
        val fromSource =
            startedPosition?.let { position ->
                layoutImpl.swipeSourceDetector.source(
                layoutImpl.swipeSourceDetector
                    .source(
                        fromScene.targetSize,
                        position.round(),
                        layoutImpl.density,
                        orientation,
                    )
                    ?.resolve(layoutImpl.layoutDirection)
            }

        val upOrLeft =
            Swipe(
            Swipe.Resolved(
                direction =
                    when (orientation) {
                        Orientation.Horizontal -> SwipeDirection.Left
                        Orientation.Vertical -> SwipeDirection.Up
                        Orientation.Horizontal -> SwipeDirection.Resolved.Left
                        Orientation.Vertical -> SwipeDirection.Resolved.Up
                    },
                pointerCount = pointersDown,
                fromSource = fromSource,
            )

        val downOrRight =
            Swipe(
            Swipe.Resolved(
                direction =
                    when (orientation) {
                        Orientation.Horizontal -> SwipeDirection.Right
                        Orientation.Vertical -> SwipeDirection.Down
                        Orientation.Horizontal -> SwipeDirection.Resolved.Right
                        Orientation.Vertical -> SwipeDirection.Resolved.Down
                    },
                pointerCount = pointersDown,
                fromSource = fromSource,
@@ -833,10 +835,10 @@ private object DefaultSwipeDistance : UserActionDistance {

/** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
private class Swipes(
    val upOrLeft: Swipe?,
    val downOrRight: Swipe?,
    val upOrLeftNoSource: Swipe?,
    val downOrRightNoSource: Swipe?,
    val upOrLeft: Swipe.Resolved?,
    val downOrRight: Swipe.Resolved?,
    val upOrLeftNoSource: Swipe.Resolved?,
    val downOrRightNoSource: Swipe.Resolved?,
) {
    /** The [UserActionResult] associated to up and down swipes. */
    var upOrLeftResult: UserActionResult? = null
@@ -844,7 +846,7 @@ private class Swipes(

    fun computeSwipesResults(fromScene: Scene): Pair<UserActionResult?, UserActionResult?> {
        val userActions = fromScene.userActions
        fun result(swipe: Swipe?): UserActionResult? {
        fun result(swipe: Swipe.Resolved?): UserActionResult? {
            return userActions[swipe ?: return null]
        }

@@ -940,25 +942,27 @@ internal class NestedScrollHandlerImpl(
                when {
                    amount < 0f -> {
                        val actionUpOrLeft =
                            Swipe(
                            Swipe.Resolved(
                                direction =
                                    when (orientation) {
                                        Orientation.Horizontal -> SwipeDirection.Left
                                        Orientation.Vertical -> SwipeDirection.Up
                                        Orientation.Horizontal -> SwipeDirection.Resolved.Left
                                        Orientation.Vertical -> SwipeDirection.Resolved.Up
                                    },
                                pointerCount = pointersInfo().pointersDown,
                                fromSource = null,
                            )
                        fromScene.userActions[actionUpOrLeft]
                    }
                    amount > 0f -> {
                        val actionDownOrRight =
                            Swipe(
                            Swipe.Resolved(
                                direction =
                                    when (orientation) {
                                        Orientation.Horizontal -> SwipeDirection.Right
                                        Orientation.Vertical -> SwipeDirection.Down
                                        Orientation.Horizontal -> SwipeDirection.Resolved.Right
                                        Orientation.Vertical -> SwipeDirection.Resolved.Down
                                    },
                                pointerCount = pointersInfo().pointersDown,
                                fromSource = null,
                            )
                        fromScene.userActions[actionDownOrRight]
                    }
+19 −5
Original line number Diff line number Diff line
@@ -21,15 +21,29 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp

/** The edge of a [SceneTransitionLayout]. */
enum class Edge : SwipeSource {
enum class Edge(private val resolveEdge: (LayoutDirection) -> Resolved) : SwipeSource {
    Top(resolveEdge = { Resolved.Top }),
    Bottom(resolveEdge = { Resolved.Bottom }),
    Left(resolveEdge = { Resolved.Left }),
    Right(resolveEdge = { Resolved.Right }),
    Start(resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.Left else Resolved.Right }),
    End(resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.Right else Resolved.Left });

    override fun resolve(layoutDirection: LayoutDirection): Resolved {
        return resolveEdge(layoutDirection)
    }

    enum class Resolved : SwipeSource.Resolved {
        Left,
        Right,
        Top,
        Bottom,
    }
}

val DefaultEdgeDetector = FixedSizeEdgeDetector(40.dp)

+3 −3
Original line number Diff line number Diff line
@@ -37,7 +37,7 @@ internal class Scene(
    val key: SceneKey,
    layoutImpl: SceneTransitionLayoutImpl,
    content: @Composable SceneScope.() -> Unit,
    actions: Map<UserAction, UserActionResult>,
    actions: Map<UserAction.Resolved, UserActionResult>,
    zIndex: Float,
) {
    internal val scope = SceneScopeImpl(layoutImpl, this)
@@ -54,8 +54,8 @@ internal class Scene(
        }

    private fun checkValid(
        userActions: Map<UserAction, UserActionResult>
    ): Map<UserAction, UserActionResult> {
        userActions: Map<UserAction.Resolved, UserActionResult>
    ): Map<UserAction.Resolved, UserActionResult> {
        userActions.forEach { (action, result) ->
            if (key == result.toScene) {
                error(
+62 −9
Original line number Diff line number Diff line
@@ -28,10 +28,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import com.android.compose.animation.scene.UserAction.Resolved

/**
 * [SceneTransitionLayout] is a container that automatically animates its content whenever its state
@@ -344,35 +347,72 @@ interface ElementBoxScope {
@Stable @ElementDsl interface MovableElementContentScope : BaseSceneScope, ElementBoxScope

/** An action performed by the user. */
sealed interface UserAction {
sealed class UserAction {
    infix fun to(scene: SceneKey): Pair<UserAction, UserActionResult> {
        return this to UserActionResult(toScene = scene)
    }

    /** Resolve this into a [Resolved] user action given [layoutDirection]. */
    internal abstract fun resolve(layoutDirection: LayoutDirection): Resolved

    /** A resolved [UserAction] that does not depend on the layout direction. */
    internal sealed class Resolved
}

/** The user navigated back, either using a gesture or by triggering a KEYCODE_BACK event. */
data object Back : UserAction
data object Back : UserAction() {
    override fun resolve(layoutDirection: LayoutDirection): Resolved = Resolved

    internal object Resolved : UserAction.Resolved()
}

/** The user swiped on the container. */
data class Swipe(
    val direction: SwipeDirection,
    val pointerCount: Int = 1,
    val fromSource: SwipeSource? = null,
) : UserAction {
) : UserAction() {
    companion object {
        val Left = Swipe(SwipeDirection.Left)
        val Up = Swipe(SwipeDirection.Up)
        val Right = Swipe(SwipeDirection.Right)
        val Down = Swipe(SwipeDirection.Down)
        val Start = Swipe(SwipeDirection.Start)
        val End = Swipe(SwipeDirection.End)
    }

    override fun resolve(layoutDirection: LayoutDirection): UserAction.Resolved {
        return Resolved(
            direction = direction.resolve(layoutDirection),
            pointerCount = pointerCount,
            fromSource = fromSource?.resolve(layoutDirection),
        )
    }

    /** A resolved [Swipe] that does not depend on the layout direction. */
    internal data class Resolved(
        val direction: SwipeDirection.Resolved,
        val pointerCount: Int,
        val fromSource: SwipeSource.Resolved?,
    ) : UserAction.Resolved()
}

enum class SwipeDirection(val orientation: Orientation) {
enum class SwipeDirection(internal val resolve: (LayoutDirection) -> Resolved) {
    Up(resolve = { Resolved.Up }),
    Down(resolve = { Resolved.Down }),
    Left(resolve = { Resolved.Left }),
    Right(resolve = { Resolved.Right }),
    Start(resolve = { if (it == LayoutDirection.Ltr) Resolved.Left else Resolved.Right }),
    End(resolve = { if (it == LayoutDirection.Ltr) Resolved.Right else Resolved.Left });

    /** A resolved [SwipeDirection] that does not depend on the layout direction. */
    internal enum class Resolved(val orientation: Orientation) {
        Up(Orientation.Vertical),
        Down(Orientation.Vertical),
        Left(Orientation.Horizontal),
        Right(Orientation.Horizontal),
    }
}

/**
 * The source of a Swipe.
@@ -386,6 +426,16 @@ interface SwipeSource {
    override fun equals(other: Any?): Boolean

    override fun hashCode(): Int

    /** Resolve this into a [Resolved] swipe source given [layoutDirection]. */
    fun resolve(layoutDirection: LayoutDirection): Resolved

    /** A resolved [SwipeSource] that does not depend on the layout direction. */
    interface Resolved {
        override fun equals(other: Any?): Boolean

        override fun hashCode(): Int
    }
}

interface SwipeSourceDetector {
@@ -460,11 +510,13 @@ internal fun SceneTransitionLayoutForTesting(
    scenes: SceneTransitionLayoutScope.() -> Unit,
) {
    val density = LocalDensity.current
    val layoutDirection = LocalLayoutDirection.current
    val coroutineScope = rememberCoroutineScope()
    val layoutImpl = remember {
        SceneTransitionLayoutImpl(
                state = state as BaseSceneTransitionLayoutState,
                density = density,
                layoutDirection = layoutDirection,
                swipeSourceDetector = swipeSourceDetector,
                transitionInterceptionThreshold = transitionInterceptionThreshold,
                builder = scenes,
@@ -475,7 +527,7 @@ internal fun SceneTransitionLayoutForTesting(

    // TODO(b/317014852): Move this into the SideEffect {} again once STLImpl.scenes is not a
    // SnapshotStateMap anymore.
    layoutImpl.updateScenes(scenes)
    layoutImpl.updateScenes(scenes, layoutDirection)

    SideEffect {
        if (state != layoutImpl.state) {
@@ -486,6 +538,7 @@ internal fun SceneTransitionLayoutForTesting(
        }

        layoutImpl.density = density
        layoutImpl.layoutDirection = layoutDirection
        layoutImpl.swipeSourceDetector = swipeSourceDetector
        layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
    }
+12 −5
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachReversed
import com.android.compose.ui.util.lerp
@@ -45,6 +46,7 @@ internal typealias MovableElementContent = @Composable (@Composable () -> Unit)
internal class SceneTransitionLayoutImpl(
    internal val state: BaseSceneTransitionLayoutState,
    internal var density: Density,
    internal var layoutDirection: LayoutDirection,
    internal var swipeSourceDetector: SwipeSourceDetector,
    internal var transitionInterceptionThreshold: Float,
    builder: SceneTransitionLayoutScope.() -> Unit,
@@ -114,7 +116,7 @@ internal class SceneTransitionLayoutImpl(
        private set

    init {
        updateScenes(builder)
        updateScenes(builder, layoutDirection)

        // DraggableHandlerImpl must wait for the scenes to be initialized, in order to access the
        // current scene (required for SwipeTransition).
@@ -147,7 +149,10 @@ internal class SceneTransitionLayoutImpl(
        return scenes[key] ?: error("Scene $key is not configured")
    }

    internal fun updateScenes(builder: SceneTransitionLayoutScope.() -> Unit) {
    internal fun updateScenes(
        builder: SceneTransitionLayoutScope.() -> Unit,
        layoutDirection: LayoutDirection,
    ) {
        // Keep a reference of the current scenes. After processing [builder], the scenes that were
        // not configured will be removed.
        val scenesToRemove = scenes.keys.toMutableSet()
@@ -163,11 +168,13 @@ internal class SceneTransitionLayoutImpl(
                ) {
                    scenesToRemove.remove(key)

                    val resolvedUserActions =
                        userActions.mapKeys { it.key.resolve(layoutDirection) }
                    val scene = scenes[key]
                    if (scene != null) {
                        // Update an existing scene.
                        scene.content = content
                        scene.userActions = userActions
                        scene.userActions = resolvedUserActions
                        scene.zIndex = zIndex
                    } else {
                        // New scene.
@@ -176,7 +183,7 @@ internal class SceneTransitionLayoutImpl(
                                key,
                                this@SceneTransitionLayoutImpl,
                                content,
                                userActions,
                                resolvedUserActions,
                                zIndex,
                            )
                    }
@@ -213,7 +220,7 @@ internal class SceneTransitionLayoutImpl(
    @Composable
    private fun BackHandler() {
        val targetSceneForBack =
            scene(state.transitionState.currentScene).userActions[Back]?.toScene
            scene(state.transitionState.currentScene).userActions[Back.Resolved]?.toScene
        PredictiveBackHandler(state, coroutineScope, targetSceneForBack)
    }

Loading