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

Commit 1695035a authored by Shawn Lee's avatar Shawn Lee Committed by Steve Elliott
Browse files

[Flexiglass] Fix NotificationScrimNestedScrollConnection rememeberSession keys

We were passing in keys to rememberSession that were references to objects using remember, and thus the equality check was breaking once those references changed upon recomposition.

Bug: 403285138
Test: verified through logging that the relevant rememberSession equality checks now pass
Flag: com.android.systemui.scene_container
Change-Id: I846a4357b9359d02288cd707f5c2ebae5fbf0e36
parent 3f7eacaa
Loading
Loading
Loading
Loading
+73 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.AnimationState
import androidx.compose.animation.core.DecayAnimationSpec
import androidx.compose.animation.core.animateDecay
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollScope
import androidx.compose.ui.MotionDurationScale
import com.android.systemui.scene.session.ui.composable.rememberSession
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.withContext
import kotlin.math.abs

/**
 * Fork of [androidx.compose.foundation.gestures.DefaultFlingBehavior] to allow us to use it with
 * [rememberSession].
 */
internal class NotificationScrimFlingBehavior(
    private var flingDecay: DecayAnimationSpec<Float>,
    private val motionDurationScale: MotionDurationScale = NotificationScrimMotionDurationScale
) : FlingBehavior {
    override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
        // come up with the better threshold, but we need it since spline curve gives us NaNs
        return withContext(motionDurationScale) {
            if (abs(initialVelocity) > 1f) {
                var velocityLeft = initialVelocity
                var lastValue = 0f
                val animationState =
                    AnimationState(
                        initialValue = 0f,
                        initialVelocity = initialVelocity,
                    )
                try {
                    animationState.animateDecay(flingDecay) {
                        val delta = value - lastValue
                        val consumed = scrollBy(delta)
                        lastValue = value
                        velocityLeft = this.velocity
                        // avoid rounding errors and stop if anything is unconsumed
                        if (abs(delta - consumed) > 0.5f) this.cancelAnimation()
                    }
                } catch (exception: CancellationException) {
                    velocityLeft = animationState.velocity
                }
                velocityLeft
            } else {
                initialVelocity
            }
        }
    }
}

internal val NotificationScrimMotionDurationScale =
    object : MotionDurationScale {
        override val scaleFactor: Float
            get() = 1f
    }
 No newline at end of file
+7 −8
Original line number Diff line number Diff line
@@ -20,12 +20,13 @@ package com.android.systemui.notifications.ui.composable
import android.util.Log
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.DecayAnimationSpec
import androidx.compose.animation.core.tween
import androidx.compose.animation.splineBasedDecay
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.gestures.rememberScrollableState
import androidx.compose.foundation.gestures.scrollBy
@@ -308,8 +309,6 @@ fun ContentScope.NotificationScrollingStack(
            ScrollState(initial = 0)
        }
    val syntheticScroll = viewModel.syntheticScroll.collectAsStateWithLifecycle(0f)
    val isCurrentGestureOverscroll =
        viewModel.isCurrentGestureOverscroll.collectAsStateWithLifecycle(false)
    val expansionFraction by viewModel.expandFraction.collectAsStateWithLifecycle(0f)
    val shadeToQsFraction by viewModel.shadeToQsFraction.collectAsStateWithLifecycle(0f)

@@ -454,15 +453,15 @@ fun ContentScope.NotificationScrollingStack(
        }
    }

    val flingBehavior = ScrollableDefaults.flingBehavior()
    val scrimNestedScrollConnection =
        shadeSession.rememberSession(
            scrimOffset,
            maxScrimTop,
            minScrimTop,
            isCurrentGestureOverscroll,
            flingBehavior,
            viewModel.isCurrentGestureOverscroll,
            density,
        ) {
            val flingSpec: DecayAnimationSpec<Float> = splineBasedDecay(density)
            val flingBehavior = NotificationScrimFlingBehavior(flingSpec)
            NotificationScrimNestedScrollConnection(
                scrimOffset = { scrimOffset.value },
                snapScrimOffset = { value -> coroutineScope.launch { scrimOffset.snapTo(value) } },
@@ -473,7 +472,7 @@ fun ContentScope.NotificationScrollingStack(
                maxScrimOffset = 0f,
                contentHeight = { stackHeight.intValue.toFloat() },
                minVisibleScrimHeight = minVisibleScrimHeight,
                isCurrentGestureOverscroll = { isCurrentGestureOverscroll.value },
                isCurrentGestureOverscroll = { viewModel.isCurrentGestureOverscroll },
                flingBehavior = flingBehavior,
            )
        }
+11 −7
Original line number Diff line number Diff line
@@ -89,6 +89,17 @@ constructor(
            source = shadeModeInteractor.shadeMode.map { getQuickSettingsShadeContentKey(it) },
        )

    /**
     * Whether the current touch gesture is overscroll. If true, it means the NSSL has already
     * consumed part of the gesture.
     */
    val isCurrentGestureOverscroll: Boolean by
        hydrator.hydratedStateOf(
            traceName = "isCurrentGestureOverscroll",
            initialValue = false,
            source = interactor.isCurrentGestureOverscroll
        )

    /** DEBUG: whether the placeholder should be made slightly visible for positional debugging. */
    val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES)

@@ -157,13 +168,6 @@ constructor(
    val syntheticScroll: Flow<Float> =
        interactor.syntheticScroll.dumpWhileCollecting("syntheticScroll")

    /**
     * Whether the current touch gesture is overscroll. If true, it means the NSSL has already
     * consumed part of the gesture.
     */
    val isCurrentGestureOverscroll: Flow<Boolean> =
        interactor.isCurrentGestureOverscroll.dumpWhileCollecting("isCurrentGestureOverScroll")

    /** Whether remote input is currently active for any notification. */
    val isRemoteInputActive = remoteInputInteractor.isRemoteInputActive