This commit is contained in:
bwees 2025-07-01 16:13:51 -05:00
parent 7f7a3bc6ef
commit 0f06193664
No known key found for this signature in database
6 changed files with 78 additions and 80 deletions

View File

@ -1,31 +1,14 @@
package app.alextran.immich.widget
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.Context
import android.graphics.Bitmap
import android.util.Log
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.glance.*
import androidx.glance.appwidget.GlanceAppWidgetManager
import androidx.glance.appwidget.state.updateAppWidgetState
import androidx.work.*
import com.google.gson.Gson
import es.antonborri.home_widget.HomeWidgetPlugin
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
@ -45,7 +28,7 @@ class ImageDownloadWorker(
private val uniqueWorkName = ImageDownloadWorker::class.java.simpleName
fun enqueue(context: Context, appWidgetId: Int, config: WidgetConfig) {
fun enqueue(context: Context, appWidgetId: Int, widgetType: WidgetType) {
val manager = WorkManager.getInstance(context)
val workRequest = PeriodicWorkRequestBuilder<ImageDownloadWorker>(
@ -58,7 +41,7 @@ class ImageDownloadWorker(
)
.setInputData(
Data.Builder()
.putString("config", Gson().toJson(config))
.putString("widgetType", widgetType.toString())
.putInt("widgetId", appWidgetId)
.build()
)
@ -78,45 +61,43 @@ class ImageDownloadWorker(
}
}
private fun getServerConfig(): ServerConfig? {
val prefs = HomeWidgetPlugin.getData(context)
val serverURL = prefs.getString("widget_server_url", "") ?: ""
val sessionKey = prefs.getString("widget_auth_token", "") ?: ""
if (serverURL == "" || sessionKey == "") {
return null
}
return ServerConfig(
serverURL,
sessionKey
)
}
override suspend fun doWork(): Result {
return try {
val configString = inputData.getString("config")
val config = Gson().fromJson(configString, WidgetConfig::class.java)
val widgetType = WidgetType.valueOf(inputData.getString("config") ?: "")
val widgetId = inputData.getInt("widgetId", -1)
val glanceId = GlanceAppWidgetManager(context).getGlanceIdBy(widgetId)
val serverConfig = getServerConfig() ?: return Result.success()
val currentState = getAppWidgetState(context, PreferencesGlanceStateDefinition, glanceId)
val currentImgUUID = currentState[stringPreferencesKey("uuid")]
// clear current image if it exists
val state = getAppWidgetState(context, PreferencesGlanceStateDefinition, glanceId)
val currentImgUUID = state[stringPreferencesKey("uuid")]
if (currentImgUUID != null) {
deleteImage(currentImgUUID)
val serverConfig = ImmichAPI.getServerConfig(context)
// clear any image caches and go to "login" state if no credentials
if (serverConfig == null) {
if (!currentImgUUID.isNullOrEmpty()) {
deleteImage(currentImgUUID)
updateWidget(widgetType, glanceId, "", WidgetState.LOG_IN)
}
return Result.success()
}
// fetch new image
val newBitmap = when (config.widgetType) {
WidgetType.RANDOM -> fetchRandom(serverConfig)
val newBitmap = when (widgetType) {
WidgetType.RANDOM -> fetchRandom(serverConfig, currentState)
}
// clear current image if it exists
if (!currentImgUUID.isNullOrEmpty()) {
deleteImage(currentImgUUID)
}
// save a new image
val imgUUID = saveImage(newBitmap)
// trigger the update routine with new image uuid
updateWidget(config, glanceId, imgUUID)
updateWidget(widgetType, glanceId, imgUUID)
Result.success()
} catch (e: Exception) {
@ -129,19 +110,29 @@ class ImageDownloadWorker(
}
}
private suspend fun updateWidget(config: WidgetConfig, glanceId: GlanceId, imageUUID: String) {
private suspend fun updateWidget(type: WidgetType, glanceId: GlanceId, imageUUID: String, widgetState: WidgetState = WidgetState.SUCCESS) {
updateAppWidgetState(context, glanceId) { prefs ->
prefs[longPreferencesKey("now")] = System.currentTimeMillis()
prefs[stringPreferencesKey("uuid")] = imageUUID
prefs[stringPreferencesKey("state")] = widgetState.toString()
}
RandomWidget().update(context,glanceId)
when (type) {
WidgetType.RANDOM -> RandomWidget().update(context,glanceId)
}
}
private suspend fun fetchRandom(serverConfig: ServerConfig): Bitmap {
private suspend fun fetchRandom(serverConfig: ServerConfig, widgetData: Preferences): Bitmap {
val api = ImmichAPI(serverConfig)
val random = api.fetchSearchResults(SearchFilters(AssetType.IMAGE, size=1))
val filters = SearchFilters(AssetType.IMAGE, size=1)
val albumId = widgetData[stringPreferencesKey("albumID")]
if (albumId != null) {
filters.albumIds = listOf(albumId)
}
val random = api.fetchSearchResults(filters)
val image = api.fetchImage(random[0])
return image

View File

@ -6,6 +6,7 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import es.antonborri.home_widget.HomeWidgetPlugin
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.OutputStreamWriter
@ -18,6 +19,25 @@ import java.util.*
class ImmichAPI(cfg: ServerConfig) {
companion object {
fun getServerConfig(context: Context): ServerConfig? {
val prefs = HomeWidgetPlugin.getData(context)
val serverURL = prefs.getString("widget_server_url", "") ?: ""
val sessionKey = prefs.getString("widget_auth_token", "") ?: ""
if (serverURL.isBlank() || sessionKey.isBlank()) {
return null
}
return ServerConfig(
serverURL,
sessionKey
)
}
}
private val gson = Gson()
private val serverConfig = cfg

View File

@ -3,6 +3,8 @@ package app.alextran.immich.widget
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.glance.appwidget.GlanceAppWidget
// MARK: Immich Entities
enum class AssetType {
IMAGE, VIDEO, AUDIO, OTHER
}
@ -32,6 +34,8 @@ data class Album(
val albumName: String
)
// MARK: Widget Specific
enum class WidgetType {
RANDOM;
@ -41,15 +45,9 @@ enum class WidgetType {
}
}
data class WidgetConfig(
val widgetType: WidgetType,
val params: Map<String, String>,
)
enum class WidgetState {
LOADING, SUCCESS, LOG_IN;
}
data class ServerConfig(val serverEndpoint: String, val sessionKey: String)
// this value is in HomeWidgetPlugin.PREFERENCES but is marked internal
const val WIDGET_PREFERENCES = "HomeWidgetPreferences"
val loggedInKey = stringPreferencesKey("isLoggedIn")
val lastUpdatedKey = stringPreferencesKey("lastUpdated")

View File

@ -14,7 +14,7 @@ import androidx.glance.text.Text
import app.alextran.immich.R
@Composable
fun PhotoWidget(image: Bitmap?, error: String?, subtitle: String?) {
fun PhotoView(image: Bitmap?, subtitle: String?, loggedIn: Boolean) {
Box(
modifier = GlanceModifier
@ -28,12 +28,14 @@ fun PhotoWidget(image: Bitmap?, error: String?, subtitle: String?) {
contentScale = ContentScale.Crop,
modifier = GlanceModifier.fillMaxSize()
)
if (subtitle != null)
Text(subtitle)
} else {
Image(
provider = ImageProvider(R.drawable.splash),
contentDescription = null,
)
Text(error ?: "NOPERS")
}
}
}

View File

@ -3,17 +3,7 @@ package app.alextran.immich.widget
import HomeWidgetGlanceWidgetReceiver
import android.appwidget.AppWidgetManager
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.glance.GlanceId
import androidx.glance.appwidget.GlanceAppWidgetManager
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import java.util.concurrent.TimeUnit
class RandomReceiver : HomeWidgetGlanceWidgetReceiver<RandomWidget>() {
override val glanceAppWidget = RandomWidget()
@ -25,11 +15,8 @@ class RandomReceiver : HomeWidgetGlanceWidgetReceiver<RandomWidget>() {
) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
val cfg = WidgetConfig(WidgetType.RANDOM, HashMap())
appWidgetIds.forEach { widgetID ->
ImageDownloadWorker.enqueue(context, widgetID, cfg)
Log.w("WIDGET_UPDATE", "WORKER ENQUEUE CALLED: $widgetID")
ImageDownloadWorker.enqueue(context, widgetID, WidgetType.RANDOM)
}
}
}

View File

@ -1,19 +1,14 @@
package app.alextran.immich.widget
import HomeWidgetGlanceState
import HomeWidgetGlanceStateDefinition
import android.content.Context
import android.graphics.Bitmap
import android.util.Log
import androidx.datastore.preferences.core.MutablePreferences
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.glance.appwidget.*
import androidx.glance.*
import androidx.glance.state.GlanceStateDefinition
import androidx.glance.state.PreferencesGlanceStateDefinition
import java.io.File
import java.util.prefs.Preferences
class RandomWidget : GlanceAppWidget() {
override var stateDefinition: GlanceStateDefinition<*> = PreferencesGlanceStateDefinition
@ -23,7 +18,10 @@ class RandomWidget : GlanceAppWidget() {
provideContent {
val prefs = currentState<MutablePreferences>()
val imageUUID = prefs[stringPreferencesKey("uuid")]
val subtitle: String? = prefs[stringPreferencesKey("subtitle")]
var bitmap: Bitmap? = null
var loggedIn = true
if (imageUUID != null) {
// fetch a random photo from server
@ -32,9 +30,11 @@ class RandomWidget : GlanceAppWidget() {
if (file.exists()) {
bitmap = loadScaledBitmap(file, 500, 500)
}
} else if (ImmichAPI.getServerConfig(context) == null) {
loggedIn = false
}
PhotoWidget(image = bitmap, error = "NOPE", subtitle = "hello")
PhotoView(image = bitmap, subtitle = subtitle, loggedIn = loggedIn)
}
}