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.app.Activity
20 import android.appwidget.AppWidgetManager
21 import android.content.Context
22 import android.content.Intent
23 import android.content.SharedPreferences
24 import android.os.Bundle
25 import androidx.activity.ComponentActivity
26 import androidx.activity.compose.setContent
27 import androidx.compose.foundation.layout.Box
28 import androidx.compose.foundation.layout.Column
29 import androidx.compose.foundation.layout.Row
30 import androidx.compose.foundation.layout.Spacer
31 import androidx.compose.foundation.layout.padding
32 import androidx.compose.foundation.layout.size
33 import androidx.compose.material3.Button
34 import androidx.compose.material3.RadioButton
35 import androidx.compose.material3.Text
36 import androidx.compose.runtime.Composable
37 import androidx.compose.runtime.LaunchedEffect
38 import androidx.compose.runtime.MutableState
39 import androidx.compose.runtime.mutableStateOf
40 import androidx.compose.runtime.remember
41 import androidx.compose.ui.Modifier
42 import androidx.compose.ui.unit.dp
43 import androidx.core.content.edit
44 import androidx.glance.GlanceId
45 import androidx.glance.appwidget.GlanceAppWidgetManager
46 import kotlinx.coroutines.DelicateCoroutinesApi
47 import kotlinx.coroutines.GlobalScope
48 import kotlinx.coroutines.launch
49
50 enum class OnErrorBehavior {
51 Default,
52 Custom,
53 Ignore
54 }
55
56 class ErrorUiAppWidgetConfigurationActivity : ComponentActivity() {
57
58 private var repo: ErrorUiAppWidgetConfigurationRepo? = null
59
onCreatenull60 override fun onCreate(savedInstanceState: Bundle?) {
61 super.onCreate(savedInstanceState)
62
63 val appWidgetId: Int =
64 intent
65 ?.extras
66 ?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
67 ?: AppWidgetManager.INVALID_APPWIDGET_ID
68
69 val resultValue = Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
70 setResult(Activity.RESULT_CANCELED, resultValue)
71
72 if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
73 finish()
74 return
75 }
76
77 val glanceId: GlanceId = GlanceAppWidgetManager(this).getGlanceIdBy(appWidgetId)
78 val repo = ErrorUiAppWidgetConfigurationRepo(context = this, glanceId = glanceId)
79 this.repo = repo
80
81 setContent { ConfigurationUi(repo, onSaveAndFinish = { saveAndFinish(appWidgetId) }) }
82 }
83
84 @OptIn(DelicateCoroutinesApi::class)
saveAndFinishnull85 private fun saveAndFinish(appWidgetId: Int) {
86 val resultValue = Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
87 setResult(Activity.RESULT_OK, resultValue)
88 finish()
89
90 GlobalScope.launch {
91 val context: Context = this@ErrorUiAppWidgetConfigurationActivity
92 val glanceId: GlanceId = GlanceAppWidgetManager(context).getGlanceIdBy(appWidgetId)
93 ErrorUiAppWidget().update(context = context, id = glanceId)
94 }
95 }
96
onStartnull97 override fun onStart() {
98 super.onStart()
99 this.repo = repo
100 }
101
onStopnull102 override fun onStop() {
103 super.onStop()
104 repo?.unregisterObserver()
105 }
106
onDestroynull107 override fun onDestroy() {
108 super.onDestroy()
109 repo = null
110 }
111 }
112
113 class ErrorUiAppWidgetConfigurationRepo(val context: Context, glanceId: GlanceId) {
114 private val prefsFile = "androidx.glance.appwidget.demos.ErrorUiAppWidgetConfigurationRepo"
115 private val prefsKey = "onErrorBehavior-$glanceId"
116 private val sharedPreferences = context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE)
117
118 private var listener: SharedPreferences.OnSharedPreferenceChangeListener? = null
119
updatenull120 fun update(behavior: OnErrorBehavior) {
121 // todo
122 sharedPreferences.edit { putInt(prefsKey, behavior.ordinal) }
123 }
124
getOnErrorBehaviornull125 fun getOnErrorBehavior(): OnErrorBehavior = getOnErrorBehavior(sharedPreferences)
126
127 private fun getOnErrorBehavior(prefs: SharedPreferences): OnErrorBehavior {
128 val ordinal = prefs.getInt(prefsKey, OnErrorBehavior.Default.ordinal)
129 return OnErrorBehavior.values()[ordinal]
130 }
131
observeOnErrorBehaviornull132 fun observeOnErrorBehavior(onChange: (OnErrorBehavior) -> Unit) {
133 val listener =
134 SharedPreferences.OnSharedPreferenceChangeListener { prefs, key ->
135 if (key != prefsKey) {
136 return@OnSharedPreferenceChangeListener
137 } else {
138 onChange(getOnErrorBehavior(prefs))
139 }
140 }
141
142 this.listener = listener
143 sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
144 }
145
unregisterObservernull146 fun unregisterObserver() {
147 listener?.let { sharedPreferences.unregisterOnSharedPreferenceChangeListener(it) }
148 }
149 }
150
151 @Composable
ConfigurationUinull152 private fun ConfigurationUi(repo: ErrorUiAppWidgetConfigurationRepo, onSaveAndFinish: () -> Unit) {
153 val selected: MutableState<OnErrorBehavior> = remember {
154 mutableStateOf(repo.getOnErrorBehavior())
155 }
156 LaunchedEffect(repo) { repo.observeOnErrorBehavior { newState -> selected.value = newState } }
157
158 Box(Modifier.padding(16.dp)) {
159 Column(Modifier.padding(24.dp)) {
160 Text(text = "OnError behavior")
161 Spacer(Modifier.size(16.dp))
162 LabeledRadioButton(
163 "Default Behavior",
164 myBehavior = OnErrorBehavior.Default,
165 selectedBehavior = selected.value,
166 onClick = repo::update
167 )
168 LabeledRadioButton(
169 "Custom Error UI",
170 myBehavior = OnErrorBehavior.Custom,
171 selectedBehavior = selected.value,
172 onClick = repo::update
173 )
174 LabeledRadioButton(
175 "Ignore Error, No UI Change",
176 myBehavior = OnErrorBehavior.Ignore,
177 selectedBehavior = selected.value,
178 onClick = repo::update
179 )
180
181 Spacer(Modifier.size(32.dp))
182 Button(onClick = onSaveAndFinish) { Text("Done") }
183 }
184 }
185 }
186
187 @Composable
LabeledRadioButtonnull188 private fun LabeledRadioButton(
189 text: String,
190 myBehavior: OnErrorBehavior,
191 selectedBehavior: OnErrorBehavior,
192 onClick: (OnErrorBehavior) -> Unit
193 ) {
194 Row {
195 RadioButton(selected = selectedBehavior == myBehavior, onClick = { onClick(myBehavior) })
196 Spacer(Modifier.size(8.dp))
197 Text(text)
198 }
199 }
200