finish minor refactoring and add some polish :)

This commit is contained in:
bwees 2025-07-07 15:24:23 -05:00
parent 403b5dd930
commit 9d19c5e3f3
No known key found for this signature in database
13 changed files with 155 additions and 63 deletions

View File

@ -146,7 +146,8 @@
<!-- Widgets -->
<receiver
android:name=".widget.RandomReceiver"
android:exported="true">
android:exported="true"
android:label="@string/random_widget_title">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
@ -157,7 +158,8 @@
<receiver
android:name=".widget.MemoryReceiver"
android:exported="true">
android:exported="true"
android:label="@string/memory_widget_title">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>

View File

@ -194,7 +194,8 @@ class ImageDownloadWorker(
val memory = memories.random()
asset = memory.assets.random()
subtitle = "${today.year - memory.data.year} years ago"
val yearDiff = today.year - memory.data.year
subtitle = "$yearDiff ${if (yearDiff == 1) "year" else "years"} ago"
} else {
val filters = SearchFilters(AssetType.IMAGE, size=1)
asset = api.fetchSearchResults(filters).first()

View File

@ -1,7 +1,6 @@
package app.alextran.immich.widget
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.google.gson.Gson
@ -13,11 +12,8 @@ import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLEncoder
import java.text.SimpleDateFormat
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.*
class ImmichAPI(cfg: ServerConfig) {

View File

@ -1,10 +1,16 @@
package app.alextran.immich.widget
import HomeWidgetGlanceWidgetReceiver
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import es.antonborri.home_widget.HomeWidgetPlugin
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MemoryReceiver : HomeWidgetGlanceWidgetReceiver<PhotoWidget>() {
class MemoryReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget = PhotoWidget()
override fun onUpdate(
@ -18,5 +24,23 @@ class MemoryReceiver : HomeWidgetGlanceWidgetReceiver<PhotoWidget>() {
ImageDownloadWorker.enqueuePeriodic(context, widgetID, WidgetType.MEMORIES)
}
}
override fun onReceive(context: Context, intent: Intent) {
val fromMainApp = intent.getBooleanExtra(HomeWidgetPlugin.TRIGGERED_FROM_HOME_WIDGET, false)
// Launch coroutine to setup a single shot if the app requested the update
if (fromMainApp) {
CoroutineScope(Dispatchers.Default).launch {
val provider = ComponentName(context, MemoryReceiver::class.java)
val glanceIds = AppWidgetManager.getInstance(context).getAppWidgetIds(provider)
glanceIds.forEach { widgetID ->
ImageDownloadWorker.singleShot(context, widgetID, WidgetType.MEMORIES)
}
}
}
super.onReceive(context, intent)
}
}

View File

@ -68,6 +68,7 @@ val kDeeplinkURL = stringPreferencesKey("deeplink")
const val kWorkerWidgetType = "widgetType"
const val kWorkerWidgetID = "widgetId"
const val kTriggeredFromApp = "triggeredFromApp"
fun imageFilename(id: String): String {
return "widget_image_$id.jpg"

View File

@ -3,6 +3,9 @@ package app.alextran.immich.widget
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.*
import androidx.core.net.toUri
@ -14,6 +17,7 @@ import androidx.glance.layout.*
import androidx.glance.state.GlanceStateDefinition
import androidx.glance.state.PreferencesGlanceStateDefinition
import androidx.glance.text.Text
import androidx.glance.text.TextAlign
import androidx.glance.text.TextStyle
import androidx.glance.unit.ColorProvider
import app.alextran.immich.R
@ -23,71 +27,101 @@ class PhotoWidget : GlanceAppWidget() {
override var stateDefinition: GlanceStateDefinition<*> = PreferencesGlanceStateDefinition
override suspend fun provideGlance(context: Context, id: GlanceId) {
provideContent {
val prefs = currentState<MutablePreferences>()
provideContent {
val prefs = currentState<MutablePreferences>()
val imageUUID = prefs[kImageUUID]
val subtitle = prefs[kSubtitleText]
val deeplinkURL = prefs[kDeeplinkURL]?.toUri()
var bitmap: Bitmap? = null
val imageUUID = prefs[kImageUUID]
val subtitle = prefs[kSubtitleText]
val deeplinkURL = prefs[kDeeplinkURL]?.toUri()
val widgetState = prefs[kWidgetState]
var bitmap: Bitmap? = null
if (imageUUID != null) {
// fetch a random photo from server
val file = File(context.cacheDir, imageFilename(imageUUID))
if (imageUUID != null) {
// fetch a random photo from server
val file = File(context.cacheDir, imageFilename(imageUUID))
if (file.exists()) {
bitmap = loadScaledBitmap(file, 500, 500)
}
if (file.exists()) {
bitmap = loadScaledBitmap(file, 500, 500)
}
}
// WIDGET CONTENT
Box(
modifier = GlanceModifier
.fillMaxSize()
.background(Color.White)
.clickable {
val intent = Intent(Intent.ACTION_VIEW, deeplinkURL ?: "immich://".toUri())
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
}
) {
if (bitmap != null) {
Image(
provider = ImageProvider(bitmap),
contentDescription = "Widget Image",
contentScale = ContentScale.Crop,
modifier = GlanceModifier.fillMaxSize()
)
// WIDGET CONTENT
Box(
modifier = GlanceModifier
.fillMaxSize()
.background(GlanceTheme.colors.background)
.clickable {
val intent = Intent(Intent.ACTION_VIEW, deeplinkURL ?: "immich://".toUri())
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
}
) {
if (bitmap != null) {
Image(
provider = ImageProvider(bitmap),
contentDescription = "Widget Image",
contentScale = ContentScale.Crop,
modifier = GlanceModifier.fillMaxSize()
)
if (!subtitle.isNullOrBlank()) {
Column(
verticalAlignment = Alignment.Bottom,
horizontalAlignment = Alignment.Start,
if (!subtitle.isNullOrBlank()) {
Column(
verticalAlignment = Alignment.Bottom,
horizontalAlignment = Alignment.Start,
modifier = GlanceModifier
.fillMaxSize()
.padding(12.dp)
) {
Text(
text = subtitle,
style = TextStyle(
color = ColorProvider(Color.White),
fontSize = 16.sp
),
modifier = GlanceModifier
.fillMaxSize()
.padding(12.dp)
) {
Text(
text = subtitle,
style = TextStyle(
color = ColorProvider(Color.White),
fontSize = 16.sp
),
modifier = GlanceModifier
.background(ColorProvider(Color(0x99000000))) // 60% black
.padding(8.dp)
.cornerRadius(8.dp)
)
}
.background(ColorProvider(Color(0x99000000))) // 60% black
.padding(8.dp)
.cornerRadius(8.dp)
)
}
} else {
}
} else {
Column(
modifier = GlanceModifier.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
provider = ImageProvider(R.drawable.splash),
contentDescription = null,
)
if (widgetState == WidgetState.LOG_IN.toString()) {
Box(
modifier = GlanceModifier.fillMaxWidth().padding(16.dp),
contentAlignment = Alignment.Center
) {
Text("Log in to your Immich server", style = TextStyle(textAlign = TextAlign.Center, color = GlanceTheme.colors.primary))
}
} else {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = GlanceModifier.fillMaxWidth().padding(16.dp)
) {
CircularProgressIndicator(
modifier = GlanceModifier.size(12.dp)
)
Spacer(modifier = GlanceModifier.width(8.dp))
Text("Loading widget...", style = TextStyle(textAlign = TextAlign.Center, color = GlanceTheme.colors.primary))
}
}
}
}
}
}
}
override suspend fun onDelete(context: Context, glanceId: GlanceId) {

View File

@ -1,11 +1,16 @@
package app.alextran.immich.widget
import HomeWidgetGlanceWidgetReceiver
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.util.Log
import android.content.Intent
import es.antonborri.home_widget.HomeWidgetPlugin
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class RandomReceiver : HomeWidgetGlanceWidgetReceiver<PhotoWidget>() {
class RandomReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget = PhotoWidget()
override fun onUpdate(
@ -19,5 +24,23 @@ class RandomReceiver : HomeWidgetGlanceWidgetReceiver<PhotoWidget>() {
ImageDownloadWorker.enqueuePeriodic(context, widgetID, WidgetType.RANDOM)
}
}
override fun onReceive(context: Context, intent: Intent) {
val fromMainApp = intent.getBooleanExtra(HomeWidgetPlugin.TRIGGERED_FROM_HOME_WIDGET, false)
// Launch coroutine to setup a single shot if the app requested the update
if (fromMainApp) {
CoroutineScope(Dispatchers.Default).launch {
val provider = ComponentName(context, RandomReceiver::class.java)
val glanceIds = AppWidgetManager.getInstance(context).getAppWidgetIds(provider)
glanceIds.forEach { widgetID ->
ImageDownloadWorker.singleShot(context, widgetID, WidgetType.MEMORIES)
}
}
}
super.onReceive(context, intent)
}
}

View File

@ -9,7 +9,6 @@ import androidx.compose.ui.platform.LocalContext
@Composable
fun LightDarkTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(), // ← This line is key
content: @Composable () -> Unit
) {
val context = LocalContext.current

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="memory_widget_title">Memories</string>
<string name="random_widget_title">Random</string>
<string name="memory_widget_description">See memories from Immich.</string>
<string name="random_widget_description">View a random image from your library or a specific album.</string>
</resources>

View File

@ -4,4 +4,6 @@
android:minHeight="110dp"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="1200000"
android:description="@string/memory_widget_description"
android:previewImage="@drawable/memory_preview"
/>

View File

@ -8,4 +8,6 @@
android:configure="app.alextran.immich.widget.configure.RandomConfigure"
android:widgetFeatures="reconfigurable|configuration_optional"
tools:targetApi="28"
android:description="@string/random_widget_description"
android:previewImage="@drawable/random_preview"
/>