From d38305360c56a01bb5f108fb02f72f3c4934af7c Mon Sep 17 00:00:00 2001 From: Dionysius <1341084+dionysius@users.noreply.github.com> Date: Fri, 28 Nov 2025 13:43:48 +0100 Subject: [PATCH 01/13] docs: DB_STORAGE_TYPE is only used by the database container (#24215) Co-authored-by: Daniel Dietzler --- docs/docs/install/environment-variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md index 8863a13ee7..c3e9b3acd3 100644 --- a/docs/docs/install/environment-variables.md +++ b/docs/docs/install/environment-variables.md @@ -80,7 +80,7 @@ Information on the current workers can be found [here](/administration/jobs-work | `DB_SSL_MODE` | Database SSL mode | | server | | `DB_VECTOR_EXTENSION`\*2 | Database vector extension (one of [`vectorchord`, `pgvector`, `pgvecto.rs`]) | | server | | `DB_SKIP_MIGRATIONS` | Whether to skip running migrations on startup (one of [`true`, `false`]) | `false` | server | -| `DB_STORAGE_TYPE` | Optimize concurrent IO on SSDs or sequential IO on HDDs ([`SSD`, `HDD`])\*3 | `SSD` | server | +| `DB_STORAGE_TYPE` | Optimize concurrent IO on SSDs or sequential IO on HDDs ([`SSD`, `HDD`])\*3 | `SSD` | database | \*1: The values of `DB_USERNAME`, `DB_PASSWORD`, and `DB_DATABASE_NAME` are passed to the Postgres container as the variables `POSTGRES_USER`, `POSTGRES_PASSWORD`, and `POSTGRES_DB` in `docker-compose.yml`. From e98a33cf9ddc5b20cb7de6d3e8e78ca72d66b4ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 28 Nov 2025 15:11:17 +0100 Subject: [PATCH 02/13] fix(docs): build `cli` for e2e tests (#24184) --- docs/docs/developer/testing.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/developer/testing.md b/docs/docs/developer/testing.md index e1b96f9164..d7c9edcd31 100644 --- a/docs/docs/developer/testing.md +++ b/docs/docs/developer/testing.md @@ -18,6 +18,7 @@ make e2e Before you can run the tests, you need to run the following commands _once_: - `pnpm install` (in `e2e/`) +- `pnpm run build` (in `cli/`) - `make open-api` (in the project root `/`) Once the test environment is running, the e2e tests can be run via: From 0c1fe35f2f6c7cd17f899edb4acee2c3475cb8fb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 15:17:25 +0100 Subject: [PATCH 03/13] chore(deps): update dependency terragrunt to v0.93.10 (#24149) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- deployment/mise.toml | 2 +- mise.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment/mise.toml b/deployment/mise.toml index f3d07ac31f..39c6d3dd32 100644 --- a/deployment/mise.toml +++ b/deployment/mise.toml @@ -1,5 +1,5 @@ [tools] -terragrunt = "0.91.2" +terragrunt = "0.93.10" opentofu = "1.10.6" [tasks."tg:fmt"] diff --git a/mise.toml b/mise.toml index d24893575a..39e8fed878 100644 --- a/mise.toml +++ b/mise.toml @@ -4,7 +4,7 @@ experimental_monorepo_root = true node = "24.11.1" flutter = "3.35.7" pnpm = "10.22.0" -terragrunt = "0.91.2" +terragrunt = "0.93.10" opentofu = "1.10.6" java = "25.0.1" From 1f6eb662e5a1ba8dc05b1fac0d8b780348831197 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 14:41:23 +0000 Subject: [PATCH 04/13] chore(deps): update dependency opentofu to v1.10.7 (#23964) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- deployment/mise.toml | 2 +- mise.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment/mise.toml b/deployment/mise.toml index 39c6d3dd32..53b683a7d3 100644 --- a/deployment/mise.toml +++ b/deployment/mise.toml @@ -1,6 +1,6 @@ [tools] terragrunt = "0.93.10" -opentofu = "1.10.6" +opentofu = "1.10.7" [tasks."tg:fmt"] run = "terragrunt hclfmt" diff --git a/mise.toml b/mise.toml index 39e8fed878..5f3aef61de 100644 --- a/mise.toml +++ b/mise.toml @@ -5,7 +5,7 @@ node = "24.11.1" flutter = "3.35.7" pnpm = "10.22.0" terragrunt = "0.93.10" -opentofu = "1.10.6" +opentofu = "1.10.7" java = "25.0.1" [tools."github:CQLabs/homebrew-dcm"] From f12f609038cb2dc1c8dec38af458e015ad9f0e94 Mon Sep 17 00:00:00 2001 From: Yaros Date: Fri, 28 Nov 2025 17:18:44 +0100 Subject: [PATCH 05/13] fix(mobile): enable backup text overflows (#24227) --- .../widgets/backup/backup_toggle_button.widget.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart b/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart index 8d374f74ff..ae4cfbd1c6 100644 --- a/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart +++ b/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart @@ -143,11 +143,13 @@ class BackupToggleButtonState extends ConsumerState with Sin Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text( - "enable_backup".t(context: context), - style: context.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w600, - color: context.primaryColor, + Flexible( + child: Text( + "enable_backup".t(context: context), + style: context.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: context.primaryColor, + ), ), ), ], From c0a3b58bba1852a9b16ff8b67847a92c7b7c6c56 Mon Sep 17 00:00:00 2001 From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Date: Fri, 28 Nov 2025 17:18:49 +0100 Subject: [PATCH 06/13] fix: rare cases of assets not loading in when scrolling backwards (#24245) --- pnpm-lock.yaml | 149 ++++++++++++++++++++++++----------------------- web/package.json | 2 +- 2 files changed, 76 insertions(+), 75 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 828fd80033..e1d38ac3a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -718,7 +718,7 @@ importers: version: link:../open-api/typescript-sdk '@immich/ui': specifier: ^0.49.2 - version: 0.49.2(svelte@5.43.12) + version: 0.49.2(svelte@5.45.2) '@mapbox/mapbox-gl-rtl-text': specifier: 0.2.3 version: 0.2.3(mapbox-gl@1.13.3) @@ -751,7 +751,7 @@ importers: version: 0.41.3 '@zoom-image/svelte': specifier: ^0.3.0 - version: 0.3.7(svelte@5.43.12) + version: 0.3.7(svelte@5.45.2) async-mutex: specifier: ^0.5.0 version: 0.5.0 @@ -805,13 +805,13 @@ importers: version: 5.2.2 svelte-i18n: specifier: ^4.0.1 - version: 4.0.1(svelte@5.43.12) + version: 4.0.1(svelte@5.45.2) svelte-maplibre: specifier: ^1.2.5 - version: 1.2.5(svelte@5.43.12) + version: 1.2.5(svelte@5.45.2) svelte-persisted-store: specifier: ^0.12.0 - version: 0.12.0(svelte@5.43.12) + version: 0.12.0(svelte@5.45.2) tabbable: specifier: ^6.2.0 version: 6.3.0 @@ -833,16 +833,16 @@ importers: version: 3.1.2 '@sveltejs/adapter-static': specifier: ^3.0.8 - version: 3.0.10(@sveltejs/kit@2.48.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))) + version: 3.0.10(@sveltejs/kit@2.48.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))) '@sveltejs/enhanced-img': specifier: ^0.8.0 - version: 0.8.5(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(rollup@4.53.3)(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) + version: 0.8.5(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(rollup@4.53.3)(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) '@sveltejs/kit': specifier: ^2.27.1 - version: 2.48.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) + version: 2.48.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) '@sveltejs/vite-plugin-svelte': specifier: 6.2.1 - version: 6.2.1(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) + version: 6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) '@tailwindcss/vite': specifier: ^4.1.7 version: 4.1.17(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) @@ -851,7 +851,7 @@ importers: version: 6.9.1 '@testing-library/svelte': specifier: ^5.2.8 - version: 5.2.9(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) + version: 5.2.9(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) '@testing-library/user-event': specifier: ^14.5.2 version: 14.6.1(@testing-library/dom@10.4.1) @@ -890,7 +890,7 @@ importers: version: 6.0.2(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-svelte: specifier: ^3.12.4 - version: 3.13.0(eslint@9.39.1(jiti@2.6.1))(svelte@5.43.12) + version: 3.13.0(eslint@9.39.1(jiti@2.6.1))(svelte@5.45.2) eslint-plugin-unicorn: specifier: ^62.0.0 version: 62.0.0(eslint@9.39.1(jiti@2.6.1)) @@ -911,19 +911,19 @@ importers: version: 4.1.1(prettier@3.6.2) prettier-plugin-svelte: specifier: ^3.3.3 - version: 3.4.0(prettier@3.6.2)(svelte@5.43.12) + version: 3.4.0(prettier@3.6.2)(svelte@5.45.2) rollup-plugin-visualizer: specifier: ^6.0.0 version: 6.0.5(rollup@4.53.3) svelte: - specifier: 5.43.12 - version: 5.43.12 + specifier: 5.45.2 + version: 5.45.2 svelte-check: specifier: ^4.1.5 - version: 4.3.4(picomatch@4.0.3)(svelte@5.43.12)(typescript@5.9.3) + version: 4.3.4(picomatch@4.0.3)(svelte@5.45.2)(typescript@5.9.3) svelte-eslint-parser: specifier: ^1.3.3 - version: 1.4.0(svelte@5.43.12) + version: 1.4.0(svelte@5.45.2) tailwindcss: specifier: ^4.1.7 version: 4.1.17 @@ -6762,8 +6762,8 @@ packages: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} - esrap@2.1.0: - resolution: {integrity: sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==} + esrap@2.2.0: + resolution: {integrity: sha512-WBmtxe7R9C5mvL4n2le8nMUe4mD5V9oiK2vJpQ9I3y20ENPUomPcphBXE8D1x/Bm84oN1V+lOfgXxtqmxTp3Xg==} esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} @@ -10772,8 +10772,8 @@ packages: peerDependencies: svelte: ^5.30.2 - svelte@5.43.12: - resolution: {integrity: sha512-d1R+3pFa39LXoHCsxHmV//D2pSFZlEMlnxCVQ54TlrQv+4o5pewJO0/Pc5MUp+j71PJrOrPJHTvREZJHn+ymDQ==} + svelte@5.45.2: + resolution: {integrity: sha512-yyXdW2u3H0H/zxxWoGwJoQlRgaSJLp+Vhktv12iRw2WRDlKqUPT54Fi0K/PkXqrdkcQ98aBazpy0AH4BCBVfoA==} engines: {node: '>=18'} svg-parser@2.0.4: @@ -14695,19 +14695,19 @@ snapshots: '@immich/justified-layout-wasm@0.4.3': {} - '@immich/svelte-markdown-preprocess@0.1.0(svelte@5.43.12)': + '@immich/svelte-markdown-preprocess@0.1.0(svelte@5.45.2)': dependencies: - svelte: 5.43.12 + svelte: 5.45.2 - '@immich/ui@0.49.2(svelte@5.43.12)': + '@immich/ui@0.49.2(svelte@5.45.2)': dependencies: - '@immich/svelte-markdown-preprocess': 0.1.0(svelte@5.43.12) + '@immich/svelte-markdown-preprocess': 0.1.0(svelte@5.45.2) '@internationalized/date': 3.10.0 '@mdi/js': 7.4.47 - bits-ui: 2.9.8(@internationalized/date@3.10.0)(svelte@5.43.12) + bits-ui: 2.9.8(@internationalized/date@3.10.0)(svelte@5.45.2) luxon: 3.7.2 simple-icons: 15.21.0 - svelte: 5.43.12 + svelte: 5.45.2 svelte-highlight: 7.8.4 tailwind-merge: 3.3.1 tailwind-variants: 3.1.1(tailwind-merge@3.3.1)(tailwindcss@4.1.17) @@ -16208,17 +16208,17 @@ snapshots: dependencies: acorn: 8.15.0 - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.48.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))': + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.48.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))': dependencies: - '@sveltejs/kit': 2.48.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) + '@sveltejs/kit': 2.48.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) - '@sveltejs/enhanced-img@0.8.5(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(rollup@4.53.3)(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': + '@sveltejs/enhanced-img@0.8.5(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(rollup@4.53.3)(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) magic-string: 0.30.21 sharp: 0.34.5 - svelte: 5.43.12 - svelte-parse-markup: 0.1.5(svelte@5.43.12) + svelte: 5.45.2 + svelte-parse-markup: 0.1.5(svelte@5.45.2) vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) vite-imagetools: 8.0.0(rollup@4.53.3) zimmerframe: 1.1.4 @@ -16226,11 +16226,11 @@ snapshots: - rollup - supports-color - '@sveltejs/kit@2.48.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': + '@sveltejs/kit@2.48.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@standard-schema/spec': 1.0.0 '@sveltejs/acorn-typescript': 1.0.7(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 0.6.0 @@ -16242,27 +16242,27 @@ snapshots: sade: 1.8.1 set-cookie-parser: 2.7.2 sirv: 3.0.2 - svelte: 5.43.12 + svelte: 5.45.2 vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) optionalDependencies: '@opentelemetry/api': 1.9.0 - '@sveltejs/vite-plugin-svelte-inspector@5.0.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) debug: 4.4.3 - svelte: 5.43.12 + svelte: 5.45.2 vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) debug: 4.4.3 deepmerge: 4.3.1 magic-string: 0.30.21 - svelte: 5.43.12 + svelte: 5.45.2 vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) vitefu: 1.1.1(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) transitivePeerDependencies: @@ -16510,10 +16510,10 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/svelte@5.2.9(svelte@5.43.12)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': + '@testing-library/svelte@5.2.9(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@testing-library/dom': 10.4.1 - svelte: 5.43.12 + svelte: 5.45.2 optionalDependencies: vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) @@ -17240,10 +17240,10 @@ snapshots: dependencies: '@namnode/store': 0.1.0 - '@zoom-image/svelte@0.3.7(svelte@5.43.12)': + '@zoom-image/svelte@0.3.7(svelte@5.45.2)': dependencies: '@zoom-image/core': 0.41.3 - svelte: 5.43.12 + svelte: 5.45.2 abab@2.0.6: optional: true @@ -17606,15 +17606,15 @@ snapshots: binary-extensions@2.3.0: {} - bits-ui@2.9.8(@internationalized/date@3.10.0)(svelte@5.43.12): + bits-ui@2.9.8(@internationalized/date@3.10.0)(svelte@5.45.2): dependencies: '@floating-ui/core': 1.7.3 '@floating-ui/dom': 1.7.4 '@internationalized/date': 3.10.0 esm-env: 1.2.2 - runed: 0.29.2(svelte@5.43.12) - svelte: 5.43.12 - svelte-toolbelt: 0.9.3(svelte@5.43.12) + runed: 0.29.2(svelte@5.45.2) + svelte: 5.45.2 + svelte-toolbelt: 0.9.3(svelte@5.45.2) tabbable: 6.3.0 bl@4.1.0: @@ -18921,7 +18921,7 @@ snapshots: '@types/eslint': 9.6.1 eslint-config-prettier: 10.1.8(eslint@9.39.1(jiti@2.6.1)) - eslint-plugin-svelte@3.13.0(eslint@9.39.1(jiti@2.6.1))(svelte@5.43.12): + eslint-plugin-svelte@3.13.0(eslint@9.39.1(jiti@2.6.1))(svelte@5.45.2): dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) '@jridgewell/sourcemap-codec': 1.5.5 @@ -18933,9 +18933,9 @@ snapshots: postcss-load-config: 3.1.4(postcss@8.5.6) postcss-safe-parser: 7.0.1(postcss@8.5.6) semver: 7.7.3 - svelte-eslint-parser: 1.4.0(svelte@5.43.12) + svelte-eslint-parser: 1.4.0(svelte@5.45.2) optionalDependencies: - svelte: 5.43.12 + svelte: 5.45.2 transitivePeerDependencies: - ts-node @@ -19037,7 +19037,7 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@2.1.0: + esrap@2.2.0: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -22623,10 +22623,10 @@ snapshots: dependencies: prettier: 3.6.2 - prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.43.12): + prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.45.2): dependencies: prettier: 3.6.2 - svelte: 5.43.12 + svelte: 5.45.2 prettier@3.6.2: {} @@ -23232,10 +23232,10 @@ snapshots: dependencies: queue-microtask: 1.2.3 - runed@0.29.2(svelte@5.43.12): + runed@0.29.2(svelte@5.45.2): dependencies: esm-env: 1.2.2 - svelte: 5.43.12 + svelte: 5.45.2 rw@1.3.3: {} @@ -23851,19 +23851,19 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.3.4(picomatch@4.0.3)(svelte@5.43.12)(typescript@5.9.3): + svelte-check@4.3.4(picomatch@4.0.3)(svelte@5.45.2)(typescript@5.9.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 fdir: 6.5.0(picomatch@4.0.3) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.43.12 + svelte: 5.45.2 typescript: 5.9.3 transitivePeerDependencies: - picomatch - svelte-eslint-parser@1.4.0(svelte@5.43.12): + svelte-eslint-parser@1.4.0(svelte@5.45.2): dependencies: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -23872,7 +23872,7 @@ snapshots: postcss-scss: 4.0.9(postcss@8.5.6) postcss-selector-parser: 7.1.0 optionalDependencies: - svelte: 5.43.12 + svelte: 5.45.2 svelte-gestures@5.2.2: {} @@ -23880,7 +23880,7 @@ snapshots: dependencies: highlight.js: 11.11.1 - svelte-i18n@4.0.1(svelte@5.43.12): + svelte-i18n@4.0.1(svelte@5.45.2): dependencies: cli-color: 2.0.4 deepmerge: 4.3.1 @@ -23888,34 +23888,34 @@ snapshots: estree-walker: 2.0.2 intl-messageformat: 10.7.18 sade: 1.8.1 - svelte: 5.43.12 + svelte: 5.45.2 tiny-glob: 0.2.9 - svelte-maplibre@1.2.5(svelte@5.43.12): + svelte-maplibre@1.2.5(svelte@5.45.2): dependencies: d3-geo: 3.1.1 dequal: 2.0.3 just-compare: 2.3.0 maplibre-gl: 5.13.0 pmtiles: 3.2.1 - svelte: 5.43.12 + svelte: 5.45.2 - svelte-parse-markup@0.1.5(svelte@5.43.12): + svelte-parse-markup@0.1.5(svelte@5.45.2): dependencies: - svelte: 5.43.12 + svelte: 5.45.2 - svelte-persisted-store@0.12.0(svelte@5.43.12): + svelte-persisted-store@0.12.0(svelte@5.45.2): dependencies: - svelte: 5.43.12 + svelte: 5.45.2 - svelte-toolbelt@0.9.3(svelte@5.43.12): + svelte-toolbelt@0.9.3(svelte@5.45.2): dependencies: clsx: 2.1.1 - runed: 0.29.2(svelte@5.43.12) + runed: 0.29.2(svelte@5.45.2) style-to-object: 1.0.11 - svelte: 5.43.12 + svelte: 5.45.2 - svelte@5.43.12: + svelte@5.45.2: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 @@ -23925,8 +23925,9 @@ snapshots: aria-query: 5.3.2 axobject-query: 4.1.0 clsx: 2.1.1 + devalue: 5.5.0 esm-env: 1.2.2 - esrap: 2.1.0 + esrap: 2.2.0 is-reference: 3.0.3 locate-character: 3.0.0 magic-string: 0.30.21 diff --git a/web/package.json b/web/package.json index fca762ef34..c44dd6d4b7 100644 --- a/web/package.json +++ b/web/package.json @@ -97,7 +97,7 @@ "prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-svelte": "^3.3.3", "rollup-plugin-visualizer": "^6.0.0", - "svelte": "5.43.12", + "svelte": "5.45.2", "svelte-check": "^4.1.5", "svelte-eslint-parser": "^1.3.3", "tailwindcss": "^4.1.7", From e36261b5529bdc5d0c359c4a3b36fdae9409ab93 Mon Sep 17 00:00:00 2001 From: Mees Frensel <33722705+meesfrensel@users.noreply.github.com> Date: Fri, 28 Nov 2025 18:50:16 +0100 Subject: [PATCH 07/13] fix(web): integrate zoom toggle button into panorama photo viewer (#24189) --- .../asset-viewer/asset-viewer.svelte | 2 +- .../asset-viewer/image-panorama-viewer.svelte | 8 ++-- .../photo-sphere-viewer-adapter.svelte | 39 ++++++++++++++++--- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 7570278e51..b657f34ece 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -512,7 +512,7 @@ {:else if asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR || (asset.originalPath && asset.originalPath .toLowerCase() .endsWith('.insp'))} - + {:else if isShowEditor && selectedEditType === 'crop'} {:else} diff --git a/web/src/lib/components/asset-viewer/image-panorama-viewer.svelte b/web/src/lib/components/asset-viewer/image-panorama-viewer.svelte index 7334aab4d4..08ba43526d 100644 --- a/web/src/lib/components/asset-viewer/image-panorama-viewer.svelte +++ b/web/src/lib/components/asset-viewer/image-panorama-viewer.svelte @@ -7,11 +7,12 @@ import { t } from 'svelte-i18n'; import { fade } from 'svelte/transition'; - interface Props { + type Props = { asset: AssetResponseDto; - } + zoomToggle?: (() => void) | null; + }; - const { asset }: Props = $props(); + let { asset, zoomToggle = $bindable() }: Props = $props(); const loadAssetData = async (id: string) => { const data = await viewAsset({ ...authManager.params, id, size: AssetMediaSize.Preview }); @@ -24,6 +25,7 @@ {:then [data, { default: PhotoSphereViewer }]} + import { shortcuts } from '$lib/actions/shortcut'; import { boundingBoxesArray, type Faces } from '$lib/stores/people.store'; import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store'; + import { photoZoomState } from '$lib/stores/zoom-image.store'; import { EquirectangularAdapter, Viewer, @@ -24,15 +26,23 @@ strokeLinejoin: 'round', }; - interface Props { + type Props = { panorama: string | { source: string }; originalPanorama?: string | { source: string }; adapter?: AdapterConstructor | [AdapterConstructor, unknown]; plugins?: (PluginConstructor | [PluginConstructor, unknown])[]; navbar?: boolean; - } + zoomToggle?: (() => void) | null; + }; - let { panorama, originalPanorama, adapter = EquirectangularAdapter, plugins = [], navbar = false }: Props = $props(); + let { + panorama, + originalPanorama, + adapter = EquirectangularAdapter, + plugins = [], + navbar = false, + zoomToggle = $bindable(), + }: Props = $props(); let container: HTMLDivElement | undefined = $state(); let viewer: Viewer; @@ -93,6 +103,14 @@ } }); + zoomToggle = () => { + if (!viewer) { + return; + } + viewer.animate({ zoom: $photoZoomState.currentZoom > 1 ? 50 : 83.3, speed: 250 }); + }; + + let hasChangedResolution: boolean = false; onMount(() => { if (!container) { return; @@ -139,10 +157,15 @@ const resolutionPlugin = viewer.getPlugin(ResolutionPlugin); const zoomHandler = ({ zoomLevel }: events.ZoomUpdatedEvent) => { // zoomLevel range: [0, 100] - if (Math.round(zoomLevel) >= 75) { + photoZoomState.set({ + ...$photoZoomState, + currentZoom: zoomLevel / 50, + }); + + if (Math.round(zoomLevel) >= 75 && !hasChangedResolution) { // Replace the preview with the original void resolutionPlugin.setResolution('original'); - viewer.removeEventListener(events.ZoomUpdatedEvent.type, zoomHandler); + hasChangedResolution = true; } }; @@ -158,7 +181,13 @@ viewer.destroy(); } boundingBoxesUnsubscribe(); + // zoomHandler is not called on initial load. Viewer initial zoom is 1, but photoZoomState could be != 1. + photoZoomState.set({ + ...$photoZoomState, + currentZoom: 1, + }); }); +
From 08f320c8015da94496f7ca580f079db46151e8b9 Mon Sep 17 00:00:00 2001 From: Niklas von Moers <49690836+NiklasvonM@users.noreply.github.com> Date: Sat, 29 Nov 2025 13:09:32 +0100 Subject: [PATCH 08/13] fix(web): use full tag path when creating nested subtags (#24249) --- web/src/lib/modals/TagCreateModal.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/lib/modals/TagCreateModal.svelte b/web/src/lib/modals/TagCreateModal.svelte index 360681d7b9..5c48e83b08 100644 --- a/web/src/lib/modals/TagCreateModal.svelte +++ b/web/src/lib/modals/TagCreateModal.svelte @@ -14,7 +14,7 @@ const { onClose, baseTag }: Props = $props(); - let tagValue = $state(baseTag?.value ? `${baseTag.value}/` : ''); + let tagValue = $state(baseTag?.path ? `${baseTag.path}/` : ''); const createTag = async () => { const [tag] = await upsertTags({ tagUpsertDto: { tags: [tagValue] } }); From e3ab16a5bd7883605211a18ee8a13aa225f7fddf Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 30 Nov 2025 12:43:33 -0600 Subject: [PATCH 09/13] chore: refactor mobile events (#24263) chore: refactor mobile evets --- mobile/lib/domain/models/events.model.dart | 32 +++++++++++++++++++ mobile/lib/domain/models/timeline.model.dart | 16 ---------- .../lib/domain/services/timeline.service.dart | 1 + .../drift_backup_asset_detail.page.dart | 2 +- mobile/lib/pages/common/tab_shell.page.dart | 3 +- .../archive_action_button.widget.dart | 2 +- .../delete_action_button.widget.dart | 2 +- .../delete_local_action_button.widget.dart | 2 +- ...delete_permanent_action_button.widget.dart | 2 +- ...e_to_lock_folder_action_button.widget.dart | 2 +- .../trash_action_button.widget.dart | 2 +- .../unarchive_action_button.widget.dart | 2 +- .../asset_viewer/asset_viewer.page.dart | 2 +- .../asset_viewer/asset_viewer.state.dart | 10 ------ .../asset_viewer/top_app_bar.widget.dart | 2 +- .../memory/memory_bottom_info.widget.dart | 2 +- .../widgets/timeline/timeline.widget.dart | 1 + .../timeline/multiselect.provider.dart | 6 ---- .../common/mesmerizing_sliver_app_bar.dart | 2 +- .../widgets/common/person_sliver_app_bar.dart | 2 +- .../common/remote_album_sliver_app_bar.dart | 2 +- 21 files changed, 49 insertions(+), 48 deletions(-) create mode 100644 mobile/lib/domain/models/events.model.dart diff --git a/mobile/lib/domain/models/events.model.dart b/mobile/lib/domain/models/events.model.dart new file mode 100644 index 0000000000..b3ab756414 --- /dev/null +++ b/mobile/lib/domain/models/events.model.dart @@ -0,0 +1,32 @@ +import 'package:immich_mobile/domain/utils/event_stream.dart'; + +// Timeline Events +class TimelineReloadEvent extends Event { + const TimelineReloadEvent(); +} + +class ScrollToTopEvent extends Event { + const ScrollToTopEvent(); +} + +class ScrollToDateEvent extends Event { + final DateTime date; + + const ScrollToDateEvent(this.date); +} + +// Asset Viewer Events +class ViewerOpenBottomSheetEvent extends Event { + final bool activitiesMode; + const ViewerOpenBottomSheetEvent({this.activitiesMode = false}); +} + +class ViewerReloadAssetEvent extends Event { + const ViewerReloadAssetEvent(); +} + +// Multi-Select Events +class MultiSelectToggleEvent extends Event { + final bool isEnabled; + const MultiSelectToggleEvent(this.isEnabled); +} diff --git a/mobile/lib/domain/models/timeline.model.dart b/mobile/lib/domain/models/timeline.model.dart index d4cc5ab5c6..c531fa4a94 100644 --- a/mobile/lib/domain/models/timeline.model.dart +++ b/mobile/lib/domain/models/timeline.model.dart @@ -1,5 +1,3 @@ -import 'package:immich_mobile/domain/utils/event_stream.dart'; - enum GroupAssetsBy { day, month, auto, none } enum HeaderType { none, month, day, monthAndDay } @@ -31,17 +29,3 @@ class TimeBucket extends Bucket { @override int get hashCode => super.hashCode ^ date.hashCode; } - -class TimelineReloadEvent extends Event { - const TimelineReloadEvent(); -} - -class ScrollToTopEvent extends Event { - const ScrollToTopEvent(); -} - -class ScrollToDateEvent extends Event { - final DateTime date; - - const ScrollToDateEvent(this.date); -} diff --git a/mobile/lib/domain/services/timeline.service.dart b/mobile/lib/domain/services/timeline.service.dart index 9537fe667a..96630f1eba 100644 --- a/mobile/lib/domain/services/timeline.service.dart +++ b/mobile/lib/domain/services/timeline.service.dart @@ -4,6 +4,7 @@ import 'dart:math' as math; import 'package:collection/collection.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/services/setting.service.dart'; diff --git a/mobile/lib/pages/backup/drift_backup_asset_detail.page.dart b/mobile/lib/pages/backup/drift_backup_asset_detail.page.dart index f3fdccc329..36d51c5624 100644 --- a/mobile/lib/pages/backup/drift_backup_asset_detail.page.dart +++ b/mobile/lib/pages/backup/drift_backup_asset_detail.page.dart @@ -3,7 +3,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; diff --git a/mobile/lib/pages/common/tab_shell.page.dart b/mobile/lib/pages/common/tab_shell.page.dart index bbb567bd3b..2fdcec4054 100644 --- a/mobile/lib/pages/common/tab_shell.page.dart +++ b/mobile/lib/pages/common/tab_shell.page.dart @@ -5,7 +5,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/presentation/pages/search/paginated_search.provider.dart'; @@ -16,7 +16,6 @@ import 'package:immich_mobile/providers/infrastructure/people.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; import 'package:immich_mobile/providers/search/search_input_focus.provider.dart'; import 'package:immich_mobile/providers/tab.provider.dart'; -import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/routing/router.dart'; @RoutePage() diff --git a/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart index 290a19f584..4ba877bcba 100644 --- a/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart index 723700af55..8b82e5c839 100644 --- a/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart index 3cd939aeb6..5d8ea8671c 100644 --- a/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart index 4979df904c..a0191e2407 100644 --- a/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; diff --git a/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart index ddc83cb383..20d391c4a6 100644 --- a/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; diff --git a/mobile/lib/presentation/widgets/action_buttons/trash_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/trash_action_button.widget.dart index df8f544601..a78ff2ccd8 100644 --- a/mobile/lib/presentation/widgets/action_buttons/trash_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/trash_action_button.widget.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; diff --git a/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart index 8b04a1b05d..32147a194f 100644 --- a/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart @@ -9,8 +9,8 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_bu import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; // used to allow performing unarchive action from different sources (without duplicating code) Future performUnArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async { diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart index 50c4347301..70eb6699aa 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart @@ -7,7 +7,7 @@ import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.state.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.state.dart index d0fb1f8ba0..36e5bf67d9 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.state.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.state.dart @@ -1,17 +1,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -class ViewerOpenBottomSheetEvent extends Event { - final bool activitiesMode; - const ViewerOpenBottomSheetEvent({this.activitiesMode = false}); -} - -class ViewerReloadAssetEvent extends Event { - const ViewerReloadAssetEvent(); -} - class AssetViewerState { final int backgroundOpacity; final bool showingBottomSheet; diff --git a/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart index ab88dffab4..5114ef6fd2 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; diff --git a/mobile/lib/presentation/widgets/memory/memory_bottom_info.widget.dart b/mobile/lib/presentation/widgets/memory/memory_bottom_info.widget.dart index f067bc6bf3..b514f9f0a5 100644 --- a/mobile/lib/presentation/widgets/memory/memory_bottom_info.widget.dart +++ b/mobile/lib/presentation/widgets/memory/memory_bottom_info.widget.dart @@ -3,8 +3,8 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/models/memory.model.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/routing/router.dart'; diff --git a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart index 70dd15bf7f..5868de92aa 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart @@ -9,6 +9,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; diff --git a/mobile/lib/providers/timeline/multiselect.provider.dart b/mobile/lib/providers/timeline/multiselect.provider.dart index 6949413cd9..0b3f7e610b 100644 --- a/mobile/lib/providers/timeline/multiselect.provider.dart +++ b/mobile/lib/providers/timeline/multiselect.provider.dart @@ -2,7 +2,6 @@ import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; -import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; final multiSelectProvider = NotifierProvider( @@ -10,11 +9,6 @@ final multiSelectProvider = NotifierProvider selectedAssets; final Set lockedSelectionAssets; diff --git a/mobile/lib/widgets/common/mesmerizing_sliver_app_bar.dart b/mobile/lib/widgets/common/mesmerizing_sliver_app_bar.dart index 73dbbfc85b..44b547a5f1 100644 --- a/mobile/lib/widgets/common/mesmerizing_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/mesmerizing_sliver_app_bar.dart @@ -4,7 +4,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; diff --git a/mobile/lib/widgets/common/person_sliver_app_bar.dart b/mobile/lib/widgets/common/person_sliver_app_bar.dart index 0f9555a101..d5a7ea7cd9 100644 --- a/mobile/lib/widgets/common/person_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/person_sliver_app_bar.dart @@ -6,8 +6,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/models/person.model.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; diff --git a/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart b/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart index c0661bad48..c486d473b0 100644 --- a/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart @@ -7,7 +7,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; From 922282b2b47c21b8fe77a16db34aa16ee75859f4 Mon Sep 17 00:00:00 2001 From: Chris Peckover Date: Sun, 30 Nov 2025 13:56:03 -0500 Subject: [PATCH 10/13] feat(web): Shared album owner labels (#21171) * - pass available album users along to the thumbnail through the asset-date-group - show a small user-avatar in bottom right of thumbnail * - change owner to their name in white text instead of the avatar * cleanup * - cleanup albumUsers creation - use font-light for the user's name * fix lint * format * - add toggle to show/hide asset owner names * update new Timeline with albumUsers * add @idubnori suggestion for the name font * Don't show 'view owners' button if the album doesn't have editors * add missing import * format * fix(web): #21171 (#24298) fix: Bind timelineManager to Timeline component --------- Co-authored-by: idubnori Co-authored-by: Alex --- .../assets/thumbnail/thumbnail.svelte | 14 +++++++++++++- .../lib/components/timeline/Timeline.svelte | 5 ++++- .../[[assetId=id]]/+page.svelte | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/web/src/lib/components/assets/thumbnail/thumbnail.svelte b/web/src/lib/components/assets/thumbnail/thumbnail.svelte index 0645541241..38d734fc22 100644 --- a/web/src/lib/components/assets/thumbnail/thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/thumbnail.svelte @@ -4,7 +4,7 @@ import { getAssetOriginalUrl, getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils'; import { timeToSeconds } from '$lib/utils/date-time'; import { getAltText } from '$lib/utils/thumbnail-util'; - import { AssetMediaSize, AssetVisibility } from '@immich/sdk'; + import { AssetMediaSize, AssetVisibility, type UserResponseDto } from '@immich/sdk'; import { mdiArchiveArrowDownOutline, mdiCameraBurst, @@ -46,6 +46,7 @@ imageClass?: ClassValue; brokenAssetClass?: ClassValue; dimmed?: boolean; + albumUsers?: UserResponseDto[]; onClick?: (asset: TimelineAsset) => void; onSelect?: (asset: TimelineAsset) => void; onMouseEvent?: (event: { isMouseOver: boolean; selectedGroupIndex: number }) => void; @@ -64,6 +65,7 @@ readonly = false, showArchiveIcon = false, showStackedIcon = true, + albumUsers = [], onClick = undefined, onSelect = undefined, onMouseEvent = undefined, @@ -85,6 +87,8 @@ let width = $derived(thumbnailSize || thumbnailWidth || 235); let height = $derived(thumbnailSize || thumbnailHeight || 235); + let assetOwner = $derived(albumUsers?.find((user) => user.id === asset.ownerId) ?? null); + const onIconClickedHandler = (e?: MouseEvent) => { e?.stopPropagation(); e?.preventDefault(); @@ -268,6 +272,14 @@ {/if} + {#if !!assetOwner} +
+

+ {assetOwner.name} +

+
+ {/if} + {#if !authManager.isSharedLink && showArchiveIcon && asset.visibility === AssetVisibility.Archive}
diff --git a/web/src/lib/components/timeline/Timeline.svelte b/web/src/lib/components/timeline/Timeline.svelte index d2873eca70..ba9cf37bff 100644 --- a/web/src/lib/components/timeline/Timeline.svelte +++ b/web/src/lib/components/timeline/Timeline.svelte @@ -23,7 +23,7 @@ import { mobileDevice } from '$lib/stores/mobile-device.svelte'; import { isAssetViewerRoute, navigate } from '$lib/utils/navigation'; import { getTimes, type ScrubberListener } from '$lib/utils/timeline-util'; - import { type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk'; + import { type AlbumResponseDto, type PersonResponseDto, type UserResponseDto } from '@immich/sdk'; import { DateTime } from 'luxon'; import { onDestroy, onMount, type Snippet } from 'svelte'; import type { UpdatePayload } from 'vite'; @@ -49,6 +49,7 @@ showArchiveIcon?: boolean; isShared?: boolean; album?: AlbumResponseDto | null; + albumUsers?: UserResponseDto[]; person?: PersonResponseDto | null; isShowDeleteConfirmation?: boolean; onSelect?: (asset: TimelineAsset) => void; @@ -81,6 +82,7 @@ showArchiveIcon = false, isShared = false, album = null, + albumUsers = [], person = null, isShowDeleteConfirmation = $bindable(false), onSelect = () => {}, @@ -702,6 +704,7 @@ showStackedIcon={withStacked} {showArchiveIcon} {asset} + {albumUsers} {groupIndex} onClick={(asset) => { if (typeof onThumbnailClick === 'function') { diff --git a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte index b99260eee4..74b0f1d6b3 100644 --- a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -66,6 +66,7 @@ } from '@immich/sdk'; import { Button, Icon, IconButton, modalManager, toastManager } from '@immich/ui'; import { + mdiAccountEyeOutline, mdiArrowLeft, mdiCogOutline, mdiDeleteOutline, @@ -100,6 +101,7 @@ let isCreatingSharedAlbum = $state(false); let isShowActivity = $state(false); let albumOrder: AssetOrder | undefined = $state(data.album.order); + let showAlbumUsers = $state(false); const assetInteraction = new AssetInteraction(); const timelineInteraction = new AssetInteraction(); @@ -290,6 +292,11 @@ let album = $derived(data.album); let albumId = $derived(album.id); + const containsEditors = $derived(album?.shared && album.albumUsers.some(({ role }) => role === AlbumUserRole.Editor)); + const albumUsers = $derived( + showAlbumUsers && containsEditors ? [album.owner, ...album.albumUsers.map(({ user }) => user)] : [], + ); + $effect(() => { if (!album.isActivityEnabled && activityManager.commentCount === 0) { isShowActivity = false; @@ -418,6 +425,7 @@ + {#if containsEditors} + (showAlbumUsers = !showAlbumUsers)} + /> + {/if} + {#if isEditor} Date: Mon, 1 Dec 2025 06:01:01 +1100 Subject: [PATCH 11/13] chore: optimisation of several UI components of the mobile app (#24098) * fix(mobile): normalize scrolling behavior in networking settings Remove ClampingScrollPhysics from networking settings page to match the scrolling behavior of other settings pages. This restores the standard iOS bounce/elastic scrolling effect. * fix(mobile): use consistent native transitions for Library pages Change Trash, Shared Links, and Folders routes from CustomRoute to AutoRoute to enable native iOS transitions with swipe-back gesture support. * fix(mobile): remove SafeArea wrapper and ClampingScrollPhysics from Settings Remove SafeArea wrapper (Scaffold handles safe areas automatically) and ClampingScrollPhysics to enable native iOS bounce scrolling. * fix(mobile): remove bottom white space in Sync Status page Replace Padding wrapper with ListView padding to match other Settings pages and eliminate bottom white space. * chore: fix Dart formatting Run dart format to fix formatting issues in settings.page.dart and sync_status_and_actions.dart * Format Dart files --------- Co-authored-by: Claude Co-authored-by: kao-byte --- mobile/lib/pages/common/settings.page.dart | 8 +- mobile/lib/routing/router.dart | 14 +- .../sync_status_and_actions.dart | 138 +++++++++--------- .../networking_settings.dart | 1 - 4 files changed, 73 insertions(+), 88 deletions(-) diff --git a/mobile/lib/pages/common/settings.page.dart b/mobile/lib/pages/common/settings.page.dart index 0fe2ccec09..86c80253dc 100644 --- a/mobile/lib/pages/common/settings.page.dart +++ b/mobile/lib/pages/common/settings.page.dart @@ -58,7 +58,7 @@ class SettingsPage extends StatelessWidget { context.locale; return Scaffold( appBar: AppBar(centerTitle: false, title: const Text('settings').tr()), - body: context.isMobile ? const SafeArea(child: _MobileLayout()) : const SafeArea(child: _TabletLayout()), + body: context.isMobile ? const _MobileLayout() : const _TabletLayout(), ); } } @@ -89,11 +89,7 @@ class _MobileLayout extends StatelessWidget { ], ) .toList(); - return ListView( - physics: const ClampingScrollPhysics(), - padding: const EdgeInsets.only(top: 10.0, bottom: 16), - children: [...settings], - ); + return ListView(padding: const EdgeInsets.only(top: 10.0, bottom: 16), children: [...settings]); } } diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index abe7ac3fa2..30f43cf3b2 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -245,23 +245,15 @@ class AppRouter extends RootStackRouter { guards: [_authGuard, _duplicateGuard], transitionsBuilder: TransitionsBuilders.slideLeft, ), - CustomRoute(page: FolderRoute.page, guards: [_authGuard], transitionsBuilder: TransitionsBuilders.fadeIn), + AutoRoute(page: FolderRoute.page, guards: [_authGuard]), AutoRoute(page: PartnerDetailRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: PersonResultRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: AllPeopleRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: MemoryRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: MapRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: AlbumOptionsRoute.page, guards: [_authGuard, _duplicateGuard]), - CustomRoute( - page: TrashRoute.page, - guards: [_authGuard, _duplicateGuard], - transitionsBuilder: TransitionsBuilders.slideLeft, - ), - CustomRoute( - page: SharedLinkRoute.page, - guards: [_authGuard, _duplicateGuard], - transitionsBuilder: TransitionsBuilders.slideLeft, - ), + AutoRoute(page: TrashRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: SharedLinkRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: SharedLinkEditRoute.page, guards: [_authGuard, _duplicateGuard]), CustomRoute( page: ActivitiesRoute.page, diff --git a/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart b/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart index 0296a6bd99..64c3d9b832 100644 --- a/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart +++ b/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart @@ -108,82 +108,80 @@ class SyncStatusAndActions extends HookConsumerWidget { ); } - return Padding( - padding: const EdgeInsets.only(top: 16, bottom: 32), - child: ListView( - children: [ - const _SyncStatsCounts(), - const Divider(height: 1, indent: 16, endIndent: 16), - const SizedBox(height: 24), - _SectionHeaderText(text: "jobs".t(context: context)), - ListTile( - title: Text( - "sync_local".t(context: context), - style: const TextStyle(fontWeight: FontWeight.w500), - ), - subtitle: Text("tap_to_run_job".t(context: context)), - leading: const Icon(Icons.sync), - trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).localSyncStatus), - onTap: () { - ref.read(backgroundSyncProvider).syncLocal(full: true); - }, + return ListView( + padding: const EdgeInsets.only(top: 16, bottom: 96), + children: [ + const _SyncStatsCounts(), + const Divider(height: 1, indent: 16, endIndent: 16), + const SizedBox(height: 24), + _SectionHeaderText(text: "jobs".t(context: context)), + ListTile( + title: Text( + "sync_local".t(context: context), + style: const TextStyle(fontWeight: FontWeight.w500), ), - ListTile( - title: Text( - "sync_remote".t(context: context), - style: const TextStyle(fontWeight: FontWeight.w500), - ), - subtitle: Text("tap_to_run_job".t(context: context)), - leading: const Icon(Icons.cloud_sync), - trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).remoteSyncStatus), - onTap: () { - ref.read(backgroundSyncProvider).syncRemote(); - }, + subtitle: Text("tap_to_run_job".t(context: context)), + leading: const Icon(Icons.sync), + trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).localSyncStatus), + onTap: () { + ref.read(backgroundSyncProvider).syncLocal(full: true); + }, + ), + ListTile( + title: Text( + "sync_remote".t(context: context), + style: const TextStyle(fontWeight: FontWeight.w500), ), - ListTile( - title: Text( - "hash_asset".t(context: context), - style: const TextStyle(fontWeight: FontWeight.w500), - ), - leading: const Icon(Icons.tag), - subtitle: Text("tap_to_run_job".t(context: context)), - trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).hashJobStatus), - onTap: () { - ref.read(backgroundSyncProvider).hashAssets(); - }, + subtitle: Text("tap_to_run_job".t(context: context)), + leading: const Icon(Icons.cloud_sync), + trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).remoteSyncStatus), + onTap: () { + ref.read(backgroundSyncProvider).syncRemote(); + }, + ), + ListTile( + title: Text( + "hash_asset".t(context: context), + style: const TextStyle(fontWeight: FontWeight.w500), ), - const Divider(height: 1, indent: 16, endIndent: 16), - const SizedBox(height: 24), - _SectionHeaderText(text: "actions".t(context: context)), - ListTile( - title: Text( - "clear_file_cache".t(context: context), - style: const TextStyle(fontWeight: FontWeight.w500), - ), - leading: const Icon(Icons.playlist_remove_rounded), - onTap: clearFileCache, + leading: const Icon(Icons.tag), + subtitle: Text("tap_to_run_job".t(context: context)), + trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).hashJobStatus), + onTap: () { + ref.read(backgroundSyncProvider).hashAssets(); + }, + ), + const Divider(height: 1, indent: 16, endIndent: 16), + const SizedBox(height: 24), + _SectionHeaderText(text: "actions".t(context: context)), + ListTile( + title: Text( + "clear_file_cache".t(context: context), + style: const TextStyle(fontWeight: FontWeight.w500), ), - ListTile( - title: Text( - "export_database".t(context: context), - style: const TextStyle(fontWeight: FontWeight.w500), - ), - subtitle: Text("export_database_description".t(context: context)), - leading: const Icon(Icons.download), - onTap: exportDatabase, + leading: const Icon(Icons.playlist_remove_rounded), + onTap: clearFileCache, + ), + ListTile( + title: Text( + "export_database".t(context: context), + style: const TextStyle(fontWeight: FontWeight.w500), ), - ListTile( - title: Text( - "reset_sqlite".t(context: context), - style: TextStyle(color: context.colorScheme.error, fontWeight: FontWeight.w500), - ), - leading: Icon(Icons.settings_backup_restore_rounded, color: context.colorScheme.error), - onTap: () async { - await resetSqliteDb(context); - }, + subtitle: Text("export_database_description".t(context: context)), + leading: const Icon(Icons.download), + onTap: exportDatabase, + ), + ListTile( + title: Text( + "reset_sqlite".t(context: context), + style: TextStyle(color: context.colorScheme.error, fontWeight: FontWeight.w500), ), - ], - ), + leading: Icon(Icons.settings_backup_restore_rounded, color: context.colorScheme.error), + onTap: () async { + await resetSqliteDb(context); + }, + ), + ], ); } } diff --git a/mobile/lib/widgets/settings/networking_settings/networking_settings.dart b/mobile/lib/widgets/settings/networking_settings/networking_settings.dart index 426ea5ac0f..272b83c9aa 100644 --- a/mobile/lib/widgets/settings/networking_settings/networking_settings.dart +++ b/mobile/lib/widgets/settings/networking_settings/networking_settings.dart @@ -86,7 +86,6 @@ class NetworkingSettings extends HookConsumerWidget { return ListView( padding: const EdgeInsets.only(bottom: 96), - physics: const ClampingScrollPhysics(), children: [ Padding( padding: const EdgeInsets.only(top: 8, left: 16, bottom: 8), From 46afd6a101c19046119ad9137fda29557f042552 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 30 Nov 2025 13:01:12 -0600 Subject: [PATCH 12/13] fix: only generate memory based on users assets (#24151) --- server/src/services/memory.service.ts | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/server/src/services/memory.service.ts b/server/src/services/memory.service.ts index 1d39169f3e..8e91c232f7 100644 --- a/server/src/services/memory.service.ts +++ b/server/src/services/memory.service.ts @@ -6,7 +6,7 @@ import { AuthDto } from 'src/dtos/auth.dto'; import { MemoryCreateDto, MemoryResponseDto, MemorySearchDto, MemoryUpdateDto, mapMemory } from 'src/dtos/memory.dto'; import { DatabaseLock, JobName, MemoryType, Permission, QueueName, SystemMetadataKey } from 'src/enum'; import { BaseService } from 'src/services/base.service'; -import { addAssets, getMyPartnerIds, removeAssets } from 'src/utils/asset.util'; +import { addAssets, removeAssets } from 'src/utils/asset.util'; const DAYS = 3; @@ -15,15 +15,6 @@ export class MemoryService extends BaseService { @OnJob({ name: JobName.MemoryGenerate, queue: QueueName.BackgroundTask }) async onMemoriesCreate() { const users = await this.userRepository.getList({ withDeleted: false }); - const usersIds = await Promise.all( - users.map((user) => - getMyPartnerIds({ - userId: user.id, - repository: this.partnerRepository, - timelineEnabled: true, - }), - ), - ); await this.databaseRepository.withLock(DatabaseLock.MemoryCreation, async () => { const state = await this.systemMetadataRepository.get(SystemMetadataKey.MemoriesState); @@ -38,7 +29,7 @@ export class MemoryService extends BaseService { } try { - await Promise.all(users.map((owner, i) => this.createOnThisDayMemories(owner.id, usersIds[i], target))); + await Promise.all(users.map((owner) => this.createOnThisDayMemories(owner.id, target))); } catch (error) { this.logger.error(`Failed to create memories for ${target.toISO()}: ${error}`); } @@ -51,10 +42,10 @@ export class MemoryService extends BaseService { }); } - private async createOnThisDayMemories(ownerId: string, userIds: string[], target: DateTime) { + private async createOnThisDayMemories(ownerId: string, target: DateTime) { const showAt = target.startOf('day').toISO(); const hideAt = target.endOf('day').toISO(); - const memories = await this.assetRepository.getByDayOfYear([ownerId, ...userIds], target); + const memories = await this.assetRepository.getByDayOfYear([ownerId], target); await Promise.all( memories.map(({ year, assets }) => this.memoryRepository.create( From fa43fae2a5c4f7a0bfdf0989b7138492aaaa6640 Mon Sep 17 00:00:00 2001 From: Matthew Momjian <50788000+mmomjian@users.noreply.github.com> Date: Sun, 30 Nov 2025 14:01:33 -0500 Subject: [PATCH 13/13] fix(mobile): docs link (#24277) update docs link --- mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart index c6a557964d..53fc32ddb3 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart @@ -193,7 +193,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { InkWell( onTap: () { context.pop(); - launchUrl(Uri.parse('https://immich.app'), mode: LaunchMode.externalApplication); + launchUrl(Uri.parse('https://docs.immich.app'), mode: LaunchMode.externalApplication); }, child: Text("documentation", style: context.textTheme.bodySmall).tr(), ),