summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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