• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 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  *      https://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 @file:Suppress("ObjectLiteralToLambda")
18 @file:OptIn(ExperimentalWearFoundationApi::class)
19 
20 package com.android.permissioncontroller.wear.permission.components.material2.layout
21 
22 import androidx.compose.foundation.MutatePriority
23 import androidx.compose.foundation.gestures.FlingBehavior
24 import androidx.compose.foundation.gestures.ScrollScope
25 import androidx.compose.foundation.gestures.ScrollableDefaults
26 import androidx.compose.foundation.gestures.ScrollableState
27 import androidx.compose.foundation.layout.Arrangement
28 import androidx.compose.foundation.layout.PaddingValues
29 import androidx.compose.foundation.layout.fillMaxSize
30 import androidx.compose.runtime.Composable
31 import androidx.compose.runtime.saveable.rememberSaveable
32 import androidx.compose.ui.Alignment
33 import androidx.compose.ui.Modifier
34 import androidx.compose.ui.platform.LocalConfiguration
35 import androidx.compose.ui.platform.LocalDensity
36 import androidx.compose.ui.unit.dp
37 import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
38 import androidx.wear.compose.foundation.lazy.AutoCenteringParams
39 import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
40 import androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults as WearScalingLazyColumnDefaults
41 import androidx.wear.compose.foundation.lazy.ScalingLazyListAnchorType
42 import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
43 import androidx.wear.compose.foundation.lazy.ScalingLazyListState
44 import androidx.wear.compose.foundation.lazy.ScalingParams
45 import com.android.permissioncontroller.wear.permission.components.material2.layout.ScalingLazyColumnDefaults.responsiveScalingParams
46 import com.android.permissioncontroller.wear.permission.components.material2.layout.ScalingLazyColumnState.RotaryMode
47 
48 // This file is a copy of ScalingLazyColumnState.kt from Horologist (go/horologist),
49 // remove it once after wear compose supports large screen dialogs.
50 
51 /**
52  * A Config and State object wrapping up all configuration for a [ScalingLazyColumn]. This allows
53  * defaults such as [ScalingLazyColumnDefaults.responsive].
54  */
55 class ScalingLazyColumnState(
56     val initialScrollPosition: ScrollPosition = ScrollPosition(1, 0),
57     val autoCentering: AutoCenteringParams? =
58         AutoCenteringParams(initialScrollPosition.index, initialScrollPosition.offsetPx),
59     val anchorType: ScalingLazyListAnchorType = ScalingLazyListAnchorType.ItemCenter,
60     val contentPadding: PaddingValues = PaddingValues(horizontal = 10.dp),
61     val rotaryMode: RotaryMode? = RotaryMode.Scroll,
62     val reverseLayout: Boolean = false,
63     val verticalArrangement: Arrangement.Vertical =
64         Arrangement.spacedBy(
65             space = 4.dp,
66             alignment = if (!reverseLayout) Alignment.Top else Alignment.Bottom,
67         ),
68     val horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
69     val flingBehavior: FlingBehavior? = null,
70     val userScrollEnabled: Boolean = true,
71     val scalingParams: ScalingParams = WearScalingLazyColumnDefaults.scalingParams(),
72     val hapticsEnabled: Boolean = true,
73 ) : ScrollableState {
74     private var _state: ScalingLazyListState? = null
75     var state: ScalingLazyListState
76         get() {
77             if (_state == null) {
78                 _state =
79                     ScalingLazyListState(
80                         initialScrollPosition.index,
81                         initialScrollPosition.offsetPx,
82                     )
83             }
84             return _state!!
85         }
86         set(value) {
87             _state = value
88         }
89 
90     override val canScrollBackward: Boolean
91         get() = state.canScrollBackward
92 
93     override val canScrollForward: Boolean
94         get() = state.canScrollForward
95 
96     override val isScrollInProgress: Boolean
97         get() = state.isScrollInProgress
98 
dispatchRawDeltanull99     override fun dispatchRawDelta(delta: Float): Float = state.dispatchRawDelta(delta)
100 
101     override suspend fun scroll(
102         scrollPriority: MutatePriority,
103         block: suspend ScrollScope.() -> Unit,
104     ) {
105         state.scroll(scrollPriority, block)
106     }
107 
108     sealed interface RotaryMode {
109         data object Snap : RotaryMode
110 
111         data object Scroll : RotaryMode
112     }
113 
114     data class ScrollPosition(val index: Int, val offsetPx: Int)
115 
interfacenull116     fun interface Factory {
117         @Composable fun create(): ScalingLazyColumnState
118     }
119 }
120 
121 // @Deprecated("Replaced by rememberResponsiveColumnState")
122 @Composable
rememberColumnStatenull123 fun rememberColumnState(
124     factory: ScalingLazyColumnState.Factory = ScalingLazyColumnDefaults.responsive()
125 ): ScalingLazyColumnState {
126     val columnState = factory.create()
127 
128     columnState.state = rememberSaveable(saver = ScalingLazyListState.Saver) { columnState.state }
129 
130     return columnState
131 }
132 
133 @Composable
rememberResponsiveColumnStatenull134 fun rememberResponsiveColumnState(
135     contentPadding: @Composable () -> PaddingValues =
136         ScalingLazyColumnDefaults.padding(
137             first = ScalingLazyColumnDefaults.ItemType.Unspecified,
138             last = ScalingLazyColumnDefaults.ItemType.Unspecified,
139         ),
140     verticalArrangement: Arrangement.Vertical =
141         Arrangement.spacedBy(space = 4.dp, alignment = Alignment.Top),
142     rotaryMode: RotaryMode? = RotaryMode.Scroll,
143     hapticsEnabled: Boolean = true,
144     reverseLayout: Boolean = false,
145     userScrollEnabled: Boolean = true,
146 ): ScalingLazyColumnState {
147     val density = LocalDensity.current
148     val configuration = LocalConfiguration.current
149     val screenWidthDp = configuration.screenWidthDp.toFloat()
150     val screenHeightDp = configuration.screenHeightDp.toFloat()
151 
152     val scalingParams = responsiveScalingParams(screenWidthDp)
153 
154     val contentPaddingCalculated = contentPadding()
155 
156     val screenHeightPx = with(density) { screenHeightDp.dp.roundToPx() }
157     val topPaddingPx = with(density) { contentPaddingCalculated.calculateTopPadding().roundToPx() }
158     val topScreenOffsetPx = screenHeightPx / 2 - topPaddingPx
159 
160     val initialScrollPosition =
161         ScalingLazyColumnState.ScrollPosition(index = 0, offsetPx = topScreenOffsetPx)
162 
163     val columnState =
164         ScalingLazyColumnState(
165             initialScrollPosition = initialScrollPosition,
166             autoCentering = null,
167             anchorType = ScalingLazyListAnchorType.ItemStart,
168             rotaryMode = rotaryMode,
169             verticalArrangement = verticalArrangement,
170             horizontalAlignment = Alignment.CenterHorizontally,
171             contentPadding = contentPaddingCalculated,
172             scalingParams = scalingParams,
173             hapticsEnabled = hapticsEnabled,
174             reverseLayout = reverseLayout,
175             userScrollEnabled = userScrollEnabled,
176         )
177 
178     columnState.state = rememberSaveable(saver = ScalingLazyListState.Saver) { columnState.state }
179 
180     return columnState
181 }
182 
183 @Composable
ScalingLazyColumnnull184 fun ScalingLazyColumn(
185     columnState: ScalingLazyColumnState,
186     modifier: Modifier = Modifier,
187     content: ScalingLazyListScope.() -> Unit,
188 ) {
189     ScalingLazyColumn(
190         modifier = modifier.fillMaxSize(),
191         state = columnState.state,
192         contentPadding = columnState.contentPadding,
193         reverseLayout = columnState.reverseLayout,
194         verticalArrangement = columnState.verticalArrangement,
195         horizontalAlignment = columnState.horizontalAlignment,
196         flingBehavior = columnState.flingBehavior ?: ScrollableDefaults.flingBehavior(),
197         userScrollEnabled = columnState.userScrollEnabled,
198         scalingParams = columnState.scalingParams,
199         anchorType = columnState.anchorType,
200         autoCentering = columnState.autoCentering,
201         content = content,
202     )
203 }
204