• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 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 package com.android.permissioncontroller.wear.permission.components.material2
17 
18 import androidx.compose.foundation.interaction.MutableInteractionSource
19 import androidx.compose.foundation.layout.BoxScope
20 import androidx.compose.foundation.layout.Row
21 import androidx.compose.foundation.layout.RowScope
22 import androidx.compose.foundation.layout.fillMaxWidth
23 import androidx.compose.foundation.layout.size
24 import androidx.compose.foundation.shape.CircleShape
25 import androidx.compose.runtime.Composable
26 import androidx.compose.runtime.remember
27 import androidx.compose.ui.Modifier
28 import androidx.compose.ui.draw.clip
29 import androidx.compose.ui.graphics.Color
30 import androidx.compose.ui.graphics.compositeOver
31 import androidx.compose.ui.res.stringResource
32 import androidx.compose.ui.semantics.Role
33 import androidx.compose.ui.semantics.role
34 import androidx.compose.ui.semantics.semantics
35 import androidx.compose.ui.semantics.stateDescription
36 import androidx.compose.ui.text.style.Hyphens
37 import androidx.compose.ui.text.style.TextAlign
38 import androidx.compose.ui.text.style.TextOverflow
39 import androidx.wear.compose.material.ChipDefaults
40 import androidx.wear.compose.material.ContentAlpha
41 import androidx.wear.compose.material.MaterialTheme
42 import androidx.wear.compose.material.Text
43 import androidx.wear.compose.material.ToggleChip
44 import androidx.wear.compose.material.ToggleChipColors
45 import androidx.wear.compose.material.ToggleChipDefaults
46 import androidx.wear.compose.material.contentColorFor
47 import com.android.permissioncontroller.wear.permission.components.R
48 import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionToggleControlType
49 
50 /**
51  * This component is an alternative to [ToggleChip], providing the following:
52  * - a convenient way of providing a label and a secondary label;
53  * - a convenient way of choosing the toggle control;
54  * - a convenient way of providing an icon and setting the icon to be mirrored in RTL mode;
55  */
56 @Composable
57 fun ToggleChip(
58     checked: Boolean,
59     onCheckedChanged: (Boolean) -> Unit,
60     label: String,
61     labelMaxLine: Int? = null,
62     toggleControl: WearPermissionToggleControlType,
63     modifier: Modifier = Modifier,
64     icon: Any? = null,
65     iconColor: Color = Color.Unspecified,
66     iconRtlMode: IconRtlMode = IconRtlMode.Default,
67     secondaryLabel: String? = null,
68     secondaryLabelMaxLine: Int? = null,
69     colors: ToggleChipColors = ToggleChipDefaults.toggleChipColors(),
70     enabled: Boolean = true,
71     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
72 ) {
73     val hasSecondaryLabel = secondaryLabel != null
74 
<lambda>null75     val labelParam: (@Composable RowScope.() -> Unit) = {
76         Text(
77             text = label,
78             modifier = Modifier.fillMaxWidth(),
79             textAlign = TextAlign.Start,
80             overflow = TextOverflow.Ellipsis,
81             maxLines = labelMaxLine ?: if (hasSecondaryLabel) 1 else 2,
82             style = MaterialTheme.typography.button.copy(hyphens = Hyphens.Auto),
83         )
84     }
85 
86     val secondaryLabelParam: (@Composable RowScope.() -> Unit)? =
<lambda>null87         secondaryLabel?.let {
88             {
89                 Text(
90                     text = secondaryLabel,
91                     overflow = TextOverflow.Ellipsis,
92                     maxLines = secondaryLabelMaxLine ?: 1,
93                     style = MaterialTheme.typography.caption2.copy(hyphens = Hyphens.Auto),
94                 )
95             }
96         }
97 
<lambda>null98     val toggleControlParam: (@Composable () -> Unit) = {
99         Icon(
100             imageVector =
101                 when (toggleControl) {
102                     WearPermissionToggleControlType.Switch -> ToggleChipDefaults.switchIcon(checked)
103                     WearPermissionToggleControlType.Radio -> ToggleChipDefaults.radioIcon(checked)
104                     WearPermissionToggleControlType.Checkbox ->
105                         ToggleChipDefaults.checkboxIcon(checked)
106                 },
107             contentDescription = null,
108             // This potentially be removed once this issue is addressed:
109             // https://issuetracker.google.com/issues/287087138
110             rtlMode =
111                 if (toggleControl == WearPermissionToggleControlType.Switch) {
112                     IconRtlMode.Mirrored
113                 } else {
114                     IconRtlMode.Default
115                 },
116         )
117     }
118 
119     val iconParam: (@Composable BoxScope.() -> Unit)? =
<lambda>null120         icon?.let {
121             {
122                 Row {
123                     Icon(
124                         icon = icon,
125                         tint = iconColor,
126                         contentDescription = null,
127                         modifier = Modifier.size(ChipDefaults.IconSize).clip(CircleShape),
128                         rtlMode = iconRtlMode,
129                     )
130                 }
131             }
132         }
133 
134     ToggleChip(
135         checked = checked,
newCheckednull136         onCheckedChange = { newChecked ->
137             // Radio buttons cannot be toggled off by tapping on it again.
138             if (toggleControl != WearPermissionToggleControlType.Radio || newChecked) {
139                 onCheckedChanged.invoke(newChecked)
140             }
141         },
142         label = labelParam,
143         toggleControl = toggleControlParam,
144         modifier = modifier.fillMaxWidth().toggleControlSemantics(toggleControl, checked),
145         appIcon = iconParam,
146         secondaryLabel = secondaryLabelParam,
147         colors = colors,
148         enabled = enabled,
149         interactionSource = interactionSource,
150     )
151 }
152 
153 /**
154  * ToggleChipColors that disabled alpha is applied based on [ToggleChipDefaults.toggleChipColors()].
155  * It is used for a ToggleChip which would like to respond to click events, meanwhile it seems
156  * disabled.
157  */
158 @Composable
toggleChipDisabledColorsnull159 fun toggleChipDisabledColors(): ToggleChipColors {
160     val checkedStartBackgroundColor =
161         MaterialTheme.colors.surface.copy(alpha = 0f).compositeOver(MaterialTheme.colors.surface)
162     val checkedEndBackgroundColor =
163         MaterialTheme.colors.primary.copy(alpha = 0.5f).compositeOver(MaterialTheme.colors.surface)
164     val checkedContentColor = MaterialTheme.colors.onSurface
165     val checkedSecondaryContentColor = MaterialTheme.colors.onSurfaceVariant
166     val checkedToggleControlColor = MaterialTheme.colors.secondary
167     val uncheckedStartBackgroundColor = MaterialTheme.colors.surface
168     val uncheckedEndBackgroundColor = uncheckedStartBackgroundColor
169     val uncheckedContentColor = contentColorFor(checkedEndBackgroundColor)
170     val uncheckedSecondaryContentColor = uncheckedContentColor
171     val uncheckedToggleControlColor = uncheckedContentColor
172 
173     return ToggleChipDefaults.toggleChipColors(
174         checkedStartBackgroundColor =
175             checkedStartBackgroundColor.copy(alpha = ContentAlpha.disabled),
176         checkedEndBackgroundColor = checkedEndBackgroundColor.copy(alpha = ContentAlpha.disabled),
177         checkedContentColor = checkedContentColor.copy(alpha = ContentAlpha.disabled),
178         checkedSecondaryContentColor =
179             checkedSecondaryContentColor.copy(alpha = ContentAlpha.disabled),
180         checkedToggleControlColor = checkedToggleControlColor.copy(alpha = ContentAlpha.disabled),
181         uncheckedStartBackgroundColor =
182             uncheckedStartBackgroundColor.copy(alpha = ContentAlpha.disabled),
183         uncheckedEndBackgroundColor =
184             uncheckedEndBackgroundColor.copy(alpha = ContentAlpha.disabled),
185         uncheckedContentColor = uncheckedContentColor.copy(alpha = ContentAlpha.disabled),
186         uncheckedSecondaryContentColor =
187             uncheckedSecondaryContentColor.copy(alpha = ContentAlpha.disabled),
188         uncheckedToggleControlColor =
189             uncheckedToggleControlColor.copy(alpha = ContentAlpha.disabled),
190     )
191 }
192 
193 /**
194  * ToggleChipColors that theme background color is applied based on
195  * [ToggleChipDefaults.toggleChipColors()]. It is used for a ToggleChip having the same background
196  * color of the screen.
197  */
198 @Composable
toggleChipBackgroundColorsnull199 fun toggleChipBackgroundColors(): ToggleChipColors {
200     val checkedStartBackgroundColor =
201         MaterialTheme.colors.background
202             .copy(alpha = 0f)
203             .compositeOver(MaterialTheme.colors.background)
204     val checkedEndBackgroundColor =
205         MaterialTheme.colors.primary
206             .copy(alpha = 0.5f)
207             .compositeOver(MaterialTheme.colors.background)
208     val checkedContentColor = MaterialTheme.colors.onBackground
209     val checkedSecondaryContentColor = MaterialTheme.colors.onSurfaceVariant
210     val checkedToggleControlColor = MaterialTheme.colors.secondary
211     val uncheckedStartBackgroundColor = MaterialTheme.colors.background
212     val uncheckedEndBackgroundColor = uncheckedStartBackgroundColor
213     val uncheckedContentColor = contentColorFor(checkedEndBackgroundColor)
214     val uncheckedSecondaryContentColor = uncheckedContentColor
215     val uncheckedToggleControlColor = uncheckedContentColor
216 
217     return ToggleChipDefaults.toggleChipColors(
218         checkedStartBackgroundColor = checkedStartBackgroundColor,
219         checkedEndBackgroundColor = checkedEndBackgroundColor,
220         checkedContentColor = checkedContentColor,
221         checkedSecondaryContentColor = checkedSecondaryContentColor,
222         checkedToggleControlColor = checkedToggleControlColor,
223         uncheckedStartBackgroundColor = uncheckedStartBackgroundColor,
224         uncheckedEndBackgroundColor = uncheckedEndBackgroundColor,
225         uncheckedContentColor = uncheckedContentColor,
226         uncheckedSecondaryContentColor = uncheckedSecondaryContentColor,
227         uncheckedToggleControlColor = uncheckedToggleControlColor,
228     )
229 }
230 
231 @Composable
Modifiernull232 fun Modifier.toggleControlSemantics(
233     toggleControl: WearPermissionToggleControlType,
234     checked: Boolean,
235 ): Modifier {
236     val semanticsRole =
237         when (toggleControl) {
238             WearPermissionToggleControlType.Switch -> Role.Switch
239             WearPermissionToggleControlType.Radio -> Role.RadioButton
240             WearPermissionToggleControlType.Checkbox -> Role.Checkbox
241         }
242     val stateDescriptionSemantics =
243         stringResource(
244             if (checked) {
245                 R.string.on
246             } else {
247                 R.string.off
248             }
249         )
250 
251     return semantics {
252         role = semanticsRole
253         stateDescription = stateDescriptionSemantics
254     }
255 }
256