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.demos
18 
19 import android.content.Context
20 import android.os.Handler
21 import android.widget.Toast
22 import androidx.compose.runtime.Composable
23 import androidx.compose.ui.graphics.Color
24 import androidx.compose.ui.unit.DpSize
25 import androidx.compose.ui.unit.dp
26 import androidx.glance.Button
27 import androidx.glance.ButtonDefaults
28 import androidx.glance.GlanceId
29 import androidx.glance.GlanceModifier
30 import androidx.glance.LocalContext
31 import androidx.glance.LocalSize
32 import androidx.glance.action.ActionParameters
33 import androidx.glance.appwidget.GlanceAppWidget
34 import androidx.glance.appwidget.GlanceAppWidgetReceiver
35 import androidx.glance.appwidget.SizeMode
36 import androidx.glance.appwidget.provideContent
37 import androidx.glance.background
38 import androidx.glance.layout.Alignment
39 import androidx.glance.layout.Box
40 import androidx.glance.layout.Column
41 import androidx.glance.layout.Row
42 import androidx.glance.layout.fillMaxSize
43 import androidx.glance.layout.padding
44 import androidx.glance.layout.size
45 import androidx.glance.text.TextAlign
46 import androidx.glance.text.TextStyle
47 import androidx.glance.unit.ColorProvider
48 
49 /**
50  * Sample AppWidget that showcase the Responsive SizeMode changing its content to Row, Column or Box
51  * based on the available space. In addition to shows how alignment and default weight works on
52  * these components.
53  */
54 class ResponsiveAppWidget : GlanceAppWidget() {
55 
56     companion object {
57         private val SMALL_BOX = DpSize(90.dp, 90.dp)
58         private val BIG_BOX = DpSize(180.dp, 180.dp)
59         private val VERY_BIG_BOX = DpSize(300.dp, 300.dp)
60         private val ROW = DpSize(180.dp, 48.dp)
61         private val LARGE_ROW = DpSize(300.dp, 48.dp)
62         private val COLUMN = DpSize(48.dp, 180.dp)
63         private val LARGE_COLUMN = DpSize(48.dp, 300.dp)
64     }
65 
66     override val sizeMode =
67         SizeMode.Responsive(setOf(SMALL_BOX, BIG_BOX, ROW, LARGE_ROW, COLUMN, LARGE_COLUMN))
68 
<lambda>null69     override suspend fun provideGlance(context: Context, id: GlanceId) = provideContent {
70         Content()
71     }
72 
73     override val previewSizeMode =
74         SizeMode.Responsive(setOf(SMALL_BOX, BIG_BOX, ROW, LARGE_ROW, COLUMN, LARGE_COLUMN))
75 
<lambda>null76     override suspend fun providePreview(context: Context, widgetCategory: Int) = provideContent {
77         Content()
78     }
79 
80     @Composable
Contentnull81     private fun Content() {
82         // Content will be called for each of the provided sizes
83         when (LocalSize.current) {
84             COLUMN -> ResponsiveColumn(numItems = 3)
85             ROW -> ResponsiveRow(numItems = 3)
86             LARGE_COLUMN -> ResponsiveColumn(numItems = 5)
87             LARGE_ROW -> ResponsiveRow(numItems = 5)
88             SMALL_BOX -> ResponsiveBox(numItems = 1)
89             BIG_BOX -> ResponsiveBox(numItems = 3)
90             VERY_BIG_BOX -> ResponsiveBox(numItems = 5)
91             else -> throw IllegalArgumentException("Invalid size not matching the provided ones")
92         }
93     }
94 }
95 
96 private val ItemClickedKey = ActionParameters.Key<String>("name")
97 
98 private val parentModifier =
99     GlanceModifier.fillMaxSize().padding(8.dp).background(R.color.default_widget_background)
100 
101 private val columnColors = listOf(Color(0xff70D689), Color(0xffB2E5BF))
102 private val rowColors = listOf(Color(0xff5087EF), Color(0xffA2BDF2))
103 private val boxColors = listOf(Color(0xffF7A998), Color(0xffFA5F3D))
104 
105 /** Displays a column with three items that share evenly the available space */
106 @Composable
ResponsiveColumnnull107 private fun ResponsiveColumn(numItems: Int) {
108     Column(parentModifier) {
109         val modifier = GlanceModifier.fillMaxSize().padding(4.dp).defaultWeight()
110         (1..numItems).forEach {
111             val color = columnColors[(it - 1) % columnColors.size]
112             ContentItem("$it", color, modifier)
113         }
114     }
115 }
116 
117 /** Displays a row with three items that share evenly the available space */
118 @Composable
ResponsiveRownull119 private fun ResponsiveRow(numItems: Int) {
120     Row(parentModifier) {
121         val modifier = GlanceModifier.fillMaxSize().padding(4.dp).defaultWeight()
122         (1..numItems).forEach {
123             val color = rowColors[(it - 1) % rowColors.size]
124             ContentItem("$it", color, modifier)
125         }
126     }
127 }
128 
129 /** Displays a Box with three items on top of each other */
130 @Composable
ResponsiveBoxnull131 private fun ResponsiveBox(numItems: Int) {
132     val size = LocalSize.current
133     Box(modifier = parentModifier, contentAlignment = Alignment.Center) {
134         (1..numItems).forEach {
135             val index = numItems - it + 1
136             val color = boxColors[(index - 1) % boxColors.size]
137             val boxSize = (size.width * index) / numItems
138             ContentItem(
139                 "$index",
140                 color,
141                 GlanceModifier.size(boxSize),
142                 textStyle = TextStyle(textAlign = TextAlign.End).takeIf { numItems != 1 }
143             )
144         }
145     }
146 }
147 
148 @Composable
ContentItemnull149 private fun ContentItem(
150     text: String,
151     color: Color,
152     modifier: GlanceModifier,
153     textStyle: TextStyle? = null
154 ) {
155     Box(modifier = modifier) {
156         val context = LocalContext.current
157         Button(
158             text = text,
159             modifier = GlanceModifier.fillMaxSize().padding(8.dp).background(color),
160             colors =
161                 ButtonDefaults.buttonColors(
162                     backgroundColor = ColorProvider(color),
163                     contentColor = ColorProvider(Color.White)
164                 ),
165             style = textStyle ?: TextStyle(textAlign = TextAlign.Center),
166             onClick = {
167                 Handler(context.mainLooper).post {
168                     Toast.makeText(context, "Item clicked: $text", Toast.LENGTH_SHORT).show()
169                 }
170             }
171         )
172     }
173 }
174 
175 class ResponsiveAppWidgetReceiver : GlanceAppWidgetReceiver() {
176     override val glanceAppWidget: GlanceAppWidget = ResponsiveAppWidget()
177 }
178