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

Commit a20f6cbf authored by Shawn Lee's avatar Shawn Lee
Browse files

[flexiglass] Send overscrolling touches from NSSL after expansion

After expanding a notification, the rest of the gesture is sent from NSSL to the flexiglass hierarchy as well as a boolean indicating the current gesture is overscrolling. The scrim then "consumes" some of the gesture so that the rest of the nested scrolling system treats the gesture as overscroll.

Bug: 332569054
Test: manual
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT
Change-Id: Ia36545754d68c441d8974d38af38a51ffdc115be
parent e2d3dbdb
Loading
Loading
Loading
Loading
+15 −5
Original line number Original line Diff line number Diff line
@@ -30,11 +30,15 @@ import com.android.compose.nestedscroll.PriorityNestedScrollConnection
 */
 */
fun NotificationScrimNestedScrollConnection(
fun NotificationScrimNestedScrollConnection(
    scrimOffset: () -> Float,
    scrimOffset: () -> Float,
    onScrimOffsetChanged: (Float) -> Unit,
    snapScrimOffset: (Float) -> Unit,
    animateScrimOffset: (Float) -> Unit,
    minScrimOffset: () -> Float,
    minScrimOffset: () -> Float,
    maxScrimOffset: Float,
    maxScrimOffset: Float,
    contentHeight: () -> Float,
    contentHeight: () -> Float,
    minVisibleScrimHeight: () -> Float,
    minVisibleScrimHeight: () -> Float,
    isCurrentGestureOverscroll: () -> Boolean,
    onStart: (Float) -> Unit = {},
    onStop: (Float) -> Unit = {},
): PriorityNestedScrollConnection {
): PriorityNestedScrollConnection {
    return PriorityNestedScrollConnection(
    return PriorityNestedScrollConnection(
        orientation = Orientation.Vertical,
        orientation = Orientation.Vertical,
@@ -49,7 +53,7 @@ fun NotificationScrimNestedScrollConnection(
        // scrolling down and content is done scrolling to top. After that, the scrim
        // scrolling down and content is done scrolling to top. After that, the scrim
        // needs to collapse; collapse the scrim until it is at the maxScrimOffset.
        // needs to collapse; collapse the scrim until it is at the maxScrimOffset.
        canStartPostScroll = { offsetAvailable, _ ->
        canStartPostScroll = { offsetAvailable, _ ->
            offsetAvailable > 0 && scrimOffset() < maxScrimOffset
            offsetAvailable > 0 && (scrimOffset() < maxScrimOffset || isCurrentGestureOverscroll())
        },
        },
        canStartPostFling = { false },
        canStartPostFling = { false },
        canContinueScroll = {
        canContinueScroll = {
@@ -57,7 +61,7 @@ fun NotificationScrimNestedScrollConnection(
            minScrimOffset() < currentHeight && currentHeight < maxScrimOffset
            minScrimOffset() < currentHeight && currentHeight < maxScrimOffset
        },
        },
        canScrollOnFling = true,
        canScrollOnFling = true,
        onStart = { /* do nothing */},
        onStart = { offsetAvailable -> onStart(offsetAvailable) },
        onScroll = { offsetAvailable ->
        onScroll = { offsetAvailable ->
            val currentHeight = scrimOffset()
            val currentHeight = scrimOffset()
            val amountConsumed =
            val amountConsumed =
@@ -68,10 +72,16 @@ fun NotificationScrimNestedScrollConnection(
                    val amountLeft = minScrimOffset() - currentHeight
                    val amountLeft = minScrimOffset() - currentHeight
                    offsetAvailable.coerceAtLeast(amountLeft)
                    offsetAvailable.coerceAtLeast(amountLeft)
                }
                }
            onScrimOffsetChanged(currentHeight + amountConsumed)
            snapScrimOffset(currentHeight + amountConsumed)
            amountConsumed
            amountConsumed
        },
        },
        // Don't consume the velocity on pre/post fling
        // Don't consume the velocity on pre/post fling
        onStop = { 0f },
        onStop = { velocityAvailable ->
            onStop(velocityAvailable)
            if (scrimOffset() < minScrimOffset()) {
                animateScrimOffset(minScrimOffset())
            }
            0f
        },
    )
    )
}
}
+19 −5
Original line number Original line Diff line number Diff line
@@ -18,6 +18,7 @@
package com.android.systemui.notifications.ui.composable
package com.android.systemui.notifications.ui.composable


import android.util.Log
import android.util.Log
import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.background
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Box
@@ -39,8 +40,8 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
@@ -77,6 +78,7 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import kotlin.math.roundToInt
import kotlin.math.roundToInt
import kotlinx.coroutines.launch


object Notifications {
object Notifications {
    object Elements {
    object Elements {
@@ -159,11 +161,13 @@ fun SceneScope.NotificationScrollingStack(
    shouldPunchHoleBehindScrim: Boolean,
    shouldPunchHoleBehindScrim: Boolean,
    modifier: Modifier = Modifier,
    modifier: Modifier = Modifier,
) {
) {
    val coroutineScope = rememberCoroutineScope()
    val density = LocalDensity.current
    val density = LocalDensity.current
    val screenCornerRadius = LocalScreenCornerRadius.current
    val screenCornerRadius = LocalScreenCornerRadius.current
    val scrimCornerRadius = dimensionResource(R.dimen.notification_scrim_corner_radius)
    val scrimCornerRadius = dimensionResource(R.dimen.notification_scrim_corner_radius)
    val scrollState = rememberScrollState()
    val scrollState = rememberScrollState()
    val syntheticScroll = viewModel.syntheticScroll.collectAsState(0f)
    val syntheticScroll = viewModel.syntheticScroll.collectAsState(0f)
    val isCurrentGestureOverscroll = viewModel.isCurrentGestureOverscroll.collectAsState(false)
    val expansionFraction by viewModel.expandFraction.collectAsState(0f)
    val expansionFraction by viewModel.expandFraction.collectAsState(0f)


    val navBarHeight =
    val navBarHeight =
@@ -180,7 +184,7 @@ fun SceneScope.NotificationScrollingStack(
    // When fully expanded (scrimOffset = minScrimOffset), its top bound is at minScrimStartY,
    // When fully expanded (scrimOffset = minScrimOffset), its top bound is at minScrimStartY,
    // which is equal to the height of the Shade Header. Thus, when the scrim is fully expanded, the
    // which is equal to the height of the Shade Header. Thus, when the scrim is fully expanded, the
    // entire height of the scrim is visible on screen.
    // entire height of the scrim is visible on screen.
    val scrimOffset = remember { mutableStateOf(0f) }
    val scrimOffset = remember { Animatable(0f) }


    // set the bounds to null when the scrim disappears
    // set the bounds to null when the scrim disappears
    DisposableEffect(Unit) { onDispose { viewModel.onScrimBoundsChanged(null) } }
    DisposableEffect(Unit) { onDispose { viewModel.onScrimBoundsChanged(null) } }
@@ -204,7 +208,7 @@ fun SceneScope.NotificationScrollingStack(
    // expanded, reset scrim offset.
    // expanded, reset scrim offset.
    LaunchedEffect(stackHeight, scrimOffset) {
    LaunchedEffect(stackHeight, scrimOffset) {
        snapshotFlow { stackHeight.value < minVisibleScrimHeight() && scrimOffset.value < 0f }
        snapshotFlow { stackHeight.value < minVisibleScrimHeight() && scrimOffset.value < 0f }
            .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.value = 0f }
            .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.snapTo(0f) }
    }
    }


    // if we receive scroll delta from NSSL, offset the scrim and placeholder accordingly.
    // if we receive scroll delta from NSSL, offset the scrim and placeholder accordingly.
@@ -214,7 +218,7 @@ fun SceneScope.NotificationScrollingStack(
                val minOffset = minScrimOffset()
                val minOffset = minScrimOffset()
                if (scrimOffset.value > minOffset) {
                if (scrimOffset.value > minOffset) {
                    val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f)
                    val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f)
                    scrimOffset.value = (scrimOffset.value - delta).coerceAtLeast(minOffset)
                    scrimOffset.snapTo((scrimOffset.value - delta).coerceAtLeast(minOffset))
                    if (remainingDelta > 0f) {
                    if (remainingDelta > 0f) {
                        scrollState.scrollBy(remainingDelta)
                        scrollState.scrollBy(remainingDelta)
                    }
                    }
@@ -296,20 +300,30 @@ fun SceneScope.NotificationScrollingStack(
                modifier =
                modifier =
                    Modifier.verticalNestedScrollToScene(
                    Modifier.verticalNestedScrollToScene(
                            topBehavior = NestedScrollBehavior.EdgeWithPreview,
                            topBehavior = NestedScrollBehavior.EdgeWithPreview,
                            isExternalOverscrollGesture = { isCurrentGestureOverscroll.value }
                        )
                        )
                        .nestedScroll(
                        .nestedScroll(
                            remember(
                            remember(
                                scrimOffset,
                                scrimOffset,
                                maxScrimTop,
                                maxScrimTop,
                                minScrimTop,
                                minScrimTop,
                                isCurrentGestureOverscroll,
                            ) {
                            ) {
                                NotificationScrimNestedScrollConnection(
                                NotificationScrimNestedScrollConnection(
                                    scrimOffset = { scrimOffset.value },
                                    scrimOffset = { scrimOffset.value },
                                    onScrimOffsetChanged = { scrimOffset.value = it },
                                    snapScrimOffset = { value ->
                                        coroutineScope.launch { scrimOffset.snapTo(value) }
                                    },
                                    animateScrimOffset = { value ->
                                        coroutineScope.launch { scrimOffset.animateTo(value) }
                                    },
                                    minScrimOffset = minScrimOffset,
                                    minScrimOffset = minScrimOffset,
                                    maxScrimOffset = 0f,
                                    maxScrimOffset = 0f,
                                    contentHeight = { stackHeight.value },
                                    contentHeight = { stackHeight.value },
                                    minVisibleScrimHeight = minVisibleScrimHeight,
                                    minVisibleScrimHeight = minVisibleScrimHeight,
                                    isCurrentGestureOverscroll = {
                                        isCurrentGestureOverscroll.value
                                    },
                                )
                                )
                            }
                            }
                        )
                        )
+5 −2
Original line number Original line Diff line number Diff line
@@ -865,6 +865,7 @@ internal class NestedScrollHandlerImpl(
    private val orientation: Orientation,
    private val orientation: Orientation,
    private val topOrLeftBehavior: NestedScrollBehavior,
    private val topOrLeftBehavior: NestedScrollBehavior,
    private val bottomOrRightBehavior: NestedScrollBehavior,
    private val bottomOrRightBehavior: NestedScrollBehavior,
    private val isExternalOverscrollGesture: () -> Boolean,
) {
) {
    private val layoutState = layoutImpl.state
    private val layoutState = layoutImpl.state
    private val draggableHandler = layoutImpl.draggableHandler(orientation)
    private val draggableHandler = layoutImpl.draggableHandler(orientation)
@@ -920,7 +921,8 @@ internal class NestedScrollHandlerImpl(
        return PriorityNestedScrollConnection(
        return PriorityNestedScrollConnection(
            orientation = orientation,
            orientation = orientation,
            canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
            canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
                canChangeScene = offsetBeforeStart == 0f
                canChangeScene =
                    if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f


                val canInterceptSwipeTransition =
                val canInterceptSwipeTransition =
                    canChangeScene &&
                    canChangeScene &&
@@ -950,7 +952,8 @@ internal class NestedScrollHandlerImpl(
                        else -> return@PriorityNestedScrollConnection false
                        else -> return@PriorityNestedScrollConnection false
                    }
                    }


                val isZeroOffset = offsetBeforeStart == 0f
                val isZeroOffset =
                    if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f


                val canStart =
                val canStart =
                    when (behavior) {
                    when (behavior) {
+11 −0
Original line number Original line Diff line number Diff line
@@ -75,6 +75,7 @@ internal fun Modifier.nestedScrollToScene(
    orientation: Orientation,
    orientation: Orientation,
    topOrLeftBehavior: NestedScrollBehavior,
    topOrLeftBehavior: NestedScrollBehavior,
    bottomOrRightBehavior: NestedScrollBehavior,
    bottomOrRightBehavior: NestedScrollBehavior,
    isExternalOverscrollGesture: () -> Boolean,
) =
) =
    this then
    this then
        NestedScrollToSceneElement(
        NestedScrollToSceneElement(
@@ -82,6 +83,7 @@ internal fun Modifier.nestedScrollToScene(
            orientation = orientation,
            orientation = orientation,
            topOrLeftBehavior = topOrLeftBehavior,
            topOrLeftBehavior = topOrLeftBehavior,
            bottomOrRightBehavior = bottomOrRightBehavior,
            bottomOrRightBehavior = bottomOrRightBehavior,
            isExternalOverscrollGesture = isExternalOverscrollGesture,
        )
        )


private data class NestedScrollToSceneElement(
private data class NestedScrollToSceneElement(
@@ -89,6 +91,7 @@ private data class NestedScrollToSceneElement(
    private val orientation: Orientation,
    private val orientation: Orientation,
    private val topOrLeftBehavior: NestedScrollBehavior,
    private val topOrLeftBehavior: NestedScrollBehavior,
    private val bottomOrRightBehavior: NestedScrollBehavior,
    private val bottomOrRightBehavior: NestedScrollBehavior,
    private val isExternalOverscrollGesture: () -> Boolean,
) : ModifierNodeElement<NestedScrollToSceneNode>() {
) : ModifierNodeElement<NestedScrollToSceneNode>() {
    override fun create() =
    override fun create() =
        NestedScrollToSceneNode(
        NestedScrollToSceneNode(
@@ -96,6 +99,7 @@ private data class NestedScrollToSceneElement(
            orientation = orientation,
            orientation = orientation,
            topOrLeftBehavior = topOrLeftBehavior,
            topOrLeftBehavior = topOrLeftBehavior,
            bottomOrRightBehavior = bottomOrRightBehavior,
            bottomOrRightBehavior = bottomOrRightBehavior,
            isExternalOverscrollGesture = isExternalOverscrollGesture,
        )
        )


    override fun update(node: NestedScrollToSceneNode) {
    override fun update(node: NestedScrollToSceneNode) {
@@ -104,6 +108,7 @@ private data class NestedScrollToSceneElement(
            orientation = orientation,
            orientation = orientation,
            topOrLeftBehavior = topOrLeftBehavior,
            topOrLeftBehavior = topOrLeftBehavior,
            bottomOrRightBehavior = bottomOrRightBehavior,
            bottomOrRightBehavior = bottomOrRightBehavior,
            isExternalOverscrollGesture = isExternalOverscrollGesture,
        )
        )
    }
    }


@@ -121,6 +126,7 @@ private class NestedScrollToSceneNode(
    orientation: Orientation,
    orientation: Orientation,
    topOrLeftBehavior: NestedScrollBehavior,
    topOrLeftBehavior: NestedScrollBehavior,
    bottomOrRightBehavior: NestedScrollBehavior,
    bottomOrRightBehavior: NestedScrollBehavior,
    isExternalOverscrollGesture: () -> Boolean,
) : DelegatingNode() {
) : DelegatingNode() {
    private var priorityNestedScrollConnection: PriorityNestedScrollConnection =
    private var priorityNestedScrollConnection: PriorityNestedScrollConnection =
        scenePriorityNestedScrollConnection(
        scenePriorityNestedScrollConnection(
@@ -128,6 +134,7 @@ private class NestedScrollToSceneNode(
            orientation = orientation,
            orientation = orientation,
            topOrLeftBehavior = topOrLeftBehavior,
            topOrLeftBehavior = topOrLeftBehavior,
            bottomOrRightBehavior = bottomOrRightBehavior,
            bottomOrRightBehavior = bottomOrRightBehavior,
            isExternalOverscrollGesture = isExternalOverscrollGesture,
        )
        )


    private var nestedScrollNode: DelegatableNode =
    private var nestedScrollNode: DelegatableNode =
@@ -150,6 +157,7 @@ private class NestedScrollToSceneNode(
        orientation: Orientation,
        orientation: Orientation,
        topOrLeftBehavior: NestedScrollBehavior,
        topOrLeftBehavior: NestedScrollBehavior,
        bottomOrRightBehavior: NestedScrollBehavior,
        bottomOrRightBehavior: NestedScrollBehavior,
        isExternalOverscrollGesture: () -> Boolean,
    ) {
    ) {
        // Clean up the old nested scroll connection
        // Clean up the old nested scroll connection
        priorityNestedScrollConnection.reset()
        priorityNestedScrollConnection.reset()
@@ -162,6 +170,7 @@ private class NestedScrollToSceneNode(
                orientation = orientation,
                orientation = orientation,
                topOrLeftBehavior = topOrLeftBehavior,
                topOrLeftBehavior = topOrLeftBehavior,
                bottomOrRightBehavior = bottomOrRightBehavior,
                bottomOrRightBehavior = bottomOrRightBehavior,
                isExternalOverscrollGesture = isExternalOverscrollGesture,
            )
            )
        nestedScrollNode =
        nestedScrollNode =
            nestedScrollModifierNode(
            nestedScrollModifierNode(
@@ -177,11 +186,13 @@ private fun scenePriorityNestedScrollConnection(
    orientation: Orientation,
    orientation: Orientation,
    topOrLeftBehavior: NestedScrollBehavior,
    topOrLeftBehavior: NestedScrollBehavior,
    bottomOrRightBehavior: NestedScrollBehavior,
    bottomOrRightBehavior: NestedScrollBehavior,
    isExternalOverscrollGesture: () -> Boolean,
) =
) =
    NestedScrollHandlerImpl(
    NestedScrollHandlerImpl(
            layoutImpl = layoutImpl,
            layoutImpl = layoutImpl,
            orientation = orientation,
            orientation = orientation,
            topOrLeftBehavior = topOrLeftBehavior,
            topOrLeftBehavior = topOrLeftBehavior,
            bottomOrRightBehavior = bottomOrRightBehavior,
            bottomOrRightBehavior = bottomOrRightBehavior,
            isExternalOverscrollGesture = isExternalOverscrollGesture,
        )
        )
        .connection
        .connection
+5 −1
Original line number Original line Diff line number Diff line
@@ -141,23 +141,27 @@ internal class SceneScopeImpl(
    override fun Modifier.horizontalNestedScrollToScene(
    override fun Modifier.horizontalNestedScrollToScene(
        leftBehavior: NestedScrollBehavior,
        leftBehavior: NestedScrollBehavior,
        rightBehavior: NestedScrollBehavior,
        rightBehavior: NestedScrollBehavior,
        isExternalOverscrollGesture: () -> Boolean,
    ): Modifier =
    ): Modifier =
        nestedScrollToScene(
        nestedScrollToScene(
            layoutImpl = layoutImpl,
            layoutImpl = layoutImpl,
            orientation = Orientation.Horizontal,
            orientation = Orientation.Horizontal,
            topOrLeftBehavior = leftBehavior,
            topOrLeftBehavior = leftBehavior,
            bottomOrRightBehavior = rightBehavior,
            bottomOrRightBehavior = rightBehavior,
            isExternalOverscrollGesture = isExternalOverscrollGesture,
        )
        )


    override fun Modifier.verticalNestedScrollToScene(
    override fun Modifier.verticalNestedScrollToScene(
        topBehavior: NestedScrollBehavior,
        topBehavior: NestedScrollBehavior,
        bottomBehavior: NestedScrollBehavior
        bottomBehavior: NestedScrollBehavior,
        isExternalOverscrollGesture: () -> Boolean,
    ): Modifier =
    ): Modifier =
        nestedScrollToScene(
        nestedScrollToScene(
            layoutImpl = layoutImpl,
            layoutImpl = layoutImpl,
            orientation = Orientation.Vertical,
            orientation = Orientation.Vertical,
            topOrLeftBehavior = topBehavior,
            topOrLeftBehavior = topBehavior,
            bottomOrRightBehavior = bottomBehavior,
            bottomOrRightBehavior = bottomBehavior,
            isExternalOverscrollGesture = isExternalOverscrollGesture,
        )
        )


    override fun Modifier.noResizeDuringTransitions(): Modifier {
    override fun Modifier.noResizeDuringTransitions(): Modifier {
Loading