1 /*
<lambda>null2  * 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.demos
18 
19 import android.content.Context
20 import android.content.Intent
21 import android.os.Bundle
22 import android.util.Log
23 import androidx.activity.ComponentActivity
24 import androidx.activity.compose.setContent
25 import androidx.compose.material.Text as ComposeText
26 import androidx.compose.runtime.Composable
27 import androidx.compose.runtime.getValue
28 import androidx.compose.runtime.mutableStateOf
29 import androidx.compose.runtime.remember
30 import androidx.compose.runtime.setValue
31 import androidx.compose.ui.graphics.Color
32 import androidx.compose.ui.unit.DpSize
33 import androidx.compose.ui.unit.dp
34 import androidx.compose.ui.unit.sp
35 import androidx.glance.Button
36 import androidx.glance.GlanceId
37 import androidx.glance.GlanceModifier
38 import androidx.glance.Image
39 import androidx.glance.ImageProvider
40 import androidx.glance.LocalContext
41 import androidx.glance.LocalSize
42 import androidx.glance.action.clickable
43 import androidx.glance.appwidget.CheckBox
44 import androidx.glance.appwidget.GlanceAppWidget
45 import androidx.glance.appwidget.GlanceAppWidgetReceiver
46 import androidx.glance.appwidget.SizeMode
47 import androidx.glance.appwidget.action.actionStartActivity
48 import androidx.glance.appwidget.lazy.GridCells
49 import androidx.glance.appwidget.lazy.LazyColumn
50 import androidx.glance.appwidget.lazy.itemsIndexed
51 import androidx.glance.appwidget.provideContent
52 import androidx.glance.background
53 import androidx.glance.layout.Alignment
54 import androidx.glance.layout.Column
55 import androidx.glance.layout.Row
56 import androidx.glance.layout.Spacer
57 import androidx.glance.layout.fillMaxHeight
58 import androidx.glance.layout.fillMaxSize
59 import androidx.glance.layout.fillMaxWidth
60 import androidx.glance.layout.padding
61 import androidx.glance.layout.size
62 import androidx.glance.layout.width
63 import androidx.glance.semantics.contentDescription
64 import androidx.glance.semantics.semantics
65 import androidx.glance.text.FontWeight
66 import androidx.glance.text.Text
67 import androidx.glance.text.TextAlign
68 import androidx.glance.text.TextDecoration
69 import androidx.glance.text.TextStyle
70 
71 /** Sample AppWidget that showcase scrollable layouts using the LazyColumn */
72 class ScrollableAppWidget : GlanceAppWidget() {
73 
74     companion object {
75         private const val TAG = "ScrollableAppWidget"
76 
77         private val singleColumn = DpSize(100.dp, 48.dp)
78         private val doubleColumn = DpSize(200.dp, 48.dp)
79         private val tripleColumn = DpSize(300.dp, 48.dp)
80     }
81 
82     override val sizeMode: SizeMode =
83         SizeMode.Responsive(setOf(singleColumn, doubleColumn, tripleColumn))
84 
85     override suspend fun provideGlance(context: Context, id: GlanceId) = provideContent {
86         Content()
87     }
88 
89     override suspend fun providePreview(context: Context, widgetCategory: Int) = provideContent {
90         Content()
91     }
92 
93     @Composable
94     private fun Content() {
95         Column(
96             modifier = GlanceModifier.fillMaxSize().background(R.color.default_widget_background)
97         ) {
98             val localSize = LocalSize.current
99             Text(
100                 text = "Fix header, LocalSize: ${localSize.width}x${localSize.height}",
101                 modifier =
102                     GlanceModifier.fillMaxWidth().padding(16.dp).background(Color(0x0a000000))
103             )
104             val width = localSize.width
105             when {
106                 width <= singleColumn.width -> ScrollColumn(GlanceModifier.fillMaxSize())
107                 width <= doubleColumn.width ->
108                     Row {
109                         val modifier = GlanceModifier.fillMaxHeight().defaultWeight()
110                         ScrollColumn(modifier)
111                         ScrollColumn(modifier)
112                     }
113                 else -> SampleGrid(cells = GridCells.Fixed(3))
114             }
115         }
116     }
117 
118     @Composable
119     private fun ScrollColumn(modifier: GlanceModifier) {
120         val localSize = LocalSize.current
121         LazyColumn(modifier) {
122             item { SectionHeading(title = "LocalSize", description = "inside lazyColumn") }
123             item {
124                 Text(
125                     text = "${localSize.width}x${localSize.height}",
126                     modifier = GlanceModifier.padding(10.dp)
127                 )
128             }
129             item {
130                 SectionHeading(
131                     title = "Activities",
132                     description = "Click the buttons to open activities"
133                 )
134             }
135 
136             itemsIndexed(
137                 listOf(
138                     GlanceAppWidgetDemoActivity::class.java,
139                     ListClickDestinationActivity::class.java
140                 )
141             ) { index, activityClass ->
142                 Row(
143                     GlanceModifier.fillMaxWidth(),
144                     horizontalAlignment = Alignment.Horizontal.CenterHorizontally
145                 ) {
146                     Button(
147                         text = "Activity ${index + 1}",
148                         onClick =
149                             actionStartActivity(
150                                 Intent(LocalContext.current, activityClass).apply {
151                                     // Move this activity to the top of the stack, so it's obvious
152                                     // in this
153                                     // demo that the button has launched this activity. Otherwise,
154                                     // if
155                                     // another activity was opened on top, the target activity might
156                                     // be
157                                     // buried in the stack.
158                                     flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
159                                 }
160                             )
161                     )
162                 }
163             }
164 
165             item {
166                 SectionHeading(
167                     title = "Callbacks",
168                     description = "Click the list items to invoke a callback"
169                 )
170             }
171 
172             items(10) { index: Int ->
173                 Text(
174                     text = "Item $index",
175                     modifier =
176                         GlanceModifier.fillMaxWidth()
177                             .padding(horizontal = 16.dp, vertical = 8.dp)
178                             .clickable { Log.i(TAG, "Click from list item $index") }
179                 )
180             }
181             item {
182                 // A11y services read out the contents as description of the row and call out
183                 // "double tap to activate" as it is clickable.
184                 Row(
185                     verticalAlignment = Alignment.CenterVertically,
186                     modifier =
187                         GlanceModifier.padding(horizontal = 16.dp, vertical = 8.dp).clickable {
188                             Log.i(TAG, "Click from an item row")
189                         },
190                 ) {
191                     Image(
192                         provider = ImageProvider(R.drawable.compose),
193                         contentDescription = "Compose logo",
194                         modifier = GlanceModifier.size(20.dp)
195                     )
196                     Spacer(modifier = GlanceModifier.width(5.dp))
197                     Text(
198                         text = "Item with click on parent row",
199                         modifier = GlanceModifier.fillMaxWidth()
200                     )
201                 }
202             }
203             item {
204                 // A11y services read out the semantics description of the row and call out
205                 // "double tap to activate" as it is clickable.
206                 Row(
207                     modifier =
208                         GlanceModifier.padding(horizontal = 16.dp, vertical = 8.dp)
209                             .clickable { Log.i(TAG, "Click from an item row with semantics set") }
210                             .semantics {
211                                 contentDescription = "A row with semantics description set"
212                             }
213                 ) {
214                     Image(
215                         provider = ImageProvider(R.drawable.compose),
216                         contentDescription = "Compose logo",
217                         modifier = GlanceModifier.size(20.dp)
218                     )
219                     Spacer(modifier = GlanceModifier.width(5.dp))
220                     Text(
221                         text = "Item with click on parent row with contentDescription set",
222                         modifier = GlanceModifier.fillMaxWidth()
223                     )
224                 }
225             }
226             item { SectionHeading(title = "Compound buttons", description = "Check buttons below") }
227             item {
228                 var checked by remember { mutableStateOf(false) }
229                 CheckBox(
230                     checked = checked,
231                     onCheckedChange = { checked = !checked },
232                     text = "Checkbox"
233                 )
234             }
235         }
236     }
237 
238     @Composable
239     private fun SectionHeading(title: String, description: String) {
240         Column {
241             Text(
242                 modifier = GlanceModifier.fillMaxWidth().padding(top = 8.dp),
243                 text = title,
244                 style =
245                     TextStyle(
246                         fontSize = 16.sp,
247                         textDecoration = TextDecoration.Underline,
248                         fontWeight = FontWeight.Bold,
249                         textAlign = TextAlign.Center
250                     )
251             )
252             Text(
253                 text = description,
254                 style = TextStyle(fontSize = 12.sp),
255                 modifier = GlanceModifier.fillMaxWidth().padding(16.dp)
256             )
257         }
258     }
259 }
260 
261 /** Activity opened by clicking a list adapter item in [ScrollableAppWidget]. */
262 class ListClickDestinationActivity : ComponentActivity() {
onCreatenull263     override fun onCreate(savedInstanceState: Bundle?) {
264         super.onCreate(savedInstanceState)
265         setContent { ComposeText("Activity started from lazy list adapter item click.") }
266     }
267 }
268 
269 class ScrollableAppWidgetReceiver : GlanceAppWidgetReceiver() {
270     override val glanceAppWidget: GlanceAppWidget = ScrollableAppWidget()
271 }
272