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

Commit 950261b7 authored by 0's avatar 0 Committed by Shawn Lee
Browse files

[flexiglass] Adds overscroll to notification stack content

Adds stackVerticalOverscroll modifier that uses a PriorityNestedScrollConnection to apply an offset to the stack content placeholder when it is scrolled up past the top of its scrollable region.

Bug: 332569054
Test: verified with logging that the overscroll offset is correctly applied when overscrolling at the top, and not applied when overscrolling at the bottom
Test: verified the overscroll looks visually correct when scrolling a long list of notifications up.
Flag: com.android.systemui.scene_container
Change-Id: I423dd7568fdde4bf5c8576c1fd2ae3b43778e33a
parent e7a4f39a
Loading
Loading
Loading
Loading
+100 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.notifications.ui.composable

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.IntOffset
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

@Composable
fun Modifier.stackVerticalOverscroll(
    coroutineScope: CoroutineScope,
    canScrollForward: () -> Boolean
): Modifier {
    val overscrollOffset = remember { Animatable(0f) }
    val stackNestedScrollConnection = remember {
        NotificationStackNestedScrollConnection(
            stackOffset = { overscrollOffset.value },
            canScrollForward = canScrollForward,
            onScroll = { offsetAvailable ->
                coroutineScope.launch {
                    overscrollOffset.snapTo(overscrollOffset.value + offsetAvailable * 0.3f)
                }
            },
            onStop = { velocityAvailable ->
                coroutineScope.launch {
                    overscrollOffset.animateTo(
                        targetValue = 0f,
                        initialVelocity = velocityAvailable,
                        animationSpec = tween()
                    )
                }
            }
        )
    }

    return this.then(
        Modifier.nestedScroll(stackNestedScrollConnection).offset {
            IntOffset(x = 0, y = overscrollOffset.value.roundToInt())
        }
    )
}

fun NotificationStackNestedScrollConnection(
    stackOffset: () -> Float,
    canScrollForward: () -> Boolean,
    onStart: (Float) -> Unit = {},
    onScroll: (Float) -> Unit,
    onStop: (Float) -> Unit = {},
): PriorityNestedScrollConnection {
    return PriorityNestedScrollConnection(
        orientation = Orientation.Vertical,
        canStartPreScroll = { _, _ -> false },
        canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
            offsetAvailable < 0f && offsetBeforeStart < 0f && !canScrollForward()
        },
        canStartPostFling = { velocityAvailable -> velocityAvailable < 0f && !canScrollForward() },
        canContinueScroll = { source ->
            if (source == NestedScrollSource.SideEffect) {
                stackOffset() > STACK_OVERSCROLL_FLING_MIN_OFFSET
            } else {
                true
            }
        },
        canScrollOnFling = true,
        onStart = { offsetAvailable -> onStart(offsetAvailable) },
        onScroll = { offsetAvailable ->
            onScroll(offsetAvailable)
            offsetAvailable
        },
        onStop = { velocityAvailable ->
            onStop(velocityAvailable)
            velocityAvailable
        },
    )
}
+2 −0
Original line number Diff line number Diff line
@@ -461,6 +461,7 @@ fun SceneScope.NotificationScrollingStack(
                        .thenIf(shadeMode == ShadeMode.Single) {
                            Modifier.nestedScroll(scrimNestedScrollConnection)
                        }
                        .stackVerticalOverscroll(coroutineScope) { scrollState.canScrollForward }
                        .verticalScroll(scrollState)
                        .padding(top = topPadding)
                        .fillMaxWidth()
@@ -658,3 +659,4 @@ private val DEBUG_HUN_COLOR = Color(0f, 0f, 1f, 0.2f)
private val DEBUG_BOX_COLOR = Color(0f, 1f, 0f, 0.2f)
private const val HUN_SNOOZE_POSITIONAL_THRESHOLD_FRACTION = 0.25f
private const val HUN_SNOOZE_VELOCITY_THRESHOLD = -70f
internal const val STACK_OVERSCROLL_FLING_MIN_OFFSET = -100f