Merge branch 'immich-app:main' into feature/photo-slideshow-animate-zoom

This commit is contained in:
hraabis 2026-08-06 15:06:12 -06:00 committed by GitHub
commit 0640f8393f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
557 changed files with 10704 additions and 19412 deletions

6
.gitattributes vendored
View file

@ -1,15 +1,9 @@
mobile/lib/**/*.g.dart -diff -merge
mobile/lib/**/*.g.dart linguist-generated=true
mobile/android/**/*.g.kt -diff -merge
mobile/android/**/*.g.kt linguist-generated=true
mobile/ios/**/*.g.swift -diff -merge
mobile/ios/**/*.g.swift linguist-generated=true
mobile/lib/**/*.drift.dart -diff -merge
mobile/lib/**/*.drift.dart linguist-generated=true
mobile/drift_schemas/main/drift_schema_*.json -diff -merge
mobile/drift_schemas/main/drift_schema_*.json linguist-generated=true

View file

@ -94,7 +94,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
working_directory: ./mobile
@ -216,7 +216,7 @@ jobs:
persist-credentials: false
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
working_directory: ./mobile

View file

@ -25,7 +25,7 @@ jobs:
persist-credentials: false
- name: Check for breaking API changes
uses: oasdiff/oasdiff-action/breaking@4bdf89062dad92caea71236f59704b04d3ce9682 # v0.1.8
uses: oasdiff/oasdiff-action/breaking@0ab8ad204b00d25acc5ae87106281433e288d0c1 # v0.1.10
with:
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
revision: open-api/immich-openapi-specs.json
@ -43,7 +43,7 @@ jobs:
persist-credentials: false
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ github.token }}
working_directory: ./mobile

View file

@ -44,7 +44,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
@ -83,7 +83,7 @@ jobs:
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Login to GitHub Container Registry
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
registry: ghcr.io

View file

@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -71,7 +71,7 @@ jobs:
# 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)
- name: Autobuild
uses: github/codeql-action/autobuild@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
uses: github/codeql-action/autobuild@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
# 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
@ -84,6 +84,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
with:
category: '/language:${{matrix.language}}'

View file

@ -61,7 +61,7 @@ jobs:
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@ -91,7 +91,7 @@ jobs:
suffix: ['']
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}

View file

@ -68,7 +68,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}

View file

@ -143,7 +143,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}

View file

@ -31,7 +31,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}

View file

@ -30,7 +30,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}

View file

@ -8,7 +8,6 @@ permissions: {}
jobs:
validate-release-label:
if: ${{ !startsWith(github.event.pull_request.head.ref, 'mergify/merge-queue/') }}
runs-on: ubuntu-latest
permissions:
issues: write

View file

@ -19,6 +19,6 @@ jobs:
permission-contents: read
permission-pull-requests: write
- uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
- uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0
with:
repo-token: ${{ steps.token.outputs.token }}

View file

@ -68,13 +68,13 @@ jobs:
ref: main
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
# TODO move to mise
- name: Install uv
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
- name: Bump version
env:

View file

@ -29,7 +29,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}

View file

@ -64,7 +64,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
working_directory: ./mobile

View file

@ -91,7 +91,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
@ -120,7 +120,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
@ -152,7 +152,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
@ -184,7 +184,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
@ -229,7 +229,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
@ -268,7 +268,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
@ -297,7 +297,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
@ -348,7 +348,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
@ -382,7 +382,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
@ -441,6 +441,7 @@ jobs:
if: ${{ !cancelled() }}
- name: Start Docker Compose
id: docker
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
if: ${{ !cancelled() }}
@ -448,13 +449,13 @@ jobs:
env:
VITEST_DISABLE_DOCKER_SETUP: true
run: pnpm test
if: ${{ !cancelled() }}
if: ${{ !cancelled() && steps.docker.outcome == 'success' }}
- name: Run e2e tests (maintenance)
env:
VITEST_DISABLE_DOCKER_SETUP: true
run: pnpm test:maintenance
if: ${{ !cancelled() }}
if: ${{ !cancelled() && steps.docker.outcome == 'success' }}
- name: Capture Docker logs
if: always()
@ -519,6 +520,7 @@ jobs:
if: ${{ !cancelled() }}
- name: Docker build
id: docker
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
if: ${{ !cancelled() }}
@ -526,7 +528,7 @@ jobs:
env:
PLAYWRIGHT_DISABLE_WEBSERVER: true
run: pnpm test:web
if: ${{ !cancelled() }}
if: ${{ !cancelled() && steps.docker.outcome == 'success' }}
- name: Archive e2e test (web) results
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
@ -539,7 +541,7 @@ jobs:
env:
PLAYWRIGHT_DISABLE_WEBSERVER: true
run: pnpm test:web:ui
if: ${{ !cancelled() }}
if: ${{ !cancelled() && steps.docker.outcome == 'success' }}
- name: Archive ui test (web) results
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
@ -552,7 +554,7 @@ jobs:
env:
PLAYWRIGHT_DISABLE_WEBSERVER: true
run: pnpm test:web:maintenance
if: ${{ !cancelled() }}
if: ${{ !cancelled() && steps.docker.outcome == 'success' }}
- name: Archive maintenance tests (web) results
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
@ -604,7 +606,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
working_directory: ./mobile
@ -642,7 +644,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
@ -674,7 +676,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
@ -727,7 +729,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}
@ -785,7 +787,7 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@4d97fb30d9edd3ff0d735f411f607a35204eea79 # use-mise-action-v3.2.0
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
with:
github_token: ${{ steps.token.outputs.token }}

View file

@ -1,2 +0,0 @@
merge_queue:
status_comments: outcomes

View file

@ -12,6 +12,8 @@
"editorconfig.editorconfig",
"foxundermoon.shell-format",
"timonwong.shellcheck",
"bluebrown.yamlfmt"
"bluebrown.yamlfmt",
// TODO TS 7 while it's still in preview
"typescriptteam.native-preview"
]
}

View file

@ -60,6 +60,8 @@
"**/dist/**": true,
"**/node_modules/**": true
},
// TODO remove once the ts7 extension is stable
"js/ts.experimental.useTsgo": true,
"js/ts.preferences.importModuleSpecifier": "non-relative",
"search.exclude": {
"**/.svelte-kit": true,

View file

@ -1,43 +1,43 @@
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
[[tools.opentofu]]
version = "1.11.6"
version = "1.12.5"
backend = "aqua:opentofu/opentofu"
[tools.opentofu."platforms.linux-arm64"]
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536382"
checksum = "sha256:e67e9da2b1ddf5050ebee62a584cb826eafe1dfd3827d7ec20899ac62791ed1a"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602545"
[tools.opentofu."platforms.linux-arm64-musl"]
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536382"
checksum = "sha256:e67e9da2b1ddf5050ebee62a584cb826eafe1dfd3827d7ec20899ac62791ed1a"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602545"
[tools.opentofu."platforms.linux-x64"]
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536401"
checksum = "sha256:a6894d45ae7a17ce83189cce8fe04b5a65f68cefceb62455b5a6a89fa53ab38f"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602624"
[tools.opentofu."platforms.linux-x64-musl"]
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536401"
checksum = "sha256:a6894d45ae7a17ce83189cce8fe04b5a65f68cefceb62455b5a6a89fa53ab38f"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602624"
[tools.opentofu."platforms.macos-arm64"]
checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536399"
checksum = "sha256:2ae38150a667f5c0bd57b318d18ad8091d08f93fcca40345f3d88998661de5a9"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_darwin_arm64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602544"
[tools.opentofu."platforms.macos-x64"]
checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536384"
checksum = "sha256:1012d8f3d4567bcbcd1f2c7d766feca39a30bced32fb8be47e1887fbbee2456d"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_darwin_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602621"
[tools.opentofu."platforms.windows-x64"]
checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536406"
checksum = "sha256:af11850b496f3720e0184084c56d8b43aa74ea92d2338978bf368d70c96473f1"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_windows_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602547"
[[tools.terragrunt]]
version = "1.0.3"

View file

@ -1,6 +1,6 @@
[tools]
terragrunt = "1.1.1"
opentofu = "1.12.4"
opentofu = "1.12.5"
[tasks."tg:fmt"]
run = "terragrunt hclfmt"

View file

@ -97,7 +97,7 @@ services:
command: ['./run.sh', '-disable-reporting']
ports:
- 3000:3000
image: grafana/grafana:12.4.5-ubuntu@sha256:00396460e499415c828b7c298f19287c8a0f95e72412ee37ac11723655c2d6b9
image: grafana/grafana:12.4.6-ubuntu@sha256:35c75489ff2e2e69c53977a180de491643d51fdb32108f9335317527978daed7
volumes:
- grafana-data:/var/lib/grafana

View file

@ -6,14 +6,18 @@ sidebar_position: 100
A config file can be provided as an alternative to the UI configuration.
:::note Interaction with the web UI
While the config file does not need to include all keys from the below example, specifying `IMMICH_CONFIG_FILE` will disable the ability to edit other properties from the Immich web UI.
:::
### Step 1 - Create a new config file
In JSON format, create a new config file (e.g. `immich.json`) and put it in a location mounted in the container that can be accessed by Immich.
In JSON format, create a new config file (e.g. `immich-config.json`) and put it in a location mounted in the container that can be accessed by Immich.
YAML-formatted config files are also supported.
The default configuration looks like this:
<details>
<summary>immich.json</summary>
<summary>immich-config.json</summary>
```json
{
@ -250,6 +254,10 @@ So you can just grab it from there, paste it into a file and you're pretty much
### Step 2 - Specify the file location
:::note
If you have any `microservices` workers, they will also need to have the config file mounted to their container.
:::
In your `.env` file, set the variable `IMMICH_CONFIG_FILE` to the path of your config.
For more information, refer to the [Environment Variables](/install/environment-variables.md) section.
@ -261,7 +269,7 @@ It is recommended to reuse this variable in your `docker-compose.yml`:
```yaml
volumes:
- ./configuration.yml:${IMMICH_CONFIG_FILE}
- ./immich-config.json:${IMMICH_CONFIG_FILE}
```
:::

View file

@ -45,7 +45,7 @@ These environment variables are used by the `docker-compose.yml` file and do **N
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api |
| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/administration/system-integrity) | | server | api, microservices |
| `IMMICH_ALLOW_SETUP` | When `false` disables the `/auth/admin-sign-up` endpoint | `true` | server | api |
| `IMMICH_ALLOW_SETUP` | When `false` disables the `/auth/admin-sign-up` and `/admin/database-backups/start-restore` endpoints | `true` | server | api |
\*1: `TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`.
`TZ` is used by `exiftool` as a fallback in case the timezone cannot be determined from the image metadata. It is also used for logfile timestamps and cron job execution.

View file

@ -1,13 +1,19 @@
---
sidebar_position: 60
sidebar_position: 70
---
# Unraid
# Unraid [ Community ]
:::note
This is a community contribution and not officially supported by the Immich team, but included here for convenience.
Community support can be found in the dedicated channel on the [Discord Server](https://discord.immich.app/).
:::
Immich can easily be installed and updated on Unraid via:
1. [Docker Compose Manager](https://forums.unraid.net/topic/114415-plugin-docker-compose-manager/) plugin from the Unraid Community Apps
2. Community made template on the Unraid Community Apps
1. Community made template on the Unraid Community Apps
2. [Docker Compose Manager](https://forums.unraid.net/topic/114415-plugin-docker-compose-manager/) plugin from the Unraid Community Apps
## Community Applications Template
@ -23,7 +29,7 @@ Once you have Redis and PostgreSQL running, search for Immich on the Unraid CA,
For more information about setting up the community image see [here](https://github.com/imagegenius/docker-immich#application-setup)
## Docker-Compose Method (Official)
## Docker-Compose Method
:::info

View file

@ -28,4 +28,4 @@ run = "prettier --write ."
run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}"
[tools]
wrangler = "4.111.0"
wrangler = "4.114.0"

View file

@ -1,5 +1,5 @@
[tasks.install]
run = "pnpm install --filter immich-e2e --frozen-lockfile"
run = "pnpm install --filter immich-e2e... --frozen-lockfile"
[tasks.build]
dir = "{{ config_root }}"
@ -40,18 +40,19 @@ run = "tsc --noEmit"
[tasks.ci-setup]
depends = [
"//:sdk:install",
"//:sdk:build",
"//packages/cli:install",
"//packages/cli:build",
run = [
{ task = "//:sdk:install" },
{ task = "//:sdk:build" },
{ task = "//packages/cli:install" },
{ task = "//packages/cli:build" },
{ task = ":install" },
]
run = { task = ":install" }
[tasks.ci-unit]
depends = ["//:sdk:install", "//:sdk:build"]
run = [
{ task = "//:sdk:install" },
{ task = "//:sdk:build" },
{ task = ":install" },
{ task = ":format" },
{ task = ":lint" },

View file

@ -38,9 +38,6 @@ export const errorDto = {
incorrectLogin: {
message: 'Incorrect email or password',
},
alreadyHasAdmin: {
message: 'The server already has an admin',
},
};
export const signupResponseDto = {

View file

@ -108,7 +108,7 @@ describe('/admin/database-backups', () => {
const { status, body } = await request(app).post('/admin/database-backups/start-restore').send();
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('The server already has an admin'));
expect(body).toEqual(errorDto.badRequest('Admin setup is not available'));
});
it.sequential('should enter maintenance mode in "database restore mode"', async () => {

View file

@ -30,6 +30,7 @@
"add_to_album_bottom_sheet_added": "Added to {album}",
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
"add_to_album_bottom_sheet_some_local_assets": "Some local assets could not be added to album",
"add_to_album_item_count": "Add {count, plural, one {# item} other {# items}} to album",
"add_to_albums": "Add to albums",
"add_to_albums_count": "Add to albums ({count})",
"add_to_bottom_bar": "Add to",

124
mise.lock
View file

@ -82,6 +82,15 @@ url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353224133"
version = "7.1.3-6"
backend = "github:jellyfin/jellyfin-ffmpeg"
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.windows-x64"]
checksum = "sha256:7b7168149689610296f3a187c717056ce0786cc125a31caf28056737e9ba1cc1"
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_win64-clang-gpl.zip"
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409036094"
[[tools."github:jellyfin/jellyfin-ffmpeg"]]
version = "7.1.3-6"
backend = "github:jellyfin/jellyfin-ffmpeg"
[tools."github:jellyfin/jellyfin-ffmpeg".options]
asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz"
@ -111,23 +120,6 @@ url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets
version = "7.1.3-6"
backend = "github:jellyfin/jellyfin-ffmpeg"
[tools."github:jellyfin/jellyfin-ffmpeg".options]
asset_pattern = "jellyfin-ffmpeg_*_portable_linux64-gpl.tar.xz"
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64"]
checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2"
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz"
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879"
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64-musl"]
checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2"
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz"
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879"
[[tools."github:jellyfin/jellyfin-ffmpeg"]]
version = "7.1.3-6"
backend = "github:jellyfin/jellyfin-ffmpeg"
[tools."github:jellyfin/jellyfin-ffmpeg".options]
asset_pattern = "jellyfin-ffmpeg_*_portable_mac64-gpl.tar.xz"
@ -140,10 +132,18 @@ url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets
version = "7.1.3-6"
backend = "github:jellyfin/jellyfin-ffmpeg"
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.windows-x64"]
checksum = "sha256:7b7168149689610296f3a187c717056ce0786cc125a31caf28056737e9ba1cc1"
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_win64-clang-gpl.zip"
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409036094"
[tools."github:jellyfin/jellyfin-ffmpeg".options]
asset_pattern = "jellyfin-ffmpeg_*_portable_linux64-gpl.tar.xz"
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64"]
checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2"
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz"
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879"
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64-musl"]
checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2"
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz"
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879"
[[tools."github:webassembly/binaryen"]]
version = "version_124"
@ -252,82 +252,82 @@ version = "7.5.0"
backend = "npm:oazapfts"
[[tools.opentofu]]
version = "1.12.4"
version = "1.12.5"
backend = "aqua:opentofu/opentofu"
[tools.opentofu."platforms.linux-arm64"]
checksum = "sha256:dc7bfcd93ce9795a86c58fbf71efd013c39dcd1febb13c9cd3555c43b9c2403a"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/475646678"
checksum = "sha256:e67e9da2b1ddf5050ebee62a584cb826eafe1dfd3827d7ec20899ac62791ed1a"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602545"
[tools.opentofu."platforms.linux-arm64-musl"]
checksum = "sha256:dc7bfcd93ce9795a86c58fbf71efd013c39dcd1febb13c9cd3555c43b9c2403a"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/475646678"
checksum = "sha256:e67e9da2b1ddf5050ebee62a584cb826eafe1dfd3827d7ec20899ac62791ed1a"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602545"
[tools.opentofu."platforms.linux-x64"]
checksum = "sha256:81836d0f12b4fe9013b85586349f993def9429b6383bb77cdd6c2f3a9d9aac24"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/475646677"
checksum = "sha256:a6894d45ae7a17ce83189cce8fe04b5a65f68cefceb62455b5a6a89fa53ab38f"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602624"
[tools.opentofu."platforms.linux-x64-musl"]
checksum = "sha256:81836d0f12b4fe9013b85586349f993def9429b6383bb77cdd6c2f3a9d9aac24"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/475646677"
checksum = "sha256:a6894d45ae7a17ce83189cce8fe04b5a65f68cefceb62455b5a6a89fa53ab38f"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602624"
[tools.opentofu."platforms.macos-arm64"]
checksum = "sha256:7c06e4390d9ccd467773e37ff1c3d833c7ca0c24742cd9e9ad47284bea472247"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_darwin_arm64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/475646544"
checksum = "sha256:2ae38150a667f5c0bd57b318d18ad8091d08f93fcca40345f3d88998661de5a9"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_darwin_arm64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602544"
[tools.opentofu."platforms.macos-x64"]
checksum = "sha256:ead1d2ce643addb4ffeb93240b9377ae1c2fd793a6bd22d65922ac37adfdf546"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_darwin_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/475646688"
checksum = "sha256:1012d8f3d4567bcbcd1f2c7d766feca39a30bced32fb8be47e1887fbbee2456d"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_darwin_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602621"
[tools.opentofu."platforms.windows-x64"]
checksum = "sha256:a4d86a07755c8d151f20f945e6cfb5b40deeed942af36a9bd385c5c2e965d5dd"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_windows_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/475646543"
checksum = "sha256:af11850b496f3720e0184084c56d8b43aa74ea92d2338978bf368d70c96473f1"
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_windows_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602547"
[[tools.pnpm]]
version = "11.13.1"
version = "11.17.0"
backend = "aqua:pnpm/pnpm"
[tools.pnpm."platforms.linux-arm64"]
checksum = "sha256:b52db99d215ed7dc9563aed815953c62a6c1ffd7cd75803d3a07ad7e4f246aed"
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-linux-arm64.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/478563983"
checksum = "sha256:730d17de742a3efbb020ba91d7acfc0456c6ba6ad1cd8eb49f4c229fe9f504d3"
url = "https://github.com/pnpm/pnpm/releases/download/v11.17.0/pnpm-linux-arm64.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/487422123"
provenance = "github-attestations"
[tools.pnpm."platforms.linux-arm64-musl"]
checksum = "sha256:2cadd4fc815c591f498a0a84c9e74a836e3e8c1236275f6e1cfd355bae6ae957"
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-linux-arm64-musl.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/478563985"
checksum = "sha256:cc072c0e7bdd290f52fb9afdcea778ede69f96f0f4dae63623163fc11d4beec3"
url = "https://github.com/pnpm/pnpm/releases/download/v11.17.0/pnpm-linux-arm64-musl.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/487422121"
provenance = "github-attestations"
[tools.pnpm."platforms.linux-x64"]
checksum = "sha256:bd6d4b0e14536207ad76bc838f5980cecd968da15f69aae0b207380cca3f2e98"
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-linux-x64.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/478563986"
checksum = "sha256:bdb1db01bf0f757495405a59a09c5c287f315889dc98d3b14bc374b9fe43a0bf"
url = "https://github.com/pnpm/pnpm/releases/download/v11.17.0/pnpm-linux-x64.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/487422122"
provenance = "github-attestations"
[tools.pnpm."platforms.linux-x64-musl"]
checksum = "sha256:ba19690f4ed1b64f1203ade14e9216352b46232d5582468b26a0160e0c9618c5"
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-linux-x64-musl.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/478563980"
checksum = "sha256:db2e4eeecab336bd41bbbc7a39626b166f1a15bb189efcf6a1dafdf159f23fe7"
url = "https://github.com/pnpm/pnpm/releases/download/v11.17.0/pnpm-linux-x64-musl.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/487422116"
provenance = "github-attestations"
[tools.pnpm."platforms.macos-arm64"]
checksum = "sha256:765c2bf04e8129cb58c0f946e324262e418370b35a203b50b1f06a0567ef8bc1"
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-darwin-arm64.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/478563984"
checksum = "sha256:1e9a35d76f9d382af365d95c64f3abf7815e24350653f5bb1a37e4546265bc2b"
url = "https://github.com/pnpm/pnpm/releases/download/v11.17.0/pnpm-darwin-arm64.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/487422117"
provenance = "github-attestations"
[tools.pnpm."platforms.windows-x64"]
checksum = "sha256:d8bebbc71df2702961c1d34a5e61196bc0aa3bbde33c33253f6afa3dd4546a6d"
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-win32-x64.zip"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/478563981"
checksum = "sha256:3025c174a6dcffec14a071a5c854cb0c350a09a8c7c2857a75f75cc7b29b939c"
url = "https://github.com/pnpm/pnpm/releases/download/v11.17.0/pnpm-win32-x64.zip"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/487422118"
provenance = "github-attestations"
[[tools.terragrunt]]

View file

@ -16,9 +16,9 @@ config_roots = [
[tools]
node = "24.15.0"
pnpm = "11.13.1"
pnpm = "11.17.0"
terragrunt = "1.1.1"
opentofu = "1.12.4"
opentofu = "1.12.5"
"npm:@openapitools/openapi-generator-cli" = "2.40.1"
"npm:oazapfts" = "7.5.0"
"github:extism/cli" = "1.6.3"
@ -69,7 +69,7 @@ sources = [
"patch/*",
"bin/generate-dart-sdk.sh",
]
outputs = ["../mobile/openapi/lib/"]
outputs = ["../mobile/generated/openapi/"]
run = "bash ./bin/generate-dart-sdk.sh"
[tasks.open-api]

7
mobile/.gitignore vendored
View file

@ -32,8 +32,15 @@
.pub/
/build/
lib/**/*.drift.dart
lib/**/*.freezed.dart
lib/routing/router.gr.dart
test/drift/main/generated/
# Pigeon related
/lib/platform/*.g.dart
/ios/**/*.g.swift
/android/**/*.g.kt
# Web related
lib/generated_plugin_registrant.dart

View file

@ -26,15 +26,51 @@ linter:
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
unawaited_futures: true
use_build_context_synchronously: false
# Formatting
avoid_print: true
require_trailing_commas: true
unrelated_type_equality_checks: true
prefer_const_constructors: true
always_use_package_imports: true
always_put_control_body_on_new_line: true
unnecessary_null_checks: true
unnecessary_parenthesis: true
prefer_final_locals: true
prefer_const_declarations: true
prefer_const_literals_to_create_immutables: true
use_super_parameters: true
directives_ordering: true
no_leading_underscores_for_local_identifiers: true
always_declare_return_types: true
avoid_void_async: true
noop_primitive_operations: true
use_named_constants: true
combinators_ordering: true
avoid_multiple_declarations_per_line: true
unnecessary_breaks: true
# Correctness
no_adjacent_strings_in_list: true
cancel_subscriptions: true
close_sinks: true
unawaited_futures: true
discarded_futures: true
no_self_assignments: true
throw_in_finally: true
collection_methods_unrelated_type: true
cast_nullable_to_non_nullable: true
# Known issues
avoid_slow_async_io: true
avoid_type_to_string: true
# Flutter specific
use_build_context_synchronously: true
sized_box_for_whitespace: true
use_colored_box: true
use_decorated_box: true
avoid_unnecessary_containers: true
use_full_hex_values_for_flutter_colors: true
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
@ -46,20 +82,12 @@ analyzer:
- lib/**/*.g.dart
- lib/**/*.drift.dart
# TODO: Re-enable after upgrading custom_lint
# plugins:
# - custom_lint
# NOTE: We explicitly do not use riverpod_lint as there are analyzer version conflicts between
# our Flutter version and the required old riverpod_lint 2.x
errors:
unawaited_futures: warning
always_put_control_body_on_new_line: warning
custom_lint:
rules:
- avoid_build_context_in_providers: false
- avoid_public_notifier_properties: false
- avoid_manual_providers_as_generated_provider_dependency: false
- unsupported_provider_value: false
dart_code_metrics:
rules:
- banned-usage:
@ -82,3 +110,17 @@ dart_code_metrics:
- prefer-const-border-radius
- prefer-declaring-const-constructor
- prefer-switch-expression
# Riverpod
- avoid-calling-notifier-members-inside-build
- avoid-notifier-constructors
- avoid-nullable-async-value-pattern
- avoid-public-notifier-properties
- avoid-ref-inside-state-dispose
- avoid-ref-read-inside-build
- avoid-ref-watch-outside-build
- avoid-unnecessary-consumer-widgets
- dispose-provided-instances
- prefer-immutable-provider-arguments
- use-ref-and-state-synchronously
- use-ref-read-synchronously

View file

@ -152,12 +152,6 @@
This is used by the Flutter tool to
generate GeneratedPluginRegistrant.java -->
<meta-data android:name="flutterEmbedding" android:value="2" />
<!-- Disables default WorkManager initialization to use our custom initialization -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
<!-- Widgets -->
<receiver

View file

@ -3,17 +3,12 @@ package app.alextran.immich
import android.app.Application
import android.os.Handler
import android.os.Looper
import androidx.work.Configuration
import androidx.work.WorkManager
import app.alextran.immich.background.BackgroundEngineLock
import app.alextran.immich.background.BackgroundWorkerApiImpl
class ImmichApp : Application() {
override fun onCreate() {
super.onCreate()
val config = Configuration.Builder().build()
WorkManager.initialize(this, config)
// always start BackupWorker after WorkManager init; this fixes the following bug:
// After the process is killed (by user or system), the first trigger (taking a new picture) is lost.
// Thus, the BackupWorker is not started. If the system kills the process after each initialization
// (because of low memory etc.), the backup is never performed.

View file

@ -1,453 +0,0 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
package app.alextran.immich.background
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private object BackgroundWorkerPigeonUtils {
fun createConnectionError(channelName: String): FlutterError {
return FlutterError("channel-error", "Unable to establish connection on channel: '$channelName'.", "") }
fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
fun doubleEquals(a: Double, b: Double): Boolean {
// Normalize -0.0 to 0.0 and handle NaN equality.
return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN())
}
fun floatEquals(a: Float, b: Float): Boolean {
// Normalize -0.0 to 0.0 and handle NaN equality.
return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN())
}
fun doubleHash(d: Double): Int {
// Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
val normalized = if (d == 0.0) 0.0 else d
val bits = java.lang.Double.doubleToLongBits(normalized)
return (bits xor (bits ushr 32)).toInt()
}
fun floatHash(f: Float): Int {
// Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
val normalized = if (f == 0.0f) 0.0f else f
return java.lang.Float.floatToIntBits(normalized)
}
fun deepEquals(a: Any?, b: Any?): Boolean {
if (a === b) {
return true
}
if (a == null || b == null) {
return false
}
if (a is ByteArray && b is ByteArray) {
return a.contentEquals(b)
}
if (a is IntArray && b is IntArray) {
return a.contentEquals(b)
}
if (a is LongArray && b is LongArray) {
return a.contentEquals(b)
}
if (a is DoubleArray && b is DoubleArray) {
if (a.size != b.size) return false
for (i in a.indices) {
if (!doubleEquals(a[i], b[i])) return false
}
return true
}
if (a is FloatArray && b is FloatArray) {
if (a.size != b.size) return false
for (i in a.indices) {
if (!floatEquals(a[i], b[i])) return false
}
return true
}
if (a is Array<*> && b is Array<*>) {
if (a.size != b.size) return false
for (i in a.indices) {
if (!deepEquals(a[i], b[i])) return false
}
return true
}
if (a is List<*> && b is List<*>) {
if (a.size != b.size) return false
val iterA = a.iterator()
val iterB = b.iterator()
while (iterA.hasNext() && iterB.hasNext()) {
if (!deepEquals(iterA.next(), iterB.next())) return false
}
return true
}
if (a is Map<*, *> && b is Map<*, *>) {
if (a.size != b.size) return false
for (entry in a) {
val key = entry.key
var found = false
for (bEntry in b) {
if (deepEquals(key, bEntry.key)) {
if (deepEquals(entry.value, bEntry.value)) {
found = true
break
} else {
return false
}
}
}
if (!found) return false
}
return true
}
if (a is Double && b is Double) {
return doubleEquals(a, b)
}
if (a is Float && b is Float) {
return floatEquals(a, b)
}
return a == b
}
fun deepHash(value: Any?): Int {
return when (value) {
null -> 0
is ByteArray -> value.contentHashCode()
is IntArray -> value.contentHashCode()
is LongArray -> value.contentHashCode()
is DoubleArray -> {
var result = 1
for (item in value) {
result = 31 * result + doubleHash(item)
}
result
}
is FloatArray -> {
var result = 1
for (item in value) {
result = 31 * result + floatHash(item)
}
result
}
is Array<*> -> {
var result = 1
for (item in value) {
result = 31 * result + deepHash(item)
}
result
}
is List<*> -> {
var result = 1
for (item in value) {
result = 31 * result + deepHash(item)
}
result
}
is Map<*, *> -> {
var result = 0
for (entry in value) {
result += ((deepHash(entry.key) * 31) xor deepHash(entry.value))
}
result
}
is Double -> doubleHash(value)
is Float -> floatHash(value)
else -> value.hashCode()
}
}
}
/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : RuntimeException()
/** Generated class from Pigeon that represents data sent in messages. */
data class BackgroundWorkerSettings (
val requiresCharging: Boolean,
val minimumDelaySeconds: Long
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): BackgroundWorkerSettings {
val requiresCharging = pigeonVar_list[0] as Boolean
val minimumDelaySeconds = pigeonVar_list[1] as Long
return BackgroundWorkerSettings(requiresCharging, minimumDelaySeconds)
}
}
fun toList(): List<Any?> {
return listOf(
requiresCharging,
minimumDelaySeconds,
)
}
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
val other = other as BackgroundWorkerSettings
return BackgroundWorkerPigeonUtils.deepEquals(this.requiresCharging, other.requiresCharging) && BackgroundWorkerPigeonUtils.deepEquals(this.minimumDelaySeconds, other.minimumDelaySeconds)
}
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + BackgroundWorkerPigeonUtils.deepHash(this.requiresCharging)
result = 31 * result + BackgroundWorkerPigeonUtils.deepHash(this.minimumDelaySeconds)
return result
}
}
private open class BackgroundWorkerPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
BackgroundWorkerSettings.fromList(it)
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is BackgroundWorkerSettings -> {
stream.write(129)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface BackgroundWorkerFgHostApi {
fun enable()
fun saveNotificationMessage(title: String, body: String)
fun configure(settings: BackgroundWorkerSettings)
fun disable()
companion object {
/** The codec used by BackgroundWorkerFgHostApi. */
val codec: MessageCodec<Any?> by lazy {
BackgroundWorkerPigeonCodec()
}
/** Sets up an instance of `BackgroundWorkerFgHostApi` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: BackgroundWorkerFgHostApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enable$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.enable()
listOf(null)
} catch (exception: Throwable) {
BackgroundWorkerPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.saveNotificationMessage$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val titleArg = args[0] as String
val bodyArg = args[1] as String
val wrapped: List<Any?> = try {
api.saveNotificationMessage(titleArg, bodyArg)
listOf(null)
} catch (exception: Throwable) {
BackgroundWorkerPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val settingsArg = args[0] as BackgroundWorkerSettings
val wrapped: List<Any?> = try {
api.configure(settingsArg)
listOf(null)
} catch (exception: Throwable) {
BackgroundWorkerPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.disable$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.disable()
listOf(null)
} catch (exception: Throwable) {
BackgroundWorkerPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface BackgroundWorkerBgHostApi {
fun onInitialized()
fun close()
companion object {
/** The codec used by BackgroundWorkerBgHostApi. */
val codec: MessageCodec<Any?> by lazy {
BackgroundWorkerPigeonCodec()
}
/** Sets up an instance of `BackgroundWorkerBgHostApi` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: BackgroundWorkerBgHostApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.onInitialized$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.onInitialized()
listOf(null)
} catch (exception: Throwable) {
BackgroundWorkerPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.close()
listOf(null)
} catch (exception: Throwable) {
BackgroundWorkerPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */
class BackgroundWorkerFlutterApi(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") {
companion object {
/** The codec used by BackgroundWorkerFlutterApi. */
val codec: MessageCodec<Any?> by lazy {
BackgroundWorkerPigeonCodec()
}
}
fun onIosUpload(isRefreshArg: Boolean, maxSecondsArg: Long?, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onIosUpload$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(isRefreshArg, maxSecondsArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(BackgroundWorkerPigeonUtils.createConnectionError(channelName)))
}
}
}
fun onAndroidUpload(maxMinutesArg: Long?, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(maxMinutesArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(BackgroundWorkerPigeonUtils.createConnectionError(channelName)))
}
}
}
fun cancel(callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.cancel$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(null) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(BackgroundWorkerPigeonUtils.createConnectionError(channelName)))
}
}
}
}

View file

@ -1,95 +0,0 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
package app.alextran.immich.background
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private object BackgroundWorkerLockPigeonUtils {
fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
}
private open class BackgroundWorkerLockPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return super.readValueOfType(type, buffer)
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
super.writeValue(stream, value)
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface BackgroundWorkerLockApi {
fun lock()
fun unlock()
companion object {
/** The codec used by BackgroundWorkerLockApi. */
val codec: MessageCodec<Any?> by lazy {
BackgroundWorkerLockPigeonCodec()
}
/** Sets up an instance of `BackgroundWorkerLockApi` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: BackgroundWorkerLockApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerLockApi.lock$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.lock()
listOf(null)
} catch (exception: Throwable) {
BackgroundWorkerLockPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerLockApi.unlock$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.unlock()
listOf(null)
} catch (exception: Throwable) {
BackgroundWorkerLockPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}

View file

@ -1,116 +0,0 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
package app.alextran.immich.connectivity
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private object ConnectivityPigeonUtils {
fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
}
/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : RuntimeException()
enum class NetworkCapability(val raw: Int) {
CELLULAR(0),
WIFI(1),
VPN(2),
UNMETERED(3);
companion object {
fun ofRaw(raw: Int): NetworkCapability? {
return values().firstOrNull { it.raw == raw }
}
}
}
private open class ConnectivityPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as Long?)?.let {
NetworkCapability.ofRaw(it.toInt())
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is NetworkCapability -> {
stream.write(129)
writeValue(stream, value.raw.toLong())
}
else -> super.writeValue(stream, value)
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface ConnectivityApi {
fun getCapabilities(): List<NetworkCapability>
companion object {
/** The codec used by ConnectivityApi. */
val codec: MessageCodec<Any?> by lazy {
ConnectivityPigeonCodec()
}
/** Sets up an instance of `ConnectivityApi` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: ConnectivityApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val taskQueue = binaryMessenger.makeBackgroundTaskQueue()
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ConnectivityApi.getCapabilities$separatedMessageChannelSuffix", codec, taskQueue)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
listOf(api.getCapabilities())
} catch (exception: Throwable) {
ConnectivityPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}

View file

@ -1,451 +0,0 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
package app.alextran.immich.core
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private object NetworkPigeonUtils {
fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
fun doubleEquals(a: Double, b: Double): Boolean {
// Normalize -0.0 to 0.0 and handle NaN equality.
return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN())
}
fun floatEquals(a: Float, b: Float): Boolean {
// Normalize -0.0 to 0.0 and handle NaN equality.
return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN())
}
fun doubleHash(d: Double): Int {
// Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
val normalized = if (d == 0.0) 0.0 else d
val bits = java.lang.Double.doubleToLongBits(normalized)
return (bits xor (bits ushr 32)).toInt()
}
fun floatHash(f: Float): Int {
// Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
val normalized = if (f == 0.0f) 0.0f else f
return java.lang.Float.floatToIntBits(normalized)
}
fun deepEquals(a: Any?, b: Any?): Boolean {
if (a === b) {
return true
}
if (a == null || b == null) {
return false
}
if (a is ByteArray && b is ByteArray) {
return a.contentEquals(b)
}
if (a is IntArray && b is IntArray) {
return a.contentEquals(b)
}
if (a is LongArray && b is LongArray) {
return a.contentEquals(b)
}
if (a is DoubleArray && b is DoubleArray) {
if (a.size != b.size) return false
for (i in a.indices) {
if (!doubleEquals(a[i], b[i])) return false
}
return true
}
if (a is FloatArray && b is FloatArray) {
if (a.size != b.size) return false
for (i in a.indices) {
if (!floatEquals(a[i], b[i])) return false
}
return true
}
if (a is Array<*> && b is Array<*>) {
if (a.size != b.size) return false
for (i in a.indices) {
if (!deepEquals(a[i], b[i])) return false
}
return true
}
if (a is List<*> && b is List<*>) {
if (a.size != b.size) return false
val iterA = a.iterator()
val iterB = b.iterator()
while (iterA.hasNext() && iterB.hasNext()) {
if (!deepEquals(iterA.next(), iterB.next())) return false
}
return true
}
if (a is Map<*, *> && b is Map<*, *>) {
if (a.size != b.size) return false
for (entry in a) {
val key = entry.key
var found = false
for (bEntry in b) {
if (deepEquals(key, bEntry.key)) {
if (deepEquals(entry.value, bEntry.value)) {
found = true
break
} else {
return false
}
}
}
if (!found) return false
}
return true
}
if (a is Double && b is Double) {
return doubleEquals(a, b)
}
if (a is Float && b is Float) {
return floatEquals(a, b)
}
return a == b
}
fun deepHash(value: Any?): Int {
return when (value) {
null -> 0
is ByteArray -> value.contentHashCode()
is IntArray -> value.contentHashCode()
is LongArray -> value.contentHashCode()
is DoubleArray -> {
var result = 1
for (item in value) {
result = 31 * result + doubleHash(item)
}
result
}
is FloatArray -> {
var result = 1
for (item in value) {
result = 31 * result + floatHash(item)
}
result
}
is Array<*> -> {
var result = 1
for (item in value) {
result = 31 * result + deepHash(item)
}
result
}
is List<*> -> {
var result = 1
for (item in value) {
result = 31 * result + deepHash(item)
}
result
}
is Map<*, *> -> {
var result = 0
for (entry in value) {
result += ((deepHash(entry.key) * 31) xor deepHash(entry.value))
}
result
}
is Double -> doubleHash(value)
is Float -> floatHash(value)
else -> value.hashCode()
}
}
}
/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : RuntimeException()
/** Generated class from Pigeon that represents data sent in messages. */
data class ClientCertData (
val data: ByteArray,
val password: String
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): ClientCertData {
val data = pigeonVar_list[0] as ByteArray
val password = pigeonVar_list[1] as String
return ClientCertData(data, password)
}
}
fun toList(): List<Any?> {
return listOf(
data,
password,
)
}
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
val other = other as ClientCertData
return NetworkPigeonUtils.deepEquals(this.data, other.data) && NetworkPigeonUtils.deepEquals(this.password, other.password)
}
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + NetworkPigeonUtils.deepHash(this.data)
result = 31 * result + NetworkPigeonUtils.deepHash(this.password)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class ClientCertPrompt (
val title: String,
val message: String,
val cancel: String,
val confirm: String
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): ClientCertPrompt {
val title = pigeonVar_list[0] as String
val message = pigeonVar_list[1] as String
val cancel = pigeonVar_list[2] as String
val confirm = pigeonVar_list[3] as String
return ClientCertPrompt(title, message, cancel, confirm)
}
}
fun toList(): List<Any?> {
return listOf(
title,
message,
cancel,
confirm,
)
}
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
val other = other as ClientCertPrompt
return NetworkPigeonUtils.deepEquals(this.title, other.title) && NetworkPigeonUtils.deepEquals(this.message, other.message) && NetworkPigeonUtils.deepEquals(this.cancel, other.cancel) && NetworkPigeonUtils.deepEquals(this.confirm, other.confirm)
}
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + NetworkPigeonUtils.deepHash(this.title)
result = 31 * result + NetworkPigeonUtils.deepHash(this.message)
result = 31 * result + NetworkPigeonUtils.deepHash(this.cancel)
result = 31 * result + NetworkPigeonUtils.deepHash(this.confirm)
return result
}
}
private open class NetworkPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ClientCertData.fromList(it)
}
}
130.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ClientCertPrompt.fromList(it)
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is ClientCertData -> {
stream.write(129)
writeValue(stream, value.toList())
}
is ClientCertPrompt -> {
stream.write(130)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface NetworkApi {
fun addCertificate(clientData: ClientCertData, callback: (Result<Unit>) -> Unit)
fun selectCertificate(promptText: ClientCertPrompt, callback: (Result<Unit>) -> Unit)
fun removeCertificate(callback: (Result<Unit>) -> Unit)
fun hasCertificate(): Boolean
fun getClientPointer(): Long
fun setRequestHeaders(headers: Map<String, String>, serverUrls: List<String>, token: String?)
fun getAppGroupId(): String
companion object {
/** The codec used by NetworkApi. */
val codec: MessageCodec<Any?> by lazy {
NetworkPigeonCodec()
}
/** Sets up an instance of `NetworkApi` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: NetworkApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.addCertificate$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val clientDataArg = args[0] as ClientCertData
api.addCertificate(clientDataArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(NetworkPigeonUtils.wrapError(error))
} else {
reply.reply(NetworkPigeonUtils.wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.selectCertificate$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val promptTextArg = args[0] as ClientCertPrompt
api.selectCertificate(promptTextArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(NetworkPigeonUtils.wrapError(error))
} else {
reply.reply(NetworkPigeonUtils.wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.removeCertificate$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.removeCertificate{ result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(NetworkPigeonUtils.wrapError(error))
} else {
reply.reply(NetworkPigeonUtils.wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.hasCertificate$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
listOf(api.hasCertificate())
} catch (exception: Throwable) {
NetworkPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.getClientPointer$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
listOf(api.getClientPointer())
} catch (exception: Throwable) {
NetworkPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.setRequestHeaders$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val headersArg = args[0] as Map<String, String>
val serverUrlsArg = args[1] as List<String>
val tokenArg = args[2] as String?
val wrapped: List<Any?> = try {
api.setRequestHeaders(headersArg, serverUrlsArg, tokenArg)
listOf(null)
} catch (exception: Throwable) {
NetworkPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.getAppGroupId$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
listOf(api.getAppGroupId())
} catch (exception: Throwable) {
NetworkPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}

View file

@ -1,140 +0,0 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
package app.alextran.immich.images
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private object LocalImagesPigeonUtils {
fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
}
/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : RuntimeException()
private open class LocalImagesPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return super.readValueOfType(type, buffer)
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
super.writeValue(stream, value)
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface LocalImageApi {
fun requestImage(assetId: String, requestId: Long, width: Long, height: Long, isVideo: Boolean, preferEncoded: Boolean, callback: (Result<Map<String, Long>?>) -> Unit)
fun cancelRequest(requestId: Long)
fun getThumbhash(thumbhash: String, callback: (Result<Map<String, Long>>) -> Unit)
companion object {
/** The codec used by LocalImageApi. */
val codec: MessageCodec<Any?> by lazy {
LocalImagesPigeonCodec()
}
/** Sets up an instance of `LocalImageApi` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: LocalImageApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.LocalImageApi.requestImage$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val assetIdArg = args[0] as String
val requestIdArg = args[1] as Long
val widthArg = args[2] as Long
val heightArg = args[3] as Long
val isVideoArg = args[4] as Boolean
val preferEncodedArg = args[5] as Boolean
api.requestImage(assetIdArg, requestIdArg, widthArg, heightArg, isVideoArg, preferEncodedArg) { result: Result<Map<String, Long>?> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(LocalImagesPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(LocalImagesPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.LocalImageApi.cancelRequest$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val requestIdArg = args[0] as Long
val wrapped: List<Any?> = try {
api.cancelRequest(requestIdArg)
listOf(null)
} catch (exception: Throwable) {
LocalImagesPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.LocalImageApi.getThumbhash$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val thumbhashArg = args[0] as String
api.getThumbhash(thumbhashArg) { result: Result<Map<String, Long>> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(LocalImagesPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(LocalImagesPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}

View file

@ -1,123 +0,0 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
package app.alextran.immich.images
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private object RemoteImagesPigeonUtils {
fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
}
private open class RemoteImagesPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return super.readValueOfType(type, buffer)
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
super.writeValue(stream, value)
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface RemoteImageApi {
fun requestImage(url: String, requestId: Long, preferEncoded: Boolean, callback: (Result<Map<String, Long>?>) -> Unit)
fun cancelRequest(requestId: Long)
fun clearCache(callback: (Result<Long>) -> Unit)
companion object {
/** The codec used by RemoteImageApi. */
val codec: MessageCodec<Any?> by lazy {
RemoteImagesPigeonCodec()
}
/** Sets up an instance of `RemoteImageApi` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: RemoteImageApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.RemoteImageApi.requestImage$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val urlArg = args[0] as String
val requestIdArg = args[1] as Long
val preferEncodedArg = args[2] as Boolean
api.requestImage(urlArg, requestIdArg, preferEncodedArg) { result: Result<Map<String, Long>?> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(RemoteImagesPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(RemoteImagesPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.RemoteImageApi.cancelRequest$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val requestIdArg = args[0] as Long
val wrapped: List<Any?> = try {
api.cancelRequest(requestIdArg)
listOf(null)
} catch (exception: Throwable) {
RemoteImagesPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.RemoteImageApi.clearCache$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.clearCache{ result: Result<Long> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(RemoteImagesPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(RemoteImagesPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}

View file

@ -1,169 +0,0 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
package app.alextran.immich.permission
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private object PermissionApiPigeonUtils {
fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
}
/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : RuntimeException()
enum class PermissionStatus(val raw: Int) {
GRANTED(0),
DENIED(1),
PERMANENTLY_DENIED(2);
companion object {
fun ofRaw(raw: Int): PermissionStatus? {
return values().firstOrNull { it.raw == raw }
}
}
}
private open class PermissionApiPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as Long?)?.let {
PermissionStatus.ofRaw(it.toInt())
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is PermissionStatus -> {
stream.write(129)
writeValue(stream, value.raw.toLong())
}
else -> super.writeValue(stream, value)
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface PermissionApi {
fun isIgnoringBatteryOptimizations(): PermissionStatus
fun hasManageMediaPermission(): Boolean
fun requestManageMediaPermission(callback: (Result<Boolean>) -> Unit)
fun manageMediaPermission(callback: (Result<Boolean>) -> Unit)
companion object {
/** The codec used by PermissionApi. */
val codec: MessageCodec<Any?> by lazy {
PermissionApiPigeonCodec()
}
/** Sets up an instance of `PermissionApi` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: PermissionApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.isIgnoringBatteryOptimizations$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
listOf(api.isIgnoringBatteryOptimizations())
} catch (exception: Throwable) {
PermissionApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
listOf(api.hasManageMediaPermission())
} catch (exception: Throwable) {
PermissionApiPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.requestManageMediaPermission$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.requestManageMediaPermission{ result: Result<Boolean> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(PermissionApiPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(PermissionApiPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.manageMediaPermission$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.manageMediaPermission{ result: Result<Boolean> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(PermissionApiPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(PermissionApiPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}

View file

@ -1,823 +0,0 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
package app.alextran.immich.sync
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private object MessagesPigeonUtils {
fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
fun doubleEquals(a: Double, b: Double): Boolean {
// Normalize -0.0 to 0.0 and handle NaN equality.
return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN())
}
fun floatEquals(a: Float, b: Float): Boolean {
// Normalize -0.0 to 0.0 and handle NaN equality.
return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN())
}
fun doubleHash(d: Double): Int {
// Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
val normalized = if (d == 0.0) 0.0 else d
val bits = java.lang.Double.doubleToLongBits(normalized)
return (bits xor (bits ushr 32)).toInt()
}
fun floatHash(f: Float): Int {
// Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
val normalized = if (f == 0.0f) 0.0f else f
return java.lang.Float.floatToIntBits(normalized)
}
fun deepEquals(a: Any?, b: Any?): Boolean {
if (a === b) {
return true
}
if (a == null || b == null) {
return false
}
if (a is ByteArray && b is ByteArray) {
return a.contentEquals(b)
}
if (a is IntArray && b is IntArray) {
return a.contentEquals(b)
}
if (a is LongArray && b is LongArray) {
return a.contentEquals(b)
}
if (a is DoubleArray && b is DoubleArray) {
if (a.size != b.size) return false
for (i in a.indices) {
if (!doubleEquals(a[i], b[i])) return false
}
return true
}
if (a is FloatArray && b is FloatArray) {
if (a.size != b.size) return false
for (i in a.indices) {
if (!floatEquals(a[i], b[i])) return false
}
return true
}
if (a is Array<*> && b is Array<*>) {
if (a.size != b.size) return false
for (i in a.indices) {
if (!deepEquals(a[i], b[i])) return false
}
return true
}
if (a is List<*> && b is List<*>) {
if (a.size != b.size) return false
val iterA = a.iterator()
val iterB = b.iterator()
while (iterA.hasNext() && iterB.hasNext()) {
if (!deepEquals(iterA.next(), iterB.next())) return false
}
return true
}
if (a is Map<*, *> && b is Map<*, *>) {
if (a.size != b.size) return false
for (entry in a) {
val key = entry.key
var found = false
for (bEntry in b) {
if (deepEquals(key, bEntry.key)) {
if (deepEquals(entry.value, bEntry.value)) {
found = true
break
} else {
return false
}
}
}
if (!found) return false
}
return true
}
if (a is Double && b is Double) {
return doubleEquals(a, b)
}
if (a is Float && b is Float) {
return floatEquals(a, b)
}
return a == b
}
fun deepHash(value: Any?): Int {
return when (value) {
null -> 0
is ByteArray -> value.contentHashCode()
is IntArray -> value.contentHashCode()
is LongArray -> value.contentHashCode()
is DoubleArray -> {
var result = 1
for (item in value) {
result = 31 * result + doubleHash(item)
}
result
}
is FloatArray -> {
var result = 1
for (item in value) {
result = 31 * result + floatHash(item)
}
result
}
is Array<*> -> {
var result = 1
for (item in value) {
result = 31 * result + deepHash(item)
}
result
}
is List<*> -> {
var result = 1
for (item in value) {
result = 31 * result + deepHash(item)
}
result
}
is Map<*, *> -> {
var result = 0
for (entry in value) {
result += ((deepHash(entry.key) * 31) xor deepHash(entry.value))
}
result
}
is Double -> doubleHash(value)
is Float -> floatHash(value)
else -> value.hashCode()
}
}
}
/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : RuntimeException()
enum class PlatformAssetPlaybackStyle(val raw: Int) {
UNKNOWN(0),
IMAGE(1),
VIDEO(2),
IMAGE_ANIMATED(3),
LIVE_PHOTO(4),
VIDEO_LOOPING(5);
companion object {
fun ofRaw(raw: Int): PlatformAssetPlaybackStyle? {
return values().firstOrNull { it.raw == raw }
}
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class PlatformAsset (
val id: String,
val name: String,
val type: Long,
val createdAt: Long? = null,
val updatedAt: Long? = null,
val width: Long? = null,
val height: Long? = null,
val durationMs: Long,
val orientation: Long,
val isFavorite: Boolean,
val adjustmentTime: Long? = null,
val latitude: Double? = null,
val longitude: Double? = null,
val playbackStyle: PlatformAssetPlaybackStyle
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): PlatformAsset {
val id = pigeonVar_list[0] as String
val name = pigeonVar_list[1] as String
val type = pigeonVar_list[2] as Long
val createdAt = pigeonVar_list[3] as Long?
val updatedAt = pigeonVar_list[4] as Long?
val width = pigeonVar_list[5] as Long?
val height = pigeonVar_list[6] as Long?
val durationMs = pigeonVar_list[7] as Long
val orientation = pigeonVar_list[8] as Long
val isFavorite = pigeonVar_list[9] as Boolean
val adjustmentTime = pigeonVar_list[10] as Long?
val latitude = pigeonVar_list[11] as Double?
val longitude = pigeonVar_list[12] as Double?
val playbackStyle = pigeonVar_list[13] as PlatformAssetPlaybackStyle
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationMs, orientation, isFavorite, adjustmentTime, latitude, longitude, playbackStyle)
}
}
fun toList(): List<Any?> {
return listOf(
id,
name,
type,
createdAt,
updatedAt,
width,
height,
durationMs,
orientation,
isFavorite,
adjustmentTime,
latitude,
longitude,
playbackStyle,
)
}
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
val other = other as PlatformAsset
return MessagesPigeonUtils.deepEquals(this.id, other.id) && MessagesPigeonUtils.deepEquals(this.name, other.name) && MessagesPigeonUtils.deepEquals(this.type, other.type) && MessagesPigeonUtils.deepEquals(this.createdAt, other.createdAt) && MessagesPigeonUtils.deepEquals(this.updatedAt, other.updatedAt) && MessagesPigeonUtils.deepEquals(this.width, other.width) && MessagesPigeonUtils.deepEquals(this.height, other.height) && MessagesPigeonUtils.deepEquals(this.durationMs, other.durationMs) && MessagesPigeonUtils.deepEquals(this.orientation, other.orientation) && MessagesPigeonUtils.deepEquals(this.isFavorite, other.isFavorite) && MessagesPigeonUtils.deepEquals(this.adjustmentTime, other.adjustmentTime) && MessagesPigeonUtils.deepEquals(this.latitude, other.latitude) && MessagesPigeonUtils.deepEquals(this.longitude, other.longitude) && MessagesPigeonUtils.deepEquals(this.playbackStyle, other.playbackStyle)
}
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + MessagesPigeonUtils.deepHash(this.id)
result = 31 * result + MessagesPigeonUtils.deepHash(this.name)
result = 31 * result + MessagesPigeonUtils.deepHash(this.type)
result = 31 * result + MessagesPigeonUtils.deepHash(this.createdAt)
result = 31 * result + MessagesPigeonUtils.deepHash(this.updatedAt)
result = 31 * result + MessagesPigeonUtils.deepHash(this.width)
result = 31 * result + MessagesPigeonUtils.deepHash(this.height)
result = 31 * result + MessagesPigeonUtils.deepHash(this.durationMs)
result = 31 * result + MessagesPigeonUtils.deepHash(this.orientation)
result = 31 * result + MessagesPigeonUtils.deepHash(this.isFavorite)
result = 31 * result + MessagesPigeonUtils.deepHash(this.adjustmentTime)
result = 31 * result + MessagesPigeonUtils.deepHash(this.latitude)
result = 31 * result + MessagesPigeonUtils.deepHash(this.longitude)
result = 31 * result + MessagesPigeonUtils.deepHash(this.playbackStyle)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class PlatformAlbum (
val id: String,
val name: String,
val updatedAt: Long? = null,
val isCloud: Boolean,
val assetCount: Long
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): PlatformAlbum {
val id = pigeonVar_list[0] as String
val name = pigeonVar_list[1] as String
val updatedAt = pigeonVar_list[2] as Long?
val isCloud = pigeonVar_list[3] as Boolean
val assetCount = pigeonVar_list[4] as Long
return PlatformAlbum(id, name, updatedAt, isCloud, assetCount)
}
}
fun toList(): List<Any?> {
return listOf(
id,
name,
updatedAt,
isCloud,
assetCount,
)
}
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
val other = other as PlatformAlbum
return MessagesPigeonUtils.deepEquals(this.id, other.id) && MessagesPigeonUtils.deepEquals(this.name, other.name) && MessagesPigeonUtils.deepEquals(this.updatedAt, other.updatedAt) && MessagesPigeonUtils.deepEquals(this.isCloud, other.isCloud) && MessagesPigeonUtils.deepEquals(this.assetCount, other.assetCount)
}
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + MessagesPigeonUtils.deepHash(this.id)
result = 31 * result + MessagesPigeonUtils.deepHash(this.name)
result = 31 * result + MessagesPigeonUtils.deepHash(this.updatedAt)
result = 31 * result + MessagesPigeonUtils.deepHash(this.isCloud)
result = 31 * result + MessagesPigeonUtils.deepHash(this.assetCount)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class SyncDelta (
val hasChanges: Boolean,
val updates: List<PlatformAsset>,
val deletes: List<String>,
val assetAlbums: Map<String, List<String>>
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): SyncDelta {
val hasChanges = pigeonVar_list[0] as Boolean
val updates = pigeonVar_list[1] as List<PlatformAsset>
val deletes = pigeonVar_list[2] as List<String>
val assetAlbums = pigeonVar_list[3] as Map<String, List<String>>
return SyncDelta(hasChanges, updates, deletes, assetAlbums)
}
}
fun toList(): List<Any?> {
return listOf(
hasChanges,
updates,
deletes,
assetAlbums,
)
}
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
val other = other as SyncDelta
return MessagesPigeonUtils.deepEquals(this.hasChanges, other.hasChanges) && MessagesPigeonUtils.deepEquals(this.updates, other.updates) && MessagesPigeonUtils.deepEquals(this.deletes, other.deletes) && MessagesPigeonUtils.deepEquals(this.assetAlbums, other.assetAlbums)
}
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + MessagesPigeonUtils.deepHash(this.hasChanges)
result = 31 * result + MessagesPigeonUtils.deepHash(this.updates)
result = 31 * result + MessagesPigeonUtils.deepHash(this.deletes)
result = 31 * result + MessagesPigeonUtils.deepHash(this.assetAlbums)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class HashResult (
val assetId: String,
val error: String? = null,
val hash: String? = null
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): HashResult {
val assetId = pigeonVar_list[0] as String
val error = pigeonVar_list[1] as String?
val hash = pigeonVar_list[2] as String?
return HashResult(assetId, error, hash)
}
}
fun toList(): List<Any?> {
return listOf(
assetId,
error,
hash,
)
}
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
val other = other as HashResult
return MessagesPigeonUtils.deepEquals(this.assetId, other.assetId) && MessagesPigeonUtils.deepEquals(this.error, other.error) && MessagesPigeonUtils.deepEquals(this.hash, other.hash)
}
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + MessagesPigeonUtils.deepHash(this.assetId)
result = 31 * result + MessagesPigeonUtils.deepHash(this.error)
result = 31 * result + MessagesPigeonUtils.deepHash(this.hash)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class CloudIdResult (
val assetId: String,
val error: String? = null,
val cloudId: String? = null
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): CloudIdResult {
val assetId = pigeonVar_list[0] as String
val error = pigeonVar_list[1] as String?
val cloudId = pigeonVar_list[2] as String?
return CloudIdResult(assetId, error, cloudId)
}
}
fun toList(): List<Any?> {
return listOf(
assetId,
error,
cloudId,
)
}
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
val other = other as CloudIdResult
return MessagesPigeonUtils.deepEquals(this.assetId, other.assetId) && MessagesPigeonUtils.deepEquals(this.error, other.error) && MessagesPigeonUtils.deepEquals(this.cloudId, other.cloudId)
}
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + MessagesPigeonUtils.deepHash(this.assetId)
result = 31 * result + MessagesPigeonUtils.deepHash(this.error)
result = 31 * result + MessagesPigeonUtils.deepHash(this.cloudId)
return result
}
}
private open class MessagesPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as Long?)?.let {
PlatformAssetPlaybackStyle.ofRaw(it.toInt())
}
}
130.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
PlatformAsset.fromList(it)
}
}
131.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
PlatformAlbum.fromList(it)
}
}
132.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
SyncDelta.fromList(it)
}
}
133.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
HashResult.fromList(it)
}
}
134.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
CloudIdResult.fromList(it)
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is PlatformAssetPlaybackStyle -> {
stream.write(129)
writeValue(stream, value.raw.toLong())
}
is PlatformAsset -> {
stream.write(130)
writeValue(stream, value.toList())
}
is PlatformAlbum -> {
stream.write(131)
writeValue(stream, value.toList())
}
is SyncDelta -> {
stream.write(132)
writeValue(stream, value.toList())
}
is HashResult -> {
stream.write(133)
writeValue(stream, value.toList())
}
is CloudIdResult -> {
stream.write(134)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface NativeSyncApi {
fun shouldFullSync(callback: (Result<Boolean>) -> Unit)
fun getMediaChanges(callback: (Result<SyncDelta>) -> Unit)
fun checkpointSync()
fun clearSyncCheckpoint()
fun getAssetIdsForAlbum(albumId: String, callback: (Result<List<String>>) -> Unit)
fun getAlbums(callback: (Result<List<PlatformAlbum>>) -> Unit)
fun getAssetsCountSince(albumId: String, timestamp: Long): Long
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?, callback: (Result<List<PlatformAsset>>) -> Unit)
fun hashAssets(assetIds: List<String>, allowNetworkAccess: Boolean, callback: (Result<List<HashResult>>) -> Unit)
fun cancelHashing()
fun cancelSync()
fun getTrashedAssets(): Map<String, List<PlatformAsset>>
fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result<Boolean>) -> Unit)
fun getCloudIdForAssetIds(assetIds: List<String>): List<CloudIdResult>
companion object {
/** The codec used by NativeSyncApi. */
val codec: MessageCodec<Any?> by lazy {
MessagesPigeonCodec()
}
/** Sets up an instance of `NativeSyncApi` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: NativeSyncApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val taskQueue = binaryMessenger.makeBackgroundTaskQueue()
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.shouldFullSync$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.shouldFullSync{ result: Result<Boolean> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(MessagesPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(MessagesPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.getMediaChanges{ result: Result<SyncDelta> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(MessagesPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(MessagesPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.checkpointSync$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.checkpointSync()
listOf(null)
} catch (exception: Throwable) {
MessagesPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.clearSyncCheckpoint$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.clearSyncCheckpoint()
listOf(null)
} catch (exception: Throwable) {
MessagesPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val albumIdArg = args[0] as String
api.getAssetIdsForAlbum(albumIdArg) { result: Result<List<String>> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(MessagesPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(MessagesPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.getAlbums{ result: Result<List<PlatformAlbum>> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(MessagesPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(MessagesPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsCountSince$separatedMessageChannelSuffix", codec, taskQueue)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val albumIdArg = args[0] as String
val timestampArg = args[1] as Long
val wrapped: List<Any?> = try {
listOf(api.getAssetsCountSince(albumIdArg, timestampArg))
} catch (exception: Throwable) {
MessagesPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsForAlbum$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val albumIdArg = args[0] as String
val updatedTimeCondArg = args[1] as Long?
api.getAssetsForAlbum(albumIdArg, updatedTimeCondArg) { result: Result<List<PlatformAsset>> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(MessagesPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(MessagesPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashAssets$separatedMessageChannelSuffix", codec, taskQueue)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val assetIdsArg = args[0] as List<String>
val allowNetworkAccessArg = args[1] as Boolean
api.hashAssets(assetIdsArg, allowNetworkAccessArg) { result: Result<List<HashResult>> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(MessagesPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(MessagesPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelHashing$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.cancelHashing()
listOf(null)
} catch (exception: Throwable) {
MessagesPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelSync$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.cancelSync()
listOf(null)
} catch (exception: Throwable) {
MessagesPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets$separatedMessageChannelSuffix", codec, taskQueue)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
listOf(api.getTrashedAssets())
} catch (exception: Throwable) {
MessagesPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.restoreFromTrashById$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val mediaIdArg = args[0] as String
val typeArg = args[1] as Long
api.restoreFromTrashById(mediaIdArg, typeArg) { result: Result<Boolean> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(MessagesPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(MessagesPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds$separatedMessageChannelSuffix", codec, taskQueue)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val assetIdsArg = args[0] as List<String>
val wrapped: List<Any?> = try {
listOf(api.getCloudIdForAssetIds(assetIdsArg))
} catch (exception: Throwable) {
MessagesPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}

View file

@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import androidx.exifinterface.media.ExifInterface
import android.os.Build
import android.os.Bundle
import android.os.ext.SdkExtensions
@ -30,6 +29,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.security.MessageDigest
import kotlin.coroutines.cancellation.CancellationException
@ -377,7 +378,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
)?.use { cursor -> cursor.count.toLong() } ?: 0L
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?, callback: (Result<List<PlatformAsset>>) -> Unit) {
fun getAssetsForAlbum(
albumId: String,
updatedTimeCond: Long?,
callback: (Result<List<PlatformAsset>>) -> Unit
) {
runSync(callback) { getAssetsForAlbum(albumId, updatedTimeCond) }
}
@ -419,7 +424,7 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
}.awaitAll()
completeWhenActive(callback, Result.success(results))
} catch (e: CancellationException) {
} catch (_: CancellationException) {
completeWhenActive(
callback, Result.failure(
FlutterError(
@ -437,23 +442,20 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
private suspend fun hashAsset(assetId: String): HashResult {
return try {
val assetUri = ContentUris.withAppendedId(
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
assetId.toLong()
)
val digest = MessageDigest.getInstance("SHA-1")
ctx.contentResolver.openInputStream(assetUri)?.use { inputStream ->
var bytesRead: Int
openOriginalStream(assetId).use { inputStream ->
val buffer = ByteArray(HASH_BUFFER_SIZE)
while (inputStream.read(buffer).also { bytesRead = it } > 0) {
while (true) {
val bytesRead = inputStream.read(buffer)
if (bytesRead == -1) break
currentCoroutineContext().ensureActive()
digest.update(buffer, 0, bytesRead)
}
} ?: return HashResult(assetId, "Cannot open input stream for asset", null)
}
val hashString = Base64.encodeToString(digest.digest(), Base64.NO_WRAP)
HashResult(assetId, null, hashString)
HashResult(assetId, null, Base64.encodeToString(digest.digest(), Base64.NO_WRAP))
} catch (e: CancellationException) {
throw e
} catch (e: SecurityException) {
HashResult(assetId, "Permission denied accessing asset: ${e.message}", null)
} catch (e: Exception) {
@ -461,6 +463,37 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
}
}
private fun openOriginalStream(assetId: String): InputStream {
val id = assetId.toLong()
val collection = when (getMediaType(id)) {
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
else -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
val uri = ContentUris.withAppendedId(collection, id)
val original = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.setRequireOriginal(uri)
} else {
uri
}
return ctx.contentResolver.openInputStream(original)
?: throw IOException("Cannot open original stream for asset $assetId")
}
private fun getMediaType(id: Long): Int =
getCursor(
MediaStore.VOLUME_EXTERNAL,
"${MediaStore.MediaColumns._ID} = ?",
arrayOf(id.toString()),
arrayOf(MediaStore.Files.FileColumns.MEDIA_TYPE)
)?.use { cursor ->
if (cursor.moveToFirst()) {
cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE))
} else {
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
}
} ?: MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
fun cancelHashing() {
hashTask?.cancel()
hashTask = null
@ -476,8 +509,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
syncJob = CoroutineScope(Dispatchers.IO).launch {
try {
completeWhenActive(callback, Result.success(work()))
} catch (e: CancellationException) {
completeWhenActive(callback, Result.failure(FlutterError(SYNC_CANCELLED_CODE, "Sync cancelled", null)))
} catch (_: CancellationException) {
completeWhenActive(
callback,
Result.failure(FlutterError(SYNC_CANCELLED_CODE, "Sync cancelled", null))
)
} catch (e: Exception) {
completeWhenActive(callback, Result.failure(e))
}

View file

@ -1,292 +0,0 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
package app.alextran.immich.viewintent
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private object ViewIntentPigeonUtils {
fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
fun doubleEquals(a: Double, b: Double): Boolean {
// Normalize -0.0 to 0.0 and handle NaN equality.
return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN())
}
fun floatEquals(a: Float, b: Float): Boolean {
// Normalize -0.0 to 0.0 and handle NaN equality.
return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN())
}
fun doubleHash(d: Double): Int {
// Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
val normalized = if (d == 0.0) 0.0 else d
val bits = java.lang.Double.doubleToLongBits(normalized)
return (bits xor (bits ushr 32)).toInt()
}
fun floatHash(f: Float): Int {
// Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
val normalized = if (f == 0.0f) 0.0f else f
return java.lang.Float.floatToIntBits(normalized)
}
fun deepEquals(a: Any?, b: Any?): Boolean {
if (a === b) {
return true
}
if (a == null || b == null) {
return false
}
if (a is ByteArray && b is ByteArray) {
return a.contentEquals(b)
}
if (a is IntArray && b is IntArray) {
return a.contentEquals(b)
}
if (a is LongArray && b is LongArray) {
return a.contentEquals(b)
}
if (a is DoubleArray && b is DoubleArray) {
if (a.size != b.size) return false
for (i in a.indices) {
if (!doubleEquals(a[i], b[i])) return false
}
return true
}
if (a is FloatArray && b is FloatArray) {
if (a.size != b.size) return false
for (i in a.indices) {
if (!floatEquals(a[i], b[i])) return false
}
return true
}
if (a is Array<*> && b is Array<*>) {
if (a.size != b.size) return false
for (i in a.indices) {
if (!deepEquals(a[i], b[i])) return false
}
return true
}
if (a is List<*> && b is List<*>) {
if (a.size != b.size) return false
val iterA = a.iterator()
val iterB = b.iterator()
while (iterA.hasNext() && iterB.hasNext()) {
if (!deepEquals(iterA.next(), iterB.next())) return false
}
return true
}
if (a is Map<*, *> && b is Map<*, *>) {
if (a.size != b.size) return false
for (entry in a) {
val key = entry.key
var found = false
for (bEntry in b) {
if (deepEquals(key, bEntry.key)) {
if (deepEquals(entry.value, bEntry.value)) {
found = true
break
} else {
return false
}
}
}
if (!found) return false
}
return true
}
if (a is Double && b is Double) {
return doubleEquals(a, b)
}
if (a is Float && b is Float) {
return floatEquals(a, b)
}
return a == b
}
fun deepHash(value: Any?): Int {
return when (value) {
null -> 0
is ByteArray -> value.contentHashCode()
is IntArray -> value.contentHashCode()
is LongArray -> value.contentHashCode()
is DoubleArray -> {
var result = 1
for (item in value) {
result = 31 * result + doubleHash(item)
}
result
}
is FloatArray -> {
var result = 1
for (item in value) {
result = 31 * result + floatHash(item)
}
result
}
is Array<*> -> {
var result = 1
for (item in value) {
result = 31 * result + deepHash(item)
}
result
}
is List<*> -> {
var result = 1
for (item in value) {
result = 31 * result + deepHash(item)
}
result
}
is Map<*, *> -> {
var result = 0
for (entry in value) {
result += ((deepHash(entry.key) * 31) xor deepHash(entry.value))
}
result
}
is Double -> doubleHash(value)
is Float -> floatHash(value)
else -> value.hashCode()
}
}
}
/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : RuntimeException()
/** Generated class from Pigeon that represents data sent in messages. */
data class ViewIntentPayload (
val path: String? = null,
val mimeType: String,
val localAssetId: String? = null
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): ViewIntentPayload {
val path = pigeonVar_list[0] as String?
val mimeType = pigeonVar_list[1] as String
val localAssetId = pigeonVar_list[2] as String?
return ViewIntentPayload(path, mimeType, localAssetId)
}
}
fun toList(): List<Any?> {
return listOf(
path,
mimeType,
localAssetId,
)
}
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
val other = other as ViewIntentPayload
return ViewIntentPigeonUtils.deepEquals(this.path, other.path) && ViewIntentPigeonUtils.deepEquals(this.mimeType, other.mimeType) && ViewIntentPigeonUtils.deepEquals(this.localAssetId, other.localAssetId)
}
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + ViewIntentPigeonUtils.deepHash(this.path)
result = 31 * result + ViewIntentPigeonUtils.deepHash(this.mimeType)
result = 31 * result + ViewIntentPigeonUtils.deepHash(this.localAssetId)
return result
}
}
private open class ViewIntentPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ViewIntentPayload.fromList(it)
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is ViewIntentPayload -> {
stream.write(129)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface ViewIntentHostApi {
fun consumeViewIntent(callback: (Result<ViewIntentPayload?>) -> Unit)
companion object {
/** The codec used by ViewIntentHostApi. */
val codec: MessageCodec<Any?> by lazy {
ViewIntentPigeonCodec()
}
/** Sets up an instance of `ViewIntentHostApi` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: ViewIntentHostApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ViewIntentHostApi.consumeViewIntent$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.consumeViewIntent{ result: Result<ViewIntentPayload?> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(ViewIntentPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(ViewIntentPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}

View file

@ -1,4 +1,4 @@
// ignore_for_file: avoid_print
// ignore_for_file: avoid_slow_async_io, avoid_print
import 'dart:convert';
import 'dart:io';

View file

@ -1,418 +0,0 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
import Foundation
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}
private func wrapError(_ error: Any) -> [Any?] {
if let pigeonError = error as? PigeonError {
return [
pigeonError.code,
pigeonError.message,
pigeonError.details,
]
}
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details,
]
}
return [
"\(error)",
"\(Swift.type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)",
]
}
private func createConnectionError(withChannelName channelName: String) -> PigeonError {
return PigeonError(code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", details: "")
}
private func isNullish(_ value: Any?) -> Bool {
return value is NSNull || value == nil
}
private func nilOrValue<T>(_ value: Any?) -> T? {
if value is NSNull { return nil }
return value as! T?
}
private func doubleEqualsBackgroundWorker(_ lhs: Double, _ rhs: Double) -> Bool {
return (lhs.isNaN && rhs.isNaN) || lhs == rhs
}
private func doubleHashBackgroundWorker(_ value: Double, _ hasher: inout Hasher) {
if value.isNaN {
hasher.combine(0x7FF8000000000000)
} else {
// Normalize -0.0 to 0.0
hasher.combine(value == 0 ? 0 : value)
}
}
func deepEqualsBackgroundWorker(_ lhs: Any?, _ rhs: Any?) -> Bool {
let cleanLhs = nilOrValue(lhs) as Any?
let cleanRhs = nilOrValue(rhs) as Any?
switch (cleanLhs, cleanRhs) {
case (nil, nil):
return true
case (nil, _), (_, nil):
return false
case (let lhs as AnyObject, let rhs as AnyObject) where lhs === rhs:
return true
case is (Void, Void):
return true
case (let lhsArray, let rhsArray) as ([Any?], [Any?]):
guard lhsArray.count == rhsArray.count else { return false }
for (index, element) in lhsArray.enumerated() {
if !deepEqualsBackgroundWorker(element, rhsArray[index]) {
return false
}
}
return true
case (let lhsArray, let rhsArray) as ([Double], [Double]):
guard lhsArray.count == rhsArray.count else { return false }
for (index, element) in lhsArray.enumerated() {
if !doubleEqualsBackgroundWorker(element, rhsArray[index]) {
return false
}
}
return true
case (let lhsDictionary, let rhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]):
guard lhsDictionary.count == rhsDictionary.count else { return false }
for (lhsKey, lhsValue) in lhsDictionary {
var found = false
for (rhsKey, rhsValue) in rhsDictionary {
if deepEqualsBackgroundWorker(lhsKey, rhsKey) {
if deepEqualsBackgroundWorker(lhsValue, rhsValue) {
found = true
break
} else {
return false
}
}
}
if !found { return false }
}
return true
case (let lhs as Double, let rhs as Double):
return doubleEqualsBackgroundWorker(lhs, rhs)
case (let lhsHashable, let rhsHashable) as (AnyHashable, AnyHashable):
return lhsHashable == rhsHashable
default:
return false
}
}
func deepHashBackgroundWorker(value: Any?, hasher: inout Hasher) {
let cleanValue = nilOrValue(value) as Any?
if let cleanValue = cleanValue {
if let doubleValue = cleanValue as? Double {
doubleHashBackgroundWorker(doubleValue, &hasher)
} else if let valueList = cleanValue as? [Any?] {
for item in valueList {
deepHashBackgroundWorker(value: item, hasher: &hasher)
}
} else if let valueList = cleanValue as? [Double] {
for item in valueList {
doubleHashBackgroundWorker(item, &hasher)
}
} else if let valueDict = cleanValue as? [AnyHashable: Any?] {
var result = 0
for (key, value) in valueDict {
var entryKeyHasher = Hasher()
deepHashBackgroundWorker(value: key, hasher: &entryKeyHasher)
var entryValueHasher = Hasher()
deepHashBackgroundWorker(value: value, hasher: &entryValueHasher)
result = result &+ ((entryKeyHasher.finalize() &* 31) ^ entryValueHasher.finalize())
}
hasher.combine(result)
} else if let hashableValue = cleanValue as? AnyHashable {
hasher.combine(hashableValue)
} else {
hasher.combine(String(describing: cleanValue))
}
} else {
hasher.combine(0)
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct BackgroundWorkerSettings: Hashable {
var requiresCharging: Bool
var minimumDelaySeconds: Int64
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> BackgroundWorkerSettings? {
let requiresCharging = pigeonVar_list[0] as! Bool
let minimumDelaySeconds = pigeonVar_list[1] as! Int64
return BackgroundWorkerSettings(
requiresCharging: requiresCharging,
minimumDelaySeconds: minimumDelaySeconds
)
}
func toList() -> [Any?] {
return [
requiresCharging,
minimumDelaySeconds,
]
}
static func == (lhs: BackgroundWorkerSettings, rhs: BackgroundWorkerSettings) -> Bool {
if Swift.type(of: lhs) != Swift.type(of: rhs) {
return false
}
return deepEqualsBackgroundWorker(lhs.requiresCharging, rhs.requiresCharging) && deepEqualsBackgroundWorker(lhs.minimumDelaySeconds, rhs.minimumDelaySeconds)
}
func hash(into hasher: inout Hasher) {
hasher.combine("BackgroundWorkerSettings")
deepHashBackgroundWorker(value: requiresCharging, hasher: &hasher)
deepHashBackgroundWorker(value: minimumDelaySeconds, hasher: &hasher)
}
}
private class BackgroundWorkerPigeonCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 129:
return BackgroundWorkerSettings.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
}
}
}
private class BackgroundWorkerPigeonCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? BackgroundWorkerSettings {
super.writeByte(129)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
}
}
private class BackgroundWorkerPigeonCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return BackgroundWorkerPigeonCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return BackgroundWorkerPigeonCodecWriter(data: data)
}
}
class BackgroundWorkerPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = BackgroundWorkerPigeonCodec(readerWriter: BackgroundWorkerPigeonCodecReaderWriter())
}
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol BackgroundWorkerFgHostApi {
func enable() throws
func saveNotificationMessage(title: String, body: String) throws
func configure(settings: BackgroundWorkerSettings) throws
func disable() throws
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class BackgroundWorkerFgHostApiSetup {
static var codec: FlutterStandardMessageCodec { BackgroundWorkerPigeonCodec.shared }
/// Sets up an instance of `BackgroundWorkerFgHostApi` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: BackgroundWorkerFgHostApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
let enableChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enable\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
enableChannel.setMessageHandler { _, reply in
do {
try api.enable()
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
enableChannel.setMessageHandler(nil)
}
let saveNotificationMessageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.saveNotificationMessage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
saveNotificationMessageChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let titleArg = args[0] as! String
let bodyArg = args[1] as! String
do {
try api.saveNotificationMessage(title: titleArg, body: bodyArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
saveNotificationMessageChannel.setMessageHandler(nil)
}
let configureChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
configureChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let settingsArg = args[0] as! BackgroundWorkerSettings
do {
try api.configure(settings: settingsArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
configureChannel.setMessageHandler(nil)
}
let disableChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.disable\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
disableChannel.setMessageHandler { _, reply in
do {
try api.disable()
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
disableChannel.setMessageHandler(nil)
}
}
}
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol BackgroundWorkerBgHostApi {
func onInitialized() throws
func close() throws
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class BackgroundWorkerBgHostApiSetup {
static var codec: FlutterStandardMessageCodec { BackgroundWorkerPigeonCodec.shared }
/// Sets up an instance of `BackgroundWorkerBgHostApi` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: BackgroundWorkerBgHostApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
let onInitializedChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.onInitialized\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
onInitializedChannel.setMessageHandler { _, reply in
do {
try api.onInitialized()
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
onInitializedChannel.setMessageHandler(nil)
}
let closeChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
closeChannel.setMessageHandler { _, reply in
do {
try api.close()
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
closeChannel.setMessageHandler(nil)
}
}
}
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
protocol BackgroundWorkerFlutterApiProtocol {
func onIosUpload(isRefresh isRefreshArg: Bool, maxSeconds maxSecondsArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void)
func onAndroidUpload(maxMinutes maxMinutesArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void)
func cancel(completion: @escaping (Result<Void, PigeonError>) -> Void)
}
class BackgroundWorkerFlutterApi: BackgroundWorkerFlutterApiProtocol {
private let binaryMessenger: FlutterBinaryMessenger
private let messageChannelSuffix: String
init(binaryMessenger: FlutterBinaryMessenger, messageChannelSuffix: String = "") {
self.binaryMessenger = binaryMessenger
self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
}
var codec: BackgroundWorkerPigeonCodec {
return BackgroundWorkerPigeonCodec.shared
}
func onIosUpload(isRefresh isRefreshArg: Bool, maxSeconds maxSecondsArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onIosUpload\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([isRefreshArg, maxSecondsArg] as [Any?]) { response in
guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName)))
return
}
if listResponse.count > 1 {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
completion(.failure(PigeonError(code: code, message: message, details: details)))
} else {
completion(.success(()))
}
}
}
func onAndroidUpload(maxMinutes maxMinutesArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([maxMinutesArg] as [Any?]) { response in
guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName)))
return
}
if listResponse.count > 1 {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
completion(.failure(PigeonError(code: code, message: message, details: details)))
} else {
completion(.success(()))
}
}
}
func cancel(completion: @escaping (Result<Void, PigeonError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.cancel\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage(nil) { response in
guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName)))
return
}
if listResponse.count > 1 {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
completion(.failure(PigeonError(code: code, message: message, details: details)))
} else {
completion(.success(()))
}
}
}
}

View file

@ -1,129 +0,0 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
import Foundation
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}
private func wrapError(_ error: Any) -> [Any?] {
if let pigeonError = error as? PigeonError {
return [
pigeonError.code,
pigeonError.message,
pigeonError.details,
]
}
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details,
]
}
return [
"\(error)",
"\(Swift.type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)",
]
}
private func isNullish(_ value: Any?) -> Bool {
return value is NSNull || value == nil
}
private func nilOrValue<T>(_ value: Any?) -> T? {
if value is NSNull { return nil }
return value as! T?
}
enum NetworkCapability: Int {
case cellular = 0
case wifi = 1
case vpn = 2
case unmetered = 3
}
private class ConnectivityPigeonCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 129:
let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?)
if let enumResultAsInt = enumResultAsInt {
return NetworkCapability(rawValue: enumResultAsInt)
}
return nil
default:
return super.readValue(ofType: type)
}
}
}
private class ConnectivityPigeonCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? NetworkCapability {
super.writeByte(129)
super.writeValue(value.rawValue)
} else {
super.writeValue(value)
}
}
}
private class ConnectivityPigeonCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return ConnectivityPigeonCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return ConnectivityPigeonCodecWriter(data: data)
}
}
class ConnectivityPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = ConnectivityPigeonCodec(readerWriter: ConnectivityPigeonCodecReaderWriter())
}
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol ConnectivityApi {
func getCapabilities() throws -> [NetworkCapability]
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class ConnectivityApiSetup {
static var codec: FlutterStandardMessageCodec { ConnectivityPigeonCodec.shared }
/// Sets up an instance of `ConnectivityApi` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: ConnectivityApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
#if os(iOS)
let taskQueue = binaryMessenger.makeBackgroundTaskQueue?()
#else
let taskQueue: FlutterTaskQueue? = nil
#endif
let getCapabilitiesChannel = taskQueue == nil
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ConnectivityApi.getCapabilities\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ConnectivityApi.getCapabilities\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
if let api = api {
getCapabilitiesChannel.setMessageHandler { _, reply in
do {
let result = try api.getCapabilities()
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
getCapabilitiesChannel.setMessageHandler(nil)
}
}
}

View file

@ -1,406 +0,0 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
import Foundation
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}
private func wrapError(_ error: Any) -> [Any?] {
if let pigeonError = error as? PigeonError {
return [
pigeonError.code,
pigeonError.message,
pigeonError.details,
]
}
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details,
]
}
return [
"\(error)",
"\(Swift.type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)",
]
}
private func isNullish(_ value: Any?) -> Bool {
return value is NSNull || value == nil
}
private func nilOrValue<T>(_ value: Any?) -> T? {
if value is NSNull { return nil }
return value as! T?
}
private func doubleEqualsNetwork(_ lhs: Double, _ rhs: Double) -> Bool {
return (lhs.isNaN && rhs.isNaN) || lhs == rhs
}
private func doubleHashNetwork(_ value: Double, _ hasher: inout Hasher) {
if value.isNaN {
hasher.combine(0x7FF8000000000000)
} else {
// Normalize -0.0 to 0.0
hasher.combine(value == 0 ? 0 : value)
}
}
func deepEqualsNetwork(_ lhs: Any?, _ rhs: Any?) -> Bool {
let cleanLhs = nilOrValue(lhs) as Any?
let cleanRhs = nilOrValue(rhs) as Any?
switch (cleanLhs, cleanRhs) {
case (nil, nil):
return true
case (nil, _), (_, nil):
return false
case (let lhs as AnyObject, let rhs as AnyObject) where lhs === rhs:
return true
case is (Void, Void):
return true
case (let lhsArray, let rhsArray) as ([Any?], [Any?]):
guard lhsArray.count == rhsArray.count else { return false }
for (index, element) in lhsArray.enumerated() {
if !deepEqualsNetwork(element, rhsArray[index]) {
return false
}
}
return true
case (let lhsArray, let rhsArray) as ([Double], [Double]):
guard lhsArray.count == rhsArray.count else { return false }
for (index, element) in lhsArray.enumerated() {
if !doubleEqualsNetwork(element, rhsArray[index]) {
return false
}
}
return true
case (let lhsDictionary, let rhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]):
guard lhsDictionary.count == rhsDictionary.count else { return false }
for (lhsKey, lhsValue) in lhsDictionary {
var found = false
for (rhsKey, rhsValue) in rhsDictionary {
if deepEqualsNetwork(lhsKey, rhsKey) {
if deepEqualsNetwork(lhsValue, rhsValue) {
found = true
break
} else {
return false
}
}
}
if !found { return false }
}
return true
case (let lhs as Double, let rhs as Double):
return doubleEqualsNetwork(lhs, rhs)
case (let lhsHashable, let rhsHashable) as (AnyHashable, AnyHashable):
return lhsHashable == rhsHashable
default:
return false
}
}
func deepHashNetwork(value: Any?, hasher: inout Hasher) {
let cleanValue = nilOrValue(value) as Any?
if let cleanValue = cleanValue {
if let doubleValue = cleanValue as? Double {
doubleHashNetwork(doubleValue, &hasher)
} else if let valueList = cleanValue as? [Any?] {
for item in valueList {
deepHashNetwork(value: item, hasher: &hasher)
}
} else if let valueList = cleanValue as? [Double] {
for item in valueList {
doubleHashNetwork(item, &hasher)
}
} else if let valueDict = cleanValue as? [AnyHashable: Any?] {
var result = 0
for (key, value) in valueDict {
var entryKeyHasher = Hasher()
deepHashNetwork(value: key, hasher: &entryKeyHasher)
var entryValueHasher = Hasher()
deepHashNetwork(value: value, hasher: &entryValueHasher)
result = result &+ ((entryKeyHasher.finalize() &* 31) ^ entryValueHasher.finalize())
}
hasher.combine(result)
} else if let hashableValue = cleanValue as? AnyHashable {
hasher.combine(hashableValue)
} else {
hasher.combine(String(describing: cleanValue))
}
} else {
hasher.combine(0)
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct ClientCertData: Hashable {
var data: FlutterStandardTypedData
var password: String
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> ClientCertData? {
let data = pigeonVar_list[0] as! FlutterStandardTypedData
let password = pigeonVar_list[1] as! String
return ClientCertData(
data: data,
password: password
)
}
func toList() -> [Any?] {
return [
data,
password,
]
}
static func == (lhs: ClientCertData, rhs: ClientCertData) -> Bool {
if Swift.type(of: lhs) != Swift.type(of: rhs) {
return false
}
return deepEqualsNetwork(lhs.data, rhs.data) && deepEqualsNetwork(lhs.password, rhs.password)
}
func hash(into hasher: inout Hasher) {
hasher.combine("ClientCertData")
deepHashNetwork(value: data, hasher: &hasher)
deepHashNetwork(value: password, hasher: &hasher)
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct ClientCertPrompt: Hashable {
var title: String
var message: String
var cancel: String
var confirm: String
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> ClientCertPrompt? {
let title = pigeonVar_list[0] as! String
let message = pigeonVar_list[1] as! String
let cancel = pigeonVar_list[2] as! String
let confirm = pigeonVar_list[3] as! String
return ClientCertPrompt(
title: title,
message: message,
cancel: cancel,
confirm: confirm
)
}
func toList() -> [Any?] {
return [
title,
message,
cancel,
confirm,
]
}
static func == (lhs: ClientCertPrompt, rhs: ClientCertPrompt) -> Bool {
if Swift.type(of: lhs) != Swift.type(of: rhs) {
return false
}
return deepEqualsNetwork(lhs.title, rhs.title) && deepEqualsNetwork(lhs.message, rhs.message) && deepEqualsNetwork(lhs.cancel, rhs.cancel) && deepEqualsNetwork(lhs.confirm, rhs.confirm)
}
func hash(into hasher: inout Hasher) {
hasher.combine("ClientCertPrompt")
deepHashNetwork(value: title, hasher: &hasher)
deepHashNetwork(value: message, hasher: &hasher)
deepHashNetwork(value: cancel, hasher: &hasher)
deepHashNetwork(value: confirm, hasher: &hasher)
}
}
private class NetworkPigeonCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 129:
return ClientCertData.fromList(self.readValue() as! [Any?])
case 130:
return ClientCertPrompt.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
}
}
}
private class NetworkPigeonCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? ClientCertData {
super.writeByte(129)
super.writeValue(value.toList())
} else if let value = value as? ClientCertPrompt {
super.writeByte(130)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
}
}
private class NetworkPigeonCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return NetworkPigeonCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return NetworkPigeonCodecWriter(data: data)
}
}
class NetworkPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = NetworkPigeonCodec(readerWriter: NetworkPigeonCodecReaderWriter())
}
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol NetworkApi {
func addCertificate(clientData: ClientCertData, completion: @escaping (Result<Void, Error>) -> Void)
func selectCertificate(promptText: ClientCertPrompt, completion: @escaping (Result<Void, Error>) -> Void)
func removeCertificate(completion: @escaping (Result<Void, Error>) -> Void)
func hasCertificate() throws -> Bool
func getClientPointer() throws -> Int64
func setRequestHeaders(headers: [String: String], serverUrls: [String], token: String?) throws
func getAppGroupId() throws -> String
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class NetworkApiSetup {
static var codec: FlutterStandardMessageCodec { NetworkPigeonCodec.shared }
/// Sets up an instance of `NetworkApi` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: NetworkApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
let addCertificateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.addCertificate\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
addCertificateChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let clientDataArg = args[0] as! ClientCertData
api.addCertificate(clientData: clientDataArg) { result in
switch result {
case .success:
reply(wrapResult(nil))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
addCertificateChannel.setMessageHandler(nil)
}
let selectCertificateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.selectCertificate\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
selectCertificateChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let promptTextArg = args[0] as! ClientCertPrompt
api.selectCertificate(promptText: promptTextArg) { result in
switch result {
case .success:
reply(wrapResult(nil))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
selectCertificateChannel.setMessageHandler(nil)
}
let removeCertificateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.removeCertificate\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
removeCertificateChannel.setMessageHandler { _, reply in
api.removeCertificate { result in
switch result {
case .success:
reply(wrapResult(nil))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
removeCertificateChannel.setMessageHandler(nil)
}
let hasCertificateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.hasCertificate\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
hasCertificateChannel.setMessageHandler { _, reply in
do {
let result = try api.hasCertificate()
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
hasCertificateChannel.setMessageHandler(nil)
}
let getClientPointerChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.getClientPointer\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getClientPointerChannel.setMessageHandler { _, reply in
do {
let result = try api.getClientPointer()
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
getClientPointerChannel.setMessageHandler(nil)
}
let setRequestHeadersChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.setRequestHeaders\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
setRequestHeadersChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let headersArg = args[0] as! [String: String]
let serverUrlsArg = args[1] as! [String]
let tokenArg: String? = nilOrValue(args[2])
do {
try api.setRequestHeaders(headers: headersArg, serverUrls: serverUrlsArg, token: tokenArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
setRequestHeadersChannel.setMessageHandler(nil)
}
let getAppGroupIdChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.getAppGroupId\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getAppGroupIdChannel.setMessageHandler { _, reply in
do {
let result = try api.getAppGroupId()
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
getAppGroupIdChannel.setMessageHandler(nil)
}
}
}

View file

@ -1,139 +0,0 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
import Foundation
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}
private func wrapError(_ error: Any) -> [Any?] {
if let pigeonError = error as? PigeonError {
return [
pigeonError.code,
pigeonError.message,
pigeonError.details,
]
}
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details,
]
}
return [
"\(error)",
"\(Swift.type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)",
]
}
private func isNullish(_ value: Any?) -> Bool {
return value is NSNull || value == nil
}
private func nilOrValue<T>(_ value: Any?) -> T? {
if value is NSNull { return nil }
return value as! T?
}
private class LocalImagesPigeonCodecReader: FlutterStandardReader {
}
private class LocalImagesPigeonCodecWriter: FlutterStandardWriter {
}
private class LocalImagesPigeonCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return LocalImagesPigeonCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return LocalImagesPigeonCodecWriter(data: data)
}
}
class LocalImagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = LocalImagesPigeonCodec(readerWriter: LocalImagesPigeonCodecReaderWriter())
}
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol LocalImageApi {
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, preferEncoded: Bool, completion: @escaping (Result<[String: Int64]?, Error>) -> Void)
func cancelRequest(requestId: Int64) throws
func getThumbhash(thumbhash: String, completion: @escaping (Result<[String: Int64], Error>) -> Void)
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class LocalImageApiSetup {
static var codec: FlutterStandardMessageCodec { LocalImagesPigeonCodec.shared }
/// Sets up an instance of `LocalImageApi` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: LocalImageApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
let requestImageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.LocalImageApi.requestImage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
requestImageChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let assetIdArg = args[0] as! String
let requestIdArg = args[1] as! Int64
let widthArg = args[2] as! Int64
let heightArg = args[3] as! Int64
let isVideoArg = args[4] as! Bool
let preferEncodedArg = args[5] as! Bool
api.requestImage(assetId: assetIdArg, requestId: requestIdArg, width: widthArg, height: heightArg, isVideo: isVideoArg, preferEncoded: preferEncodedArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
requestImageChannel.setMessageHandler(nil)
}
let cancelRequestChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.LocalImageApi.cancelRequest\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
cancelRequestChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let requestIdArg = args[0] as! Int64
do {
try api.cancelRequest(requestId: requestIdArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
cancelRequestChannel.setMessageHandler(nil)
}
let getThumbhashChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.LocalImageApi.getThumbhash\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getThumbhashChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let thumbhashArg = args[0] as! String
api.getThumbhash(thumbhash: thumbhashArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
getThumbhashChannel.setMessageHandler(nil)
}
}
}

View file

@ -20,6 +20,7 @@ class LocalImageApiImpl: LocalImageApi {
requestOptions.version = .current
return requestOptions
}()
private static let maxPixelSize: CGFloat = 16384
private static let registry = RequestRegistry<ImageRequest>()
@ -108,11 +109,16 @@ class LocalImageApiImpl: LocalImageApi {
]))
}
let isOriginal = !(width > 0 && height > 0)
let targetSize = isOriginal
? CGSize(width: Self.maxPixelSize, height: Self.maxPixelSize)
: CGSize(width: Double(width), height: Double(height))
let contentMode: PHImageContentMode = isOriginal ? .aspectFit : .aspectFill
var image: UIImage?
Self.imageManager.requestImage(
for: asset,
targetSize: width > 0 && height > 0 ? CGSize(width: Double(width), height: Double(height)) : PHImageManagerMaximumSize,
contentMode: .aspectFill,
targetSize: targetSize,
contentMode: contentMode,
options: Self.requestOptions,
resultHandler: { (_image, info) -> Void in
image = _image
@ -123,12 +129,38 @@ class LocalImageApiImpl: LocalImageApi {
return request.completion(ImageProcessing.cancelledResult)
}
guard let image = image,
let cgImage = image.cgImage else {
guard let fastImage = image,
var cgImage = fastImage.cgImage else {
Self.registry.remove(requestId: requestId)
return request.completion(.failure(PigeonError(code: "", message: "Could not get pixel data for \(assetId)", details: nil)))
}
// .fast can return larger than the target, so retry with .exact to guarantee the bound.
if max(cgImage.width, cgImage.height) > Int(Self.maxPixelSize) {
let exactOptions = Self.requestOptions.copy() as! PHImageRequestOptions
exactOptions.resizeMode = .exact
image = nil
Self.imageManager.requestImage(
for: asset,
targetSize: targetSize,
contentMode: contentMode,
options: exactOptions,
resultHandler: { (_image, info) -> Void in
image = _image
}
)
if request.isCancelled {
return request.completion(ImageProcessing.cancelledResult)
}
guard let exactImage = image?.cgImage else {
Self.registry.remove(requestId: requestId)
return request.completion(.failure(PigeonError(code: "", message: "Could not resize image for \(assetId)", details: nil)))
}
cgImage = exactImage
}
if request.isCancelled {
return request.completion(ImageProcessing.cancelledResult)
}

View file

@ -1,134 +0,0 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
import Foundation
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}
private func wrapError(_ error: Any) -> [Any?] {
if let pigeonError = error as? PigeonError {
return [
pigeonError.code,
pigeonError.message,
pigeonError.details,
]
}
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details,
]
}
return [
"\(error)",
"\(Swift.type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)",
]
}
private func isNullish(_ value: Any?) -> Bool {
return value is NSNull || value == nil
}
private func nilOrValue<T>(_ value: Any?) -> T? {
if value is NSNull { return nil }
return value as! T?
}
private class RemoteImagesPigeonCodecReader: FlutterStandardReader {
}
private class RemoteImagesPigeonCodecWriter: FlutterStandardWriter {
}
private class RemoteImagesPigeonCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return RemoteImagesPigeonCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return RemoteImagesPigeonCodecWriter(data: data)
}
}
class RemoteImagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = RemoteImagesPigeonCodec(readerWriter: RemoteImagesPigeonCodecReaderWriter())
}
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol RemoteImageApi {
func requestImage(url: String, requestId: Int64, preferEncoded: Bool, completion: @escaping (Result<[String: Int64]?, Error>) -> Void)
func cancelRequest(requestId: Int64) throws
func clearCache(completion: @escaping (Result<Int64, Error>) -> Void)
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class RemoteImageApiSetup {
static var codec: FlutterStandardMessageCodec { RemoteImagesPigeonCodec.shared }
/// Sets up an instance of `RemoteImageApi` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: RemoteImageApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
let requestImageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.RemoteImageApi.requestImage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
requestImageChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let urlArg = args[0] as! String
let requestIdArg = args[1] as! Int64
let preferEncodedArg = args[2] as! Bool
api.requestImage(url: urlArg, requestId: requestIdArg, preferEncoded: preferEncodedArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
requestImageChannel.setMessageHandler(nil)
}
let cancelRequestChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.RemoteImageApi.cancelRequest\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
cancelRequestChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let requestIdArg = args[0] as! Int64
do {
try api.cancelRequest(requestId: requestIdArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
cancelRequestChannel.setMessageHandler(nil)
}
let clearCacheChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.RemoteImageApi.clearCache\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
clearCacheChannel.setMessageHandler { _, reply in
api.clearCache { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
clearCacheChannel.setMessageHandler(nil)
}
}
}

View file

@ -1,168 +0,0 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
import Foundation
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}
private func wrapError(_ error: Any) -> [Any?] {
if let pigeonError = error as? PigeonError {
return [
pigeonError.code,
pigeonError.message,
pigeonError.details,
]
}
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details,
]
}
return [
"\(error)",
"\(Swift.type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)",
]
}
private func isNullish(_ value: Any?) -> Bool {
return value is NSNull || value == nil
}
private func nilOrValue<T>(_ value: Any?) -> T? {
if value is NSNull { return nil }
return value as! T?
}
enum PermissionStatus: Int {
case granted = 0
case denied = 1
case permanentlyDenied = 2
}
private class PermissionApiPigeonCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 129:
let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?)
if let enumResultAsInt = enumResultAsInt {
return PermissionStatus(rawValue: enumResultAsInt)
}
return nil
default:
return super.readValue(ofType: type)
}
}
}
private class PermissionApiPigeonCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? PermissionStatus {
super.writeByte(129)
super.writeValue(value.rawValue)
} else {
super.writeValue(value)
}
}
}
private class PermissionApiPigeonCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return PermissionApiPigeonCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return PermissionApiPigeonCodecWriter(data: data)
}
}
class PermissionApiPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = PermissionApiPigeonCodec(readerWriter: PermissionApiPigeonCodecReaderWriter())
}
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol PermissionApi {
func isIgnoringBatteryOptimizations() throws -> PermissionStatus
func hasManageMediaPermission() throws -> Bool
func requestManageMediaPermission(completion: @escaping (Result<Bool, Error>) -> Void)
func manageMediaPermission(completion: @escaping (Result<Bool, Error>) -> Void)
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class PermissionApiSetup {
static var codec: FlutterStandardMessageCodec { PermissionApiPigeonCodec.shared }
/// Sets up an instance of `PermissionApi` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: PermissionApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
let isIgnoringBatteryOptimizationsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.isIgnoringBatteryOptimizations\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
isIgnoringBatteryOptimizationsChannel.setMessageHandler { _, reply in
do {
let result = try api.isIgnoringBatteryOptimizations()
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
isIgnoringBatteryOptimizationsChannel.setMessageHandler(nil)
}
let hasManageMediaPermissionChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
hasManageMediaPermissionChannel.setMessageHandler { _, reply in
do {
let result = try api.hasManageMediaPermission()
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
hasManageMediaPermissionChannel.setMessageHandler(nil)
}
let requestManageMediaPermissionChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.requestManageMediaPermission\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
requestManageMediaPermissionChannel.setMessageHandler { _, reply in
api.requestManageMediaPermission { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
requestManageMediaPermissionChannel.setMessageHandler(nil)
}
let manageMediaPermissionChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.manageMediaPermission\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
manageMediaPermissionChannel.setMessageHandler { _, reply in
api.manageMediaPermission { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
manageMediaPermissionChannel.setMessageHandler(nil)
}
}
}

View file

@ -1,777 +0,0 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
import Foundation
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
/// Error class for passing custom error details to Dart side.
final class PigeonError: Error {
let code: String
let message: String?
let details: Sendable?
init(code: String, message: String?, details: Sendable?) {
self.code = code
self.message = message
self.details = details
}
var localizedDescription: String {
return
"PigeonError(code: \(code), message: \(message ?? "<nil>"), details: \(details ?? "<nil>")"
}
}
private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}
private func wrapError(_ error: Any) -> [Any?] {
if let pigeonError = error as? PigeonError {
return [
pigeonError.code,
pigeonError.message,
pigeonError.details,
]
}
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details,
]
}
return [
"\(error)",
"\(Swift.type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)",
]
}
private func isNullish(_ value: Any?) -> Bool {
return value is NSNull || value == nil
}
private func nilOrValue<T>(_ value: Any?) -> T? {
if value is NSNull { return nil }
return value as! T?
}
private func doubleEqualsMessages(_ lhs: Double, _ rhs: Double) -> Bool {
return (lhs.isNaN && rhs.isNaN) || lhs == rhs
}
private func doubleHashMessages(_ value: Double, _ hasher: inout Hasher) {
if value.isNaN {
hasher.combine(0x7FF8000000000000)
} else {
// Normalize -0.0 to 0.0
hasher.combine(value == 0 ? 0 : value)
}
}
func deepEqualsMessages(_ lhs: Any?, _ rhs: Any?) -> Bool {
let cleanLhs = nilOrValue(lhs) as Any?
let cleanRhs = nilOrValue(rhs) as Any?
switch (cleanLhs, cleanRhs) {
case (nil, nil):
return true
case (nil, _), (_, nil):
return false
case (let lhs as AnyObject, let rhs as AnyObject) where lhs === rhs:
return true
case is (Void, Void):
return true
case (let lhsArray, let rhsArray) as ([Any?], [Any?]):
guard lhsArray.count == rhsArray.count else { return false }
for (index, element) in lhsArray.enumerated() {
if !deepEqualsMessages(element, rhsArray[index]) {
return false
}
}
return true
case (let lhsArray, let rhsArray) as ([Double], [Double]):
guard lhsArray.count == rhsArray.count else { return false }
for (index, element) in lhsArray.enumerated() {
if !doubleEqualsMessages(element, rhsArray[index]) {
return false
}
}
return true
case (let lhsDictionary, let rhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]):
guard lhsDictionary.count == rhsDictionary.count else { return false }
for (lhsKey, lhsValue) in lhsDictionary {
var found = false
for (rhsKey, rhsValue) in rhsDictionary {
if deepEqualsMessages(lhsKey, rhsKey) {
if deepEqualsMessages(lhsValue, rhsValue) {
found = true
break
} else {
return false
}
}
}
if !found { return false }
}
return true
case (let lhs as Double, let rhs as Double):
return doubleEqualsMessages(lhs, rhs)
case (let lhsHashable, let rhsHashable) as (AnyHashable, AnyHashable):
return lhsHashable == rhsHashable
default:
return false
}
}
func deepHashMessages(value: Any?, hasher: inout Hasher) {
let cleanValue = nilOrValue(value) as Any?
if let cleanValue = cleanValue {
if let doubleValue = cleanValue as? Double {
doubleHashMessages(doubleValue, &hasher)
} else if let valueList = cleanValue as? [Any?] {
for item in valueList {
deepHashMessages(value: item, hasher: &hasher)
}
} else if let valueList = cleanValue as? [Double] {
for item in valueList {
doubleHashMessages(item, &hasher)
}
} else if let valueDict = cleanValue as? [AnyHashable: Any?] {
var result = 0
for (key, value) in valueDict {
var entryKeyHasher = Hasher()
deepHashMessages(value: key, hasher: &entryKeyHasher)
var entryValueHasher = Hasher()
deepHashMessages(value: value, hasher: &entryValueHasher)
result = result &+ ((entryKeyHasher.finalize() &* 31) ^ entryValueHasher.finalize())
}
hasher.combine(result)
} else if let hashableValue = cleanValue as? AnyHashable {
hasher.combine(hashableValue)
} else {
hasher.combine(String(describing: cleanValue))
}
} else {
hasher.combine(0)
}
}
enum PlatformAssetPlaybackStyle: Int {
case unknown = 0
case image = 1
case video = 2
case imageAnimated = 3
case livePhoto = 4
case videoLooping = 5
}
/// Generated class from Pigeon that represents data sent in messages.
struct PlatformAsset: Hashable {
var id: String
var name: String
var type: Int64
var createdAt: Int64? = nil
var updatedAt: Int64? = nil
var width: Int64? = nil
var height: Int64? = nil
var durationMs: Int64
var orientation: Int64
var isFavorite: Bool
var adjustmentTime: Int64? = nil
var latitude: Double? = nil
var longitude: Double? = nil
var playbackStyle: PlatformAssetPlaybackStyle
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> PlatformAsset? {
let id = pigeonVar_list[0] as! String
let name = pigeonVar_list[1] as! String
let type = pigeonVar_list[2] as! Int64
let createdAt: Int64? = nilOrValue(pigeonVar_list[3])
let updatedAt: Int64? = nilOrValue(pigeonVar_list[4])
let width: Int64? = nilOrValue(pigeonVar_list[5])
let height: Int64? = nilOrValue(pigeonVar_list[6])
let durationMs = pigeonVar_list[7] as! Int64
let orientation = pigeonVar_list[8] as! Int64
let isFavorite = pigeonVar_list[9] as! Bool
let adjustmentTime: Int64? = nilOrValue(pigeonVar_list[10])
let latitude: Double? = nilOrValue(pigeonVar_list[11])
let longitude: Double? = nilOrValue(pigeonVar_list[12])
let playbackStyle = pigeonVar_list[13] as! PlatformAssetPlaybackStyle
return PlatformAsset(
id: id,
name: name,
type: type,
createdAt: createdAt,
updatedAt: updatedAt,
width: width,
height: height,
durationMs: durationMs,
orientation: orientation,
isFavorite: isFavorite,
adjustmentTime: adjustmentTime,
latitude: latitude,
longitude: longitude,
playbackStyle: playbackStyle
)
}
func toList() -> [Any?] {
return [
id,
name,
type,
createdAt,
updatedAt,
width,
height,
durationMs,
orientation,
isFavorite,
adjustmentTime,
latitude,
longitude,
playbackStyle,
]
}
static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool {
if Swift.type(of: lhs) != Swift.type(of: rhs) {
return false
}
return deepEqualsMessages(lhs.id, rhs.id) && deepEqualsMessages(lhs.name, rhs.name) && deepEqualsMessages(lhs.type, rhs.type) && deepEqualsMessages(lhs.createdAt, rhs.createdAt) && deepEqualsMessages(lhs.updatedAt, rhs.updatedAt) && deepEqualsMessages(lhs.width, rhs.width) && deepEqualsMessages(lhs.height, rhs.height) && deepEqualsMessages(lhs.durationMs, rhs.durationMs) && deepEqualsMessages(lhs.orientation, rhs.orientation) && deepEqualsMessages(lhs.isFavorite, rhs.isFavorite) && deepEqualsMessages(lhs.adjustmentTime, rhs.adjustmentTime) && deepEqualsMessages(lhs.latitude, rhs.latitude) && deepEqualsMessages(lhs.longitude, rhs.longitude) && deepEqualsMessages(lhs.playbackStyle, rhs.playbackStyle)
}
func hash(into hasher: inout Hasher) {
hasher.combine("PlatformAsset")
deepHashMessages(value: id, hasher: &hasher)
deepHashMessages(value: name, hasher: &hasher)
deepHashMessages(value: type, hasher: &hasher)
deepHashMessages(value: createdAt, hasher: &hasher)
deepHashMessages(value: updatedAt, hasher: &hasher)
deepHashMessages(value: width, hasher: &hasher)
deepHashMessages(value: height, hasher: &hasher)
deepHashMessages(value: durationMs, hasher: &hasher)
deepHashMessages(value: orientation, hasher: &hasher)
deepHashMessages(value: isFavorite, hasher: &hasher)
deepHashMessages(value: adjustmentTime, hasher: &hasher)
deepHashMessages(value: latitude, hasher: &hasher)
deepHashMessages(value: longitude, hasher: &hasher)
deepHashMessages(value: playbackStyle, hasher: &hasher)
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct PlatformAlbum: Hashable {
var id: String
var name: String
var updatedAt: Int64? = nil
var isCloud: Bool
var assetCount: Int64
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> PlatformAlbum? {
let id = pigeonVar_list[0] as! String
let name = pigeonVar_list[1] as! String
let updatedAt: Int64? = nilOrValue(pigeonVar_list[2])
let isCloud = pigeonVar_list[3] as! Bool
let assetCount = pigeonVar_list[4] as! Int64
return PlatformAlbum(
id: id,
name: name,
updatedAt: updatedAt,
isCloud: isCloud,
assetCount: assetCount
)
}
func toList() -> [Any?] {
return [
id,
name,
updatedAt,
isCloud,
assetCount,
]
}
static func == (lhs: PlatformAlbum, rhs: PlatformAlbum) -> Bool {
if Swift.type(of: lhs) != Swift.type(of: rhs) {
return false
}
return deepEqualsMessages(lhs.id, rhs.id) && deepEqualsMessages(lhs.name, rhs.name) && deepEqualsMessages(lhs.updatedAt, rhs.updatedAt) && deepEqualsMessages(lhs.isCloud, rhs.isCloud) && deepEqualsMessages(lhs.assetCount, rhs.assetCount)
}
func hash(into hasher: inout Hasher) {
hasher.combine("PlatformAlbum")
deepHashMessages(value: id, hasher: &hasher)
deepHashMessages(value: name, hasher: &hasher)
deepHashMessages(value: updatedAt, hasher: &hasher)
deepHashMessages(value: isCloud, hasher: &hasher)
deepHashMessages(value: assetCount, hasher: &hasher)
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct SyncDelta: Hashable {
var hasChanges: Bool
var updates: [PlatformAsset]
var deletes: [String]
var assetAlbums: [String: [String]]
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> SyncDelta? {
let hasChanges = pigeonVar_list[0] as! Bool
let updates = pigeonVar_list[1] as! [PlatformAsset]
let deletes = pigeonVar_list[2] as! [String]
let assetAlbums = pigeonVar_list[3] as! [String: [String]]
return SyncDelta(
hasChanges: hasChanges,
updates: updates,
deletes: deletes,
assetAlbums: assetAlbums
)
}
func toList() -> [Any?] {
return [
hasChanges,
updates,
deletes,
assetAlbums,
]
}
static func == (lhs: SyncDelta, rhs: SyncDelta) -> Bool {
if Swift.type(of: lhs) != Swift.type(of: rhs) {
return false
}
return deepEqualsMessages(lhs.hasChanges, rhs.hasChanges) && deepEqualsMessages(lhs.updates, rhs.updates) && deepEqualsMessages(lhs.deletes, rhs.deletes) && deepEqualsMessages(lhs.assetAlbums, rhs.assetAlbums)
}
func hash(into hasher: inout Hasher) {
hasher.combine("SyncDelta")
deepHashMessages(value: hasChanges, hasher: &hasher)
deepHashMessages(value: updates, hasher: &hasher)
deepHashMessages(value: deletes, hasher: &hasher)
deepHashMessages(value: assetAlbums, hasher: &hasher)
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct HashResult: Hashable {
var assetId: String
var error: String? = nil
var hash: String? = nil
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> HashResult? {
let assetId = pigeonVar_list[0] as! String
let error: String? = nilOrValue(pigeonVar_list[1])
let hash: String? = nilOrValue(pigeonVar_list[2])
return HashResult(
assetId: assetId,
error: error,
hash: hash
)
}
func toList() -> [Any?] {
return [
assetId,
error,
hash,
]
}
static func == (lhs: HashResult, rhs: HashResult) -> Bool {
if Swift.type(of: lhs) != Swift.type(of: rhs) {
return false
}
return deepEqualsMessages(lhs.assetId, rhs.assetId) && deepEqualsMessages(lhs.error, rhs.error) && deepEqualsMessages(lhs.hash, rhs.hash)
}
func hash(into hasher: inout Hasher) {
hasher.combine("HashResult")
deepHashMessages(value: assetId, hasher: &hasher)
deepHashMessages(value: error, hasher: &hasher)
deepHashMessages(value: hash, hasher: &hasher)
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct CloudIdResult: Hashable {
var assetId: String
var error: String? = nil
var cloudId: String? = nil
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> CloudIdResult? {
let assetId = pigeonVar_list[0] as! String
let error: String? = nilOrValue(pigeonVar_list[1])
let cloudId: String? = nilOrValue(pigeonVar_list[2])
return CloudIdResult(
assetId: assetId,
error: error,
cloudId: cloudId
)
}
func toList() -> [Any?] {
return [
assetId,
error,
cloudId,
]
}
static func == (lhs: CloudIdResult, rhs: CloudIdResult) -> Bool {
if Swift.type(of: lhs) != Swift.type(of: rhs) {
return false
}
return deepEqualsMessages(lhs.assetId, rhs.assetId) && deepEqualsMessages(lhs.error, rhs.error) && deepEqualsMessages(lhs.cloudId, rhs.cloudId)
}
func hash(into hasher: inout Hasher) {
hasher.combine("CloudIdResult")
deepHashMessages(value: assetId, hasher: &hasher)
deepHashMessages(value: error, hasher: &hasher)
deepHashMessages(value: cloudId, hasher: &hasher)
}
}
private class MessagesPigeonCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 129:
let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?)
if let enumResultAsInt = enumResultAsInt {
return PlatformAssetPlaybackStyle(rawValue: enumResultAsInt)
}
return nil
case 130:
return PlatformAsset.fromList(self.readValue() as! [Any?])
case 131:
return PlatformAlbum.fromList(self.readValue() as! [Any?])
case 132:
return SyncDelta.fromList(self.readValue() as! [Any?])
case 133:
return HashResult.fromList(self.readValue() as! [Any?])
case 134:
return CloudIdResult.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
}
}
}
private class MessagesPigeonCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? PlatformAssetPlaybackStyle {
super.writeByte(129)
super.writeValue(value.rawValue)
} else if let value = value as? PlatformAsset {
super.writeByte(130)
super.writeValue(value.toList())
} else if let value = value as? PlatformAlbum {
super.writeByte(131)
super.writeValue(value.toList())
} else if let value = value as? SyncDelta {
super.writeByte(132)
super.writeValue(value.toList())
} else if let value = value as? HashResult {
super.writeByte(133)
super.writeValue(value.toList())
} else if let value = value as? CloudIdResult {
super.writeByte(134)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
}
}
private class MessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return MessagesPigeonCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return MessagesPigeonCodecWriter(data: data)
}
}
class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = MessagesPigeonCodec(readerWriter: MessagesPigeonCodecReaderWriter())
}
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol NativeSyncApi {
func shouldFullSync(completion: @escaping (Result<Bool, Error>) -> Void)
func getMediaChanges(completion: @escaping (Result<SyncDelta, Error>) -> Void)
func checkpointSync() throws
func clearSyncCheckpoint() throws
func getAssetIdsForAlbum(albumId: String, completion: @escaping (Result<[String], Error>) -> Void)
func getAlbums(completion: @escaping (Result<[PlatformAlbum], Error>) -> Void)
func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?, completion: @escaping (Result<[PlatformAsset], Error>) -> Void)
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void)
func cancelHashing() throws
func cancelSync() throws
func getTrashedAssets() throws -> [String: [PlatformAsset]]
func restoreFromTrashById(mediaId: String, type: Int64, completion: @escaping (Result<Bool, Error>) -> Void)
func getCloudIdForAssetIds(assetIds: [String]) throws -> [CloudIdResult]
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class NativeSyncApiSetup {
static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared }
/// Sets up an instance of `NativeSyncApi` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: NativeSyncApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
#if os(iOS)
let taskQueue = binaryMessenger.makeBackgroundTaskQueue?()
#else
let taskQueue: FlutterTaskQueue? = nil
#endif
let shouldFullSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.shouldFullSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
shouldFullSyncChannel.setMessageHandler { _, reply in
api.shouldFullSync { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
shouldFullSyncChannel.setMessageHandler(nil)
}
let getMediaChangesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getMediaChangesChannel.setMessageHandler { _, reply in
api.getMediaChanges { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
getMediaChangesChannel.setMessageHandler(nil)
}
let checkpointSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.checkpointSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
checkpointSyncChannel.setMessageHandler { _, reply in
do {
try api.checkpointSync()
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
checkpointSyncChannel.setMessageHandler(nil)
}
let clearSyncCheckpointChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.clearSyncCheckpoint\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
clearSyncCheckpointChannel.setMessageHandler { _, reply in
do {
try api.clearSyncCheckpoint()
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
clearSyncCheckpointChannel.setMessageHandler(nil)
}
let getAssetIdsForAlbumChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getAssetIdsForAlbumChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let albumIdArg = args[0] as! String
api.getAssetIdsForAlbum(albumId: albumIdArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
getAssetIdsForAlbumChannel.setMessageHandler(nil)
}
let getAlbumsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getAlbumsChannel.setMessageHandler { _, reply in
api.getAlbums { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
getAlbumsChannel.setMessageHandler(nil)
}
let getAssetsCountSinceChannel = taskQueue == nil
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsCountSince\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsCountSince\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
if let api = api {
getAssetsCountSinceChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let albumIdArg = args[0] as! String
let timestampArg = args[1] as! Int64
do {
let result = try api.getAssetsCountSince(albumId: albumIdArg, timestamp: timestampArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
getAssetsCountSinceChannel.setMessageHandler(nil)
}
let getAssetsForAlbumChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getAssetsForAlbumChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let albumIdArg = args[0] as! String
let updatedTimeCondArg: Int64? = nilOrValue(args[1])
api.getAssetsForAlbum(albumId: albumIdArg, updatedTimeCond: updatedTimeCondArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
getAssetsForAlbumChannel.setMessageHandler(nil)
}
let hashAssetsChannel = taskQueue == nil
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
if let api = api {
hashAssetsChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let assetIdsArg = args[0] as! [String]
let allowNetworkAccessArg = args[1] as! Bool
api.hashAssets(assetIds: assetIdsArg, allowNetworkAccess: allowNetworkAccessArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
hashAssetsChannel.setMessageHandler(nil)
}
let cancelHashingChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelHashing\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
cancelHashingChannel.setMessageHandler { _, reply in
do {
try api.cancelHashing()
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
cancelHashingChannel.setMessageHandler(nil)
}
let cancelSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
cancelSyncChannel.setMessageHandler { _, reply in
do {
try api.cancelSync()
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
cancelSyncChannel.setMessageHandler(nil)
}
let getTrashedAssetsChannel = taskQueue == nil
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
if let api = api {
getTrashedAssetsChannel.setMessageHandler { _, reply in
do {
let result = try api.getTrashedAssets()
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
getTrashedAssetsChannel.setMessageHandler(nil)
}
let restoreFromTrashByIdChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.restoreFromTrashById\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
restoreFromTrashByIdChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let mediaIdArg = args[0] as! String
let typeArg = args[1] as! Int64
api.restoreFromTrashById(mediaId: mediaIdArg, type: typeArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
restoreFromTrashByIdChannel.setMessageHandler(nil)
}
let getCloudIdForAssetIdsChannel = taskQueue == nil
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
if let api = api {
getCloudIdForAssetIdsChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let assetIdsArg = args[0] as! [String]
do {
let result = try api.getCloudIdForAssetIds(assetIds: assetIdsArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
getCloudIdForAssetIdsChannel.setMessageHandler(nil)
}
}
}

View file

@ -12,13 +12,15 @@ struct ImageEntry: TimelineEntry {
var subtitle: String? = nil
var error: WidgetError? = nil
var deepLink: URL? = nil
var forceFullColor: Bool = true
}
static func build(
api: ImmichAPI,
asset: Asset,
dateOffset: Int,
subtitle: String? = nil
subtitle: String? = nil,
forceFullColor: Bool = true
)
async throws -> Self
{
@ -34,7 +36,8 @@ struct ImageEntry: TimelineEntry {
image: image,
metadata: EntryMetadata(
subtitle: subtitle,
deepLink: asset.deepLink
deepLink: asset.deepLink,
forceFullColor: forceFullColor
)
)
}
@ -111,12 +114,26 @@ struct ImageEntry: TimelineEntry {
}
// Required so that we can decode older versions of the metadata
extension ImageEntry.Metadata {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
subtitle = try container.decodeIfPresent(String.self, forKey: .subtitle)
error = try container.decodeIfPresent(WidgetError.self, forKey: .error)
deepLink = try container.decodeIfPresent(URL.self, forKey: .deepLink)
forceFullColor =
try container.decodeIfPresent(Bool.self, forKey: .forceFullColor) ?? true
}
}
func generateRandomEntries(
api: ImmichAPI,
now: Date,
count: Int,
filter: SearchFilter = Album.NONE.filter,
subtitle: String? = nil
subtitle: String? = nil,
forceFullColor: Bool = true
)
async throws -> [ImageEntry]
{
@ -132,7 +149,8 @@ func generateRandomEntries(
api: api,
asset: asset,
dateOffset: dateOffset,
subtitle: subtitle
subtitle: subtitle,
forceFullColor: forceFullColor
)
}
}

View file

@ -3,10 +3,10 @@ import WidgetKit
extension Image {
@ViewBuilder
func tintedWidgetImageModifier() -> some View {
func tintedWidgetImageModifier(forceFullColor: Bool) -> some View {
if #available(iOS 18.0, *) {
self
.widgetAccentedRenderingMode(.accentedDesaturated)
.widgetAccentedRenderingMode(forceFullColor ? .accentedDesaturated : .fullColor)
} else {
self
}
@ -18,15 +18,24 @@ struct ImmichWidgetView: View {
var body: some View {
if let image = entry.image {
ImmichWidgetContentView(image: image, subtitle: entry.metadata.subtitle, deepLink: entry.metadata.deepLink)
ImmichWidgetContentView(
image: image,
subtitle: entry.metadata.subtitle,
deepLink: entry.metadata.deepLink,
forceFullColor: entry.metadata.forceFullColor
)
} else {
ImmichWidgetLoadingView(message: entry.metadata.error?.errorDescription)
ImmichWidgetLoadingView(
message: entry.metadata.error?.errorDescription,
forceFullColor: entry.metadata.forceFullColor
)
}
}
}
private struct ImmichWidgetLoadingView: View {
let message: String?
let forceFullColor: Bool
var body: some View {
let messageText = Text(message ?? "")
@ -39,7 +48,7 @@ private struct ImmichWidgetLoadingView: View {
messageText.hidden()
Image("LaunchImage")
.tintedWidgetImageModifier()
.tintedWidgetImageModifier(forceFullColor: forceFullColor)
messageText
}
@ -50,13 +59,14 @@ private struct ImmichWidgetContentView: View {
let image: UIImage
let subtitle: String?
let deepLink: URL?
let forceFullColor: Bool
var body: some View {
ZStack(alignment: .leading) {
Color.clear.overlay(
Image(uiImage: image)
.resizable()
.tintedWidgetImageModifier()
.tintedWidgetImageModifier(forceFullColor: forceFullColor)
.scaledToFill()
)

View file

@ -2,7 +2,21 @@ import AppIntents
import SwiftUI
import WidgetKit
struct ImmichMemoryProvider: TimelineProvider {
// MARK: Widget Configuration
struct MemoryConfigurationAppIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource { "Memories" }
static var description: IntentDescription {
"See memories from Immich."
}
@Parameter(title: "Always Display in Full Color", default: false)
var forceFullColor: Bool
}
// MARK: Provider
struct ImmichMemoryProvider: AppIntentTimelineProvider {
func getYearDifferenceSubtitle(assetYear: Int) -> String {
let currentYear = Calendar.current.component(.year, from: Date.now)
// construct a "X years ago" subtitle
@ -15,144 +29,132 @@ struct ImmichMemoryProvider: TimelineProvider {
ImageEntry(date: Date(), image: nil)
}
func getSnapshot(
in context: Context,
completion: @escaping @Sendable (ImageEntry) -> Void
) {
func snapshot(
for configuration: MemoryConfigurationAppIntent,
in context: Context
) async -> ImageEntry {
let cacheKey = "memory_\(context.family.rawValue)"
Task {
guard let api = try? await ImmichAPI() else {
completion(
ImageEntry.handleError(for: cacheKey, error: .noLogin).entries.first!
)
return
}
guard let memories = try? await api.fetchMemory(for: Date.now)
else {
completion(ImageEntry.handleError(for: cacheKey).entries.first!)
return
}
for memory in memories {
if let asset = memory.assets.first(where: { $0.type == .image }),
let entry = try? await ImageEntry.build(
api: api,
asset: asset,
dateOffset: 0,
subtitle: getYearDifferenceSubtitle(assetYear: memory.data.year)
)
{
completion(entry)
return
}
}
// fallback to random image
guard
let randomImage = try? await api.fetchSearchResults().first,
let imageEntry = try? await ImageEntry.build(
api: api,
asset: randomImage,
dateOffset: 0
)
else {
completion(ImageEntry.handleError(for: cacheKey).entries.first!)
return
}
completion(imageEntry)
guard let api = try? await ImmichAPI() else {
return ImageEntry.handleError(for: cacheKey, error: .noLogin).entries
.first!
}
guard let memories = try? await api.fetchMemory(for: Date.now) else {
return ImageEntry.handleError(for: cacheKey).entries.first!
}
for memory in memories {
if let asset = memory.assets.first(where: { $0.type == .image }),
let entry = try? await ImageEntry.build(
api: api,
asset: asset,
dateOffset: 0,
subtitle: getYearDifferenceSubtitle(assetYear: memory.data.year),
forceFullColor: configuration.forceFullColor
)
{
return entry
}
}
// fallback to random image
guard
let randomImage = try? await api.fetchSearchResults().first,
let imageEntry = try? await ImageEntry.build(
api: api,
asset: randomImage,
dateOffset: 0,
forceFullColor: configuration.forceFullColor
)
else {
return ImageEntry.handleError(for: cacheKey).entries.first!
}
return imageEntry
}
func getTimeline(
in context: Context,
completion: @escaping @Sendable (Timeline<ImageEntry>) -> Void
) {
Task {
var entries: [ImageEntry] = []
let now = Date()
func timeline(
for configuration: MemoryConfigurationAppIntent,
in context: Context
) async
-> Timeline<ImageEntry>
{
var entries: [ImageEntry] = []
let now = Date()
let cacheKey = "memory_\(context.family.rawValue)"
let cacheKey = "memory_\(context.family.rawValue)"
guard let api = try? await ImmichAPI() else {
completion(
ImageEntry.handleError(for: cacheKey, error: .noLogin)
)
return
}
let memories: [MemoryResult]
do {
memories = try await api.fetchMemory(for: Date.now)
await withTaskGroup(of: ImageEntry?.self) { group in
var totalAssets = 0
for memory in memories {
for asset in memory.assets {
if asset.type == .image && totalAssets < 12 {
group.addTask {
try? await ImageEntry.build(
api: api,
asset: asset,
dateOffset: totalAssets,
subtitle: getYearDifferenceSubtitle(
assetYear: memory.data.year
)
)
}
totalAssets += 1
}
}
}
for await result in group {
if let entry = result {
entries.append(entry)
}
}
}
} catch {
completion(ImageEntry.handleError(for: cacheKey))
return
}
// If we didn't add any memory images (some failure occurred or no images in memory),
// default to 12 hours of random photos
if entries.count == 0 {
// this must be a do/catch since we need to
// distinguish between a network fail and an empty search
do {
let search = try await generateRandomEntries(
api: api,
now: now,
count: 12
)
// Load or save a cached asset for when network conditions are bad
if search.count == 0 {
completion(
ImageEntry.handleError(for: cacheKey, error: .noAssetsAvailable)
)
return
}
entries.append(contentsOf: search)
} catch {
completion(ImageEntry.handleError(for: cacheKey))
return
}
}
// cache the last image
try? entries.last!.cache(for: cacheKey)
completion(Timeline(entries: entries, policy: .atEnd))
guard let api = try? await ImmichAPI() else {
return ImageEntry.handleError(for: cacheKey, error: .noLogin)
}
let memories: [MemoryResult]
do {
memories = try await api.fetchMemory(for: Date.now)
await withTaskGroup(of: ImageEntry?.self) { group in
var totalAssets = 0
for memory in memories {
for asset in memory.assets {
if asset.type == .image && totalAssets < 12 {
group.addTask {
try? await ImageEntry.build(
api: api,
asset: asset,
dateOffset: totalAssets,
subtitle: getYearDifferenceSubtitle(
assetYear: memory.data.year
),
forceFullColor: configuration.forceFullColor
)
}
totalAssets += 1
}
}
}
for await result in group {
if let entry = result {
entries.append(entry)
}
}
}
} catch {
return ImageEntry.handleError(for: cacheKey)
}
// If we didn't add any memory images (some failure occurred or no images in memory),
// default to 12 hours of random photos
if entries.count == 0 {
// this must be a do/catch since we need to
// distinguish between a network fail and an empty search
do {
let search = try await generateRandomEntries(
api: api,
now: now,
count: 12,
forceFullColor: configuration.forceFullColor
)
// Load or save a cached asset for when network conditions are bad
if search.count == 0 {
return ImageEntry.handleError(for: cacheKey, error: .noAssetsAvailable)
}
entries.append(contentsOf: search)
} catch {
return ImageEntry.handleError(for: cacheKey)
}
}
// cache the last image
try? entries.last!.cache(for: cacheKey)
return Timeline(entries: entries, policy: .atEnd)
}
}
@ -160,8 +162,9 @@ struct ImmichMemoryWidget: Widget {
let kind: String = "com.immich.widget.memory"
var body: some WidgetConfiguration {
StaticConfiguration(
AppIntentConfiguration(
kind: kind,
intent: MemoryConfigurationAppIntent.self,
provider: ImmichMemoryProvider()
) { entry in
ImmichWidgetView(entry: entry)

View file

@ -47,6 +47,9 @@ struct RandomConfigurationAppIntent: WidgetConfigurationIntent {
@Parameter(title: "Show Album Name", default: false)
var showAlbumName: Bool
@Parameter(title: "Always Display in Full Color", default: false)
var forceFullColor: Bool
}
// MARK: Provider
@ -76,7 +79,8 @@ struct ImmichRandomProvider: AppIntentTimelineProvider {
let entry = try? await ImageEntry.build(
api: api,
asset: randomImage,
dateOffset: 0
dateOffset: 0,
forceFullColor: configuration.forceFullColor
)
else {
return ImageEntry.handleError(for: cacheKey).entries.first!
@ -114,7 +118,8 @@ struct ImmichRandomProvider: AppIntentTimelineProvider {
now: now,
count: 12,
filter: album.filter,
subtitle: configuration.showAlbumName ? albumName : nil
subtitle: configuration.showAlbumName ? albumName : nil,
forceFullColor: configuration.forceFullColor
)
// Load or save a cached asset for when network conditions are bad

View file

@ -42,8 +42,8 @@ class CropAspectRatio {
}
}
const aspectRatioFree = CropAspectRatio(customLabel: "Free", icon: Icons.crop_free);
const aspectRatioOriginal = CropAspectRatio(customLabel: "Original", icon: Icons.crop_original);
const aspectRatioFree = CropAspectRatio.free;
const aspectRatioOriginal = CropAspectRatio.original;
final aspectRatioPresets = [
CropAspectRatio.free,

View file

@ -7,7 +7,7 @@ const Map<String, Locale> locales = {
'Arabic (ar)': Locale('ar'),
'Basque (eu)': Locale('eu'),
'Belarusian (be)': Locale('be'),
'Bosnian (bl)': Locale('bn'),
'Bengali (bn)': Locale('bn'),
'Brazilian Portuguese (pt_BR)': Locale('pt', 'BR'),
'Bulgarian (bg)': Locale('bg'),
'Catalan (ca)': Locale('ca'),

View file

@ -1,3 +1,8 @@
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'album.model.freezed.dart';
enum AlbumAssetOrder {
// do not change this order!
asc,
@ -12,118 +17,20 @@ enum AlbumUserRole {
}
// Model for an album stored in the server
class RemoteAlbum {
final String id;
final String name;
final String ownerId;
final String description;
final DateTime createdAt;
final DateTime updatedAt;
final String? thumbnailAssetId;
final bool isActivityEnabled;
final AlbumAssetOrder order;
final int assetCount;
final String ownerName;
final bool isShared;
const RemoteAlbum({
required this.id,
required this.name,
required this.ownerId,
required this.description,
required this.createdAt,
required this.updatedAt,
this.thumbnailAssetId,
required this.isActivityEnabled,
required this.order,
required this.assetCount,
required this.ownerName,
required this.isShared,
});
@override
String toString() {
return '''Album {
id: $id,
name: $name,
ownerId: $ownerId,
description: $description,
createdAt: $createdAt,
updatedAt: $updatedAt,
isActivityEnabled: $isActivityEnabled,
order: $order,
thumbnailAssetId: ${thumbnailAssetId ?? "<NA>"}
assetCount: $assetCount
ownerName: $ownerName
isShared: $isShared
}''';
}
@override
bool operator ==(Object other) {
if (other is! RemoteAlbum) {
return false;
}
if (identical(this, other)) {
return true;
}
return id == other.id &&
name == other.name &&
ownerId == other.ownerId &&
description == other.description &&
createdAt == other.createdAt &&
updatedAt == other.updatedAt &&
thumbnailAssetId == other.thumbnailAssetId &&
isActivityEnabled == other.isActivityEnabled &&
order == other.order &&
assetCount == other.assetCount &&
ownerName == other.ownerName &&
isShared == other.isShared;
}
@override
int get hashCode {
return id.hashCode ^
name.hashCode ^
ownerId.hashCode ^
description.hashCode ^
createdAt.hashCode ^
updatedAt.hashCode ^
thumbnailAssetId.hashCode ^
isActivityEnabled.hashCode ^
order.hashCode ^
assetCount.hashCode ^
ownerName.hashCode ^
isShared.hashCode;
}
RemoteAlbum copyWith({
String? id,
String? name,
String? ownerId,
String? description,
DateTime? createdAt,
DateTime? updatedAt,
@freezed
abstract class RemoteAlbum with _$RemoteAlbum {
const factory RemoteAlbum({
required String id,
required String name,
required String ownerId,
required String description,
required DateTime createdAt,
required DateTime updatedAt,
String? thumbnailAssetId,
bool? isActivityEnabled,
AlbumAssetOrder? order,
int? assetCount,
String? ownerName,
bool? isShared,
}) {
return RemoteAlbum(
id: id ?? this.id,
name: name ?? this.name,
ownerId: ownerId ?? this.ownerId,
description: description ?? this.description,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId,
isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled,
order: order ?? this.order,
assetCount: assetCount ?? this.assetCount,
ownerName: ownerName ?? this.ownerName,
isShared: isShared ?? this.isShared,
);
}
required bool isActivityEnabled,
required AlbumAssetOrder order,
required int assetCount,
required String ownerName,
required bool isShared,
}) = _RemoteAlbum;
}

View file

@ -1,3 +1,8 @@
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'local_album.model.freezed.dart';
enum BackupSelection {
// Used to sort albums based on the backupSelection
// selected -> none -> excluded
@ -7,85 +12,15 @@ enum BackupSelection {
excluded,
}
class LocalAlbum {
final String id;
final String name;
final DateTime updatedAt;
final bool isIosSharedAlbum;
final int assetCount;
final BackupSelection backupSelection;
final String? linkedRemoteAlbumId;
const LocalAlbum({
required this.id,
required this.name,
required this.updatedAt,
this.assetCount = 0,
this.backupSelection = BackupSelection.none,
this.isIosSharedAlbum = false,
this.linkedRemoteAlbumId,
});
LocalAlbum copyWith({
String? id,
String? name,
DateTime? updatedAt,
int? assetCount,
BackupSelection? backupSelection,
bool? isIosSharedAlbum,
@freezed
abstract class LocalAlbum with _$LocalAlbum {
const factory LocalAlbum({
required String id,
required String name,
required DateTime updatedAt,
@Default(0) int assetCount,
@Default(BackupSelection.none) BackupSelection backupSelection,
@Default(false) bool isIosSharedAlbum,
String? linkedRemoteAlbumId,
}) {
return LocalAlbum(
id: id ?? this.id,
name: name ?? this.name,
updatedAt: updatedAt ?? this.updatedAt,
assetCount: assetCount ?? this.assetCount,
backupSelection: backupSelection ?? this.backupSelection,
isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum,
linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId,
);
}
@override
bool operator ==(Object other) {
if (other is! LocalAlbum) {
return false;
}
if (identical(this, other)) {
return true;
}
return other.id == id &&
other.name == name &&
other.updatedAt == updatedAt &&
other.assetCount == assetCount &&
other.backupSelection == backupSelection &&
other.isIosSharedAlbum == isIosSharedAlbum &&
other.linkedRemoteAlbumId == linkedRemoteAlbumId;
}
@override
int get hashCode {
return id.hashCode ^
name.hashCode ^
updatedAt.hashCode ^
assetCount.hashCode ^
backupSelection.hashCode ^
isIosSharedAlbum.hashCode ^
linkedRemoteAlbumId.hashCode;
}
@override
String toString() {
return '''LocalAlbum: {
id: $id,
name: $name,
updatedAt: $updatedAt,
assetCount: $assetCount,
backupSelection: $backupSelection,
isIosSharedAlbum: $isIosSharedAlbum
linkedRemoteAlbumId: $linkedRemoteAlbumId,
}''';
}
}) = _LocalAlbum;
}

View file

@ -53,7 +53,7 @@ sealed class BaseAsset {
if (durationMs != null) {
return Duration(milliseconds: durationMs);
}
return const Duration();
return Duration.zero;
}
bool get hasRemote => storage == AssetState.remote || storage == AssetState.merged;

View file

@ -1,4 +1,4 @@
import "package:openapi/api.dart" show CropParameters, RotateParameters, MirrorParameters;
import "package:openapi/api.dart" show CropParameters, MirrorParameters, RotateParameters;
enum AssetEditAction { rotate, crop, mirror, other }

View file

@ -1,100 +1,21 @@
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'asset_face.model.freezed.dart';
// Model for an asset face stored in the server
class AssetFace {
final String id;
final String assetId;
final String? personId;
final int imageWidth;
final int imageHeight;
final int boundingBoxX1;
final int boundingBoxY1;
final int boundingBoxX2;
final int boundingBoxY2;
final String sourceType;
const AssetFace({
required this.id,
required this.assetId,
this.personId,
required this.imageWidth,
required this.imageHeight,
required this.boundingBoxX1,
required this.boundingBoxY1,
required this.boundingBoxX2,
required this.boundingBoxY2,
required this.sourceType,
});
AssetFace copyWith({
String? id,
String? assetId,
@freezed
abstract class AssetFace with _$AssetFace {
const factory AssetFace({
required String id,
required String assetId,
String? personId,
int? imageWidth,
int? imageHeight,
int? boundingBoxX1,
int? boundingBoxY1,
int? boundingBoxX2,
int? boundingBoxY2,
String? sourceType,
}) {
return AssetFace(
id: id ?? this.id,
assetId: assetId ?? this.assetId,
personId: personId ?? this.personId,
imageWidth: imageWidth ?? this.imageWidth,
imageHeight: imageHeight ?? this.imageHeight,
boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1,
boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1,
boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2,
boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2,
sourceType: sourceType ?? this.sourceType,
);
}
@override
String toString() {
return '''AssetFace {
id: $id,
assetId: $assetId,
personId: ${personId ?? "<NA>"},
imageWidth: $imageWidth,
imageHeight: $imageHeight,
boundingBoxX1: $boundingBoxX1,
boundingBoxY1: $boundingBoxY1,
boundingBoxX2: $boundingBoxX2,
boundingBoxY2: $boundingBoxY2,
sourceType: $sourceType,
}''';
}
@override
bool operator ==(covariant AssetFace other) {
if (identical(this, other)) {
return true;
}
return other.id == id &&
other.assetId == assetId &&
other.personId == personId &&
other.imageWidth == imageWidth &&
other.imageHeight == imageHeight &&
other.boundingBoxX1 == boundingBoxX1 &&
other.boundingBoxY1 == boundingBoxY1 &&
other.boundingBoxX2 == boundingBoxX2 &&
other.boundingBoxY2 == boundingBoxY2 &&
other.sourceType == sourceType;
}
@override
int get hashCode {
return id.hashCode ^
assetId.hashCode ^
personId.hashCode ^
imageWidth.hashCode ^
imageHeight.hashCode ^
boundingBoxX1.hashCode ^
boundingBoxY1.hashCode ^
boundingBoxX2.hashCode ^
boundingBoxY2.hashCode ^
sourceType.hashCode;
}
required int imageWidth,
required int imageHeight,
required int boundingBoxX1,
required int boundingBoxY1,
required int boundingBoxX2,
required int boundingBoxY2,
required String sourceType,
}) = _AssetFace;
}

View file

@ -1,26 +1,14 @@
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
class AlbumConfig {
final AlbumSortMode sortMode;
final bool isReverse;
final bool isGrid;
part 'album_config.freezed.dart';
const AlbumConfig({this.sortMode = AlbumSortMode.mostRecent, this.isReverse = true, this.isGrid = false});
AlbumConfig copyWith({AlbumSortMode? sortMode, bool? isReverse, bool? isGrid}) => AlbumConfig(
sortMode: sortMode ?? this.sortMode,
isReverse: isReverse ?? this.isReverse,
isGrid: isGrid ?? this.isGrid,
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is AlbumConfig && other.sortMode == sortMode && other.isReverse == isReverse && other.isGrid == isGrid);
@override
int get hashCode => Object.hash(sortMode, isReverse, isGrid);
@override
String toString() => 'AlbumConfig(sortMode: $sortMode, isReverse: $isReverse, isGrid: $isGrid)';
@freezed
abstract class AlbumConfig with _$AlbumConfig {
const factory AlbumConfig({
@Default(AlbumSortMode.mostRecent) AlbumSortMode sortMode,
@Default(true) bool isReverse,
@Default(false) bool isGrid,
}) = _AlbumConfig;
}

View file

@ -1,4 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:immich_mobile/constants/colors.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/config/album_config.dart';
@ -19,107 +21,29 @@ import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
import 'package:immich_mobile/utils/semver.dart';
part 'app_config.freezed.dart';
const defaultConfig = AppConfig();
class AppConfig {
final LogLevel logLevel;
final ThemeConfig theme;
final CleanupConfig cleanup;
final MapConfig map;
final TimelineConfig timeline;
final ImageConfig image;
final ViewerConfig viewer;
final SlideshowConfig slideshow;
final AlbumConfig album;
final BackupConfig backup;
final NetworkConfig network;
final ShareConfig share;
final FeatureMessageConfig featureMessage;
@freezed
abstract class AppConfig with _$AppConfig {
const AppConfig._();
const AppConfig({
this.logLevel = .info,
this.theme = const .new(),
this.cleanup = const .new(),
this.map = const .new(),
this.timeline = const .new(),
this.image = const .new(),
this.viewer = const .new(),
this.slideshow = const .new(),
this.album = const .new(),
this.backup = const .new(),
this.network = const .new(),
this.share = const .new(),
this.featureMessage = const .new(),
});
AppConfig copyWith({
LogLevel? logLevel,
ThemeConfig? theme,
CleanupConfig? cleanup,
MapConfig? map,
TimelineConfig? timeline,
ImageConfig? image,
ViewerConfig? viewer,
SlideshowConfig? slideshow,
AlbumConfig? album,
BackupConfig? backup,
NetworkConfig? network,
ShareConfig? share,
FeatureMessageConfig? featureMessage,
}) => .new(
logLevel: logLevel ?? this.logLevel,
theme: theme ?? this.theme,
cleanup: cleanup ?? this.cleanup,
map: map ?? this.map,
timeline: timeline ?? this.timeline,
image: image ?? this.image,
viewer: viewer ?? this.viewer,
slideshow: slideshow ?? this.slideshow,
album: album ?? this.album,
backup: backup ?? this.backup,
network: network ?? this.network,
share: share ?? this.share,
featureMessage: featureMessage ?? this.featureMessage,
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is AppConfig &&
other.logLevel == logLevel &&
other.theme == theme &&
other.cleanup == cleanup &&
other.map == map &&
other.timeline == timeline &&
other.image == image &&
other.viewer == viewer &&
other.slideshow == slideshow &&
other.album == album &&
other.backup == backup &&
other.network == network &&
other.share == share &&
other.featureMessage == featureMessage);
@override
int get hashCode => Object.hash(
logLevel,
theme,
cleanup,
map,
timeline,
image,
viewer,
slideshow,
album,
backup,
network,
share,
featureMessage,
);
@override
String toString() =>
'AppConfig(logLevel: $logLevel, theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow, album: $album, backup: $backup, network: $network, share: $share, featureMessage: $featureMessage)';
const factory AppConfig({
@Default(LogLevel.info) LogLevel logLevel,
@Default(ThemeConfig()) ThemeConfig theme,
@Default(CleanupConfig()) CleanupConfig cleanup,
@Default(MapConfig()) MapConfig map,
@Default(TimelineConfig()) TimelineConfig timeline,
@Default(ImageConfig()) ImageConfig image,
@Default(ViewerConfig()) ViewerConfig viewer,
@Default(SlideshowConfig()) SlideshowConfig slideshow,
@Default(AlbumConfig()) AlbumConfig album,
@Default(BackupConfig()) BackupConfig backup,
@Default(NetworkConfig()) NetworkConfig network,
@Default(ShareConfig()) ShareConfig share,
@Default(FeatureMessageConfig()) FeatureMessageConfig featureMessage,
}) = _AppConfig;
T read<T>(SettingsKey<T> key) =>
(switch (key) {
@ -190,9 +114,9 @@ class AppConfig {
.viewerTapToNavigate => copyWith(viewer: viewer.copyWith(tapToNavigate: value as bool)),
.networkAutoEndpointSwitching => copyWith(network: network.copyWith(autoEndpointSwitching: value as bool)),
.networkPreferredWifiName => copyWith(
network: network.copyWith(preferredWifiName: .fromNullable((value as String?))),
network: network.copyWith(preferredWifiName: .fromNullable(value as String?)),
),
.networkLocalEndpoint => copyWith(network: network.copyWith(localEndpoint: .fromNullable((value as String?)))),
.networkLocalEndpoint => copyWith(network: network.copyWith(localEndpoint: .fromNullable(value as String?))),
.networkExternalEndpointList => copyWith(network: network.copyWith(externalEndpointList: value as List<String>)),
.networkCustomHeaders => copyWith(network: network.copyWith(customHeaders: value as Map<String, String>)),
.albumSortMode => copyWith(album: album.copyWith(sortMode: value as AlbumSortMode)),

View file

@ -1,52 +1,16 @@
class BackupConfig {
final bool enabled;
final bool useCellularForVideos;
final bool useCellularForPhotos;
final bool requireCharging;
final int triggerDelay;
final bool syncAlbums;
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
const BackupConfig({
this.enabled = false,
this.useCellularForVideos = false,
this.useCellularForPhotos = false,
this.requireCharging = false,
this.triggerDelay = 30,
this.syncAlbums = false,
});
part 'backup_config.freezed.dart';
BackupConfig copyWith({
bool? enabled,
bool? useCellularForVideos,
bool? useCellularForPhotos,
bool? requireCharging,
int? triggerDelay,
bool? syncAlbums,
}) => BackupConfig(
enabled: enabled ?? this.enabled,
useCellularForVideos: useCellularForVideos ?? this.useCellularForVideos,
useCellularForPhotos: useCellularForPhotos ?? this.useCellularForPhotos,
requireCharging: requireCharging ?? this.requireCharging,
triggerDelay: triggerDelay ?? this.triggerDelay,
syncAlbums: syncAlbums ?? this.syncAlbums,
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is BackupConfig &&
other.enabled == enabled &&
other.useCellularForVideos == useCellularForVideos &&
other.useCellularForPhotos == useCellularForPhotos &&
other.requireCharging == requireCharging &&
other.triggerDelay == triggerDelay &&
other.syncAlbums == syncAlbums);
@override
int get hashCode =>
Object.hash(enabled, useCellularForVideos, useCellularForPhotos, requireCharging, triggerDelay, syncAlbums);
@override
String toString() =>
'BackupConfig(enabled: $enabled, useCellularForVideos: $useCellularForVideos, useCellularForPhotos: $useCellularForPhotos, requireCharging: $requireCharging, triggerDelay: $triggerDelay, syncAlbums: $syncAlbums)';
@freezed
abstract class BackupConfig with _$BackupConfig {
const factory BackupConfig({
@Default(false) bool enabled,
@Default(false) bool useCellularForVideos,
@Default(false) bool useCellularForPhotos,
@Default(false) bool requireCharging,
@Default(30) int triggerDelay,
@Default(false) bool syncAlbums,
}) = _BackupConfig;
}

View file

@ -1,20 +1,11 @@
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:immich_mobile/utils/semver.dart';
class FeatureMessageConfig {
final SemVer seenRelease;
part 'feature_message_config.freezed.dart';
const FeatureMessageConfig({this.seenRelease = const SemVer(major: 0, minor: 0, patch: 0)});
FeatureMessageConfig copyWith({SemVer? seenRelease}) =>
FeatureMessageConfig(seenRelease: seenRelease ?? this.seenRelease);
@override
bool operator ==(Object other) =>
identical(this, other) || (other is FeatureMessageConfig && other.seenRelease == seenRelease);
@override
int get hashCode => seenRelease.hashCode;
@override
String toString() => 'FeatureMessageConfig(seenRelease: $seenRelease)';
@freezed
abstract class FeatureMessageConfig with _$FeatureMessageConfig {
const factory FeatureMessageConfig({@Default(SemVer(major: 0, minor: 0, patch: 0)) SemVer seenRelease}) =
_FeatureMessageConfig;
}

View file

@ -1,20 +1,9 @@
class ImageConfig {
final bool preferRemote;
final bool loadOriginal;
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
const ImageConfig({this.preferRemote = false, this.loadOriginal = false});
part 'image_config.freezed.dart';
ImageConfig copyWith({bool? preferRemote, bool? loadOriginal}) =>
ImageConfig(preferRemote: preferRemote ?? this.preferRemote, loadOriginal: loadOriginal ?? this.loadOriginal);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is ImageConfig && other.preferRemote == preferRemote && other.loadOriginal == loadOriginal);
@override
int get hashCode => Object.hash(preferRemote, loadOriginal);
@override
String toString() => 'ImageConfig(preferRemoteImage: $preferRemote, loadOriginal: $loadOriginal)';
@freezed
abstract class ImageConfig with _$ImageConfig {
const factory ImageConfig({@Default(false) bool preferRemote, @Default(false) bool loadOriginal}) = _ImageConfig;
}

View file

@ -1,18 +1,10 @@
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:immich_mobile/constants/enums.dart';
class ShareConfig {
final ShareAssetType fileType;
part 'share_config.freezed.dart';
const ShareConfig({this.fileType = ShareAssetType.original});
ShareConfig copyWith({ShareAssetType? fileType}) => ShareConfig(fileType: fileType ?? this.fileType);
@override
bool operator ==(Object other) => identical(this, other) || (other is ShareConfig && other.fileType == fileType);
@override
int get hashCode => fileType.hashCode;
@override
String toString() => 'ShareConfig(fileType: $fileType)';
@freezed
abstract class ShareConfig with _$ShareConfig {
const factory ShareConfig({@Default(ShareAssetType.original) ShareAssetType fileType}) = _ShareConfig;
}

View file

@ -1,38 +1,15 @@
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:immich_mobile/constants/enums.dart';
class SlideshowConfig {
final bool repeat;
final int duration;
final SlideshowLook look;
final SlideshowDirection direction;
part 'slideshow_config.freezed.dart';
const SlideshowConfig({
this.repeat = true,
this.duration = 5,
this.look = SlideshowLook.blurredBackground,
this.direction = SlideshowDirection.forward,
});
SlideshowConfig copyWith({bool? repeat, int? duration, SlideshowLook? look, SlideshowDirection? direction}) =>
SlideshowConfig(
repeat: repeat ?? this.repeat,
duration: duration ?? this.duration,
look: look ?? this.look,
direction: direction ?? this.direction,
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is SlideshowConfig &&
other.repeat == repeat &&
other.duration == duration &&
other.look == look &&
other.direction == direction);
@override
int get hashCode => Object.hash(repeat, duration, look, direction);
@override
String toString() => 'SlideshowConfig(repeat: $repeat, duration: $duration, look: $look, direction: $direction)';
@freezed
abstract class SlideshowConfig with _$SlideshowConfig {
const factory SlideshowConfig({
@Default(true) bool repeat,
@Default(5) int duration,
@Default(SlideshowLook.blurredBackground) SlideshowLook look,
@Default(SlideshowDirection.forward) SlideshowDirection direction,
}) = _SlideshowConfig;
}

View file

@ -1,44 +1,16 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:immich_mobile/constants/colors.dart';
class ThemeConfig {
final ThemeMode mode;
final ImmichColorPreset primaryColor;
final bool dynamicTheme;
final bool colorfulInterface;
part 'theme_config.freezed.dart';
const ThemeConfig({
this.mode = .system,
this.primaryColor = .indigo,
this.dynamicTheme = false,
this.colorfulInterface = true,
});
ThemeConfig copyWith({
ThemeMode? mode,
ImmichColorPreset? primaryColor,
bool? dynamicTheme,
bool? colorfulInterface,
}) => .new(
mode: mode ?? this.mode,
primaryColor: primaryColor ?? this.primaryColor,
dynamicTheme: dynamicTheme ?? this.dynamicTheme,
colorfulInterface: colorfulInterface ?? this.colorfulInterface,
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is ThemeConfig &&
other.mode == mode &&
other.primaryColor == primaryColor &&
other.dynamicTheme == dynamicTheme &&
other.colorfulInterface == colorfulInterface);
@override
int get hashCode => Object.hash(mode, primaryColor, dynamicTheme, colorfulInterface);
@override
String toString() =>
'ThemeConfig(mode: $mode, primaryColor: $primaryColor, dynamicTheme: $dynamicTheme, colorfulInterface: $colorfulInterface)';
@freezed
abstract class ThemeConfig with _$ThemeConfig {
const factory ThemeConfig({
@Default(ThemeMode.system) ThemeMode mode,
@Default(ImmichColorPreset.indigo) ImmichColorPreset primaryColor,
@Default(false) bool dynamicTheme,
@Default(true) bool colorfulInterface,
}) = _ThemeConfig;
}

View file

@ -1,30 +1,14 @@
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
class TimelineConfig {
final int tilesPerRow;
final GroupAssetsBy groupAssetsBy;
final bool storageIndicator;
part 'timeline_config.freezed.dart';
const TimelineConfig({this.tilesPerRow = 4, this.groupAssetsBy = GroupAssetsBy.day, this.storageIndicator = true});
TimelineConfig copyWith({int? tilesPerRow, GroupAssetsBy? groupAssetsBy, bool? storageIndicator}) => TimelineConfig(
tilesPerRow: tilesPerRow ?? this.tilesPerRow,
groupAssetsBy: groupAssetsBy ?? this.groupAssetsBy,
storageIndicator: storageIndicator ?? this.storageIndicator,
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is TimelineConfig &&
other.tilesPerRow == tilesPerRow &&
other.groupAssetsBy == groupAssetsBy &&
other.storageIndicator == storageIndicator);
@override
int get hashCode => Object.hash(tilesPerRow, groupAssetsBy, storageIndicator);
@override
String toString() =>
'TimelineConfig(tilesPerRow: $tilesPerRow, groupAssetsBy: $groupAssetsBy, storageIndicator: $storageIndicator)';
@freezed
abstract class TimelineConfig with _$TimelineConfig {
const factory TimelineConfig({
@Default(4) int tilesPerRow,
@Default(GroupAssetsBy.day) GroupAssetsBy groupAssetsBy,
@Default(true) bool storageIndicator,
}) = _TimelineConfig;
}

View file

@ -1,37 +1,14 @@
class ViewerConfig {
final bool loopVideo;
final bool loadOriginalVideo;
final bool autoPlayVideo;
final bool tapToNavigate;
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
const ViewerConfig({
this.loopVideo = true,
this.loadOriginalVideo = false,
this.autoPlayVideo = true,
this.tapToNavigate = false,
});
part 'viewer_config.freezed.dart';
ViewerConfig copyWith({bool? loopVideo, bool? loadOriginalVideo, bool? autoPlayVideo, bool? tapToNavigate}) =>
ViewerConfig(
loopVideo: loopVideo ?? this.loopVideo,
loadOriginalVideo: loadOriginalVideo ?? this.loadOriginalVideo,
autoPlayVideo: autoPlayVideo ?? this.autoPlayVideo,
tapToNavigate: tapToNavigate ?? this.tapToNavigate,
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is ViewerConfig &&
other.loopVideo == loopVideo &&
other.loadOriginalVideo == loadOriginalVideo &&
other.autoPlayVideo == autoPlayVideo &&
other.tapToNavigate == tapToNavigate);
@override
int get hashCode => Object.hash(loopVideo, loadOriginalVideo, autoPlayVideo, tapToNavigate);
@override
String toString() =>
'ViewerConfig(loopVideo: $loopVideo, loadOriginalVideo: $loadOriginalVideo, autoPlayVideo: $autoPlayVideo, tapToNavigate: $tapToNavigate)';
@freezed
abstract class ViewerConfig with _$ViewerConfig {
const factory ViewerConfig({
@Default(true) bool loopVideo,
@Default(false) bool loadOriginalVideo,
@Default(true) bool autoPlayVideo,
@Default(false) bool tapToNavigate,
}) = _ViewerConfig;
}

View file

@ -1,30 +1,40 @@
class ExifInfo {
final int? assetId;
final int? fileSize;
final String? description;
final bool isFlipped;
final String? orientation;
final String? timeZone;
final DateTime? dateTimeOriginal;
final int? rating;
final int? width;
final int? height;
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
// GPS
final double? latitude;
final double? longitude;
final String? city;
final String? state;
final String? country;
part 'exif.model.freezed.dart';
// Camera related
final String? make;
final String? model;
final String? lens;
final double? f;
final double? mm;
final int? iso;
final double? exposureSeconds;
@freezed
abstract class ExifInfo with _$ExifInfo {
const ExifInfo._();
const factory ExifInfo({
int? assetId,
int? fileSize,
String? description,
@Default(false) bool isFlipped,
String? orientation,
String? timeZone,
DateTime? dateTimeOriginal,
int? rating,
int? width,
int? height,
// GPS
double? latitude,
double? longitude,
String? city,
String? state,
String? country,
// Camera related
String? make,
String? model,
String? lens,
double? f,
double? mm,
int? iso,
double? exposureSeconds,
}) = _ExifInfo;
bool get hasCoordinates => latitude != null && longitude != null && latitude != 0 && longitude != 0;
@ -41,162 +51,4 @@ class ExifInfo {
String get fNumber => f == null ? "" : f!.toStringAsFixed(1);
String get focalLength => mm == null ? "" : mm!.toStringAsFixed(3);
const ExifInfo({
this.assetId,
this.fileSize,
this.description,
this.orientation,
this.timeZone,
this.dateTimeOriginal,
this.rating,
this.width,
this.height,
this.isFlipped = false,
this.latitude,
this.longitude,
this.city,
this.state,
this.country,
this.make,
this.model,
this.lens,
this.f,
this.mm,
this.iso,
this.exposureSeconds,
});
@override
bool operator ==(covariant ExifInfo other) {
if (identical(this, other)) {
return true;
}
return other.fileSize == fileSize &&
other.description == description &&
other.isFlipped == isFlipped &&
other.orientation == orientation &&
other.timeZone == timeZone &&
other.dateTimeOriginal == dateTimeOriginal &&
other.rating == rating &&
other.width == width &&
other.height == height &&
other.latitude == latitude &&
other.longitude == longitude &&
other.city == city &&
other.state == state &&
other.country == country &&
other.make == make &&
other.model == model &&
other.lens == lens &&
other.f == f &&
other.mm == mm &&
other.iso == iso &&
other.exposureSeconds == exposureSeconds &&
other.assetId == assetId;
}
@override
int get hashCode {
return fileSize.hashCode ^
description.hashCode ^
orientation.hashCode ^
isFlipped.hashCode ^
timeZone.hashCode ^
dateTimeOriginal.hashCode ^
rating.hashCode ^
width.hashCode ^
height.hashCode ^
latitude.hashCode ^
longitude.hashCode ^
city.hashCode ^
state.hashCode ^
country.hashCode ^
make.hashCode ^
model.hashCode ^
lens.hashCode ^
f.hashCode ^
mm.hashCode ^
iso.hashCode ^
exposureSeconds.hashCode ^
assetId.hashCode;
}
@override
String toString() {
return '''{
fileSize: ${fileSize ?? 'NA'},
description: ${description ?? 'NA'},
orientation: ${orientation ?? 'NA'},
isFlipped: $isFlipped,
timeZone: ${timeZone ?? 'NA'},
dateTimeOriginal: ${dateTimeOriginal ?? 'NA'},
rating: ${rating ?? 'NA'},
width: ${width ?? 'NA'},
height: ${height ?? 'NA'},
latitude: ${latitude ?? 'NA'},
longitude: ${longitude ?? 'NA'},
city: ${city ?? 'NA'},
state: ${state ?? 'NA'},
country: ${country ?? '<NA>'},
make: ${make ?? 'NA'},
model: ${model ?? 'NA'},
lens: ${lens ?? 'NA'},
f: ${f ?? 'NA'},
mm: ${mm ?? '<NA>'},
iso: ${iso ?? 'NA'},
exposureSeconds: ${exposureSeconds ?? 'NA'},
}''';
}
ExifInfo copyWith({
int? assetId,
int? fileSize,
String? description,
String? orientation,
String? timeZone,
DateTime? dateTimeOriginal,
int? rating,
int? width,
int? height,
double? latitude,
double? longitude,
String? city,
String? state,
String? country,
bool? isFlipped,
String? make,
String? model,
String? lens,
double? f,
double? mm,
int? iso,
double? exposureSeconds,
}) {
return ExifInfo(
assetId: assetId ?? this.assetId,
fileSize: fileSize ?? this.fileSize,
description: description ?? this.description,
orientation: orientation ?? this.orientation,
timeZone: timeZone ?? this.timeZone,
dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal,
rating: rating ?? this.rating,
width: width ?? this.width,
height: height ?? this.height,
isFlipped: isFlipped ?? this.isFlipped,
latitude: latitude ?? this.latitude,
longitude: longitude ?? this.longitude,
city: city ?? this.city,
state: state ?? this.state,
country: country ?? this.country,
make: make ?? this.make,
model: model ?? this.model,
lens: lens ?? this.lens,
f: f ?? this.f,
mm: mm ?? this.mm,
iso: iso ?? this.iso,
exposureSeconds: exposureSeconds ?? this.exposureSeconds,
);
}
}

View file

@ -1,128 +1,24 @@
class Ocr {
final String id;
final String assetId;
final double x1;
final double y1;
final double x2;
final double y2;
final double x3;
final double y3;
final double x4;
final double y4;
final double boxScore;
final double textScore;
final String text;
final bool isVisible;
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
const Ocr({
required this.id,
required this.assetId,
required this.x1,
required this.y1,
required this.x2,
required this.y2,
required this.x3,
required this.y3,
required this.x4,
required this.y4,
required this.boxScore,
required this.textScore,
required this.text,
required this.isVisible,
});
part 'ocr.model.freezed.dart';
Ocr copyWith({
String? id,
String? assetId,
double? x1,
double? y1,
double? x2,
double? y2,
double? x3,
double? y3,
double? x4,
double? y4,
double? boxScore,
double? textScore,
String? text,
bool? isVisible,
}) {
return Ocr(
id: id ?? this.id,
assetId: assetId ?? this.assetId,
x1: x1 ?? this.x1,
y1: y1 ?? this.y1,
x2: x2 ?? this.x2,
y2: y2 ?? this.y2,
x3: x3 ?? this.x3,
y3: y3 ?? this.y3,
x4: x4 ?? this.x4,
y4: y4 ?? this.y4,
boxScore: boxScore ?? this.boxScore,
textScore: textScore ?? this.textScore,
text: text ?? this.text,
isVisible: isVisible ?? this.isVisible,
);
}
@override
String toString() {
return '''Ocr {
id: $id,
assetId: $assetId,
x1: $x1,
y1: $y1,
x2: $x2,
y2: $y2,
x3: $x3,
y3: $y3,
x4: $x4,
y4: $y4,
boxScore: $boxScore,
textScore: $textScore,
text: $text,
isVisible: $isVisible
}''';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
return other is Ocr &&
other.id == id &&
other.assetId == assetId &&
other.x1 == x1 &&
other.y1 == y1 &&
other.x2 == x2 &&
other.y2 == y2 &&
other.x3 == x3 &&
other.y3 == y3 &&
other.x4 == x4 &&
other.y4 == y4 &&
other.boxScore == boxScore &&
other.textScore == textScore &&
other.text == text &&
other.isVisible == isVisible;
}
@override
int get hashCode {
return id.hashCode ^
assetId.hashCode ^
x1.hashCode ^
y1.hashCode ^
x2.hashCode ^
y2.hashCode ^
x3.hashCode ^
y3.hashCode ^
x4.hashCode ^
y4.hashCode ^
boxScore.hashCode ^
textScore.hashCode ^
text.hashCode ^
isVisible.hashCode;
}
@freezed
abstract class Ocr with _$Ocr {
const factory Ocr({
required String id,
required String assetId,
required double x1,
required double y1,
required double x2,
required double y2,
required double x3,
required double y3,
required double x4,
required double y4,
required double boxScore,
required double textScore,
required String text,
required bool isVisible,
}) = _Ocr;
}

View file

@ -1,5 +1,10 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'person.model.freezed.dart';
// TODO: Remove PersonDto once Isar is removed
class PersonDto {
const PersonDto({
@ -93,102 +98,18 @@ class PersonDto {
}
// Model for a person stored in the server
class DriftPerson {
final String id;
final DateTime createdAt;
final DateTime updatedAt;
final String ownerId;
final String name;
final String? faceAssetId;
final bool isFavorite;
final bool isHidden;
final String? color;
final DateTime? birthDate;
const DriftPerson({
required this.id,
required this.createdAt,
required this.updatedAt,
required this.ownerId,
required this.name,
this.faceAssetId,
required this.isFavorite,
required this.isHidden,
required this.color,
this.birthDate,
});
DriftPerson copyWith({
String? id,
DateTime? createdAt,
DateTime? updatedAt,
String? ownerId,
String? name,
@freezed
abstract class DriftPerson with _$DriftPerson {
const factory DriftPerson({
required String id,
required DateTime createdAt,
required DateTime updatedAt,
required String ownerId,
required String name,
String? faceAssetId,
bool? isFavorite,
bool? isHidden,
String? color,
required bool isFavorite,
required bool isHidden,
required String? color,
DateTime? birthDate,
}) {
return DriftPerson(
id: id ?? this.id,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
ownerId: ownerId ?? this.ownerId,
name: name ?? this.name,
faceAssetId: faceAssetId ?? this.faceAssetId,
isFavorite: isFavorite ?? this.isFavorite,
isHidden: isHidden ?? this.isHidden,
color: color ?? this.color,
birthDate: birthDate ?? this.birthDate,
);
}
@override
String toString() {
return '''Person {
id: $id,
createdAt: $createdAt,
updatedAt: $updatedAt,
ownerId: $ownerId,
name: $name,
faceAssetId: ${faceAssetId ?? "<NA>"},
isFavorite: $isFavorite,
isHidden: $isHidden,
color: ${color ?? "<NA>"},
birthDate: ${birthDate ?? "<NA>"}
}''';
}
@override
bool operator ==(covariant DriftPerson other) {
if (identical(this, other)) {
return true;
}
return other.id == id &&
other.createdAt == createdAt &&
other.updatedAt == updatedAt &&
other.ownerId == ownerId &&
other.name == name &&
other.faceAssetId == faceAssetId &&
other.isFavorite == isFavorite &&
other.isHidden == isHidden &&
other.color == color &&
other.birthDate == birthDate;
}
@override
int get hashCode {
return id.hashCode ^
createdAt.hashCode ^
updatedAt.hashCode ^
ownerId.hashCode ^
name.hashCode ^
faceAssetId.hashCode ^
isFavorite.hashCode ^
isHidden.hashCode ^
color.hashCode ^
birthDate.hashCode;
}
}) = _DriftPerson;
}

View file

@ -1,57 +1,18 @@
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'stack.model.freezed.dart';
// Model for a stack stored in the server
class Stack {
final String id;
final DateTime createdAt;
final DateTime updatedAt;
final String ownerId;
final String primaryAssetId;
const Stack({
required this.id,
required this.createdAt,
required this.updatedAt,
required this.ownerId,
required this.primaryAssetId,
});
Stack copyWith({String? id, DateTime? createdAt, DateTime? updatedAt, String? ownerId, String? primaryAssetId}) {
return Stack(
id: id ?? this.id,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
ownerId: ownerId ?? this.ownerId,
primaryAssetId: primaryAssetId ?? this.primaryAssetId,
);
}
@override
String toString() {
return '''Stack {
id: $id,
createdAt: $createdAt,
updatedAt: $updatedAt,
ownerId: $ownerId,
primaryAssetId: $primaryAssetId
}''';
}
@override
bool operator ==(covariant Stack other) {
if (identical(this, other)) {
return true;
}
return other.id == id &&
other.createdAt == createdAt &&
other.updatedAt == updatedAt &&
other.ownerId == ownerId &&
other.primaryAssetId == primaryAssetId;
}
@override
int get hashCode {
return id.hashCode ^ createdAt.hashCode ^ updatedAt.hashCode ^ ownerId.hashCode ^ primaryAssetId.hashCode;
}
@freezed
abstract class Stack with _$Stack {
const factory Stack({
required String id,
required DateTime createdAt,
required DateTime updatedAt,
required String ownerId,
required String primaryAssetId,
}) = _Stack;
}
class StackResponse {

View file

@ -1,5 +1,9 @@
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
part 'user_metadata.model.freezed.dart';
enum UserMetadataKey {
// do not change this order!
onboarding,
@ -23,7 +27,7 @@ class Onboarding {
}
factory Onboarding.fromMap(Map<String, Object?> map) {
return Onboarding(isOnboarded: map["isOnboarded"] as bool);
return Onboarding(isOnboarded: map["isOnboarded"]! as bool);
}
@override
@ -195,9 +199,9 @@ class License {
factory License.fromMap(Map<String, Object?> map) {
return License(
activatedAt: DateTime.parse(map["activatedAt"] as String),
activationKey: map["activationKey"] as String,
licenseKey: map["licenseKey"] as String,
activatedAt: DateTime.parse(map["activatedAt"]! as String),
activationKey: map["activationKey"]! as String,
licenseKey: map["licenseKey"]! as String,
);
}
@ -224,61 +228,17 @@ licenseKey: $licenseKey,
}
// Model for a user metadata stored in the server
class UserMetadata {
final String userId;
final UserMetadataKey key;
final Onboarding? onboarding;
final Preferences? preferences;
final License? license;
const UserMetadata({required this.userId, required this.key, this.onboarding, this.preferences, this.license})
: assert(
onboarding != null || preferences != null || license != null,
'One of onboarding, preferences and license must be provided',
);
UserMetadata copyWith({
String? userId,
UserMetadataKey? key,
@freezed
abstract class UserMetadata with _$UserMetadata {
@Assert(
'onboarding != null || preferences != null || license != null',
'One of onboarding, preferences and license must be provided',
)
const factory UserMetadata({
required String userId,
required UserMetadataKey key,
Onboarding? onboarding,
Preferences? preferences,
License? license,
}) {
return UserMetadata(
userId: userId ?? this.userId,
key: key ?? this.key,
onboarding: onboarding ?? this.onboarding,
preferences: preferences ?? this.preferences,
license: license ?? this.license,
);
}
@override
String toString() {
return '''UserMetadata: {
userId: $userId,
key: $key,
onboarding: ${onboarding ?? "<NA>"},
preferences: ${preferences ?? "<NA>"},
license: ${license ?? "<NA>"},
}''';
}
@override
bool operator ==(covariant UserMetadata other) {
if (identical(this, other)) {
return true;
}
return other.userId == userId &&
other.key == key &&
other.onboarding == onboarding &&
other.preferences == preferences &&
other.license == license;
}
@override
int get hashCode {
return userId.hashCode ^ key.hashCode ^ onboarding.hashCode ^ preferences.hashCode ^ license.hashCode;
}
}) = _UserMetadata;
}

View file

@ -2,10 +2,15 @@ import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/asset_edit.model.dart';
import 'package:immich_mobile/domain/models/exif.model.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/platform_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_exif.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
import 'package:immich_mobile/repositories/asset_api.repository.dart';
import 'package:immich_mobile/repositories/asset_media.repository.dart';
import 'package:immich_mobile/utils/option.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
@ -14,12 +19,16 @@ class AssetService {
final RemoteExifRepository _exifRepository;
final DriftLocalAssetRepository _localRepository;
final AssetApiRepository _apiRepository;
final AssetMediaRepository _mediaRepository;
final DriftTrashedLocalAssetRepository _trashedLocalRepository;
const AssetService({
required this._remoteRepository,
required this._exifRepository,
required this._localRepository,
required this._apiRepository,
required this._mediaRepository,
required this._trashedLocalRepository,
});
Future<BaseAsset?> getAsset(BaseAsset asset) {
@ -36,8 +45,8 @@ class AssetService {
return _localRepository.getByChecksum(checksum);
}
Future<RemoteAsset?> getRemoteAssetByChecksum(String checksum) {
return _remoteRepository.getByChecksum(checksum);
Future<List<RemoteAsset>> getAllRemoteAssetDebugByChecksum(String checksum) {
return _remoteRepository.getAllDebugForChecksum(checksum);
}
Future<RemoteAsset?> getRemoteAsset(String id) {
@ -167,17 +176,25 @@ class AssetService {
}
}
// TODO(shenlong): remove after action migration
Future<int> deleteLocal(List<String> localIds) async {
if (localIds.isEmpty) {
return 0;
}
final deletedIds = await _mediaRepository.deleteAll(localIds);
if (deletedIds.isEmpty) {
return 0;
}
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
await _trashedLocalRepository.applyTrashedAssets(deletedIds);
} else {
await _localRepository.delete(deletedIds);
}
return deletedIds.length;
}
Future<LocalAsset?> getLocalAsset(String id) {
return _localRepository.get(id);
}
Future<void> updateFavorite(List<String> remoteIds, bool isFavorite) async {
if (remoteIds.isEmpty) {
return;
}
await _apiRepository.updateFavorite(remoteIds, isFavorite);
await _remoteRepository.updateFavorite(remoteIds, isFavorite);
}
}

View file

@ -32,7 +32,7 @@ class HashService {
}) : _batchSize = batchSize ?? kBatchHashFileLimit {
// Stop the in-flight native hash call promptly on cancellation; the loops
// below also observe [isCancelled] to bail between batches.
_cancellation?.future.then((_) => _nativeSyncApi.cancelHashing().onError(_log.warning));
unawaited(_cancellation?.future.then((_) => _nativeSyncApi.cancelHashing().onError(_log.warning)));
}
bool get isCancelled => _cancellation?.isCompleted ?? false;
@ -66,11 +66,12 @@ class HashService {
await _hashAssets(pseudoAlbum, trashedToHash, isTrashed: true);
}
}
} on PlatformException catch (e) {
} on PlatformException catch (e, s) {
if (e.code == _kHashCancelledCode) {
_log.warning("Hashing cancelled by platform");
return;
}
_log.severe("Native hashing failed: ${e.code}", e, s);
} catch (e, s) {
_log.severe("Error during hashing", e, s);
}

View file

@ -40,7 +40,7 @@ class LocalSyncService {
required this._permissionRepository,
this._cancellation,
}) {
_cancellation?.future.then((_) => _nativeSyncApi.cancelSync().onError(_log.warning));
unawaited(_cancellation?.future.then((_) => _nativeSyncApi.cancelSync().onError(_log.warning)));
}
bool get _isCancelled => _cancellation?.isCompleted ?? false;

View file

@ -105,10 +105,8 @@ class RemoteAlbumService {
switch (filterMode) {
case QuickFilterMode.myAlbums:
filtered = filtered.where((album) => album.ownerId == userId).toList();
break;
case QuickFilterMode.sharedWithMe:
filtered = filtered.where((album) => album.ownerId != userId).toList();
break;
case QuickFilterMode.all:
break;
}

View file

@ -155,7 +155,7 @@ class SyncStreamService {
}
Future<void> _handleEvents(List<SyncEvent> events, Function() abort, Function() reset) async {
List<SyncEvent> items = [];
final List<SyncEvent> items = [];
for (final event in events) {
if (isCancelled) {
_logger.warning("Sync stream cancelled");

View file

@ -106,32 +106,34 @@ class TimelineService {
TimelineService._({required this._assetSource, required this._bucketSource, required this.origin}) {
_bucketSubscription = _bucketSource().listen((buckets) {
_mutex.run(() async {
final totalAssets = buckets.fold<int>(0, (acc, bucket) => acc + bucket.assetCount);
unawaited(
_mutex.run(() async {
final totalAssets = buckets.fold<int>(0, (acc, bucket) => acc + bucket.assetCount);
if (totalAssets == 0) {
_bufferOffset = 0;
_buffer = [];
} else {
final int offset;
final int count;
// When the buffer is empty or the old bufferOffset is greater than the new total assets,
// we need to reset the buffer and load the first batch of assets.
if (_bufferOffset >= totalAssets || _buffer.isEmpty) {
offset = 0;
count = kTimelineAssetLoadBatchSize;
if (totalAssets == 0) {
_bufferOffset = 0;
_buffer = [];
} else {
offset = _bufferOffset;
count = math.min(_buffer.length, totalAssets - _bufferOffset);
final int offset;
final int count;
// When the buffer is empty or the old bufferOffset is greater than the new total assets,
// we need to reset the buffer and load the first batch of assets.
if (_bufferOffset >= totalAssets || _buffer.isEmpty) {
offset = 0;
count = kTimelineAssetLoadBatchSize;
} else {
offset = _bufferOffset;
count = math.min(_buffer.length, totalAssets - _bufferOffset);
}
_buffer = await _assetSource(offset, count);
_bufferOffset = offset;
}
_buffer = await _assetSource(offset, count);
_bufferOffset = offset;
}
// change the state's total assets count only after the buffer is reloaded
_totalAssets = totalAssets;
EventStream.shared.emit(const TimelineReloadEvent());
});
// change the state's total assets count only after the buffer is reloaded
_totalAssets = totalAssets;
EventStream.shared.emit(const TimelineReloadEvent());
}),
);
});
}
@ -178,7 +180,7 @@ class TimelineService {
if (!hasRange(index, count)) {
throw RangeError('TimelineService::getAssets Index out of range');
}
int start = index - _bufferOffset;
final int start = index - _bufferOffset;
return _buffer.slice(start, start + count);
}

View file

@ -28,6 +28,7 @@ class BackgroundSyncManager {
final SyncErrorCallback? onCloudIdSyncError;
Cancelable<bool?>? _syncTask;
bool _syncQueued = false;
Cancelable<void>? _syncWebsocketTask;
Cancelable<void>? _cloudIdSyncTask;
Cancelable<void>? _deviceAlbumSyncTask;
@ -50,6 +51,7 @@ class BackgroundSyncManager {
});
Future<void> cancel() async {
_syncQueued = false;
final tasks = [
_syncTask,
_syncWebsocketTask,
@ -133,8 +135,9 @@ class BackgroundSyncManager {
});
}
Future<bool> syncRemote() {
Future<bool> syncRemote({bool enqueue = false}) {
if (_syncTask != null) {
_syncQueued |= enqueue;
return _syncTask!.future.then((result) => result ?? false).catchError((_) => false);
}
@ -148,15 +151,20 @@ class BackgroundSyncManager {
.then((result) {
final success = result ?? false;
onRemoteSyncComplete?.call(success);
_syncQueued &= success;
return success;
})
.catchError((error) {
onRemoteSyncError?.call(error.toString());
_syncTask = null;
_syncQueued = false;
return false;
})
.whenComplete(() {
_syncTask = null;
if (_syncQueued) {
_syncQueued = false;
unawaited(syncRemote());
}
});
}

View file

@ -32,7 +32,7 @@ class EventStream {
}
/// Closes the stream controller
void dispose() {
_controller.close();
Future<void> dispose() {
return _controller.close();
}
}

View file

@ -15,8 +15,8 @@ extension DTOToAsset on api.AssetResponseDto {
ownerId: ownerId,
visibility: visibility.toAssetVisibility(),
durationMs: duration,
height: height?.toInt(),
width: width?.toInt(),
height: height,
width: width,
isFavorite: isFavorite,
livePhotoVideoId: livePhotoVideoId.orElse(null),
thumbHash: thumbhash,
@ -38,8 +38,8 @@ extension DTOToAsset on api.AssetResponseDto {
ownerId: ownerId,
visibility: visibility.toAssetVisibility(),
durationMs: duration,
height: height?.toInt(),
width: width?.toInt(),
height: height,
width: width,
isFavorite: isFavorite,
livePhotoVideoId: livePhotoVideoId.orElse(null),
thumbHash: thumbhash,

View file

@ -5,7 +5,8 @@ import 'package:collection/collection.dart';
extension ListExtension<E> on List<E> {
List<E> uniqueConsecutive({int Function(E a, E b)? compare, void Function(E a, E b)? onDuplicate}) {
compare ??= (E a, E b) => a == b ? 0 : 1;
int i = 1, j = 1;
int i = 1;
int j = 1;
for (; i < length; i++) {
if (compare(this[i - 1], this[i]) != 0) {
if (i != j) {

View file

@ -4,7 +4,7 @@ import 'package:easy_localization/easy_localization.dart';
extension TimeAgoExtension on DateTime {
/// Displays the time difference of this [DateTime] object to the current time as a [String]
String timeAgo({bool numericDates = true}) {
DateTime date = toLocal();
final DateTime date = toLocal();
final now = DateTime.now().toLocal();
final difference = now.difference(date);
@ -13,27 +13,27 @@ extension TimeAgoExtension on DateTime {
} else if (difference.inSeconds < 60) {
return '${difference.inSeconds} seconds ago';
} else if (difference.inMinutes <= 1) {
return (numericDates) ? '1 minute ago' : 'A minute ago';
return numericDates ? '1 minute ago' : 'A minute ago';
} else if (difference.inMinutes < 60) {
return '${difference.inMinutes} minutes ago';
} else if (difference.inHours <= 1) {
return (numericDates) ? '1 hour ago' : 'An hour ago';
return numericDates ? '1 hour ago' : 'An hour ago';
} else if (difference.inHours < 60) {
return '${difference.inHours} hours ago';
} else if (difference.inDays <= 1) {
return (numericDates) ? '1 day ago' : 'Yesterday';
return numericDates ? '1 day ago' : 'Yesterday';
} else if (difference.inDays < 6) {
return '${difference.inDays} days ago';
} else if ((difference.inDays / 7).ceil() <= 1) {
return (numericDates) ? '1 week ago' : 'Last week';
return numericDates ? '1 week ago' : 'Last week';
} else if ((difference.inDays / 7).ceil() < 4) {
return '${(difference.inDays / 7).ceil()} weeks ago';
} else if ((difference.inDays / 30).ceil() <= 1) {
return (numericDates) ? '1 month ago' : 'Last month';
return numericDates ? '1 month ago' : 'Last month';
} else if ((difference.inDays / 30).ceil() < 30) {
return '${(difference.inDays / 30).ceil()} months ago';
} else if ((difference.inDays / 365).ceil() <= 1) {
return (numericDates) ? '1 year ago' : 'Last year';
return numericDates ? '1 year ago' : 'Last year';
}
return '${(difference.inDays / 365).floor()} years ago';
}

View file

@ -25,7 +25,7 @@ class AssetEditEntity extends Table with DriftDefaultsMixin {
}
final JsonTypeConverter2<Map<String, Object?>, Uint8List, Object?> editParameterConverter = TypeConverter.jsonb(
fromJson: (json) => json as Map<String, Object?>,
fromJson: (json) => json! as Map<String, Object?>,
);
extension AssetEditEntityDataDomainEx on AssetEditEntityData {

View file

@ -79,8 +79,8 @@ extension RemoteExifEntityDataDomainEx on RemoteExifEntityData {
orientation: orientation,
latitude: latitude,
longitude: longitude,
f: fNumber?.toDouble(),
mm: focalLength?.toDouble(),
f: fNumber,
mm: focalLength,
lens: lens,
isFlipped: ExifDtoConverter.isOrientationFlipped(orientation),
exposureSeconds: ExifDtoConverter.exposureTimeToSeconds(exposureTime),

View file

@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
import 'package:immich_mobile/infrastructure/entities/log.entity.drift.dart';
import 'package:immich_mobile/domain/models/log.model.dart' as domain;
import 'package:immich_mobile/infrastructure/entities/log.entity.drift.dart';
class LogMessageEntity extends Table {
const LogMessageEntity();

View file

@ -17,5 +17,5 @@ class UserMetadataEntity extends Table with DriftDefaultsMixin {
}
final JsonTypeConverter2<Map<String, Object?>, Uint8List, Object?> userMetadataConverter = TypeConverter.jsonb(
fromJson: (json) => json as Map<String, Object?>,
fromJson: (json) => json! as Map<String, Object?>,
);

View file

@ -18,7 +18,7 @@ abstract class ImageRequest {
final int requestId = _nextRequestId++;
bool _isCancelled = false;
get isCancelled => _isCancelled;
bool get isCancelled => _isCancelled;
ImageRequest();

View file

@ -14,8 +14,13 @@ class RemoteImageRequest extends ImageRequest {
final info = await remoteImageApi.requestImage(uri, requestId: requestId, preferEncoded: false);
// Android falls back to encoded data if native decoding fails, so check for both shapes of the response.
final frame = switch (info) {
{'pointer': int pointer, 'length': int length} => await _fromEncodedPlatformImage(pointer, length),
{'pointer': int pointer, 'width': int width, 'height': int height, 'rowBytes': int rowBytes} =>
{'pointer': final int pointer, 'length': final int length} => await _fromEncodedPlatformImage(pointer, length),
{
'pointer': final int pointer,
'width': final int width,
'height': final int height,
'rowBytes': final int rowBytes,
} =>
await _fromDecodedPlatformImage(pointer, width, height, rowBytes),
_ => null,
};

View file

@ -4,6 +4,7 @@ import 'package:drift/drift.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
@ -16,7 +17,7 @@ class DriftBackupRepository extends DriftDatabaseRepository {
final Drift _db;
const DriftBackupRepository(this._db) : super(_db);
_getExcludedSubquery() {
JoinedSelectStatement<$LocalAlbumAssetEntityTable, LocalAlbumAssetEntityData> _getExcludedSubquery() {
return _db.localAlbumAssetEntity.selectOnly()
..addColumns([_db.localAlbumAssetEntity.assetId])
..join([

View file

@ -356,7 +356,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
return _deleteAssets(assetIds);
}
List<String> assetsToDelete = [];
final List<String> assetsToDelete = [];
List<String> assetsToUnLink = [];
final uniqueAssets = await _getUniqueAssetsInAlbum(albumId);

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