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.appwidget.AppWidgetManager
20 import android.content.Context
21 import android.util.Log
22 import android.widget.RemoteViews
23 import androidx.compose.runtime.Composable
24 import androidx.compose.ui.graphics.Color
25 import androidx.compose.ui.unit.dp
26 import androidx.compose.ui.unit.sp
27 import androidx.glance.GlanceId
28 import androidx.glance.GlanceModifier
29 import androidx.glance.LocalSize
30 import androidx.glance.appwidget.GlanceAppWidget
31 import androidx.glance.appwidget.GlanceAppWidgetReceiver
32 import androidx.glance.appwidget.SizeMode
33 import androidx.glance.appwidget.background
34 import androidx.glance.appwidget.provideContent
35 import androidx.glance.layout.Alignment
36 import androidx.glance.layout.Box
37 import androidx.glance.layout.Column
38 import androidx.glance.layout.fillMaxSize
39 import androidx.glance.layout.fillMaxWidth
40 import androidx.glance.layout.padding
41 import androidx.glance.layout.wrapContentHeight
42 import androidx.glance.text.FontWeight
43 import androidx.glance.text.Text
44 import androidx.glance.text.TextAlign
45 import androidx.glance.text.TextStyle
46 import kotlin.math.roundToInt
47 
48 /**
49  * Demonstrates different methods of handling errors. A widget can use either the default Glance
50  * error ui, provide a custom layout, or ignore the error and not make any change to the ui state.
51  */
52 class ErrorUiAppWidget : GlanceAppWidget() {
53     override val sizeMode: SizeMode = SizeMode.Exact
54 
provideGlancenull55     override suspend fun provideGlance(context: Context, id: GlanceId) {
56         val repo = ErrorUiAppWidgetConfigurationRepo(context = context, glanceId = id)
57 
58         provideContent {
59             val errorBehavior = repo.getOnErrorBehavior()
60 
61             Content(errorBehavior)
62         }
63     }
64 
providePreviewnull65     override suspend fun providePreview(context: Context, widgetCategory: Int) {
66         provideContent { Content() }
67     }
68 
69     @Composable
Contentnull70     private fun Content(
71         errorBehavior: OnErrorBehavior = OnErrorBehavior.Default,
72     ) {
73         val size = LocalSize.current
74         Column(
75             modifier =
76                 GlanceModifier.fillMaxSize()
77                     .background(day = Color.LightGray, night = Color.DarkGray)
78                     .padding(8.dp),
79         ) {
80             Text(
81                 "Error UI Demo. Method: $errorBehavior",
82                 modifier = GlanceModifier.fillMaxWidth().wrapContentHeight(),
83                 style =
84                     TextStyle(
85                         fontWeight = FontWeight.Bold,
86                         fontSize = 18.sp,
87                         textAlign = TextAlign.Center
88                     )
89             )
90             Box(
91                 modifier = GlanceModifier.fillMaxWidth().defaultWeight(),
92                 contentAlignment = Alignment.Center
93             ) {
94                 Text(
95                     "Error UI triggers if width or height reach 400 dp in any orientation.",
96                     style = TextStyle(fontWeight = FontWeight.Medium, fontSize = 15.sp)
97                 )
98                 check(size.width < 400.dp && size.height < 400.dp) { "Too large now!" }
99             }
100             Text(
101                 " Current size: ${size.width.value.roundToInt()} dp x " +
102                     "${size.height.value.roundToInt()} dp"
103             )
104         }
105     }
106 
onCompositionErrornull107     override fun onCompositionError(
108         context: Context,
109         glanceId: GlanceId,
110         appWidgetId: Int,
111         throwable: Throwable
112     ) {
113         fun showCustomError() {
114             // Optionally, a custom error view can also be created.
115             val rv = RemoteViews(context.packageName, R.layout.error_ui_app_widget_on_error_layout)
116             rv.setTextViewText(
117                 R.id.error_text_view,
118                 "Error was thrown. \nThis is a custom view \nError Message: `${throwable.message}`"
119             )
120             AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, rv)
121         }
122 
123         val repo = ErrorUiAppWidgetConfigurationRepo(context = context, glanceId = glanceId)
124 
125         when (repo.getOnErrorBehavior()) {
126             OnErrorBehavior.Default ->
127                 super.onCompositionError(context, glanceId, appWidgetId, throwable)
128             OnErrorBehavior.Custom -> showCustomError()
129             OnErrorBehavior.Ignore -> Unit // do nothing beyond the logging
130         }
131 
132         // onCreateErrorLayout is a good place to perform logging.
133         Log.w("Error App", "onCreateErrorLayout called.")
134     }
135 }
136 
137 class ErrorUiAppWidgetReceiver : GlanceAppWidgetReceiver() {
138     override val glanceAppWidget: GlanceAppWidget = ErrorUiAppWidget()
139 }
140