• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.notifications.ui.composable
18 
19 import androidx.compose.foundation.gestures.FlingBehavior
20 import androidx.compose.foundation.gestures.Orientation
21 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
22 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
23 import androidx.compose.ui.util.fastCoerceAtLeast
24 import androidx.compose.ui.util.fastCoerceAtMost
25 import com.android.compose.nestedscroll.OnStopScope
26 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
27 import com.android.compose.nestedscroll.ScrollController
28 
29 /**
30  * A [NestedScrollConnection] that listens for all vertical scroll events and responds in the
31  * following way:
32  * - If you **scroll up**, it **first brings the [scrimOffset]** back to the [minScrimOffset] and
33  *   then allows scrolling of the children (usually the content).
34  * - If you **scroll down**, it **first allows scrolling of the children** (usually the content) and
35  *   then resets the [scrimOffset] to [maxScrimOffset].
36  */
37 fun NotificationScrimNestedScrollConnection(
38     scrimOffset: () -> Float,
39     snapScrimOffset: (Float) -> Unit,
40     animateScrimOffset: (Float) -> Unit,
41     minScrimOffset: () -> Float,
42     maxScrimOffset: Float,
43     contentHeight: () -> Float,
44     minVisibleScrimHeight: () -> Float,
45     isCurrentGestureOverscroll: () -> Boolean,
46     onStart: (Float) -> Unit = {},
<lambda>null47     onStop: (Float) -> Unit = {},
48     flingBehavior: FlingBehavior,
49 ): PriorityNestedScrollConnection {
50     return PriorityNestedScrollConnection(
51         orientation = Orientation.Vertical,
52         // scrolling up and inner content is taller than the scrim, so scrim needs to
53         // expand; content can scroll once scrim is at the minScrimOffset.
offsetBeforeStartnull54         canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
55             offsetAvailable < 0 &&
56                 offsetBeforeStart == 0f &&
57                 contentHeight() > minVisibleScrimHeight() &&
58                 scrimOffset() > minScrimOffset()
59         },
60         // scrolling down and content is done scrolling to top. After that, the scrim
61         // needs to collapse; collapse the scrim until it is at the maxScrimOffset.
offsetAvailablenull62         canStartPostScroll = { offsetAvailable, _, _ ->
63             offsetAvailable > 0 && (scrimOffset() < maxScrimOffset || isCurrentGestureOverscroll())
64         },
firstScrollnull65         onStart = { firstScroll ->
66             onStart(firstScroll)
67             object : ScrollController {
68                 override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
69                     val currentHeight = scrimOffset()
70                     val amountConsumed =
71                         if (deltaScroll > 0) {
72                             val amountLeft = maxScrimOffset - currentHeight
73                             deltaScroll.fastCoerceAtMost(amountLeft)
74                         } else {
75                             val amountLeft = minScrimOffset() - currentHeight
76                             deltaScroll.fastCoerceAtLeast(amountLeft)
77                         }
78                     snapScrimOffset(currentHeight + amountConsumed)
79                     return amountConsumed
80                 }
81 
82                 override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
83                     val consumedByScroll = flingToScroll(initialVelocity, flingBehavior)
84                     onStop(initialVelocity - consumedByScroll)
85                     if (scrimOffset() < minScrimOffset()) {
86                         animateScrimOffset(minScrimOffset())
87                     }
88                     // Don't consume the velocity on pre/post fling
89                     return 0f
90                 }
91 
92                 override fun onCancel() {
93                     onStop(0f)
94                     if (scrimOffset() < minScrimOffset()) {
95                         animateScrimOffset(minScrimOffset())
96                     }
97                 }
98 
99                 override fun canStopOnPreFling() = false
100             }
101         },
102     )
103 }
104