1 /*
<lambda>null2  * Copyright 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 androidx.glance.appwidget.demos
18 
19 import android.content.Context
20 import androidx.collection.floatListOf
21 import androidx.compose.runtime.Composable
22 import androidx.compose.runtime.collectAsState
23 import androidx.compose.runtime.getValue
24 import androidx.compose.ui.graphics.Color
25 import androidx.compose.ui.unit.dp
26 import androidx.datastore.preferences.core.edit
27 import androidx.datastore.preferences.core.intPreferencesKey
28 import androidx.datastore.preferences.preferencesDataStore
29 import androidx.glance.ColorFilter
30 import androidx.glance.GlanceId
31 import androidx.glance.GlanceModifier
32 import androidx.glance.GlanceTheme
33 import androidx.glance.ImageProvider
34 import androidx.glance.LocalSize
35 import androidx.glance.appwidget.GlanceAppWidget
36 import androidx.glance.appwidget.GlanceAppWidgetReceiver
37 import androidx.glance.appwidget.SizeMode
38 import androidx.glance.appwidget.components.CircleIconButton
39 import androidx.glance.appwidget.components.Scaffold
40 import androidx.glance.appwidget.components.TitleBar
41 import androidx.glance.appwidget.lazy.LazyColumn
42 import androidx.glance.appwidget.provideContent
43 import androidx.glance.background
44 import androidx.glance.layout.Alignment
45 import androidx.glance.layout.fillMaxSize
46 import androidx.glance.layout.fillMaxWidth
47 import androidx.glance.layout.height
48 import androidx.glance.layout.padding
49 import androidx.glance.text.FontWeight
50 import androidx.glance.text.Text
51 import androidx.glance.text.TextAlign
52 import androidx.glance.text.TextStyle
53 import androidx.glance.unit.ColorProvider
54 import kotlinx.coroutines.DelicateCoroutinesApi
55 import kotlinx.coroutines.Dispatchers
56 import kotlinx.coroutines.GlobalScope
57 import kotlinx.coroutines.flow.map
58 import kotlinx.coroutines.launch
59 import kotlinx.coroutines.withContext
60 
61 class BackgroundTintWidgetBroadcastReceiver : GlanceAppWidgetReceiver() {
62     override val glanceAppWidget: GlanceAppWidget
63         get() = BackgroundTintWidget()
64 }
65 
66 /** Demonstrates tinting background drawables with [ColorFilter] and applying opacity with alpha. */
67 class BackgroundTintWidget : GlanceAppWidget() {
68     override val sizeMode: SizeMode
69         get() = SizeMode.Exact
70 
71     @OptIn(DelicateCoroutinesApi::class)
provideGlancenull72     override suspend fun provideGlance(context: Context, id: GlanceId) {
73         val repo = AlphaRepo(context, id)
74         val alphaFlow = withContext(Dispatchers.IO) { repo.load() }
75 
76         provideContent {
77             val alpha by alphaFlow.collectAsState(0.5f)
78 
79             Content(
80                 alpha = alpha,
81                 alphaToggleAction = {
82                     GlobalScope.launch { withContext(Dispatchers.IO) { repo.changeToNextAlpha() } }
83                 }
84             )
85         }
86     }
87 
providePreviewnull88     override suspend fun providePreview(context: Context, widgetCategory: Int) {
89         provideContent { Content(alpha = 0.5f, alphaToggleAction = {}) }
90     }
91 
92     @Composable
Contentnull93     private fun Content(alpha: Float, alphaToggleAction: () -> Unit) {
94         GlanceTheme {
95             Scaffold(titleBar = { DemoTitleBar(alphaToggleAction) }) {
96                 LazyColumn(
97                     modifier = GlanceModifier.fillMaxSize(),
98                     horizontalAlignment = Alignment.CenterHorizontally,
99                 ) {
100                     item {
101                         // Tint a <shape> background
102                         DemoText(
103                             text = "Shape drawable bg",
104                             modifier =
105                                 GlanceModifier.background(
106                                     ImageProvider(R.drawable.shape_btn_demo),
107                                     colorFilter = ColorFilter.tint(GlanceTheme.colors.primary),
108                                 ),
109                         )
110                     }
111                     item {
112                         // tint an AVD background
113                         DemoText(
114                             text = "AVD bg",
115                             modifier =
116                                 GlanceModifier.background(
117                                     ImageProvider(R.drawable.ic_android),
118                                     colorFilter = ColorFilter.tint(ColorProvider(Color.Cyan)),
119                                 ),
120                         )
121                     }
122                     item { DemoText(text = "with alpha = $alpha") }
123                     item {
124                         // Tint a <shape> background and apply alpha
125                         DemoText(
126                             text = "Shape drawable bg",
127                             modifier =
128                                 GlanceModifier.background(
129                                     ImageProvider(R.drawable.shape_btn_demo),
130                                     colorFilter = ColorFilter.tint(GlanceTheme.colors.primary),
131                                     alpha = alpha
132                                 ),
133                         )
134                     }
135                     item {
136                         // tint an AVD background and apply alpha
137                         DemoText(
138                             text = "AVD bg",
139                             modifier =
140                                 GlanceModifier.background(
141                                     ImageProvider(R.drawable.ic_android),
142                                     colorFilter = ColorFilter.tint(ColorProvider(Color.Cyan)),
143                                     alpha = alpha
144                                 ),
145                         )
146                     }
147                 }
148             }
149         }
150     }
151 
152     @Composable
DemoTextnull153     private fun DemoText(text: String, modifier: GlanceModifier = GlanceModifier) {
154         Text(
155             text = text,
156             style = TextStyle(fontWeight = FontWeight.Bold, textAlign = TextAlign.Center),
157             modifier = modifier.padding(top = 20.dp).height(60.dp).fillMaxWidth(),
158         )
159     }
160 
161     @Composable
DemoTitleBarnull162     private fun DemoTitleBar(alphaToggleAction: () -> Unit) {
163         TitleBar(
164             startIcon = ImageProvider(R.drawable.ic_demo_app),
165             title = "",
166             actions = {
167                 CircleIconButton(
168                     imageProvider = ImageProvider(R.drawable.ic_opacity),
169                     backgroundColor = null,
170                     contentColor = GlanceTheme.colors.secondary,
171                     contentDescription = "toggle alpha",
172                     onClick = alphaToggleAction,
173                     key = "${LocalSize.current.width} ${LocalSize.current.height}"
174                 )
175             }
176         )
177     }
178 
179     /** A datastore backed repository that produces alpha value for the demo */
180     private class AlphaRepo(val context: Context, glanceId: GlanceId) {
181         private val preferencesKey = intPreferencesKey("ALPHA_VALUES_$glanceId")
182 
183         /** Mimics a backend load and returns a flow to listen to updates */
loadnull184         suspend fun load() =
185             context.datastore.data.map { preferences ->
186                 AlphaValues[coerceToValidAlphaIndex(preferences[preferencesKey])]
187             }
188 
189         /** Updates the repository to select next alpha */
changeToNextAlphanull190         suspend fun changeToNextAlpha() {
191             context.datastore.edit { preferences ->
192                 val currentIndex = coerceToValidAlphaIndex(preferences[preferencesKey])
193                 val newIndex = (currentIndex + 1) % AlphaValues.size
194                 preferences[preferencesKey] = newIndex
195             }
196         }
197 
198         companion object {
199             private val AlphaValues = floatListOf(0.1f, 0.2f, 0.5f, 0.6f, 0.9f, 1f, 0f)
200             private val Context.datastore by preferencesDataStore("alphaRepo")
201 
coerceToValidAlphaIndexnull202             private fun coerceToValidAlphaIndex(index: Int?) =
203                 index?.coerceIn(0, AlphaValues.size) ?: 0
204         }
205     }
206 }
207