diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml
index 3fcc44ad3f..0d25c658e0 100644
--- a/mobile/android/app/src/main/AndroidManifest.xml
+++ b/mobile/android/app/src/main/AndroidManifest.xml
@@ -155,16 +155,16 @@
android:resource="@xml/random_widget" />
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
fetchRandom(serverConfig, currentState)
+ val (newBitmap, subtitle) = when (widgetType) {
+ WidgetType.RANDOM -> fetchRandom(serverConfig, widgetConfig)
+ WidgetType.MEMORIES -> fetchMemory(serverConfig)
}
// clear current image if it exists
@@ -110,10 +112,11 @@ class ImageDownloadWorker(
}
// save a new image
- val imgUUID = saveImage(newBitmap)
+ val imgUUID = UUID.randomUUID().toString()
+ saveImage(newBitmap, imgUUID)
// trigger the update routine with new image uuid
- updateWidget(widgetType, glanceId, imgUUID)
+ updateWidget(glanceId, imgUUID, subtitle)
Result.success()
} catch (e: Exception) {
@@ -126,32 +129,65 @@ class ImageDownloadWorker(
}
}
- private suspend fun updateWidget(type: WidgetType, glanceId: GlanceId, imageUUID: String, widgetState: WidgetState = WidgetState.SUCCESS) {
+ private suspend fun updateWidget(
+ glanceId: GlanceId,
+ imageUUID: String,
+ subtitle: String?,
+ widgetState: WidgetState = WidgetState.SUCCESS
+ ) {
updateAppWidgetState(context, glanceId) { prefs ->
prefs[kNow] = System.currentTimeMillis()
prefs[kImageUUID] = imageUUID
prefs[kWidgetState] = widgetState.toString()
+ prefs[kSubtitleText] = subtitle ?: ""
}
- when (type) {
- WidgetType.RANDOM -> RandomWidget().update(context,glanceId)
- }
+ PhotoWidget().update(context,glanceId)
}
- private suspend fun fetchRandom(serverConfig: ServerConfig, widgetData: Preferences): Bitmap {
+ private suspend fun fetchRandom(
+ serverConfig: ServerConfig,
+ widgetConfig: Preferences
+ ): Pair {
val api = ImmichAPI(serverConfig)
val filters = SearchFilters(AssetType.IMAGE, size=1)
- val albumId = widgetData[kSelectedAlbum]
+ val albumId = widgetConfig[kSelectedAlbum]
+ val albumName = widgetConfig[kSelectedAlbumName]
if (albumId != null) {
filters.albumIds = listOf(albumId)
}
- val random = api.fetchSearchResults(filters)
- val image = api.fetchImage(random[0])
+ val random = api.fetchSearchResults(filters).first()
+ val image = api.fetchImage(random)
- return image
+ return Pair(image, albumName)
+ }
+
+ private suspend fun fetchMemory(
+ serverConfig: ServerConfig
+ ): Pair {
+ val api = ImmichAPI(serverConfig)
+
+ val today = LocalDate.now()
+ val memories = api.fetchMemory(today)
+ val asset: SearchResult
+ var subtitle: String? = null
+
+ if (memories.isNotEmpty()) {
+ // pick a random asset from a random memory
+ val memory = memories.random()
+ asset = memory.assets.random()
+
+ 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)
}
private suspend fun deleteImage(uuid: String) = withContext(Dispatchers.IO) {
@@ -159,13 +195,10 @@ class ImageDownloadWorker(
file.delete()
}
- private suspend fun saveImage(bitmap: Bitmap): String = withContext(Dispatchers.IO) {
- val uuid = UUID.randomUUID().toString()
+ private suspend fun saveImage(bitmap: Bitmap, uuid: String) = withContext(Dispatchers.IO) {
val file = File(context.cacheDir, imageFilename(uuid))
FileOutputStream(file).use { out ->
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out)
}
-
- uuid
}
}
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 4ae35421ef..68f09ba846 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
@@ -14,6 +14,8 @@ 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.*
@@ -71,8 +73,8 @@ class ImmichAPI(cfg: ServerConfig) {
gson.fromJson(response, type)
}
- suspend fun fetchMemory(date: Date): List = withContext(Dispatchers.IO) {
- val iso8601 = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.US).format(date)
+ suspend fun fetchMemory(date: LocalDate): List = withContext(Dispatchers.IO) {
+ val iso8601 = date.format(DateTimeFormatter.ISO_LOCAL_DATE)
val url = buildRequestURL("/memories", listOf("for" to iso8601))
val connection = (url.openConnection() as HttpURLConnection).apply {
requestMethod = "GET"
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/MemoryReceiver.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/MemoryReceiver.kt
index b92bfbcf82..46a8972258 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/MemoryReceiver.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/MemoryReceiver.kt
@@ -1,8 +1,22 @@
package app.alextran.immich.widget
import HomeWidgetGlanceWidgetReceiver
+import android.appwidget.AppWidgetManager
+import android.content.Context
-class MemoryReceiver : HomeWidgetGlanceWidgetReceiver() {
- override val glanceAppWidget = RandomWidget()
+class MemoryReceiver : HomeWidgetGlanceWidgetReceiver() {
+ override val glanceAppWidget = PhotoWidget()
+
+ override fun onUpdate(
+ context: Context,
+ appWidgetManager: AppWidgetManager,
+ appWidgetIds: IntArray
+ ) {
+ super.onUpdate(context, appWidgetManager, appWidgetIds)
+
+ appWidgetIds.forEach { widgetID ->
+ ImageDownloadWorker.enqueuePeriodic(context, widgetID, WidgetType.MEMORIES)
+ }
+ }
}
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 22ff710d55..7f78883e49 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
@@ -40,12 +40,7 @@ data class Album(
// MARK: Widget Specific
enum class WidgetType {
- RANDOM;
-
- val widgetClass: Class
- get() = when (this) {
- RANDOM -> RandomWidget::class.java
- }
+ RANDOM, MEMORIES;
}
enum class WidgetState {
@@ -60,6 +55,7 @@ val kSubtitleText = stringPreferencesKey("subtitle")
val kNow = longPreferencesKey("now")
val kWidgetState = stringPreferencesKey("state")
val kSelectedAlbum = stringPreferencesKey("albumID")
+val kSelectedAlbumName = stringPreferencesKey("albumName")
val kShowAlbumName = booleanPreferencesKey("showAlbumName")
const val kWorkerWidgetType = "widgetType"
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/PhotoView.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/PhotoView.kt
deleted file mode 100644
index 9501091df4..0000000000
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/PhotoView.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-package app.alextran.immich.widget
-
-import android.graphics.Bitmap
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.graphics.Color
-import androidx.glance.GlanceModifier
-import androidx.glance.Image
-import androidx.glance.ImageProvider
-import androidx.glance.background
-import androidx.glance.layout.Box
-import androidx.glance.layout.ContentScale
-import androidx.glance.layout.fillMaxSize
-import androidx.glance.text.Text
-import app.alextran.immich.R
-
-@Composable
-fun PhotoView(image: Bitmap?, subtitle: String?, loggedIn: Boolean) {
-
- Box(
- modifier = GlanceModifier
- .fillMaxSize()
- .background(Color.White) // your color here
- ) {
- if (image != null) {
- Image(
- provider = ImageProvider(image),
- contentDescription = "Widget Image",
- contentScale = ContentScale.Crop,
- modifier = GlanceModifier.fillMaxSize()
- )
- if (subtitle != null)
- Text(subtitle)
- } else {
- Image(
- provider = ImageProvider(R.drawable.splash),
- contentDescription = null,
- )
-
- }
- }
-}
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/RandomWidget.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/PhotoWidget.kt
similarity index 52%
rename from mobile/android/app/src/main/kotlin/app/alextran/immich/widget/RandomWidget.kt
rename to mobile/android/app/src/main/kotlin/app/alextran/immich/widget/PhotoWidget.kt
index c6fbf8be99..1120950c7c 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/RandomWidget.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/PhotoWidget.kt
@@ -2,43 +2,64 @@ package app.alextran.immich.widget
import android.content.Context
import android.graphics.Bitmap
+import androidx.compose.ui.graphics.Color
import androidx.datastore.preferences.core.MutablePreferences
-import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.glance.appwidget.*
import androidx.glance.*
+import androidx.glance.layout.Box
+import androidx.glance.layout.ContentScale
+import androidx.glance.layout.fillMaxSize
import androidx.glance.state.GlanceStateDefinition
import androidx.glance.state.PreferencesGlanceStateDefinition
+import androidx.glance.text.Text
+import app.alextran.immich.R
import java.io.File
-class RandomWidget : GlanceAppWidget() {
+class PhotoWidget : GlanceAppWidget() {
override var stateDefinition: GlanceStateDefinition<*> = PreferencesGlanceStateDefinition
override suspend fun provideGlance(context: Context, id: GlanceId) {
-
provideContent {
val prefs = currentState()
- val imageUUID = prefs[kImageUUID]
+ val imageUUID = prefs[kImageUUID]
val subtitle: String? = prefs[kSubtitleText]
var bitmap: Bitmap? = null
- var loggedIn = true
if (imageUUID != null) {
// fetch a random photo from server
- val file = File(context.cacheDir, imageFilename(id))
+ val file = File(context.cacheDir, imageFilename(imageUUID))
if (file.exists()) {
bitmap = loadScaledBitmap(file, 500, 500)
}
- } else if (ImmichAPI.getServerConfig(context) == null) {
- loggedIn = false
}
- PhotoView(image = bitmap, subtitle = subtitle, loggedIn = loggedIn)
+ // WIDGET CONTENT
+ Box(
+ modifier = GlanceModifier
+ .fillMaxSize()
+ .background(Color.White)
+ ) {
+ if (bitmap != null) {
+ Image(
+ provider = ImageProvider(bitmap),
+ contentDescription = "Widget Image",
+ contentScale = ContentScale.Crop,
+ modifier = GlanceModifier.fillMaxSize()
+ )
+ if (subtitle != null)
+ Text(subtitle)
+ } else {
+ Image(
+ provider = ImageProvider(R.drawable.splash),
+ contentDescription = null,
+ )
+ }
+ }
}
}
-
override suspend fun onDelete(context: Context, glanceId: GlanceId) {
super.onDelete(context, glanceId)
ImageDownloadWorker.cancel(context, glanceId)
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/RandomReceiver.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/RandomReceiver.kt
index e7b2291a12..687cb87b36 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/RandomReceiver.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/RandomReceiver.kt
@@ -5,8 +5,8 @@ import android.appwidget.AppWidgetManager
import android.content.Context
import android.util.Log
-class RandomReceiver : HomeWidgetGlanceWidgetReceiver() {
- override val glanceAppWidget = RandomWidget()
+class RandomReceiver : HomeWidgetGlanceWidgetReceiver() {
+ override val glanceAppWidget = PhotoWidget()
override fun onUpdate(
context: Context,
diff --git a/mobile/android/app/src/main/res/xml/memory_widget.xml b/mobile/android/app/src/main/res/xml/memory_widget.xml
new file mode 100644
index 0000000000..84847f6bd6
--- /dev/null
+++ b/mobile/android/app/src/main/res/xml/memory_widget.xml
@@ -0,0 +1,7 @@
+