1 /* 2 * Copyright 2022 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.preview 18 19 import android.appwidget.AppWidgetHostView 20 import android.content.Context 21 import android.util.AttributeSet 22 import androidx.compose.runtime.Composable 23 import androidx.compose.runtime.currentComposer 24 import androidx.compose.ui.unit.Dp 25 import androidx.compose.ui.unit.DpSize 26 import androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi 27 import androidx.glance.appwidget.GlanceRemoteViews 28 import androidx.glance.appwidget.preview.ComposableInvoker.invokeComposable 29 import kotlinx.coroutines.runBlocking 30 31 private const val TOOLS_NS_URI = "http://schemas.android.com/tools" 32 private const val ANDROID_NS_URI = "http://schemas.android.com/apk/res/android" 33 34 /** 35 * View adapter that renders a glance `@Composable`. The `@Composable` is found by reading the 36 * `tools:composableName` attribute that contains the FQN of the function. 37 */ 38 internal class GlanceAppWidgetViewAdapter : AppWidgetHostView { 39 40 constructor(context: Context, attrs: AttributeSet) : super(context) { 41 init(attrs) 42 } 43 44 constructor( 45 context: Context, 46 attrs: AttributeSet, 47 @Suppress("UNUSED_PARAMETER") defStyleAttr: Int 48 ) : super(context) { 49 init(attrs) 50 } 51 52 @OptIn(ExperimentalGlanceRemoteViewsApi::class) initnull53 internal fun init( 54 className: String, 55 methodName: String, 56 size: DpSize, 57 ) { 58 val content = 59 @Composable { 60 val composer = currentComposer 61 invokeComposable(className, methodName, composer) 62 } 63 64 val remoteViews = runBlocking { 65 GlanceRemoteViews() 66 .compose(context = context, size = size, content = content) 67 .remoteViews 68 } 69 val view = remoteViews.apply(context, this) 70 addView(view) 71 } 72 initnull73 private fun init(attrs: AttributeSet) { 74 val composableName = attrs.getAttributeValue(TOOLS_NS_URI, "composableName") ?: return 75 val className = composableName.substringBeforeLast('.') 76 val methodName = composableName.substringAfterLast('.') 77 78 val width = 79 attrs 80 .getAttributeValue(ANDROID_NS_URI, "layout_width") 81 ?.removeSuffix("dp") 82 ?.toFloatOrNull() 83 val height = 84 attrs 85 .getAttributeValue(ANDROID_NS_URI, "layout_height") 86 ?.removeSuffix("dp") 87 ?.toFloatOrNull() 88 var size = DpSize.Unspecified 89 if (width != null && height != null) size = DpSize(Dp(width), Dp(height)) 90 91 init(className, methodName, size) 92 } 93 } 94