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