diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml
index 5f4de8284a..c6f0232bb6 100644
--- a/mobile/android/app/src/main/AndroidManifest.xml
+++ b/mobile/android/app/src/main/AndroidManifest.xml
@@ -178,6 +178,67 @@
android:resource="@xml/memory_widget" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/FlexWindowReceivers.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/FlexWindowReceivers.kt
new file mode 100644
index 0000000000..fd43f71fcf
--- /dev/null
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/widget/FlexWindowReceivers.kt
@@ -0,0 +1,11 @@
+package app.alextran.immich.widget
+
+// Widget providers for the Samsung Flex Window cover screen.
+
+class MemoryReceiverFlex2x2 : MemoryReceiver()
+
+class MemoryReceiverFlex4x2 : MemoryReceiver()
+
+class RandomReceiverFlex2x2 : RandomReceiver()
+
+class RandomReceiverFlex4x2 : RandomReceiver()
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 63b32eb6f0..aee4398e58 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
@@ -11,7 +11,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-class MemoryReceiver : GlanceAppWidgetReceiver() {
+open class MemoryReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget = PhotoWidget()
override fun onUpdate(
@@ -28,7 +28,7 @@ class MemoryReceiver : GlanceAppWidgetReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val fromMainApp = intent.getBooleanExtra(HomeWidgetPlugin.TRIGGERED_FROM_HOME_WIDGET, false)
- val provider = ComponentName(context, MemoryReceiver::class.java)
+ val provider = ComponentName(context, javaClass)
val glanceIds = AppWidgetManager.getInstance(context).getAppWidgetIds(provider)
// Launch coroutine to setup a single shot if the app requested the update
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 a7662181bc..3d4c581e52 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
@@ -11,7 +11,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-class RandomReceiver : GlanceAppWidgetReceiver() {
+open class RandomReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget = PhotoWidget()
override fun onUpdate(
@@ -28,7 +28,7 @@ class RandomReceiver : GlanceAppWidgetReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val fromMainApp = intent.getBooleanExtra(HomeWidgetPlugin.TRIGGERED_FROM_HOME_WIDGET, false)
- val provider = ComponentName(context, RandomReceiver::class.java)
+ val provider = ComponentName(context, javaClass)
val glanceIds = AppWidgetManager.getInstance(context).getAppWidgetIds(provider)
// Launch coroutine to setup a single shot if the app requested the update
diff --git a/mobile/android/app/src/main/res/values/attrs_samsung_widget.xml b/mobile/android/app/src/main/res/values/attrs_samsung_widget.xml
new file mode 100644
index 0000000000..27254c3866
--- /dev/null
+++ b/mobile/android/app/src/main/res/values/attrs_samsung_widget.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/mobile/android/app/src/main/res/values/strings.xml b/mobile/android/app/src/main/res/values/strings.xml
index d543e33116..89b6384ae4 100644
--- a/mobile/android/app/src/main/res/values/strings.xml
+++ b/mobile/android/app/src/main/res/values/strings.xml
@@ -2,6 +2,11 @@
Memories
Random
+
+ Memories (2x2)
+ Memories (4x2)
+ Random (2x2)
+ Random (4x2)
See memories from Immich.
View a random image from your library or a specific album.
diff --git a/mobile/android/app/src/main/res/xml/memory_widget_flex_2x2.xml b/mobile/android/app/src/main/res/xml/memory_widget_flex_2x2.xml
new file mode 100644
index 0000000000..8e0379e14f
--- /dev/null
+++ b/mobile/android/app/src/main/res/xml/memory_widget_flex_2x2.xml
@@ -0,0 +1,19 @@
+
+
+
diff --git a/mobile/android/app/src/main/res/xml/memory_widget_flex_4x2.xml b/mobile/android/app/src/main/res/xml/memory_widget_flex_4x2.xml
new file mode 100644
index 0000000000..40d2faff9c
--- /dev/null
+++ b/mobile/android/app/src/main/res/xml/memory_widget_flex_4x2.xml
@@ -0,0 +1,19 @@
+
+
+
diff --git a/mobile/android/app/src/main/res/xml/random_widget_flex_2x2.xml b/mobile/android/app/src/main/res/xml/random_widget_flex_2x2.xml
new file mode 100644
index 0000000000..791bc03c66
--- /dev/null
+++ b/mobile/android/app/src/main/res/xml/random_widget_flex_2x2.xml
@@ -0,0 +1,21 @@
+
+
+
diff --git a/mobile/android/app/src/main/res/xml/random_widget_flex_4x2.xml b/mobile/android/app/src/main/res/xml/random_widget_flex_4x2.xml
new file mode 100644
index 0000000000..5baa4e01c0
--- /dev/null
+++ b/mobile/android/app/src/main/res/xml/random_widget_flex_4x2.xml
@@ -0,0 +1,21 @@
+
+
+
diff --git a/mobile/android/app/src/main/res/xml/samsung_appwidget_sub_screen.xml b/mobile/android/app/src/main/res/xml/samsung_appwidget_sub_screen.xml
new file mode 100644
index 0000000000..3e7c6a0f27
--- /dev/null
+++ b/mobile/android/app/src/main/res/xml/samsung_appwidget_sub_screen.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mobile/lib/constants/constants.dart b/mobile/lib/constants/constants.dart
index c2a13c3ec2..cadb896c42 100644
--- a/mobile/lib/constants/constants.dart
+++ b/mobile/lib/constants/constants.dart
@@ -37,9 +37,14 @@ const String kWidgetCustomHeaders = "widget_custom_headers";
// add widget identifiers here for new widgets
// these are used to force a widget refresh
// (iOSName, androidFQDN)
+// Android-only providers reuse the iOS name of their counterpart, updateWidget rejects a null one.
const List<(String, String)> kWidgetNames = [
('com.immich.widget.random', 'app.alextran.immich.widget.RandomReceiver'),
('com.immich.widget.memory', 'app.alextran.immich.widget.MemoryReceiver'),
+ ('com.immich.widget.memory', 'app.alextran.immich.widget.MemoryReceiverFlex2x2'),
+ ('com.immich.widget.memory', 'app.alextran.immich.widget.MemoryReceiverFlex4x2'),
+ ('com.immich.widget.random', 'app.alextran.immich.widget.RandomReceiverFlex2x2'),
+ ('com.immich.widget.random', 'app.alextran.immich.widget.RandomReceiverFlex4x2'),
];
const int kMinMonthsToEnableScrubberSnap = 12;