summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Shawn Lee <syeonlee@google.com> 2025-03-13 10:40:15 -0700
committer Steve Elliott <steell@google.com> 2025-03-13 17:09:42 -0400
commit1695035a09adeec10a1a08cf816c79d6348488fd (patch)
tree4bedeb2cc197d9664829f4d366578ae604d70a39
parent3f7eacaaf860a21ea15b6203abcdc0e60f9bd269 (diff)
[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
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimFlingBehavior.kt73
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt18
3 files changed, 91 insertions, 15 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimFlingBehavior.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimFlingBehavior.kt
new file mode 100644
index 000000000000..bc38ef0abb25
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimFlingBehavior.kt
@@ -0,0 +1,73 @@
+/*
+ * 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
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 09b8d178cc8e..800501a920fd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -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,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 000b3f643e9a..12b48eba7a96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -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