mirror of
https://github.com/immich-app/immich.git
synced 2026-01-25 10:54:37 -08:00
Compare commits
57 Commits
v2.1.0
...
feat/plugi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a35fc8631 | ||
|
|
c3a533ab40 | ||
|
|
dbd6dcb786 | ||
|
|
9dffbaea98 | ||
|
|
70bda45551 | ||
|
|
d9452e485c | ||
|
|
85e9ced68d | ||
|
|
04e2e42c88 | ||
|
|
bcfdb2f9df | ||
|
|
23a34bee6f | ||
|
|
6f31f27218 | ||
|
|
b102f94e97 | ||
|
|
becb56e1b1 | ||
|
|
05f174a180 | ||
|
|
476bb1cacd | ||
|
|
24fe62ff9d | ||
|
|
a390e44402 | ||
|
|
08f81eb3c6 | ||
|
|
13d33f834f | ||
|
|
58f9659cf6 | ||
|
|
e14d5fb277 | ||
|
|
06151ad173 | ||
|
|
0700758621 | ||
|
|
f26db8053b | ||
|
|
4836047e50 | ||
|
|
0979528a05 | ||
|
|
24a6757630 | ||
|
|
67f093f75b | ||
|
|
3174a27902 | ||
|
|
e7d6a066f8 | ||
|
|
73da80394e | ||
|
|
471cc74ff2 | ||
|
|
ca745d00ee | ||
|
|
3ea8d140a2 | ||
|
|
8b8012f89d | ||
|
|
4b7f851428 | ||
|
|
cc1cd299f3 | ||
|
|
3163afd24a | ||
|
|
95889a69c9 | ||
|
|
81554e5ad1 | ||
|
|
505e16c37c | ||
|
|
24bfdf3263 | ||
|
|
a23dfff6cf | ||
|
|
2919ee4c65 | ||
|
|
d0eae97037 | ||
|
|
9d639607c7 | ||
|
|
74a9be4a0e | ||
|
|
26e877cba7 | ||
|
|
7b7d91a5e1 | ||
|
|
b3055d2e94 | ||
|
|
f1e03d0022 | ||
|
|
9b5855f848 | ||
|
|
7d0228a159 | ||
|
|
c18df7ae25 | ||
|
|
72f5ca4420 | ||
|
|
02beb85642 | ||
|
|
1b62c2ef55 |
16
.github/workflows/build-mobile.yml
vendored
16
.github/workflows/build-mobile.yml
vendored
@@ -34,10 +34,17 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
mobile:
|
mobile:
|
||||||
- 'mobile/**'
|
- 'mobile/**'
|
||||||
@@ -55,10 +62,17 @@ jobs:
|
|||||||
runs-on: mich
|
runs-on: mich
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.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
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Create the Keystore
|
- name: Create the Keystore
|
||||||
env:
|
env:
|
||||||
|
|||||||
9
.github/workflows/cache-cleanup.yml
vendored
9
.github/workflows/cache-cleanup.yml
vendored
@@ -18,14 +18,21 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
actions: write
|
actions: write
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Cleanup
|
- name: Cleanup
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ steps.token.outputs.token }}
|
||||||
REF: ${{ github.ref }}
|
REF: ${{ github.ref }}
|
||||||
run: |
|
run: |
|
||||||
gh extension install actions/gh-actions-cache
|
gh extension install actions/gh-actions-cache
|
||||||
|
|||||||
19
.github/workflows/cli.yml
vendored
19
.github/workflows/cli.yml
vendored
@@ -29,12 +29,19 @@ jobs:
|
|||||||
working-directory: ./cli
|
working-directory: ./cli
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
@@ -64,10 +71,17 @@ jobs:
|
|||||||
needs: publish
|
needs: publish
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||||
@@ -81,7 +95,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Get package version
|
- name: Get package version
|
||||||
id: package-version
|
id: package-version
|
||||||
@@ -111,3 +125,4 @@ jobs:
|
|||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
tags: ${{ steps.metadata.outputs.tags }}
|
tags: ${{ steps.metadata.outputs.tags }}
|
||||||
labels: ${{ steps.metadata.outputs.labels }}
|
labels: ${{ steps.metadata.outputs.labels }}
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
|
|||||||
12
.github/workflows/close-duplicates.yml
vendored
12
.github/workflows/close-duplicates.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
needs: [get_body, should_run]
|
needs: [get_body, should_run]
|
||||||
if: ${{ needs.should_run.outputs.should_run == 'true' }}
|
if: ${{ needs.should_run.outputs.should_run == 'true' }}
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/immich-app/mdq:main@sha256:d8ae47cf2e6cf4e2559bd57a60b73674fe44f897cba2c2bddff2987a05be10a4
|
image: ghcr.io/immich-app/mdq:main@sha256:6b8450bfc06770af1af66bce9bf2ced7d1d9b90df1a59fc4c83a17777a9f6723
|
||||||
outputs:
|
outputs:
|
||||||
checked: ${{ steps.get_checkbox.outputs.checked }}
|
checked: ${{ steps.get_checkbox.outputs.checked }}
|
||||||
steps:
|
steps:
|
||||||
@@ -54,10 +54,16 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
discussions: write
|
discussions: write
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Close issue
|
- name: Close issue
|
||||||
if: ${{ github.event_name == 'issues' }}
|
if: ${{ github.event_name == 'issues' }}
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ steps.token.outputs.token }}
|
||||||
NODE_ID: ${{ github.event.issue.node_id }}
|
NODE_ID: ${{ github.event.issue.node_id }}
|
||||||
run: |
|
run: |
|
||||||
gh api graphql \
|
gh api graphql \
|
||||||
@@ -83,7 +89,7 @@ jobs:
|
|||||||
- name: Close discussion
|
- name: Close discussion
|
||||||
if: ${{ github.event_name == 'discussion' && github.event.discussion.category.name == 'Feature Request' }}
|
if: ${{ github.event_name == 'discussion' && github.event.discussion.category.name == 'Feature Request' }}
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ steps.token.outputs.token }}
|
||||||
NODE_ID: ${{ github.event.discussion.node_id }}
|
NODE_ID: ${{ github.event.discussion.node_id }}
|
||||||
run: |
|
run: |
|
||||||
gh api graphql \
|
gh api graphql \
|
||||||
|
|||||||
13
.github/workflows/codeql-analysis.yml
vendored
13
.github/workflows/codeql-analysis.yml
vendored
@@ -43,14 +43,21 @@ jobs:
|
|||||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
|
uses: github/codeql-action/init@755f44910c12a3d7ca0d8c6e42c048b3362f7cec # v3.30.8
|
||||||
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 +70,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@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
|
uses: github/codeql-action/autobuild@755f44910c12a3d7ca0d8c6e42c048b3362f7cec # v3.30.8
|
||||||
|
|
||||||
# ℹ️ 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 +83,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@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
|
uses: github/codeql-action/analyze@755f44910c12a3d7ca0d8c6e42c048b3362f7cec # v3.30.8
|
||||||
with:
|
with:
|
||||||
category: '/language:${{matrix.language}}'
|
category: '/language:${{matrix.language}}'
|
||||||
|
|||||||
11
.github/workflows/docker.yml
vendored
11
.github/workflows/docker.yml
vendored
@@ -22,10 +22,17 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
server:
|
server:
|
||||||
- 'server/**'
|
- 'server/**'
|
||||||
@@ -58,6 +65,7 @@ jobs:
|
|||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Re-tag image
|
- name: Re-tag image
|
||||||
env:
|
env:
|
||||||
REGISTRY_NAME: 'ghcr.io'
|
REGISTRY_NAME: 'ghcr.io'
|
||||||
@@ -87,6 +95,7 @@ jobs:
|
|||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Re-tag image
|
- name: Re-tag image
|
||||||
env:
|
env:
|
||||||
REGISTRY_NAME: 'ghcr.io'
|
REGISTRY_NAME: 'ghcr.io'
|
||||||
|
|||||||
18
.github/workflows/docs-build.yml
vendored
18
.github/workflows/docs-build.yml
vendored
@@ -20,10 +20,17 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
docs:
|
docs:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
@@ -46,13 +53,20 @@ jobs:
|
|||||||
working-directory: ./docs
|
working-directory: ./docs
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
|
|||||||
45
.github/workflows/docs-deploy.yml
vendored
45
.github/workflows/docs-deploy.yml
vendored
@@ -16,12 +16,19 @@ jobs:
|
|||||||
parameters: ${{ steps.parameters.outputs.result }}
|
parameters: ${{ steps.parameters.outputs.result }}
|
||||||
artifact: ${{ steps.get-artifact.outputs.result }}
|
artifact: ${{ steps.get-artifact.outputs.result }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- if: ${{ github.event.workflow_run.conclusion != 'success' }}
|
- if: ${{ github.event.workflow_run.conclusion != 'success' }}
|
||||||
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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
@@ -42,6 +49,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
const eventType = context.payload.workflow_run.event;
|
const eventType = context.payload.workflow_run.event;
|
||||||
const isFork = context.payload.workflow_run.repository.fork;
|
const isFork = context.payload.workflow_run.repository.fork;
|
||||||
@@ -107,10 +115,20 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
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:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
- name: Setup Mise
|
||||||
|
uses: immich-app/devtools/actions/use-mise@cd24790a7f5f6439ac32cc94f5523cb2de8bfa8c # use-mise-action-v1.1.0
|
||||||
|
|
||||||
- name: Load parameters
|
- name: Load parameters
|
||||||
id: parameters
|
id: parameters
|
||||||
@@ -118,6 +136,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
PARAM_JSON: ${{ needs.checks.outputs.parameters }}
|
PARAM_JSON: ${{ needs.checks.outputs.parameters }}
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
const parameters = JSON.parse(process.env.PARAM_JSON);
|
const parameters = JSON.parse(process.env.PARAM_JSON);
|
||||||
core.setOutput("event", parameters.event);
|
core.setOutput("event", parameters.event);
|
||||||
@@ -129,6 +148,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
ARTIFACT_JSON: ${{ needs.checks.outputs.artifact }}
|
ARTIFACT_JSON: ${{ needs.checks.outputs.artifact }}
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
let artifact = JSON.parse(process.env.ARTIFACT_JSON);
|
let artifact = JSON.parse(process.env.ARTIFACT_JSON);
|
||||||
let download = await github.rest.actions.downloadArtifact({
|
let download = await github.rest.actions.downloadArtifact({
|
||||||
@@ -150,12 +170,8 @@ jobs:
|
|||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
||||||
uses: gruntwork-io/terragrunt-action@aee21a7df999be8b471c2a8564c6cd853cb674e1 # v2.1.8
|
working-directory: 'deployment/modules/cloudflare/docs'
|
||||||
with:
|
run: 'mise run tf apply'
|
||||||
tg_version: '0.58.12'
|
|
||||||
tofu_version: '1.7.1'
|
|
||||||
tg_dir: 'deployment/modules/cloudflare/docs'
|
|
||||||
tg_command: 'apply'
|
|
||||||
|
|
||||||
- name: Deploy Docs Subdomain Output
|
- name: Deploy Docs Subdomain Output
|
||||||
id: docs-output
|
id: docs-output
|
||||||
@@ -165,12 +181,8 @@ jobs:
|
|||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
||||||
uses: gruntwork-io/terragrunt-action@aee21a7df999be8b471c2a8564c6cd853cb674e1 # v2.1.8
|
working-directory: 'deployment/modules/cloudflare/docs'
|
||||||
with:
|
run: 'mise run tf output -json'
|
||||||
tg_version: '0.58.12'
|
|
||||||
tofu_version: '1.7.1'
|
|
||||||
tg_dir: 'deployment/modules/cloudflare/docs'
|
|
||||||
tg_command: 'output -json'
|
|
||||||
|
|
||||||
- name: Output Cleaning
|
- name: Output Cleaning
|
||||||
id: clean
|
id: clean
|
||||||
@@ -199,17 +211,14 @@ jobs:
|
|||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
||||||
uses: gruntwork-io/terragrunt-action@aee21a7df999be8b471c2a8564c6cd853cb674e1 # v2.1.8
|
working-directory: 'deployment/modules/cloudflare/docs-release'
|
||||||
with:
|
run: 'mise run tf apply'
|
||||||
tg_version: '0.58.12'
|
|
||||||
tofu_version: '1.7.1'
|
|
||||||
tg_dir: 'deployment/modules/cloudflare/docs-release'
|
|
||||||
tg_command: 'apply'
|
|
||||||
|
|
||||||
- name: Comment
|
- name: Comment
|
||||||
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
|
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
|
||||||
if: ${{ steps.parameters.outputs.event == 'pr' }}
|
if: ${{ steps.parameters.outputs.event == 'pr' }}
|
||||||
with:
|
with:
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }}
|
number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }}
|
||||||
body: |
|
body: |
|
||||||
📖 Documentation deployed to [${{ fromJson(steps.clean.outputs.output).immich_app_branch_subdomain.value }}](https://${{ fromJson(steps.clean.outputs.output).immich_app_branch_subdomain.value }})
|
📖 Documentation deployed to [${{ fromJson(steps.clean.outputs.output).immich_app_branch_subdomain.value }}](https://${{ fromJson(steps.clean.outputs.output).immich_app_branch_subdomain.value }})
|
||||||
|
|||||||
19
.github/workflows/docs-destroy.yml
vendored
19
.github/workflows/docs-destroy.yml
vendored
@@ -13,10 +13,20 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
- name: Setup Mise
|
||||||
|
uses: immich-app/devtools/actions/use-mise@cd24790a7f5f6439ac32cc94f5523cb2de8bfa8c # use-mise-action-v1.1.0
|
||||||
|
|
||||||
- name: Destroy Docs Subdomain
|
- name: Destroy Docs Subdomain
|
||||||
env:
|
env:
|
||||||
@@ -25,16 +35,13 @@ jobs:
|
|||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
||||||
uses: gruntwork-io/terragrunt-action@aee21a7df999be8b471c2a8564c6cd853cb674e1 # v2.1.8
|
working-directory: 'deployment/modules/cloudflare/docs'
|
||||||
with:
|
run: 'mise run tf destroy -refresh=false'
|
||||||
tg_version: '0.58.12'
|
|
||||||
tofu_version: '1.7.1'
|
|
||||||
tg_dir: 'deployment/modules/cloudflare/docs'
|
|
||||||
tg_command: 'destroy -refresh=false'
|
|
||||||
|
|
||||||
- name: Comment
|
- name: Comment
|
||||||
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
|
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
|
||||||
with:
|
with:
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
number: ${{ github.event.number }}
|
number: ${{ github.event.number }}
|
||||||
delete: true
|
delete: true
|
||||||
body-include: '<!-- Docs PR URL -->'
|
body-include: '<!-- Docs PR URL -->'
|
||||||
|
|||||||
3
.github/workflows/fix-format.yml
vendored
3
.github/workflows/fix-format.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
@@ -51,6 +51,7 @@ jobs:
|
|||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.generate-token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.removeLabel({
|
github.rest.issues.removeLabel({
|
||||||
issue_number: context.payload.pull_request.number,
|
issue_number: context.payload.pull_request.number,
|
||||||
|
|||||||
18
.github/workflows/merge-translations.yml
vendored
18
.github/workflows/merge-translations.yml
vendored
@@ -28,11 +28,19 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
|
- name: Generate a token
|
||||||
|
id: generate_token
|
||||||
|
if: ${{ inputs.skip != true }}
|
||||||
|
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Find translation PR
|
- name: Find translation PR
|
||||||
id: find_pr
|
id: find_pr
|
||||||
if: ${{ inputs.skip != true }}
|
if: ${{ inputs.skip != true }}
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -55,14 +63,6 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Generate a token
|
|
||||||
id: generate_token
|
|
||||||
if: ${{ inputs.skip != true }}
|
|
||||||
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
|
|
||||||
with:
|
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
|
||||||
|
|
||||||
- name: Lock weblate
|
- name: Lock weblate
|
||||||
if: ${{ inputs.skip != true }}
|
if: ${{ inputs.skip != true }}
|
||||||
env:
|
env:
|
||||||
|
|||||||
7
.github/workflows/pr-label-validation.yml
vendored
7
.github/workflows/pr-label-validation.yml
vendored
@@ -13,9 +13,16 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Require PR to have a changelog label
|
- name: Require PR to have a changelog label
|
||||||
uses: mheap/github-action-required-labels@8afbe8ae6ab7647d0c9f0cfa7c2f939650d22509 # v5.5.1
|
uses: mheap/github-action-required-labels@8afbe8ae6ab7647d0c9f0cfa7c2f939650d22509 # v5.5.1
|
||||||
with:
|
with:
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
mode: exactly
|
mode: exactly
|
||||||
count: 1
|
count: 1
|
||||||
use_regex: true
|
use_regex: true
|
||||||
|
|||||||
8
.github/workflows/pr-labeler.yml
vendored
8
.github/workflows/pr-labeler.yml
vendored
@@ -11,4 +11,12 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
||||||
|
with:
|
||||||
|
repo-token: ${{ steps.token.outputs.token }}
|
||||||
|
|||||||
5
.github/workflows/prepare-release.yml
vendored
5
.github/workflows/prepare-release.yml
vendored
@@ -65,7 +65,7 @@ jobs:
|
|||||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
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@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
@@ -126,9 +126,10 @@ jobs:
|
|||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: release-apk-signed
|
name: release-apk-signed
|
||||||
|
github-token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
- name: Create draft release
|
- name: Create draft release
|
||||||
uses: softprops/action-gh-release@aec2ec56f94eb8180ceec724245f64ef008b89f5 # v2.4.0
|
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
tag_name: ${{ env.IMMICH_VERSION }}
|
tag_name: ${{ env.IMMICH_VERSION }}
|
||||||
|
|||||||
18
.github/workflows/preview-label.yaml
vendored
18
.github/workflows/preview-label.yaml
vendored
@@ -13,10 +13,17 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2
|
- uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
message-id: 'preview-status'
|
message-id: 'preview-status'
|
||||||
message: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.cloud/'
|
message: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.build/'
|
||||||
|
|
||||||
remove-label:
|
remove-label:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -24,8 +31,15 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.removeLabel({
|
github.rest.issues.removeLabel({
|
||||||
issue_number: context.payload.pull_request.number,
|
issue_number: context.payload.pull_request.number,
|
||||||
@@ -37,11 +51,13 @@ jobs:
|
|||||||
- uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2
|
- uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2
|
||||||
if: ${{ github.event.pull_request.head.repo.fork }}
|
if: ${{ github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
message-id: 'preview-status'
|
message-id: 'preview-status'
|
||||||
message: 'PRs from forks cannot have preview environments.'
|
message: 'PRs from forks cannot have preview environments.'
|
||||||
|
|
||||||
- uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2
|
- uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2
|
||||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
message-id: 'preview-status'
|
message-id: 'preview-status'
|
||||||
message: 'Preview environment has been removed.'
|
message: 'Preview environment has been removed.'
|
||||||
|
|||||||
9
.github/workflows/sdk.yml
vendored
9
.github/workflows/sdk.yml
vendored
@@ -16,12 +16,19 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./open-api/typescript-sdk
|
working-directory: ./open-api/typescript-sdk
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
|
|
||||||
# Setup .npmrc file to publish to npm
|
# Setup .npmrc file to publish to npm
|
||||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
|
|||||||
18
.github/workflows/static_analysis.yml
vendored
18
.github/workflows/static_analysis.yml
vendored
@@ -19,10 +19,17 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
mobile:
|
mobile:
|
||||||
- 'mobile/**'
|
- 'mobile/**'
|
||||||
@@ -41,10 +48,17 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Flutter SDK
|
- name: Setup Flutter SDK
|
||||||
uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2.21.0
|
uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2.21.0
|
||||||
@@ -58,7 +72,7 @@ jobs:
|
|||||||
- name: Install DCM
|
- name: Install DCM
|
||||||
uses: CQLabs/setup-dcm@8697ae0790c0852e964a6ef1d768d62a6675481a # v2.0.1
|
uses: CQLabs/setup-dcm@8697ae0790c0852e964a6ef1d768d62a6675481a # v2.0.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
version: auto
|
version: auto
|
||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
|
|
||||||
|
|||||||
148
.github/workflows/test.yml
vendored
148
.github/workflows/test.yml
vendored
@@ -16,10 +16,17 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
i18n:
|
i18n:
|
||||||
- 'i18n/**'
|
- 'i18n/**'
|
||||||
@@ -55,12 +62,20 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./server
|
working-directory: ./server
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
@@ -92,12 +107,19 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./cli
|
working-directory: ./cli
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
@@ -132,12 +154,19 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./cli
|
working-directory: ./cli
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
@@ -167,12 +196,19 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
@@ -204,12 +240,19 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
@@ -235,12 +278,19 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
@@ -276,12 +326,19 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./e2e
|
working-directory: ./e2e
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
@@ -315,12 +372,19 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./server
|
working-directory: ./server
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
@@ -346,13 +410,20 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
@@ -394,13 +465,20 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
@@ -441,9 +519,16 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup Flutter SDK
|
- name: Setup Flutter SDK
|
||||||
uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2.21.0
|
uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2.21.0
|
||||||
with:
|
with:
|
||||||
@@ -466,9 +551,16 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./machine-learning
|
working-directory: ./machine-learning
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
||||||
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
@@ -502,12 +594,19 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./.github
|
working-directory: ./.github
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
@@ -525,9 +624,16 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Run ShellCheck
|
- name: Run ShellCheck
|
||||||
uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0
|
uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0
|
||||||
with:
|
with:
|
||||||
@@ -539,12 +645,19 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
@@ -594,12 +707,19 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./server
|
working-directory: ./server
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
|
|||||||
17
.github/workflows/weblate-lock.yml
vendored
17
.github/workflows/weblate-lock.yml
vendored
@@ -23,10 +23,17 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
i18n:
|
i18n:
|
||||||
- 'i18n/!(en)**\.json'
|
- 'i18n/!(en)**\.json'
|
||||||
@@ -40,10 +47,16 @@ jobs:
|
|||||||
permissions: {}
|
permissions: {}
|
||||||
if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }}
|
if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Bot review status
|
- name: Bot review status
|
||||||
env:
|
env:
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number || github.event.pull_request_review.pull_request.number }}
|
PR_NUMBER: ${{ github.event.pull_request.number || github.event.pull_request_review.pull_request.number }}
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ steps.token.outputs.token }}
|
||||||
run: |
|
run: |
|
||||||
# Then check for APPROVED by the bot, if absent fail
|
# Then check for APPROVED by the bot, if absent fail
|
||||||
gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json reviews | jq -e '.reviews | map(select(.author.login == env.BOT_NAME and .state == "APPROVED")) | length > 0' \
|
gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json reviews | jq -e '.reviews | map(select(.author.login == env.BOT_NAME and .state == "APPROVED")) | length > 0' \
|
||||||
|
|||||||
@@ -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.8",
|
"@types/node": "^22.18.10",
|
||||||
"@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",
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/valkey/valkey:8-bookworm@sha256:fea8b3e67b15729d4bb70589eb03367bab9ad1ee89c876f54327fc7c6e618571
|
image: docker.io/valkey/valkey:8@sha256:81db6d39e1bba3b3ff32bd3a1b19a6d69690f94a3954ec131277b9a26b95b3aa
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/valkey/valkey:8-bookworm@sha256:fea8b3e67b15729d4bb70589eb03367bab9ad1ee89c876f54327fc7c6e618571
|
image: docker.io/valkey/valkey:8@sha256:81db6d39e1bba3b3ff32bd3a1b19a6d69690f94a3954ec131277b9a26b95b3aa
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
restart: always
|
restart: always
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/valkey/valkey:8-bookworm@sha256:fea8b3e67b15729d4bb70589eb03367bab9ad1ee89c876f54327fc7c6e618571
|
image: docker.io/valkey/valkey:8@sha256:81db6d39e1bba3b3ff32bd3a1b19a6d69690f94a3954ec131277b9a26b95b3aa
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
restart: always
|
restart: always
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ Then please follow the steps in the following section for restoring the database
|
|||||||
<TabItem value="Linux system" label="Linux system" default>
|
<TabItem value="Linux system" label="Linux system" default>
|
||||||
|
|
||||||
```bash title='Backup'
|
```bash title='Backup'
|
||||||
|
# Replace <DB_USERNAME> with the database username - usually postgres unless you have changed it.
|
||||||
docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=<DB_USERNAME> | gzip > "/path/to/backup/dump.sql.gz"
|
docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=<DB_USERNAME> | gzip > "/path/to/backup/dump.sql.gz"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -69,16 +70,18 @@ docker compose create # Create Docker containers for Immich apps witho
|
|||||||
docker start immich_postgres # Start Postgres server
|
docker start immich_postgres # Start Postgres server
|
||||||
sleep 10 # Wait for Postgres server to start up
|
sleep 10 # Wait for Postgres server to start up
|
||||||
# Check the database user if you deviated from the default
|
# Check the database user if you deviated from the default
|
||||||
|
# Replace <DB_USERNAME> with the database username - usually postgres unless you have changed it.
|
||||||
gunzip --stdout "/path/to/backup/dump.sql.gz" \
|
gunzip --stdout "/path/to/backup/dump.sql.gz" \
|
||||||
| sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \
|
| sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \
|
||||||
| docker exec -i immich_postgres psql --dbname=postgres --username=<DB_USERNAME> # Restore Backup
|
| docker exec -i immich_postgres psql --dbname=postgres --username=<DB_USERNAME> # Restore Backup
|
||||||
docker compose up -d # Start remainder of Immich apps
|
docker compose up -d # Start remainder of Immich apps
|
||||||
```
|
```
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem value="Windows system (PowerShell)" label="Windows system (PowerShell)">
|
<TabItem value="Windows system (PowerShell)" label="Windows system (PowerShell)">
|
||||||
|
|
||||||
```powershell title='Backup'
|
```powershell title='Backup'
|
||||||
|
# Replace <DB_USERNAME> with the database username - usually postgres unless you have changed it.
|
||||||
[System.IO.File]::WriteAllLines("C:\absolute\path\to\backup\dump.sql", (docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=<DB_USERNAME>))
|
[System.IO.File]::WriteAllLines("C:\absolute\path\to\backup\dump.sql", (docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=<DB_USERNAME>))
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -92,13 +95,15 @@ docker compose create # Create Docker containers for
|
|||||||
docker start immich_postgres # Start Postgres server
|
docker start immich_postgres # Start Postgres server
|
||||||
sleep 10 # Wait for Postgres server to start up
|
sleep 10 # Wait for Postgres server to start up
|
||||||
docker exec -it immich_postgres bash # Enter the Docker shell and run the following command
|
docker exec -it immich_postgres bash # Enter the Docker shell and run the following command
|
||||||
# Check the database user if you deviated from the default. If your backup ends in `.gz`, replace `cat` with `gunzip --stdout`
|
# If your backup ends in `.gz`, replace `cat` with `gunzip --stdout`
|
||||||
|
# Replace <DB_USERNAME> with the database username - usually postgres unless you have changed it.
|
||||||
|
|
||||||
cat "/dump.sql" | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" | psql --dbname=postgres --username=<DB_USERNAME>
|
cat "/dump.sql" | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" | psql --dbname=postgres --username=<DB_USERNAME>
|
||||||
exit # Exit the Docker shell
|
exit # Exit the Docker shell
|
||||||
docker compose up -d # Start remainder of Immich apps
|
docker compose up -d # Start remainder of Immich apps
|
||||||
```
|
```
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
Note that for the database restore to proceed properly, it requires a completely fresh install (i.e. the Immich server has never run since creating the Docker containers). If the Immich app has run, Postgres conflicts may be encountered upon database restoration (relation already exists, violated foreign key constraints, multiple primary keys, etc.), in which case you need to delete the `DB_DATA_LOCATION` folder to reset the database.
|
Note that for the database restore to proceed properly, it requires a completely fresh install (i.e. the Immich server has never run since creating the Docker containers). If the Immich app has run, Postgres conflicts may be encountered upon database restoration (relation already exists, violated foreign key constraints, multiple primary keys, etc.), in which case you need to delete the `DB_DATA_LOCATION` folder to reset the database.
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ Users can deploy a custom reverse proxy that forwards requests to Immich. This w
|
|||||||
Immich does not support being served on a sub-path such as `location /immich {`. It has to be served on the root path of a (sub)domain.
|
Immich does not support being served on a sub-path such as `location /immich {`. It has to be served on the root path of a (sub)domain.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::info
|
||||||
|
If your reverse proxy uses the [Let's Encrypt](https://letsencrypt.org/) [http-01 challenge](https://letsencrypt.org/docs/challenge-types/#http-01-challenge), you may want to verify that the Immich well-known endpoint (`/.well-known/immich`) gets correctly routed to Immich, otherwise it will likely be routed elsewhere and the mobile app may run into connection issues.
|
||||||
|
:::
|
||||||
|
|
||||||
### Nginx example config
|
### Nginx example config
|
||||||
|
|
||||||
Below is an example config for nginx. Make sure to set `public_url` to the front-facing URL of your instance, and `backend_url` to the path of the Immich server.
|
Below is an example config for nginx. Make sure to set `public_url` to the front-facing URL of your instance, and `backend_url` to the path of the Immich server.
|
||||||
@@ -37,29 +41,14 @@ server {
|
|||||||
location / {
|
location / {
|
||||||
proxy_pass http://<backend_url>:2283;
|
proxy_pass http://<backend_url>:2283;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# useful when using Let's Encrypt http-01 challenge
|
||||||
|
# location = /.well-known/immich {
|
||||||
|
# proxy_pass http://<backend_url>:2283;
|
||||||
|
# }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Compatibility with Let's Encrypt
|
|
||||||
|
|
||||||
In the event that your nginx configuration includes a section for Let's Encrypt, it's likely that you have a segment similar to the following:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
location ~ /.well-known {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This particular `location` directive can inadvertently prevent mobile clients from reaching the `/.well-known/immich` path, which is crucial for discovery. Usual error message for this case is: "Your app major version is not compatible with the server". To remedy this, you should introduce an additional location block specifically for this path, ensuring that requests are correctly proxied to the Immich server:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
location = /.well-known/immich {
|
|
||||||
proxy_pass http://<backend_url>:2283;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
By doing so, you'll maintain the functionality of Let's Encrypt while allowing mobile clients to access the necessary Immich path without obstruction.
|
|
||||||
|
|
||||||
### Caddy example config
|
### Caddy example config
|
||||||
|
|
||||||
As an alternative to nginx, you can also use [Caddy](https://caddyserver.com/) as a reverse proxy (with automatic HTTPS configuration). Below is an example config.
|
As an alternative to nginx, you can also use [Caddy](https://caddyserver.com/) as a reverse proxy (with automatic HTTPS configuration). Below is an example config.
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
# Community Guides
|
|
||||||
|
|
||||||
This page lists community guides that are written around Immich, but not officially supported by the development team.
|
|
||||||
|
|
||||||
:::warning
|
|
||||||
This list comes with no guarantees about security, performance, reliability, or accuracy. Use at your own risk.
|
|
||||||
:::
|
|
||||||
|
|
||||||
import CommunityGuides from '../src/components/community-guides.tsx';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
<CommunityGuides />
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# Community Projects
|
|
||||||
|
|
||||||
This page lists community projects that are built around Immich, but not officially supported by the development team.
|
|
||||||
|
|
||||||
:::warning
|
|
||||||
This list comes with no guarantees about security, performance, reliability, or accuracy. Use at your own risk.
|
|
||||||
:::
|
|
||||||
|
|
||||||
import CommunityProjects from '../src/components/community-projects.tsx';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
<CommunityProjects />
|
|
||||||
@@ -182,7 +182,7 @@ For example to get a list of files that would be uploaded for further
|
|||||||
processing:
|
processing:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
immich upload --dry-run . | tail -n +4 | jq .newFiles[]
|
immich upload --dry-run . | tail -n +6 | jq .newFiles[]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Obtain the API Key
|
### Obtain the API Key
|
||||||
|
|||||||
@@ -54,9 +54,25 @@ You do not need to redo any machine learning jobs after enabling hardware accele
|
|||||||
#### OpenVINO
|
#### OpenVINO
|
||||||
|
|
||||||
- Integrated GPUs are more likely to experience issues than discrete GPUs, especially for older processors or servers with low RAM.
|
- Integrated GPUs are more likely to experience issues than discrete GPUs, especially for older processors or servers with low RAM.
|
||||||
- Ensure the server's kernel version is new enough to use the device for hardware accceleration.
|
- Ensure the server's kernel version is new enough to use the device for hardware acceleration.
|
||||||
- Expect higher RAM usage when using OpenVINO compared to CPU processing.
|
- Expect higher RAM usage when using OpenVINO compared to CPU processing.
|
||||||
|
|
||||||
|
#### OpenVINO-WSL
|
||||||
|
|
||||||
|
- Ensure your container can access the /dev/dri directory, you can verify this by doing `docker exec -t immich_machine_learning ls -la /dev/dri`. If this is not the case execute `getent group render` and `getent group video` on the WSL host, then add those groups to hwaccel.ml.yaml
|
||||||
|
```yaml
|
||||||
|
openvino-wsl:
|
||||||
|
devices:
|
||||||
|
- /dev/dri:/dev/dri
|
||||||
|
- /dev/dxg:/dev/dxg
|
||||||
|
volumes:
|
||||||
|
- /dev/bus/usb:/dev/bus/usb
|
||||||
|
- /usr/lib/wsl:/usr/lib/wsl
|
||||||
|
group_add:
|
||||||
|
- 44 # Replace this number with the number you found with getent group video
|
||||||
|
- 992 # Replace this number with the number you found with getent group render
|
||||||
|
```
|
||||||
|
|
||||||
#### RKNN
|
#### RKNN
|
||||||
|
|
||||||
- You must have a supported Rockchip SoC: only RK3566, RK3568, RK3576 and RK3588 are supported at this moment.
|
- You must have a supported Rockchip SoC: only RK3566, RK3568, RK3576 and RK3588 are supported at this moment.
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { mdiCloudOffOutline, mdiCloudCheckOutline } from '@mdi/js';
|
|||||||
import MobileAppDownload from '/docs/partials/_mobile-app-download.md';
|
import MobileAppDownload from '/docs/partials/_mobile-app-download.md';
|
||||||
import MobileAppLogin from '/docs/partials/_mobile-app-login.md';
|
import MobileAppLogin from '/docs/partials/_mobile-app-login.md';
|
||||||
import MobileAppBackup from '/docs/partials/_mobile-app-backup.md';
|
import MobileAppBackup from '/docs/partials/_mobile-app-backup.md';
|
||||||
import { cloudDonePath, cloudOffPath } from '@site/src/components/svg-paths';
|
|
||||||
|
|
||||||
# Mobile App
|
# Mobile App
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ You can read this guide to learn more about [partner sharing](/features/partner-
|
|||||||
|
|
||||||
## Public sharing
|
## Public sharing
|
||||||
|
|
||||||
You can create a public link to share a group of photos or videos, or an album, with anyone. The public link can be shared via email, social media, or any other method. There are a varierity of options to customize the public link, such as setting an expiration date, password protection, and more. Public shared link is handy when you want to share a group of photos or videos with someone who doesn't have an Immich account and allow the shared user to upload their photos or videos to your account.
|
You can create a public link to share a group of photos or videos, or an album, with anyone. The public link can be shared via email, social media, or any other method. There are a variety of options to customize the public link, such as setting an expiration date, password protection, and more. Public shared link is handy when you want to share a group of photos or videos with someone who doesn't have an Immich account and allow the shared user to upload their photos or videos to your account.
|
||||||
|
|
||||||
The public shared link is generated with a random URL, which acts as as a secret to avoid the link being guessed by unwanted parties, for instance.
|
The public shared link is generated with a random URL, which acts as as a secret to avoid the link being guessed by unwanted parties, for instance.
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ In the Immich web UI:
|
|||||||
<img src={require('./img/create-external-library.webp').default} width="50%" title="Create Library button" />
|
<img src={require('./img/create-external-library.webp').default} width="50%" title="Create Library button" />
|
||||||
|
|
||||||
- In the dialog, select which user should own the new library
|
- In the dialog, select which user should own the new library
|
||||||
<img src={require('./img/library-owner.webp').default} width="50%" title="Library owner diaglog" />
|
<img src={require('./img/library-owner.webp').default} width="50%" title="Library owner dialog" />
|
||||||
|
|
||||||
- Click the three-dots menu and select **Edit Import Paths**
|
- Click the three-dots menu and select **Edit Import Paths**
|
||||||
<img src={require('./img/edit-import-paths.webp').default} width="50%" title="Edit Import Paths menu option" />
|
<img src={require('./img/edit-import-paths.webp').default} width="50%" title="Edit Import Paths menu option" />
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ Redis (Sentinel) URL example JSON before encoding:
|
|||||||
| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning |
|
| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning |
|
||||||
| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning |
|
| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning |
|
||||||
| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spinned up while inferencing. | `1` | machine learning |
|
| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spinned up while inferencing. | `1` | machine learning |
|
||||||
|
| `MACHINE_LEARNING_MODEL_ARENA` | Pre-allocates CPU memory to avoid memory fragmentation | true | machine learning |
|
||||||
|
|
||||||
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
|
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ In the settings of your new project, set "**Project name**" to a name you'll rem
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ After making a backup, please modify your `docker-compose.yml` file with the fol
|
|||||||
If you deviated from the defaults of pg14 or pgvectors0.2.0, you must adjust the pg major version and pgvecto.rs version. If you are still using the default `docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0` image, you can just follow the changes above. For example, if the previous image is `docker.io/tensorchord/pgvecto-rs:pg16-v0.3.0`, the new image should be `ghcr.io/immich-app/postgres:16-vectorchord0.3.0-pgvectors0.3.0` instead of the image specified in the diff.
|
If you deviated from the defaults of pg14 or pgvectors0.2.0, you must adjust the pg major version and pgvecto.rs version. If you are still using the default `docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0` image, you can just follow the changes above. For example, if the previous image is `docker.io/tensorchord/pgvecto-rs:pg16-v0.3.0`, the new image should be `ghcr.io/immich-app/postgres:16-vectorchord0.3.0-pgvectors0.3.0` instead of the image specified in the diff.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
After making these changes, you can start Immich as normal. Immich will make some changes to the DB during startup, which can take seconds to minutes to finish, depending on hardware and library size. In particular, it’s normal for the server logs to be seemingly stuck at `Reindexing clip_index` and `Reindexing face_index`for some time if you have over 100k assets in Immich and/or Immich is on a relatively weak server. If you see these logs and there are no errors, just give it time.
|
After making these changes, you can start Immich as normal. Immich will make some changes to the DB during startup, which can take seconds to minutes to finish, depending on hardware and library size. In particular, it’s normal for the server logs to be seemingly stuck at `Reindexing clip_index` and `Reindexing face_index` for some time if you have over 100k assets in Immich and/or Immich is on a relatively weak server. If you see these logs and there are no errors, just give it time.
|
||||||
|
|
||||||
:::danger
|
:::danger
|
||||||
After switching to VectorChord, you should not downgrade Immich below 1.133.0.
|
After switching to VectorChord, you should not downgrade Immich below 1.133.0.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
The mobile app can be downloaded from the following places:
|
The mobile app can be downloaded from the following places:
|
||||||
|
|
||||||
|
- Obtainium: You can get your Obtainium config link from the [Utilities page of your Immich server](https://my.immich.app/utilities).
|
||||||
- [Google Play Store](https://play.google.com/store/apps/details?id=app.alextran.immich)
|
- [Google Play Store](https://play.google.com/store/apps/details?id=app.alextran.immich)
|
||||||
- [Apple App Store](https://apps.apple.com/us/app/immich/id1613945652)
|
- [Apple App Store](https://apps.apple.com/us/app/immich/id1613945652)
|
||||||
- [F-Droid](https://f-droid.org/packages/app.alextran.immich)
|
- [F-Droid](https://f-droid.org/packages/app.alextran.immich)
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
import Link from '@docusaurus/Link';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface CommunityGuidesProps {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const guides: CommunityGuidesProps[] = [
|
|
||||||
{
|
|
||||||
title: 'Cloudflare Tunnels with SSO/OAuth',
|
|
||||||
description: `Setting up Cloudflare Tunnels and a SaaS App for Immich.`,
|
|
||||||
url: 'https://github.com/immich-app/immich/discussions/8299',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Database backup in TrueNAS',
|
|
||||||
description: `Create a database backup with pgAdmin in TrueNAS.`,
|
|
||||||
url: 'https://github.com/immich-app/immich/discussions/8809',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Unraid backup scripts',
|
|
||||||
description: `Back up your assets in Unraid with a pre-prepared script.`,
|
|
||||||
url: 'https://github.com/immich-app/immich/discussions/8416',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Sync folders with albums',
|
|
||||||
description: `synchronize folders in imported library with albums having the folders name.`,
|
|
||||||
url: 'https://github.com/immich-app/immich/discussions/3382',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Podman Quadlets Handbook',
|
|
||||||
description:
|
|
||||||
'A rewrite of the original Immich Docker Compose file using Podman Quadlets, with a set of extra guides in the repository’s wiki.',
|
|
||||||
url: 'https://github.com/linux-universe/immich-podman-quadlets/blob/main/README.md',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Podman/Quadlets Install',
|
|
||||||
description: 'Documentation for simple podman setup using quadlets.',
|
|
||||||
url: 'https://github.com/tbelway/immich-podman-quadlets/blob/main/docs/install/podman-quadlet.md',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Google Photos import + albums',
|
|
||||||
description: 'Import your Google Photos files into Immich and add your albums.',
|
|
||||||
url: 'https://github.com/immich-app/immich/discussions/1340',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Access Immich with custom domain',
|
|
||||||
description: 'Access your local Immich installation over the internet using your own domain.',
|
|
||||||
url: 'https://github.com/ppr88/immich-guides/blob/main/open-immich-custom-domain.md',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Nginx caching map server',
|
|
||||||
description: 'Increase privacy by using nginx as a caching proxy in front of a map tile server.',
|
|
||||||
url: 'https://github.com/pcouy/pcouy.github.io/blob/main/_posts/2024-08-30-proxying-a-map-tile-server-for-increased-privacy.md',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'fail2ban setup instructions',
|
|
||||||
description: 'How to configure an existing fail2ban installation to block incorrect login attempts.',
|
|
||||||
url: 'https://github.com/immich-app/immich/discussions/3243#discussioncomment-6681948',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich remote access with NordVPN Meshnet',
|
|
||||||
description: 'Access Immich with an end-to-end encrypted connection.',
|
|
||||||
url: 'https://meshnet.nordvpn.com/how-to/remote-files-media-access/immich-remote-access',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Trust Self Signed Certificates with Immich - OAuth Setup',
|
|
||||||
description:
|
|
||||||
'Set up Certificate Authority trust with Immich, and your private OAuth2/OpenID service, while using a private CA for HTTPS commication.',
|
|
||||||
url: 'https://github.com/immich-app/immich/discussions/18614',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function CommunityGuide({ title, description, url }: CommunityGuidesProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<section className="flex flex-col gap-4 justify-between dark:bg-immich-dark-gray bg-immich-gray dark:border-0 border-gray-200 border border-solid rounded-2xl px-4 py-6">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<p className="m-0 items-start flex gap-2 text-2xl font-bold text-immich-primary dark:text-immich-dark-primary">
|
|
||||||
<span>{title}</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p className="m-0 text-sm text-gray-600 dark:text-gray-300">{description}</p>
|
|
||||||
<p className="m-0 text-sm text-gray-600 dark:text-gray-300 my-4">
|
|
||||||
<a href={url}>{url}</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<Link
|
|
||||||
className="px-4 py-2 bg-immich-primary/10 dark:bg-gray-300 rounded-xl text-sm hover:no-underline text-immich-primary dark:text-immich-dark-bg font-semibold"
|
|
||||||
to={url}
|
|
||||||
>
|
|
||||||
View Guide
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CommunityGuides(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
|
||||||
{guides.map((guides) => (
|
|
||||||
<CommunityGuide {...guides} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
import Link from '@docusaurus/Link';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface CommunityProjectProps {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const projects: CommunityProjectProps[] = [
|
|
||||||
{
|
|
||||||
title: 'immich-go',
|
|
||||||
description: `An alternative to the immich-CLI that doesn't depend on nodejs. It specializes in importing Google Photos Takeout archives.`,
|
|
||||||
url: 'https://github.com/simulot/immich-go',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'ImmichFrame',
|
|
||||||
description: 'Run an Immich slideshow in a photo frame.',
|
|
||||||
url: 'https://github.com/3rob3/ImmichFrame',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'API Album Sync',
|
|
||||||
description: 'A Python script to sync folders as albums.',
|
|
||||||
url: 'https://git.orenit.solutions/open/immichalbumpull',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich-Tools',
|
|
||||||
description: 'Provides scripts for handling problems on the repair page.',
|
|
||||||
url: 'https://github.com/clumsyCoder00/Immich-Tools',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Lightroom Publisher: mi.Immich.Publisher',
|
|
||||||
description: 'Lightroom plugin to publish photos from Lightroom collections to Immich albums.',
|
|
||||||
url: 'https://github.com/midzelis/mi.Immich.Publisher',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Lightroom Immich Plugin: lrc-immich-plugin',
|
|
||||||
description:
|
|
||||||
'Lightroom plugin to publish, export photos from Lightroom to Immich. Import from Immich to Lightroom is also supported.',
|
|
||||||
url: 'https://blog.fokuspunk.de/lrc-immich-plugin/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich-Tiktok-Remover',
|
|
||||||
description: 'Script to search for and remove TikTok videos from your Immich library.',
|
|
||||||
url: 'https://github.com/mxc2/immich-tiktok-remover',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Android TV',
|
|
||||||
description: 'Unofficial Immich Android TV app.',
|
|
||||||
url: 'https://github.com/giejay/Immich-Android-TV',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Create albums from folders',
|
|
||||||
description: 'A Python script to create albums based on the folder structure of an external library.',
|
|
||||||
url: 'https://github.com/Salvoxia/immich-folder-album-creator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Powershell Module PSImmich',
|
|
||||||
description: 'Powershell Module for the Immich API',
|
|
||||||
url: 'https://github.com/hanpq/PSImmich',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Distribution',
|
|
||||||
description: 'Snap package for easy install and zero-care auto updates of Immich. Self-hosted photo management.',
|
|
||||||
url: 'https://immich-distribution.nsg.cc',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Kiosk',
|
|
||||||
description: 'Lightweight slideshow to run on kiosk devices and browsers.',
|
|
||||||
url: 'https://github.com/damongolding/immich-kiosk',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Power Tools',
|
|
||||||
description: 'Power tools for organizing your immich library.',
|
|
||||||
url: 'https://github.com/varun-raj/immich-power-tools',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Public Proxy',
|
|
||||||
description:
|
|
||||||
'Share your Immich photos and albums in a safe way without exposing your Immich instance to the public.',
|
|
||||||
url: 'https://github.com/alangrainger/immich-public-proxy',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Kodi',
|
|
||||||
description: 'Unofficial Kodi plugin for Immich.',
|
|
||||||
url: 'https://github.com/vladd11/immich-kodi',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Downloader',
|
|
||||||
description: 'Downloads a configurable number of random photos based on people or album ID.',
|
|
||||||
url: 'https://github.com/jon6fingrs/immich-dl',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Upload Optimizer',
|
|
||||||
description: 'Automatically optimize files uploaded to Immich in order to save storage space',
|
|
||||||
url: 'https://github.com/miguelangel-nubla/immich-upload-optimizer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Machine Learning Load Balancer',
|
|
||||||
description: 'Speed up your machine learning by load balancing your requests to multiple computers',
|
|
||||||
url: 'https://github.com/apetersson/immich_ml_balancer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Drop Uploader',
|
|
||||||
description: 'A tiny, zero-login web app for collecting photos/videos from anyone into your Immich server.',
|
|
||||||
url: 'https://github.com/Nasogaa/immich-drop',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Birthday Sync',
|
|
||||||
description: 'Bulk-upload and -download birthdays, with CardDAV sync support',
|
|
||||||
url: 'https://github.com/sid3windr/immich-birthday',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Stack',
|
|
||||||
description: 'Auto-stack photos with identical filenames and differing extensions (i.e. JPG+RAW)',
|
|
||||||
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 {
|
|
||||||
return (
|
|
||||||
<section className="flex flex-col gap-4 justify-between dark:bg-immich-dark-gray bg-immich-gray dark:border-0 border-gray-200 border border-solid rounded-2xl px-4 py-6">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<p className="m-0 items-start flex gap-2 text-2xl font-bold text-immich-primary dark:text-immich-dark-primary">
|
|
||||||
<span>{title}</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p className="m-0 text-sm text-gray-600 dark:text-gray-300">{description}</p>
|
|
||||||
<p className="m-0 text-sm text-gray-600 dark:text-gray-300 my-4">
|
|
||||||
<a href={url}>{url}</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<Link
|
|
||||||
className="px-4 py-2 bg-immich-primary/10 dark:bg-gray-300 rounded-xl text-sm hover:no-underline text-immich-primary dark:text-immich-dark-bg font-semibold"
|
|
||||||
to={url}
|
|
||||||
>
|
|
||||||
View Link
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CommunityProjects(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
|
||||||
{projects.map((project) => (
|
|
||||||
<CommunityProject {...project} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export const discordPath =
|
|
||||||
'M81.15,0c-1.2376,2.1973-2.3489,4.4704-3.3591,6.794-9.5975-1.4396-19.3718-1.4396-28.9945,0-.985-2.3236-2.1216-4.5967-3.3591-6.794-9.0166,1.5407-17.8059,4.2431-26.1405,8.0568C2.779,32.5304-1.6914,56.3725.5312,79.8863c9.6732,7.1476,20.5083,12.603,32.0505,16.0884,2.6014-3.4854,4.8998-7.1981,6.8698-11.0623-3.738-1.3891-7.3497-3.1318-10.8098-5.1523.9092-.6567,1.7932-1.3386,2.6519-1.9953,20.281,9.547,43.7696,9.547,64.0758,0,.8587.7072,1.7427,1.3891,2.6519,1.9953-3.4601,2.0457-7.0718,3.7632-10.835,5.1776,1.97,3.8642,4.2683,7.5769,6.8698,11.0623,11.5419-3.4854,22.3769-8.9156,32.0509-16.0631,2.626-27.2771-4.496-50.9172-18.817-71.8548C98.9811,4.2684,90.1918,1.5659,81.1752.0505l-.0252-.0505ZM42.2802,65.4144c-6.2383,0-11.4159-5.6575-11.4159-12.6535s4.9755-12.6788,11.3907-12.6788,11.5169,5.708,11.4159,12.6788c-.101,6.9708-5.026,12.6535-11.3907,12.6535ZM84.3576,65.4144c-6.2637,0-11.3907-5.6575-11.3907-12.6535s4.9755-12.6788,11.3907-12.6788,11.4917,5.708,11.3906,12.6788c-.101,6.9708-5.026,12.6535-11.3906,12.6535Z';
|
|
||||||
export const discordViewBox = '0 0 126.644 96';
|
|
||||||
6
docs/static/_redirects
vendored
6
docs/static/_redirects
vendored
@@ -27,8 +27,10 @@
|
|||||||
/administration/password-login /administration/system-settings 307
|
/administration/password-login /administration/system-settings 307
|
||||||
/features/search /features/searching 307
|
/features/search /features/searching 307
|
||||||
/features/smart-search /features/searching 307
|
/features/smart-search /features/searching 307
|
||||||
/guides/api-album-sync /community-projects 307
|
/guides/api-album-sync https://awesome.immich.app/ 307
|
||||||
/guides/remove-offline-files /community-projects 307
|
/guides/remove-offline-files https://awesome.immich.app/ 307
|
||||||
|
/community-guides https://awesome.immich.app/ 307
|
||||||
|
/community-projects https://awesome.immich.app/ 307
|
||||||
/overview/introduction /overview/quick-start 307
|
/overview/introduction /overview/quick-start 307
|
||||||
/overview/welcome /overview/quick-start 307
|
/overview/welcome /overview/quick-start 307
|
||||||
/docs/* /:splat 307
|
/docs/* /:splat 307
|
||||||
|
|||||||
@@ -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.8",
|
"@types/node": "^22.18.10",
|
||||||
"@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",
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-unicorn": "^60.0.0",
|
"eslint-plugin-unicorn": "^60.0.0",
|
||||||
"exiftool-vendored": "^28.3.1",
|
"exiftool-vendored": "^31.1.0",
|
||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
"jose": "^5.6.3",
|
"jose": "^5.6.3",
|
||||||
"luxon": "^3.4.4",
|
"luxon": "^3.4.4",
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
...user1Albums[0],
|
...user1Albums[0],
|
||||||
assets: [expect.objectContaining({ isFavorite: false })],
|
assets: [expect.objectContaining({ isFavorite: false })],
|
||||||
|
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
lastModifiedAssetTimestamp: expect.any(String),
|
||||||
startDate: expect.any(String),
|
startDate: expect.any(String),
|
||||||
endDate: expect.any(String),
|
endDate: expect.any(String),
|
||||||
@@ -310,6 +311,7 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
...user1Albums[0],
|
...user1Albums[0],
|
||||||
assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })],
|
assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })],
|
||||||
|
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
lastModifiedAssetTimestamp: expect.any(String),
|
||||||
startDate: expect.any(String),
|
startDate: expect.any(String),
|
||||||
endDate: expect.any(String),
|
endDate: expect.any(String),
|
||||||
@@ -345,6 +347,7 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
...user1Albums[0],
|
...user1Albums[0],
|
||||||
assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })],
|
assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })],
|
||||||
|
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
lastModifiedAssetTimestamp: expect.any(String),
|
||||||
startDate: expect.any(String),
|
startDate: expect.any(String),
|
||||||
endDate: expect.any(String),
|
endDate: expect.any(String),
|
||||||
@@ -362,6 +365,7 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
...user1Albums[0],
|
...user1Albums[0],
|
||||||
assets: [],
|
assets: [],
|
||||||
|
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
||||||
assetCount: 1,
|
assetCount: 1,
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
lastModifiedAssetTimestamp: expect.any(String),
|
||||||
endDate: expect.any(String),
|
endDate: expect.any(String),
|
||||||
@@ -382,6 +386,7 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
...user2Albums[0],
|
...user2Albums[0],
|
||||||
assets: [],
|
assets: [],
|
||||||
|
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
||||||
assetCount: 1,
|
assetCount: 1,
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
lastModifiedAssetTimestamp: expect.any(String),
|
||||||
endDate: expect.any(String),
|
endDate: expect.any(String),
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ test.describe('Registration', () => {
|
|||||||
await page.getByRole('button', { name: 'User Privacy' }).click();
|
await page.getByRole('button', { name: 'User Privacy' }).click();
|
||||||
await page.getByRole('button', { name: 'Storage Template' }).click();
|
await page.getByRole('button', { name: 'Storage Template' }).click();
|
||||||
await page.getByRole('button', { name: 'Backups' }).click();
|
await page.getByRole('button', { name: 'Backups' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Mobile App' }).click();
|
||||||
await page.getByRole('button', { name: 'Done' }).click();
|
await page.getByRole('button', { name: 'Done' }).click();
|
||||||
|
|
||||||
// success
|
// success
|
||||||
@@ -85,6 +86,7 @@ test.describe('Registration', () => {
|
|||||||
await page.getByRole('button', { name: 'Theme' }).click();
|
await page.getByRole('button', { name: 'Theme' }).click();
|
||||||
await page.getByRole('button', { name: 'Language' }).click();
|
await page.getByRole('button', { name: 'Language' }).click();
|
||||||
await page.getByRole('button', { name: 'User Privacy' }).click();
|
await page.getByRole('button', { name: 'User Privacy' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Mobile App' }).click();
|
||||||
await page.getByRole('button', { name: 'Done' }).click();
|
await page.getByRole('button', { name: 'Done' }).click();
|
||||||
|
|
||||||
// success
|
// success
|
||||||
|
|||||||
41
i18n/en.json
41
i18n/en.json
@@ -120,7 +120,7 @@
|
|||||||
"library_settings_description": "Manage external library settings",
|
"library_settings_description": "Manage external library settings",
|
||||||
"library_tasks_description": "Scan external libraries for new and/or changed assets",
|
"library_tasks_description": "Scan external libraries for new and/or changed assets",
|
||||||
"library_watching_enable_description": "Watch external libraries for file changes",
|
"library_watching_enable_description": "Watch external libraries for file changes",
|
||||||
"library_watching_settings": "Library watching (EXPERIMENTAL)",
|
"library_watching_settings": "Library watching [EXPERIMENTAL]",
|
||||||
"library_watching_settings_description": "Automatically watch for changed files",
|
"library_watching_settings_description": "Automatically watch for changed files",
|
||||||
"logging_enable_description": "Enable logging",
|
"logging_enable_description": "Enable logging",
|
||||||
"logging_level_description": "When enabled, what log level to use.",
|
"logging_level_description": "When enabled, what log level to use.",
|
||||||
@@ -211,6 +211,8 @@
|
|||||||
"notification_email_ignore_certificate_errors_description": "Ignore TLS certificate validation errors (not recommended)",
|
"notification_email_ignore_certificate_errors_description": "Ignore TLS certificate validation errors (not recommended)",
|
||||||
"notification_email_password_description": "Password to use when authenticating with the email server",
|
"notification_email_password_description": "Password to use when authenticating with the email server",
|
||||||
"notification_email_port_description": "Port of the email server (e.g 25, 465, or 587)",
|
"notification_email_port_description": "Port of the email server (e.g 25, 465, or 587)",
|
||||||
|
"notification_email_secure": "SMTPS",
|
||||||
|
"notification_email_secure_description": "Use SMTPS (SMTP over TLS)",
|
||||||
"notification_email_sent_test_email_button": "Send test email and save",
|
"notification_email_sent_test_email_button": "Send test email and save",
|
||||||
"notification_email_setting_description": "Settings for sending email notifications",
|
"notification_email_setting_description": "Settings for sending email notifications",
|
||||||
"notification_email_test_email": "Send test email",
|
"notification_email_test_email": "Send test email",
|
||||||
@@ -333,7 +335,7 @@
|
|||||||
"transcoding_max_b_frames": "Maximum B-frames",
|
"transcoding_max_b_frames": "Maximum B-frames",
|
||||||
"transcoding_max_b_frames_description": "Higher values improve compression efficiency, but slow down encoding. May not be compatible with hardware acceleration on older devices. 0 disables B-frames, while -1 sets this value automatically.",
|
"transcoding_max_b_frames_description": "Higher values improve compression efficiency, but slow down encoding. May not be compatible with hardware acceleration on older devices. 0 disables B-frames, while -1 sets this value automatically.",
|
||||||
"transcoding_max_bitrate": "Maximum bitrate",
|
"transcoding_max_bitrate": "Maximum bitrate",
|
||||||
"transcoding_max_bitrate_description": "Setting a max bitrate can make file sizes more predictable at a minor cost to quality. At 720p, typical values are 2600 kbit/s for VP9 or HEVC, or 4500 kbit/s for H.264. Disabled if set to 0.",
|
"transcoding_max_bitrate_description": "Setting a max bitrate can make file sizes more predictable at a minor cost to quality. At 720p, typical values are 2600 kbit/s for VP9 or HEVC, or 4500 kbit/s for H.264. Disabled if set to 0. When no unit is specified, k (for kbit/s) is assumed; therefore 5000, 5000k, and 5M (for Mbit/s) are equivalent.",
|
||||||
"transcoding_max_keyframe_interval": "Maximum keyframe interval",
|
"transcoding_max_keyframe_interval": "Maximum keyframe interval",
|
||||||
"transcoding_max_keyframe_interval_description": "Sets the maximum frame distance between keyframes. Lower values worsen compression efficiency, but improve seek times and may improve quality in scenes with fast movement. 0 sets this value automatically.",
|
"transcoding_max_keyframe_interval_description": "Sets the maximum frame distance between keyframes. Lower values worsen compression efficiency, but improve seek times and may improve quality in scenes with fast movement. 0 sets this value automatically.",
|
||||||
"transcoding_optimal_description": "Videos higher than target resolution or not in an accepted format",
|
"transcoding_optimal_description": "Videos higher than target resolution or not in an accepted format",
|
||||||
@@ -351,7 +353,7 @@
|
|||||||
"transcoding_target_resolution": "Target resolution",
|
"transcoding_target_resolution": "Target resolution",
|
||||||
"transcoding_target_resolution_description": "Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness.",
|
"transcoding_target_resolution_description": "Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness.",
|
||||||
"transcoding_temporal_aq": "Temporal AQ",
|
"transcoding_temporal_aq": "Temporal AQ",
|
||||||
"transcoding_temporal_aq_description": "Applies only to NVENC. Increases quality of high-detail, low-motion scenes. May not be compatible with older devices.",
|
"transcoding_temporal_aq_description": "Applies only to NVENC. Temporal Adaptive Quantization increases quality of high-detail, low-motion scenes. May not be compatible with older devices.",
|
||||||
"transcoding_threads": "Threads",
|
"transcoding_threads": "Threads",
|
||||||
"transcoding_threads_description": "Higher values lead to faster encoding, but leave less room for the server to process other tasks while active. This value should not be more than the number of CPU cores. Maximizes utilization if set to 0.",
|
"transcoding_threads_description": "Higher values lead to faster encoding, but leave less room for the server to process other tasks while active. This value should not be more than the number of CPU cores. Maximizes utilization if set to 0.",
|
||||||
"transcoding_tone_mapping": "Tone-mapping",
|
"transcoding_tone_mapping": "Tone-mapping",
|
||||||
@@ -402,11 +404,11 @@
|
|||||||
"advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from local assets. Activate this setting to load remote images instead.",
|
"advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from local assets. Activate this setting to load remote images instead.",
|
||||||
"advanced_settings_prefer_remote_title": "Prefer remote images",
|
"advanced_settings_prefer_remote_title": "Prefer remote images",
|
||||||
"advanced_settings_proxy_headers_subtitle": "Define proxy headers Immich should send with each network request",
|
"advanced_settings_proxy_headers_subtitle": "Define proxy headers Immich should send with each network request",
|
||||||
"advanced_settings_proxy_headers_title": "Proxy Headers",
|
"advanced_settings_proxy_headers_title": "Custom proxy headers [EXPERIMENTAL]",
|
||||||
"advanced_settings_readonly_mode_subtitle": "Enables the read-only mode where the photos can be only viewed, things like selecting multiple images, sharing, casting, delete are all disabled. Enable/Disable read-only via user avatar from the main screen",
|
"advanced_settings_readonly_mode_subtitle": "Enables the read-only mode where the photos can be only viewed, things like selecting multiple images, sharing, casting, delete are all disabled. Enable/Disable read-only via user avatar from the main screen",
|
||||||
"advanced_settings_readonly_mode_title": "Read-only Mode",
|
"advanced_settings_readonly_mode_title": "Read-only mode",
|
||||||
"advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.",
|
"advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.",
|
||||||
"advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates",
|
"advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates [EXPERIMENTAL]",
|
||||||
"advanced_settings_sync_remote_deletions_subtitle": "Automatically delete or restore an asset on this device when that action is taken on the web",
|
"advanced_settings_sync_remote_deletions_subtitle": "Automatically delete or restore an asset on this device when that action is taken on the web",
|
||||||
"advanced_settings_sync_remote_deletions_title": "Sync remote deletions [EXPERIMENTAL]",
|
"advanced_settings_sync_remote_deletions_title": "Sync remote deletions [EXPERIMENTAL]",
|
||||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
||||||
@@ -466,10 +468,13 @@
|
|||||||
"api_key_description": "This value will only be shown once. Please be sure to copy it before closing the window.",
|
"api_key_description": "This value will only be shown once. Please be sure to copy it before closing the window.",
|
||||||
"api_key_empty": "Your API Key name shouldn't be empty",
|
"api_key_empty": "Your API Key name shouldn't be empty",
|
||||||
"api_keys": "API Keys",
|
"api_keys": "API Keys",
|
||||||
|
"app_architecture_variant": "Variant (Architecture)",
|
||||||
"app_bar_signout_dialog_content": "Are you sure you want to sign out?",
|
"app_bar_signout_dialog_content": "Are you sure you want to sign out?",
|
||||||
"app_bar_signout_dialog_ok": "Yes",
|
"app_bar_signout_dialog_ok": "Yes",
|
||||||
"app_bar_signout_dialog_title": "Sign out",
|
"app_bar_signout_dialog_title": "Sign out",
|
||||||
|
"app_download_links": "App Download Links",
|
||||||
"app_settings": "App Settings",
|
"app_settings": "App Settings",
|
||||||
|
"app_update_available": "App update is available",
|
||||||
"appears_in": "Appears in",
|
"appears_in": "Appears in",
|
||||||
"apply_count": "Apply ({count, number})",
|
"apply_count": "Apply ({count, number})",
|
||||||
"archive": "Archive",
|
"archive": "Archive",
|
||||||
@@ -553,6 +558,7 @@
|
|||||||
"backup_albums_sync": "Backup albums synchronization",
|
"backup_albums_sync": "Backup albums synchronization",
|
||||||
"backup_all": "All",
|
"backup_all": "All",
|
||||||
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
|
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
|
||||||
|
"backup_background_service_complete_notification": "Asset backup complete",
|
||||||
"backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…",
|
"backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…",
|
||||||
"backup_background_service_current_upload_notification": "Uploading {filename}",
|
"backup_background_service_current_upload_notification": "Uploading {filename}",
|
||||||
"backup_background_service_default_notification": "Checking for new assets…",
|
"backup_background_service_default_notification": "Checking for new assets…",
|
||||||
@@ -688,8 +694,8 @@
|
|||||||
"client_cert_import_success_msg": "Client certificate is imported",
|
"client_cert_import_success_msg": "Client certificate is imported",
|
||||||
"client_cert_invalid_msg": "Invalid certificate file or wrong password",
|
"client_cert_invalid_msg": "Invalid certificate file or wrong password",
|
||||||
"client_cert_remove_msg": "Client certificate is removed",
|
"client_cert_remove_msg": "Client certificate is removed",
|
||||||
"client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate Import/Remove is available only before login",
|
"client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate import/removal is available only before login",
|
||||||
"client_cert_title": "SSL Client Certificate",
|
"client_cert_title": "SSL client certificate [EXPERIMENTAL]",
|
||||||
"clockwise": "Сlockwise",
|
"clockwise": "Сlockwise",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"collapse": "Collapse",
|
"collapse": "Collapse",
|
||||||
@@ -701,7 +707,6 @@
|
|||||||
"comments_and_likes": "Comments & likes",
|
"comments_and_likes": "Comments & likes",
|
||||||
"comments_are_disabled": "Comments are disabled",
|
"comments_are_disabled": "Comments are disabled",
|
||||||
"common_create_new_album": "Create new album",
|
"common_create_new_album": "Create new album",
|
||||||
"common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.",
|
|
||||||
"completed": "Completed",
|
"completed": "Completed",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
"confirm_admin_password": "Confirm Admin Password",
|
"confirm_admin_password": "Confirm Admin Password",
|
||||||
@@ -1117,11 +1122,10 @@
|
|||||||
"hash_asset": "Hash asset",
|
"hash_asset": "Hash asset",
|
||||||
"hashed_assets": "Hashed assets",
|
"hashed_assets": "Hashed assets",
|
||||||
"hashing": "Hashing",
|
"hashing": "Hashing",
|
||||||
"header_settings_add_header_tip": "Add Header",
|
"header_settings_add_header_tip": "Add header",
|
||||||
"header_settings_field_validator_msg": "Value cannot be empty",
|
"header_settings_field_validator_msg": "Value cannot be empty",
|
||||||
"header_settings_header_name_input": "Header name",
|
"header_settings_header_name_input": "Header name",
|
||||||
"header_settings_header_value_input": "Header value",
|
"header_settings_header_value_input": "Header value",
|
||||||
"headers_settings_tile_subtitle": "Define proxy headers the app should send with each network request",
|
|
||||||
"headers_settings_tile_title": "Custom proxy headers",
|
"headers_settings_tile_title": "Custom proxy headers",
|
||||||
"hi_user": "Hi {name} ({email})",
|
"hi_user": "Hi {name} ({email})",
|
||||||
"hide_all_people": "Hide all people",
|
"hide_all_people": "Hide all people",
|
||||||
@@ -1346,6 +1350,8 @@
|
|||||||
"minute": "Minute",
|
"minute": "Minute",
|
||||||
"minutes": "Minutes",
|
"minutes": "Minutes",
|
||||||
"missing": "Missing",
|
"missing": "Missing",
|
||||||
|
"mobile_app": "Mobile App",
|
||||||
|
"mobile_app_download_onboarding_note": "You can access these options again from the Utilities page.",
|
||||||
"model": "Model",
|
"model": "Model",
|
||||||
"month": "Month",
|
"month": "Month",
|
||||||
"monthly_title_text_date_format": "MMMM y",
|
"monthly_title_text_date_format": "MMMM y",
|
||||||
@@ -1364,6 +1370,8 @@
|
|||||||
"my_albums": "My albums",
|
"my_albums": "My albums",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"name_or_nickname": "Name or nickname",
|
"name_or_nickname": "Name or nickname",
|
||||||
|
"navigate": "Navigate",
|
||||||
|
"navigate_to_time": "Navigate to Time",
|
||||||
"network_requirement_photos_upload": "Use cellular data to backup photos",
|
"network_requirement_photos_upload": "Use cellular data to backup photos",
|
||||||
"network_requirement_videos_upload": "Use cellular data to backup videos",
|
"network_requirement_videos_upload": "Use cellular data to backup videos",
|
||||||
"network_requirements": "Network Requirements",
|
"network_requirements": "Network Requirements",
|
||||||
@@ -1373,6 +1381,7 @@
|
|||||||
"never": "Never",
|
"never": "Never",
|
||||||
"new_album": "New Album",
|
"new_album": "New Album",
|
||||||
"new_api_key": "New API Key",
|
"new_api_key": "New API Key",
|
||||||
|
"new_date_range": "New date range",
|
||||||
"new_password": "New password",
|
"new_password": "New password",
|
||||||
"new_person": "New person",
|
"new_person": "New person",
|
||||||
"new_pin_code": "New PIN code",
|
"new_pin_code": "New PIN code",
|
||||||
@@ -1423,6 +1432,8 @@
|
|||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
"notifications_setting_description": "Manage notifications",
|
"notifications_setting_description": "Manage notifications",
|
||||||
"oauth": "OAuth",
|
"oauth": "OAuth",
|
||||||
|
"obtainium_configurator": "Obtainium Configurator",
|
||||||
|
"obtainium_configurator_instructions": "Please create an API key and select a variant to create your Obtainium configuration link.",
|
||||||
"official_immich_resources": "Official Immich Resources",
|
"official_immich_resources": "Official Immich Resources",
|
||||||
"offline": "Offline",
|
"offline": "Offline",
|
||||||
"offset": "Offset",
|
"offset": "Offset",
|
||||||
@@ -1544,13 +1555,9 @@
|
|||||||
"privacy": "Privacy",
|
"privacy": "Privacy",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
"profile_drawer_app_logs": "Logs",
|
"profile_drawer_app_logs": "Logs",
|
||||||
"profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.",
|
|
||||||
"profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.",
|
|
||||||
"profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
|
"profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
|
||||||
"profile_drawer_github": "GitHub",
|
"profile_drawer_github": "GitHub",
|
||||||
"profile_drawer_readonly_mode": "Read-only mode enabled. Long-press the user avatar icon to exit.",
|
"profile_drawer_readonly_mode": "Read-only mode enabled. Long-press the user avatar icon to exit.",
|
||||||
"profile_drawer_server_out_of_date_major": "Server is out of date. Please update to the latest major version.",
|
|
||||||
"profile_drawer_server_out_of_date_minor": "Server is out of date. Please update to the latest minor version.",
|
|
||||||
"profile_image_of_user": "Profile image of {user}",
|
"profile_image_of_user": "Profile image of {user}",
|
||||||
"profile_picture_set": "Profile picture set.",
|
"profile_picture_set": "Profile picture set.",
|
||||||
"public_album": "Public album",
|
"public_album": "Public album",
|
||||||
@@ -1779,6 +1786,7 @@
|
|||||||
"server_online": "Server Online",
|
"server_online": "Server Online",
|
||||||
"server_privacy": "Server Privacy",
|
"server_privacy": "Server Privacy",
|
||||||
"server_stats": "Server Stats",
|
"server_stats": "Server Stats",
|
||||||
|
"server_update_available": "Server update is available",
|
||||||
"server_version": "Server Version",
|
"server_version": "Server Version",
|
||||||
"set": "Set",
|
"set": "Set",
|
||||||
"set_as_album_cover": "Set as album cover",
|
"set_as_album_cover": "Set as album cover",
|
||||||
@@ -1807,6 +1815,8 @@
|
|||||||
"setting_notifications_subtitle": "Adjust your notification preferences",
|
"setting_notifications_subtitle": "Adjust your notification preferences",
|
||||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||||
|
"setting_video_viewer_auto_play_subtitle": "Automatically start playing videos when they are opened",
|
||||||
|
"setting_video_viewer_auto_play_title": "Auto play videos",
|
||||||
"setting_video_viewer_looping_title": "Looping",
|
"setting_video_viewer_looping_title": "Looping",
|
||||||
"setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.",
|
"setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.",
|
||||||
"setting_video_viewer_original_video_title": "Force original video",
|
"setting_video_viewer_original_video_title": "Force original video",
|
||||||
@@ -2018,6 +2028,7 @@
|
|||||||
"troubleshoot": "Troubleshoot",
|
"troubleshoot": "Troubleshoot",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
"unable_to_change_pin_code": "Unable to change PIN code",
|
"unable_to_change_pin_code": "Unable to change PIN code",
|
||||||
|
"unable_to_check_version": "Unable to check app or server version",
|
||||||
"unable_to_setup_pin_code": "Unable to setup PIN code",
|
"unable_to_setup_pin_code": "Unable to setup PIN code",
|
||||||
"unarchive": "Unarchive",
|
"unarchive": "Unarchive",
|
||||||
"unarchive_action_prompt": "{count} removed from Archive",
|
"unarchive_action_prompt": "{count} removed from Archive",
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ RUN if [ "$DEVICE" = "rocm" ]; then \
|
|||||||
|
|
||||||
FROM python:3.11-slim-bookworm@sha256:873f91540d53b36327ed4fb018c9669107a4e2a676719720edb4209c4b15d029 AS prod-cpu
|
FROM python:3.11-slim-bookworm@sha256:873f91540d53b36327ed4fb018c9669107a4e2a676719720edb4209c4b15d029 AS prod-cpu
|
||||||
|
|
||||||
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2
|
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \
|
||||||
|
MACHINE_LEARNING_MODEL_ARENA=false
|
||||||
|
|
||||||
FROM python:3.11-slim-bookworm@sha256:873f91540d53b36327ed4fb018c9669107a4e2a676719720edb4209c4b15d029 AS prod-openvino
|
FROM python:3.11-slim-bookworm@sha256:873f91540d53b36327ed4fb018c9669107a4e2a676719720edb4209c4b15d029 AS prod-openvino
|
||||||
|
|
||||||
@@ -88,10 +89,12 @@ RUN apt-get update && \
|
|||||||
|
|
||||||
FROM nvidia/cuda:12.2.2-runtime-ubuntu22.04@sha256:94c1577b2cd9dd6c0312dc04dff9cb2fdce2b268018abc3d7c2dbcacf1155000 AS prod-cuda
|
FROM nvidia/cuda:12.2.2-runtime-ubuntu22.04@sha256:94c1577b2cd9dd6c0312dc04dff9cb2fdce2b268018abc3d7c2dbcacf1155000 AS prod-cuda
|
||||||
|
|
||||||
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2
|
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \
|
||||||
|
MACHINE_LEARNING_MODEL_ARENA=false
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install --no-install-recommends -yqq libcudnn9-cuda-12 && \
|
# Pascal support was dropped in 9.11
|
||||||
|
apt-get install --no-install-recommends -yqq libcudnn9-cuda-12=9.10.2.21-1 && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
@@ -104,7 +107,8 @@ FROM rocm/dev-ubuntu-22.04:6.4.3-complete@sha256:1f7e92ca7e3a3785680473329ed1091
|
|||||||
FROM prod-cpu AS prod-armnn
|
FROM prod-cpu AS prod-armnn
|
||||||
|
|
||||||
ENV LD_LIBRARY_PATH=/opt/armnn \
|
ENV LD_LIBRARY_PATH=/opt/armnn \
|
||||||
LD_PRELOAD=/usr/lib/libmimalloc.so.2
|
LD_PRELOAD=/usr/lib/libmimalloc.so.2 \
|
||||||
|
MACHINE_LEARNING_MODEL_ARENA=false
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends ocl-icd-libopencl1 mesa-opencl-icd libgomp1 && \
|
RUN apt-get update && apt-get install -y --no-install-recommends ocl-icd-libopencl1 mesa-opencl-icd libgomp1 && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
@@ -127,7 +131,8 @@ FROM prod-cpu AS prod-rknn
|
|||||||
# renovate: datasource=github-tags depName=airockchip/rknn-toolkit2
|
# renovate: datasource=github-tags depName=airockchip/rknn-toolkit2
|
||||||
ARG RKNN_TOOLKIT_VERSION="v2.3.0"
|
ARG RKNN_TOOLKIT_VERSION="v2.3.0"
|
||||||
|
|
||||||
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2
|
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \
|
||||||
|
MACHINE_LEARNING_MODEL_ARENA=false
|
||||||
|
|
||||||
ADD --checksum=sha256:73993ed4b440460825f21611731564503cc1d5a0c123746477da6cd574f34885 "https://github.com/airockchip/rknn-toolkit2/raw/refs/tags/${RKNN_TOOLKIT_VERSION}/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so" /usr/lib/
|
ADD --checksum=sha256:73993ed4b440460825f21611731564503cc1d5a0c123746477da6cd574f34885 "https://github.com/airockchip/rknn-toolkit2/raw/refs/tags/${RKNN_TOOLKIT_VERSION}/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so" /usr/lib/
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class Settings(BaseSettings):
|
|||||||
request_threads: int = os.cpu_count() or 4
|
request_threads: int = os.cpu_count() or 4
|
||||||
model_inter_op_threads: int = 0
|
model_inter_op_threads: int = 0
|
||||||
model_intra_op_threads: int = 0
|
model_intra_op_threads: int = 0
|
||||||
|
model_arena: bool = True
|
||||||
ann: bool = True
|
ann: bool = True
|
||||||
ann_fp16_turbo: bool = False
|
ann_fp16_turbo: bool = False
|
||||||
ann_tuning_level: int = 2
|
ann_tuning_level: int = 2
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ SUPPORTED_PROVIDERS = [
|
|||||||
"CUDAExecutionProvider",
|
"CUDAExecutionProvider",
|
||||||
"ROCMExecutionProvider",
|
"ROCMExecutionProvider",
|
||||||
"OpenVINOExecutionProvider",
|
"OpenVINOExecutionProvider",
|
||||||
|
"CoreMLExecutionProvider",
|
||||||
"CPUExecutionProvider",
|
"CPUExecutionProvider",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,14 @@ class OrtSession:
|
|||||||
"precision": "FP32",
|
"precision": "FP32",
|
||||||
"cache_dir": (self.model_path.parent / "openvino").as_posix(),
|
"cache_dir": (self.model_path.parent / "openvino").as_posix(),
|
||||||
}
|
}
|
||||||
|
case "CoreMLExecutionProvider":
|
||||||
|
options = {
|
||||||
|
"ModelFormat": "MLProgram",
|
||||||
|
"MLComputeUnits": "ALL",
|
||||||
|
"SpecializationStrategy": "FastPrediction",
|
||||||
|
"AllowLowPrecisionAccumulationOnGPU": "1",
|
||||||
|
"ModelCacheDirectory": (self.model_path.parent / "coreml").as_posix(),
|
||||||
|
}
|
||||||
case _:
|
case _:
|
||||||
options = {}
|
options = {}
|
||||||
provider_options.append(options)
|
provider_options.append(options)
|
||||||
@@ -115,7 +123,7 @@ class OrtSession:
|
|||||||
@property
|
@property
|
||||||
def _sess_options_default(self) -> ort.SessionOptions:
|
def _sess_options_default(self) -> ort.SessionOptions:
|
||||||
sess_options = ort.SessionOptions()
|
sess_options = ort.SessionOptions()
|
||||||
sess_options.enable_cpu_mem_arena = False
|
sess_options.enable_cpu_mem_arena = settings.model_arena
|
||||||
|
|
||||||
# avoid thread contention between models
|
# avoid thread contention between models
|
||||||
if settings.model_inter_op_threads > 0:
|
if settings.model_inter_op_threads > 0:
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ class TestOrtSession:
|
|||||||
CUDA_EP_OUT_OF_ORDER = ["CPUExecutionProvider", "CUDAExecutionProvider"]
|
CUDA_EP_OUT_OF_ORDER = ["CPUExecutionProvider", "CUDAExecutionProvider"]
|
||||||
TRT_EP = ["TensorrtExecutionProvider", "CUDAExecutionProvider", "CPUExecutionProvider"]
|
TRT_EP = ["TensorrtExecutionProvider", "CUDAExecutionProvider", "CPUExecutionProvider"]
|
||||||
ROCM_EP = ["ROCMExecutionProvider", "CPUExecutionProvider"]
|
ROCM_EP = ["ROCMExecutionProvider", "CPUExecutionProvider"]
|
||||||
|
COREML_EP = ["CoreMLExecutionProvider", "CPUExecutionProvider"]
|
||||||
|
|
||||||
@pytest.mark.providers(CPU_EP)
|
@pytest.mark.providers(CPU_EP)
|
||||||
def test_sets_cpu_provider(self, providers: list[str]) -> None:
|
def test_sets_cpu_provider(self, providers: list[str]) -> None:
|
||||||
@@ -225,6 +226,12 @@ class TestOrtSession:
|
|||||||
|
|
||||||
assert session.providers == self.ROCM_EP
|
assert session.providers == self.ROCM_EP
|
||||||
|
|
||||||
|
@pytest.mark.providers(COREML_EP)
|
||||||
|
def test_uses_coreml(self, providers: list[str]) -> None:
|
||||||
|
session = OrtSession("ViT-B-32__openai")
|
||||||
|
|
||||||
|
assert session.providers == self.COREML_EP
|
||||||
|
|
||||||
def test_sets_provider_kwarg(self) -> None:
|
def test_sets_provider_kwarg(self) -> None:
|
||||||
providers = ["CUDAExecutionProvider"]
|
providers = ["CUDAExecutionProvider"]
|
||||||
session = OrtSession("ViT-B-32__openai", providers=providers)
|
session = OrtSession("ViT-B-32__openai", providers=providers)
|
||||||
@@ -284,7 +291,6 @@ class TestOrtSession:
|
|||||||
assert session.sess_options.execution_mode == ort.ExecutionMode.ORT_SEQUENTIAL
|
assert session.sess_options.execution_mode == ort.ExecutionMode.ORT_SEQUENTIAL
|
||||||
assert session.sess_options.inter_op_num_threads == 1
|
assert session.sess_options.inter_op_num_threads == 1
|
||||||
assert session.sess_options.intra_op_num_threads == 2
|
assert session.sess_options.intra_op_num_threads == 2
|
||||||
assert session.sess_options.enable_cpu_mem_arena is False
|
|
||||||
|
|
||||||
def test_sets_default_sess_options_does_not_set_threads_if_non_cpu_and_default_threads(self) -> None:
|
def test_sets_default_sess_options_does_not_set_threads_if_non_cpu_and_default_threads(self) -> None:
|
||||||
session = OrtSession("ViT-B-32__openai", providers=["CUDAExecutionProvider", "CPUExecutionProvider"])
|
session = OrtSession("ViT-B-32__openai", providers=["CUDAExecutionProvider", "CPUExecutionProvider"])
|
||||||
@@ -302,6 +308,26 @@ class TestOrtSession:
|
|||||||
assert session.sess_options.inter_op_num_threads == 2
|
assert session.sess_options.inter_op_num_threads == 2
|
||||||
assert session.sess_options.intra_op_num_threads == 4
|
assert session.sess_options.intra_op_num_threads == 4
|
||||||
|
|
||||||
|
def test_uses_arena_if_enabled(self, mocker: MockerFixture) -> None:
|
||||||
|
mock_settings = mocker.patch("immich_ml.sessions.ort.settings", autospec=True)
|
||||||
|
mock_settings.model_inter_op_threads = 0
|
||||||
|
mock_settings.model_intra_op_threads = 0
|
||||||
|
mock_settings.model_arena = True
|
||||||
|
|
||||||
|
session = OrtSession("ViT-B-32__openai", providers=["CPUExecutionProvider"])
|
||||||
|
|
||||||
|
assert session.sess_options.enable_cpu_mem_arena
|
||||||
|
|
||||||
|
def test_does_not_use_arena_if_disabled(self, mocker: MockerFixture) -> None:
|
||||||
|
mock_settings = mocker.patch("immich_ml.sessions.ort.settings", autospec=True)
|
||||||
|
mock_settings.model_inter_op_threads = 0
|
||||||
|
mock_settings.model_intra_op_threads = 0
|
||||||
|
mock_settings.model_arena = False
|
||||||
|
|
||||||
|
session = OrtSession("ViT-B-32__openai", providers=["CPUExecutionProvider"])
|
||||||
|
|
||||||
|
assert not session.sess_options.enable_cpu_mem_arena
|
||||||
|
|
||||||
def test_sets_sess_options_kwarg(self) -> None:
|
def test_sets_sess_options_kwarg(self) -> None:
|
||||||
sess_options = ort.SessionOptions()
|
sess_options = ort.SessionOptions()
|
||||||
session = OrtSession(
|
session = OrtSession(
|
||||||
|
|||||||
22
mise.toml
22
mise.toml
@@ -1,7 +1,9 @@
|
|||||||
[tools]
|
[tools]
|
||||||
node = "22.20.0"
|
node = "22.20.0"
|
||||||
flutter = "3.35.5"
|
flutter = "3.35.6"
|
||||||
pnpm = "10.18.1"
|
pnpm = "10.18.1"
|
||||||
|
terragrunt = "0.58.12"
|
||||||
|
opentofu = "1.7.1"
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"]
|
[tools."github:CQLabs/homebrew-dcm"]
|
||||||
version = "1.30.0"
|
version = "1.30.0"
|
||||||
@@ -505,3 +507,21 @@ description = "Auto-fix Dart Code Metrics"
|
|||||||
dir = "mobile"
|
dir = "mobile"
|
||||||
hide = true
|
hide = true
|
||||||
run = "dcm fix lib"
|
run = "dcm fix lib"
|
||||||
|
|
||||||
|
# docs deployment
|
||||||
|
[tasks."tg:fmt"]
|
||||||
|
run = "terragrunt hclfmt"
|
||||||
|
description = "Format terragrunt files"
|
||||||
|
|
||||||
|
[tasks.tf]
|
||||||
|
run = "terragrunt run --all"
|
||||||
|
description = "Wrapper for terragrunt run-all"
|
||||||
|
dir = "{{cwd}}"
|
||||||
|
|
||||||
|
[tasks."tf:fmt"]
|
||||||
|
run = "tofu fmt -recursive tf/"
|
||||||
|
description = "Format terraform files"
|
||||||
|
|
||||||
|
[tasks."tf:init"]
|
||||||
|
run = "mise run tf init -- -reconfigure"
|
||||||
|
dir = "{{cwd}}"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"flutter": "3.35.4"
|
"flutter": "3.35.6"
|
||||||
}
|
}
|
||||||
2
mobile/.vscode/settings.json
vendored
2
mobile/.vscode/settings.json
vendored
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dart.flutterSdkPath": ".fvm/versions/3.35.4",
|
"dart.flutterSdkPath": ".fvm/versions/3.35.6",
|
||||||
"dart.lineLength": 120,
|
"dart.lineLength": 120,
|
||||||
"[dart]": {
|
"[dart]": {
|
||||||
"editor.rulers": [120]
|
"editor.rulers": [120]
|
||||||
|
|||||||
@@ -133,11 +133,15 @@
|
|||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
B231F52D2E93A44A00BC45D1 /* Core */ = {
|
B231F52D2E93A44A00BC45D1 /* Core */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
);
|
||||||
path = Core;
|
path = Core;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
|
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
);
|
||||||
path = Sync;
|
path = Sync;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -526,14 +530,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Copy Pods Resources";
|
name = "[CP] Copy Pods Resources";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
@@ -562,14 +562,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
@@ -718,7 +714,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 230;
|
CURRENT_PROJECT_VERSION = 231;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -862,7 +858,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 230;
|
CURRENT_PROJECT_VERSION = 231;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -892,7 +888,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 230;
|
CURRENT_PROJECT_VERSION = 231;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -926,7 +922,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 230;
|
CURRENT_PROJECT_VERSION = 231;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
@@ -969,7 +965,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 230;
|
CURRENT_PROJECT_VERSION = 231;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
@@ -1009,7 +1005,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 230;
|
CURRENT_PROJECT_VERSION = 231;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
@@ -1048,7 +1044,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 230;
|
CURRENT_PROJECT_VERSION = 231;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@@ -1092,7 +1088,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 230;
|
CURRENT_PROJECT_VERSION = 231;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@@ -1133,7 +1129,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 230;
|
CURRENT_PROJECT_VERSION = 231;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
|||||||
@@ -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.1</string>
|
<string>2.1.0</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>230</string>
|
<string>231</string>
|
||||||
<key>FLTEnableImpeller</key>
|
<key>FLTEnableImpeller</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
|||||||
@@ -103,7 +103,9 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
|
|||||||
let options = PHFetchOptions()
|
let options = PHFetchOptions()
|
||||||
options.sortDescriptors = [NSSortDescriptor(key: "modificationDate", ascending: false)]
|
options.sortDescriptors = [NSSortDescriptor(key: "modificationDate", ascending: false)]
|
||||||
options.includeHiddenAssets = false
|
options.includeHiddenAssets = false
|
||||||
let assets = PHAsset.fetchAssets(in: album, options: options)
|
|
||||||
|
let assets = getAssetsFromAlbum(in: album, options: options)
|
||||||
|
|
||||||
let isCloud = album.assetCollectionSubtype == .albumCloudShared || album.assetCollectionSubtype == .albumMyPhotoStream
|
let isCloud = album.assetCollectionSubtype == .albumCloudShared || album.assetCollectionSubtype == .albumMyPhotoStream
|
||||||
|
|
||||||
var domainAlbum = PlatformAlbum(
|
var domainAlbum = PlatformAlbum(
|
||||||
@@ -201,7 +203,7 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
|
|||||||
let options = PHFetchOptions()
|
let options = PHFetchOptions()
|
||||||
options.predicate = NSPredicate(format: "localIdentifier IN %@", assets.map(\.id))
|
options.predicate = NSPredicate(format: "localIdentifier IN %@", assets.map(\.id))
|
||||||
options.includeHiddenAssets = false
|
options.includeHiddenAssets = false
|
||||||
let result = PHAsset.fetchAssets(in: album, options: options)
|
let result = self.getAssetsFromAlbum(in: album, options: options)
|
||||||
result.enumerateObjects { (asset, _, _) in
|
result.enumerateObjects { (asset, _, _) in
|
||||||
albumAssets[asset.localIdentifier, default: []].append(album.localIdentifier)
|
albumAssets[asset.localIdentifier, default: []].append(album.localIdentifier)
|
||||||
}
|
}
|
||||||
@@ -219,7 +221,7 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
|
|||||||
var ids: [String] = []
|
var ids: [String] = []
|
||||||
let options = PHFetchOptions()
|
let options = PHFetchOptions()
|
||||||
options.includeHiddenAssets = false
|
options.includeHiddenAssets = false
|
||||||
let assets = PHAsset.fetchAssets(in: album, options: options)
|
let assets = getAssetsFromAlbum(in: album, options: options)
|
||||||
assets.enumerateObjects { (asset, _, _) in
|
assets.enumerateObjects { (asset, _, _) in
|
||||||
ids.append(asset.localIdentifier)
|
ids.append(asset.localIdentifier)
|
||||||
}
|
}
|
||||||
@@ -236,7 +238,7 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
|
|||||||
let options = PHFetchOptions()
|
let options = PHFetchOptions()
|
||||||
options.predicate = NSPredicate(format: "creationDate > %@ OR modificationDate > %@", date, date)
|
options.predicate = NSPredicate(format: "creationDate > %@ OR modificationDate > %@", date, date)
|
||||||
options.includeHiddenAssets = false
|
options.includeHiddenAssets = false
|
||||||
let assets = PHAsset.fetchAssets(in: album, options: options)
|
let assets = getAssetsFromAlbum(in: album, options: options)
|
||||||
return Int64(assets.count)
|
return Int64(assets.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +255,7 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
|
|||||||
options.predicate = NSPredicate(format: "creationDate > %@ OR modificationDate > %@", date, date)
|
options.predicate = NSPredicate(format: "creationDate > %@ OR modificationDate > %@", date, date)
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = PHAsset.fetchAssets(in: album, options: options)
|
let result = getAssetsFromAlbum(in: album, options: options)
|
||||||
if(result.count == 0) {
|
if(result.count == 0) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -375,4 +377,13 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
|
|||||||
PHAssetResourceManager.default().cancelDataRequest(requestId)
|
PHAssetResourceManager.default().cancelDataRequest(requestId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func getAssetsFromAlbum(in album: PHAssetCollection, options: PHFetchOptions) -> PHFetchResult<PHAsset> {
|
||||||
|
// Ensure to actually getting all assets for the Recents album
|
||||||
|
if (album.assetCollectionSubtype == .smartAlbumUserLibrary) {
|
||||||
|
return PHAsset.fetchAssets(with: options)
|
||||||
|
} else {
|
||||||
|
return PHAsset.fetchAssets(in: album, options: options)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,3 +49,7 @@ const double kUploadStatusFailed = -1.0;
|
|||||||
const double kUploadStatusCanceled = -2.0;
|
const double kUploadStatusCanceled = -2.0;
|
||||||
|
|
||||||
const int kMinMonthsToEnableScrubberSnap = 12;
|
const int kMinMonthsToEnableScrubberSnap = 12;
|
||||||
|
|
||||||
|
const String kImmichAppStoreLink = "https://apps.apple.com/app/immich/id6449244941";
|
||||||
|
const String kImmichPlayStoreLink = "https://play.google.com/store/apps/details?id=app.alextran.immich";
|
||||||
|
const String kImmichLatestRelease = "https://github.com/immich-app/immich/releases/latest";
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ enum Setting<T> {
|
|||||||
showStorageIndicator<bool>(StoreKey.storageIndicator, true),
|
showStorageIndicator<bool>(StoreKey.storageIndicator, true),
|
||||||
loadOriginal<bool>(StoreKey.loadOriginal, false),
|
loadOriginal<bool>(StoreKey.loadOriginal, false),
|
||||||
loadOriginalVideo<bool>(StoreKey.loadOriginalVideo, false),
|
loadOriginalVideo<bool>(StoreKey.loadOriginalVideo, false),
|
||||||
|
autoPlayVideo<bool>(StoreKey.autoPlayVideo, true),
|
||||||
preferRemoteImage<bool>(StoreKey.preferRemoteImage, false),
|
preferRemoteImage<bool>(StoreKey.preferRemoteImage, false),
|
||||||
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, false),
|
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, false),
|
||||||
enableBackup<bool>(StoreKey.enableBackup, false);
|
enableBackup<bool>(StoreKey.enableBackup, false);
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ enum StoreKey<T> {
|
|||||||
// Read-only Mode settings
|
// Read-only Mode settings
|
||||||
readonlyModeEnabled<bool>._(138),
|
readonlyModeEnabled<bool>._(138),
|
||||||
|
|
||||||
|
autoPlayVideo<bool>._(139),
|
||||||
|
|
||||||
// Experimental stuff
|
// Experimental stuff
|
||||||
photoManagerCustomFilter<bool>._(1000),
|
photoManagerCustomFilter<bool>._(1000),
|
||||||
betaPromptShown<bool>._(1001),
|
betaPromptShown<bool>._(1001),
|
||||||
|
|||||||
@@ -1,17 +1,30 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:immich_mobile/models/server_info/server_config.model.dart';
|
import 'package:immich_mobile/models/server_info/server_config.model.dart';
|
||||||
import 'package:immich_mobile/models/server_info/server_disk_info.model.dart';
|
import 'package:immich_mobile/models/server_info/server_disk_info.model.dart';
|
||||||
import 'package:immich_mobile/models/server_info/server_features.model.dart';
|
import 'package:immich_mobile/models/server_info/server_features.model.dart';
|
||||||
import 'package:immich_mobile/models/server_info/server_version.model.dart';
|
import 'package:immich_mobile/models/server_info/server_version.model.dart';
|
||||||
|
|
||||||
|
enum VersionStatus {
|
||||||
|
upToDate,
|
||||||
|
clientOutOfDate,
|
||||||
|
serverOutOfDate,
|
||||||
|
error;
|
||||||
|
|
||||||
|
String get message => switch (this) {
|
||||||
|
VersionStatus.upToDate => "",
|
||||||
|
VersionStatus.clientOutOfDate => "app_update_available".tr(),
|
||||||
|
VersionStatus.serverOutOfDate => "server_update_available".tr(),
|
||||||
|
VersionStatus.error => "unable_to_check_version".tr(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
class ServerInfo {
|
class ServerInfo {
|
||||||
final ServerVersion serverVersion;
|
final ServerVersion serverVersion;
|
||||||
final ServerVersion latestVersion;
|
final ServerVersion latestVersion;
|
||||||
final ServerFeatures serverFeatures;
|
final ServerFeatures serverFeatures;
|
||||||
final ServerConfig serverConfig;
|
final ServerConfig serverConfig;
|
||||||
final ServerDiskInfo serverDiskInfo;
|
final ServerDiskInfo serverDiskInfo;
|
||||||
final bool isVersionMismatch;
|
final VersionStatus versionStatus;
|
||||||
final bool isNewReleaseAvailable;
|
|
||||||
final String versionMismatchErrorMessage;
|
|
||||||
|
|
||||||
const ServerInfo({
|
const ServerInfo({
|
||||||
required this.serverVersion,
|
required this.serverVersion,
|
||||||
@@ -19,9 +32,7 @@ class ServerInfo {
|
|||||||
required this.serverFeatures,
|
required this.serverFeatures,
|
||||||
required this.serverConfig,
|
required this.serverConfig,
|
||||||
required this.serverDiskInfo,
|
required this.serverDiskInfo,
|
||||||
required this.isVersionMismatch,
|
required this.versionStatus,
|
||||||
required this.isNewReleaseAvailable,
|
|
||||||
required this.versionMismatchErrorMessage,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ServerInfo copyWith({
|
ServerInfo copyWith({
|
||||||
@@ -30,9 +41,7 @@ class ServerInfo {
|
|||||||
ServerFeatures? serverFeatures,
|
ServerFeatures? serverFeatures,
|
||||||
ServerConfig? serverConfig,
|
ServerConfig? serverConfig,
|
||||||
ServerDiskInfo? serverDiskInfo,
|
ServerDiskInfo? serverDiskInfo,
|
||||||
bool? isVersionMismatch,
|
VersionStatus? versionStatus,
|
||||||
bool? isNewReleaseAvailable,
|
|
||||||
String? versionMismatchErrorMessage,
|
|
||||||
}) {
|
}) {
|
||||||
return ServerInfo(
|
return ServerInfo(
|
||||||
serverVersion: serverVersion ?? this.serverVersion,
|
serverVersion: serverVersion ?? this.serverVersion,
|
||||||
@@ -40,15 +49,13 @@ class ServerInfo {
|
|||||||
serverFeatures: serverFeatures ?? this.serverFeatures,
|
serverFeatures: serverFeatures ?? this.serverFeatures,
|
||||||
serverConfig: serverConfig ?? this.serverConfig,
|
serverConfig: serverConfig ?? this.serverConfig,
|
||||||
serverDiskInfo: serverDiskInfo ?? this.serverDiskInfo,
|
serverDiskInfo: serverDiskInfo ?? this.serverDiskInfo,
|
||||||
isVersionMismatch: isVersionMismatch ?? this.isVersionMismatch,
|
versionStatus: versionStatus ?? this.versionStatus,
|
||||||
isNewReleaseAvailable: isNewReleaseAvailable ?? this.isNewReleaseAvailable,
|
|
||||||
versionMismatchErrorMessage: versionMismatchErrorMessage ?? this.versionMismatchErrorMessage,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'ServerInfo(serverVersion: $serverVersion, latestVersion: $latestVersion, serverFeatures: $serverFeatures, serverConfig: $serverConfig, serverDiskInfo: $serverDiskInfo, isVersionMismatch: $isVersionMismatch, isNewReleaseAvailable: $isNewReleaseAvailable, versionMismatchErrorMessage: $versionMismatchErrorMessage)';
|
return 'ServerInfo(serverVersion: $serverVersion, latestVersion: $latestVersion, serverFeatures: $serverFeatures, serverConfig: $serverConfig, serverDiskInfo: $serverDiskInfo, versionStatus: $versionStatus)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -61,9 +68,7 @@ class ServerInfo {
|
|||||||
other.serverFeatures == serverFeatures &&
|
other.serverFeatures == serverFeatures &&
|
||||||
other.serverConfig == serverConfig &&
|
other.serverConfig == serverConfig &&
|
||||||
other.serverDiskInfo == serverDiskInfo &&
|
other.serverDiskInfo == serverDiskInfo &&
|
||||||
other.isVersionMismatch == isVersionMismatch &&
|
other.versionStatus == versionStatus;
|
||||||
other.isNewReleaseAvailable == isNewReleaseAvailable &&
|
|
||||||
other.versionMismatchErrorMessage == versionMismatchErrorMessage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -73,8 +78,6 @@ class ServerInfo {
|
|||||||
serverFeatures.hashCode ^
|
serverFeatures.hashCode ^
|
||||||
serverConfig.hashCode ^
|
serverConfig.hashCode ^
|
||||||
serverDiskInfo.hashCode ^
|
serverDiskInfo.hashCode ^
|
||||||
isVersionMismatch.hashCode ^
|
versionStatus.hashCode;
|
||||||
isNewReleaseAvailable.hashCode ^
|
|
||||||
versionMismatchErrorMessage.hashCode;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,13 @@
|
|||||||
|
import 'package:immich_mobile/utils/semver.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
class ServerVersion {
|
class ServerVersion extends SemVer {
|
||||||
final int major;
|
const ServerVersion({required super.major, required super.minor, required super.patch});
|
||||||
final int minor;
|
|
||||||
final int patch;
|
|
||||||
|
|
||||||
const ServerVersion({required this.major, required this.minor, required this.patch});
|
|
||||||
|
|
||||||
ServerVersion copyWith({int? major, int? minor, int? patch}) {
|
|
||||||
return ServerVersion(major: major ?? this.major, minor: minor ?? this.minor, patch: patch ?? this.patch);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'ServerVersion(major: $major, minor: $minor, patch: $patch)';
|
return 'ServerVersion(major: $major, minor: $minor, patch: $patch)';
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerVersion.fromDto(ServerVersionResponseDto dto) : major = dto.major, minor = dto.minor, patch = dto.patch_;
|
ServerVersion.fromDto(ServerVersionResponseDto dto) : super(major: dto.major, minor: dto.minor, patch: dto.patch_);
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
if (identical(this, other)) return true;
|
|
||||||
|
|
||||||
return other is ServerVersion && other.major == major && other.minor == minor && other.patch == patch;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode {
|
|
||||||
return major.hashCode ^ minor.hashCode ^ patch.hashCode;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
|
import 'package:immich_mobile/generated/intl_keys.g.dart';
|
||||||
|
|
||||||
class SettingsHeader {
|
class SettingsHeader {
|
||||||
String key = "";
|
String key = "";
|
||||||
@@ -60,7 +61,7 @@ class HeaderSettingsPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('advanced_settings_proxy_headers_title').tr(),
|
title: const Text(IntlKeys.headers_settings_tile_title).tr(),
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|||||||
@@ -190,7 +190,10 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
|||||||
isVideoReady.value = true;
|
isVideoReady.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await videoController.play();
|
final autoPlayVideo = ref.read(appSettingsServiceProvider).getSetting<bool>(AppSettingsEnum.autoPlayVideo);
|
||||||
|
if (autoPlayVideo) {
|
||||||
|
await videoController.play();
|
||||||
|
}
|
||||||
await videoController.setVolume(0.9);
|
await videoController.setVolume(0.9);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.severe('Error playing video: $error');
|
log.severe('Error playing video: $error');
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_bu
|
|||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
||||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart';
|
|
||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
|
|
||||||
/// This delete action has the following behavior:
|
/// This delete action has the following behavior:
|
||||||
@@ -23,17 +22,11 @@ class DeleteLocalActionButton extends ConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool? backedUpOnly = await showDialog<bool>(
|
final result = await ref.read(actionProvider.notifier).deleteLocal(source, context);
|
||||||
context: context,
|
if (result == null) {
|
||||||
builder: (BuildContext context) => DeleteLocalOnlyDialog(onDeleteLocal: (_) {}),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (backedUpOnly == null) {
|
|
||||||
// User cancelled the dialog
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final result = await ref.read(actionProvider.notifier).deleteLocal(source, backedUpOnly);
|
|
||||||
ref.read(multiSelectProvider.notifier).reset();
|
ref.read(multiSelectProvider.notifier).reset();
|
||||||
|
|
||||||
if (source == ActionSource.viewer) {
|
if (source == ActionSource.viewer) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
|||||||
|
|
||||||
class DriftActivityTextField extends ConsumerStatefulWidget {
|
class DriftActivityTextField extends ConsumerStatefulWidget {
|
||||||
final bool isEnabled;
|
final bool isEnabled;
|
||||||
|
final bool isBottomSheet;
|
||||||
final String? likeId;
|
final String? likeId;
|
||||||
final Function(String) onSubmit;
|
final Function(String) onSubmit;
|
||||||
final Function()? onKeyboardFocus;
|
final Function()? onKeyboardFocus;
|
||||||
@@ -16,6 +17,7 @@ class DriftActivityTextField extends ConsumerStatefulWidget {
|
|||||||
this.isEnabled = true,
|
this.isEnabled = true,
|
||||||
this.likeId,
|
this.likeId,
|
||||||
this.onKeyboardFocus,
|
this.onKeyboardFocus,
|
||||||
|
this.isBottomSheet = false,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -34,7 +36,9 @@ class _DriftActivityTextFieldState extends ConsumerState<DriftActivityTextField>
|
|||||||
inputController = TextEditingController();
|
inputController = TextEditingController();
|
||||||
inputFocusNode = FocusNode();
|
inputFocusNode = FocusNode();
|
||||||
|
|
||||||
inputFocusNode.requestFocus();
|
if (!widget.isBottomSheet) {
|
||||||
|
inputFocusNode.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
inputFocusNode.addListener(() {
|
inputFocusNode.addListener(() {
|
||||||
if (inputFocusNode.hasFocus) {
|
if (inputFocusNode.hasFocus) {
|
||||||
@@ -72,7 +76,7 @@ class _DriftActivityTextFieldState extends ConsumerState<DriftActivityTextField>
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
padding: EdgeInsets.symmetric(vertical: widget.isBottomSheet ? 0 : 10),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: inputController,
|
controller: inputController,
|
||||||
enabled: widget.isEnabled,
|
enabled: widget.isEnabled,
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/presentation/widgets/album/drift_activity_text_field.dart';
|
||||||
|
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||||
|
import 'package:immich_mobile/providers/activity.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
import 'package:immich_mobile/widgets/activities/activity_tile.dart';
|
||||||
|
import 'package:immich_mobile/widgets/activities/dismissible_activity.dart';
|
||||||
|
|
||||||
|
class ActivitiesBottomSheet extends HookConsumerWidget {
|
||||||
|
final DraggableScrollableController controller;
|
||||||
|
final double initialChildSize;
|
||||||
|
final bool scrollToBottomInitially;
|
||||||
|
|
||||||
|
const ActivitiesBottomSheet({
|
||||||
|
required this.controller,
|
||||||
|
this.initialChildSize = 0.35,
|
||||||
|
this.scrollToBottomInitially = true,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final album = ref.watch(currentRemoteAlbumProvider)!;
|
||||||
|
final asset = ref.watch(currentAssetNotifier) as RemoteAsset?;
|
||||||
|
final user = ref.watch(currentUserProvider);
|
||||||
|
|
||||||
|
final activityNotifier = ref.read(albumActivityProvider(album.id, asset?.id).notifier);
|
||||||
|
final activities = ref.watch(albumActivityProvider(album.id, asset?.id));
|
||||||
|
|
||||||
|
Future<void> onAddComment(String comment) async {
|
||||||
|
await activityNotifier.addComment(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildActivitiesSliver() {
|
||||||
|
return activities.widgetWhen(
|
||||||
|
onLoading: () => const SliverToBoxAdapter(child: SizedBox.shrink()),
|
||||||
|
onData: (data) {
|
||||||
|
return SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
|
if (index == data.length) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
final activity = data[data.length - 1 - index];
|
||||||
|
final canDelete = activity.user.id == user?.id || album.ownerId == user?.id;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 1),
|
||||||
|
child: DismissibleActivity(
|
||||||
|
activity.id,
|
||||||
|
ActivityTile(activity, isBottomSheet: true),
|
||||||
|
onDismiss: canDelete
|
||||||
|
? (activityId) async => await activityNotifier.removeActivity(activity.id)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}, childCount: data.length + 1),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BaseBottomSheet(
|
||||||
|
actions: [],
|
||||||
|
slivers: [buildActivitiesSliver()],
|
||||||
|
footer: Padding(
|
||||||
|
// TODO: avoid fixed padding, use context.padding.bottom
|
||||||
|
padding: const EdgeInsets.only(bottom: 32),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Divider(indent: 16, endIndent: 16),
|
||||||
|
DriftActivityTextField(
|
||||||
|
isEnabled: album.isActivityEnabled,
|
||||||
|
isBottomSheet: true,
|
||||||
|
// likeId: likedId,
|
||||||
|
onSubmit: onAddComment,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
controller: controller,
|
||||||
|
initialChildSize: initialChildSize,
|
||||||
|
minChildSize: 0.1,
|
||||||
|
maxChildSize: 0.88,
|
||||||
|
expand: false,
|
||||||
|
shouldCloseOnMinExtent: false,
|
||||||
|
resizeOnScroll: false,
|
||||||
|
backgroundColor: context.isDarkTheme ? Colors.black : Colors.white,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_bar.widge
|
|||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/top_app_bar.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/top_app_bar.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart';
|
||||||
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/activities_bottom_sheet.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/presentation/widgets/images/thumbnail.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
|
||||||
@@ -418,7 +419,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
|
|
||||||
if (event is ViewerOpenBottomSheetEvent) {
|
if (event is ViewerOpenBottomSheetEvent) {
|
||||||
final extent = _kBottomSheetMinimumExtent + 0.3;
|
final extent = _kBottomSheetMinimumExtent + 0.3;
|
||||||
_openBottomSheet(scaffoldContext!, extent: extent);
|
_openBottomSheet(scaffoldContext!, extent: extent, activitiesMode: event.activitiesMode);
|
||||||
final offset = _getVerticalOffsetForBottomSheet(extent);
|
final offset = _getVerticalOffsetForBottomSheet(extent);
|
||||||
viewController?.position = Offset(0, -offset);
|
viewController?.position = Offset(0, -offset);
|
||||||
return;
|
return;
|
||||||
@@ -460,7 +461,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openBottomSheet(BuildContext ctx, {double extent = _kBottomSheetMinimumExtent}) {
|
void _openBottomSheet(BuildContext ctx, {double extent = _kBottomSheetMinimumExtent, bool activitiesMode = false}) {
|
||||||
ref.read(assetViewerProvider.notifier).setBottomSheet(true);
|
ref.read(assetViewerProvider.notifier).setBottomSheet(true);
|
||||||
initialScale = viewController?.scale;
|
initialScale = viewController?.scale;
|
||||||
// viewController?.updateMultiple(scale: (viewController?.scale ?? 1.0) + 0.01);
|
// viewController?.updateMultiple(scale: (viewController?.scale ?? 1.0) + 0.01);
|
||||||
@@ -474,7 +475,9 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
builder: (_) {
|
builder: (_) {
|
||||||
return NotificationListener<Notification>(
|
return NotificationListener<Notification>(
|
||||||
onNotification: _onNotification,
|
onNotification: _onNotification,
|
||||||
child: AssetDetailBottomSheet(controller: bottomSheetController, initialChildSize: extent),
|
child: activitiesMode
|
||||||
|
? ActivitiesBottomSheet(controller: bottomSheetController, initialChildSize: extent)
|
||||||
|
: AssetDetailBottomSheet(controller: bottomSheetController, initialChildSize: extent),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provi
|
|||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
class ViewerOpenBottomSheetEvent extends Event {
|
class ViewerOpenBottomSheetEvent extends Event {
|
||||||
const ViewerOpenBottomSheetEvent();
|
final bool activitiesMode;
|
||||||
|
const ViewerOpenBottomSheetEvent({this.activitiesMode = false});
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewerReloadAssetEvent extends Event {
|
class ViewerReloadAssetEvent extends Event {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_actio
|
|||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
||||||
|
import 'package:immich_mobile/providers/activity.provider.dart';
|
||||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
import 'package:immich_mobile/providers/cast.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/providers/infrastructure/current_album.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
||||||
@@ -53,6 +54,10 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||||||
int opacity = ref.watch(assetViewerProvider.select((state) => state.backgroundOpacity));
|
int opacity = ref.watch(assetViewerProvider.select((state) => state.backgroundOpacity));
|
||||||
final showControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
final showControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
||||||
|
|
||||||
|
if (album != null && album.isActivityEnabled && album.isShared && asset is RemoteAsset) {
|
||||||
|
ref.watch(albumActivityProvider(album.id, asset.id));
|
||||||
|
}
|
||||||
|
|
||||||
if (!showControls) {
|
if (!showControls) {
|
||||||
opacity = 0;
|
opacity = 0;
|
||||||
}
|
}
|
||||||
@@ -66,7 +71,7 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.chat_outlined),
|
icon: const Icon(Icons.chat_outlined),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.navigateTo(const DriftActivitiesRoute());
|
EventStream.shared.emit(const ViewerOpenBottomSheetEvent(activitiesMode: true));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (showViewInTimelineButton)
|
if (showViewInTimelineButton)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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';
|
||||||
import 'package:immich_mobile/domain/models/setting.model.dart';
|
import 'package:immich_mobile/domain/models/setting.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/setting.service.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
||||||
@@ -218,7 +219,10 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await videoController.play();
|
final autoPlayVideo = AppSetting.get(Setting.autoPlayVideo);
|
||||||
|
if (autoPlayVideo) {
|
||||||
|
await videoController.play();
|
||||||
|
}
|
||||||
await videoController.setVolume(0.9);
|
await videoController.setVolume(0.9);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.severe('Error playing video: $error');
|
log.severe('Error playing video: $error');
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class BaseBottomSheet extends ConsumerStatefulWidget {
|
|||||||
final List<Widget> actions;
|
final List<Widget> actions;
|
||||||
final DraggableScrollableController? controller;
|
final DraggableScrollableController? controller;
|
||||||
final List<Widget>? slivers;
|
final List<Widget>? slivers;
|
||||||
|
final Widget? footer;
|
||||||
final double initialChildSize;
|
final double initialChildSize;
|
||||||
final double minChildSize;
|
final double minChildSize;
|
||||||
final double maxChildSize;
|
final double maxChildSize;
|
||||||
@@ -20,6 +21,7 @@ class BaseBottomSheet extends ConsumerStatefulWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
required this.actions,
|
required this.actions,
|
||||||
this.slivers,
|
this.slivers,
|
||||||
|
this.footer,
|
||||||
this.controller,
|
this.controller,
|
||||||
this.initialChildSize = 0.35,
|
this.initialChildSize = 0.35,
|
||||||
double? minChildSize,
|
double? minChildSize,
|
||||||
@@ -73,24 +75,35 @@ class _BaseDraggableScrollableSheetState extends ConsumerState<BaseBottomSheet>
|
|||||||
elevation: 3.0,
|
elevation: 3.0,
|
||||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(18))),
|
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(18))),
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 0),
|
margin: const EdgeInsets.symmetric(horizontal: 0),
|
||||||
child: CustomScrollView(
|
child: Column(
|
||||||
controller: scrollController,
|
children: [
|
||||||
slivers: [
|
Expanded(
|
||||||
const SliverPersistentHeader(delegate: _DragHandleDelegate(), pinned: true),
|
child: CustomScrollView(
|
||||||
if (widget.actions.isNotEmpty)
|
controller: scrollController,
|
||||||
SliverToBoxAdapter(
|
slivers: [
|
||||||
child: Column(
|
const SliverPersistentHeader(delegate: _DragHandleDelegate(), pinned: true),
|
||||||
children: [
|
if (widget.actions.isNotEmpty)
|
||||||
SizedBox(
|
SliverToBoxAdapter(
|
||||||
height: 115,
|
child: Column(
|
||||||
child: ListView(shrinkWrap: true, scrollDirection: Axis.horizontal, children: widget.actions),
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 115,
|
||||||
|
child: ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
children: widget.actions,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(indent: 16, endIndent: 16),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const Divider(indent: 16, endIndent: 16),
|
if (widget.slivers != null) ...widget.slivers!,
|
||||||
const SizedBox(height: 16),
|
],
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (widget.slivers != null) ...widget.slivers!,
|
),
|
||||||
|
if (widget.footer != null) widget.footer!,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import 'package:immich_mobile/services/action.service.dart';
|
|||||||
import 'package:immich_mobile/services/download.service.dart';
|
import 'package:immich_mobile/services/download.service.dart';
|
||||||
import 'package:immich_mobile/services/timeline.service.dart';
|
import 'package:immich_mobile/services/timeline.service.dart';
|
||||||
import 'package:immich_mobile/services/upload.service.dart';
|
import 'package:immich_mobile/services/upload.service.dart';
|
||||||
|
import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
@@ -260,10 +261,23 @@ class ActionNotifier extends Notifier<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ActionResult> deleteLocal(ActionSource source, bool backedUpOnly) async {
|
Future<ActionResult?> deleteLocal(ActionSource source, BuildContext context) async {
|
||||||
|
// Always perform the operation if there is only one merged asset
|
||||||
|
final assets = _getAssets(source);
|
||||||
|
bool? backedUpOnly = assets.length == 1 && assets.first.storage == AssetState.merged
|
||||||
|
? true
|
||||||
|
: await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) => DeleteLocalOnlyDialog(onDeleteLocal: (_) {}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (backedUpOnly == null) {
|
||||||
|
// User cancelled the dialog
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
final List<String> ids;
|
final List<String> ids;
|
||||||
if (backedUpOnly) {
|
if (backedUpOnly) {
|
||||||
final assets = _getAssets(source);
|
|
||||||
ids = assets.where((asset) => asset.storage == AssetState.merged).map((asset) => asset.localId!).toList();
|
ids = assets.where((asset) => asset.storage == AssetState.merged).map((asset) => asset.localId!).toList();
|
||||||
} else {
|
} else {
|
||||||
ids = _getLocalIdsForSource(source);
|
ids = _getLocalIdsForSource(source);
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/models/server_info/server_config.model.dart';
|
import 'package:immich_mobile/models/server_info/server_config.model.dart';
|
||||||
import 'package:immich_mobile/models/server_info/server_disk_info.model.dart';
|
import 'package:immich_mobile/models/server_info/server_disk_info.model.dart';
|
||||||
import 'package:immich_mobile/models/server_info/server_features.model.dart';
|
import 'package:immich_mobile/models/server_info/server_features.model.dart';
|
||||||
import 'package:immich_mobile/models/server_info/server_info.model.dart';
|
import 'package:immich_mobile/models/server_info/server_info.model.dart';
|
||||||
import 'package:immich_mobile/models/server_info/server_version.model.dart';
|
import 'package:immich_mobile/models/server_info/server_version.model.dart';
|
||||||
import 'package:immich_mobile/services/server_info.service.dart';
|
import 'package:immich_mobile/services/server_info.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/semver.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
@@ -24,9 +25,7 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
|
|||||||
mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json',
|
mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json',
|
||||||
),
|
),
|
||||||
serverDiskInfo: ServerDiskInfo(diskAvailable: "0", diskSize: "0", diskUse: "0", diskUsagePercentage: 0),
|
serverDiskInfo: ServerDiskInfo(diskAvailable: "0", diskSize: "0", diskUse: "0", diskUsagePercentage: 0),
|
||||||
isVersionMismatch: false,
|
versionStatus: VersionStatus.upToDate,
|
||||||
isNewReleaseAvailable: false,
|
|
||||||
versionMismatchErrorMessage: "",
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -43,73 +42,42 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
|
|||||||
try {
|
try {
|
||||||
final serverVersion = await _serverInfoService.getServerVersion();
|
final serverVersion = await _serverInfoService.getServerVersion();
|
||||||
|
|
||||||
|
// using isClientOutOfDate since that will show to users reguardless of if they are an admin
|
||||||
if (serverVersion == null) {
|
if (serverVersion == null) {
|
||||||
state = state.copyWith(isVersionMismatch: true, versionMismatchErrorMessage: "common_server_error".tr());
|
state = state.copyWith(versionStatus: VersionStatus.error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _checkServerVersionMismatch(serverVersion);
|
await _checkServerVersionMismatch(serverVersion);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.severe("Failed to get server version", e, stackTrace);
|
_log.severe("Failed to get server version", e, stackTrace);
|
||||||
state = state.copyWith(isVersionMismatch: true);
|
state = state.copyWith(versionStatus: VersionStatus.error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkServerVersionMismatch(ServerVersion serverVersion) async {
|
_checkServerVersionMismatch(ServerVersion serverVersion, {ServerVersion? latestVersion}) async {
|
||||||
state = state.copyWith(serverVersion: serverVersion);
|
state = state.copyWith(serverVersion: serverVersion, latestVersion: latestVersion);
|
||||||
|
|
||||||
var packageInfo = await PackageInfo.fromPlatform();
|
var packageInfo = await PackageInfo.fromPlatform();
|
||||||
|
SemVer clientVersion = SemVer.fromString(packageInfo.version);
|
||||||
|
|
||||||
Map<String, int> appVersion = _getDetailVersion(packageInfo.version);
|
if (serverVersion < clientVersion || (latestVersion != null && serverVersion < latestVersion)) {
|
||||||
|
state = state.copyWith(versionStatus: VersionStatus.serverOutOfDate);
|
||||||
if (appVersion["major"]! > serverVersion.major) {
|
|
||||||
state = state.copyWith(
|
|
||||||
isVersionMismatch: true,
|
|
||||||
versionMismatchErrorMessage: "profile_drawer_server_out_of_date_major".tr(),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appVersion["major"]! < serverVersion.major) {
|
if (clientVersion < serverVersion) {
|
||||||
state = state.copyWith(
|
state = state.copyWith(versionStatus: VersionStatus.clientOutOfDate);
|
||||||
isVersionMismatch: true,
|
|
||||||
versionMismatchErrorMessage: "profile_drawer_client_out_of_date_major".tr(),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appVersion["minor"]! > serverVersion.minor) {
|
state = state.copyWith(versionStatus: VersionStatus.upToDate);
|
||||||
state = state.copyWith(
|
|
||||||
isVersionMismatch: true,
|
|
||||||
versionMismatchErrorMessage: "profile_drawer_server_out_of_date_minor".tr(),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appVersion["minor"]! < serverVersion.minor) {
|
|
||||||
state = state.copyWith(
|
|
||||||
isVersionMismatch: true,
|
|
||||||
versionMismatchErrorMessage: "profile_drawer_client_out_of_date_minor".tr(),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
state = state.copyWith(isVersionMismatch: false, versionMismatchErrorMessage: "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNewRelease(ServerVersion serverVersion, ServerVersion latestVersion) {
|
handleReleaseInfo(ServerVersion serverVersion, ServerVersion latestVersion) {
|
||||||
// Update local server version
|
// Update local server version
|
||||||
_checkServerVersionMismatch(serverVersion);
|
_checkServerVersionMismatch(serverVersion, latestVersion: latestVersion);
|
||||||
|
|
||||||
final majorEqual = latestVersion.major == serverVersion.major;
|
|
||||||
final minorEqual = majorEqual && latestVersion.minor == serverVersion.minor;
|
|
||||||
final newVersionAvailable =
|
|
||||||
latestVersion.major > serverVersion.major ||
|
|
||||||
(majorEqual && latestVersion.minor > serverVersion.minor) ||
|
|
||||||
(minorEqual && latestVersion.patch > serverVersion.patch);
|
|
||||||
|
|
||||||
state = state.copyWith(latestVersion: latestVersion, isNewReleaseAvailable: newVersionAvailable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getServerFeatures() async {
|
getServerFeatures() async {
|
||||||
@@ -127,18 +95,15 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
|
|||||||
}
|
}
|
||||||
state = state.copyWith(serverConfig: serverConfig);
|
state = state.copyWith(serverConfig: serverConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, int> _getDetailVersion(String version) {
|
|
||||||
List<String> detail = version.split(".");
|
|
||||||
|
|
||||||
var major = detail[0];
|
|
||||||
var minor = detail[1];
|
|
||||||
var patch = detail[2];
|
|
||||||
|
|
||||||
return {"major": int.parse(major), "minor": int.parse(minor), "patch": int.parse(patch.replaceAll("-DEBUG", ""))};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final serverInfoProvider = StateNotifierProvider<ServerInfoNotifier, ServerInfo>((ref) {
|
final serverInfoProvider = StateNotifierProvider<ServerInfoNotifier, ServerInfo>((ref) {
|
||||||
return ServerInfoNotifier(ref.read(serverInfoServiceProvider));
|
return ServerInfoNotifier(ref.read(serverInfoServiceProvider));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final versionWarningPresentProvider = Provider.family<bool, UserDto?>((ref, user) {
|
||||||
|
final serverInfo = ref.watch(serverInfoProvider);
|
||||||
|
return serverInfo.versionStatus == VersionStatus.clientOutOfDate ||
|
||||||
|
serverInfo.versionStatus == VersionStatus.error ||
|
||||||
|
((user?.isAdmin ?? false) && serverInfo.versionStatus == VersionStatus.serverOutOfDate);
|
||||||
|
});
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ import 'package:immich_mobile/providers/server_info.provider.dart';
|
|||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:immich_mobile/services/sync.service.dart';
|
import 'package:immich_mobile/services/sync.service.dart';
|
||||||
import 'package:immich_mobile/utils/debounce.dart';
|
import 'package:immich_mobile/utils/debounce.dart';
|
||||||
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
import 'package:socket_io_client/socket_io_client.dart';
|
import 'package:socket_io_client/socket_io_client.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
|
||||||
|
|
||||||
enum PendingAction { assetDelete, assetUploaded, assetHidden, assetTrash }
|
enum PendingAction { assetDelete, assetUploaded, assetHidden, assetTrash }
|
||||||
|
|
||||||
@@ -307,7 +307,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
|
|
||||||
final serverVersion = ServerVersion.fromDto(serverVersionDto);
|
final serverVersion = ServerVersion.fromDto(serverVersionDto);
|
||||||
final releaseVersion = ServerVersion.fromDto(releaseVersionDto);
|
final releaseVersion = ServerVersion.fromDto(releaseVersionDto);
|
||||||
_ref.read(serverInfoProvider.notifier).handleNewRelease(serverVersion, releaseVersion);
|
_ref.read(serverInfoProvider.notifier).handleReleaseInfo(serverVersion, releaseVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSyncAssetUploadReady(dynamic data) {
|
void _handleSyncAssetUploadReady(dynamic data) {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ enum AppSettingsEnum<T> {
|
|||||||
preferRemoteImage<bool>(StoreKey.preferRemoteImage, null, false),
|
preferRemoteImage<bool>(StoreKey.preferRemoteImage, null, false),
|
||||||
loopVideo<bool>(StoreKey.loopVideo, "loopVideo", true),
|
loopVideo<bool>(StoreKey.loopVideo, "loopVideo", true),
|
||||||
loadOriginalVideo<bool>(StoreKey.loadOriginalVideo, "loadOriginalVideo", false),
|
loadOriginalVideo<bool>(StoreKey.loadOriginalVideo, "loadOriginalVideo", false),
|
||||||
|
autoPlayVideo<bool>(StoreKey.autoPlayVideo, "autoPlayVideo", true),
|
||||||
mapThemeMode<int>(StoreKey.mapThemeMode, null, 0),
|
mapThemeMode<int>(StoreKey.mapThemeMode, null, 0),
|
||||||
mapShowFavoriteOnly<bool>(StoreKey.mapShowFavoriteOnly, null, false),
|
mapShowFavoriteOnly<bool>(StoreKey.mapShowFavoriteOnly, null, false),
|
||||||
mapIncludeArchived<bool>(StoreKey.mapIncludeArchived, null, false),
|
mapIncludeArchived<bool>(StoreKey.mapIncludeArchived, null, false),
|
||||||
|
|||||||
@@ -43,14 +43,14 @@ void configureFileDownloaderNotifications() {
|
|||||||
FileDownloader().configureNotificationForGroup(
|
FileDownloader().configureNotificationForGroup(
|
||||||
kManualUploadGroup,
|
kManualUploadGroup,
|
||||||
running: TaskNotification('uploading_media'.t(), 'backup_background_service_in_progress_notification'.t()),
|
running: TaskNotification('uploading_media'.t(), 'backup_background_service_in_progress_notification'.t()),
|
||||||
complete: TaskNotification('upload_finished'.t(), 'backup_background_service_in_progress_notification'.t()),
|
complete: TaskNotification('upload_finished'.t(), 'backup_background_service_complete_notification'.t()),
|
||||||
groupNotificationId: kManualUploadGroup,
|
groupNotificationId: kManualUploadGroup,
|
||||||
);
|
);
|
||||||
|
|
||||||
FileDownloader().configureNotificationForGroup(
|
FileDownloader().configureNotificationForGroup(
|
||||||
kBackupGroup,
|
kBackupGroup,
|
||||||
running: TaskNotification('uploading_media'.t(), 'backup_background_service_in_progress_notification'.t()),
|
running: TaskNotification('uploading_media'.t(), 'backup_background_service_in_progress_notification'.t()),
|
||||||
complete: TaskNotification('upload_finished'.t(), 'backup_background_service_in_progress_notification'.t()),
|
complete: TaskNotification('upload_finished'.t(), 'backup_background_service_complete_notification'.t()),
|
||||||
groupNotificationId: kBackupGroup,
|
groupNotificationId: kBackupGroup,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
59
mobile/lib/utils/semver.dart
Normal file
59
mobile/lib/utils/semver.dart
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
class SemVer {
|
||||||
|
final int major;
|
||||||
|
final int minor;
|
||||||
|
final int patch;
|
||||||
|
|
||||||
|
const SemVer({required this.major, required this.minor, required this.patch});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '$major.$minor.$patch';
|
||||||
|
}
|
||||||
|
|
||||||
|
SemVer copyWith({int? major, int? minor, int? patch}) {
|
||||||
|
return SemVer(major: major ?? this.major, minor: minor ?? this.minor, patch: patch ?? this.patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SemVer.fromString(String version) {
|
||||||
|
final parts = version.split('.');
|
||||||
|
return SemVer(major: int.parse(parts[0]), minor: int.parse(parts[1]), patch: int.parse(parts[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator >(SemVer other) {
|
||||||
|
if (major != other.major) {
|
||||||
|
return major > other.major;
|
||||||
|
}
|
||||||
|
if (minor != other.minor) {
|
||||||
|
return minor > other.minor;
|
||||||
|
}
|
||||||
|
return patch > other.patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator <(SemVer other) {
|
||||||
|
if (major != other.major) {
|
||||||
|
return major < other.major;
|
||||||
|
}
|
||||||
|
if (minor != other.minor) {
|
||||||
|
return minor < other.minor;
|
||||||
|
}
|
||||||
|
return patch < other.patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator >=(SemVer other) {
|
||||||
|
return this > other || this == other;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator <=(SemVer other) {
|
||||||
|
return this < other || this == other;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other is SemVer && other.major == major && other.minor == minor && other.patch == patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => major.hashCode ^ minor.hashCode ^ patch.hashCode;
|
||||||
|
}
|
||||||
@@ -9,8 +9,9 @@ import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
|||||||
|
|
||||||
class ActivityTile extends HookConsumerWidget {
|
class ActivityTile extends HookConsumerWidget {
|
||||||
final Activity activity;
|
final Activity activity;
|
||||||
|
final bool isBottomSheet;
|
||||||
|
|
||||||
const ActivityTile(this.activity, {super.key});
|
const ActivityTile(this.activity, {super.key, this.isBottomSheet = false});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -18,21 +19,23 @@ class ActivityTile extends HookConsumerWidget {
|
|||||||
final isLike = activity.type == ActivityType.like;
|
final isLike = activity.type == ActivityType.like;
|
||||||
// Asset thumbnail is displayed when we are accessing activities from the album page
|
// Asset thumbnail is displayed when we are accessing activities from the album page
|
||||||
// currentAssetProvider will not be set until we open the gallery viewer
|
// currentAssetProvider will not be set until we open the gallery viewer
|
||||||
final showAssetThumbnail = asset == null && activity.assetId != null;
|
final showAssetThumbnail = asset == null && activity.assetId != null && !isBottomSheet;
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
minVerticalPadding: 15,
|
minVerticalPadding: 15,
|
||||||
leading: isLike
|
leading: isLike
|
||||||
? Container(
|
? Container(
|
||||||
width: 44,
|
width: isBottomSheet ? 30 : 44,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Icon(Icons.favorite_rounded, color: Colors.red[700]),
|
child: Icon(Icons.favorite_rounded, color: Colors.red[700]),
|
||||||
)
|
)
|
||||||
|
: isBottomSheet
|
||||||
|
? UserCircleAvatar(user: activity.user, size: 30, radius: 15)
|
||||||
: UserCircleAvatar(user: activity.user),
|
: UserCircleAvatar(user: activity.user),
|
||||||
title: _ActivityTitle(
|
title: _ActivityTitle(
|
||||||
userName: activity.user.name,
|
userName: activity.user.name,
|
||||||
createdAt: activity.createdAt.timeAgo(),
|
createdAt: activity.createdAt.timeAgo(),
|
||||||
leftAlign: isLike || showAssetThumbnail,
|
leftAlign: isBottomSheet ? false : (isLike || showAssetThumbnail),
|
||||||
),
|
),
|
||||||
// No subtitle for like, so center title
|
// No subtitle for like, so center title
|
||||||
titleAlignment: !isLike ? ListTileTitleAlignment.top : ListTileTitleAlignment.center,
|
titleAlignment: !isLike ? ListTileTitleAlignment.top : ListTileTitleAlignment.center,
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import 'package:immich_mobile/extensions/theme_extensions.dart';
|
|||||||
import 'package:immich_mobile/models/server_info/server_info.model.dart';
|
import 'package:immich_mobile/models/server_info/server_info.model.dart';
|
||||||
import 'package:immich_mobile/providers/locale_provider.dart';
|
import 'package:immich_mobile/providers/locale_provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/utils/url_helper.dart';
|
import 'package:immich_mobile/utils/url_helper.dart';
|
||||||
|
import 'package:immich_mobile/widgets/common/app_bar_dialog/server_update_notification.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
class AppBarServerInfo extends HookConsumerWidget {
|
class AppBarServerInfo extends HookConsumerWidget {
|
||||||
@@ -17,6 +19,8 @@ class AppBarServerInfo extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
ref.watch(localeProvider);
|
ref.watch(localeProvider);
|
||||||
ServerInfo serverInfoState = ref.watch(serverInfoProvider);
|
ServerInfo serverInfoState = ref.watch(serverInfoProvider);
|
||||||
|
final user = ref.watch(currentUserProvider);
|
||||||
|
final bool showVersionWarning = ref.watch(versionWarningPresentProvider(user));
|
||||||
|
|
||||||
final appInfo = useState({});
|
final appInfo = useState({});
|
||||||
const titleFontSize = 12.0;
|
const titleFontSize = 12.0;
|
||||||
@@ -45,17 +49,10 @@ class AppBarServerInfo extends HookConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
if (showVersionWarning) ...[
|
||||||
padding: const EdgeInsets.all(8.0),
|
const Padding(padding: EdgeInsets.symmetric(horizontal: 8.0), child: ServerUpdateNotification()),
|
||||||
child: Text(
|
const Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: Divider(thickness: 1)),
|
||||||
serverInfoState.isVersionMismatch
|
],
|
||||||
? serverInfoState.versionMismatchErrorMessage
|
|
||||||
: "profile_drawer_client_server_up_to_date".tr(),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(fontSize: 11, color: context.primaryColor, fontWeight: FontWeight.w500),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: Divider(thickness: 1)),
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@@ -182,7 +179,7 @@ class AppBarServerInfo extends HookConsumerWidget {
|
|||||||
padding: const EdgeInsets.only(left: 10.0),
|
padding: const EdgeInsets.only(left: 10.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
if (serverInfoState.isNewReleaseAvailable)
|
if (serverInfoState.versionStatus == VersionStatus.serverOutOfDate)
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.only(right: 5.0),
|
padding: EdgeInsets.only(right: 5.0),
|
||||||
child: Icon(Icons.info, color: Color.fromARGB(255, 243, 188, 106), size: 12),
|
child: Icon(Icons.info, color: Color.fromARGB(255, 243, 188, 106), size: 12),
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/models/server_info/server_info.model.dart';
|
||||||
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
|
class ServerUpdateNotification extends HookConsumerWidget {
|
||||||
|
const ServerUpdateNotification({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final serverInfoState = ref.watch(serverInfoProvider);
|
||||||
|
|
||||||
|
Color errorColor = const Color.fromARGB(85, 253, 97, 83);
|
||||||
|
Color infoColor = context.isDarkTheme ? context.primaryColor.withAlpha(55) : context.primaryColor.withAlpha(25);
|
||||||
|
void openUpdateLink() {
|
||||||
|
String url;
|
||||||
|
if (serverInfoState.versionStatus == VersionStatus.serverOutOfDate) {
|
||||||
|
url = kImmichLatestRelease;
|
||||||
|
} else {
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
url = kImmichAppStoreLink;
|
||||||
|
} else if (Platform.isAndroid) {
|
||||||
|
url = kImmichPlayStoreLink;
|
||||||
|
} else {
|
||||||
|
// Fallback to latest release for other/unknown platforms
|
||||||
|
url = kImmichLatestRelease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launchUrlString(url, mode: LaunchMode.externalApplication);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: serverInfoState.versionStatus == VersionStatus.error ? errorColor : infoColor,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
border: Border.all(
|
||||||
|
color: serverInfoState.versionStatus == VersionStatus.error
|
||||||
|
? errorColor
|
||||||
|
: context.primaryColor.withAlpha(50),
|
||||||
|
width: 0.75,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
serverInfoState.versionStatus.message,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
maxLines: 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: context.textTheme.labelLarge,
|
||||||
|
),
|
||||||
|
if (serverInfoState.versionStatus == VersionStatus.serverOutOfDate ||
|
||||||
|
serverInfoState.versionStatus == VersionStatus.clientOutOfDate) ...[
|
||||||
|
const Spacer(),
|
||||||
|
TextButton(
|
||||||
|
onPressed: openUpdateLink,
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
minimumSize: const Size(0, 0),
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
),
|
||||||
|
child: serverInfoState.versionStatus == VersionStatus.clientOutOfDate
|
||||||
|
? Text("action_common_update".tr(context: context))
|
||||||
|
: Text("view".tr()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ import 'package:flutter_svg/svg.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/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
||||||
import 'package:immich_mobile/models/server_info/server_info.model.dart';
|
|
||||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
@@ -28,8 +27,8 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final BackUpState backupState = ref.watch(backupProvider);
|
final BackUpState backupState = ref.watch(backupProvider);
|
||||||
final bool isEnableAutoBackup = backupState.backgroundBackup || backupState.autoBackup;
|
final bool isEnableAutoBackup = backupState.backgroundBackup || backupState.autoBackup;
|
||||||
final ServerInfo serverInfoState = ref.watch(serverInfoProvider);
|
|
||||||
final user = ref.watch(currentUserProvider);
|
final user = ref.watch(currentUserProvider);
|
||||||
|
final bool versionWarningPresent = ref.watch(versionWarningPresentProvider(user));
|
||||||
final isDarkTheme = context.isDarkTheme;
|
final isDarkTheme = context.isDarkTheme;
|
||||||
const widgetSize = 30.0;
|
const widgetSize = 30.0;
|
||||||
final isCasting = ref.watch(castProvider.select((c) => c.isCasting));
|
final isCasting = ref.watch(castProvider.select((c) => c.isCasting));
|
||||||
@@ -46,8 +45,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||||||
),
|
),
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
alignment: Alignment.bottomRight,
|
alignment: Alignment.bottomRight,
|
||||||
isLabelVisible:
|
isLabelVisible: versionWarningPresent,
|
||||||
serverInfoState.isVersionMismatch || ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable),
|
|
||||||
offset: const Offset(-2, -12),
|
offset: const Offset(-2, -12),
|
||||||
child: user == null
|
child: user == null
|
||||||
? const Icon(Icons.face_outlined, size: widgetSize)
|
? const Icon(Icons.face_outlined, size: widgetSize)
|
||||||
|
|||||||
@@ -118,8 +118,10 @@ class _ProfileIndicator extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final ServerInfo serverInfoState = ref.watch(serverInfoProvider);
|
|
||||||
final user = ref.watch(currentUserProvider);
|
final user = ref.watch(currentUserProvider);
|
||||||
|
final bool versionWarningPresent = ref.watch(versionWarningPresentProvider(user));
|
||||||
|
final serverInfoState = ref.watch(serverInfoProvider);
|
||||||
|
|
||||||
const widgetSize = 30.0;
|
const widgetSize = 30.0;
|
||||||
|
|
||||||
void toggleReadonlyMode() {
|
void toggleReadonlyMode() {
|
||||||
@@ -143,13 +145,21 @@ class _ProfileIndicator extends ConsumerWidget {
|
|||||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
child: Badge(
|
child: Badge(
|
||||||
label: Container(
|
label: Container(
|
||||||
decoration: BoxDecoration(color: Colors.black, borderRadius: BorderRadius.circular(widgetSize / 2)),
|
decoration: BoxDecoration(
|
||||||
child: const Icon(Icons.info, color: Color.fromARGB(255, 243, 188, 106), size: widgetSize / 2),
|
color: context.isDarkTheme ? Colors.black : Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(widgetSize / 2),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.info,
|
||||||
|
color: serverInfoState.versionStatus == VersionStatus.error
|
||||||
|
? context.colorScheme.error
|
||||||
|
: context.primaryColor,
|
||||||
|
size: widgetSize / 2,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
alignment: Alignment.bottomRight,
|
alignment: Alignment.bottomRight,
|
||||||
isLabelVisible:
|
isLabelVisible: versionWarningPresent,
|
||||||
serverInfoState.isVersionMismatch || ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable),
|
|
||||||
offset: const Offset(-2, -12),
|
offset: const Offset(-2, -12),
|
||||||
child: user == null
|
child: user == null
|
||||||
? const Icon(Icons.face_outlined, size: widgetSize)
|
? const Icon(Icons.face_outlined, size: widgetSize)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import 'package:immich_mobile/services/app_settings.service.dart';
|
|||||||
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||||
import 'package:immich_mobile/utils/http_ssl_options.dart';
|
import 'package:immich_mobile/utils/http_ssl_options.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/beta_timeline_list_tile.dart';
|
import 'package:immich_mobile/widgets/settings/beta_timeline_list_tile.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/custom_proxy_headers_settings/custome_proxy_headers_settings.dart';
|
import 'package:immich_mobile/widgets/settings/custom_proxy_headers_settings/custom_proxy_headers_settings.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/local_storage_settings.dart';
|
import 'package:immich_mobile/widgets/settings/local_storage_settings.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
|
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
|
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
|
||||||
@@ -100,7 +100,7 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||||||
subtitle: "advanced_settings_self_signed_ssl_subtitle".tr(),
|
subtitle: "advanced_settings_self_signed_ssl_subtitle".tr(),
|
||||||
onChanged: HttpSSLOptions.applyFromSettings,
|
onChanged: HttpSSLOptions.applyFromSettings,
|
||||||
),
|
),
|
||||||
const CustomeProxyHeaderSettings(),
|
const CustomProxyHeaderSettings(),
|
||||||
SslClientCertSettings(isLoggedIn: ref.read(currentUserProvider) != null),
|
SslClientCertSettings(isLoggedIn: ref.read(currentUserProvider) != null),
|
||||||
if (!Store.isBetaTimelineEnabled)
|
if (!Store.isBetaTimelineEnabled)
|
||||||
SettingsSwitchListTile(
|
SettingsSwitchListTile(
|
||||||
|
|||||||
@@ -14,11 +14,18 @@ class VideoViewerSettings extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final useLoopVideo = useAppSettingsState(AppSettingsEnum.loopVideo);
|
final useLoopVideo = useAppSettingsState(AppSettingsEnum.loopVideo);
|
||||||
final useOriginalVideo = useAppSettingsState(AppSettingsEnum.loadOriginalVideo);
|
final useOriginalVideo = useAppSettingsState(AppSettingsEnum.loadOriginalVideo);
|
||||||
|
final useAutoPlayVideo = useAppSettingsState(AppSettingsEnum.autoPlayVideo);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SettingsSubTitle(title: "videos".tr()),
|
SettingsSubTitle(title: "videos".tr()),
|
||||||
|
SettingsSwitchListTile(
|
||||||
|
valueNotifier: useAutoPlayVideo,
|
||||||
|
title: "setting_video_viewer_auto_play_title".tr(),
|
||||||
|
subtitle: "setting_video_viewer_auto_play_subtitle".tr(),
|
||||||
|
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
|
||||||
|
),
|
||||||
SettingsSwitchListTile(
|
SettingsSwitchListTile(
|
||||||
valueNotifier: useLoopVideo,
|
valueNotifier: useLoopVideo,
|
||||||
title: "setting_video_viewer_looping_title".tr(),
|
title: "setting_video_viewer_looping_title".tr(),
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
|
import 'package:immich_mobile/generated/intl_keys.g.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
|
||||||
class CustomeProxyHeaderSettings extends StatelessWidget {
|
class CustomProxyHeaderSettings extends StatelessWidget {
|
||||||
const CustomeProxyHeaderSettings({super.key});
|
const CustomProxyHeaderSettings({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -14,11 +15,11 @@ class CustomeProxyHeaderSettings extends StatelessWidget {
|
|||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
dense: true,
|
dense: true,
|
||||||
title: Text(
|
title: Text(
|
||||||
"headers_settings_tile_title".tr(),
|
IntlKeys.advanced_settings_proxy_headers_title.tr(),
|
||||||
style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500),
|
style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500),
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
"headers_settings_tile_subtitle".tr(),
|
IntlKeys.advanced_settings_proxy_headers_subtitle.tr(),
|
||||||
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||||
),
|
),
|
||||||
onTap: () => context.pushRoute(const HeaderSettingsRoute()),
|
onTap: () => context.pushRoute(const HeaderSettingsRoute()),
|
||||||
1
mobile/openapi/README.md
generated
1
mobile/openapi/README.md
generated
@@ -359,6 +359,7 @@ Class | Method | HTTP request | Description
|
|||||||
- [CheckExistingAssetsDto](doc//CheckExistingAssetsDto.md)
|
- [CheckExistingAssetsDto](doc//CheckExistingAssetsDto.md)
|
||||||
- [CheckExistingAssetsResponseDto](doc//CheckExistingAssetsResponseDto.md)
|
- [CheckExistingAssetsResponseDto](doc//CheckExistingAssetsResponseDto.md)
|
||||||
- [Colorspace](doc//Colorspace.md)
|
- [Colorspace](doc//Colorspace.md)
|
||||||
|
- [ContributorCountResponseDto](doc//ContributorCountResponseDto.md)
|
||||||
- [CreateAlbumDto](doc//CreateAlbumDto.md)
|
- [CreateAlbumDto](doc//CreateAlbumDto.md)
|
||||||
- [CreateLibraryDto](doc//CreateLibraryDto.md)
|
- [CreateLibraryDto](doc//CreateLibraryDto.md)
|
||||||
- [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md)
|
- [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md)
|
||||||
|
|||||||
1
mobile/openapi/lib/api.dart
generated
1
mobile/openapi/lib/api.dart
generated
@@ -130,6 +130,7 @@ part 'model/change_password_dto.dart';
|
|||||||
part 'model/check_existing_assets_dto.dart';
|
part 'model/check_existing_assets_dto.dart';
|
||||||
part 'model/check_existing_assets_response_dto.dart';
|
part 'model/check_existing_assets_response_dto.dart';
|
||||||
part 'model/colorspace.dart';
|
part 'model/colorspace.dart';
|
||||||
|
part 'model/contributor_count_response_dto.dart';
|
||||||
part 'model/create_album_dto.dart';
|
part 'model/create_album_dto.dart';
|
||||||
part 'model/create_library_dto.dart';
|
part 'model/create_library_dto.dart';
|
||||||
part 'model/create_profile_image_response_dto.dart';
|
part 'model/create_profile_image_response_dto.dart';
|
||||||
|
|||||||
2
mobile/openapi/lib/api_client.dart
generated
2
mobile/openapi/lib/api_client.dart
generated
@@ -314,6 +314,8 @@ class ApiClient {
|
|||||||
return CheckExistingAssetsResponseDto.fromJson(value);
|
return CheckExistingAssetsResponseDto.fromJson(value);
|
||||||
case 'Colorspace':
|
case 'Colorspace':
|
||||||
return ColorspaceTypeTransformer().decode(value);
|
return ColorspaceTypeTransformer().decode(value);
|
||||||
|
case 'ContributorCountResponseDto':
|
||||||
|
return ContributorCountResponseDto.fromJson(value);
|
||||||
case 'CreateAlbumDto':
|
case 'CreateAlbumDto':
|
||||||
return CreateAlbumDto.fromJson(value);
|
return CreateAlbumDto.fromJson(value);
|
||||||
case 'CreateLibraryDto':
|
case 'CreateLibraryDto':
|
||||||
|
|||||||
9
mobile/openapi/lib/model/album_response_dto.dart
generated
9
mobile/openapi/lib/model/album_response_dto.dart
generated
@@ -18,6 +18,7 @@ class AlbumResponseDto {
|
|||||||
this.albumUsers = const [],
|
this.albumUsers = const [],
|
||||||
required this.assetCount,
|
required this.assetCount,
|
||||||
this.assets = const [],
|
this.assets = const [],
|
||||||
|
this.contributorCounts = const [],
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
required this.description,
|
required this.description,
|
||||||
this.endDate,
|
this.endDate,
|
||||||
@@ -43,6 +44,8 @@ class AlbumResponseDto {
|
|||||||
|
|
||||||
List<AssetResponseDto> assets;
|
List<AssetResponseDto> assets;
|
||||||
|
|
||||||
|
List<ContributorCountResponseDto> contributorCounts;
|
||||||
|
|
||||||
DateTime createdAt;
|
DateTime createdAt;
|
||||||
|
|
||||||
String description;
|
String description;
|
||||||
@@ -100,6 +103,7 @@ class AlbumResponseDto {
|
|||||||
_deepEquality.equals(other.albumUsers, albumUsers) &&
|
_deepEquality.equals(other.albumUsers, albumUsers) &&
|
||||||
other.assetCount == assetCount &&
|
other.assetCount == assetCount &&
|
||||||
_deepEquality.equals(other.assets, assets) &&
|
_deepEquality.equals(other.assets, assets) &&
|
||||||
|
_deepEquality.equals(other.contributorCounts, contributorCounts) &&
|
||||||
other.createdAt == createdAt &&
|
other.createdAt == createdAt &&
|
||||||
other.description == description &&
|
other.description == description &&
|
||||||
other.endDate == endDate &&
|
other.endDate == endDate &&
|
||||||
@@ -122,6 +126,7 @@ class AlbumResponseDto {
|
|||||||
(albumUsers.hashCode) +
|
(albumUsers.hashCode) +
|
||||||
(assetCount.hashCode) +
|
(assetCount.hashCode) +
|
||||||
(assets.hashCode) +
|
(assets.hashCode) +
|
||||||
|
(contributorCounts.hashCode) +
|
||||||
(createdAt.hashCode) +
|
(createdAt.hashCode) +
|
||||||
(description.hashCode) +
|
(description.hashCode) +
|
||||||
(endDate == null ? 0 : endDate!.hashCode) +
|
(endDate == null ? 0 : endDate!.hashCode) +
|
||||||
@@ -137,7 +142,7 @@ class AlbumResponseDto {
|
|||||||
(updatedAt.hashCode);
|
(updatedAt.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, albumUsers=$albumUsers, assetCount=$assetCount, assets=$assets, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, owner=$owner, ownerId=$ownerId, shared=$shared, startDate=$startDate, updatedAt=$updatedAt]';
|
String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, albumUsers=$albumUsers, assetCount=$assetCount, assets=$assets, contributorCounts=$contributorCounts, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, owner=$owner, ownerId=$ownerId, shared=$shared, startDate=$startDate, updatedAt=$updatedAt]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@@ -150,6 +155,7 @@ class AlbumResponseDto {
|
|||||||
json[r'albumUsers'] = this.albumUsers;
|
json[r'albumUsers'] = this.albumUsers;
|
||||||
json[r'assetCount'] = this.assetCount;
|
json[r'assetCount'] = this.assetCount;
|
||||||
json[r'assets'] = this.assets;
|
json[r'assets'] = this.assets;
|
||||||
|
json[r'contributorCounts'] = this.contributorCounts;
|
||||||
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
||||||
json[r'description'] = this.description;
|
json[r'description'] = this.description;
|
||||||
if (this.endDate != null) {
|
if (this.endDate != null) {
|
||||||
@@ -196,6 +202,7 @@ class AlbumResponseDto {
|
|||||||
albumUsers: AlbumUserResponseDto.listFromJson(json[r'albumUsers']),
|
albumUsers: AlbumUserResponseDto.listFromJson(json[r'albumUsers']),
|
||||||
assetCount: mapValueOfType<int>(json, r'assetCount')!,
|
assetCount: mapValueOfType<int>(json, r'assetCount')!,
|
||||||
assets: AssetResponseDto.listFromJson(json[r'assets']),
|
assets: AssetResponseDto.listFromJson(json[r'assets']),
|
||||||
|
contributorCounts: ContributorCountResponseDto.listFromJson(json[r'contributorCounts']),
|
||||||
createdAt: mapDateTime(json, r'createdAt', r'')!,
|
createdAt: mapDateTime(json, r'createdAt', r'')!,
|
||||||
description: mapValueOfType<String>(json, r'description')!,
|
description: mapValueOfType<String>(json, r'description')!,
|
||||||
endDate: mapDateTime(json, r'endDate', r''),
|
endDate: mapDateTime(json, r'endDate', r''),
|
||||||
|
|||||||
107
mobile/openapi/lib/model/contributor_count_response_dto.dart
generated
Normal file
107
mobile/openapi/lib/model/contributor_count_response_dto.dart
generated
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class ContributorCountResponseDto {
|
||||||
|
/// Returns a new [ContributorCountResponseDto] instance.
|
||||||
|
ContributorCountResponseDto({
|
||||||
|
required this.assetCount,
|
||||||
|
required this.userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
int assetCount;
|
||||||
|
|
||||||
|
String userId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is ContributorCountResponseDto &&
|
||||||
|
other.assetCount == assetCount &&
|
||||||
|
other.userId == userId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(assetCount.hashCode) +
|
||||||
|
(userId.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'ContributorCountResponseDto[assetCount=$assetCount, userId=$userId]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'assetCount'] = this.assetCount;
|
||||||
|
json[r'userId'] = this.userId;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [ContributorCountResponseDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static ContributorCountResponseDto? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "ContributorCountResponseDto");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return ContributorCountResponseDto(
|
||||||
|
assetCount: mapValueOfType<int>(json, r'assetCount')!,
|
||||||
|
userId: mapValueOfType<String>(json, r'userId')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<ContributorCountResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <ContributorCountResponseDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = ContributorCountResponseDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, ContributorCountResponseDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, ContributorCountResponseDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = ContributorCountResponseDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of ContributorCountResponseDto-objects as value to a dart map
|
||||||
|
static Map<String, List<ContributorCountResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<ContributorCountResponseDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = ContributorCountResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'assetCount',
|
||||||
|
'userId',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -17,6 +17,7 @@ class SystemConfigSmtpTransportDto {
|
|||||||
required this.ignoreCert,
|
required this.ignoreCert,
|
||||||
required this.password,
|
required this.password,
|
||||||
required this.port,
|
required this.port,
|
||||||
|
required this.secure,
|
||||||
required this.username,
|
required this.username,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -30,6 +31,8 @@ class SystemConfigSmtpTransportDto {
|
|||||||
/// Maximum value: 65535
|
/// Maximum value: 65535
|
||||||
num port;
|
num port;
|
||||||
|
|
||||||
|
bool secure;
|
||||||
|
|
||||||
String username;
|
String username;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -38,6 +41,7 @@ class SystemConfigSmtpTransportDto {
|
|||||||
other.ignoreCert == ignoreCert &&
|
other.ignoreCert == ignoreCert &&
|
||||||
other.password == password &&
|
other.password == password &&
|
||||||
other.port == port &&
|
other.port == port &&
|
||||||
|
other.secure == secure &&
|
||||||
other.username == username;
|
other.username == username;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -47,10 +51,11 @@ class SystemConfigSmtpTransportDto {
|
|||||||
(ignoreCert.hashCode) +
|
(ignoreCert.hashCode) +
|
||||||
(password.hashCode) +
|
(password.hashCode) +
|
||||||
(port.hashCode) +
|
(port.hashCode) +
|
||||||
|
(secure.hashCode) +
|
||||||
(username.hashCode);
|
(username.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'SystemConfigSmtpTransportDto[host=$host, ignoreCert=$ignoreCert, password=$password, port=$port, username=$username]';
|
String toString() => 'SystemConfigSmtpTransportDto[host=$host, ignoreCert=$ignoreCert, password=$password, port=$port, secure=$secure, username=$username]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@@ -58,6 +63,7 @@ class SystemConfigSmtpTransportDto {
|
|||||||
json[r'ignoreCert'] = this.ignoreCert;
|
json[r'ignoreCert'] = this.ignoreCert;
|
||||||
json[r'password'] = this.password;
|
json[r'password'] = this.password;
|
||||||
json[r'port'] = this.port;
|
json[r'port'] = this.port;
|
||||||
|
json[r'secure'] = this.secure;
|
||||||
json[r'username'] = this.username;
|
json[r'username'] = this.username;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
@@ -75,6 +81,7 @@ class SystemConfigSmtpTransportDto {
|
|||||||
ignoreCert: mapValueOfType<bool>(json, r'ignoreCert')!,
|
ignoreCert: mapValueOfType<bool>(json, r'ignoreCert')!,
|
||||||
password: mapValueOfType<String>(json, r'password')!,
|
password: mapValueOfType<String>(json, r'password')!,
|
||||||
port: num.parse('${json[r'port']}'),
|
port: num.parse('${json[r'port']}'),
|
||||||
|
secure: mapValueOfType<bool>(json, r'secure')!,
|
||||||
username: mapValueOfType<String>(json, r'username')!,
|
username: mapValueOfType<String>(json, r'username')!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -127,6 +134,7 @@ class SystemConfigSmtpTransportDto {
|
|||||||
'ignoreCert',
|
'ignoreCert',
|
||||||
'password',
|
'password',
|
||||||
'port',
|
'port',
|
||||||
|
'secure',
|
||||||
'username',
|
'username',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -481,14 +481,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.7.0"
|
||||||
easy_image_viewer:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: easy_image_viewer
|
|
||||||
sha256: fb6cb123c3605552cc91150dcdb50ca977001dcddfb71d20caa0c5edc9a80947
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.5.1"
|
|
||||||
easy_localization:
|
easy_localization:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1413,14 +1405,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.6.4"
|
version: "3.6.4"
|
||||||
photo_manager_image_provider:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: photo_manager_image_provider
|
|
||||||
sha256: b6015b67b32f345f57cf32c126f871bced2501236c405aafaefa885f7c821e4f
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.2.0"
|
|
||||||
pigeon:
|
pigeon:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -2180,4 +2164,4 @@ packages:
|
|||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.8.0 <4.0.0"
|
dart: ">=3.8.0 <4.0.0"
|
||||||
flutter: ">=3.35.4"
|
flutter: ">=3.35.6"
|
||||||
|
|||||||
@@ -6,12 +6,9 @@ version: 2.1.0+3022
|
|||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.8.0 <4.0.0'
|
sdk: '>=3.8.0 <4.0.0'
|
||||||
flutter: 3.35.4
|
flutter: 3.35.6
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
|
||||||
sdk: flutter
|
|
||||||
|
|
||||||
async: ^2.11.0
|
async: ^2.11.0
|
||||||
auto_route: ^9.2.0
|
auto_route: ^9.2.0
|
||||||
background_downloader: ^9.2.5
|
background_downloader: ^9.2.5
|
||||||
@@ -23,10 +20,15 @@ dependencies:
|
|||||||
crop_image: ^1.0.16
|
crop_image: ^1.0.16
|
||||||
crypto: ^3.0.6
|
crypto: ^3.0.6
|
||||||
device_info_plus: ^12.0.0
|
device_info_plus: ^12.0.0
|
||||||
|
# DB
|
||||||
|
drift: ^2.23.1
|
||||||
|
drift_flutter: ^0.2.4
|
||||||
dynamic_color: ^1.7.0
|
dynamic_color: ^1.7.0
|
||||||
easy_image_viewer: ^1.5.1
|
|
||||||
easy_localization: ^3.0.7+1
|
easy_localization: ^3.0.7+1
|
||||||
|
ffi: ^2.1.4
|
||||||
file_picker: ^8.0.0+1
|
file_picker: ^8.0.0+1
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
flutter_cache_manager: ^3.4.1
|
flutter_cache_manager: ^3.4.1
|
||||||
flutter_displaymode: ^0.6.0
|
flutter_displaymode: ^0.6.0
|
||||||
flutter_hooks: ^0.21.2
|
flutter_hooks: ^0.21.2
|
||||||
@@ -37,26 +39,39 @@ dependencies:
|
|||||||
flutter_web_auth_2: ^5.0.0-alpha.0
|
flutter_web_auth_2: ^5.0.0-alpha.0
|
||||||
fluttertoast: ^8.2.12
|
fluttertoast: ^8.2.12
|
||||||
geolocator: ^14.0.0
|
geolocator: ^14.0.0
|
||||||
hooks_riverpod: ^2.6.1
|
|
||||||
home_widget: ^0.8.0
|
home_widget: ^0.8.0
|
||||||
|
hooks_riverpod: ^2.6.1
|
||||||
http: ^1.3.0
|
http: ^1.3.0
|
||||||
image_picker: ^1.1.2
|
image_picker: ^1.1.2
|
||||||
intl: ^0.20.0
|
intl: ^0.20.0
|
||||||
|
isar:
|
||||||
|
git:
|
||||||
|
url: https://github.com/immich-app/isar
|
||||||
|
ref: 'bb1dca40fe87a001122e5d43bc6254718cb49f3a'
|
||||||
|
path: packages/isar/
|
||||||
|
isar_community_flutter_libs: 3.3.0-dev.3
|
||||||
local_auth: ^2.3.0
|
local_auth: ^2.3.0
|
||||||
logging: ^1.3.0
|
logging: ^1.3.0
|
||||||
maplibre_gl: ^0.22.0
|
maplibre_gl: ^0.22.0
|
||||||
|
|
||||||
|
native_video_player:
|
||||||
|
git:
|
||||||
|
url: https://github.com/immich-app/native_video_player
|
||||||
|
ref: '893894b'
|
||||||
network_info_plus: ^6.1.3
|
network_info_plus: ^6.1.3
|
||||||
octo_image: ^2.1.0
|
octo_image: ^2.1.0
|
||||||
|
openapi:
|
||||||
|
path: openapi
|
||||||
package_info_plus: ^8.3.0
|
package_info_plus: ^8.3.0
|
||||||
path: ^1.9.1
|
path: ^1.9.1
|
||||||
path_provider: ^2.1.5
|
path_provider: ^2.1.5
|
||||||
path_provider_foundation: ^2.4.1
|
path_provider_foundation: ^2.4.1
|
||||||
permission_handler: ^11.4.0
|
permission_handler: ^11.4.0
|
||||||
photo_manager: ^3.6.4
|
photo_manager: ^3.6.4
|
||||||
photo_manager_image_provider: ^2.2.0
|
|
||||||
pinput: ^5.0.1
|
pinput: ^5.0.1
|
||||||
punycode: ^1.0.0
|
punycode: ^1.0.0
|
||||||
riverpod_annotation: ^2.6.1
|
riverpod_annotation: ^2.6.1
|
||||||
|
scroll_date_picker: ^3.8.0
|
||||||
scrollable_positioned_list: ^0.3.8
|
scrollable_positioned_list: ^0.3.8
|
||||||
share_handler: ^0.0.22
|
share_handler: ^0.0.22
|
||||||
share_plus: ^10.1.4
|
share_plus: ^10.1.4
|
||||||
@@ -69,52 +84,34 @@ dependencies:
|
|||||||
uuid: ^4.5.1
|
uuid: ^4.5.1
|
||||||
wakelock_plus: ^1.2.10
|
wakelock_plus: ^1.2.10
|
||||||
worker_manager: ^7.2.3
|
worker_manager: ^7.2.3
|
||||||
scroll_date_picker: ^3.8.0
|
|
||||||
ffi: ^2.1.4
|
|
||||||
|
|
||||||
native_video_player:
|
|
||||||
git:
|
|
||||||
url: https://github.com/immich-app/native_video_player
|
|
||||||
ref: '893894b'
|
|
||||||
openapi:
|
|
||||||
path: openapi
|
|
||||||
isar:
|
|
||||||
git:
|
|
||||||
url: https://github.com/immich-app/isar
|
|
||||||
ref: 'bb1dca40fe87a001122e5d43bc6254718cb49f3a'
|
|
||||||
path: packages/isar/
|
|
||||||
isar_community_flutter_libs: 3.3.0-dev.3
|
|
||||||
# DB
|
|
||||||
drift: ^2.23.1
|
|
||||||
drift_flutter: ^0.2.4
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
auto_route_generator: ^9.0.0
|
||||||
|
build_runner: ^2.4.8
|
||||||
|
custom_lint: ^0.7.5
|
||||||
|
# Drift generator
|
||||||
|
drift_dev: ^2.23.1
|
||||||
|
fake_async: ^1.3.1
|
||||||
|
file: ^7.0.1 # for MemoryFileSystem
|
||||||
|
flutter_launcher_icons: ^0.14.3
|
||||||
|
flutter_lints: ^5.0.0
|
||||||
|
flutter_native_splash: ^2.4.5
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^5.0.0
|
immich_mobile_immich_lint:
|
||||||
build_runner: ^2.4.8
|
path: './immich_lint'
|
||||||
auto_route_generator: ^9.0.0
|
integration_test:
|
||||||
flutter_launcher_icons: ^0.14.3
|
sdk: flutter
|
||||||
flutter_native_splash: ^2.4.5
|
|
||||||
isar_generator:
|
isar_generator:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/immich-app/isar
|
url: https://github.com/immich-app/isar
|
||||||
ref: 'bb1dca40fe87a001122e5d43bc6254718cb49f3a'
|
ref: 'bb1dca40fe87a001122e5d43bc6254718cb49f3a'
|
||||||
path: packages/isar_generator/
|
path: packages/isar_generator/
|
||||||
integration_test:
|
|
||||||
sdk: flutter
|
|
||||||
custom_lint: ^0.7.5
|
|
||||||
riverpod_lint: ^2.6.1
|
|
||||||
riverpod_generator: ^2.6.1
|
|
||||||
mocktail: ^1.0.4
|
mocktail: ^1.0.4
|
||||||
immich_mobile_immich_lint:
|
|
||||||
path: './immich_lint'
|
|
||||||
fake_async: ^1.3.1
|
|
||||||
file: ^7.0.1 # for MemoryFileSystem
|
|
||||||
# Drift generator
|
|
||||||
drift_dev: ^2.23.1
|
|
||||||
# Type safe platform code
|
# Type safe platform code
|
||||||
pigeon: ^26.0.0
|
pigeon: ^26.0.0
|
||||||
|
riverpod_generator: ^2.6.1
|
||||||
|
riverpod_lint: ^2.6.1
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ function dart {
|
|||||||
patch --no-backup-if-mismatch -u api.mustache <api.mustache.patch
|
patch --no-backup-if-mismatch -u api.mustache <api.mustache.patch
|
||||||
|
|
||||||
cd ../../
|
cd ../../
|
||||||
pnpx @openapitools/openapi-generator-cli generate -g dart -i ./immich-openapi-specs.json -o ../mobile/openapi -t ./templates/mobile
|
pnpm dlx @openapitools/openapi-generator-cli generate -g dart -i ./immich-openapi-specs.json -o ../mobile/openapi -t ./templates/mobile
|
||||||
|
|
||||||
# Post generate patches
|
# Post generate patches
|
||||||
patch --no-backup-if-mismatch -u ../mobile/openapi/lib/api_client.dart <./patch/api_client.dart.patch
|
patch --no-backup-if-mismatch -u ../mobile/openapi/lib/api_client.dart <./patch/api_client.dart.patch
|
||||||
@@ -27,7 +27,7 @@ function dart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function typescript {
|
function typescript {
|
||||||
pnpx oazapfts --optimistic --argumentStyle=object --useEnumType immich-openapi-specs.json typescript-sdk/src/fetch-client.ts
|
pnpm dlx oazapfts --optimistic --argumentStyle=object --useEnumType immich-openapi-specs.json typescript-sdk/src/fetch-client.ts
|
||||||
pnpm --filter @immich/sdk install --frozen-lockfile
|
pnpm --filter @immich/sdk install --frozen-lockfile
|
||||||
pnpm --filter @immich/sdk build
|
pnpm --filter @immich/sdk build
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10096,6 +10096,12 @@
|
|||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
|
"contributorCounts": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ContributorCountResponseDto"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
"createdAt": {
|
"createdAt": {
|
||||||
"format": "date-time",
|
"format": "date-time",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -11471,6 +11477,21 @@
|
|||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"ContributorCountResponseDto": {
|
||||||
|
"properties": {
|
||||||
|
"assetCount": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"assetCount",
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"CreateAlbumDto": {
|
"CreateAlbumDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"albumName": {
|
"albumName": {
|
||||||
@@ -16705,6 +16726,9 @@
|
|||||||
"minimum": 0,
|
"minimum": 0,
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
|
"secure": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"username": {
|
"username": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@@ -16714,6 +16738,7 @@
|
|||||||
"ignoreCert",
|
"ignoreCert",
|
||||||
"password",
|
"password",
|
||||||
"port",
|
"port",
|
||||||
|
"secure",
|
||||||
"username"
|
"username"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.18.8",
|
"@types/node": "^22.18.10",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ export type SystemConfigSmtpTransportDto = {
|
|||||||
ignoreCert: boolean;
|
ignoreCert: boolean;
|
||||||
password: string;
|
password: string;
|
||||||
port: number;
|
port: number;
|
||||||
|
secure: boolean;
|
||||||
username: string;
|
username: string;
|
||||||
};
|
};
|
||||||
export type SystemConfigSmtpDto = {
|
export type SystemConfigSmtpDto = {
|
||||||
@@ -356,12 +357,17 @@ export type AssetResponseDto = {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
visibility: AssetVisibility;
|
visibility: AssetVisibility;
|
||||||
};
|
};
|
||||||
|
export type ContributorCountResponseDto = {
|
||||||
|
assetCount: number;
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
export type AlbumResponseDto = {
|
export type AlbumResponseDto = {
|
||||||
albumName: string;
|
albumName: string;
|
||||||
albumThumbnailAssetId: string | null;
|
albumThumbnailAssetId: string | null;
|
||||||
albumUsers: AlbumUserResponseDto[];
|
albumUsers: AlbumUserResponseDto[];
|
||||||
assetCount: number;
|
assetCount: number;
|
||||||
assets: AssetResponseDto[];
|
assets: AssetResponseDto[];
|
||||||
|
contributorCounts?: ContributorCountResponseDto[];
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
description: string;
|
description: string;
|
||||||
endDate?: string;
|
endDate?: string;
|
||||||
|
|||||||
@@ -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.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34",
|
"packageManager": "pnpm@10.18.3+sha512.bbd16e6d7286fd7e01f6b3c0b3c932cda2965c06a908328f74663f10a9aea51f1129eea615134bf992831b009eabe167ecb7008b597f40ff9bc75946aadfb08d",
|
||||||
"engines": {
|
"engines": {
|
||||||
"pnpm": ">=10.0.0"
|
"pnpm": ">=10.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
2
plugins/.gitignore
vendored
Normal file
2
plugins/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
26
plugins/LICENSE
Normal file
26
plugins/LICENSE
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
Copyright 2024, The Extism Authors.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
12
plugins/esbuild.js
Normal file
12
plugins/esbuild.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const esbuild = require('esbuild');
|
||||||
|
|
||||||
|
esbuild
|
||||||
|
.build({
|
||||||
|
entryPoints: ['src/index.ts'],
|
||||||
|
outdir: 'dist',
|
||||||
|
bundle: true,
|
||||||
|
sourcemap: true,
|
||||||
|
minify: false, // might want to use true for production build
|
||||||
|
format: 'cjs', // needs to be CJS for now
|
||||||
|
target: ['es2020'] // don't go over es2020 because quickjs doesn't support it
|
||||||
|
})
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user