From 1b8fa513154ec83eb9eef593bc63d4e350caf915 Mon Sep 17 00:00:00 2001
From: Aamir Azad <82281117+aamirazad@users.noreply.github.com>
Date: Thu, 22 May 2025 15:40:53 -0400
Subject: [PATCH 1/4] chore: change stable release estimate on roadmap (#18497)

Change stable release estimate
---
 docs/src/pages/roadmap.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/src/pages/roadmap.tsx b/docs/src/pages/roadmap.tsx
index 50f7a47902..1258000052 100644
--- a/docs/src/pages/roadmap.tsx
+++ b/docs/src/pages/roadmap.tsx
@@ -218,7 +218,7 @@ const roadmap: Item[] = [
     iconColor: 'indianred',
     title: 'Stable release',
     description: 'Immich goes stable',
-    getDateLabel: () => 'Planned for early 2025',
+    getDateLabel: () => 'Planned for 2025',
   },
   {
     done: false,

From 15877ddf1f63b7e69edb46bd77b296fad8276145 Mon Sep 17 00:00:00 2001
From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com>
Date: Fri, 23 May 2025 01:43:16 +0530
Subject: [PATCH 2/4] fix: translations from background service (#18473)

* fix: translations from background service

* test: generate translation before running tests

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
---
 .github/workflows/test.yml                    | 5 +++++
 mobile/lib/services/localization.service.dart | 6 +++---
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index e19f9db6fc..dc7d22fc57 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -504,6 +504,11 @@ jobs:
         with:
           channel: 'stable'
           flutter-version-file: ./mobile/pubspec.yaml
+
+      - name: Generate translation file
+        run: make translation
+        working-directory: ./mobile
+
       - name: Run tests
         working-directory: ./mobile
         run: flutter test -j 1
diff --git a/mobile/lib/services/localization.service.dart b/mobile/lib/services/localization.service.dart
index c8ef662896..8bee710544 100644
--- a/mobile/lib/services/localization.service.dart
+++ b/mobile/lib/services/localization.service.dart
@@ -1,10 +1,10 @@
 // ignore_for_file: implementation_imports
 
-import 'package:flutter/foundation.dart';
-import 'package:easy_localization/src/asset_loader.dart';
 import 'package:easy_localization/src/easy_localization_controller.dart';
 import 'package:easy_localization/src/localization.dart';
+import 'package:flutter/foundation.dart';
 import 'package:immich_mobile/constants/locales.dart';
+import 'package:immich_mobile/generated/codegen_loader.g.dart';
 
 /// Workaround to manually load translations in another Isolate
 Future<bool> loadTranslations() async {
@@ -14,7 +14,7 @@ Future<bool> loadTranslations() async {
     supportedLocales: locales.values.toList(),
     useFallbackTranslations: true,
     saveLocale: true,
-    assetLoader: const RootBundleAssetLoader(),
+    assetLoader: const CodegenLoader(),
     path: translationsPath,
     useOnlyLangCode: false,
     onLoadError: (e) => debugPrint(e.toString()),

From 065f7c7d5d630382526e89b288012d4be96a2f49 Mon Sep 17 00:00:00 2001
From: Alex <alex.tran1502@gmail.com>
Date: Thu, 22 May 2025 15:17:14 -0500
Subject: [PATCH 3/4] fix: more z-index issue (#18493)

---
 .../asset-viewer/detail-panel.svelte           |  1 +
 .../assets/thumbnail/video-thumbnail.svelte    |  2 +-
 .../components/photos-page/memory-lane.svelte  |  4 ++--
 .../shared-components/map/map.svelte           | 18 +++++++++++-------
 .../[[assetId=id]]/+page.svelte                |  2 +-
 5 files changed, 16 insertions(+), 11 deletions(-)

diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte
index 24329d13bf..4d0c164ae1 100644
--- a/web/src/lib/components/asset-viewer/detail-panel.svelte
+++ b/web/src/lib/components/asset-viewer/detail-panel.svelte
@@ -495,6 +495,7 @@
         zoom={12.5}
         simplified
         useLocationPin
+        showSimpleControls={!showEditFaces}
         onOpenInMapView={() => goto(`${AppRoute.MAP}#12.5/${latlng.lat}/${latlng.lng}`)}
       >
         {#snippet popup({ marker })}
diff --git a/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte b/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte
index d59a03158a..2c03ca48f8 100644
--- a/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte
+++ b/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte
@@ -55,7 +55,7 @@
   };
 </script>
 
-<div class="absolute end-0 top-0 flex place-items-center gap-1 text-xs font-medium text-white">
+<div class="absolute end-0 top-0 z-1 flex place-items-center gap-1 text-xs font-medium text-white">
   {#if showTime}
     <span class="pt-2">
       {#if remainingSeconds < 60}
diff --git a/web/src/lib/components/photos-page/memory-lane.svelte b/web/src/lib/components/photos-page/memory-lane.svelte
index 475f6ceef9..bb1e29e4a0 100644
--- a/web/src/lib/components/photos-page/memory-lane.svelte
+++ b/web/src/lib/components/photos-page/memory-lane.svelte
@@ -45,7 +45,7 @@
     onscroll={onScroll}
   >
     {#if canScrollLeft || canScrollRight}
-      <div class="sticky start-0">
+      <div class="sticky start-0 z-1">
         {#if canScrollLeft}
           <div class="absolute start-4 top-24" transition:fade={{ duration: 200 }}>
             <button
@@ -60,7 +60,7 @@
           </div>
         {/if}
         {#if canScrollRight}
-          <div class="absolute end-4 top-24" transition:fade={{ duration: 200 }}>
+          <div class="absolute end-4 top-24 z-1" transition:fade={{ duration: 200 }}>
             <button
               type="button"
               class="rounded-full border border-gray-500 bg-gray-100 p-2 text-gray-500 opacity-50 hover:opacity-100"
diff --git a/web/src/lib/components/shared-components/map/map.svelte b/web/src/lib/components/shared-components/map/map.svelte
index 6e1e10049d..0c8094a1a1 100644
--- a/web/src/lib/components/shared-components/map/map.svelte
+++ b/web/src/lib/components/shared-components/map/map.svelte
@@ -54,6 +54,7 @@
     onClickPoint?: ({ lat, lng }: { lat: number; lng: number }) => void;
     popup?: import('svelte').Snippet<[{ marker: MapMarkerResponseDto }]>;
     rounded?: boolean;
+    showSimpleControls?: boolean;
   }
 
   let {
@@ -70,6 +71,7 @@
     onClickPoint = () => {},
     popup,
     rounded = false,
+    showSimpleControls = true,
   }: Props = $props();
 
   let map: maplibregl.Map | undefined = $state();
@@ -266,13 +268,15 @@
   bind:map
 >
   {#snippet children({ map }: { map: maplibregl.Map })}
-    <NavigationControl position="top-left" showCompass={!simplified} />
+    {#if showSimpleControls}
+      <NavigationControl position="top-left" showCompass={!simplified} />
 
-    {#if !simplified}
-      <GeolocateControl position="top-left" />
-      <FullscreenControl position="top-left" />
-      <ScaleControl />
-      <AttributionControl compact={false} />
+      {#if !simplified}
+        <GeolocateControl position="top-left" />
+        <FullscreenControl position="top-left" />
+        <ScaleControl />
+        <AttributionControl compact={false} />
+      {/if}
     {/if}
 
     {#if showSettings}
@@ -285,7 +289,7 @@
       </Control>
     {/if}
 
-    {#if onOpenInMapView}
+    {#if onOpenInMapView && showSimpleControls}
       <Control position="top-right">
         <ControlGroup>
           <ControlButton onclick={() => onOpenInMapView()}>
diff --git a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 1e1dfc254d..8995483c84 100644
--- a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -452,7 +452,7 @@
             {/if}
           </section>
           {#if isEditingName}
-            <div class="absolute w-64 sm:w-96">
+            <div class="absolute w-64 sm:w-96 z-1">
               {#if isSearchingPeople}
                 <div
                   class="flex border h-14 rounded-b-lg border-gray-400 dark:border-immich-dark-gray place-items-center bg-gray-200 p-2 dark:bg-gray-700"

From c7dc31151d222dc25ce2aab216b86267181bfba8 Mon Sep 17 00:00:00 2001
From: Daimolean <92239625+wuzihao051119@users.noreply.github.com>
Date: Fri, 23 May 2025 04:17:34 +0800
Subject: [PATCH 4/4] fix(web): multi-select (#18485)

---
 .../components/photos-page/asset-grid.svelte  | 31 ++++++++++---------
 1 file changed, 16 insertions(+), 15 deletions(-)

diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte
index 7b45019ec3..2a2f5ceec4 100644
--- a/web/src/lib/components/photos-page/asset-grid.svelte
+++ b/web/src/lib/components/photos-page/asset-grid.svelte
@@ -564,12 +564,9 @@
         return;
       }
 
-      // Select/deselect assets in range (start,end]
+      // Select/deselect assets in range (start,end)
       let started = false;
       for (const bucket of assetStore.buckets) {
-        if (bucket === startBucket) {
-          started = true;
-        }
         if (bucket === endBucket) {
           break;
         }
@@ -583,27 +580,31 @@
             }
           }
         }
+        if (bucket === startBucket) {
+          started = true;
+        }
       }
 
-      // Update date group selection
+      // Update date group selection in range [start,end]
       started = false;
       for (const bucket of assetStore.buckets) {
         if (bucket === startBucket) {
           started = true;
         }
+        if (started) {
+          // Split bucket into date groups and check each group
+          for (const dateGroup of bucket.dateGroups) {
+            const dateGroupTitle = dateGroup.groupTitle;
+            if (dateGroup.getAssets().every((a) => assetInteraction.hasSelectedAsset(a.id))) {
+              assetInteraction.addGroupToMultiselectGroup(dateGroupTitle);
+            } else {
+              assetInteraction.removeGroupFromMultiselectGroup(dateGroupTitle);
+            }
+          }
+        }
         if (bucket === endBucket) {
           break;
         }
-
-        // Split bucket into date groups and check each group
-        for (const dateGroup of bucket.dateGroups) {
-          const dateGroupTitle = dateGroup.groupTitle;
-          if (dateGroup.getAssets().every((a) => assetInteraction.hasSelectedAsset(a.id))) {
-            assetInteraction.addGroupToMultiselectGroup(dateGroupTitle);
-          } else {
-            assetInteraction.removeGroupFromMultiselectGroup(dateGroupTitle);
-          }
-        }
       }
     }