mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
Merge branch 'immich-app:main' into feature/photo-slideshow-animate-zoom
This commit is contained in:
commit
0640f8393f
557 changed files with 10704 additions and 19412 deletions
6
.gitattributes
vendored
6
.gitattributes
vendored
|
|
@ -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 -diff -merge
|
||||||
mobile/android/**/*.g.kt linguist-generated=true
|
mobile/android/**/*.g.kt linguist-generated=true
|
||||||
|
|
||||||
mobile/ios/**/*.g.swift -diff -merge
|
mobile/ios/**/*.g.swift -diff -merge
|
||||||
mobile/ios/**/*.g.swift linguist-generated=true
|
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 -diff -merge
|
||||||
mobile/drift_schemas/main/drift_schema_*.json linguist-generated=true
|
mobile/drift_schemas/main/drift_schema_*.json linguist-generated=true
|
||||||
|
|
||||||
|
|
|
||||||
4
.github/workflows/build-mobile.yml
vendored
4
.github/workflows/build-mobile.yml
vendored
|
|
@ -94,7 +94,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
working_directory: ./mobile
|
working_directory: ./mobile
|
||||||
|
|
@ -216,7 +216,7 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
working_directory: ./mobile
|
working_directory: ./mobile
|
||||||
|
|
|
||||||
4
.github/workflows/check-openapi.yml
vendored
4
.github/workflows/check-openapi.yml
vendored
|
|
@ -25,7 +25,7 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Check for breaking API changes
|
- 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:
|
with:
|
||||||
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
||||||
revision: open-api/immich-openapi-specs.json
|
revision: open-api/immich-openapi-specs.json
|
||||||
|
|
@ -43,7 +43,7 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ github.token }}
|
github_token: ${{ github.token }}
|
||||||
working_directory: ./mobile
|
working_directory: ./mobile
|
||||||
|
|
|
||||||
4
.github/workflows/cli.yml
vendored
4
.github/workflows/cli.yml
vendored
|
|
@ -44,7 +44,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
@ -83,7 +83,7 @@ jobs:
|
||||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- 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 }}
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
|
|
|
||||||
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
|
|
@ -58,7 +58,7 @@ jobs:
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
|
@ -71,7 +71,7 @@ jobs:
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
uses: github/codeql-action/autobuild@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
|
|
@ -84,6 +84,6 @@ jobs:
|
||||||
# ./location_of_script_within_repo/buildscript.sh
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||||
with:
|
with:
|
||||||
category: '/language:${{matrix.language}}'
|
category: '/language:${{matrix.language}}'
|
||||||
|
|
|
||||||
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
|
|
@ -61,7 +61,7 @@ jobs:
|
||||||
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
|
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
|
||||||
steps:
|
steps:
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
|
|
@ -91,7 +91,7 @@ jobs:
|
||||||
suffix: ['']
|
suffix: ['']
|
||||||
steps:
|
steps:
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
|
|
|
||||||
2
.github/workflows/docs-build.yml
vendored
2
.github/workflows/docs-build.yml
vendored
|
|
@ -68,7 +68,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
|
||||||
2
.github/workflows/docs-deploy.yml
vendored
2
.github/workflows/docs-deploy.yml
vendored
|
|
@ -143,7 +143,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
|
||||||
2
.github/workflows/docs-destroy.yml
vendored
2
.github/workflows/docs-destroy.yml
vendored
|
|
@ -31,7 +31,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
|
||||||
2
.github/workflows/fix-format.yml
vendored
2
.github/workflows/fix-format.yml
vendored
|
|
@ -30,7 +30,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
|
||||||
1
.github/workflows/pr-label-validation.yml
vendored
1
.github/workflows/pr-label-validation.yml
vendored
|
|
@ -8,7 +8,6 @@ permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
validate-release-label:
|
validate-release-label:
|
||||||
if: ${{ !startsWith(github.event.pull_request.head.ref, 'mergify/merge-queue/') }}
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
|
|
|
||||||
2
.github/workflows/pr-labeler.yml
vendored
2
.github/workflows/pr-labeler.yml
vendored
|
|
@ -19,6 +19,6 @@ jobs:
|
||||||
permission-contents: read
|
permission-contents: read
|
||||||
permission-pull-requests: write
|
permission-pull-requests: write
|
||||||
|
|
||||||
- uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
|
- uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ steps.token.outputs.token }}
|
repo-token: ${{ steps.token.outputs.token }}
|
||||||
|
|
|
||||||
4
.github/workflows/prepare-release.yml
vendored
4
.github/workflows/prepare-release.yml
vendored
|
|
@ -68,13 +68,13 @@ jobs:
|
||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
# TODO move to mise
|
# TODO move to mise
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||||
|
|
||||||
- name: Bump version
|
- name: Bump version
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
2
.github/workflows/sdk.yml
vendored
2
.github/workflows/sdk.yml
vendored
|
|
@ -29,7 +29,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
|
||||||
2
.github/workflows/static_analysis.yml
vendored
2
.github/workflows/static_analysis.yml
vendored
|
|
@ -64,7 +64,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
working_directory: ./mobile
|
working_directory: ./mobile
|
||||||
|
|
|
||||||
40
.github/workflows/test.yml
vendored
40
.github/workflows/test.yml
vendored
|
|
@ -91,7 +91,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
@ -120,7 +120,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
@ -152,7 +152,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
@ -184,7 +184,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
@ -229,7 +229,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
@ -268,7 +268,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
@ -297,7 +297,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
@ -348,7 +348,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
@ -382,7 +382,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
@ -441,6 +441,7 @@ jobs:
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
- name: Start Docker Compose
|
- name: Start Docker Compose
|
||||||
|
id: docker
|
||||||
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
|
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
|
|
@ -448,13 +449,13 @@ jobs:
|
||||||
env:
|
env:
|
||||||
VITEST_DISABLE_DOCKER_SETUP: true
|
VITEST_DISABLE_DOCKER_SETUP: true
|
||||||
run: pnpm test
|
run: pnpm test
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() && steps.docker.outcome == 'success' }}
|
||||||
|
|
||||||
- name: Run e2e tests (maintenance)
|
- name: Run e2e tests (maintenance)
|
||||||
env:
|
env:
|
||||||
VITEST_DISABLE_DOCKER_SETUP: true
|
VITEST_DISABLE_DOCKER_SETUP: true
|
||||||
run: pnpm test:maintenance
|
run: pnpm test:maintenance
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() && steps.docker.outcome == 'success' }}
|
||||||
|
|
||||||
- name: Capture Docker logs
|
- name: Capture Docker logs
|
||||||
if: always()
|
if: always()
|
||||||
|
|
@ -519,6 +520,7 @@ jobs:
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
- name: Docker build
|
- name: Docker build
|
||||||
|
id: docker
|
||||||
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
|
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
|
||||||
|
|
@ -526,7 +528,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
||||||
run: pnpm test:web
|
run: pnpm test:web
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() && steps.docker.outcome == 'success' }}
|
||||||
|
|
||||||
- name: Archive e2e test (web) results
|
- name: Archive e2e test (web) results
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
|
@ -539,7 +541,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
||||||
run: pnpm test:web:ui
|
run: pnpm test:web:ui
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() && steps.docker.outcome == 'success' }}
|
||||||
|
|
||||||
- name: Archive ui test (web) results
|
- name: Archive ui test (web) results
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
|
@ -552,7 +554,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
||||||
run: pnpm test:web:maintenance
|
run: pnpm test:web:maintenance
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() && steps.docker.outcome == 'success' }}
|
||||||
|
|
||||||
- name: Archive maintenance tests (web) results
|
- name: Archive maintenance tests (web) results
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
|
@ -604,7 +606,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
working_directory: ./mobile
|
working_directory: ./mobile
|
||||||
|
|
@ -642,7 +644,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
@ -674,7 +676,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
@ -727,7 +729,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
@ -785,7 +787,7 @@ jobs:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
merge_queue:
|
|
||||||
status_comments: outcomes
|
|
||||||
4
.vscode/extensions.json
vendored
4
.vscode/extensions.json
vendored
|
|
@ -12,6 +12,8 @@
|
||||||
"editorconfig.editorconfig",
|
"editorconfig.editorconfig",
|
||||||
"foxundermoon.shell-format",
|
"foxundermoon.shell-format",
|
||||||
"timonwong.shellcheck",
|
"timonwong.shellcheck",
|
||||||
"bluebrown.yamlfmt"
|
"bluebrown.yamlfmt",
|
||||||
|
// TODO TS 7 while it's still in preview
|
||||||
|
"typescriptteam.native-preview"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -60,6 +60,8 @@
|
||||||
"**/dist/**": true,
|
"**/dist/**": true,
|
||||||
"**/node_modules/**": true
|
"**/node_modules/**": true
|
||||||
},
|
},
|
||||||
|
// TODO remove once the ts7 extension is stable
|
||||||
|
"js/ts.experimental.useTsgo": true,
|
||||||
"js/ts.preferences.importModuleSpecifier": "non-relative",
|
"js/ts.preferences.importModuleSpecifier": "non-relative",
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/.svelte-kit": true,
|
"**/.svelte-kit": true,
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,43 @@
|
||||||
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
|
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
|
||||||
|
|
||||||
[[tools.opentofu]]
|
[[tools.opentofu]]
|
||||||
version = "1.11.6"
|
version = "1.12.5"
|
||||||
backend = "aqua:opentofu/opentofu"
|
backend = "aqua:opentofu/opentofu"
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-arm64"]
|
[tools.opentofu."platforms.linux-arm64"]
|
||||||
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
checksum = "sha256:e67e9da2b1ddf5050ebee62a584cb826eafe1dfd3827d7ec20899ac62791ed1a"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
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/391536382"
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602545"
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-arm64-musl"]
|
[tools.opentofu."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
checksum = "sha256:e67e9da2b1ddf5050ebee62a584cb826eafe1dfd3827d7ec20899ac62791ed1a"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
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/391536382"
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602545"
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-x64"]
|
[tools.opentofu."platforms.linux-x64"]
|
||||||
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
checksum = "sha256:a6894d45ae7a17ce83189cce8fe04b5a65f68cefceb62455b5a6a89fa53ab38f"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
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/391536401"
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602624"
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-x64-musl"]
|
[tools.opentofu."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
checksum = "sha256:a6894d45ae7a17ce83189cce8fe04b5a65f68cefceb62455b5a6a89fa53ab38f"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
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/391536401"
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602624"
|
||||||
|
|
||||||
[tools.opentofu."platforms.macos-arm64"]
|
[tools.opentofu."platforms.macos-arm64"]
|
||||||
checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1"
|
checksum = "sha256:2ae38150a667f5c0bd57b318d18ad8091d08f93fcca40345f3d88998661de5a9"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz"
|
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/391536399"
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602544"
|
||||||
|
|
||||||
[tools.opentofu."platforms.macos-x64"]
|
[tools.opentofu."platforms.macos-x64"]
|
||||||
checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6"
|
checksum = "sha256:1012d8f3d4567bcbcd1f2c7d766feca39a30bced32fb8be47e1887fbbee2456d"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz"
|
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/391536384"
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602621"
|
||||||
|
|
||||||
[tools.opentofu."platforms.windows-x64"]
|
[tools.opentofu."platforms.windows-x64"]
|
||||||
checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e"
|
checksum = "sha256:af11850b496f3720e0184084c56d8b43aa74ea92d2338978bf368d70c96473f1"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
|
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/391536406"
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602547"
|
||||||
|
|
||||||
[[tools.terragrunt]]
|
[[tools.terragrunt]]
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[tools]
|
[tools]
|
||||||
terragrunt = "1.1.1"
|
terragrunt = "1.1.1"
|
||||||
opentofu = "1.12.4"
|
opentofu = "1.12.5"
|
||||||
|
|
||||||
[tasks."tg:fmt"]
|
[tasks."tg:fmt"]
|
||||||
run = "terragrunt hclfmt"
|
run = "terragrunt hclfmt"
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ services:
|
||||||
command: ['./run.sh', '-disable-reporting']
|
command: ['./run.sh', '-disable-reporting']
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
image: grafana/grafana:12.4.5-ubuntu@sha256:00396460e499415c828b7c298f19287c8a0f95e72412ee37ac11723655c2d6b9
|
image: grafana/grafana:12.4.6-ubuntu@sha256:35c75489ff2e2e69c53977a180de491643d51fdb32108f9335317527978daed7
|
||||||
volumes:
|
volumes:
|
||||||
- grafana-data:/var/lib/grafana
|
- grafana-data:/var/lib/grafana
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,18 @@ sidebar_position: 100
|
||||||
|
|
||||||
A config file can be provided as an alternative to the UI configuration.
|
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
|
### 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.
|
YAML-formatted config files are also supported.
|
||||||
The default configuration looks like this:
|
The default configuration looks like this:
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>immich.json</summary>
|
<summary>immich-config.json</summary>
|
||||||
|
|
||||||
```json
|
```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
|
### 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.
|
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.
|
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
|
```yaml
|
||||||
volumes:
|
volumes:
|
||||||
- ./configuration.yml:${IMMICH_CONFIG_FILE}
|
- ./immich-config.json:${IMMICH_CONFIG_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_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_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_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"`.
|
\*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.
|
`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.
|
||||||
|
|
|
||||||
|
|
@ -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:
|
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
|
1. Community made template on the Unraid Community Apps
|
||||||
2. 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
|
## 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)
|
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
|
:::info
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,4 +28,4 @@ run = "prettier --write ."
|
||||||
run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}"
|
run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}"
|
||||||
|
|
||||||
[tools]
|
[tools]
|
||||||
wrangler = "4.111.0"
|
wrangler = "4.114.0"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[tasks.install]
|
[tasks.install]
|
||||||
run = "pnpm install --filter immich-e2e --frozen-lockfile"
|
run = "pnpm install --filter immich-e2e... --frozen-lockfile"
|
||||||
|
|
||||||
[tasks.build]
|
[tasks.build]
|
||||||
dir = "{{ config_root }}"
|
dir = "{{ config_root }}"
|
||||||
|
|
@ -40,18 +40,19 @@ run = "tsc --noEmit"
|
||||||
|
|
||||||
|
|
||||||
[tasks.ci-setup]
|
[tasks.ci-setup]
|
||||||
depends = [
|
run = [
|
||||||
"//:sdk:install",
|
{ task = "//:sdk:install" },
|
||||||
"//:sdk:build",
|
{ task = "//:sdk:build" },
|
||||||
"//packages/cli:install",
|
{ task = "//packages/cli:install" },
|
||||||
"//packages/cli:build",
|
{ task = "//packages/cli:build" },
|
||||||
|
{ task = ":install" },
|
||||||
]
|
]
|
||||||
run = { task = ":install" }
|
|
||||||
|
|
||||||
|
|
||||||
[tasks.ci-unit]
|
[tasks.ci-unit]
|
||||||
depends = ["//:sdk:install", "//:sdk:build"]
|
|
||||||
run = [
|
run = [
|
||||||
|
{ task = "//:sdk:install" },
|
||||||
|
{ task = "//:sdk:build" },
|
||||||
{ task = ":install" },
|
{ task = ":install" },
|
||||||
{ task = ":format" },
|
{ task = ":format" },
|
||||||
{ task = ":lint" },
|
{ task = ":lint" },
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,6 @@ export const errorDto = {
|
||||||
incorrectLogin: {
|
incorrectLogin: {
|
||||||
message: 'Incorrect email or password',
|
message: 'Incorrect email or password',
|
||||||
},
|
},
|
||||||
alreadyHasAdmin: {
|
|
||||||
message: 'The server already has an admin',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const signupResponseDto = {
|
export const signupResponseDto = {
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ describe('/admin/database-backups', () => {
|
||||||
const { status, body } = await request(app).post('/admin/database-backups/start-restore').send();
|
const { status, body } = await request(app).post('/admin/database-backups/start-restore').send();
|
||||||
|
|
||||||
expect(status).toBe(400);
|
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 () => {
|
it.sequential('should enter maintenance mode in "database restore mode"', async () => {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
"add_to_album_bottom_sheet_added": "Added to {album}",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Already in {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_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": "Add to albums",
|
||||||
"add_to_albums_count": "Add to albums ({count})",
|
"add_to_albums_count": "Add to albums ({count})",
|
||||||
"add_to_bottom_bar": "Add to",
|
"add_to_bottom_bar": "Add to",
|
||||||
|
|
|
||||||
124
mise.lock
124
mise.lock
|
|
@ -82,6 +82,15 @@ url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353224133"
|
||||||
version = "7.1.3-6"
|
version = "7.1.3-6"
|
||||||
backend = "github:jellyfin/jellyfin-ffmpeg"
|
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]
|
[tools."github:jellyfin/jellyfin-ffmpeg".options]
|
||||||
asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz"
|
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"
|
version = "7.1.3-6"
|
||||||
backend = "github:jellyfin/jellyfin-ffmpeg"
|
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]
|
[tools."github:jellyfin/jellyfin-ffmpeg".options]
|
||||||
asset_pattern = "jellyfin-ffmpeg_*_portable_mac64-gpl.tar.xz"
|
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"
|
version = "7.1.3-6"
|
||||||
backend = "github:jellyfin/jellyfin-ffmpeg"
|
backend = "github:jellyfin/jellyfin-ffmpeg"
|
||||||
|
|
||||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.windows-x64"]
|
[tools."github:jellyfin/jellyfin-ffmpeg".options]
|
||||||
checksum = "sha256:7b7168149689610296f3a187c717056ce0786cc125a31caf28056737e9ba1cc1"
|
asset_pattern = "jellyfin-ffmpeg_*_portable_linux64-gpl.tar.xz"
|
||||||
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"."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"]]
|
[[tools."github:webassembly/binaryen"]]
|
||||||
version = "version_124"
|
version = "version_124"
|
||||||
|
|
@ -252,82 +252,82 @@ version = "7.5.0"
|
||||||
backend = "npm:oazapfts"
|
backend = "npm:oazapfts"
|
||||||
|
|
||||||
[[tools.opentofu]]
|
[[tools.opentofu]]
|
||||||
version = "1.12.4"
|
version = "1.12.5"
|
||||||
backend = "aqua:opentofu/opentofu"
|
backend = "aqua:opentofu/opentofu"
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-arm64"]
|
[tools.opentofu."platforms.linux-arm64"]
|
||||||
checksum = "sha256:dc7bfcd93ce9795a86c58fbf71efd013c39dcd1febb13c9cd3555c43b9c2403a"
|
checksum = "sha256:e67e9da2b1ddf5050ebee62a584cb826eafe1dfd3827d7ec20899ac62791ed1a"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_linux_arm64.tar.gz"
|
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/475646678"
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602545"
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-arm64-musl"]
|
[tools.opentofu."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:dc7bfcd93ce9795a86c58fbf71efd013c39dcd1febb13c9cd3555c43b9c2403a"
|
checksum = "sha256:e67e9da2b1ddf5050ebee62a584cb826eafe1dfd3827d7ec20899ac62791ed1a"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_linux_arm64.tar.gz"
|
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/475646678"
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602545"
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-x64"]
|
[tools.opentofu."platforms.linux-x64"]
|
||||||
checksum = "sha256:81836d0f12b4fe9013b85586349f993def9429b6383bb77cdd6c2f3a9d9aac24"
|
checksum = "sha256:a6894d45ae7a17ce83189cce8fe04b5a65f68cefceb62455b5a6a89fa53ab38f"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_linux_amd64.tar.gz"
|
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/475646677"
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602624"
|
||||||
|
|
||||||
[tools.opentofu."platforms.linux-x64-musl"]
|
[tools.opentofu."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:81836d0f12b4fe9013b85586349f993def9429b6383bb77cdd6c2f3a9d9aac24"
|
checksum = "sha256:a6894d45ae7a17ce83189cce8fe04b5a65f68cefceb62455b5a6a89fa53ab38f"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_linux_amd64.tar.gz"
|
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/475646677"
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602624"
|
||||||
|
|
||||||
[tools.opentofu."platforms.macos-arm64"]
|
[tools.opentofu."platforms.macos-arm64"]
|
||||||
checksum = "sha256:7c06e4390d9ccd467773e37ff1c3d833c7ca0c24742cd9e9ad47284bea472247"
|
checksum = "sha256:2ae38150a667f5c0bd57b318d18ad8091d08f93fcca40345f3d88998661de5a9"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_darwin_arm64.tar.gz"
|
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/475646544"
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602544"
|
||||||
|
|
||||||
[tools.opentofu."platforms.macos-x64"]
|
[tools.opentofu."platforms.macos-x64"]
|
||||||
checksum = "sha256:ead1d2ce643addb4ffeb93240b9377ae1c2fd793a6bd22d65922ac37adfdf546"
|
checksum = "sha256:1012d8f3d4567bcbcd1f2c7d766feca39a30bced32fb8be47e1887fbbee2456d"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_darwin_amd64.tar.gz"
|
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/475646688"
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602621"
|
||||||
|
|
||||||
[tools.opentofu."platforms.windows-x64"]
|
[tools.opentofu."platforms.windows-x64"]
|
||||||
checksum = "sha256:a4d86a07755c8d151f20f945e6cfb5b40deeed942af36a9bd385c5c2e965d5dd"
|
checksum = "sha256:af11850b496f3720e0184084c56d8b43aa74ea92d2338978bf368d70c96473f1"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.4/tofu_1.12.4_windows_amd64.tar.gz"
|
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/475646543"
|
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602547"
|
||||||
|
|
||||||
[[tools.pnpm]]
|
[[tools.pnpm]]
|
||||||
version = "11.13.1"
|
version = "11.17.0"
|
||||||
backend = "aqua:pnpm/pnpm"
|
backend = "aqua:pnpm/pnpm"
|
||||||
|
|
||||||
[tools.pnpm."platforms.linux-arm64"]
|
[tools.pnpm."platforms.linux-arm64"]
|
||||||
checksum = "sha256:b52db99d215ed7dc9563aed815953c62a6c1ffd7cd75803d3a07ad7e4f246aed"
|
checksum = "sha256:730d17de742a3efbb020ba91d7acfc0456c6ba6ad1cd8eb49f4c229fe9f504d3"
|
||||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-linux-arm64.tar.gz"
|
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/478563983"
|
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/487422123"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.pnpm."platforms.linux-arm64-musl"]
|
[tools.pnpm."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:2cadd4fc815c591f498a0a84c9e74a836e3e8c1236275f6e1cfd355bae6ae957"
|
checksum = "sha256:cc072c0e7bdd290f52fb9afdcea778ede69f96f0f4dae63623163fc11d4beec3"
|
||||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-linux-arm64-musl.tar.gz"
|
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/478563985"
|
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/487422121"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.pnpm."platforms.linux-x64"]
|
[tools.pnpm."platforms.linux-x64"]
|
||||||
checksum = "sha256:bd6d4b0e14536207ad76bc838f5980cecd968da15f69aae0b207380cca3f2e98"
|
checksum = "sha256:bdb1db01bf0f757495405a59a09c5c287f315889dc98d3b14bc374b9fe43a0bf"
|
||||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-linux-x64.tar.gz"
|
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/478563986"
|
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/487422122"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.pnpm."platforms.linux-x64-musl"]
|
[tools.pnpm."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:ba19690f4ed1b64f1203ade14e9216352b46232d5582468b26a0160e0c9618c5"
|
checksum = "sha256:db2e4eeecab336bd41bbbc7a39626b166f1a15bb189efcf6a1dafdf159f23fe7"
|
||||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-linux-x64-musl.tar.gz"
|
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/478563980"
|
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/487422116"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.pnpm."platforms.macos-arm64"]
|
[tools.pnpm."platforms.macos-arm64"]
|
||||||
checksum = "sha256:765c2bf04e8129cb58c0f946e324262e418370b35a203b50b1f06a0567ef8bc1"
|
checksum = "sha256:1e9a35d76f9d382af365d95c64f3abf7815e24350653f5bb1a37e4546265bc2b"
|
||||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-darwin-arm64.tar.gz"
|
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/478563984"
|
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/487422117"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.pnpm."platforms.windows-x64"]
|
[tools.pnpm."platforms.windows-x64"]
|
||||||
checksum = "sha256:d8bebbc71df2702961c1d34a5e61196bc0aa3bbde33c33253f6afa3dd4546a6d"
|
checksum = "sha256:3025c174a6dcffec14a071a5c854cb0c350a09a8c7c2857a75f75cc7b29b939c"
|
||||||
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-win32-x64.zip"
|
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/478563981"
|
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/487422118"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[[tools.terragrunt]]
|
[[tools.terragrunt]]
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@ config_roots = [
|
||||||
|
|
||||||
[tools]
|
[tools]
|
||||||
node = "24.15.0"
|
node = "24.15.0"
|
||||||
pnpm = "11.13.1"
|
pnpm = "11.17.0"
|
||||||
terragrunt = "1.1.1"
|
terragrunt = "1.1.1"
|
||||||
opentofu = "1.12.4"
|
opentofu = "1.12.5"
|
||||||
"npm:@openapitools/openapi-generator-cli" = "2.40.1"
|
"npm:@openapitools/openapi-generator-cli" = "2.40.1"
|
||||||
"npm:oazapfts" = "7.5.0"
|
"npm:oazapfts" = "7.5.0"
|
||||||
"github:extism/cli" = "1.6.3"
|
"github:extism/cli" = "1.6.3"
|
||||||
|
|
@ -69,7 +69,7 @@ sources = [
|
||||||
"patch/*",
|
"patch/*",
|
||||||
"bin/generate-dart-sdk.sh",
|
"bin/generate-dart-sdk.sh",
|
||||||
]
|
]
|
||||||
outputs = ["../mobile/openapi/lib/"]
|
outputs = ["../mobile/generated/openapi/"]
|
||||||
run = "bash ./bin/generate-dart-sdk.sh"
|
run = "bash ./bin/generate-dart-sdk.sh"
|
||||||
|
|
||||||
[tasks.open-api]
|
[tasks.open-api]
|
||||||
|
|
|
||||||
7
mobile/.gitignore
vendored
7
mobile/.gitignore
vendored
|
|
@ -32,8 +32,15 @@
|
||||||
.pub/
|
.pub/
|
||||||
/build/
|
/build/
|
||||||
lib/**/*.drift.dart
|
lib/**/*.drift.dart
|
||||||
|
lib/**/*.freezed.dart
|
||||||
|
lib/routing/router.gr.dart
|
||||||
test/drift/main/generated/
|
test/drift/main/generated/
|
||||||
|
|
||||||
|
# Pigeon related
|
||||||
|
/lib/platform/*.g.dart
|
||||||
|
/ios/**/*.g.swift
|
||||||
|
/android/**/*.g.kt
|
||||||
|
|
||||||
# Web related
|
# Web related
|
||||||
lib/generated_plugin_registrant.dart
|
lib/generated_plugin_registrant.dart
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,15 +26,51 @@ linter:
|
||||||
# producing the lint.
|
# producing the lint.
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
# Formatting
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
avoid_print: true
|
||||||
unawaited_futures: true
|
|
||||||
use_build_context_synchronously: false
|
|
||||||
require_trailing_commas: true
|
require_trailing_commas: true
|
||||||
unrelated_type_equality_checks: true
|
unrelated_type_equality_checks: true
|
||||||
prefer_const_constructors: true
|
prefer_const_constructors: true
|
||||||
always_use_package_imports: true
|
always_use_package_imports: true
|
||||||
always_put_control_body_on_new_line: 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
|
# Additional information about this file can be found at
|
||||||
# https://dart.dev/guides/language/analysis-options
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
|
@ -46,20 +82,12 @@ analyzer:
|
||||||
- lib/**/*.g.dart
|
- lib/**/*.g.dart
|
||||||
- lib/**/*.drift.dart
|
- lib/**/*.drift.dart
|
||||||
|
|
||||||
# TODO: Re-enable after upgrading custom_lint
|
# NOTE: We explicitly do not use riverpod_lint as there are analyzer version conflicts between
|
||||||
# plugins:
|
# our Flutter version and the required old riverpod_lint 2.x
|
||||||
# - custom_lint
|
|
||||||
errors:
|
errors:
|
||||||
unawaited_futures: warning
|
unawaited_futures: warning
|
||||||
always_put_control_body_on_new_line: 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:
|
dart_code_metrics:
|
||||||
rules:
|
rules:
|
||||||
- banned-usage:
|
- banned-usage:
|
||||||
|
|
@ -82,3 +110,17 @@ dart_code_metrics:
|
||||||
- prefer-const-border-radius
|
- prefer-const-border-radius
|
||||||
- prefer-declaring-const-constructor
|
- prefer-declaring-const-constructor
|
||||||
- prefer-switch-expression
|
- 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
|
||||||
|
|
|
||||||
|
|
@ -152,12 +152,6 @@
|
||||||
This is used by the Flutter tool to
|
This is used by the Flutter tool to
|
||||||
generate GeneratedPluginRegistrant.java -->
|
generate GeneratedPluginRegistrant.java -->
|
||||||
<meta-data android:name="flutterEmbedding" android:value="2" />
|
<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 -->
|
<!-- Widgets -->
|
||||||
<receiver
|
<receiver
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,12 @@ package app.alextran.immich
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.work.Configuration
|
|
||||||
import androidx.work.WorkManager
|
|
||||||
import app.alextran.immich.background.BackgroundEngineLock
|
import app.alextran.immich.background.BackgroundEngineLock
|
||||||
import app.alextran.immich.background.BackgroundWorkerApiImpl
|
import app.alextran.immich.background.BackgroundWorkerApiImpl
|
||||||
|
|
||||||
class ImmichApp : Application() {
|
class ImmichApp : Application() {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.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.
|
// 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
|
// 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.
|
// (because of low memory etc.), the backup is never performed.
|
||||||
|
|
|
||||||
|
|
@ -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)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
||||||
import android.content.ContentUris
|
import android.content.ContentUris
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import androidx.exifinterface.media.ExifInterface
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.ext.SdkExtensions
|
import android.os.ext.SdkExtensions
|
||||||
|
|
@ -30,6 +29,8 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import kotlinx.coroutines.sync.withPermit
|
import kotlinx.coroutines.sync.withPermit
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import kotlin.coroutines.cancellation.CancellationException
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
|
|
||||||
|
|
@ -377,7 +378,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
||||||
)?.use { cursor -> cursor.count.toLong() } ?: 0L
|
)?.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) }
|
runSync(callback) { getAssetsForAlbum(albumId, updatedTimeCond) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -419,7 +424,7 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
||||||
}.awaitAll()
|
}.awaitAll()
|
||||||
|
|
||||||
completeWhenActive(callback, Result.success(results))
|
completeWhenActive(callback, Result.success(results))
|
||||||
} catch (e: CancellationException) {
|
} catch (_: CancellationException) {
|
||||||
completeWhenActive(
|
completeWhenActive(
|
||||||
callback, Result.failure(
|
callback, Result.failure(
|
||||||
FlutterError(
|
FlutterError(
|
||||||
|
|
@ -437,23 +442,20 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
||||||
|
|
||||||
private suspend fun hashAsset(assetId: String): HashResult {
|
private suspend fun hashAsset(assetId: String): HashResult {
|
||||||
return try {
|
return try {
|
||||||
val assetUri = ContentUris.withAppendedId(
|
|
||||||
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
|
|
||||||
assetId.toLong()
|
|
||||||
)
|
|
||||||
|
|
||||||
val digest = MessageDigest.getInstance("SHA-1")
|
val digest = MessageDigest.getInstance("SHA-1")
|
||||||
ctx.contentResolver.openInputStream(assetUri)?.use { inputStream ->
|
openOriginalStream(assetId).use { inputStream ->
|
||||||
var bytesRead: Int
|
|
||||||
val buffer = ByteArray(HASH_BUFFER_SIZE)
|
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()
|
currentCoroutineContext().ensureActive()
|
||||||
digest.update(buffer, 0, bytesRead)
|
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, Base64.encodeToString(digest.digest(), Base64.NO_WRAP))
|
||||||
HashResult(assetId, null, hashString)
|
} catch (e: CancellationException) {
|
||||||
|
throw e
|
||||||
} catch (e: SecurityException) {
|
} catch (e: SecurityException) {
|
||||||
HashResult(assetId, "Permission denied accessing asset: ${e.message}", null)
|
HashResult(assetId, "Permission denied accessing asset: ${e.message}", null)
|
||||||
} catch (e: Exception) {
|
} 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() {
|
fun cancelHashing() {
|
||||||
hashTask?.cancel()
|
hashTask?.cancel()
|
||||||
hashTask = null
|
hashTask = null
|
||||||
|
|
@ -476,8 +509,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
||||||
syncJob = CoroutineScope(Dispatchers.IO).launch {
|
syncJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
try {
|
try {
|
||||||
completeWhenActive(callback, Result.success(work()))
|
completeWhenActive(callback, Result.success(work()))
|
||||||
} catch (e: CancellationException) {
|
} catch (_: CancellationException) {
|
||||||
completeWhenActive(callback, Result.failure(FlutterError(SYNC_CANCELLED_CODE, "Sync cancelled", null)))
|
completeWhenActive(
|
||||||
|
callback,
|
||||||
|
Result.failure(FlutterError(SYNC_CANCELLED_CODE, "Sync cancelled", null))
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
completeWhenActive(callback, Result.failure(e))
|
completeWhenActive(callback, Result.failure(e))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// ignore_for_file: avoid_print
|
// ignore_for_file: avoid_slow_async_io, avoid_print
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
|
||||||
418
mobile/ios/Runner/Background/BackgroundWorker.g.swift
generated
418
mobile/ios/Runner/Background/BackgroundWorker.g.swift
generated
|
|
@ -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(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
129
mobile/ios/Runner/Connectivity/Connectivity.g.swift
generated
129
mobile/ios/Runner/Connectivity/Connectivity.g.swift
generated
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
406
mobile/ios/Runner/Core/Network.g.swift
generated
406
mobile/ios/Runner/Core/Network.g.swift
generated
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
139
mobile/ios/Runner/Images/LocalImages.g.swift
generated
139
mobile/ios/Runner/Images/LocalImages.g.swift
generated
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -20,6 +20,7 @@ class LocalImageApiImpl: LocalImageApi {
|
||||||
requestOptions.version = .current
|
requestOptions.version = .current
|
||||||
return requestOptions
|
return requestOptions
|
||||||
}()
|
}()
|
||||||
|
private static let maxPixelSize: CGFloat = 16384
|
||||||
|
|
||||||
private static let registry = RequestRegistry<ImageRequest>()
|
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?
|
var image: UIImage?
|
||||||
Self.imageManager.requestImage(
|
Self.imageManager.requestImage(
|
||||||
for: asset,
|
for: asset,
|
||||||
targetSize: width > 0 && height > 0 ? CGSize(width: Double(width), height: Double(height)) : PHImageManagerMaximumSize,
|
targetSize: targetSize,
|
||||||
contentMode: .aspectFill,
|
contentMode: contentMode,
|
||||||
options: Self.requestOptions,
|
options: Self.requestOptions,
|
||||||
resultHandler: { (_image, info) -> Void in
|
resultHandler: { (_image, info) -> Void in
|
||||||
image = _image
|
image = _image
|
||||||
|
|
@ -123,12 +129,38 @@ class LocalImageApiImpl: LocalImageApi {
|
||||||
return request.completion(ImageProcessing.cancelledResult)
|
return request.completion(ImageProcessing.cancelledResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let image = image,
|
guard let fastImage = image,
|
||||||
let cgImage = image.cgImage else {
|
var cgImage = fastImage.cgImage else {
|
||||||
Self.registry.remove(requestId: requestId)
|
Self.registry.remove(requestId: requestId)
|
||||||
return request.completion(.failure(PigeonError(code: "", message: "Could not get pixel data for \(assetId)", details: nil)))
|
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 {
|
if request.isCancelled {
|
||||||
return request.completion(ImageProcessing.cancelledResult)
|
return request.completion(ImageProcessing.cancelledResult)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
134
mobile/ios/Runner/Images/RemoteImages.g.swift
generated
134
mobile/ios/Runner/Images/RemoteImages.g.swift
generated
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
168
mobile/ios/Runner/Permission/PermissionApi.g.swift
generated
168
mobile/ios/Runner/Permission/PermissionApi.g.swift
generated
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
777
mobile/ios/Runner/Sync/Messages.g.swift
generated
777
mobile/ios/Runner/Sync/Messages.g.swift
generated
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -12,13 +12,15 @@ struct ImageEntry: TimelineEntry {
|
||||||
var subtitle: String? = nil
|
var subtitle: String? = nil
|
||||||
var error: WidgetError? = nil
|
var error: WidgetError? = nil
|
||||||
var deepLink: URL? = nil
|
var deepLink: URL? = nil
|
||||||
|
var forceFullColor: Bool = true
|
||||||
}
|
}
|
||||||
|
|
||||||
static func build(
|
static func build(
|
||||||
api: ImmichAPI,
|
api: ImmichAPI,
|
||||||
asset: Asset,
|
asset: Asset,
|
||||||
dateOffset: Int,
|
dateOffset: Int,
|
||||||
subtitle: String? = nil
|
subtitle: String? = nil,
|
||||||
|
forceFullColor: Bool = true
|
||||||
)
|
)
|
||||||
async throws -> Self
|
async throws -> Self
|
||||||
{
|
{
|
||||||
|
|
@ -34,7 +36,8 @@ struct ImageEntry: TimelineEntry {
|
||||||
image: image,
|
image: image,
|
||||||
metadata: EntryMetadata(
|
metadata: EntryMetadata(
|
||||||
subtitle: subtitle,
|
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(
|
func generateRandomEntries(
|
||||||
api: ImmichAPI,
|
api: ImmichAPI,
|
||||||
now: Date,
|
now: Date,
|
||||||
count: Int,
|
count: Int,
|
||||||
filter: SearchFilter = Album.NONE.filter,
|
filter: SearchFilter = Album.NONE.filter,
|
||||||
subtitle: String? = nil
|
subtitle: String? = nil,
|
||||||
|
forceFullColor: Bool = true
|
||||||
)
|
)
|
||||||
async throws -> [ImageEntry]
|
async throws -> [ImageEntry]
|
||||||
{
|
{
|
||||||
|
|
@ -132,7 +149,8 @@ func generateRandomEntries(
|
||||||
api: api,
|
api: api,
|
||||||
asset: asset,
|
asset: asset,
|
||||||
dateOffset: dateOffset,
|
dateOffset: dateOffset,
|
||||||
subtitle: subtitle
|
subtitle: subtitle,
|
||||||
|
forceFullColor: forceFullColor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ import WidgetKit
|
||||||
|
|
||||||
extension Image {
|
extension Image {
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func tintedWidgetImageModifier() -> some View {
|
func tintedWidgetImageModifier(forceFullColor: Bool) -> some View {
|
||||||
if #available(iOS 18.0, *) {
|
if #available(iOS 18.0, *) {
|
||||||
self
|
self
|
||||||
.widgetAccentedRenderingMode(.accentedDesaturated)
|
.widgetAccentedRenderingMode(forceFullColor ? .accentedDesaturated : .fullColor)
|
||||||
} else {
|
} else {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -18,15 +18,24 @@ struct ImmichWidgetView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if let image = entry.image {
|
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 {
|
} else {
|
||||||
ImmichWidgetLoadingView(message: entry.metadata.error?.errorDescription)
|
ImmichWidgetLoadingView(
|
||||||
|
message: entry.metadata.error?.errorDescription,
|
||||||
|
forceFullColor: entry.metadata.forceFullColor
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct ImmichWidgetLoadingView: View {
|
private struct ImmichWidgetLoadingView: View {
|
||||||
let message: String?
|
let message: String?
|
||||||
|
let forceFullColor: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let messageText = Text(message ?? "")
|
let messageText = Text(message ?? "")
|
||||||
|
|
@ -39,7 +48,7 @@ private struct ImmichWidgetLoadingView: View {
|
||||||
messageText.hidden()
|
messageText.hidden()
|
||||||
|
|
||||||
Image("LaunchImage")
|
Image("LaunchImage")
|
||||||
.tintedWidgetImageModifier()
|
.tintedWidgetImageModifier(forceFullColor: forceFullColor)
|
||||||
|
|
||||||
messageText
|
messageText
|
||||||
}
|
}
|
||||||
|
|
@ -50,13 +59,14 @@ private struct ImmichWidgetContentView: View {
|
||||||
let image: UIImage
|
let image: UIImage
|
||||||
let subtitle: String?
|
let subtitle: String?
|
||||||
let deepLink: URL?
|
let deepLink: URL?
|
||||||
|
let forceFullColor: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .leading) {
|
ZStack(alignment: .leading) {
|
||||||
Color.clear.overlay(
|
Color.clear.overlay(
|
||||||
Image(uiImage: image)
|
Image(uiImage: image)
|
||||||
.resizable()
|
.resizable()
|
||||||
.tintedWidgetImageModifier()
|
.tintedWidgetImageModifier(forceFullColor: forceFullColor)
|
||||||
.scaledToFill()
|
.scaledToFill()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,21 @@ import AppIntents
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import WidgetKit
|
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 {
|
func getYearDifferenceSubtitle(assetYear: Int) -> String {
|
||||||
let currentYear = Calendar.current.component(.year, from: Date.now)
|
let currentYear = Calendar.current.component(.year, from: Date.now)
|
||||||
// construct a "X years ago" subtitle
|
// construct a "X years ago" subtitle
|
||||||
|
|
@ -15,144 +29,132 @@ struct ImmichMemoryProvider: TimelineProvider {
|
||||||
ImageEntry(date: Date(), image: nil)
|
ImageEntry(date: Date(), image: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSnapshot(
|
func snapshot(
|
||||||
in context: Context,
|
for configuration: MemoryConfigurationAppIntent,
|
||||||
completion: @escaping @Sendable (ImageEntry) -> Void
|
in context: Context
|
||||||
) {
|
) async -> ImageEntry {
|
||||||
let cacheKey = "memory_\(context.family.rawValue)"
|
let cacheKey = "memory_\(context.family.rawValue)"
|
||||||
|
|
||||||
Task {
|
guard let api = try? await ImmichAPI() else {
|
||||||
guard let api = try? await ImmichAPI() else {
|
return ImageEntry.handleError(for: cacheKey, error: .noLogin).entries
|
||||||
completion(
|
.first!
|
||||||
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 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(
|
func timeline(
|
||||||
in context: Context,
|
for configuration: MemoryConfigurationAppIntent,
|
||||||
completion: @escaping @Sendable (Timeline<ImageEntry>) -> Void
|
in context: Context
|
||||||
) {
|
) async
|
||||||
Task {
|
-> Timeline<ImageEntry>
|
||||||
var entries: [ImageEntry] = []
|
{
|
||||||
let now = Date()
|
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 {
|
guard let api = try? await ImmichAPI() else {
|
||||||
completion(
|
return ImageEntry.handleError(for: cacheKey, error: .noLogin)
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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"
|
let kind: String = "com.immich.widget.memory"
|
||||||
|
|
||||||
var body: some WidgetConfiguration {
|
var body: some WidgetConfiguration {
|
||||||
StaticConfiguration(
|
AppIntentConfiguration(
|
||||||
kind: kind,
|
kind: kind,
|
||||||
|
intent: MemoryConfigurationAppIntent.self,
|
||||||
provider: ImmichMemoryProvider()
|
provider: ImmichMemoryProvider()
|
||||||
) { entry in
|
) { entry in
|
||||||
ImmichWidgetView(entry: entry)
|
ImmichWidgetView(entry: entry)
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,9 @@ struct RandomConfigurationAppIntent: WidgetConfigurationIntent {
|
||||||
|
|
||||||
@Parameter(title: "Show Album Name", default: false)
|
@Parameter(title: "Show Album Name", default: false)
|
||||||
var showAlbumName: Bool
|
var showAlbumName: Bool
|
||||||
|
|
||||||
|
@Parameter(title: "Always Display in Full Color", default: false)
|
||||||
|
var forceFullColor: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Provider
|
// MARK: Provider
|
||||||
|
|
@ -76,7 +79,8 @@ struct ImmichRandomProvider: AppIntentTimelineProvider {
|
||||||
let entry = try? await ImageEntry.build(
|
let entry = try? await ImageEntry.build(
|
||||||
api: api,
|
api: api,
|
||||||
asset: randomImage,
|
asset: randomImage,
|
||||||
dateOffset: 0
|
dateOffset: 0,
|
||||||
|
forceFullColor: configuration.forceFullColor
|
||||||
)
|
)
|
||||||
else {
|
else {
|
||||||
return ImageEntry.handleError(for: cacheKey).entries.first!
|
return ImageEntry.handleError(for: cacheKey).entries.first!
|
||||||
|
|
@ -114,7 +118,8 @@ struct ImmichRandomProvider: AppIntentTimelineProvider {
|
||||||
now: now,
|
now: now,
|
||||||
count: 12,
|
count: 12,
|
||||||
filter: album.filter,
|
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
|
// Load or save a cached asset for when network conditions are bad
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,8 @@ class CropAspectRatio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const aspectRatioFree = CropAspectRatio(customLabel: "Free", icon: Icons.crop_free);
|
const aspectRatioFree = CropAspectRatio.free;
|
||||||
const aspectRatioOriginal = CropAspectRatio(customLabel: "Original", icon: Icons.crop_original);
|
const aspectRatioOriginal = CropAspectRatio.original;
|
||||||
|
|
||||||
final aspectRatioPresets = [
|
final aspectRatioPresets = [
|
||||||
CropAspectRatio.free,
|
CropAspectRatio.free,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ const Map<String, Locale> locales = {
|
||||||
'Arabic (ar)': Locale('ar'),
|
'Arabic (ar)': Locale('ar'),
|
||||||
'Basque (eu)': Locale('eu'),
|
'Basque (eu)': Locale('eu'),
|
||||||
'Belarusian (be)': Locale('be'),
|
'Belarusian (be)': Locale('be'),
|
||||||
'Bosnian (bl)': Locale('bn'),
|
'Bengali (bn)': Locale('bn'),
|
||||||
'Brazilian Portuguese (pt_BR)': Locale('pt', 'BR'),
|
'Brazilian Portuguese (pt_BR)': Locale('pt', 'BR'),
|
||||||
'Bulgarian (bg)': Locale('bg'),
|
'Bulgarian (bg)': Locale('bg'),
|
||||||
'Catalan (ca)': Locale('ca'),
|
'Catalan (ca)': Locale('ca'),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'album.model.freezed.dart';
|
||||||
|
|
||||||
enum AlbumAssetOrder {
|
enum AlbumAssetOrder {
|
||||||
// do not change this order!
|
// do not change this order!
|
||||||
asc,
|
asc,
|
||||||
|
|
@ -12,118 +17,20 @@ enum AlbumUserRole {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Model for an album stored in the server
|
// Model for an album stored in the server
|
||||||
class RemoteAlbum {
|
@freezed
|
||||||
final String id;
|
abstract class RemoteAlbum with _$RemoteAlbum {
|
||||||
final String name;
|
const factory RemoteAlbum({
|
||||||
final String ownerId;
|
required String id,
|
||||||
final String description;
|
required String name,
|
||||||
final DateTime createdAt;
|
required String ownerId,
|
||||||
final DateTime updatedAt;
|
required String description,
|
||||||
final String? thumbnailAssetId;
|
required DateTime createdAt,
|
||||||
final bool isActivityEnabled;
|
required DateTime updatedAt,
|
||||||
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,
|
|
||||||
String? thumbnailAssetId,
|
String? thumbnailAssetId,
|
||||||
bool? isActivityEnabled,
|
required bool isActivityEnabled,
|
||||||
AlbumAssetOrder? order,
|
required AlbumAssetOrder order,
|
||||||
int? assetCount,
|
required int assetCount,
|
||||||
String? ownerName,
|
required String ownerName,
|
||||||
bool? isShared,
|
required bool isShared,
|
||||||
}) {
|
}) = _RemoteAlbum;
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'local_album.model.freezed.dart';
|
||||||
|
|
||||||
enum BackupSelection {
|
enum BackupSelection {
|
||||||
// Used to sort albums based on the backupSelection
|
// Used to sort albums based on the backupSelection
|
||||||
// selected -> none -> excluded
|
// selected -> none -> excluded
|
||||||
|
|
@ -7,85 +12,15 @@ enum BackupSelection {
|
||||||
excluded,
|
excluded,
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalAlbum {
|
@freezed
|
||||||
final String id;
|
abstract class LocalAlbum with _$LocalAlbum {
|
||||||
final String name;
|
const factory LocalAlbum({
|
||||||
final DateTime updatedAt;
|
required String id,
|
||||||
final bool isIosSharedAlbum;
|
required String name,
|
||||||
|
required DateTime updatedAt,
|
||||||
final int assetCount;
|
@Default(0) int assetCount,
|
||||||
final BackupSelection backupSelection;
|
@Default(BackupSelection.none) BackupSelection backupSelection,
|
||||||
final String? linkedRemoteAlbumId;
|
@Default(false) bool isIosSharedAlbum,
|
||||||
|
|
||||||
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,
|
|
||||||
String? linkedRemoteAlbumId,
|
String? linkedRemoteAlbumId,
|
||||||
}) {
|
}) = _LocalAlbum;
|
||||||
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,
|
|
||||||
}''';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ sealed class BaseAsset {
|
||||||
if (durationMs != null) {
|
if (durationMs != null) {
|
||||||
return Duration(milliseconds: durationMs);
|
return Duration(milliseconds: durationMs);
|
||||||
}
|
}
|
||||||
return const Duration();
|
return Duration.zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get hasRemote => storage == AssetState.remote || storage == AssetState.merged;
|
bool get hasRemote => storage == AssetState.remote || storage == AssetState.merged;
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
enum AssetEditAction { rotate, crop, mirror, other }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
// Model for an asset face stored in the server
|
||||||
class AssetFace {
|
@freezed
|
||||||
final String id;
|
abstract class AssetFace with _$AssetFace {
|
||||||
final String assetId;
|
const factory AssetFace({
|
||||||
final String? personId;
|
required String id,
|
||||||
final int imageWidth;
|
required String assetId,
|
||||||
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,
|
|
||||||
String? personId,
|
String? personId,
|
||||||
int? imageWidth,
|
required int imageWidth,
|
||||||
int? imageHeight,
|
required int imageHeight,
|
||||||
int? boundingBoxX1,
|
required int boundingBoxX1,
|
||||||
int? boundingBoxY1,
|
required int boundingBoxY1,
|
||||||
int? boundingBoxX2,
|
required int boundingBoxX2,
|
||||||
int? boundingBoxY2,
|
required int boundingBoxY2,
|
||||||
String? sourceType,
|
required String sourceType,
|
||||||
}) {
|
}) = _AssetFace;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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';
|
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||||
|
|
||||||
class AlbumConfig {
|
part 'album_config.freezed.dart';
|
||||||
final AlbumSortMode sortMode;
|
|
||||||
final bool isReverse;
|
|
||||||
final bool isGrid;
|
|
||||||
|
|
||||||
const AlbumConfig({this.sortMode = AlbumSortMode.mostRecent, this.isReverse = true, this.isGrid = false});
|
@freezed
|
||||||
|
abstract class AlbumConfig with _$AlbumConfig {
|
||||||
AlbumConfig copyWith({AlbumSortMode? sortMode, bool? isReverse, bool? isGrid}) => AlbumConfig(
|
const factory AlbumConfig({
|
||||||
sortMode: sortMode ?? this.sortMode,
|
@Default(AlbumSortMode.mostRecent) AlbumSortMode sortMode,
|
||||||
isReverse: isReverse ?? this.isReverse,
|
@Default(true) bool isReverse,
|
||||||
isGrid: isGrid ?? this.isGrid,
|
@Default(false) bool isGrid,
|
||||||
);
|
}) = _AlbumConfig;
|
||||||
|
|
||||||
@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)';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.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/colors.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/album_config.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/providers/album/album_sort_by_options.provider.dart';
|
||||||
import 'package:immich_mobile/utils/semver.dart';
|
import 'package:immich_mobile/utils/semver.dart';
|
||||||
|
|
||||||
|
part 'app_config.freezed.dart';
|
||||||
|
|
||||||
const defaultConfig = AppConfig();
|
const defaultConfig = AppConfig();
|
||||||
|
|
||||||
class AppConfig {
|
@freezed
|
||||||
final LogLevel logLevel;
|
abstract class AppConfig with _$AppConfig {
|
||||||
final ThemeConfig theme;
|
const AppConfig._();
|
||||||
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;
|
|
||||||
|
|
||||||
const AppConfig({
|
const factory AppConfig({
|
||||||
this.logLevel = .info,
|
@Default(LogLevel.info) LogLevel logLevel,
|
||||||
this.theme = const .new(),
|
@Default(ThemeConfig()) ThemeConfig theme,
|
||||||
this.cleanup = const .new(),
|
@Default(CleanupConfig()) CleanupConfig cleanup,
|
||||||
this.map = const .new(),
|
@Default(MapConfig()) MapConfig map,
|
||||||
this.timeline = const .new(),
|
@Default(TimelineConfig()) TimelineConfig timeline,
|
||||||
this.image = const .new(),
|
@Default(ImageConfig()) ImageConfig image,
|
||||||
this.viewer = const .new(),
|
@Default(ViewerConfig()) ViewerConfig viewer,
|
||||||
this.slideshow = const .new(),
|
@Default(SlideshowConfig()) SlideshowConfig slideshow,
|
||||||
this.album = const .new(),
|
@Default(AlbumConfig()) AlbumConfig album,
|
||||||
this.backup = const .new(),
|
@Default(BackupConfig()) BackupConfig backup,
|
||||||
this.network = const .new(),
|
@Default(NetworkConfig()) NetworkConfig network,
|
||||||
this.share = const .new(),
|
@Default(ShareConfig()) ShareConfig share,
|
||||||
this.featureMessage = const .new(),
|
@Default(FeatureMessageConfig()) FeatureMessageConfig featureMessage,
|
||||||
});
|
}) = _AppConfig;
|
||||||
|
|
||||||
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)';
|
|
||||||
|
|
||||||
T read<T>(SettingsKey<T> key) =>
|
T read<T>(SettingsKey<T> key) =>
|
||||||
(switch (key) {
|
(switch (key) {
|
||||||
|
|
@ -190,9 +114,9 @@ class AppConfig {
|
||||||
.viewerTapToNavigate => copyWith(viewer: viewer.copyWith(tapToNavigate: value as bool)),
|
.viewerTapToNavigate => copyWith(viewer: viewer.copyWith(tapToNavigate: value as bool)),
|
||||||
.networkAutoEndpointSwitching => copyWith(network: network.copyWith(autoEndpointSwitching: value as bool)),
|
.networkAutoEndpointSwitching => copyWith(network: network.copyWith(autoEndpointSwitching: value as bool)),
|
||||||
.networkPreferredWifiName => copyWith(
|
.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>)),
|
.networkExternalEndpointList => copyWith(network: network.copyWith(externalEndpointList: value as List<String>)),
|
||||||
.networkCustomHeaders => copyWith(network: network.copyWith(customHeaders: value as Map<String, String>)),
|
.networkCustomHeaders => copyWith(network: network.copyWith(customHeaders: value as Map<String, String>)),
|
||||||
.albumSortMode => copyWith(album: album.copyWith(sortMode: value as AlbumSortMode)),
|
.albumSortMode => copyWith(album: album.copyWith(sortMode: value as AlbumSortMode)),
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,16 @@
|
||||||
class BackupConfig {
|
import 'package:flutter/foundation.dart';
|
||||||
final bool enabled;
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
final bool useCellularForVideos;
|
|
||||||
final bool useCellularForPhotos;
|
|
||||||
final bool requireCharging;
|
|
||||||
final int triggerDelay;
|
|
||||||
final bool syncAlbums;
|
|
||||||
|
|
||||||
const BackupConfig({
|
part 'backup_config.freezed.dart';
|
||||||
this.enabled = false,
|
|
||||||
this.useCellularForVideos = false,
|
|
||||||
this.useCellularForPhotos = false,
|
|
||||||
this.requireCharging = false,
|
|
||||||
this.triggerDelay = 30,
|
|
||||||
this.syncAlbums = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
BackupConfig copyWith({
|
@freezed
|
||||||
bool? enabled,
|
abstract class BackupConfig with _$BackupConfig {
|
||||||
bool? useCellularForVideos,
|
const factory BackupConfig({
|
||||||
bool? useCellularForPhotos,
|
@Default(false) bool enabled,
|
||||||
bool? requireCharging,
|
@Default(false) bool useCellularForVideos,
|
||||||
int? triggerDelay,
|
@Default(false) bool useCellularForPhotos,
|
||||||
bool? syncAlbums,
|
@Default(false) bool requireCharging,
|
||||||
}) => BackupConfig(
|
@Default(30) int triggerDelay,
|
||||||
enabled: enabled ?? this.enabled,
|
@Default(false) bool syncAlbums,
|
||||||
useCellularForVideos: useCellularForVideos ?? this.useCellularForVideos,
|
}) = _BackupConfig;
|
||||||
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)';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,11 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:immich_mobile/utils/semver.dart';
|
import 'package:immich_mobile/utils/semver.dart';
|
||||||
|
|
||||||
class FeatureMessageConfig {
|
part 'feature_message_config.freezed.dart';
|
||||||
final SemVer seenRelease;
|
|
||||||
|
|
||||||
const FeatureMessageConfig({this.seenRelease = const SemVer(major: 0, minor: 0, patch: 0)});
|
@freezed
|
||||||
|
abstract class FeatureMessageConfig with _$FeatureMessageConfig {
|
||||||
FeatureMessageConfig copyWith({SemVer? seenRelease}) =>
|
const factory FeatureMessageConfig({@Default(SemVer(major: 0, minor: 0, patch: 0)) SemVer seenRelease}) =
|
||||||
FeatureMessageConfig(seenRelease: seenRelease ?? this.seenRelease);
|
_FeatureMessageConfig;
|
||||||
|
|
||||||
@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)';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,9 @@
|
||||||
class ImageConfig {
|
import 'package:flutter/foundation.dart';
|
||||||
final bool preferRemote;
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
final bool loadOriginal;
|
|
||||||
|
|
||||||
const ImageConfig({this.preferRemote = false, this.loadOriginal = false});
|
part 'image_config.freezed.dart';
|
||||||
|
|
||||||
ImageConfig copyWith({bool? preferRemote, bool? loadOriginal}) =>
|
@freezed
|
||||||
ImageConfig(preferRemote: preferRemote ?? this.preferRemote, loadOriginal: loadOriginal ?? this.loadOriginal);
|
abstract class ImageConfig with _$ImageConfig {
|
||||||
|
const factory ImageConfig({@Default(false) bool preferRemote, @Default(false) bool loadOriginal}) = _ImageConfig;
|
||||||
@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)';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,10 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
|
|
||||||
class ShareConfig {
|
part 'share_config.freezed.dart';
|
||||||
final ShareAssetType fileType;
|
|
||||||
|
|
||||||
const ShareConfig({this.fileType = ShareAssetType.original});
|
@freezed
|
||||||
|
abstract class ShareConfig with _$ShareConfig {
|
||||||
ShareConfig copyWith({ShareAssetType? fileType}) => ShareConfig(fileType: fileType ?? this.fileType);
|
const factory ShareConfig({@Default(ShareAssetType.original) ShareAssetType fileType}) = _ShareConfig;
|
||||||
|
|
||||||
@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)';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,15 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
|
|
||||||
class SlideshowConfig {
|
part 'slideshow_config.freezed.dart';
|
||||||
final bool repeat;
|
|
||||||
final int duration;
|
|
||||||
final SlideshowLook look;
|
|
||||||
final SlideshowDirection direction;
|
|
||||||
|
|
||||||
const SlideshowConfig({
|
@freezed
|
||||||
this.repeat = true,
|
abstract class SlideshowConfig with _$SlideshowConfig {
|
||||||
this.duration = 5,
|
const factory SlideshowConfig({
|
||||||
this.look = SlideshowLook.blurredBackground,
|
@Default(true) bool repeat,
|
||||||
this.direction = SlideshowDirection.forward,
|
@Default(5) int duration,
|
||||||
});
|
@Default(SlideshowLook.blurredBackground) SlideshowLook look,
|
||||||
|
@Default(SlideshowDirection.forward) SlideshowDirection direction,
|
||||||
SlideshowConfig copyWith({bool? repeat, int? duration, SlideshowLook? look, SlideshowDirection? direction}) =>
|
}) = _SlideshowConfig;
|
||||||
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)';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,16 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.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/colors.dart';
|
||||||
|
|
||||||
class ThemeConfig {
|
part 'theme_config.freezed.dart';
|
||||||
final ThemeMode mode;
|
|
||||||
final ImmichColorPreset primaryColor;
|
|
||||||
final bool dynamicTheme;
|
|
||||||
final bool colorfulInterface;
|
|
||||||
|
|
||||||
const ThemeConfig({
|
@freezed
|
||||||
this.mode = .system,
|
abstract class ThemeConfig with _$ThemeConfig {
|
||||||
this.primaryColor = .indigo,
|
const factory ThemeConfig({
|
||||||
this.dynamicTheme = false,
|
@Default(ThemeMode.system) ThemeMode mode,
|
||||||
this.colorfulInterface = true,
|
@Default(ImmichColorPreset.indigo) ImmichColorPreset primaryColor,
|
||||||
});
|
@Default(false) bool dynamicTheme,
|
||||||
|
@Default(true) bool colorfulInterface,
|
||||||
ThemeConfig copyWith({
|
}) = _ThemeConfig;
|
||||||
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)';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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';
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
|
|
||||||
class TimelineConfig {
|
part 'timeline_config.freezed.dart';
|
||||||
final int tilesPerRow;
|
|
||||||
final GroupAssetsBy groupAssetsBy;
|
|
||||||
final bool storageIndicator;
|
|
||||||
|
|
||||||
const TimelineConfig({this.tilesPerRow = 4, this.groupAssetsBy = GroupAssetsBy.day, this.storageIndicator = true});
|
@freezed
|
||||||
|
abstract class TimelineConfig with _$TimelineConfig {
|
||||||
TimelineConfig copyWith({int? tilesPerRow, GroupAssetsBy? groupAssetsBy, bool? storageIndicator}) => TimelineConfig(
|
const factory TimelineConfig({
|
||||||
tilesPerRow: tilesPerRow ?? this.tilesPerRow,
|
@Default(4) int tilesPerRow,
|
||||||
groupAssetsBy: groupAssetsBy ?? this.groupAssetsBy,
|
@Default(GroupAssetsBy.day) GroupAssetsBy groupAssetsBy,
|
||||||
storageIndicator: storageIndicator ?? this.storageIndicator,
|
@Default(true) bool storageIndicator,
|
||||||
);
|
}) = _TimelineConfig;
|
||||||
|
|
||||||
@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)';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,14 @@
|
||||||
class ViewerConfig {
|
import 'package:flutter/foundation.dart';
|
||||||
final bool loopVideo;
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
final bool loadOriginalVideo;
|
|
||||||
final bool autoPlayVideo;
|
|
||||||
final bool tapToNavigate;
|
|
||||||
|
|
||||||
const ViewerConfig({
|
part 'viewer_config.freezed.dart';
|
||||||
this.loopVideo = true,
|
|
||||||
this.loadOriginalVideo = false,
|
|
||||||
this.autoPlayVideo = true,
|
|
||||||
this.tapToNavigate = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
ViewerConfig copyWith({bool? loopVideo, bool? loadOriginalVideo, bool? autoPlayVideo, bool? tapToNavigate}) =>
|
@freezed
|
||||||
ViewerConfig(
|
abstract class ViewerConfig with _$ViewerConfig {
|
||||||
loopVideo: loopVideo ?? this.loopVideo,
|
const factory ViewerConfig({
|
||||||
loadOriginalVideo: loadOriginalVideo ?? this.loadOriginalVideo,
|
@Default(true) bool loopVideo,
|
||||||
autoPlayVideo: autoPlayVideo ?? this.autoPlayVideo,
|
@Default(false) bool loadOriginalVideo,
|
||||||
tapToNavigate: tapToNavigate ?? this.tapToNavigate,
|
@Default(true) bool autoPlayVideo,
|
||||||
);
|
@Default(false) bool tapToNavigate,
|
||||||
|
}) = _ViewerConfig;
|
||||||
@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)';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,40 @@
|
||||||
class ExifInfo {
|
import 'package:flutter/foundation.dart';
|
||||||
final int? assetId;
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
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;
|
|
||||||
|
|
||||||
// GPS
|
part 'exif.model.freezed.dart';
|
||||||
final double? latitude;
|
|
||||||
final double? longitude;
|
|
||||||
final String? city;
|
|
||||||
final String? state;
|
|
||||||
final String? country;
|
|
||||||
|
|
||||||
// Camera related
|
@freezed
|
||||||
final String? make;
|
abstract class ExifInfo with _$ExifInfo {
|
||||||
final String? model;
|
const ExifInfo._();
|
||||||
final String? lens;
|
|
||||||
final double? f;
|
const factory ExifInfo({
|
||||||
final double? mm;
|
int? assetId,
|
||||||
final int? iso;
|
int? fileSize,
|
||||||
final double? exposureSeconds;
|
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;
|
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 fNumber => f == null ? "" : f!.toStringAsFixed(1);
|
||||||
|
|
||||||
String get focalLength => mm == null ? "" : mm!.toStringAsFixed(3);
|
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,128 +1,24 @@
|
||||||
class Ocr {
|
import 'package:flutter/foundation.dart';
|
||||||
final String id;
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
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;
|
|
||||||
|
|
||||||
const Ocr({
|
part 'ocr.model.freezed.dart';
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ocr copyWith({
|
@freezed
|
||||||
String? id,
|
abstract class Ocr with _$Ocr {
|
||||||
String? assetId,
|
const factory Ocr({
|
||||||
double? x1,
|
required String id,
|
||||||
double? y1,
|
required String assetId,
|
||||||
double? x2,
|
required double x1,
|
||||||
double? y2,
|
required double y1,
|
||||||
double? x3,
|
required double x2,
|
||||||
double? y3,
|
required double y2,
|
||||||
double? x4,
|
required double x3,
|
||||||
double? y4,
|
required double y3,
|
||||||
double? boxScore,
|
required double x4,
|
||||||
double? textScore,
|
required double y4,
|
||||||
String? text,
|
required double boxScore,
|
||||||
bool? isVisible,
|
required double textScore,
|
||||||
}) {
|
required String text,
|
||||||
return Ocr(
|
required bool isVisible,
|
||||||
id: id ?? this.id,
|
}) = _Ocr;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
import 'dart:convert';
|
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
|
// TODO: Remove PersonDto once Isar is removed
|
||||||
class PersonDto {
|
class PersonDto {
|
||||||
const PersonDto({
|
const PersonDto({
|
||||||
|
|
@ -93,102 +98,18 @@ class PersonDto {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Model for a person stored in the server
|
// Model for a person stored in the server
|
||||||
class DriftPerson {
|
@freezed
|
||||||
final String id;
|
abstract class DriftPerson with _$DriftPerson {
|
||||||
final DateTime createdAt;
|
const factory DriftPerson({
|
||||||
final DateTime updatedAt;
|
required String id,
|
||||||
final String ownerId;
|
required DateTime createdAt,
|
||||||
final String name;
|
required DateTime updatedAt,
|
||||||
final String? faceAssetId;
|
required String ownerId,
|
||||||
final bool isFavorite;
|
required String name,
|
||||||
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,
|
|
||||||
String? faceAssetId,
|
String? faceAssetId,
|
||||||
bool? isFavorite,
|
required bool isFavorite,
|
||||||
bool? isHidden,
|
required bool isHidden,
|
||||||
String? color,
|
required String? color,
|
||||||
DateTime? birthDate,
|
DateTime? birthDate,
|
||||||
}) {
|
}) = _DriftPerson;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
// Model for a stack stored in the server
|
||||||
class Stack {
|
@freezed
|
||||||
final String id;
|
abstract class Stack with _$Stack {
|
||||||
final DateTime createdAt;
|
const factory Stack({
|
||||||
final DateTime updatedAt;
|
required String id,
|
||||||
final String ownerId;
|
required DateTime createdAt,
|
||||||
final String primaryAssetId;
|
required DateTime updatedAt,
|
||||||
|
required String ownerId,
|
||||||
const Stack({
|
required String primaryAssetId,
|
||||||
required this.id,
|
}) = _Stack;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class StackResponse {
|
class StackResponse {
|
||||||
|
|
|
||||||
|
|
@ -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';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
|
||||||
|
part 'user_metadata.model.freezed.dart';
|
||||||
|
|
||||||
enum UserMetadataKey {
|
enum UserMetadataKey {
|
||||||
// do not change this order!
|
// do not change this order!
|
||||||
onboarding,
|
onboarding,
|
||||||
|
|
@ -23,7 +27,7 @@ class Onboarding {
|
||||||
}
|
}
|
||||||
|
|
||||||
factory Onboarding.fromMap(Map<String, Object?> map) {
|
factory Onboarding.fromMap(Map<String, Object?> map) {
|
||||||
return Onboarding(isOnboarded: map["isOnboarded"] as bool);
|
return Onboarding(isOnboarded: map["isOnboarded"]! as bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -195,9 +199,9 @@ class License {
|
||||||
|
|
||||||
factory License.fromMap(Map<String, Object?> map) {
|
factory License.fromMap(Map<String, Object?> map) {
|
||||||
return License(
|
return License(
|
||||||
activatedAt: DateTime.parse(map["activatedAt"] as String),
|
activatedAt: DateTime.parse(map["activatedAt"]! as String),
|
||||||
activationKey: map["activationKey"] as String,
|
activationKey: map["activationKey"]! as String,
|
||||||
licenseKey: map["licenseKey"] as String,
|
licenseKey: map["licenseKey"]! as String,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,61 +228,17 @@ licenseKey: $licenseKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Model for a user metadata stored in the server
|
// Model for a user metadata stored in the server
|
||||||
class UserMetadata {
|
@freezed
|
||||||
final String userId;
|
abstract class UserMetadata with _$UserMetadata {
|
||||||
final UserMetadataKey key;
|
@Assert(
|
||||||
final Onboarding? onboarding;
|
'onboarding != null || preferences != null || license != null',
|
||||||
final Preferences? preferences;
|
'One of onboarding, preferences and license must be provided',
|
||||||
final License? license;
|
)
|
||||||
|
const factory UserMetadata({
|
||||||
const UserMetadata({required this.userId, required this.key, this.onboarding, this.preferences, this.license})
|
required String userId,
|
||||||
: assert(
|
required UserMetadataKey key,
|
||||||
onboarding != null || preferences != null || license != null,
|
|
||||||
'One of onboarding, preferences and license must be provided',
|
|
||||||
);
|
|
||||||
|
|
||||||
UserMetadata copyWith({
|
|
||||||
String? userId,
|
|
||||||
UserMetadataKey? key,
|
|
||||||
Onboarding? onboarding,
|
Onboarding? onboarding,
|
||||||
Preferences? preferences,
|
Preferences? preferences,
|
||||||
License? license,
|
License? license,
|
||||||
}) {
|
}) = _UserMetadata;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset_edit.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/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/local_asset.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/remote_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/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_api.repository.dart';
|
||||||
|
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||||
import 'package:immich_mobile/utils/option.dart';
|
import 'package:immich_mobile/utils/option.dart';
|
||||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||||
|
|
||||||
|
|
@ -14,12 +19,16 @@ class AssetService {
|
||||||
final RemoteExifRepository _exifRepository;
|
final RemoteExifRepository _exifRepository;
|
||||||
final DriftLocalAssetRepository _localRepository;
|
final DriftLocalAssetRepository _localRepository;
|
||||||
final AssetApiRepository _apiRepository;
|
final AssetApiRepository _apiRepository;
|
||||||
|
final AssetMediaRepository _mediaRepository;
|
||||||
|
final DriftTrashedLocalAssetRepository _trashedLocalRepository;
|
||||||
|
|
||||||
const AssetService({
|
const AssetService({
|
||||||
required this._remoteRepository,
|
required this._remoteRepository,
|
||||||
required this._exifRepository,
|
required this._exifRepository,
|
||||||
required this._localRepository,
|
required this._localRepository,
|
||||||
required this._apiRepository,
|
required this._apiRepository,
|
||||||
|
required this._mediaRepository,
|
||||||
|
required this._trashedLocalRepository,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<BaseAsset?> getAsset(BaseAsset asset) {
|
Future<BaseAsset?> getAsset(BaseAsset asset) {
|
||||||
|
|
@ -36,8 +45,8 @@ class AssetService {
|
||||||
return _localRepository.getByChecksum(checksum);
|
return _localRepository.getByChecksum(checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<RemoteAsset?> getRemoteAssetByChecksum(String checksum) {
|
Future<List<RemoteAsset>> getAllRemoteAssetDebugByChecksum(String checksum) {
|
||||||
return _remoteRepository.getByChecksum(checksum);
|
return _remoteRepository.getAllDebugForChecksum(checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<RemoteAsset?> getRemoteAsset(String id) {
|
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) {
|
Future<LocalAsset?> getLocalAsset(String id) {
|
||||||
return _localRepository.get(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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class HashService {
|
||||||
}) : _batchSize = batchSize ?? kBatchHashFileLimit {
|
}) : _batchSize = batchSize ?? kBatchHashFileLimit {
|
||||||
// Stop the in-flight native hash call promptly on cancellation; the loops
|
// Stop the in-flight native hash call promptly on cancellation; the loops
|
||||||
// below also observe [isCancelled] to bail between batches.
|
// 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;
|
bool get isCancelled => _cancellation?.isCompleted ?? false;
|
||||||
|
|
@ -66,11 +66,12 @@ class HashService {
|
||||||
await _hashAssets(pseudoAlbum, trashedToHash, isTrashed: true);
|
await _hashAssets(pseudoAlbum, trashedToHash, isTrashed: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e, s) {
|
||||||
if (e.code == _kHashCancelledCode) {
|
if (e.code == _kHashCancelledCode) {
|
||||||
_log.warning("Hashing cancelled by platform");
|
_log.warning("Hashing cancelled by platform");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_log.severe("Native hashing failed: ${e.code}", e, s);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
_log.severe("Error during hashing", e, s);
|
_log.severe("Error during hashing", e, s);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ class LocalSyncService {
|
||||||
required this._permissionRepository,
|
required this._permissionRepository,
|
||||||
this._cancellation,
|
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;
|
bool get _isCancelled => _cancellation?.isCompleted ?? false;
|
||||||
|
|
|
||||||
|
|
@ -105,10 +105,8 @@ class RemoteAlbumService {
|
||||||
switch (filterMode) {
|
switch (filterMode) {
|
||||||
case QuickFilterMode.myAlbums:
|
case QuickFilterMode.myAlbums:
|
||||||
filtered = filtered.where((album) => album.ownerId == userId).toList();
|
filtered = filtered.where((album) => album.ownerId == userId).toList();
|
||||||
break;
|
|
||||||
case QuickFilterMode.sharedWithMe:
|
case QuickFilterMode.sharedWithMe:
|
||||||
filtered = filtered.where((album) => album.ownerId != userId).toList();
|
filtered = filtered.where((album) => album.ownerId != userId).toList();
|
||||||
break;
|
|
||||||
case QuickFilterMode.all:
|
case QuickFilterMode.all:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ class SyncStreamService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleEvents(List<SyncEvent> events, Function() abort, Function() reset) async {
|
Future<void> _handleEvents(List<SyncEvent> events, Function() abort, Function() reset) async {
|
||||||
List<SyncEvent> items = [];
|
final List<SyncEvent> items = [];
|
||||||
for (final event in events) {
|
for (final event in events) {
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
_logger.warning("Sync stream cancelled");
|
_logger.warning("Sync stream cancelled");
|
||||||
|
|
|
||||||
|
|
@ -106,32 +106,34 @@ class TimelineService {
|
||||||
|
|
||||||
TimelineService._({required this._assetSource, required this._bucketSource, required this.origin}) {
|
TimelineService._({required this._assetSource, required this._bucketSource, required this.origin}) {
|
||||||
_bucketSubscription = _bucketSource().listen((buckets) {
|
_bucketSubscription = _bucketSource().listen((buckets) {
|
||||||
_mutex.run(() async {
|
unawaited(
|
||||||
final totalAssets = buckets.fold<int>(0, (acc, bucket) => acc + bucket.assetCount);
|
_mutex.run(() async {
|
||||||
|
final totalAssets = buckets.fold<int>(0, (acc, bucket) => acc + bucket.assetCount);
|
||||||
|
|
||||||
if (totalAssets == 0) {
|
if (totalAssets == 0) {
|
||||||
_bufferOffset = 0;
|
_bufferOffset = 0;
|
||||||
_buffer = [];
|
_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;
|
|
||||||
} else {
|
} else {
|
||||||
offset = _bufferOffset;
|
final int offset;
|
||||||
count = math.min(_buffer.length, totalAssets - _bufferOffset);
|
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
|
// change the state's total assets count only after the buffer is reloaded
|
||||||
_totalAssets = totalAssets;
|
_totalAssets = totalAssets;
|
||||||
EventStream.shared.emit(const TimelineReloadEvent());
|
EventStream.shared.emit(const TimelineReloadEvent());
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,7 +180,7 @@ class TimelineService {
|
||||||
if (!hasRange(index, count)) {
|
if (!hasRange(index, count)) {
|
||||||
throw RangeError('TimelineService::getAssets Index out of range');
|
throw RangeError('TimelineService::getAssets Index out of range');
|
||||||
}
|
}
|
||||||
int start = index - _bufferOffset;
|
final int start = index - _bufferOffset;
|
||||||
return _buffer.slice(start, start + count);
|
return _buffer.slice(start, start + count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ class BackgroundSyncManager {
|
||||||
final SyncErrorCallback? onCloudIdSyncError;
|
final SyncErrorCallback? onCloudIdSyncError;
|
||||||
|
|
||||||
Cancelable<bool?>? _syncTask;
|
Cancelable<bool?>? _syncTask;
|
||||||
|
bool _syncQueued = false;
|
||||||
Cancelable<void>? _syncWebsocketTask;
|
Cancelable<void>? _syncWebsocketTask;
|
||||||
Cancelable<void>? _cloudIdSyncTask;
|
Cancelable<void>? _cloudIdSyncTask;
|
||||||
Cancelable<void>? _deviceAlbumSyncTask;
|
Cancelable<void>? _deviceAlbumSyncTask;
|
||||||
|
|
@ -50,6 +51,7 @@ class BackgroundSyncManager {
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<void> cancel() async {
|
Future<void> cancel() async {
|
||||||
|
_syncQueued = false;
|
||||||
final tasks = [
|
final tasks = [
|
||||||
_syncTask,
|
_syncTask,
|
||||||
_syncWebsocketTask,
|
_syncWebsocketTask,
|
||||||
|
|
@ -133,8 +135,9 @@ class BackgroundSyncManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> syncRemote() {
|
Future<bool> syncRemote({bool enqueue = false}) {
|
||||||
if (_syncTask != null) {
|
if (_syncTask != null) {
|
||||||
|
_syncQueued |= enqueue;
|
||||||
return _syncTask!.future.then((result) => result ?? false).catchError((_) => false);
|
return _syncTask!.future.then((result) => result ?? false).catchError((_) => false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,15 +151,20 @@ class BackgroundSyncManager {
|
||||||
.then((result) {
|
.then((result) {
|
||||||
final success = result ?? false;
|
final success = result ?? false;
|
||||||
onRemoteSyncComplete?.call(success);
|
onRemoteSyncComplete?.call(success);
|
||||||
|
_syncQueued &= success;
|
||||||
return success;
|
return success;
|
||||||
})
|
})
|
||||||
.catchError((error) {
|
.catchError((error) {
|
||||||
onRemoteSyncError?.call(error.toString());
|
onRemoteSyncError?.call(error.toString());
|
||||||
_syncTask = null;
|
_syncQueued = false;
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
.whenComplete(() {
|
.whenComplete(() {
|
||||||
_syncTask = null;
|
_syncTask = null;
|
||||||
|
if (_syncQueued) {
|
||||||
|
_syncQueued = false;
|
||||||
|
unawaited(syncRemote());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class EventStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Closes the stream controller
|
/// Closes the stream controller
|
||||||
void dispose() {
|
Future<void> dispose() {
|
||||||
_controller.close();
|
return _controller.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ extension DTOToAsset on api.AssetResponseDto {
|
||||||
ownerId: ownerId,
|
ownerId: ownerId,
|
||||||
visibility: visibility.toAssetVisibility(),
|
visibility: visibility.toAssetVisibility(),
|
||||||
durationMs: duration,
|
durationMs: duration,
|
||||||
height: height?.toInt(),
|
height: height,
|
||||||
width: width?.toInt(),
|
width: width,
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
livePhotoVideoId: livePhotoVideoId.orElse(null),
|
livePhotoVideoId: livePhotoVideoId.orElse(null),
|
||||||
thumbHash: thumbhash,
|
thumbHash: thumbhash,
|
||||||
|
|
@ -38,8 +38,8 @@ extension DTOToAsset on api.AssetResponseDto {
|
||||||
ownerId: ownerId,
|
ownerId: ownerId,
|
||||||
visibility: visibility.toAssetVisibility(),
|
visibility: visibility.toAssetVisibility(),
|
||||||
durationMs: duration,
|
durationMs: duration,
|
||||||
height: height?.toInt(),
|
height: height,
|
||||||
width: width?.toInt(),
|
width: width,
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
livePhotoVideoId: livePhotoVideoId.orElse(null),
|
livePhotoVideoId: livePhotoVideoId.orElse(null),
|
||||||
thumbHash: thumbhash,
|
thumbHash: thumbhash,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ import 'package:collection/collection.dart';
|
||||||
extension ListExtension<E> on List<E> {
|
extension ListExtension<E> on List<E> {
|
||||||
List<E> uniqueConsecutive({int Function(E a, E b)? compare, void Function(E a, E b)? onDuplicate}) {
|
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;
|
compare ??= (E a, E b) => a == b ? 0 : 1;
|
||||||
int i = 1, j = 1;
|
int i = 1;
|
||||||
|
int j = 1;
|
||||||
for (; i < length; i++) {
|
for (; i < length; i++) {
|
||||||
if (compare(this[i - 1], this[i]) != 0) {
|
if (compare(this[i - 1], this[i]) != 0) {
|
||||||
if (i != j) {
|
if (i != j) {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||||
extension TimeAgoExtension on DateTime {
|
extension TimeAgoExtension on DateTime {
|
||||||
/// Displays the time difference of this [DateTime] object to the current time as a [String]
|
/// Displays the time difference of this [DateTime] object to the current time as a [String]
|
||||||
String timeAgo({bool numericDates = true}) {
|
String timeAgo({bool numericDates = true}) {
|
||||||
DateTime date = toLocal();
|
final DateTime date = toLocal();
|
||||||
final now = DateTime.now().toLocal();
|
final now = DateTime.now().toLocal();
|
||||||
final difference = now.difference(date);
|
final difference = now.difference(date);
|
||||||
|
|
||||||
|
|
@ -13,27 +13,27 @@ extension TimeAgoExtension on DateTime {
|
||||||
} else if (difference.inSeconds < 60) {
|
} else if (difference.inSeconds < 60) {
|
||||||
return '${difference.inSeconds} seconds ago';
|
return '${difference.inSeconds} seconds ago';
|
||||||
} else if (difference.inMinutes <= 1) {
|
} 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) {
|
} else if (difference.inMinutes < 60) {
|
||||||
return '${difference.inMinutes} minutes ago';
|
return '${difference.inMinutes} minutes ago';
|
||||||
} else if (difference.inHours <= 1) {
|
} 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) {
|
} else if (difference.inHours < 60) {
|
||||||
return '${difference.inHours} hours ago';
|
return '${difference.inHours} hours ago';
|
||||||
} else if (difference.inDays <= 1) {
|
} else if (difference.inDays <= 1) {
|
||||||
return (numericDates) ? '1 day ago' : 'Yesterday';
|
return numericDates ? '1 day ago' : 'Yesterday';
|
||||||
} else if (difference.inDays < 6) {
|
} else if (difference.inDays < 6) {
|
||||||
return '${difference.inDays} days ago';
|
return '${difference.inDays} days ago';
|
||||||
} else if ((difference.inDays / 7).ceil() <= 1) {
|
} 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) {
|
} else if ((difference.inDays / 7).ceil() < 4) {
|
||||||
return '${(difference.inDays / 7).ceil()} weeks ago';
|
return '${(difference.inDays / 7).ceil()} weeks ago';
|
||||||
} else if ((difference.inDays / 30).ceil() <= 1) {
|
} 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) {
|
} else if ((difference.inDays / 30).ceil() < 30) {
|
||||||
return '${(difference.inDays / 30).ceil()} months ago';
|
return '${(difference.inDays / 30).ceil()} months ago';
|
||||||
} else if ((difference.inDays / 365).ceil() <= 1) {
|
} 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';
|
return '${(difference.inDays / 365).floor()} years ago';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ class AssetEditEntity extends Table with DriftDefaultsMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
final JsonTypeConverter2<Map<String, Object?>, Uint8List, Object?> editParameterConverter = TypeConverter.jsonb(
|
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 {
|
extension AssetEditEntityDataDomainEx on AssetEditEntityData {
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,8 @@ extension RemoteExifEntityDataDomainEx on RemoteExifEntityData {
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude,
|
||||||
f: fNumber?.toDouble(),
|
f: fNumber,
|
||||||
mm: focalLength?.toDouble(),
|
mm: focalLength,
|
||||||
lens: lens,
|
lens: lens,
|
||||||
isFlipped: ExifDtoConverter.isOrientationFlipped(orientation),
|
isFlipped: ExifDtoConverter.isOrientationFlipped(orientation),
|
||||||
exposureSeconds: ExifDtoConverter.exposureTimeToSeconds(exposureTime),
|
exposureSeconds: ExifDtoConverter.exposureTimeToSeconds(exposureTime),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:drift/drift.dart';
|
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/domain/models/log.model.dart' as domain;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/log.entity.drift.dart';
|
||||||
|
|
||||||
class LogMessageEntity extends Table {
|
class LogMessageEntity extends Table {
|
||||||
const LogMessageEntity();
|
const LogMessageEntity();
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,5 @@ class UserMetadataEntity extends Table with DriftDefaultsMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
final JsonTypeConverter2<Map<String, Object?>, Uint8List, Object?> userMetadataConverter = TypeConverter.jsonb(
|
final JsonTypeConverter2<Map<String, Object?>, Uint8List, Object?> userMetadataConverter = TypeConverter.jsonb(
|
||||||
fromJson: (json) => json as Map<String, Object?>,
|
fromJson: (json) => json! as Map<String, Object?>,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ abstract class ImageRequest {
|
||||||
final int requestId = _nextRequestId++;
|
final int requestId = _nextRequestId++;
|
||||||
bool _isCancelled = false;
|
bool _isCancelled = false;
|
||||||
|
|
||||||
get isCancelled => _isCancelled;
|
bool get isCancelled => _isCancelled;
|
||||||
|
|
||||||
ImageRequest();
|
ImageRequest();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,13 @@ class RemoteImageRequest extends ImageRequest {
|
||||||
final info = await remoteImageApi.requestImage(uri, requestId: requestId, preferEncoded: false);
|
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.
|
// Android falls back to encoded data if native decoding fails, so check for both shapes of the response.
|
||||||
final frame = switch (info) {
|
final frame = switch (info) {
|
||||||
{'pointer': int pointer, 'length': int length} => await _fromEncodedPlatformImage(pointer, length),
|
{'pointer': final int pointer, 'length': final int length} => await _fromEncodedPlatformImage(pointer, length),
|
||||||
{'pointer': int pointer, 'width': int width, 'height': int height, 'rowBytes': int rowBytes} =>
|
{
|
||||||
|
'pointer': final int pointer,
|
||||||
|
'width': final int width,
|
||||||
|
'height': final int height,
|
||||||
|
'rowBytes': final int rowBytes,
|
||||||
|
} =>
|
||||||
await _fromDecodedPlatformImage(pointer, width, height, rowBytes),
|
await _fromDecodedPlatformImage(pointer, width, height, rowBytes),
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'package:drift/drift.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/album/local_album.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
|
@ -16,7 +17,7 @@ class DriftBackupRepository extends DriftDatabaseRepository {
|
||||||
final Drift _db;
|
final Drift _db;
|
||||||
const DriftBackupRepository(this._db) : super(_db);
|
const DriftBackupRepository(this._db) : super(_db);
|
||||||
|
|
||||||
_getExcludedSubquery() {
|
JoinedSelectStatement<$LocalAlbumAssetEntityTable, LocalAlbumAssetEntityData> _getExcludedSubquery() {
|
||||||
return _db.localAlbumAssetEntity.selectOnly()
|
return _db.localAlbumAssetEntity.selectOnly()
|
||||||
..addColumns([_db.localAlbumAssetEntity.assetId])
|
..addColumns([_db.localAlbumAssetEntity.assetId])
|
||||||
..join([
|
..join([
|
||||||
|
|
|
||||||
|
|
@ -356,7 +356,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||||
return _deleteAssets(assetIds);
|
return _deleteAssets(assetIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> assetsToDelete = [];
|
final List<String> assetsToDelete = [];
|
||||||
List<String> assetsToUnLink = [];
|
List<String> assetsToUnLink = [];
|
||||||
|
|
||||||
final uniqueAssets = await _getUniqueAssetsInAlbum(albumId);
|
final uniqueAssets = await _getUniqueAssetsInAlbum(albumId);
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue