diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/ImageDownloadWorker.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/ImageDownloadWorker.kt index 4995ad7961..07d063cc3e 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/ImageDownloadWorker.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/ImageDownloadWorker.kt @@ -94,14 +94,20 @@ class ImageDownloadWorker( if (serverConfig == null) { if (!currentImgUUID.isNullOrEmpty()) { deleteImage(currentImgUUID) - updateWidget(glanceId, "", "", WidgetState.LOG_IN) + updateWidget( + glanceId, + "", + "", + "immich://", + WidgetState.LOG_IN + ) } return Result.success() } // fetch new image - val (newBitmap, subtitle) = when (widgetType) { + val entry = when (widgetType) { WidgetType.RANDOM -> fetchRandom(serverConfig, widgetConfig) WidgetType.MEMORIES -> fetchMemory(serverConfig) } @@ -113,10 +119,10 @@ class ImageDownloadWorker( // save a new image val imgUUID = UUID.randomUUID().toString() - saveImage(newBitmap, imgUUID) + saveImage(entry.image, imgUUID) // trigger the update routine with new image uuid - updateWidget(glanceId, imgUUID, subtitle) + updateWidget(glanceId, imgUUID, entry.subtitle, entry.deeplink) Result.success() } catch (e: Exception) { @@ -133,6 +139,7 @@ class ImageDownloadWorker( glanceId: GlanceId, imageUUID: String, subtitle: String?, + deeplink: String?, widgetState: WidgetState = WidgetState.SUCCESS ) { updateAppWidgetState(context, glanceId) { prefs -> @@ -140,6 +147,7 @@ class ImageDownloadWorker( prefs[kImageUUID] = imageUUID prefs[kWidgetState] = widgetState.toString() prefs[kSubtitleText] = subtitle ?: "" + prefs[kDeeplinkURL] = deeplink ?: "" } PhotoWidget().update(context,glanceId) @@ -148,7 +156,7 @@ class ImageDownloadWorker( private suspend fun fetchRandom( serverConfig: ServerConfig, widgetConfig: Preferences - ): Pair { + ): WidgetEntry { val api = ImmichAPI(serverConfig) val filters = SearchFilters(AssetType.IMAGE, size=1) @@ -164,17 +172,21 @@ class ImageDownloadWorker( val random = api.fetchSearchResults(filters).first() val image = api.fetchImage(random) - return Pair(image, subtitle) + return WidgetEntry( + image, + subtitle, + assetDeeplink(random) + ) } private suspend fun fetchMemory( serverConfig: ServerConfig - ): Pair { + ): WidgetEntry { val api = ImmichAPI(serverConfig) val today = LocalDate.now() val memories = api.fetchMemory(today) - val asset: SearchResult + val asset: Asset var subtitle: String? = null if (memories.isNotEmpty()) { @@ -182,14 +194,18 @@ class ImageDownloadWorker( val memory = memories.random() asset = memory.assets.random() - subtitle = "${today.year-memory.data.year} years ago" + subtitle = "${today.year - memory.data.year} years ago" } else { val filters = SearchFilters(AssetType.IMAGE, size=1) asset = api.fetchSearchResults(filters).first() } val image = api.fetchImage(asset) - return Pair(image, subtitle) + return WidgetEntry( + image, + subtitle, + assetDeeplink(asset) + ) } private suspend fun deleteImage(uuid: String) = withContext(Dispatchers.IO) { diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/ImmichAPI.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/ImmichAPI.kt index 68f09ba846..2d9aad0b68 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/ImmichAPI.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/ImmichAPI.kt @@ -53,7 +53,7 @@ class ImmichAPI(cfg: ServerConfig) { return URL(urlString.toString()) } - suspend fun fetchSearchResults(filters: SearchFilters): List = withContext(Dispatchers.IO) { + suspend fun fetchSearchResults(filters: SearchFilters): List = withContext(Dispatchers.IO) { val url = buildRequestURL("/search/random") val connection = (url.openConnection() as HttpURLConnection).apply { requestMethod = "POST" @@ -69,7 +69,7 @@ class ImmichAPI(cfg: ServerConfig) { } val response = connection.inputStream.bufferedReader().readText() - val type = object : TypeToken>() {}.type + val type = object : TypeToken>() {}.type gson.fromJson(response, type) } @@ -85,7 +85,7 @@ class ImmichAPI(cfg: ServerConfig) { gson.fromJson(response, type) } - suspend fun fetchImage(asset: SearchResult): Bitmap = withContext(Dispatchers.IO) { + suspend fun fetchImage(asset: Asset): Bitmap = withContext(Dispatchers.IO) { val url = buildRequestURL("/assets/${asset.id}/thumbnail", listOf("size" to "preview")) val connection = url.openConnection() val data = connection.getInputStream().readBytes() diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/Model.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/Model.kt index 7f78883e49..9aaeec8980 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/Model.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/Model.kt @@ -1,5 +1,6 @@ package app.alextran.immich.widget +import android.graphics.Bitmap import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.doublePreferencesKey import androidx.datastore.preferences.core.longPreferencesKey @@ -12,9 +13,9 @@ enum class AssetType { IMAGE, VIDEO, AUDIO, OTHER } -data class SearchResult( +data class Asset( val id: String, - val type: AssetType + val type: AssetType, ) data class SearchFilters( @@ -25,7 +26,7 @@ data class SearchFilters( data class MemoryResult( val id: String, - var assets: List, + var assets: List, val type: String, val data: MemoryData ) { @@ -47,6 +48,12 @@ enum class WidgetState { LOADING, SUCCESS, LOG_IN; } +data class WidgetEntry ( + val image: Bitmap, + val subtitle: String?, + val deeplink: String? +) + data class ServerConfig(val serverEndpoint: String, val sessionKey: String) // MARK: Widget State Keys @@ -57,6 +64,7 @@ val kWidgetState = stringPreferencesKey("state") val kSelectedAlbum = stringPreferencesKey("albumID") val kSelectedAlbumName = stringPreferencesKey("albumName") val kShowAlbumName = booleanPreferencesKey("showAlbumName") +val kDeeplinkURL = stringPreferencesKey("deeplink") const val kWorkerWidgetType = "widgetType" const val kWorkerWidgetID = "widgetId" @@ -64,3 +72,7 @@ const val kWorkerWidgetID = "widgetId" fun imageFilename(id: String): String { return "widget_image_$id.jpg" } + +fun assetDeeplink(asset: Asset): String { + return "immich://asset?id=${asset.id}" +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/PhotoWidget.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/PhotoWidget.kt index 33dcd91b4b..c41c5b374a 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/PhotoWidget.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/PhotoWidget.kt @@ -1,9 +1,11 @@ package app.alextran.immich.widget import android.content.Context +import android.content.Intent import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.* +import androidx.core.net.toUri import androidx.datastore.preferences.core.MutablePreferences import androidx.glance.appwidget.* import androidx.glance.* @@ -25,7 +27,8 @@ class PhotoWidget : GlanceAppWidget() { val prefs = currentState() val imageUUID = prefs[kImageUUID] - val subtitle: String? = prefs[kSubtitleText] + val subtitle = prefs[kSubtitleText] + val deeplinkURL = prefs[kDeeplinkURL]?.toUri() var bitmap: Bitmap? = null if (imageUUID != null) { @@ -42,6 +45,11 @@ class PhotoWidget : GlanceAppWidget() { 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(