1 /*
2 * Copyright 2021 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 androidx.glance.appwidget
18
19 import androidx.annotation.RestrictTo
20 import androidx.compose.runtime.Composable
21 import androidx.compose.ui.graphics.Color
22 import androidx.glance.Emittable
23 import androidx.glance.EmittableCheckable
24 import androidx.glance.ExperimentalGlanceApi
25 import androidx.glance.GlanceModifier
26 import androidx.glance.GlanceNode
27 import androidx.glance.GlanceTheme
28 import androidx.glance.action.Action
29 import androidx.glance.action.ActionModifier
30 import androidx.glance.action.action
31 import androidx.glance.appwidget.action.CompoundButtonAction
32 import androidx.glance.appwidget.unit.CheckableColorProvider
33 import androidx.glance.appwidget.unit.CheckedUncheckedColorProvider.Companion.createCheckableColorProvider
34 import androidx.glance.appwidget.unit.ResourceCheckableColorProvider
35 import androidx.glance.color.DynamicThemeColorProviders
36 import androidx.glance.text.TextStyle
37 import androidx.glance.unit.ColorProvider
38 import androidx.glance.unit.FixedColorProvider
39
40 /** Set of colors to apply to a CheckBox depending on the checked state. */
41 sealed class CheckBoxColors {
42 internal abstract val checkBox: CheckableColorProvider
43 }
44
45 internal data class CheckBoxColorsImpl(override val checkBox: CheckableColorProvider) :
46 CheckBoxColors()
47
48 /**
49 * Adds a check box view to the glance view.
50 *
51 * @param checked whether the check box is checked
52 * @param onCheckedChange the action to be run when the checkbox is clicked. The current value of
53 * checked is provided to this action in its ActionParameters, and can be retrieved using the
54 * [ToggleableStateKey]. If this action launches an activity, the current value of checked will be
55 * passed as an intent extra with the name [RemoteViews.EXTRA_CHECKED]. In order to allow the
56 * Launcher to provide this extra on Android version S and later, we use a mutable PendingIntent
57 * ([android.app.PendingIntent.FLAG_MUTABLE]) when this action is not a lambda. Before S, and for
58 * lambda actions, this will be an immutable PendingIntent.
59 * @param modifier the modifier to apply to the check box
60 * @param text the text to display to the end of the check box
61 * @param style the style to apply to [text]
62 * @param colors the color tint to apply to the check box
63 * @param maxLines An optional maximum number of lines for the text to span, wrapping if necessary.
64 * If the text exceeds the given number of lines, it will be truncated.
65 */
66 @Composable
CheckBoxnull67 fun CheckBox(
68 checked: Boolean,
69 onCheckedChange: Action?,
70 modifier: GlanceModifier = GlanceModifier,
71 text: String = "",
72 style: TextStyle? = null,
73 colors: CheckBoxColors = CheckboxDefaults.colors(),
74 maxLines: Int = Int.MAX_VALUE,
75 ) = CheckBoxElement(checked, onCheckedChange, modifier, text, style, colors, maxLines)
76
77 /**
78 * Adds a check box view to the glance view.
79 *
80 * @param checked whether the check box is checked
81 * @param onCheckedChange the action to be run when the checkbox is clicked
82 * @param modifier the modifier to apply to the check box
83 * @param text the text to display to the end of the check box
84 * @param style the style to apply to [text]
85 * @param colors the color tint to apply to the check box
86 * @param maxLines An optional maximum number of lines for the text to span, wrapping if necessary.
87 * If the text exceeds the given number of lines, it will be truncated.
88 */
89 @Composable
90 fun CheckBox(
91 checked: Boolean,
92 onCheckedChange: () -> Unit,
93 modifier: GlanceModifier = GlanceModifier,
94 text: String = "",
95 style: TextStyle? = null,
96 colors: CheckBoxColors = CheckboxDefaults.colors(),
97 maxLines: Int = Int.MAX_VALUE,
98 ) =
99 CheckBoxElement(
100 checked,
101 action(block = onCheckedChange),
102 modifier,
103 text,
104 style,
105 colors,
106 maxLines
107 )
108
109 /**
110 * Adds a check box view to the glance view.
111 *
112 * @param checked whether the check box is checked
113 * @param onCheckedChange the action to be run when the checkbox is clicked
114 * @param modifier the modifier to apply to the check box
115 * @param text the text to display to the end of the check box
116 * @param style the style to apply to [text]
117 * @param colors the color tint to apply to the check box
118 * @param maxLines An optional maximum number of lines for the text to span, wrapping if necessary.
119 * If the text exceeds the given number of lines, it will be truncated.
120 * @param key A stable and unique key that identifies the action for this checkbox. This ensures
121 * that the correct action is triggered, especially in cases of items that change order. If not
122 * provided we use the key that is automatically generated by the Compose runtime, which is unique
123 * for every exact code location in the composition tree.
124 */
125 @ExperimentalGlanceApi
126 @Composable
127 fun CheckBox(
128 checked: Boolean,
129 onCheckedChange: () -> Unit,
130 modifier: GlanceModifier = GlanceModifier,
131 text: String = "",
132 style: TextStyle? = null,
133 colors: CheckBoxColors = CheckboxDefaults.colors(),
134 maxLines: Int = Int.MAX_VALUE,
135 key: String? = null,
136 ) = CheckBoxElement(checked, action(key, onCheckedChange), modifier, text, style, colors, maxLines)
137
138 @Composable
139 private fun CheckBoxElement(
140 checked: Boolean,
141 onCheckedChange: Action?,
142 modifier: GlanceModifier = GlanceModifier,
143 text: String = "",
144 style: TextStyle? = null,
145 colors: CheckBoxColors = CheckboxDefaults.colors(),
146 maxLines: Int = Int.MAX_VALUE,
147 ) {
148 val finalModifier =
149 if (onCheckedChange != null) {
150 modifier.then(ActionModifier(CompoundButtonAction(onCheckedChange, checked)))
151 } else {
152 modifier
153 }
154 GlanceNode(
155 factory = { EmittableCheckBox(colors) },
156 update = {
157 this.set(checked) { this.checked = it }
158 this.set(text) { this.text = it }
159 this.set(finalModifier) { this.modifier = it }
160 this.set(style) { this.style = it }
161 this.set(colors) { this.colors = it }
162 this.set(maxLines) { this.maxLines = it }
163 }
164 )
165 }
166
167 /** Contains the default values used by [CheckBox]. */
168 object CheckboxDefaults {
169
170 /**
171 * @param checkedColor the [ColorProvider] to use when the check box is checked.
172 * @param uncheckedColor the [ColorProvider] to use when the check box is not checked.
173 * @return [CheckBoxColors] that uses [checkedColor] or [uncheckedColor] depending on the
174 * checked state of the CheckBox.
175 */
176 @Composable
colorsnull177 fun colors(checkedColor: ColorProvider, uncheckedColor: ColorProvider): CheckBoxColors =
178 checkBoxColors(checkedColor, uncheckedColor)
179
180 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
181 fun checkBoxColors(checkedColor: ColorProvider, uncheckedColor: ColorProvider): CheckBoxColors =
182 CheckBoxColorsImpl(
183 createCheckableColorProvider(
184 source = "CheckBoxColors",
185 checked = checkedColor,
186 unchecked = uncheckedColor,
187 )
188 )
189
190 /**
191 * @param checkedColor the [Color] to use when the check box is checked.
192 * @param uncheckedColor the [Color] to use when the check box is not checked.
193 * @return [CheckBoxColors] that uses [checkedColor] or [uncheckedColor] depending on the
194 * checked state of the CheckBox.
195 */
196 @Composable
197 fun colors(checkedColor: Color, uncheckedColor: Color): CheckBoxColors =
198 CheckboxDefaults.colors(
199 checkedColor = FixedColorProvider(checkedColor),
200 uncheckedColor = FixedColorProvider(uncheckedColor)
201 )
202
203 /**
204 * Creates a default [CheckBoxColors].
205 *
206 * @return default [CheckBoxColors].
207 */
208 @Composable
209 fun colors(): CheckBoxColors {
210 val colorProvider =
211 if (GlanceTheme.colors == DynamicThemeColorProviders) {
212 // If using the m3 dynamic color theme, we need to create a color provider from xml
213 // because resource backed ColorStateLists cannot be created programmatically
214 ResourceCheckableColorProvider(R.color.glance_default_check_box)
215 } else {
216 createCheckableColorProvider(
217 source = "CheckBoxColors",
218 checked = GlanceTheme.colors.primary,
219 unchecked = GlanceTheme.colors.onSurface
220 )
221 }
222
223 return CheckBoxColorsImpl(colorProvider)
224 }
225 }
226
227 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
228 class EmittableCheckBox(var colors: CheckBoxColors) : EmittableCheckable() {
229 override var modifier: GlanceModifier = GlanceModifier
230
copynull231 override fun copy(): Emittable =
232 EmittableCheckBox(colors = colors).also {
233 it.modifier = modifier
234 it.checked = checked
235 it.text = text
236 it.style = style
237 it.maxLines = maxLines
238 }
239
toStringnull240 override fun toString(): String =
241 "EmittableCheckBox(" +
242 "modifier=$modifier, " +
243 "checked=$checked, " +
244 "text=$text, " +
245 "style=$style, " +
246 "colors=$colors, " +
247 "maxLines=$maxLines" +
248 ")"
249 }
250