Create a small test widget, I just wanted to learn hoe Jetpack Compose Glance works?

This commit is contained in:
Nacho 2025-07-27 19:48:01 +02:00
parent d46769b0cb
commit 93f568dd8d
7 changed files with 185 additions and 0 deletions

View File

@ -91,4 +91,7 @@ dependencies {
implementation("com.google.android.material:material:1.2.0") implementation("com.google.android.material:material:1.2.0")
implementation(libs.protobuf.javalite) implementation(libs.protobuf.javalite)
implementation("androidx.compose.material:material") implementation("androidx.compose.material:material")
implementation("androidx.glance:glance:1.1.1")
implementation("androidx.glance:glance-appwidget:1.1.1")
implementation("androidx.work:work-runtime-ktx:2.9.0")
} }

View File

@ -27,6 +27,15 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<receiver android:name=".widget.CharacterDisplayWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_info" />
</receiver>
</application> </application>
</manifest> </manifest>

View File

@ -0,0 +1,117 @@
package com.github.nacabaro.vbhelper.widget
import android.content.Context
import android.graphics.Bitmap
import androidx.compose.material3.MaterialTheme
import androidx.glance.GlanceTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.glance.background
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.provideContent
import androidx.glance.layout.Alignment
import androidx.glance.layout.Column
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.size
import androidx.glance.state.PreferencesGlanceStateDefinition
import androidx.glance.text.Text
import androidx.glance.unit.ColorProvider
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
/*
RANT: Why did they have to create the components from Jetpack Compose
with a different API for Glance? Now there are things I use in Jetpack Compose
like the filterQuality, which isn't here.
*/
val CURRENT_IMAGE_KEY = booleanPreferencesKey("current_image")
class CharacterDisplayWidget : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
val application = context.applicationContext as VBHelper
val storageRepository = application.container.db
val currentCharacter = storageRepository
.userCharacterDao()
.getActiveCharacter()
val multiplier = 4
provideContent {
var bitmapIdle1 by remember { mutableStateOf<Bitmap?>(null) }
var bitmapIdle2 by remember { mutableStateOf<Bitmap?>(null) }
var dpSize by remember { mutableStateOf(0.dp) }
if (currentCharacter != null) {
val currentCharacterBitmapIdle1 = BitmapData(
currentCharacter.spriteIdle,
currentCharacter.spriteWidth,
currentCharacter.spriteHeight
)
val currentCharacterBitmapIdle2 = BitmapData(
currentCharacter.spriteIdle2,
currentCharacter.spriteWidth,
currentCharacter.spriteHeight
)
bitmapIdle1 = currentCharacterBitmapIdle1.getBitmap()
bitmapIdle2 = currentCharacterBitmapIdle2.getBitmap()
val density: Float = application.resources.displayMetrics.density
dpSize = (currentCharacterBitmapIdle1.width * multiplier / density).dp
}
WidgetContent(
imageBitmapFrame1 = bitmapIdle1,
imageBitmapFrame2 = bitmapIdle2,
dpSize = dpSize
)
}
}
@Composable
private fun WidgetContent(
imageBitmapFrame1: Bitmap?,
imageBitmapFrame2: Bitmap?,
dpSize: Dp
) {
GlanceTheme {
Column (
horizontalAlignment = Alignment.Horizontal.CenterHorizontally,
verticalAlignment = Alignment.Vertical.CenterVertically,
modifier = GlanceModifier
.fillMaxSize()
.background(ColorProvider(MaterialTheme.colorScheme.background))
) {
if (imageBitmapFrame1 != null) {
Image(
provider = ImageProvider(
bitmap = imageBitmapFrame1
),
contentDescription = "Character",
modifier = GlanceModifier
.size(dpSize)
)
} else {
Text(
text = "No character selected"
)
}
}
}
}
}

View File

@ -0,0 +1,3 @@
package com.github.nacabaro.vbhelper.widget
import androidx.glance.state.PreferencesGlanceStateDefinition

View File

@ -0,0 +1,8 @@
package com.github.nacabaro.vbhelper.widget
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
class CharacterDisplayWidgetReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget = CharacterDisplayWidget()
}

View File

@ -0,0 +1,35 @@
package com.github.nacabaro.vbhelper.widget
import android.content.Context
import androidx.glance.appwidget.GlanceAppWidgetManager
import androidx.glance.appwidget.state.updateAppWidgetState
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
class CharacterDisplayWidgetWorker(
appContext: Context,
workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
// Get all widget IDs for your ImageSwapWidget
val glanceIds = GlanceAppWidgetManager(applicationContext)
.getGlanceIds(CharacterDisplayWidget::class.java)
glanceIds.forEach { glanceId ->
// Update the widget's state (toggle the boolean)
updateAppWidgetState(
context = applicationContext,
glanceId = glanceId
) { prefs ->
val currentImageIsImage1 = prefs[CURRENT_IMAGE_KEY] ?: true
prefs[CURRENT_IMAGE_KEY] = !currentImageIsImage1 // Toggle the boolean
}
// Trigger the widget to redraw itself with the new state
CharacterDisplayWidget().update(applicationContext, glanceId)
}
// Indicate that the work was successful
return Result.success()
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/glance_default_loading_layout"
android:minWidth="40dp"
android:minHeight="20dp"
android:minResizeWidth="20dp"
android:previewImage="@drawable/baseline_image_24"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="300000"
android:widgetCategory="home_screen" />