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
18 
19 import androidx.annotation.RestrictTo
20 import androidx.compose.runtime.Composable
21 import androidx.glance.action.Action
22 import androidx.glance.action.action
23 import androidx.glance.action.clickable
24 import androidx.glance.text.EmittableText
25 import androidx.glance.text.TextStyle
26 import androidx.glance.unit.ColorProvider
27 
28 /**
29  * Adds a button view to the glance view.
30  *
31  * @param text The text that this button will show.
32  * @param onClick The action to be performed when this button is clicked.
33  * @param modifier The modifier to be applied to this button.
34  * @param enabled If false, the button will not be clickable.
35  * @param style The style to be applied to the text in this button.
36  * @param colors The colors to use for the background and content of the button.
37  * @param maxLines An optional maximum number of lines for the text to span, wrapping if necessary.
38  *   If the text exceeds the given number of lines, it will be truncated.
39  */
40 @Composable
Buttonnull41 fun Button(
42     text: String,
43     onClick: Action,
44     modifier: GlanceModifier = GlanceModifier,
45     enabled: Boolean = true,
46     style: TextStyle? = null,
47     colors: ButtonColors = ButtonDefaults.buttonColors(),
48     maxLines: Int = Int.MAX_VALUE,
49 ) = ButtonElement(text, onClick, modifier, enabled, style, colors, maxLines)
50 
51 /**
52  * Adds a button view to the glance view.
53  *
54  * @param text The text that this button will show.
55  * @param onClick The action to be performed when this button is clicked.
56  * @param modifier The modifier to be applied to this button.
57  * @param enabled If false, the button will not be clickable.
58  * @param style The style to be applied to the text in this button.
59  * @param colors The colors to use for the background and content of the button.
60  * @param maxLines An optional maximum number of lines for the text to span, wrapping if necessary.
61  *   If the text exceeds the given number of lines, it will be truncated.
62  */
63 @Composable
64 fun Button(
65     text: String,
66     onClick: () -> Unit,
67     modifier: GlanceModifier = GlanceModifier,
68     enabled: Boolean = true,
69     style: TextStyle? = null,
70     colors: ButtonColors = ButtonDefaults.buttonColors(),
71     maxLines: Int = Int.MAX_VALUE,
72 ) = ButtonElement(text, action(block = onClick), modifier, enabled, style, colors, maxLines)
73 
74 /**
75  * Adds a button view to the glance view.
76  *
77  * @param text The text that this button will show.
78  * @param onClick The action to be performed when this button is clicked.
79  * @param modifier The modifier to be applied to this button.
80  * @param enabled If false, the button will not be clickable.
81  * @param style The style to be applied to the text in this button.
82  * @param colors The colors to use for the background and content of the button.
83  * @param maxLines An optional maximum number of lines for the text to span, wrapping if necessary.
84  *   If the text exceeds the given number of lines, it will be truncated.
85  * @param key A stable and unique key that identifies the action for this button. This ensures that
86  *   the correct action is triggered, especially in cases of items that change order. If not
87  *   provided we use the key that is automatically generated by the Compose runtime, which is unique
88  *   for every exact code location in the composition tree.
89  */
90 @ExperimentalGlanceApi
91 @Composable
92 fun Button(
93     text: String,
94     onClick: () -> Unit,
95     modifier: GlanceModifier = GlanceModifier,
96     enabled: Boolean = true,
97     style: TextStyle? = null,
98     colors: ButtonColors = ButtonDefaults.buttonColors(),
99     maxLines: Int = Int.MAX_VALUE,
100     key: String? = null
101 ) = ButtonElement(text, action(key, onClick), modifier, enabled, style, colors, maxLines)
102 
103 @Composable
104 internal fun ButtonElement(
105     text: String,
106     onClick: Action,
107     modifier: GlanceModifier = GlanceModifier,
108     enabled: Boolean = true,
109     style: TextStyle? = null,
110     colors: ButtonColors = ButtonDefaults.buttonColors(),
111     maxLines: Int = Int.MAX_VALUE,
112 ) {
113     var finalModifier = if (enabled) modifier.clickable(onClick) else modifier
114     finalModifier = finalModifier.background(colors.backgroundColor)
115     val finalStyle =
116         style?.copy(color = colors.contentColor) ?: TextStyle(color = colors.contentColor)
117 
118     GlanceNode(
119         factory = ::EmittableButton,
120         update = {
121             this.set(text) { this.text = it }
122             this.set(finalModifier) { this.modifier = it }
123             this.set(finalStyle) { this.style = it }
124             this.set(colors) { this.colors = it }
125             this.set(enabled) { this.enabled = it }
126             this.set(maxLines) { this.maxLines = it }
127         }
128     )
129 }
130 
131 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
132 class EmittableButton : EmittableWithText() {
133     override var modifier: GlanceModifier = GlanceModifier
134     var colors: ButtonColors? = null
135     var enabled: Boolean = true
136 
copynull137     override fun copy(): Emittable =
138         EmittableButton().also {
139             it.modifier = modifier
140             it.text = text
141             it.style = style
142             it.colors = colors
143             it.enabled = enabled
144             it.maxLines = maxLines
145         }
146 
toStringnull147     override fun toString(): String =
148         "EmittableButton('$text', enabled=$enabled, style=$style, " +
149             "colors=$colors modifier=$modifier, maxLines=$maxLines)"
150 }
151 
152 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
153 fun EmittableButton.toEmittableText() =
154     EmittableText().also {
155         it.modifier = modifier
156         it.text = text
157         it.style = style
158         it.maxLines = maxLines
159     }
160 
161 /** Represents the colors used to style a button, prefer this to using the modifier. */
162 class ButtonColors
163 internal constructor(val backgroundColor: ColorProvider, val contentColor: ColorProvider) {
equalsnull164     override fun equals(other: Any?): Boolean {
165         if (this === other) return true
166         if (javaClass != other?.javaClass) return false
167 
168         other as ButtonColors
169 
170         if (backgroundColor != other.backgroundColor) return false
171         if (contentColor != other.contentColor) return false
172 
173         return true
174     }
175 
hashCodenull176     override fun hashCode(): Int {
177         var result = backgroundColor.hashCode()
178         result = 31 * result + contentColor.hashCode()
179         return result
180     }
181 }
182 
183 /** Contains the default values used by [Button]. */
184 object ButtonDefaults {
185     @Composable
186     /**
187      * Creates a [ButtonColors] that represents the default background and content colors used in a
188      * [Button].
189      *
190      * @param backgroundColor the background color of this [Button]
191      * @param contentColor the content color of this [Button]
192      */
buttonColorsnull193     fun buttonColors(
194         backgroundColor: ColorProvider = GlanceTheme.colors.primary,
195         contentColor: ColorProvider = GlanceTheme.colors.onPrimary
196     ) = ButtonColors(backgroundColor = backgroundColor, contentColor = contentColor)
197 }
198