• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2022 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.settingslib.spa.widget.ui
18 
19 import androidx.compose.foundation.layout.Arrangement
20 import androidx.compose.foundation.layout.Column
21 import androidx.compose.foundation.layout.ColumnScope
22 import androidx.compose.foundation.layout.PaddingValues
23 import androidx.compose.foundation.layout.fillMaxSize
24 import androidx.compose.foundation.layout.fillMaxWidth
25 import androidx.compose.foundation.layout.padding
26 import androidx.compose.foundation.lazy.LazyColumn
27 import androidx.compose.foundation.lazy.LazyListState
28 import androidx.compose.foundation.lazy.rememberLazyListState
29 import androidx.compose.material.icons.Icons
30 import androidx.compose.material.icons.outlined.TouchApp
31 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
32 import androidx.compose.material3.MaterialTheme
33 import androidx.compose.material3.Text
34 import androidx.compose.runtime.Composable
35 import androidx.compose.runtime.CompositionLocalProvider
36 import androidx.compose.runtime.compositionLocalOf
37 import androidx.compose.runtime.getValue
38 import androidx.compose.runtime.mutableStateOf
39 import androidx.compose.runtime.remember
40 import androidx.compose.runtime.setValue
41 import androidx.compose.ui.Modifier
42 import androidx.compose.ui.draw.clip
43 import androidx.compose.ui.layout.onGloballyPositioned
44 import androidx.compose.ui.tooling.preview.Preview
45 import androidx.compose.ui.unit.Dp
46 import androidx.compose.ui.unit.dp
47 import com.android.settingslib.spa.framework.theme.SettingsDimension
48 import com.android.settingslib.spa.framework.theme.SettingsShape
49 import com.android.settingslib.spa.framework.theme.SettingsTheme
50 import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
51 import com.android.settingslib.spa.widget.preference.Preference
52 import com.android.settingslib.spa.widget.preference.PreferenceModel
53 
54 /** A category title that is placed before a group of similar items. */
55 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
56 @Composable
57 fun CategoryTitle(title: String) {
58     Text(
59         text = title,
60         modifier =
61             Modifier.padding(
62                 start =
63                     if (isSpaExpressiveEnabled) SettingsDimension.paddingSmall
64                     else SettingsDimension.itemPaddingStart,
65                 top = 20.dp,
66                 end =
67                     if (isSpaExpressiveEnabled) SettingsDimension.paddingSmall
68                     else SettingsDimension.itemPaddingEnd,
69                 bottom = 8.dp,
70             ),
71         color = MaterialTheme.colorScheme.primary,
72         style =
73             if (isSpaExpressiveEnabled) MaterialTheme.typography.labelLargeEmphasized
74             else MaterialTheme.typography.labelMedium,
75     )
76 }
77 
78 /**
79  * A container that is used to group similar items. A [Category] displays a [CategoryTitle] and
80  * visually separates groups of items.
81  *
82  * @param content The content of the category.
83  */
84 @Composable
Categorynull85 fun Category(
86     title: String? = null,
87     modifier: Modifier = Modifier,
88     content: @Composable ColumnScope.() -> Unit,
89 ) {
90     var displayTitle by remember { mutableStateOf(false) }
91     Column(
92         modifier =
93             if (isSpaExpressiveEnabled && displayTitle)
94                 Modifier.padding(
95                     horizontal = SettingsDimension.paddingLarge,
96                     vertical = SettingsDimension.paddingSmall,
97                 )
98             else Modifier
99     ) {
100         if (title != null && displayTitle) CategoryTitle(title = title)
101         Column(
102             modifier =
103                 modifier
104                     .onGloballyPositioned { coordinates ->
105                         displayTitle = coordinates.size.height > 0
106                     }
107                     .then(
108                         if (isSpaExpressiveEnabled)
109                             Modifier.fillMaxWidth().clip(SettingsShape.CornerMedium2)
110                         else Modifier
111                     ),
112             verticalArrangement =
113                 if (isSpaExpressiveEnabled) Arrangement.spacedBy(SettingsDimension.paddingTiny)
114                 else Arrangement.Top,
115             content = { CompositionLocalProvider(LocalIsInCategory provides true) { content() } },
116         )
117     }
118 }
119 
120 /**
121  * A container that is used to group items with lazy loading.
122  *
123  * @param list The list of items to display.
124  * @param entry The entry for each list item according to its index in list.
125  * @param key Optional. The key for each item in list to provide unique item identifiers, making the
126  *   list more efficient.
127  * @param title Optional. Category title for each item or each group of items in the list. It should
128  *   be decided by the index.
129  * @param bottomPadding Optional. Bottom outside padding of the category.
130  * @param state Optional. State of LazyList.
131  * @param footer Optional. Content to be shown at the bottom of the category.
132  * @param header Optional. Content to be shown at the top of the category.
133  */
134 @Composable
LazyCategorynull135 fun LazyCategory(
136     list: List<Any>,
137     entry: (Int) -> @Composable () -> Unit,
138     key: ((Int) -> Any)? = null,
139     title: ((Int) -> String?)? = null,
140     bottomPadding: Dp = SettingsDimension.paddingSmall,
141     state: LazyListState = rememberLazyListState(),
142     footer: @Composable () -> Unit = {},
143     header: @Composable () -> Unit,
144 ) {
145     Column(
146         Modifier.padding(
147                 PaddingValues(
148                     start = SettingsDimension.paddingLarge,
149                     end = SettingsDimension.paddingLarge,
150                     top = SettingsDimension.paddingSmall,
151                     bottom = bottomPadding,
152                 )
153             )
154             .clip(SettingsShape.CornerMedium2)
<lambda>null155     ) {
156         LazyColumn(
157             modifier = Modifier.fillMaxSize(),
158             verticalArrangement = Arrangement.spacedBy(SettingsDimension.paddingTiny),
159             state = state,
160         ) {
161             item { CompositionLocalProvider(LocalIsInCategory provides true) { header() } }
162 
163             items(count = list.size, key = key) {
164                 title?.invoke(it)?.let { title -> CategoryTitle(title) }
165                 CompositionLocalProvider(LocalIsInCategory provides true) { entry(it)() }
166             }
167 
168             item { CompositionLocalProvider(LocalIsInCategory provides true) { footer() } }
169         }
170     }
171 }
172 
173 /** LocalIsInCategory containing the if the current composable is in a category. */
<lambda>null174 internal val LocalIsInCategory = compositionLocalOf { false }
175 
176 @Preview
177 @Composable
CategoryPreviewnull178 private fun CategoryPreview() {
179     SettingsTheme {
180         Category(title = "Appearance") {
181             Preference(
182                 object : PreferenceModel {
183                     override val title = "Title"
184                     override val summary = { "Summary" }
185                 }
186             )
187             Preference(
188                 object : PreferenceModel {
189                     override val title = "Title"
190                     override val summary = { "Summary" }
191                     override val icon =
192                         @Composable { SettingsIcon(imageVector = Icons.Outlined.TouchApp) }
193                 }
194             )
195         }
196     }
197 }
198 
199 @Preview
200 @Composable
LazyCategoryPreviewnull201 private fun LazyCategoryPreview() {
202     SettingsTheme {
203         LazyCategory(
204             list = listOf(1, 2, 3),
205             entry = { key ->
206                 @Composable {
207                     Preference(
208                         object : PreferenceModel {
209                             override val title = key.toString()
210                         }
211                     )
212                 }
213             },
214             footer = @Composable {
215                 Footer("Footer")
216             },
217             header = @Composable {
218                 Text("Header")
219             },
220         )
221     }
222 }
223