1 /*
2  * 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 
17 package com.android.permissioncontroller.permission.ui.wear.elements
18 
19 import androidx.compose.foundation.interaction.MutableInteractionSource
20 import androidx.compose.foundation.layout.BoxScope
21 import androidx.compose.foundation.layout.Row
22 import androidx.compose.foundation.layout.RowScope
23 import androidx.compose.foundation.layout.fillMaxWidth
24 import androidx.compose.foundation.layout.size
25 import androidx.compose.foundation.shape.CircleShape
26 import androidx.compose.runtime.Composable
27 import androidx.compose.runtime.remember
28 import androidx.compose.ui.Modifier
29 import androidx.compose.ui.draw.clip
30 import androidx.compose.ui.graphics.Color
31 import androidx.compose.ui.graphics.compositeOver
32 import androidx.compose.ui.res.stringResource
33 import androidx.compose.ui.semantics.semantics
34 import androidx.compose.ui.semantics.stateDescription
35 import androidx.compose.ui.text.style.TextAlign
36 import androidx.compose.ui.text.style.TextOverflow
37 import androidx.wear.compose.material.ChipDefaults
38 import androidx.wear.compose.material.ContentAlpha
39 import androidx.wear.compose.material.MaterialTheme
40 import androidx.wear.compose.material.Text
41 import androidx.wear.compose.material.ToggleChip
42 import androidx.wear.compose.material.ToggleChipColors
43 import androidx.wear.compose.material.ToggleChipDefaults
44 import androidx.wear.compose.material.contentColorFor
45 import com.android.permissioncontroller.R
46 
47 /**
48  * This component is an alternative to [ToggleChip], providing the following:
49  * - a convenient way of providing a label and a secondary label;
50  * - a convenient way of choosing the toggle control;
51  * - a convenient way of providing an icon and setting the icon to be mirrored in RTL mode;
52  */
53 @Composable
ToggleChipnull54 public fun ToggleChip(
55     checked: Boolean,
56     onCheckedChanged: (Boolean) -> Unit,
57     label: String,
58     labelMaxLine: Int? = null,
59     toggleControl: ToggleChipToggleControl,
60     modifier: Modifier = Modifier,
61     icon: Any? = null,
62     iconColor: Color = Color.Unspecified,
63     iconRtlMode: IconRtlMode = IconRtlMode.Default,
64     secondaryLabel: String? = null,
65     secondaryLabelMaxLine: Int? = null,
66     colors: ToggleChipColors = ToggleChipDefaults.toggleChipColors(),
67     enabled: Boolean = true,
68     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
69 ) {
70     val hasSecondaryLabel = secondaryLabel != null
71 
<lambda>null72     val labelParam: (@Composable RowScope.() -> Unit) = {
73         Text(
74             text = label,
75             modifier = Modifier.fillMaxWidth(),
76             textAlign = TextAlign.Start,
77             overflow = TextOverflow.Ellipsis,
78             maxLines = labelMaxLine ?: if (hasSecondaryLabel) 1 else 2,
79             style = MaterialTheme.typography.button
80         )
81     }
82 
83     val secondaryLabelParam: (@Composable RowScope.() -> Unit)? =
<lambda>null84         secondaryLabel?.let {
85             {
86                 Text(
87                     text = secondaryLabel,
88                     overflow = TextOverflow.Ellipsis,
89                     maxLines = secondaryLabelMaxLine ?: 1,
90                     style = MaterialTheme.typography.caption2
91                 )
92             }
93         }
94 
95     val toggleControlParam: (@Composable () -> Unit) = {
96         Icon(
97             imageVector =
98                 when (toggleControl) {
99                     ToggleChipToggleControl.Switch -> ToggleChipDefaults.switchIcon(checked)
100                     ToggleChipToggleControl.Radio -> ToggleChipDefaults.radioIcon(checked)
101                     ToggleChipToggleControl.Checkbox -> ToggleChipDefaults.checkboxIcon(checked)
102                 },
103             contentDescription = null,
104             // This potentially be removed once this issue is addressed:
105             // https://issuetracker.google.com/issues/287087138
106             rtlMode =
107                 if (toggleControl == ToggleChipToggleControl.Switch) {
108                     IconRtlMode.Mirrored
109                 } else {
110                     IconRtlMode.Default
111                 }
112         )
113     }
114 
115     val iconParam: (@Composable BoxScope.() -> Unit)? =
<lambda>null116         icon?.let {
117             {
118                 Row {
119                     Icon(
120                         icon = icon,
121                         tint = iconColor,
122                         contentDescription = null,
123                         modifier = Modifier.size(ChipDefaults.IconSize).clip(CircleShape),
124                         rtlMode = iconRtlMode
125                     )
126                 }
127             }
128         }
129 
130     val stateDescriptionSemantics =
131         stringResource(
132             if (checked) {
133                 R.string.on
134             } else {
135                 R.string.off
136             }
137         )
138     ToggleChip(
139         checked = checked,
140         onCheckedChange = onCheckedChanged,
141         label = labelParam,
142         toggleControl = toggleControlParam,
143         modifier =
<lambda>null144             modifier.fillMaxWidth().semantics { stateDescription = stateDescriptionSemantics },
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