Compare commits

..

53 Commits

Author SHA1 Message Date
bo0tzz
f163cc07f9 fix: force docs-build on deployment changes 2025-10-13 17:44:17 +02:00
luzpaz
46869f664d fix: various typos (#22867)
Found via `codespell -q 3 -S "*.svg,./i18n,./docs/package-lock.json,./readme_i18n,./mobile/assets/i18n" -L afterall,devlop,finaly,inout,nd,optin,renderd,sade`
2025-10-13 15:25:23 +05:30
renovate[bot]
f2b553182a chore(deps): update github-actions (#22793)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-12 12:59:36 +02:00
renovate[bot]
8fe54a4de1 fix(deps): update dependency happy-dom to v20 [security] (#22846)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-11 13:17:25 +02:00
Min Idzelis
ce4e8fa6ba feat: (perf) remove scroll compensation (#22837) 2025-10-10 15:48:29 -04:00
Brandon Wees
efa21af6f9 fix: only cast to device if the asset is a RemoteAsset (#22805) 2025-10-10 11:13:31 -05:00
renovate[bot]
ea610760ee fix(deps): update typescript-projects (#22809)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-10-10 00:50:48 +02:00
renovate[bot]
a5e0d83d9f chore(deps): update base-image to v202510092146 (major) (#22818)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-09 22:40:23 +00:00
Daniel Dietzler
9793828dc7 chore: don't enforce runes (#22813) 2025-10-09 19:17:42 +00:00
bo0tzz
aed7bb53aa fix: revert terragrunt-action bump (#22812) 2025-10-09 21:11:39 +02:00
renovate[bot]
1fdbe2c6b8 chore(deps): update github-actions (major) (#22810)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-09 20:51:51 +02:00
bo0tzz
84302dc14c fix: remove postgres exclude datasource match (#22811) 2025-10-09 18:44:20 +00:00
bo0tzz
f7250f24fe chore: ignore renovate major updates for postgres image (#22764) 2025-10-09 18:34:54 +00:00
Peter Dave Hello
53680d9643 feat(docs): add zh_TW Traditional Chinese version README (#22703)
docs: add zh_TW Traditional Chinese version README
2025-10-08 15:10:58 -04:00
Tushar Harsora
b2d00405f1 feat(server): add immich.users.total metric (#21780)
* Add immich.users.total metric

* Fix tests & one lint error

* Lint

* Fix SQL Schema checks

* Fix nit

* Use workers argument in OnEvent hook and remove condition from method body
2025-10-08 13:24:11 -04:00
Sebastian Schneider
cf60f4cdcd feat(web): Add upload to stack action (#19842)
* feat(web): Add upload to stack action

* Event handling and translation

* Update asset viewer instead

* lint, improve upload return type

* Add suggestions from code review

* Resolve merge conflicts

* Apply suggestions from code review
2025-10-08 13:22:33 -04:00
Qhilm
d764a59011 docs: add Immich-Stack to community-projects (#21563)
docs: add Immich Stack community project

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2025-10-08 17:21:44 +00:00
Jorge Montejo
7ee1b977c1 feat(cli): add debug development config (#22712)
* add debug and change ts-node with tsx

* update pr changes

* update pnpm-lock

* remove ts-node from readme

* typo

* resolve conflicts

* remove tsx

* launch from dist

* add preLaunchTask

* update readme

* undo main in package.json

* remove typo

* Apply suggestion from @bwees

Co-authored-by: Brandon Wees <brandonwees@gmail.com>

* revert pnpm-lock changes

* @jrasm91 suggestions

* chore: run node with source maps

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
Co-authored-by: Brandon Wees <brandonwees@gmail.com>
2025-10-08 17:08:33 +00:00
kaziu687
9838634067 feat(web): seconds and milliseconds in timestamps (#20337)
* fix(web): seconds in timestamps

* changed date-input step to provide millisecond precision
2025-10-08 16:30:54 +00:00
Jason Rasmussen
eee793bfe4 docs: add some external library notes (#22776) 2025-10-08 12:13:41 -04:00
shenlong
b3342323de fix: persist search page scroll offset between rebuilds (#22733)
fix: persist search scroll between rebuilds

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-10-08 10:00:51 -05:00
Pascal Sommer
6f3cb4f1bb fix(web): Uniform random distribution during shuffle (#19902)
feat: better random distribution
2025-10-08 10:19:33 -04:00
Saschl
54ed78d0bf fix: brief flashing when swiping from video (#22187) 2025-10-08 09:31:15 -04:00
shenlong
265ed0b38f fix: skip local only assets in move to lock action (#22728)
* fix:prefer trashing to deletions

* skip local only assets in move to lock action

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-10-08 04:21:34 -05:00
shenlong
63c2f4415b chore: use hosted isar flutter libs (#22757)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-10-08 04:19:46 -05:00
renovate[bot]
a7cfd7f183 fix(deps): update dependency connectivity_plus to v7 (#22723)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-10-07 21:21:43 -05:00
renovate[bot]
ee4c45d5d3 chore(deps): update dependency nodemailer to v7.0.7 [security] (#22740)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 21:24:33 +02:00
renovate[bot]
24334aa3df chore(deps): update dependency @types/node to ^22.18.8 (#22719)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 14:42:00 -04:00
Brandon Wees
882baecf21 fix: bottom sheet blank with local assets that have remote counterparts (#22743) 2025-10-07 18:04:23 +00:00
shenlong
f16327d0ab chore: use isar immich fork (#22738)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-10-07 13:13:35 -04:00
Mert
8353db6a50 chore(deps): cache pnpm dependencies in prod build (#22555)
* cache pnpm dependencies

use different ids to be safe

unnecessary lines

* use buildcache folder
2025-10-07 13:10:54 -04:00
Mert
5270107926 fix(ml): ipv6 check (#22735) 2025-10-07 12:24:23 -04:00
bo0tzz
740ca14a68 chore: track full actions/cache version in comment (#22359) 2025-10-07 15:58:26 +00:00
Min Idzelis
966ab22065 refactor(web): extract asset viewer logic from Timeline into TimelineAssetViewer component (#22268)
refactor(web): extract asset viewer logic from Timeline into TimelineAssetViewer component

- Extracted asset viewer navigation and action handling logic from Timeline.svelte into a dedicated TimelineAssetViewer component
- Reduces Timeline.svelte complexity by ~150 lines and improves separation of concerns
- No functional changes - purely a refactoring to improve code organization

## Changes
- Created new TimelineAssetViewer.svelte component containing all asset viewer-related logic
- Moved handlePrevious, handleNext, handleRandom, handleClose, handlePreAction, and handleAction methods
- Timeline.svelte now only passes required props to the new component
- Maintained all existing functionality including navigation, asset actions, and stack management
2025-10-07 14:01:06 +00:00
Min Idzelis
78fbe0fd49 feat: make skeleton title optional (#22396)
feat: skeleton title is optional

feat: skeleton title optional
2025-10-07 13:58:59 +00:00
Min Idzelis
5862c454b7 refactor(web): extract timeline keyboard actions into separate component (#22266)
refactor(web): extract timeline keyboard actions into separate component

Extracts keyboard shortcuts and related functionality from Timeline component into a dedicated TimelineKeyboardActions component for better separation of concerns and maintainability.
2025-10-07 13:52:19 +00:00
shenlong
8ee495b08f fix: promote to foreground service before starting engine (#22517)
fix: show notification from native

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-10-07 08:49:43 -05:00
Min Idzelis
83db851b00 refactor(web): Clarify property names in Timeline and Scrubber (#22265)
refactor(web): Clarify property names in Timeline and Scrubber

  Renamed properties across Timeline/Scrubber components for clarity:
  - scrubOverallPercent → timelineScrollPercent
  - scrubberMonthPercent → viewportTopMonthScrollPercent
  - scrubberMonth → viewportTopMonth
  - leadout → isInLeadOutSection

  Additional changes:
  - Updated ScrubberListener signature to accept object parameter
  - Added detailed JSDoc comments for all Scrubber props
  - Fixed callback invocations to use new object syntax
  - Aligned Timeline's local state variables with Scrubber prop names
2025-10-07 09:43:27 -04:00
bo0tzz
70037018c8 fix: --no-git-checks on pnpm publish (#22715)
* fix: --no-git-checks on sdk publish

* fix: --no-git-checks on cli publish
2025-10-07 08:33:19 -05:00
renovate[bot]
796444d211 chore(deps): update github-actions (#22721)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 08:33:07 -05:00
renovate[bot]
0d66a15d9b chore(deps): update dependency flutter to v3.35.5 (#22720)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 08:32:55 -05:00
renovate[bot]
3cf8ed5f2d fix(deps): update dependency device_info_plus to v12 (#22724)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 08:31:26 -05:00
Min Idzelis
ff01af2450 chore: update devcontainers for trixie, devenv changes (#22194) 2025-10-07 08:28:47 -05:00
renovate[bot]
2de1b832e5 chore(deps): update redis:6.2-alpine docker digest to 2185e74 (#22718)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 12:41:16 +02:00
Alex
25142bb6c6 fix: improve the selected sidebar item text color in dark mode (#22640) 2025-10-06 21:51:00 +00:00
TDR001
01660b20fd docs: update Synology install guide (#21996)
Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
2025-10-06 21:48:39 +00:00
Xantin
9affee1ea0 docs: update TrueNAS migration instructions (#22463)
Co-authored-by: bo0tzz <git@bo0tzz.me>
Co-authored-by: Nicholas Flamy <30300649+NicholasFlamy@users.noreply.github.com>
2025-10-06 23:02:43 +02:00
Yaros
d02a82b618 fix(mobile): closing editor goes back to main page (#22647)
Co-authored-by: bwees <brandonwees@gmail.com>
2025-10-06 20:56:35 +00:00
Mārtiņš Bruņenieks
ad87dff18d fix(docs): Remove immich_remove_offline_files as no longer functional (#21774)
Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>
Co-authored-by: Brandon Wees <brandonwees@gmail.com>
2025-10-06 20:54:35 +00:00
CuberL
b7e06e7b6f fix: Fix issue fail to download iOS live photos (#22708)
Co-authored-by: bwees <brandonwees@gmail.com>
2025-10-06 20:53:35 +00:00
Diogo Correia
21f49572b1 chore(server): support vectorchord 0.5.x (#21602)
Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>
2025-10-06 20:37:54 +00:00
Brandon Wees
2b7d28528d fix: hide view in timeline button on local timeline (#22713) 2025-10-06 15:28:59 -05:00
Alex
cf4cf56ac0 chore: post release tasks (#22616) 2025-10-06 20:30:23 +01:00
152 changed files with 4284 additions and 3391 deletions

View File

@@ -6,28 +6,35 @@ services:
- IMMICH_SERVER_URL=http://127.0.0.1:2283/ - IMMICH_SERVER_URL=http://127.0.0.1:2283/
volumes: !override # bind mount host to /workspaces/immich volumes: !override # bind mount host to /workspaces/immich
- ..:/workspaces/immich - ..:/workspaces/immich
- cli_node_modules:/workspaces/immich/cli/node_modules - ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data
- e2e_node_modules:/workspaces/immich/e2e/node_modules - pnpm-store:/usr/src/app/.pnpm-store
- open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules - server-node_modules:/usr/src/app/server/node_modules
- server_node_modules:/workspaces/immich/server/node_modules - web-node_modules:/usr/src/app/web/node_modules
- web_node_modules:/workspaces/immich/web/node_modules - github-node_modules:/usr/src/app/.github/node_modules
- ${UPLOAD_LOCATION}/photos:/data - cli-node_modules:/usr/src/app/cli/node_modules
- docs-node_modules:/usr/src/app/docs/node_modules
- e2e-node_modules:/usr/src/app/e2e/node_modules
- sdk-node_modules:/usr/src/app/open-api/typescript-sdk/node_modules
- app-node_modules:/usr/src/app/node_modules
- sveltekit:/usr/src/app/web/.svelte-kit
- coverage:/usr/src/app/web/coverage
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
immich-web:
env_file: !reset []
immich-machine-learning:
env_file: !reset []
database: database:
env_file: !reset []
environment: !override
POSTGRES_PASSWORD: ${DB_PASSWORD-postgres}
POSTGRES_USER: ${DB_USERNAME-postgres}
POSTGRES_DB: ${DB_DATABASE_NAME-immich}
POSTGRES_INITDB_ARGS: '--data-checksums'
POSTGRES_HOST_AUTH_METHOD: md5
volumes: volumes:
- ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data - ${UPLOAD_LOCATION:-postgres-devcontainer-volume}${UPLOAD_LOCATION:+/postgres}:/var/lib/postgresql/data
redis:
env_file: !reset []
volumes: volumes:
# Node modules for each service to avoid conflicts and ensure consistent dependencies upload-devcontainer-volume:
cli_node_modules: postgres-devcontainer-volume:
e2e_node_modules:
open_api_node_modules:
server_node_modules:
web_node_modules:
# UPLOAD_LOCATION must be set to a absolute path or vol-upload
vol-upload:
# DB_DATA_LOCATION must be set to a absolute path or vol-database
vol-database:

View File

@@ -40,7 +40,7 @@
"userEnvProbe": "loginInteractiveShell", "userEnvProbe": "loginInteractiveShell",
"remoteEnv": { "remoteEnv": {
// The location where your uploaded files are stored // The location where your uploaded files are stored
"UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./Library}", "UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./library}",
// Connection secret for postgres. You should change it to a random password // Connection secret for postgres. You should change it to a random password
// Please use only the characters `A-Za-z0-9`, without special characters or spaces // Please use only the characters `A-Za-z0-9`, without special characters or spaces
"DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}", "DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}",

View File

@@ -55,7 +55,7 @@ jobs:
runs-on: mich runs-on: mich
steps: steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
ref: ${{ inputs.ref || github.sha }} ref: ${{ inputs.ref || github.sha }}
persist-credentials: false persist-credentials: false
@@ -66,14 +66,14 @@ jobs:
working-directory: ./mobile working-directory: ./mobile
run: printf "%s" $KEY_JKS | base64 -d > android/key.jks run: printf "%s" $KEY_JKS | base64 -d > android/key.jks
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with: with:
distribution: 'zulu' distribution: 'zulu'
java-version: '17' java-version: '17'
- name: Restore Gradle Cache - name: Restore Gradle Cache
id: cache-gradle-restore id: cache-gradle-restore
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@@ -130,7 +130,7 @@ jobs:
- name: Save Gradle Cache - name: Save Gradle Cache
id: cache-gradle-save id: cache-gradle-save
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
if: github.ref == 'refs/heads/main' if: github.ref == 'refs/heads/main'
with: with:
path: | path: |

View File

@@ -19,7 +19,7 @@ jobs:
actions: write actions: write
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false

View File

@@ -29,7 +29,7 @@ jobs:
working-directory: ./cli working-directory: ./cli
steps: steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -37,7 +37,7 @@ jobs:
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './cli/.nvmrc' node-version-file: './cli/.nvmrc'
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
@@ -50,7 +50,7 @@ jobs:
- run: pnpm install --frozen-lockfile - run: pnpm install --frozen-lockfile
- run: pnpm build - run: pnpm build
- run: pnpm publish - run: pnpm publish --no-git-checks
if: ${{ github.event_name == 'release' }} if: ${{ github.event_name == 'release' }}
env: env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -65,7 +65,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -76,7 +76,7 @@ jobs:
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
if: ${{ !github.event.pull_request.head.repo.fork }} if: ${{ !github.event.pull_request.head.repo.fork }}
with: with:
registry: ghcr.io registry: ghcr.io

View File

@@ -44,13 +44,13 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3 uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -63,7 +63,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3 uses: github/codeql-action/autobuild@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -76,6 +76,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh # ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3 uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
with: with:
category: '/language:${{matrix.language}}' category: '/language:${{matrix.language}}'

View File

@@ -53,7 +53,7 @@ jobs:
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn'] suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
steps: steps:
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@@ -82,7 +82,7 @@ jobs:
suffix: [''] suffix: ['']
steps: steps:
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@@ -124,7 +124,7 @@ jobs:
tag-suffix: '-rocm' tag-suffix: '-rocm'
platforms: linux/amd64 platforms: linux/amd64
runner-mapping: '{"linux/amd64": "mich"}' runner-mapping: '{"linux/amd64": "mich"}'
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@129aeda75a450666ce96e8bc8126652e717917a7 # multi-runner-build-workflow-0.1.1 uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@946acac326940f8badf09ccf591d9cb345d6a689 # multi-runner-build-workflow-v0.2.1
permissions: permissions:
contents: read contents: read
actions: read actions: read
@@ -147,7 +147,7 @@ jobs:
name: Build and Push Server name: Build and Push Server
needs: pre-job needs: pre-job
if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }} if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }}
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@129aeda75a450666ce96e8bc8126652e717917a7 # multi-runner-build-workflow-0.1.1 uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@946acac326940f8badf09ccf591d9cb345d6a689 # multi-runner-build-workflow-v0.2.1
permissions: permissions:
contents: read contents: read
actions: read actions: read

View File

@@ -31,6 +31,8 @@ jobs:
- 'open-api/immich-openapi-specs.json' - 'open-api/immich-openapi-specs.json'
force-filters: | force-filters: |
- '.github/workflows/docs-build.yml' - '.github/workflows/docs-build.yml'
- '.github/workflows/docs-deploy.yml'
- 'deployment/**'
force-events: 'release' force-events: 'release'
force-branches: 'main' force-branches: 'main'
@@ -47,7 +49,7 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -55,7 +57,7 @@ jobs:
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './docs/.nvmrc' node-version-file: './docs/.nvmrc'
cache: 'pnpm' cache: 'pnpm'

View File

@@ -20,7 +20,7 @@ jobs:
run: echo 'The triggering workflow did not succeed' && exit 1 run: echo 'The triggering workflow did not succeed' && exit 1
- name: Get artifact - name: Get artifact
id: get-artifact id: get-artifact
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with: with:
script: | script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
@@ -38,7 +38,7 @@ jobs:
return { found: true, id: matchArtifact.id }; return { found: true, id: matchArtifact.id };
- name: Determine deploy parameters - name: Determine deploy parameters
id: parameters id: parameters
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env: env:
HEAD_SHA: ${{ github.event.workflow_run.head_sha }} HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
with: with:
@@ -108,13 +108,13 @@ jobs:
if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }} if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Load parameters - name: Load parameters
id: parameters id: parameters
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env: env:
PARAM_JSON: ${{ needs.checks.outputs.parameters }} PARAM_JSON: ${{ needs.checks.outputs.parameters }}
with: with:
@@ -125,7 +125,7 @@ jobs:
core.setOutput("shouldDeploy", parameters.shouldDeploy); core.setOutput("shouldDeploy", parameters.shouldDeploy);
- name: Download artifact - name: Download artifact
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env: env:
ARTIFACT_JSON: ${{ needs.checks.outputs.artifact }} ARTIFACT_JSON: ${{ needs.checks.outputs.artifact }}
with: with:

View File

@@ -14,7 +14,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false

View File

@@ -22,7 +22,7 @@ jobs:
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- name: 'Checkout' - name: 'Checkout'
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
ref: ${{ github.event.pull_request.head.ref }} ref: ${{ github.event.pull_request.head.ref }}
token: ${{ steps.generate-token.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
@@ -32,7 +32,7 @@ jobs:
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -48,7 +48,7 @@ jobs:
message: 'chore: fix formatting' message: 'chore: fix formatting'
- name: Remove label - name: Remove label
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
if: always() if: always()
with: with:
script: | script: |

View File

@@ -11,4 +11,4 @@ jobs:
pull-requests: write pull-requests: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0 - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1

View File

@@ -55,20 +55,20 @@ jobs:
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- name: Checkout - name: Checkout
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
token: ${{ steps.generate-token.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
persist-credentials: true persist-credentials: true
ref: main ref: main
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -117,18 +117,18 @@ jobs:
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- name: Checkout - name: Checkout
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
token: ${{ steps.generate-token.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
persist-credentials: false persist-credentials: false
- name: Download APK - name: Download APK
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with: with:
name: release-apk-signed name: release-apk-signed
- name: Create draft release - name: Create draft release
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3 uses: softprops/action-gh-release@aec2ec56f94eb8180ceec724245f64ef008b89f5 # v2.4.0
with: with:
draft: true draft: true
tag_name: ${{ env.IMMICH_VERSION }} tag_name: ${{ env.IMMICH_VERSION }}

View File

@@ -24,7 +24,7 @@ jobs:
permissions: permissions:
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with: with:
script: | script: |
github.rest.issues.removeLabel({ github.rest.issues.removeLabel({

View File

@@ -16,7 +16,7 @@ jobs:
run: run:
working-directory: ./open-api/typescript-sdk working-directory: ./open-api/typescript-sdk
steps: steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -24,7 +24,7 @@ jobs:
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
# Setup .npmrc file to publish to npm # Setup .npmrc file to publish to npm
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './open-api/typescript-sdk/.nvmrc' node-version-file: './open-api/typescript-sdk/.nvmrc'
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
@@ -35,6 +35,6 @@ jobs:
- name: Build - name: Build
run: pnpm build run: pnpm build
- name: Publish - name: Publish
run: pnpm publish run: pnpm publish --no-git-checks
env: env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -42,7 +42,7 @@ jobs:
working-directory: ./mobile working-directory: ./mobile
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -71,14 +71,23 @@ jobs:
- name: Generate platform API - name: Generate platform API
run: make pigeon run: make pigeon
- name: Find file changes
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
id: verify-changed-files
with:
files: |
mobile/**/*.g.dart
mobile/**/*.gr.dart
mobile/**/*.drift.dart
- name: Verify files have not changed - name: Verify files have not changed
if: steps.verify-changed-files.outputs.files_changed == 'true'
env:
CHANGED_FILES: ${{ steps.verify-changed-files.outputs.changed_files }}
run: | run: |
if ! git diff --exit-code --quiet '**/*.g.dart' '**/*.gr.dart' '**/*.drift.dart'; then echo "ERROR: Generated files not up to date! Run 'make build' and 'make pigeon' inside the mobile directory"
echo "ERROR: Generated files not up to date! Run 'make build' and 'make pigeon' inside the mobile directory" echo "Changed files: ${CHANGED_FILES}"
echo "Changed files:" exit 1
git diff --name-only '**/*.g.dart' '**/*.gr.dart' '**/*.drift.dart'
exit 1
fi
- name: Run dart analyze - name: Run dart analyze
run: dart analyze --fatal-infos run: dart analyze --fatal-infos

View File

@@ -56,13 +56,13 @@ jobs:
working-directory: ./server working-directory: ./server
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -93,13 +93,13 @@ jobs:
working-directory: ./cli working-directory: ./cli
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './cli/.nvmrc' node-version-file: './cli/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -133,13 +133,13 @@ jobs:
working-directory: ./cli working-directory: ./cli
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './cli/.nvmrc' node-version-file: './cli/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -168,13 +168,13 @@ jobs:
working-directory: ./web working-directory: ./web
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './web/.nvmrc' node-version-file: './web/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -205,13 +205,13 @@ jobs:
working-directory: ./web working-directory: ./web
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './web/.nvmrc' node-version-file: './web/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -236,13 +236,13 @@ jobs:
contents: read contents: read
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './web/.nvmrc' node-version-file: './web/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -251,14 +251,20 @@ jobs:
run: pnpm --filter=immich-web install --frozen-lockfile run: pnpm --filter=immich-web install --frozen-lockfile
- name: Format - name: Format
run: pnpm --filter=immich-web format:i18n run: pnpm --filter=immich-web format:i18n
- name: Find file changes
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
id: verify-changed-files
with:
files: |
i18n/**
- name: Verify files have not changed - name: Verify files have not changed
if: steps.verify-changed-files.outputs.files_changed == 'true'
env:
CHANGED_FILES: ${{ steps.verify-changed-files.outputs.changed_files }}
run: | run: |
if ! git diff --exit-code --quiet i18n/; then echo "ERROR: i18n files not up to date!"
echo "ERROR: i18n files not up to date!" echo "Changed files: ${CHANGED_FILES}"
echo "Changed files:" exit 1
git diff --name-only i18n/
exit 1
fi
e2e-tests-lint: e2e-tests-lint:
name: End-to-End Lint name: End-to-End Lint
needs: pre-job needs: pre-job
@@ -271,13 +277,13 @@ jobs:
working-directory: ./e2e working-directory: ./e2e
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './e2e/.nvmrc' node-version-file: './e2e/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -310,13 +316,13 @@ jobs:
working-directory: ./server working-directory: ./server
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -341,14 +347,14 @@ jobs:
runner: [ubuntu-latest, ubuntu-24.04-arm] runner: [ubuntu-latest, ubuntu-24.04-arm]
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
submodules: 'recursive' submodules: 'recursive'
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './e2e/.nvmrc' node-version-file: './e2e/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -389,14 +395,14 @@ jobs:
runner: [ubuntu-latest, ubuntu-24.04-arm] runner: [ubuntu-latest, ubuntu-24.04-arm]
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
submodules: 'recursive' submodules: 'recursive'
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './e2e/.nvmrc' node-version-file: './e2e/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -435,7 +441,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Setup Flutter SDK - name: Setup Flutter SDK
@@ -460,12 +466,12 @@ jobs:
run: run:
working-directory: ./machine-learning working-directory: ./machine-learning
steps: steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
# TODO: add caching when supported (https://github.com/actions/setup-python/pull/818) # TODO: add caching when supported (https://github.com/actions/setup-python/pull/818)
# with: # with:
# python-version: 3.11 # python-version: 3.11
@@ -497,13 +503,13 @@ jobs:
working-directory: ./.github working-directory: ./.github
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './.github/.nvmrc' node-version-file: './.github/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -519,7 +525,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Run ShellCheck - name: Run ShellCheck
@@ -534,13 +540,13 @@ jobs:
contents: read contents: read
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -552,14 +558,22 @@ jobs:
- name: Run API generation - name: Run API generation
run: ./bin/generate-open-api.sh run: ./bin/generate-open-api.sh
working-directory: open-api working-directory: open-api
- name: Find file changes
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
id: verify-changed-files
with:
files: |
mobile/openapi
open-api/typescript-sdk
open-api/immich-openapi-specs.json
- name: Verify files have not changed - name: Verify files have not changed
if: steps.verify-changed-files.outputs.files_changed == 'true'
env:
CHANGED_FILES: ${{ steps.verify-changed-files.outputs.changed_files }}
run: | run: |
if ! git diff --exit-code --quiet mobile/openapi open-api/typescript-sdk open-api/immich-openapi-specs.json; then echo "ERROR: Generated files not up to date!"
echo "ERROR: Generated files not up to date!" echo "Changed files: ${CHANGED_FILES}"
echo "Changed files:" exit 1
git diff --name-only mobile/openapi open-api/typescript-sdk open-api/immich-openapi-specs.json
exit 1
fi
sql-schema-up-to-date: sql-schema-up-to-date:
name: SQL Schema Checks name: SQL Schema Checks
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -567,7 +581,7 @@ jobs:
contents: read contents: read
services: services:
postgres: postgres:
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3@sha256:da52bbead5d818adaa8077c8dcdaad0aaf93038c31ad8348b51f9f0ec1310a4d image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3@sha256:dbf18b3ffea4a81434c65b71e20d27203baf903a0275f4341e4c16dfd901fd67
env: env:
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres POSTGRES_USER: postgres
@@ -581,13 +595,13 @@ jobs:
working-directory: ./server working-directory: ./server
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
cache: 'pnpm' cache: 'pnpm'
@@ -603,28 +617,40 @@ jobs:
- name: Generate new migrations - name: Generate new migrations
continue-on-error: true continue-on-error: true
run: pnpm migrations:generate src/TestMigration run: pnpm migrations:generate src/TestMigration
- name: Find file changes
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
id: verify-changed-files
with:
files: |
server/src
- name: Verify migration files have not changed - name: Verify migration files have not changed
if: steps.verify-changed-files.outputs.files_changed == 'true'
env:
CHANGED_FILES: ${{ steps.verify-changed-files.outputs.changed_files }}
run: | run: |
if ! git diff --exit-code --quiet src; then echo "ERROR: Generated migration files not up to date!"
echo "ERROR: Generated migration files not up to date!" echo "Changed files: ${CHANGED_FILES}"
echo "Changed files:" cat ./src/*-TestMigration.ts
git diff --name-only src exit 1
cat ./src/*-TestMigration.ts
exit 1
fi
- name: Run SQL generation - name: Run SQL generation
run: pnpm sync:sql run: pnpm sync:sql
env: env:
DB_URL: postgres://postgres:postgres@localhost:5432/immich DB_URL: postgres://postgres:postgres@localhost:5432/immich
- name: Find file changes
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
id: verify-changed-sql-files
with:
files: |
server/src/queries
- name: Verify SQL files have not changed - name: Verify SQL files have not changed
if: steps.verify-changed-sql-files.outputs.files_changed == 'true'
env:
CHANGED_FILES: ${{ steps.verify-changed-sql-files.outputs.changed_files }}
run: | run: |
if ! git diff --exit-code --quiet src/queries; then echo "ERROR: Generated SQL files not up to date!"
echo "ERROR: Generated SQL files not up to date!" echo "Changed files: ${CHANGED_FILES}"
echo "Changed files:" git diff
git diff --name-only src/queries exit 1
git diff
exit 1
fi
# mobile-integration-tests: # mobile-integration-tests:
# name: Run mobile end-to-end integration tests # name: Run mobile end-to-end integration tests

14
.vscode/launch.json vendored
View File

@@ -18,6 +18,20 @@
"name": "Immich Workers", "name": "Immich Workers",
"remoteRoot": "/usr/src/app/server", "remoteRoot": "/usr/src/app/server",
"localRoot": "${workspaceFolder}/server" "localRoot": "${workspaceFolder}/server"
},
{
"type": "node",
"request": "launch",
"name": "Immich CLI",
"program": "${workspaceFolder}/cli/dist/index.js",
"args": ["upload", "--help"],
"runtimeArgs": ["--enable-source-maps"],
"console": "integratedTerminal",
"resolveSourceMapLocations": ["${workspaceFolder}/cli/dist/**/*.js.map"],
"sourceMaps": true,
"outFiles": ["${workspaceFolder}/cli/dist/**/*.js"],
"skipFiles": ["<node_internals>/**"],
"preLaunchTask": "Build Immich CLI"
} }
] ]
} }

5
.vscode/tasks.json vendored
View File

@@ -70,6 +70,11 @@
"runOn": "folderOpen" "runOn": "folderOpen"
}, },
"problemMatcher": [] "problemMatcher": []
},
{
"label": "Build Immich CLI",
"type": "shell",
"command": "pnpm --filter cli build:dev"
} }
] ]
} }

View File

@@ -28,7 +28,8 @@
<a href="readme_i18n/README_de_DE.md">Deutsch</a> <a href="readme_i18n/README_de_DE.md">Deutsch</a>
<a href="readme_i18n/README_nl_NL.md">Nederlands</a> <a href="readme_i18n/README_nl_NL.md">Nederlands</a>
<a href="readme_i18n/README_tr_TR.md">Türkçe</a> <a href="readme_i18n/README_tr_TR.md">Türkçe</a>
<a href="readme_i18n/README_zh_CN.md">中文</a> <a href="readme_i18n/README_zh_CN.md">简体中文</a>
<a href="readme_i18n/README_zh_TW.md">正體中文</a>
<a href="readme_i18n/README_uk_UA.md">Українська</a> <a href="readme_i18n/README_uk_UA.md">Українська</a>
<a href="readme_i18n/README_ru_RU.md">Русский</a> <a href="readme_i18n/README_ru_RU.md">Русский</a>
<a href="readme_i18n/README_pt_BR.md">Português Brasileiro</a> <a href="readme_i18n/README_pt_BR.md">Português Brasileiro</a>

View File

@@ -13,15 +13,23 @@ Then, to build the open-api client run the following in the open-api folder:
$ ./bin/generate-open-api.sh $ ./bin/generate-open-api.sh
To run the Immich CLI from source, run the following in the cli folder: ## Run from build
Go to the cli folder and build it:
$ pnpm install $ pnpm install
$ pnpm run build $ pnpm run build
$ ts-node . $ node dist/index.js
You'll need ts-node, the easiest way to install it is to use pnpm: ## Run and Debug from source (VSCode)
$ pnpm i -g ts-node With VScode you can run and debug the Immich CLI. Go to the launch.json file, find the Immich CLI config and change this with the command you need to debug
`"args": ["upload", "--help"],`
replace that for the command of your choice.
## Install from build
You can also build and install the CLI using You can also build and install the CLI using

View File

@@ -20,7 +20,7 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/micromatch": "^4.0.9", "@types/micromatch": "^4.0.9",
"@types/mock-fs": "^4.13.1", "@types/mock-fs": "^4.13.1",
"@types/node": "^22.18.1", "@types/node": "^22.18.8",
"@vitest/coverage-v8": "^3.0.0", "@vitest/coverage-v8": "^3.0.0",
"byte-size": "^9.0.0", "byte-size": "^9.0.0",
"cli-progress": "^3.12.0", "cli-progress": "^3.12.0",
@@ -43,6 +43,7 @@
}, },
"scripts": { "scripts": {
"build": "vite build", "build": "vite build",
"build:dev": "vite build --sourcemap true",
"lint": "eslint \"src/**/*.ts\" --max-warnings 0", "lint": "eslint \"src/**/*.ts\" --max-warnings 0",
"lint:fix": "npm run lint -- --fix", "lint:fix": "npm run lint -- --fix",
"prepack": "npm run build", "prepack": "npm run build",

View File

@@ -22,7 +22,7 @@ For organizations seeking to resell Immich, we have established the following gu
- Do not misrepresent your reseller site or services as being officially affiliated with or endorsed by Immich or our development team. - Do not misrepresent your reseller site or services as being officially affiliated with or endorsed by Immich or our development team.
- For small resellers who wish to contribute financially to Immich's development, we recommend directing your customers to purchase licenses directy from us rather than attempting to broker revenue-sharing arrangements. We ask that you refrain from misrepresenting reseller activities as directly supporting our development work. - For small resellers who wish to contribute financially to Immich's development, we recommend directing your customers to purchase licenses directly from us rather than attempting to broker revenue-sharing arrangements. We ask that you refrain from misrepresenting reseller activities as directly supporting our development work.
When in doubt or if you have an edge case scenario, we encourage you to contact us directly via email to discuss the use of our trademark. We can provide clear guidance on what is acceptable and what is not. You can reach out at: questions@immich.app When in doubt or if you have an edge case scenario, we encourage you to contact us directly via email to discuss the use of our trademark. We can provide clear guidance on what is acceptable and what is not. You can reach out at: questions@immich.app

View File

@@ -4,7 +4,7 @@ Immich supports the Google's Cast protocol so that photos and videos can be cast
## Enable Google Cast Support ## Enable Google Cast Support
Google Cast support is disabled by default. The web UI uses Google-provided scripts and must retreive them from Google servers when the page loads. This is a privacy concern for some and is thus opt-in. Google Cast support is disabled by default. The web UI uses Google-provided scripts and must retrieve them from Google servers when the page loads. This is a privacy concern for some and is thus opt-in.
You can enable Google Cast support through `Account Settings > Features > Cast > Google Cast` You can enable Google Cast support through `Account Settings > Features > Cast > Google Cast`

View File

@@ -1,5 +1,9 @@
# External Libraries # External Libraries
:::info
Currently an external library can only belong to a single user which is selected when the library is initially created.
:::
External libraries track assets stored in the filesystem outside of Immich. When the external library is scanned, Immich will load videos and photos from disk and create the corresponding assets. These assets will then be shown in the main timeline, and they will look and behave like any other asset, including viewing on the map, adding to albums, etc. Later, if a file is modified outside of Immich, you need to scan the library for the changes to show up. External libraries track assets stored in the filesystem outside of Immich. When the external library is scanned, Immich will load videos and photos from disk and create the corresponding assets. These assets will then be shown in the main timeline, and they will look and behave like any other asset, including viewing on the map, adding to albums, etc. Later, if a file is modified outside of Immich, you need to scan the library for the changes to show up.
If an external asset is deleted from disk, Immich will move it to trash on rescan. To restore the asset, you need to restore the original file. After 30 days the file will be removed from trash, and any changes to metadata within Immich will be lost. If an external asset is deleted from disk, Immich will move it to trash on rescan. To restore the asset, you need to restore the original file. After 30 days the file will be removed from trash, and any changes to metadata within Immich will be lost.

View File

@@ -21,6 +21,10 @@ Restart Immich by running `docker compose up -d`.
# Create the library # Create the library
:::info
External library management requires administrator access and the steps below assume you are using an admin account.
:::
In the Immich web UI: In the Immich web UI:
- click the **Administration** link in the upper right corner. - click the **Administration** link in the upper right corner.

View File

@@ -16,7 +16,7 @@ Immich can easily be installed on a Synology NAS using Container Manager within
## Step 1 - Download the required files ## Step 1 - Download the required files
Create a directory of your choice (e.g. `./immich-app`) to house Immich. In general, it's a best practice to have all Docker-based applications running under the `./docker` directory, so in this case, your directory structure will look like `./docker/immich-app`. Create a directory of your choice (e.g. `./immich-app`) to house Immich. In general, it's best practice to have all Docker-based applications running under the `./docker` directory, so in this case, your directory structure will look like `./docker/immich-app`.
Now create a `./postgres` and `./library` directory as sub-directories of the `./docker/immich-app`. Now create a `./postgres` and `./library` directory as sub-directories of the `./docker/immich-app`.
@@ -25,7 +25,7 @@ When you're all done, you should have the following:
- `./docker/immich-app/postgres` - `./docker/immich-app/postgres`
- `./docker/immich-app/library` - `./docker/immich-app/library`
Download [`docker-compose.yml`](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) and [`example.env`](https://github.com/immich-app/immich/releases/latest/download/example.env) to your computer. Upload the files to the `./docker/immich-app` directory, and rename `example.env` to `.env`. Download [`docker-compose.yml`](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) and [`example.env`](https://github.com/immich-app/immich/releases/latest/download/example.env) to your computer. Upload the files to the `./docker/immich-app` directory, and rename `example.env` to `.env`. Note: If you plan to use the Synology Text editor to edit the `.env` file on the NAS within File Station, you will need to rename it to a temporary name (e.g. `example.txt`) in order to see 'Open with Text Editor' in the file context menu. Once saved, rename it back to `.env`.
## Step 2 - Populate the .env file with custom values ## Step 2 - Populate the .env file with custom values
@@ -34,23 +34,23 @@ Follow [Step 2 in Docker Compose](/install/docker-compose#step-2---populate-the-
## Step 3 - Create a new project in Container Manager ## Step 3 - Create a new project in Container Manager
Open Container Manager, and select the "**Project**" action on the left navigation bar and then click "**Create**". Open Container Manager, and select the "**Project**" action on the left navigation bar and then click "**Create**".
![Create Project](../../static/img/synology-container-manager-create-project.png) ![Create project](../../static/img/synology-container-manager-create-project.png)
In the settings of your new project, set "**Project name**" to a name you'll remember, such as _immich-app_. When setting the "**Path**", select the `./docker/immich-app` directory you created earlier. Doing so will prompt a message to use the existing `docker-compose.yml` already present in the directory for your project. Click "**OK**" to continue. In the settings of your new project, set "**Project name**" to a name you'll remember, such as _immich-app_. When setting the "**Path**", select the `./docker/immich-app` directory you created earlier. Doing so will prompt a message to use the existing `docker-compose.yml` already present in the directory for your project. Click "**OK**" to continue.
![Set Path](../../static/img/synology-container-manager-set-path.png) ![Set path](../../static/img/synology-container-manager-set-path.png)
The following screen will give you the option to further customize your `docker-compose.yml` file, giving you a warning regarding the `start_interval` property. Under the `healthcheck` heading, remove the `start_interval: 30s` completely and click "**Next**". The following screen will give you the option to further customize your `docker-compose.yml` file. Take note of `DB_STORAGE_TYPE: 'HDD'`and uncomment if applicable for your Synology setup.
![start interval](../../static/img/synology-container-manager-customize-docker-compose.png) ![DB storage](../../static/img/synology-container-manager-customize-docker-compose.png)
Skip the section asking to set-up a portal for Web Station, and then complete the wizard which will build and start the containers for your project. Skip the section asking to set-up a portal for Web Station, and then complete the wizard which will build and start the containers for your project.
Once your containers are successfully running, navigate to the "**Container**" section of Container Manager, right-click on the "**immich-server**" container, and choose the "**Details**". Once your containers are successfully running, navigate to the "**Container**" section of Container Manager, right-click on the "**immich-server**" container, and choose the "**Details**".
Scroll to the bottom of the "**Details**" section, and find the `IP Address` of the container, located in the `Network` section. Take note of the container's IP address as you will need it for **Step 4**. Scroll to the bottom of the "**Details**" section and find the `IP Address` listed in the `Network` section. Take note of the container's IP address as you will need it for **Step 4**.
![Container Details](../../static/img/synology-container-manager-container-details.png) ![Container details](../../static/img/synology-container-manager-container-details.png)
## Step 4 - Configure Firewall Settings ## Step 4 - Configure Firewall Settings
@@ -63,8 +63,66 @@ Open "**Control Panel**" on your Synology NAS, and select "**Security**". Naviga
Click "**Edit Rules**" and add the following firewall rules: Click "**Edit Rules**" and add the following firewall rules:
- Add a "**Source IP**" rule for the IP address of your container that you obtained in Step 3 above - Add a "**Source IP**" rule for the IP address of your container that you obtained in Step 3 above
![IP address rule](../../static/img/synology-ipaddress-firewall-rule.png)
- Add a "**Ports**" rule for the port specified in the `docker-compose.yml`, which should be `2283` - Add a "**Ports**" rule for the port specified in the `docker-compose.yml`, which should be `2283`
![Custom port rule](../../static/img/synology-custom-port-firewall-rule.png)
## Next Steps ## Next Steps
Read the [Post Installation](/install/post-install.mdx) steps and [upgrade instructions](/install/upgrading.md). Read the [Post Installation](/install/post-install.mdx) steps and [upgrade instructions](/install/upgrading.md).
<details>
<summary>Updating Immich using Container Manager</summary>
Check the post installation and upgrade instructions at the links above before proceeding with this section.
## Step 1. Backup
Ensure your photos and videos are backed up. Your `.env` settings will define where they are stored. There is no need to delete any files or folders within the `docker` folder when doing a release upgrade unless instructed in the release notes.
## Step 2. Check release notes
Always check the [release notes](https://github.com/immich-app/immich/releases) before proceeding with an update!
## Step 3. Stop containers & clean up
Open **Container Manager**. Select **Project** then your Immich app
![Select project](../../static/img/synology-select-proj.png)
Select **Stop**
![Stop project](../../static/img/synology-project-stop.png)
Select **Action** then **Clean**. This removes the containers.
![Clean project](../../static/img/synology-action-clean.png)
Go to **Image** and select **Remove Unused Images**.
![Remove unused](../../static/img/synology-remove-unused.png)
## Step 4. Build
Go to **Project**, select **Action** then **Build**. This will download, unpack, install and start the containers.
![Build](../../static/img/synology-build.png)
## Step 5. Update firewall rule
The default behavior is to automatically start the containers once installed. If `immich_server` runs for a few seconds and then stops, it may be because the firewall rule no longer matches the server IP address.
Go to the **Container** section. Click on `immich_server` and scroll down on **General** to find the IP address.
![Container IP](../../static/img/synology-container-ip.png)
Go to Synology **Control Panel**. Select **Security** and **Firewall**.
![Firewall](../../static/img/synology-fw-rules.png)
In this example, the IP addresses mismatch and the firewall rule needs to be edited to match above.
![Edit IP](../../static/img/synology-fw-ipedit.png)
</details>

View File

@@ -387,27 +387,35 @@ To migrate from the old storage configuration to the new one, you will need to c
3. **Copy the data** from the old datasets to the new dataset. We advise using the `rsync` command to copy the data, as it will preserve the permissions and ownership of the files. The following commands are examples: 3. **Copy the data** from the old datasets to the new dataset. We advise using the `rsync` command to copy the data, as it will preserve the permissions and ownership of the files. The following commands are examples:
```bash ```bash
rsync -av /mnt/tank/immich/library/ /mnt/tank/immich/data/library/ sudo rsync -av /mnt/tank/immich/library/ /mnt/tank/immich/data/library/
rsync -av /mnt/tank/immich/upload/ /mnt/tank/immich/data/upload/ sudo rsync -av /mnt/tank/immich/upload/ /mnt/tank/immich/data/upload/
rsync -av /mnt/tank/immich/thumbs/ /mnt/tank/immich/data/thumbs/ sudo rsync -av /mnt/tank/immich/thumbs/ /mnt/tank/immich/data/thumbs/
rsync -av /mnt/tank/immich/profile/ /mnt/tank/immich/data/profile/ sudo rsync -av /mnt/tank/immich/profile/ /mnt/tank/immich/data/profile/
rsync -av /mnt/tank/immich/video/ /mnt/tank/immich/data/encoded-video/ sudo rsync -av /mnt/tank/immich/video/ /mnt/tank/immich/data/encoded-video/
rsync -av /mnt/tank/immich/backups/ /mnt/tank/immich/data/backups/ sudo rsync -av /mnt/tank/immich/backups/ /mnt/tank/immich/data/backups/
``` ```
Make sure to replace `/mnt/tank/immich/` with the correct path to your old datasets and `/mnt/tank/immich/data/` with the correct path to your new dataset. Make sure to replace `/mnt/tank/immich/` with the correct path to your old datasets and `/mnt/tank/immich/data/` with the correct path to your new dataset.
:::tip :::tip
If you were using **ixVolume (dataset created automatically by the system)** for Immich data storage, the path to the data should be `/mnt/.ix-apps/app_mounts/immich/`. You have to use this path instead of `/mnt/tank/immich/` in the `rsync` command above, for example: If you were using **ixVolume (dataset created automatically by the system)** for some of Immich data storage, the path to the data should be `/mnt/.ix-apps/app_mounts/immich/`. You have to use this path instead of `/mnt/tank/immich/` in the `rsync` command above, for example:
```bash ```bash
rsync -av /mnt/.ix-apps/app_mounts/immich/library/ /mnt/tank/immich/data/library/ sudo rsync -av /mnt/.ix-apps/app_mounts/immich/library/ /mnt/tank/immich/data/library/
``` ```
If you also were storing your files in the **ixVolume**, the **_upload_** folder is named `uploads` instead of `upload`, so the command to run should be:
```bash
sudo rsync -av /mnt/.ix-apps/app_mounts/immich/uploads/ /mnt/tank/immich/data/upload/
```
This means that depending on your old storage configuration, you might have to use a mix of paths in the `rsync` commands above.
If you were also using an ixVolume for Postgres data storage, you also should, first create the pgData dataset, as described in the [Setting up Storage Datasets](#setting-up-storage-datasets) section above, and then you can use the following command to copy the Postgres data: If you were also using an ixVolume for Postgres data storage, you also should, first create the pgData dataset, as described in the [Setting up Storage Datasets](#setting-up-storage-datasets) section above, and then you can use the following command to copy the Postgres data:
```bash ```bash
rsync -av /mnt/.ix-apps/app_mounts/immich/pgData/ /mnt/tank/immich/pgData/ sudo rsync -av /mnt/.ix-apps/app_mounts/immich/pgData/ /mnt/tank/immich/pgData/
``` ```
::: :::
@@ -416,7 +424,7 @@ rsync -av /mnt/.ix-apps/app_mounts/immich/pgData/ /mnt/tank/immich/pgData/
Make sure that for each folder, the `.immich` file is copied as well, as it contains important metadata for Immich. If for some reason the `.immich` file is not copied, you can copy it manually with the `rsync` command, for example: Make sure that for each folder, the `.immich` file is copied as well, as it contains important metadata for Immich. If for some reason the `.immich` file is not copied, you can copy it manually with the `rsync` command, for example:
```bash ```bash
rsync -av /mnt/tank/immich/library/.immich /mnt/tank/immich/data/library/ sudo rsync -av /mnt/tank/immich/library/.immich /mnt/tank/immich/data/library/
``` ```
Replace `library` with the name of the folder where you are copying the file. Replace `library` with the name of the folder where you are copying the file.
@@ -437,38 +445,37 @@ This will recreate the Immich container with the new storage configuration and s
If everything went well, you should now be able to access Immich with the new storage configuration. You can verify that the data has been copied correctly by checking the Immich web interface and ensuring that all your photos and videos are still available. You may delete the old datasets, if you no longer need them, using the TrueNAS web interface. If everything went well, you should now be able to access Immich with the new storage configuration. You can verify that the data has been copied correctly by checking the Immich web interface and ensuring that all your photos and videos are still available. You may delete the old datasets, if you no longer need them, using the TrueNAS web interface.
:::tip
If you were using **ixVolume (dataset created automatically by the system)** or folders for Immich data storage, you can delete the old datasets using the following commands: If you were using **ixVolume (dataset created automatically by the system)** or folders for Immich data storage, you can delete the old datasets using the following commands:
```bash ```bash
rm -r /mnt/.ix-apps/app_mounts/immich/library sudo rm -r /mnt/.ix-apps/app_mounts/immich/*
rm -r /mnt/.ix-apps/app_mounts/immich/uploads
rm -r /mnt/.ix-apps/app_mounts/immich/thumbs
rm -r /mnt/.ix-apps/app_mounts/immich/profile
rm -r /mnt/.ix-apps/app_mounts/immich/video
rm -r /mnt/.ix-apps/app_mounts/immich/backups
``` ```
:::
</TabItem> </TabItem>
<TabItem value="migrate-old-dataset" label="Keep the existing datasets"> <TabItem value="migrate-old-dataset" label="Keep the existing datasets">
To migrate from the old storage configuration to the new one without creating new datasets. To migrate from the old storage configuration to the new one without creating new datasets.
1. **Stop the Immich app** from the TrueNAS web interface to ensure no data is being written while you are updating the app. 1. **Stop the Immich app** from the TrueNAS web interface to ensure no data is being written while you are updating the app.
2. **Update the datasets permissions**: Ensure that the datasets used for Immich data storage (`library`, `upload`, `thumbs`, `profile`, `video`, `backups`) have the correct permissions set for the user who will run Immich. The user should have ***modify*** permissions on these datasets. The default user for Immich is `apps` (UID 568) and the default group is `apps` (GID 568). If you are using a different user, make sure to set the permissions accordingly. You can do this from the TrueNAS web interface by going to the **Datasets** screen, selecting each dataset, clicking on the **Edit** button next to **Permissions**, and adding the user with ***modify*** permissions. 2. **Update the datasets permissions**: Ensure that the datasets used for Immich data storage (`library`, `upload`, `thumbs`, `profile`, `video`, `backups`) have the correct permissions set for the user who will run Immich. The user should have **_modify_** permissions on these datasets. The default user for Immich is `apps` (UID 568) and the default group is `apps` (GID 568). If you are using a different user, make sure to set the permissions accordingly. You can do this from the TrueNAS web interface by going to the **Datasets** screen, selecting each dataset, clicking on the **Edit** button next to **Permissions**, and adding the user with **_modify_** permissions.
3. **Update the Immich app** to use the existing datasets: 3. **Update the Immich app** to use the existing datasets:
- Go to the **Installed Applications** screen and select Immich from the list of installed applications. - Go to the **Installed Applications** screen and select Immich from the list of installed applications.
- Click **Edit** on the **Application Info** widget. - Click **Edit** on the **Application Info** widget.
- In the **Storage Configuration** section, untick the **Use Old Storage Configuration (Deprecated)** checkbox. - In the **Storage Configuration** section, untick the **Use Old Storage Configuration (Deprecated)** checkbox.
- For the **Data Storage**, you can keep the **ixVolume (dataset created automatically by the system)** as no data will be directly written to it. We recommend selecting **Host Path (Path that already exists on the system)** and then select a **new** dataset you created for Immich data storage, for example, `data`. - For the **Data Storage**, you can keep the **ixVolume (dataset created automatically by the system)** as no data will be directly written to it. We recommend selecting **Host Path (Path that already exists on the system)** and then select a **new** dataset you created for Immich data storage, for example, `data`.
- For the **Postgres Data Storage**, keep **Host Path (Path that already exists on the system)** and then select the existing dataset you used for Postgres data storage, for example, `pgData`. - For the **Postgres Data Storage**, keep **Host Path (Path that already exists on the system)** and then select the existing dataset you used for Postgres data storage, for example, `pgData`.
- Following the instructions in the [Multiple Datasets for Immich Storage](#additional-storage-advanced-users) section, you can add, **for each old dataset**, a new Additional Storage with the following settings: - Following the instructions in the [Multiple Datasets for Immich Storage](#additional-storage-advanced-users) section, you can add, **for each old dataset**, a new Additional Storage with the following settings:
- **Type**: `Host Path (Path that already exists on the system)` - **Type**: `Host Path (Path that already exists on the system)`
- **Mount Path**: `/data/<folder-name>` (e.g. `/data/library`) - **Mount Path**: `/data/<folder-name>` (e.g. `/data/library`)
- **Host Path**: `/mnt/<your-pool-name>/<dataset-name>` (e.g. `/mnt/tank/immich/library`) - **Host Path**: `/mnt/<your-pool-name>/<dataset-name>` (e.g. `/mnt/tank/immich/library`)
:::danger Ensure using the correct paths names :::danger Ensure using the correct paths names
Make sure to replace `<folder-name>` with the actual name of the folder used by Immich: `library`, `upload`, `thumbs`, `profile`, `encoded-video`, and `backups`. Also, replace `<your-pool-name>` and `<dataset-name>` with the actual names of your pool and dataset. Make sure to replace `<folder-name>` with the actual name of the folder used by Immich: `library`, `upload`, `thumbs`, `profile`, `encoded-video`, and `backups`. Also, replace `<your-pool-name>` and `<dataset-name>` with the actual names of your pool and dataset.
::: :::
- **Read Only**: Keep it unticked as Immich needs to write to these datasets. - **Read Only**: Keep it unticked as Immich needs to write to these datasets.
- Click **Update** at the bottom of the page to save changes. - Click **Update** at the bottom of the page to save changes.
4. **Start the Immich app** from the TrueNAS web interface. This will recreate the Immich container with the new storage configuration and start the app. If everything went well, you should now be able to access Immich with the new storage configuration. You can verify that the data is still available by checking the Immich web interface and ensuring that all your photos and videos are still accessible. 4. **Start the Immich app** from the TrueNAS web interface. This will recreate the Immich container with the new storage configuration and start the app. If everything went well, you should now be able to access Immich with the new storage configuration. You can verify that the data is still available by checking the Immich web interface and ensuring that all your photos and videos are still accessible.
</TabItem> </TabItem>

View File

@@ -17,9 +17,9 @@
"write-heading-ids": "docusaurus write-heading-ids" "write-heading-ids": "docusaurus write-heading-ids"
}, },
"dependencies": { "dependencies": {
"@docusaurus/core": "~3.8.0", "@docusaurus/core": "~3.9.0",
"@docusaurus/preset-classic": "~3.8.0", "@docusaurus/preset-classic": "~3.9.0",
"@docusaurus/theme-common": "~3.8.0", "@docusaurus/theme-common": "~3.9.0",
"@mdi/js": "^7.3.67", "@mdi/js": "^7.3.67",
"@mdi/react": "^1.6.1", "@mdi/react": "^1.6.1",
"@mdx-js/react": "^3.0.0", "@mdx-js/react": "^3.0.0",
@@ -35,7 +35,7 @@
"url": "^0.11.0" "url": "^0.11.0"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "~3.8.0", "@docusaurus/module-type-aliases": "~3.9.0",
"@docusaurus/tsconfig": "^3.7.0", "@docusaurus/tsconfig": "^3.7.0",
"@docusaurus/types": "^3.7.0", "@docusaurus/types": "^3.7.0",
"prettier": "^3.2.4", "prettier": "^3.2.4",

View File

@@ -23,11 +23,6 @@ const projects: CommunityProjectProps[] = [
description: 'A Python script to sync folders as albums.', description: 'A Python script to sync folders as albums.',
url: 'https://git.orenit.solutions/open/immichalbumpull', url: 'https://git.orenit.solutions/open/immichalbumpull',
}, },
{
title: 'Remove offline files',
description: 'A simple way to remove orphaned offline assets from the Immich database',
url: 'https://github.com/Thoroslives/immich_remove_offline_files',
},
{ {
title: 'Immich-Tools', title: 'Immich-Tools',
description: 'Provides scripts for handling problems on the repair page.', description: 'Provides scripts for handling problems on the repair page.',
@@ -120,6 +115,11 @@ const projects: CommunityProjectProps[] = [
description: 'Auto-stack photos with identical filenames and differing extensions (i.e. JPG+RAW)', description: 'Auto-stack photos with identical filenames and differing extensions (i.e. JPG+RAW)',
url: 'https://github.com/sid3windr/immich-stack', url: 'https://github.com/sid3windr/immich-stack',
}, },
{
title: 'Immich Stack',
description: 'Automatically groups similar photos into stacks within the Immich photo management system.',
url: 'https://github.com/Majorfi/immich-stack/',
},
]; ];
function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element { function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element {

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
docs/static/img/synology-build.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
docs/static/img/synology-fw-ipedit.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/static/img/synology-fw-rules.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/static/img/synology-select-proj.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -35,7 +35,7 @@ services:
- 2285:2285 - 2285:2285
redis: redis:
image: redis:6.2-alpine@sha256:7fe72c486b910f6b1a9769c937dad5d63648ddee82e056f47417542dd40825bb image: redis:6.2-alpine@sha256:2185e741f4c1e7b0ea9ca1e163a3767c4270a73086b6bbea2049a7203212fb7f
database: database:
image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:11ced39d65a92a54d12890ced6a26cc2003f92697d6f0d4d944b98459dba7138 image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:11ced39d65a92a54d12890ced6a26cc2003f92697d6f0d4d944b98459dba7138

View File

@@ -25,7 +25,7 @@
"@playwright/test": "^1.44.1", "@playwright/test": "^1.44.1",
"@socket.io/component-emitter": "^3.1.2", "@socket.io/component-emitter": "^3.1.2",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.4.2",
"@types/node": "^22.18.1", "@types/node": "^22.18.8",
"@types/oidc-provider": "^9.0.0", "@types/oidc-provider": "^9.0.0",
"@types/pg": "^8.15.1", "@types/pg": "^8.15.1",
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
@@ -43,7 +43,7 @@
"pngjs": "^7.0.0", "pngjs": "^7.0.0",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-organize-imports": "^4.0.0",
"sharp": "^0.34.3", "sharp": "^0.34.4",
"socket.io-client": "^4.7.4", "socket.io-client": "^4.7.4",
"supertest": "^7.0.0", "supertest": "^7.0.0",
"typescript": "^5.3.3", "typescript": "^5.3.3",

View File

@@ -33,6 +33,7 @@
"add_to_albums": "Add to albums", "add_to_albums": "Add to albums",
"add_to_albums_count": "Add to albums ({count})", "add_to_albums_count": "Add to albums ({count})",
"add_to_shared_album": "Add to shared album", "add_to_shared_album": "Add to shared album",
"add_upload_to_stack": "Add upload to stack",
"add_url": "Add URL", "add_url": "Add URL",
"added_to_archive": "Added to archive", "added_to_archive": "Added to archive",
"added_to_favorites": "Added to favorites", "added_to_favorites": "Added to favorites",

View File

@@ -13,8 +13,16 @@ else:
module_dir = Path(__file__).parent module_dir = Path(__file__).parent
def is_ipv6(host: str) -> bool:
try:
return ip_address(host).version == 6
except ValueError:
return False
bind_host = non_prefixed_settings.immich_host bind_host = non_prefixed_settings.immich_host
if ip_address(bind_host).version == 6: if is_ipv6(bind_host):
bind_host = f"[{bind_host}]" bind_host = f"[{bind_host}]"
bind_address = f"{bind_host}:{non_prefixed_settings.immich_port}" bind_address = f"{bind_host}:{non_prefixed_settings.immich_port}"

View File

@@ -7,8 +7,16 @@ import requests
port = os.getenv("IMMICH_PORT", 3003) port = os.getenv("IMMICH_PORT", 3003)
host = os.getenv("IMMICH_HOST", "0.0.0.0") host = os.getenv("IMMICH_HOST", "0.0.0.0")
def is_ipv6(host: str) -> bool:
try:
return ip_address(host).version == 6
except ValueError:
return False
host = "localhost" if host == "0.0.0.0" else host host = "localhost" if host == "0.0.0.0" else host
host = f"[{host}]" if ip_address(host).version == 6 else host host = f"[{host}]" if is_ipv6(host) else host
try: try:
response = requests.get(f"http://{host}:{port}/ping", timeout=2) response = requests.get(f"http://{host}:{port}/ping", timeout=2)

View File

@@ -1,7 +1,7 @@
[tools] [tools]
node = "22.20.0" node = "22.20.0"
flutter = "3.35.4" flutter = "3.35.5"
pnpm = "10.15.1" pnpm = "10.18.0"
[tools."github:CQLabs/homebrew-dcm"] [tools."github:CQLabs/homebrew-dcm"]
version = "1.30.0" version = "1.30.0"

View File

@@ -136,6 +136,7 @@ private open class BackgroundWorkerPigeonCodec : StandardMessageCodec() {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface BackgroundWorkerFgHostApi { interface BackgroundWorkerFgHostApi {
fun enable() fun enable()
fun saveNotificationMessage(title: String, body: String)
fun configure(settings: BackgroundWorkerSettings) fun configure(settings: BackgroundWorkerSettings)
fun disable() fun disable()
@@ -164,6 +165,25 @@ interface BackgroundWorkerFgHostApi {
channel.setMessageHandler(null) channel.setMessageHandler(null)
} }
} }
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.saveNotificationMessage$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val titleArg = args[0] as String
val bodyArg = args[1] as String
val wrapped: List<Any?> = try {
api.saveNotificationMessage(titleArg, bodyArg)
listOf(null)
} catch (exception: Throwable) {
BackgroundWorkerPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run { run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure$separatedMessageChannelSuffix", codec) val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure$separatedMessageChannelSuffix", codec)
if (api != null) { if (api != null) {
@@ -204,7 +224,6 @@ interface BackgroundWorkerFgHostApi {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface BackgroundWorkerBgHostApi { interface BackgroundWorkerBgHostApi {
fun onInitialized() fun onInitialized()
fun showNotification(title: String, content: String)
fun close() fun close()
companion object { companion object {
@@ -232,25 +251,6 @@ interface BackgroundWorkerBgHostApi {
channel.setMessageHandler(null) channel.setMessageHandler(null)
} }
} }
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.showNotification$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val titleArg = args[0] as String
val contentArg = args[1] as String
val wrapped: List<Any?> = try {
api.showNotification(titleArg, contentArg)
listOf(null)
} catch (exception: Throwable) {
BackgroundWorkerPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run { run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close$separatedMessageChannelSuffix", codec) val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close$separatedMessageChannelSuffix", codec)
if (api != null) { if (api != null) {

View File

@@ -73,6 +73,8 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
) )
notificationManager.createNotificationChannel(notificationChannel) notificationManager.createNotificationChannel(notificationChannel)
val notificationConfig = BackgroundWorkerPreferences(ctx).getNotificationConfig()
showNotification(notificationConfig.first, notificationConfig.second)
loader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) { loader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) {
engine = FlutterEngine(ctx) engine = FlutterEngine(ctx)
@@ -109,7 +111,7 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
} }
// TODO: Move this to a separate NotificationManager class // TODO: Move this to a separate NotificationManager class
override fun showNotification(title: String, content: String) { private fun showNotification(title: String, content: String) {
val notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID) val notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon) .setSmallIcon(R.drawable.notification_icon)
.setOnlyAlertOnce(true) .setOnlyAlertOnce(true)

View File

@@ -20,6 +20,10 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
enqueueMediaObserver(ctx) enqueueMediaObserver(ctx)
} }
override fun saveNotificationMessage(title: String, body: String) {
BackgroundWorkerPreferences(ctx).updateNotificationConfig(title, body)
}
override fun configure(settings: BackgroundWorkerSettings) { override fun configure(settings: BackgroundWorkerSettings) {
BackgroundWorkerPreferences(ctx).updateSettings(settings) BackgroundWorkerPreferences(ctx).updateSettings(settings)
enqueueMediaObserver(ctx) enqueueMediaObserver(ctx)

View File

@@ -10,9 +10,13 @@ class BackgroundWorkerPreferences(private val ctx: Context) {
private const val SHARED_PREF_MIN_DELAY_KEY = "BackgroundWorker::minDelaySeconds" private const val SHARED_PREF_MIN_DELAY_KEY = "BackgroundWorker::minDelaySeconds"
private const val SHARED_PREF_REQUIRE_CHARGING_KEY = "BackgroundWorker::requireCharging" private const val SHARED_PREF_REQUIRE_CHARGING_KEY = "BackgroundWorker::requireCharging"
private const val SHARED_PREF_LOCK_KEY = "BackgroundWorker::isLocked" private const val SHARED_PREF_LOCK_KEY = "BackgroundWorker::isLocked"
private const val SHARED_PREF_NOTIF_TITLE_KEY = "BackgroundWorker::notificationTitle"
private const val SHARED_PREF_NOTIF_MSG_KEY = "BackgroundWorker::notificationMessage"
private const val DEFAULT_MIN_DELAY_SECONDS = 30L private const val DEFAULT_MIN_DELAY_SECONDS = 30L
private const val DEFAULT_REQUIRE_CHARGING = false private const val DEFAULT_REQUIRE_CHARGING = false
private const val DEFAULT_NOTIF_TITLE = "Uploading media"
private const val DEFAULT_NOTIF_MSG = "Checking for new assets…"
} }
private val sp: SharedPreferences by lazy { private val sp: SharedPreferences by lazy {
@@ -38,6 +42,20 @@ class BackgroundWorkerPreferences(private val ctx: Context) {
) )
} }
fun updateNotificationConfig(title: String, message: String) {
sp.edit {
putString(SHARED_PREF_NOTIF_TITLE_KEY, title)
putString(SHARED_PREF_NOTIF_MSG_KEY, message)
}
}
fun getNotificationConfig(): Pair<String, String> {
val title =
sp.getString(SHARED_PREF_NOTIF_TITLE_KEY, DEFAULT_NOTIF_TITLE) ?: DEFAULT_NOTIF_TITLE
val message = sp.getString(SHARED_PREF_NOTIF_MSG_KEY, DEFAULT_NOTIF_MSG) ?: DEFAULT_NOTIF_MSG
return Pair(title, message)
}
fun setLocked(paused: Boolean) { fun setLocked(paused: Boolean) {
sp.edit { sp.edit {
putBoolean(SHARED_PREF_LOCK_KEY, paused) putBoolean(SHARED_PREF_LOCK_KEY, paused)

View File

@@ -64,7 +64,7 @@ PODS:
- Flutter - Flutter
- integration_test (0.0.1): - integration_test (0.0.1):
- Flutter - Flutter
- isar_flutter_libs (1.0.0): - isar_community_flutter_libs (1.0.0):
- Flutter - Flutter
- local_auth_darwin (0.0.1): - local_auth_darwin (0.0.1):
- Flutter - Flutter
@@ -149,7 +149,7 @@ DEPENDENCIES:
- home_widget (from `.symlinks/plugins/home_widget/ios`) - home_widget (from `.symlinks/plugins/home_widget/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- integration_test (from `.symlinks/plugins/integration_test/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`)
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`) - isar_community_flutter_libs (from `.symlinks/plugins/isar_community_flutter_libs/ios`)
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
- maplibre_gl (from `.symlinks/plugins/maplibre_gl/ios`) - maplibre_gl (from `.symlinks/plugins/maplibre_gl/ios`)
- native_video_player (from `.symlinks/plugins/native_video_player/ios`) - native_video_player (from `.symlinks/plugins/native_video_player/ios`)
@@ -210,8 +210,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/image_picker_ios/ios" :path: ".symlinks/plugins/image_picker_ios/ios"
integration_test: integration_test:
:path: ".symlinks/plugins/integration_test/ios" :path: ".symlinks/plugins/integration_test/ios"
isar_flutter_libs: isar_community_flutter_libs:
:path: ".symlinks/plugins/isar_flutter_libs/ios" :path: ".symlinks/plugins/isar_community_flutter_libs/ios"
local_auth_darwin: local_auth_darwin:
:path: ".symlinks/plugins/local_auth_darwin/darwin" :path: ".symlinks/plugins/local_auth_darwin/darwin"
maplibre_gl: maplibre_gl:
@@ -264,7 +264,7 @@ SPEC CHECKSUMS:
home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
isar_flutter_libs: bc909e72c3d756c2759f14c8776c13b5b0556e26 isar_community_flutter_libs: bede843185a61a05ff364a05c9b23209523f7e0d
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391 local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
MapLibre: 69e572367f4ef6287e18246cfafc39c80cdcabcd MapLibre: 69e572367f4ef6287e18246cfafc39c80cdcabcd
maplibre_gl: 3c924e44725147b03dda33430ad216005b40555f maplibre_gl: 3c924e44725147b03dda33430ad216005b40555f

View File

@@ -182,6 +182,7 @@ class BackgroundWorkerPigeonCodec: FlutterStandardMessageCodec, @unchecked Senda
/// Generated protocol from Pigeon that represents a handler of messages from Flutter. /// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol BackgroundWorkerFgHostApi { protocol BackgroundWorkerFgHostApi {
func enable() throws func enable() throws
func saveNotificationMessage(title: String, body: String) throws
func configure(settings: BackgroundWorkerSettings) throws func configure(settings: BackgroundWorkerSettings) throws
func disable() throws func disable() throws
} }
@@ -205,6 +206,22 @@ class BackgroundWorkerFgHostApiSetup {
} else { } else {
enableChannel.setMessageHandler(nil) enableChannel.setMessageHandler(nil)
} }
let saveNotificationMessageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.saveNotificationMessage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
saveNotificationMessageChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let titleArg = args[0] as! String
let bodyArg = args[1] as! String
do {
try api.saveNotificationMessage(title: titleArg, body: bodyArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
saveNotificationMessageChannel.setMessageHandler(nil)
}
let configureChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) let configureChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api { if let api = api {
configureChannel.setMessageHandler { message, reply in configureChannel.setMessageHandler { message, reply in
@@ -238,7 +255,6 @@ class BackgroundWorkerFgHostApiSetup {
/// Generated protocol from Pigeon that represents a handler of messages from Flutter. /// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol BackgroundWorkerBgHostApi { protocol BackgroundWorkerBgHostApi {
func onInitialized() throws func onInitialized() throws
func showNotification(title: String, content: String) throws
func close() throws func close() throws
} }
@@ -261,22 +277,6 @@ class BackgroundWorkerBgHostApiSetup {
} else { } else {
onInitializedChannel.setMessageHandler(nil) onInitializedChannel.setMessageHandler(nil)
} }
let showNotificationChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.showNotification\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
showNotificationChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let titleArg = args[0] as! String
let contentArg = args[1] as! String
do {
try api.showNotification(title: titleArg, content: contentArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
showNotificationChannel.setMessageHandler(nil)
}
let closeChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) let closeChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api { if let api = api {
closeChannel.setMessageHandler { _, reply in closeChannel.setMessageHandler { _, reply in

View File

@@ -119,10 +119,6 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
}) })
} }
func showNotification(title: String, content: String) throws {
// No-op on iOS for the time being
}
/** /**
* Cancels the currently running background task, either due to timeout or external request. * Cancels the currently running background task, either due to timeout or external request.
* Sends a cancel signal to the Flutter side and sets up a fallback timer to ensure * Sends a cancel signal to the Flutter side and sets up a fallback timer to ensure

View File

@@ -12,6 +12,10 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
// Android only // Android only
} }
func saveNotificationMessage(title: String, body: String) throws {
// Android only
}
func disable() throws { func disable() throws {
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.refreshTaskID); BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.refreshTaskID);
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.processingTaskID); BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.processingTaskID);

View File

@@ -80,7 +80,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2.0.0</string> <string>2.0.1</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>

View File

@@ -114,7 +114,7 @@ struct ImmichMemoryProvider: TimelineProvider {
} }
} }
// If we didnt add any memory images (some failure occured or no images in memory), // If we didn't add any memory images (some failure occurred or no images in memory),
// default to 12 hours of random photos // default to 12 hours of random photos
if entries.count == 0 { if entries.count == 0 {
// this must be a do/catch since we need to // this must be a do/catch since we need to

View File

@@ -3,27 +3,30 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
class SearchResult { class SearchResult {
final List<BaseAsset> assets; final List<BaseAsset> assets;
final double scrollOffset;
final int? nextPage; final int? nextPage;
const SearchResult({required this.assets, this.nextPage}); const SearchResult({required this.assets, this.scrollOffset = 0.0, this.nextPage});
int get totalAssets => assets.length; SearchResult copyWith({List<BaseAsset>? assets, int? nextPage, double? scrollOffset}) {
return SearchResult(
SearchResult copyWith({List<BaseAsset>? assets, int? nextPage}) { assets: assets ?? this.assets,
return SearchResult(assets: assets ?? this.assets, nextPage: nextPage ?? this.nextPage); nextPage: nextPage ?? this.nextPage,
scrollOffset: scrollOffset ?? this.scrollOffset,
);
} }
@override @override
String toString() => 'SearchResult(assets: $assets, nextPage: $nextPage)'; String toString() => 'SearchResult(assets: ${assets.length}, nextPage: $nextPage, scrollOffset: $scrollOffset)';
@override @override
bool operator ==(covariant SearchResult other) { bool operator ==(covariant SearchResult other) {
if (identical(this, other)) return true; if (identical(this, other)) return true;
final listEquals = const DeepCollectionEquality().equals; final listEquals = const DeepCollectionEquality().equals;
return listEquals(other.assets, assets) && other.nextPage == nextPage; return listEquals(other.assets, assets) && other.nextPage == nextPage && other.scrollOffset == scrollOffset;
} }
@override @override
int get hashCode => assets.hashCode ^ nextPage.hashCode; int get hashCode => assets.hashCode ^ nextPage.hashCode ^ scrollOffset.hashCode;
} }

View File

@@ -11,8 +11,6 @@ import 'package:immich_mobile/domain/services/log.service.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/network_capability_extensions.dart'; import 'package:immich_mobile/extensions/network_capability_extensions.dart';
import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/generated/intl_keys.g.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
import 'package:immich_mobile/platform/background_worker_api.g.dart'; import 'package:immich_mobile/platform/background_worker_api.g.dart';
@@ -44,6 +42,9 @@ class BackgroundWorkerFgService {
// TODO: Move this call to native side once old timeline is removed // TODO: Move this call to native side once old timeline is removed
Future<void> enable() => _foregroundHostApi.enable(); Future<void> enable() => _foregroundHostApi.enable();
Future<void> saveNotificationMessage(String title, String body) =>
_foregroundHostApi.saveNotificationMessage(title, body);
Future<void> configure({int? minimumDelaySeconds, bool? requireCharging}) => _foregroundHostApi.configure( Future<void> configure({int? minimumDelaySeconds, bool? requireCharging}) => _foregroundHostApi.configure(
BackgroundWorkerSettings( BackgroundWorkerSettings(
minimumDelaySeconds: minimumDelaySeconds:
@@ -112,13 +113,6 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
configureFileDownloaderNotifications(); configureFileDownloaderNotifications();
if (Platform.isAndroid) {
await _backgroundHostApi.showNotification(
IntlKeys.uploading_media.t(),
IntlKeys.backup_background_service_default_notification.t(),
);
}
// Notify the host that the background worker service has been initialized and is ready to use // Notify the host that the background worker service has been initialized and is ready to use
_backgroundHostApi.onInitialized(); _backgroundHostApi.onInitialized();
} catch (error, stack) { } catch (error, stack) {

View File

@@ -86,7 +86,7 @@ class StoreService {
_cache.remove(key.id); _cache.remove(key.id);
} }
/// Clears all values from thw store (cache and DB) /// Clears all values from the store (cache and DB)
Future<void> clear() async { Future<void> clear() async {
await _storeRepository.deleteAll(); await _storeRepository.deleteAll();
_cache.clear(); _cache.clear();

View File

@@ -203,7 +203,7 @@ class TimelineService {
Future<void> dispose() async { Future<void> dispose() async {
await _bucketSubscription?.cancel(); await _bucketSubscription?.cancel();
_bucketSubscription = null; _bucketSubscription = null;
_buffer.clear(); _buffer = [];
_bufferOffset = 0; _bufferOffset = 0;
} }
} }

View File

@@ -132,7 +132,7 @@ const AlbumSchema = CollectionSchema(
getId: _albumGetId, getId: _albumGetId,
getLinks: _albumGetLinks, getLinks: _albumGetLinks,
attach: _albumAttach, attach: _albumAttach,
version: '3.1.8', version: '3.3.0-dev.3',
); );
int _albumEstimateSize( int _albumEstimateSize(

View File

@@ -47,7 +47,7 @@ const AndroidDeviceAssetSchema = CollectionSchema(
getId: _androidDeviceAssetGetId, getId: _androidDeviceAssetGetId,
getLinks: _androidDeviceAssetGetLinks, getLinks: _androidDeviceAssetGetLinks,
attach: _androidDeviceAssetAttach, attach: _androidDeviceAssetAttach,
version: '3.1.8', version: '3.3.0-dev.3',
); );
int _androidDeviceAssetEstimateSize( int _androidDeviceAssetEstimateSize(

View File

@@ -168,7 +168,7 @@ const AssetSchema = CollectionSchema(
getId: _assetGetId, getId: _assetGetId,
getLinks: _assetGetLinks, getLinks: _assetGetLinks,
attach: _assetAttach, attach: _assetAttach,
version: '3.1.8', version: '3.3.0-dev.3',
); );
int _assetEstimateSize( int _assetEstimateSize(

View File

@@ -43,7 +43,7 @@ const BackupAlbumSchema = CollectionSchema(
getId: _backupAlbumGetId, getId: _backupAlbumGetId,
getLinks: _backupAlbumGetLinks, getLinks: _backupAlbumGetLinks,
attach: _backupAlbumAttach, attach: _backupAlbumAttach,
version: '3.1.8', version: '3.3.0-dev.3',
); );
int _backupAlbumEstimateSize( int _backupAlbumEstimateSize(

View File

@@ -32,7 +32,7 @@ const DuplicatedAssetSchema = CollectionSchema(
getId: _duplicatedAssetGetId, getId: _duplicatedAssetGetId,
getLinks: _duplicatedAssetGetLinks, getLinks: _duplicatedAssetGetLinks,
attach: _duplicatedAssetAttach, attach: _duplicatedAssetAttach,
version: '3.1.8', version: '3.3.0-dev.3',
); );
int _duplicatedAssetEstimateSize( int _duplicatedAssetEstimateSize(

View File

@@ -52,7 +52,7 @@ const ETagSchema = CollectionSchema(
getId: _eTagGetId, getId: _eTagGetId,
getLinks: _eTagGetLinks, getLinks: _eTagGetLinks,
attach: _eTagAttach, attach: _eTagAttach,
version: '3.1.8', version: '3.3.0-dev.3',
); );
int _eTagEstimateSize( int _eTagEstimateSize(

View File

@@ -60,7 +60,7 @@ const IOSDeviceAssetSchema = CollectionSchema(
getId: _iOSDeviceAssetGetId, getId: _iOSDeviceAssetGetId,
getLinks: _iOSDeviceAssetGetLinks, getLinks: _iOSDeviceAssetGetLinks,
attach: _iOSDeviceAssetAttach, attach: _iOSDeviceAssetAttach,
version: '3.1.8', version: '3.3.0-dev.3',
); );
int _iOSDeviceAssetEstimateSize( int _iOSDeviceAssetEstimateSize(

View File

@@ -65,7 +65,7 @@ const DeviceAssetEntitySchema = CollectionSchema(
getId: _deviceAssetEntityGetId, getId: _deviceAssetEntityGetId,
getLinks: _deviceAssetEntityGetLinks, getLinks: _deviceAssetEntityGetLinks,
attach: _deviceAssetEntityAttach, attach: _deviceAssetEntityAttach,
version: '3.1.8', version: '3.3.0-dev.3',
); );
int _deviceAssetEntityEstimateSize( int _deviceAssetEntityEstimateSize(

View File

@@ -68,7 +68,7 @@ const ExifInfoSchema = CollectionSchema(
getId: _exifInfoGetId, getId: _exifInfoGetId,
getLinks: _exifInfoGetLinks, getLinks: _exifInfoGetLinks,
attach: _exifInfoAttach, attach: _exifInfoAttach,
version: '3.1.8', version: '3.3.0-dev.3',
); );
int _exifInfoEstimateSize( int _exifInfoEstimateSize(

View File

@@ -37,7 +37,7 @@ const StoreValueSchema = CollectionSchema(
getId: _storeValueGetId, getId: _storeValueGetId,
getLinks: _storeValueGetLinks, getLinks: _storeValueGetLinks,
attach: _storeValueAttach, attach: _storeValueAttach,
version: '3.1.8', version: '3.3.0-dev.3',
); );
int _storeValueEstimateSize( int _storeValueEstimateSize(

View File

@@ -95,7 +95,7 @@ const UserSchema = CollectionSchema(
getId: _userGetId, getId: _userGetId,
getLinks: _userGetLinks, getLinks: _userGetLinks,
attach: _userAttach, attach: _userAttach,
version: '3.1.8', version: '3.3.0-dev.3',
); );
int _userEstimateSize( int _userEstimateSize(

View File

@@ -15,7 +15,9 @@ import 'package:immich_mobile/constants/locales.dart';
import 'package:immich_mobile/domain/services/background_worker.service.dart'; import 'package:immich_mobile/domain/services/background_worker.service.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/generated/codegen_loader.g.dart'; import 'package:immich_mobile/generated/codegen_loader.g.dart';
import 'package:immich_mobile/generated/intl_keys.g.dart';
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart'; import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
import 'package:immich_mobile/providers/app_life_cycle.provider.dart'; import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
@@ -210,6 +212,14 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
if (Store.isBetaTimelineEnabled) { if (Store.isBetaTimelineEnabled) {
ref.read(backgroundServiceProvider).disableService(); ref.read(backgroundServiceProvider).disableService();
ref.read(backgroundWorkerFgServiceProvider).enable(); ref.read(backgroundWorkerFgServiceProvider).enable();
if (Platform.isAndroid) {
ref
.read(backgroundWorkerFgServiceProvider)
.saveNotificationMessage(
IntlKeys.uploading_media.t(),
IntlKeys.backup_background_service_default_notification.t(),
);
}
} else { } else {
ref.read(backgroundWorkerFgServiceProvider).disable(); ref.read(backgroundWorkerFgServiceProvider).disable();
ref.read(backgroundServiceProvider).resumeServiceIfEnabled(); ref.read(backgroundServiceProvider).resumeServiceIfEnabled();

View File

@@ -138,6 +138,29 @@ class BackgroundWorkerFgHostApi {
} }
} }
Future<void> saveNotificationMessage(String title, String body) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.saveNotificationMessage$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[title, body]);
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
Future<void> configure(BackgroundWorkerSettings settings) async { Future<void> configure(BackgroundWorkerSettings settings) async {
final String pigeonVar_channelName = final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure$pigeonVar_messageChannelSuffix'; 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure$pigeonVar_messageChannelSuffix';
@@ -221,29 +244,6 @@ class BackgroundWorkerBgHostApi {
} }
} }
Future<void> showNotification(String title, String content) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.showNotification$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[title, content]);
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
Future<void> close() async { Future<void> close() async {
final String pigeonVar_channelName = final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close$pigeonVar_messageChannelSuffix'; 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close$pigeonVar_messageChannelSuffix';

View File

@@ -50,6 +50,11 @@ class DriftEditImagePage extends ConsumerWidget {
return completer.future; return completer.future;
} }
void _exitEditing(BuildContext context) {
// this assumes that the only way to get to this page is from the AssetViewerRoute
context.navigator.popUntil((route) => route.data?.name == AssetViewerRoute.name);
}
Future<void> _saveEditedImage(BuildContext context, BaseAsset asset, Image image, WidgetRef ref) async { Future<void> _saveEditedImage(BuildContext context, BaseAsset asset, Image image, WidgetRef ref) async {
try { try {
final Uint8List imageData = await _imageToUint8List(image); final Uint8List imageData = await _imageToUint8List(image);
@@ -66,7 +71,7 @@ class DriftEditImagePage extends ConsumerWidget {
} }
ref.read(backgroundSyncProvider).syncLocal(full: true); ref.read(backgroundSyncProvider).syncLocal(full: true);
context.navigator.popUntil((route) => route.isFirst); _exitEditing(context);
ImmichToast.show(durationInSecond: 3, context: context, msg: 'Image Saved!'); ImmichToast.show(durationInSecond: 3, context: context, msg: 'Image Saved!');
if (localAsset == null) { if (localAsset == null) {
@@ -91,7 +96,7 @@ class DriftEditImagePage extends ConsumerWidget {
backgroundColor: context.scaffoldBackgroundColor, backgroundColor: context.scaffoldBackgroundColor,
leading: IconButton( leading: IconButton(
icon: Icon(Icons.close_rounded, color: context.primaryColor, size: 24), icon: Icon(Icons.close_rounded, color: context.primaryColor, size: 24),
onPressed: () => context.navigator.popUntil((route) => route.isFirst), onPressed: () => _exitEditing(context),
), ),
actions: <Widget>[ actions: <Widget>[
TextButton( TextButton(

View File

@@ -599,9 +599,9 @@ class _SearchResultGrid extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final searchResult = ref.watch(paginatedSearchProvider); final assets = ref.watch(paginatedSearchProvider.select((s) => s.assets));
if (searchResult.totalAssets == 0) { if (assets.isEmpty) {
return const _SearchEmptyContent(); return const _SearchEmptyContent();
} }
@@ -615,6 +615,7 @@ class _SearchResultGrid extends ConsumerWidget {
if (metrics.pixels >= metrics.maxScrollExtent && isVerticalScroll && !isBottomSheetNotification) { if (metrics.pixels >= metrics.maxScrollExtent && isVerticalScroll && !isBottomSheetNotification) {
onScrollEnd(); onScrollEnd();
ref.read(paginatedSearchProvider.notifier).setScrollOffset(metrics.maxScrollExtent);
} }
return true; return true;
@@ -623,17 +624,18 @@ class _SearchResultGrid extends ConsumerWidget {
child: ProviderScope( child: ProviderScope(
overrides: [ overrides: [
timelineServiceProvider.overrideWith((ref) { timelineServiceProvider.overrideWith((ref) {
final timelineService = ref.watch(timelineFactoryProvider).fromAssets(searchResult.assets); final timelineService = ref.watch(timelineFactoryProvider).fromAssets(assets);
ref.onDispose(timelineService.dispose); ref.onDispose(timelineService.dispose);
return timelineService; return timelineService;
}), }),
], ],
child: Timeline( child: Timeline(
key: ValueKey(searchResult.totalAssets), key: ValueKey(assets.length),
groupBy: GroupAssetsBy.none, groupBy: GroupAssetsBy.none,
appBar: null, appBar: null,
bottomSheet: const GeneralBottomSheet(minChildSize: 0.20), bottomSheet: const GeneralBottomSheet(minChildSize: 0.20),
snapToMonth: false, snapToMonth: false,
initialScrollOffset: ref.read(paginatedSearchProvider.select((s) => s.scrollOffset)),
), ),
), ),
), ),

View File

@@ -24,12 +24,20 @@ class PaginatedSearchNotifier extends StateNotifier<SearchResult> {
return false; return false;
} }
state = SearchResult(assets: [...state.assets, ...result.assets], nextPage: result.nextPage); state = SearchResult(
assets: [...state.assets, ...result.assets],
nextPage: result.nextPage,
scrollOffset: state.scrollOffset,
);
return true; return true;
} }
void setScrollOffset(double offset) {
state = state.copyWith(scrollOffset: offset);
}
clear() { clear() {
state = const SearchResult(assets: [], nextPage: 1); state = const SearchResult(assets: [], nextPage: 1, scrollOffset: 0.0);
} }
} }

View File

@@ -1,11 +1,11 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/pages/editing/drift_edit.page.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/routing/router.dart';
class EditImageActionButton extends ConsumerWidget { class EditImageActionButton extends ConsumerWidget {
const EditImageActionButton({super.key}); const EditImageActionButton({super.key});
@@ -20,12 +20,7 @@ class EditImageActionButton extends ConsumerWidget {
} }
final image = Image(image: getFullImageProvider(currentAsset)); final image = Image(image: getFullImageProvider(currentAsset));
context.pushRoute(DriftEditImageRoute(asset: currentAsset, image: image, isEdited: false));
context.navigator.push(
MaterialPageRoute(
builder: (context) => DriftEditImagePage(asset: currentAsset, image: image, isEdited: false),
),
);
} }
return BaseActionButton( return BaseActionButton(

View File

@@ -221,10 +221,8 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
context.scaffoldMessenger.hideCurrentSnackBar(); context.scaffoldMessenger.hideCurrentSnackBar();
// send image to casting if the server has it // send image to casting if the server has it
if (asset.hasRemote) { if (asset is RemoteAsset) {
final remoteAsset = asset as RemoteAsset; ref.read(castProvider.notifier).loadMedia(asset, false);
ref.read(castProvider.notifier).loadMedia(remoteAsset, false);
} else { } else {
// casting cannot show local assets // casting cannot show local assets
context.scaffoldMessenger.clearSnackBars(); context.scaffoldMessenger.clearSnackBars();

View File

@@ -51,7 +51,7 @@ class AssetDetailBottomSheet extends ConsumerWidget {
isArchived: isArchived, isArchived: isArchived,
isTrashEnabled: isTrashEnable, isTrashEnabled: isTrashEnable,
isInLockedView: isInLockedView, isInLockedView: isInLockedView,
isStacked: asset.hasRemote && (asset as RemoteAsset).stackId != null, isStacked: asset is RemoteAsset && asset.stackId != null,
currentAlbum: currentAlbum, currentAlbum: currentAlbum,
advancedTroubleshooting: advancedTroubleshooting, advancedTroubleshooting: advancedTroubleshooting,
source: ActionSource.viewer, source: ActionSource.viewer,

View File

@@ -44,7 +44,8 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
final showViewInTimelineButton = final showViewInTimelineButton =
(previousRouteName != TabShellRoute.name || tabRoute == TabEnum.search) && (previousRouteName != TabShellRoute.name || tabRoute == TabEnum.search) &&
previousRouteName != AssetViewerRoute.name && previousRouteName != AssetViewerRoute.name &&
previousRouteName != null; previousRouteName != null &&
previousRouteName != LocalTimelineRoute.name;
final isShowingSheet = ref.watch(assetViewerProvider.select((state) => state.showingBottomSheet)); final isShowingSheet = ref.watch(assetViewerProvider.select((state) => state.showingBottomSheet));
int opacity = ref.watch(assetViewerProvider.select((state) => state.backgroundOpacity)); int opacity = ref.watch(assetViewerProvider.select((state) => state.backgroundOpacity));

View File

@@ -322,6 +322,9 @@ class NativeVideoViewer extends HookConsumerWidget {
removeListeners(playerController); removeListeners(playerController);
} }
if (value != null) {
isVisible.value = _isCurrentAsset(value, asset);
}
final curAsset = currentAsset.value; final curAsset = currentAsset.value;
if (curAsset == asset) { if (curAsset == asset) {
return; return;

View File

@@ -40,6 +40,7 @@ class Timeline extends StatelessWidget {
this.groupBy, this.groupBy,
this.withScrubber = true, this.withScrubber = true,
this.snapToMonth = true, this.snapToMonth = true,
this.initialScrollOffset,
}); });
final Widget? topSliverWidget; final Widget? topSliverWidget;
@@ -51,6 +52,7 @@ class Timeline extends StatelessWidget {
final GroupAssetsBy? groupBy; final GroupAssetsBy? groupBy;
final bool withScrubber; final bool withScrubber;
final bool snapToMonth; final bool snapToMonth;
final double? initialScrollOffset;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -78,6 +80,7 @@ class Timeline extends StatelessWidget {
bottomSheet: bottomSheet, bottomSheet: bottomSheet,
withScrubber: withScrubber, withScrubber: withScrubber,
snapToMonth: snapToMonth, snapToMonth: snapToMonth,
initialScrollOffset: initialScrollOffset,
), ),
), ),
), ),
@@ -93,6 +96,7 @@ class _SliverTimeline extends ConsumerStatefulWidget {
this.bottomSheet, this.bottomSheet,
this.withScrubber = true, this.withScrubber = true,
this.snapToMonth = true, this.snapToMonth = true,
this.initialScrollOffset,
}); });
final Widget? topSliverWidget; final Widget? topSliverWidget;
@@ -101,6 +105,7 @@ class _SliverTimeline extends ConsumerStatefulWidget {
final Widget? bottomSheet; final Widget? bottomSheet;
final bool withScrubber; final bool withScrubber;
final bool snapToMonth; final bool snapToMonth;
final double? initialScrollOffset;
@override @override
ConsumerState createState() => _SliverTimelineState(); ConsumerState createState() => _SliverTimelineState();
@@ -124,7 +129,10 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_scrollController = ScrollController(onAttach: _restoreScalePosition); _scrollController = ScrollController(
initialScrollOffset: widget.initialScrollOffset ?? 0.0,
onAttach: _restoreScalePosition,
);
_eventSubscription = EventStream.shared.listen(_onEvent); _eventSubscription = EventStream.shared.listen(_onEvent);
final currentTilesPerRow = ref.read(settingsProvider).get(Setting.tilesPerRow); final currentTilesPerRow = ref.read(settingsProvider).get(Setting.tilesPerRow);

View File

@@ -77,11 +77,14 @@ class ActionNotifier extends Notifier<void> {
return _getAssets(source).whereType<RemoteAsset>().toIds().toList(growable: false); return _getAssets(source).whereType<RemoteAsset>().toIds().toList(growable: false);
} }
List<String> _getLocalIdsForSource(ActionSource source) { List<String> _getLocalIdsForSource(ActionSource source, {bool ignoreLocalOnly = false}) {
final Set<BaseAsset> assets = _getAssets(source); final Set<BaseAsset> assets = _getAssets(source);
final List<String> localIds = []; final List<String> localIds = [];
for (final asset in assets) { for (final asset in assets) {
if (ignoreLocalOnly && asset.storage != AssetState.merged) {
continue;
}
if (asset is LocalAsset) { if (asset is LocalAsset) {
localIds.add(asset.id); localIds.add(asset.id);
} else if (asset is RemoteAsset && asset.localId != null) { } else if (asset is RemoteAsset && asset.localId != null) {
@@ -189,7 +192,7 @@ class ActionNotifier extends Notifier<void> {
Future<ActionResult> moveToLockFolder(ActionSource source) async { Future<ActionResult> moveToLockFolder(ActionSource source) async {
final ids = _getOwnedRemoteIdsForSource(source); final ids = _getOwnedRemoteIdsForSource(source);
final localIds = _getLocalIdsForSource(source); final localIds = _getLocalIdsForSource(source, ignoreLocalOnly: true);
try { try {
await _service.moveToLockFolder(ids, localIds); await _service.moveToLockFolder(ids, localIds);
return ActionResult(count: ids.length, success: true); return ActionResult(count: ids.length, success: true);

View File

@@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/asset/base_asset.model.dart';
@@ -25,7 +26,28 @@ class AssetMediaRepository {
const AssetMediaRepository(this._assetApiRepository); const AssetMediaRepository(this._assetApiRepository);
Future<List<String>> deleteAll(List<String> ids) => PhotoManager.editor.deleteWithIds(ids); Future<bool> _androidSupportsTrash() async {
if (Platform.isAndroid) {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
int sdkVersion = androidInfo.version.sdkInt;
return sdkVersion >= 31;
}
return false;
}
Future<List<String>> deleteAll(List<String> ids) async {
if (CurrentPlatform.isAndroid) {
if (await _androidSupportsTrash()) {
return PhotoManager.editor.android.moveToTrash(
ids.map((e) => AssetEntity(id: e, width: 1, height: 1, typeInt: 0)).toList(),
);
} else {
return PhotoManager.editor.deleteWithIds(ids);
}
}
return PhotoManager.editor.deleteWithIds(ids);
}
Future<asset_entity.Asset?> get(String id) async { Future<asset_entity.Asset?> get(String id) async {
final entity = await AssetEntity.fromId(id); final entity = await AssetEntity.fromId(id);

View File

@@ -121,7 +121,7 @@ class DownloadRepository {
_dummyMetadata['part'] = LivePhotosPart.video.index; _dummyMetadata['part'] = LivePhotosPart.video.index;
tasks[taskIndex++] = DownloadTask( tasks[taskIndex++] = DownloadTask(
taskId: livePhotoVideoId, taskId: livePhotoVideoId,
url: url, url: getOriginalUrlForRemoteId(livePhotoVideoId),
headers: headers, headers: headers,
filename: asset.name.toUpperCase().replaceAll(RegExp(r"\.(JPG|HEIC)$"), '.MOV'), filename: asset.name.toUpperCase().replaceAll(RegExp(r"\.(JPG|HEIC)$"), '.MOV'),
updates: Updates.statusAndProgress, updates: Updates.statusAndProgress,

View File

@@ -22,6 +22,8 @@ class BackgroundWorkerSettings {
abstract class BackgroundWorkerFgHostApi { abstract class BackgroundWorkerFgHostApi {
void enable(); void enable();
void saveNotificationMessage(String title, String body);
void configure(BackgroundWorkerSettings settings); void configure(BackgroundWorkerSettings settings);
void disable(); void disable();
@@ -33,8 +35,6 @@ abstract class BackgroundWorkerBgHostApi {
// required platform channels to notify the native side to start the background upload // required platform channels to notify the native side to start the background upload
void onInitialized(); void onInitialized();
void showNotification(String title, String content);
// Called from the background flutter engine to request the native side to cleanup // Called from the background flutter engine to request the native side to cleanup
void close(); void close();
} }

View File

@@ -317,10 +317,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: connectivity_plus name: connectivity_plus
sha256: "04bf81bb0b77de31557b58d052b24b3eee33f09a6e7a8c68a3e247c7df19ec27" sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.3" version: "6.1.5"
connectivity_plus_platform_interface: connectivity_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -437,18 +437,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: device_info_plus name: device_info_plus
sha256: "306b78788d1bb569edb7c55d622953c2414ca12445b41c9117963e03afc5c513" sha256: "49413c8ca514dea7633e8def233b25efdf83ec8522955cc2c0e3ad802927e7c6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "11.3.3" version: "12.1.0"
device_info_plus_platform_interface: device_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: device_info_plus_platform_interface name: device_info_plus_platform_interface
sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.2" version: "7.0.3"
drift: drift:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1022,25 +1022,34 @@ packages:
isar: isar:
dependency: "direct main" dependency: "direct main"
description: description:
name: isar path: "packages/isar"
sha256: e17a9555bc7f22ff26568b8c64d019b4ffa2dc6bd4cb1c8d9b269aefd32e53ad ref: bb1dca40fe87a001122e5d43bc6254718cb49f3a
url: "https://pub.isar-community.dev" resolved-ref: bb1dca40fe87a001122e5d43bc6254718cb49f3a
source: hosted url: "https://github.com/immich-app/isar"
source: git
version: "3.1.8" version: "3.1.8"
isar_flutter_libs: isar_community:
dependency: transitive
description:
name: isar_community
sha256: "28f59e54636c45ba0bb1b3b7f2656f1c50325f740cea6efcd101900be3fba546"
url: "https://pub.dev"
source: hosted
version: "3.3.0-dev.3"
isar_community_flutter_libs:
dependency: "direct main" dependency: "direct main"
description: description:
name: isar_flutter_libs name: isar_community_flutter_libs
sha256: "78710781e658ce4bff59b3f38c5b2735e899e627f4e926e1221934e77b95231a" sha256: c2934fe755bb3181cb67602fd5df0d080b3d3eb52799f98623aa4fc5acbea010
url: "https://pub.isar-community.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.8" version: "3.3.0-dev.3"
isar_generator: isar_generator:
dependency: "direct dev" dependency: "direct dev"
description: description:
path: "packages/isar_generator" path: "packages/isar_generator"
ref: v3 ref: bb1dca40fe87a001122e5d43bc6254718cb49f3a
resolved-ref: ad574f60ed6f39d2995cd16fc7dc3de9a646ef30 resolved-ref: bb1dca40fe87a001122e5d43bc6254718cb49f3a
url: "https://github.com/immich-app/isar" url: "https://github.com/immich-app/isar"
source: git source: git
version: "3.1.8" version: "3.1.8"

View File

@@ -8,8 +8,6 @@ environment:
sdk: '>=3.8.0 <4.0.0' sdk: '>=3.8.0 <4.0.0'
flutter: 3.35.4 flutter: 3.35.4
isar_version: &isar_version 3.1.8
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
@@ -24,7 +22,7 @@ dependencies:
connectivity_plus: ^6.1.3 connectivity_plus: ^6.1.3
crop_image: ^1.0.16 crop_image: ^1.0.16
crypto: ^3.0.6 crypto: ^3.0.6
device_info_plus: ^11.3.3 device_info_plus: ^12.0.0
dynamic_color: ^1.7.0 dynamic_color: ^1.7.0
easy_image_viewer: ^1.5.1 easy_image_viewer: ^1.5.1
easy_localization: ^3.0.7+1 easy_localization: ^3.0.7+1
@@ -81,11 +79,11 @@ dependencies:
openapi: openapi:
path: openapi path: openapi
isar: isar:
version: *isar_version git:
hosted: https://pub.isar-community.dev/ url: https://github.com/immich-app/isar
isar_flutter_libs: # contains Isar Core ref: 'bb1dca40fe87a001122e5d43bc6254718cb49f3a'
version: *isar_version path: packages/isar/
hosted: https://pub.isar-community.dev/ isar_community_flutter_libs: 3.3.0-dev.3
# DB # DB
drift: ^2.23.1 drift: ^2.23.1
drift_flutter: ^0.2.4 drift_flutter: ^0.2.4
@@ -101,7 +99,7 @@ dev_dependencies:
isar_generator: isar_generator:
git: git:
url: https://github.com/immich-app/isar url: https://github.com/immich-app/isar
ref: v3 ref: 'bb1dca40fe87a001122e5d43bc6254718cb49f3a'
path: packages/isar_generator/ path: packages/isar_generator/
integration_test: integration_test:
sdk: flutter sdk: flutter

View File

@@ -19,7 +19,7 @@
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.18.1", "@types/node": "^22.18.8",
"typescript": "^5.3.3" "typescript": "^5.3.3"
}, },
"repository": { "repository": {

View File

@@ -3,7 +3,7 @@
"version": "0.0.1", "version": "0.0.1",
"description": "Monorepo for Immich", "description": "Monorepo for Immich",
"private": true, "private": true,
"packageManager": "pnpm@10.15.1+sha512.34e538c329b5553014ca8e8f4535997f96180a1d0f614339357449935350d924e22f8614682191264ec33d1462ac21561aff97f6bb18065351c162c7e8f6de67", "packageManager": "pnpm@10.18.0+sha512.e804f889f1cecc40d572db084eec3e4881739f8dec69c0ff10d2d1beff9a4e309383ba27b5b750059d7f4c149535b6cd0d2cb1ed3aeb739239a4284a68f40cfa",
"engines": { "engines": {
"pnpm": ">=10.0.0" "pnpm": ">=10.0.0"
} }

5171
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -27,7 +27,7 @@ onlyBuiltDependencies:
- '@tailwindcss/oxide' - '@tailwindcss/oxide'
overrides: overrides:
canvas: 2.11.2 canvas: 2.11.2
sharp: ^0.34.3 sharp: ^0.34.4
packageExtensions: packageExtensions:
nestjs-kysely: nestjs-kysely:
dependencies: dependencies:

View File

@@ -28,7 +28,8 @@
<a href="README_de_DE.md">Deutsch</a> <a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a> <a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a> <a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a> <a href="README_zh_CN.md">简体中文</a>
<a href="README_zh_TW.md">正體中文</a>
<a href="README_uk_UA.md">Українська</a> <a href="README_uk_UA.md">Українська</a>
<a href="README_ru_RU.md">Русский</a> <a href="README_ru_RU.md">Русский</a>
<a href="README_pt_BR.md">Português Brasileiro</a> <a href="README_pt_BR.md">Português Brasileiro</a>

View File

@@ -27,7 +27,8 @@
<a href="README_de_DE.md">Deutsch</a> <a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a> <a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a> <a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a> <a href="README_zh_CN.md">简体中文</a>
<a href="README_zh_TW.md">正體中文</a>
<a href="README_uk_UA.md">Українська</a> <a href="README_uk_UA.md">Українська</a>
<a href="README_ru_RU.md">Русский</a> <a href="README_ru_RU.md">Русский</a>
<a href="README_pt_BR.md">Português Brasileiro</a> <a href="README_pt_BR.md">Português Brasileiro</a>

View File

@@ -27,7 +27,8 @@
<a href="README_ko_KR.md">한국어</a> <a href="README_ko_KR.md">한국어</a>
<a href="README_nl_NL.md">Nederlands</a> <a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a> <a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a> <a href="README_zh_CN.md">简体中文</a>
<a href="README_zh_TW.md">正體中文</a>
<a href="README_uk_UA.md">Українська</a> <a href="README_uk_UA.md">Українська</a>
<a href="README_ru_RU.md">Русский</a> <a href="README_ru_RU.md">Русский</a>
<a href="README_pt_BR.md">Português Brasileiro</a> <a href="README_pt_BR.md">Português Brasileiro</a>

View File

@@ -27,7 +27,8 @@
<a href="README_de_DE.md">Deutsch</a> <a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a> <a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a> <a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a> <a href="README_zh_CN.md">简体中文</a>
<a href="README_zh_TW.md">正體中文</a>
<a href="README_uk_UA.md">Українська</a> <a href="README_uk_UA.md">Українська</a>
<a href="README_ru_RU.md">Русский</a> <a href="README_ru_RU.md">Русский</a>
<a href="README_pt_BR.md">Português Brasileiro</a> <a href="README_pt_BR.md">Português Brasileiro</a>

View File

@@ -27,7 +27,8 @@
<a href="README_de_DE.md">Deutsch</a> <a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a> <a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a> <a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a> <a href="README_zh_CN.md">简体中文</a>
<a href="README_zh_TW.md">正體中文</a>
<a href="README_uk_UA.md">Українська</a> <a href="README_uk_UA.md">Українська</a>
<a href="README_ru_RU.md">Русский</a> <a href="README_ru_RU.md">Русский</a>
<a href="README_pt_BR.md">Português Brasileiro</a> <a href="README_pt_BR.md">Português Brasileiro</a>

View File

@@ -28,7 +28,8 @@
<a href="README_de_DE.md">Deutsch</a> <a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a> <a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a> <a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a> <a href="README_zh_CN.md">简体中文</a>
<a href="README_zh_TW.md">正體中文</a>
<a href="README_uk_UA.md">Українська</a> <a href="README_uk_UA.md">Українська</a>
<a href="README_ru_RU.md">Русский</a> <a href="README_ru_RU.md">Русский</a>
<a href="README_pt_BR.md">Português Brasileiro</a> <a href="README_pt_BR.md">Português Brasileiro</a>

View File

@@ -27,7 +27,8 @@
<a href="README_de_DE.md">Deutsch</a> <a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a> <a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a> <a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a> <a href="README_zh_CN.md">简体中文</a>
<a href="README_zh_TW.md">正體中文</a>
<a href="README_ru_RU.md">Русский</a> <a href="README_ru_RU.md">Русский</a>
<a href="README_pt_BR.md">Português Brasileiro</a> <a href="README_pt_BR.md">Português Brasileiro</a>
<a href="README_sv_SE.md">Svenska</a> <a href="README_sv_SE.md">Svenska</a>

Some files were not shown because too many files have changed in this diff Show More