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

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

Replace swipe Edge (detector) by SwipeSource (detector) (1/2)

This CL replaces Edge and EdgeDetector by a more generic SwipeSource and
SwipeSourceDetector. This will allow users to define their own
SwipeSource's, for instance to implement a dual shade where swipe down
from the left half of the screen has a different behavior than swiping
down on the right side of the screen.

Bug: 322130260
Test: FixedSizeEdgeDetectorTest
Test: SwipeToSceneTest
Flag: N/A
Change-Id: Ib50c75ea7bb5a9fa0d775845a2ee16acb4aceecd
parent 148b7af1
Loading
Loading
Loading
Loading
+4 −3
Original line number Diff line number Diff line
@@ -85,13 +85,14 @@ fun CommunalContainer(
    SceneTransitionLayout(
        state = sceneTransitionLayoutState,
        modifier = modifier.fillMaxSize(),
        edgeDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize),
        swipeSourceDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize),
    ) {
        scene(
            TransitionSceneKey.Blank,
            userActions =
                mapOf(
                    Swipe(SwipeDirection.Left, fromEdge = Edge.Right) to TransitionSceneKey.Communal
                    Swipe(SwipeDirection.Left, fromSource = Edge.Right) to
                        TransitionSceneKey.Communal
                )
        ) {
            // This scene shows nothing only allowing for transitions to the communal scene.
@@ -102,7 +103,7 @@ fun CommunalContainer(
            TransitionSceneKey.Communal,
            userActions =
                mapOf(
                    Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to TransitionSceneKey.Blank
                    Swipe(SwipeDirection.Right, fromSource = Edge.Left) to TransitionSceneKey.Blank
                ),
        ) {
            CommunalScene(viewModel, modifier = modifier)
+1 −1
Original line number Diff line number Diff line
@@ -183,7 +183,7 @@ private fun UserAction.toTransitionUserAction(): SceneTransitionUserAction {
        is UserAction.Swipe ->
            Swipe(
                pointerCount = pointerCount,
                fromEdge =
                fromSource =
                    when (this.fromEdge) {
                        null -> null
                        Edge.LEFT -> SceneTransitionEdge.Left
+9 −14
Original line number Diff line number Diff line
@@ -23,24 +23,19 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp

interface EdgeDetector {
    /**
     * Return the [Edge] associated to [position] inside a layout of size [layoutSize], given
     * [density] and [orientation].
     */
    fun edge(
        layoutSize: IntSize,
        position: IntOffset,
        density: Density,
        orientation: Orientation,
    ): Edge?
/** The edge of a [SceneTransitionLayout]. */
enum class Edge : SwipeSource {
    Left,
    Right,
    Top,
    Bottom,
}

val DefaultEdgeDetector = FixedSizeEdgeDetector(40.dp)

/** An [EdgeDetector] that detects edges assuming a fixed edge size of [size]. */
class FixedSizeEdgeDetector(val size: Dp) : EdgeDetector {
    override fun edge(
/** An [SwipeSourceDetector] that detects edges assuming a fixed edge size of [size]. */
class FixedSizeEdgeDetector(val size: Dp) : SwipeSourceDetector {
    override fun source(
        layoutSize: IntSize,
        position: IntOffset,
        density: Density,
+13 −13
Original line number Diff line number Diff line
@@ -80,8 +80,8 @@ internal class SceneGestureHandler(
    /** The [Swipe]s associated to the current gesture. */
    private var upOrLeftSwipe: Swipe? = null
    private var downOrRightSwipe: Swipe? = null
    private var upOrLeftNoEdgeSwipe: Swipe? = null
    private var downOrRightNoEdgeSwipe: Swipe? = null
    private var upOrLeftNoSourceSwipe: Swipe? = null
    private var downOrRightNoSourceSwipe: Swipe? = null

    /** The [UserActionResult] associated to up and down swipes. */
    private var upOrLeftResult: UserActionResult? = null
@@ -117,9 +117,9 @@ internal class SceneGestureHandler(
    }

    private fun setCurrentActions(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
        val fromEdge =
        val fromSource =
            startedPosition?.let { position ->
                layoutImpl.edgeDetector.edge(
                layoutImpl.swipeSourceDetector.source(
                    fromScene.targetSize,
                    position.round(),
                    layoutImpl.density,
@@ -135,7 +135,7 @@ internal class SceneGestureHandler(
                        Orientation.Vertical -> SwipeDirection.Up
                    },
                pointerCount = pointersDown,
                fromEdge = fromEdge,
                fromSource = fromSource,
            )

        val downOrRight =
@@ -146,19 +146,19 @@ internal class SceneGestureHandler(
                        Orientation.Vertical -> SwipeDirection.Down
                    },
                pointerCount = pointersDown,
                fromEdge = fromEdge,
                fromSource = fromSource,
            )

        if (fromEdge == null) {
        if (fromSource == null) {
            upOrLeftSwipe = null
            downOrRightSwipe = null
            upOrLeftNoEdgeSwipe = upOrLeft
            downOrRightNoEdgeSwipe = downOrRight
            upOrLeftNoSourceSwipe = upOrLeft
            downOrRightNoSourceSwipe = downOrRight
        } else {
            upOrLeftSwipe = upOrLeft
            downOrRightSwipe = downOrRight
            upOrLeftNoEdgeSwipe = upOrLeft.copy(fromEdge = null)
            downOrRightNoEdgeSwipe = downOrRight.copy(fromEdge = null)
            upOrLeftNoSourceSwipe = upOrLeft.copy(fromSource = null)
            downOrRightNoSourceSwipe = downOrRight.copy(fromSource = null)
        }
    }

@@ -204,9 +204,9 @@ internal class SceneGestureHandler(
            return userActions[swipe ?: return null]
        }

        upOrLeftResult = sceneToSwipePair(upOrLeftSwipe) ?: sceneToSwipePair(upOrLeftNoEdgeSwipe)
        upOrLeftResult = sceneToSwipePair(upOrLeftSwipe) ?: sceneToSwipePair(upOrLeftNoSourceSwipe)
        downOrRightResult =
            sceneToSwipePair(downOrRightSwipe) ?: sceneToSwipePair(downOrRightNoEdgeSwipe)
            sceneToSwipePair(downOrRightSwipe) ?: sceneToSwipePair(downOrRightNoSourceSwipe)
    }

    /**
+40 −10
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize

/**
@@ -41,7 +42,8 @@ import androidx.compose.ui.unit.IntSize
 * UI code.
 *
 * @param state the state of this layout.
 * @param edgeDetector the edge detector used to detect which edge a swipe is started from, if any.
 * @param swipeSourceDetector the edge detector used to detect which edge a swipe is started from,
 *   if any.
 * @param transitionInterceptionThreshold used during a scene transition. For the scene to be
 *   intercepted, the progress value must be above the threshold, and below (1 - threshold).
 * @param scenes the configuration of the different scenes of this layout.
@@ -51,14 +53,14 @@ import androidx.compose.ui.unit.IntSize
fun SceneTransitionLayout(
    state: SceneTransitionLayoutState,
    modifier: Modifier = Modifier,
    edgeDetector: EdgeDetector = DefaultEdgeDetector,
    swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
    @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
    scenes: SceneTransitionLayoutScope.() -> Unit,
) {
    SceneTransitionLayoutForTesting(
        state,
        modifier,
        edgeDetector,
        swipeSourceDetector,
        transitionInterceptionThreshold,
        onLayoutImpl = null,
        scenes,
@@ -79,7 +81,8 @@ fun SceneTransitionLayout(
 *   This is called when the user commits a transition to a new scene because of a [UserAction], for
 *   instance by triggering back navigation or by swiping to a new scene.
 * @param transitions the definition of the transitions used to animate a change of scene.
 * @param edgeDetector the edge detector used to detect which edge a swipe is started from, if any.
 * @param swipeSourceDetector the source detector used to detect which source a swipe is started
 *   from, if any.
 * @param transitionInterceptionThreshold used during a scene transition. For the scene to be
 *   intercepted, the progress value must be above the threshold, and below (1 - threshold).
 * @param scenes the configuration of the different scenes of this layout.
@@ -90,7 +93,7 @@ fun SceneTransitionLayout(
    onChangeScene: (SceneKey) -> Unit,
    transitions: SceneTransitions,
    modifier: Modifier = Modifier,
    edgeDetector: EdgeDetector = DefaultEdgeDetector,
    swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
    @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
    scenes: SceneTransitionLayoutScope.() -> Unit,
) {
@@ -98,7 +101,7 @@ fun SceneTransitionLayout(
    SceneTransitionLayout(
        state,
        modifier,
        edgeDetector,
        swipeSourceDetector,
        transitionInterceptionThreshold,
        scenes,
    )
@@ -338,7 +341,7 @@ data object Back : UserAction
data class Swipe(
    val direction: SwipeDirection,
    val pointerCount: Int = 1,
    val fromEdge: Edge? = null,
    val fromSource: SwipeSource? = null,
) : UserAction {
    companion object {
        val Left = Swipe(SwipeDirection.Left)
@@ -355,6 +358,33 @@ enum class SwipeDirection(val orientation: Orientation) {
    Right(Orientation.Horizontal),
}

/**
 * The source of a Swipe.
 *
 * Important: This can be anything that can be returned by any [SwipeSourceDetector], but this must
 * implement [equals] and [hashCode]. Note that those can be trivially implemented using data
 * classes.
 */
interface SwipeSource {
    // Require equals() and hashCode() to be implemented.
    override fun equals(other: Any?): Boolean

    override fun hashCode(): Int
}

interface SwipeSourceDetector {
    /**
     * Return the [SwipeSource] associated to [position] inside a layout of size [layoutSize], given
     * [density] and [orientation].
     */
    fun source(
        layoutSize: IntSize,
        position: IntOffset,
        density: Density,
        orientation: Orientation,
    ): SwipeSource?
}

/**
 * The result of performing a [UserAction].
 *
@@ -427,7 +457,7 @@ private class FixedDistance(private val distance: Dp) : UserActionDistance {
internal fun SceneTransitionLayoutForTesting(
    state: SceneTransitionLayoutState,
    modifier: Modifier = Modifier,
    edgeDetector: EdgeDetector = DefaultEdgeDetector,
    swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
    transitionInterceptionThreshold: Float = 0f,
    onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
    scenes: SceneTransitionLayoutScope.() -> Unit,
@@ -438,7 +468,7 @@ internal fun SceneTransitionLayoutForTesting(
        SceneTransitionLayoutImpl(
                state = state as BaseSceneTransitionLayoutState,
                density = density,
                edgeDetector = edgeDetector,
                swipeSourceDetector = swipeSourceDetector,
                transitionInterceptionThreshold = transitionInterceptionThreshold,
                builder = scenes,
                coroutineScope = coroutineScope,
@@ -459,7 +489,7 @@ internal fun SceneTransitionLayoutForTesting(
        }

        layoutImpl.density = density
        layoutImpl.edgeDetector = edgeDetector
        layoutImpl.swipeSourceDetector = swipeSourceDetector
    }

    layoutImpl.Content(modifier)
Loading