mirror of
https://github.com/immich-app/immich.git
synced 2026-01-11 04:35:25 -08:00
Compare commits
25 Commits
renovate/m
...
perf/optim
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee23794625 | ||
|
|
e8c80d88a5 | ||
|
|
76241a7b2b | ||
|
|
1e4af9731d | ||
|
|
88327fb872 | ||
|
|
702499b97d | ||
|
|
da248414af | ||
|
|
af2c232c87 | ||
|
|
cca037b03c | ||
|
|
1d71bb5a79 | ||
|
|
ee4f2c735d | ||
|
|
4d559a63ec | ||
|
|
573e9b0d52 | ||
|
|
a2502109ab | ||
|
|
3cdece4945 | ||
|
|
520b825511 | ||
|
|
191401f2f1 | ||
|
|
8136d7fd54 | ||
|
|
5d1e486478 | ||
|
|
85b0b97ef2 | ||
|
|
471fab0591 | ||
|
|
6997ed83c4 | ||
|
|
a2ba36c16d | ||
|
|
109c79125d | ||
|
|
fbd49e0b79 |
12
.github/workflows/build-mobile.yml
vendored
12
.github/workflows/build-mobile.yml
vendored
@@ -84,7 +84,7 @@ jobs:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.sha }}
|
||||
persist-credentials: false
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
|
||||
- name: Restore Gradle Cache
|
||||
id: cache-gradle-restore
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -153,14 +153,14 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Publish Android Artifact
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: release-apk-signed
|
||||
path: mobile/build/app/outputs/flutter-apk/*.apk
|
||||
|
||||
- name: Save Gradle Cache
|
||||
id: cache-gradle-save
|
||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
if: github.ref == 'refs/heads/main'
|
||||
with:
|
||||
path: |
|
||||
@@ -182,7 +182,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.sha }}
|
||||
persist-credentials: false
|
||||
@@ -286,7 +286,7 @@ jobs:
|
||||
security delete-keychain build.keychain || true
|
||||
|
||||
- name: Upload IPA artifact
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: ios-release-ipa
|
||||
path: mobile/ios/Runner.ipa
|
||||
|
||||
2
.github/workflows/cache-cleanup.yml
vendored
2
.github/workflows/cache-cleanup.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
4
.github/workflows/cli.yml
vendored
4
.github/workflows/cli.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
4
.github/workflows/docs-build.yml
vendored
4
.github/workflows/docs-build.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
run: pnpm build
|
||||
|
||||
- name: Upload build output
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: docs-build-output
|
||||
path: docs/build/
|
||||
|
||||
4
.github/workflows/docs-deploy.yml
vendored
4
.github/workflows/docs-deploy.yml
vendored
@@ -125,13 +125,13 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cd24790a7f5f6439ac32cc94f5523cb2de8bfa8c # use-mise-action-v1.1.0
|
||||
uses: immich-app/devtools/actions/use-mise@b868e6e7c8cc212beec876330b4059e661ee44bb # use-mise-action-v1.1.1
|
||||
|
||||
- name: Load parameters
|
||||
id: parameters
|
||||
|
||||
4
.github/workflows/docs-destroy.yml
vendored
4
.github/workflows/docs-destroy.yml
vendored
@@ -23,13 +23,13 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@cd24790a7f5f6439ac32cc94f5523cb2de8bfa8c # use-mise-action-v1.1.0
|
||||
uses: immich-app/devtools/actions/use-mise@b868e6e7c8cc212beec876330b4059e661ee44bb # use-mise-action-v1.1.1
|
||||
|
||||
- name: Destroy Docs Subdomain
|
||||
env:
|
||||
|
||||
2
.github/workflows/fix-format.yml
vendored
2
.github/workflows/fix-format.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
6
.github/workflows/prepare-release.yml
vendored
6
.github/workflows/prepare-release.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
@@ -136,13 +136,13 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download APK
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
name: release-apk-signed
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
4
.github/workflows/release-pr.yml
vendored
4
.github/workflows/release-pr.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
@@ -159,7 +159,7 @@ jobs:
|
||||
|
||||
- name: Create PR
|
||||
id: create-pr
|
||||
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11
|
||||
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
commit-message: 'chore: release ${{ steps.bump-type.outputs.next }}'
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
persist-credentials: false
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Download APK
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
name: release-apk-signed
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
2
.github/workflows/sdk.yml
vendored
2
.github/workflows/sdk.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
2
.github/workflows/static_analysis.yml
vendored
2
.github/workflows/static_analysis.yml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
34
.github/workflows/test.yml
vendored
34
.github/workflows/test.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -114,7 +114,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -161,7 +161,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -203,7 +203,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -247,7 +247,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -285,7 +285,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -333,7 +333,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -379,7 +379,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: 'recursive'
|
||||
@@ -418,7 +418,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: 'recursive'
|
||||
@@ -473,7 +473,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: 'recursive'
|
||||
@@ -505,7 +505,7 @@ jobs:
|
||||
run: npx playwright test
|
||||
if: ${{ !cancelled() }}
|
||||
- name: Archive test results
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
if: success() || failure()
|
||||
with:
|
||||
name: e2e-web-test-results-${{ matrix.runner }}
|
||||
@@ -534,7 +534,7 @@ jobs:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -566,7 +566,7 @@ jobs:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -607,7 +607,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -636,7 +636,7 @@ jobs:
|
||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -658,7 +658,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -720,7 +720,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -33,7 +33,7 @@ You can create a public link to share a group of photos or videos, or an album,
|
||||
The public shared link is generated with a random URL, which acts as as a secret to avoid the link being guessed by unwanted parties, for instance.
|
||||
|
||||
```
|
||||
https://immich.yourdomain.com/share/JUckRMxlgpo7F9BpyqGk_cZEwDzaU_U5LU5_oNZp1ETIBa9dpQ0b5ghNm_22QVJfn3k
|
||||
https://my.immich.app/share/JUckRMxlgpo7F9BpyqGk_cZEwDzaU_U5LU5_oNZp1ETIBa9dpQ0b5ghNm_22QVJfn3k
|
||||
```
|
||||
|
||||
### Creating a public share link
|
||||
|
||||
6
e2e-auth-server/Dockerfile
Normal file
6
e2e-auth-server/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
||||
FROM node:24.1.0-alpine3.20@sha256:8fe019e0d57dbdce5f5c27c0b63d2775cf34b00e3755a7dea969802d7e0c2b25
|
||||
RUN corepack enable
|
||||
ADD package.json *.ts ./
|
||||
RUN pnpm install
|
||||
EXPOSE 2286
|
||||
CMD ["pnpm", "run", "start"]
|
||||
@@ -125,7 +125,7 @@ const setup = async () => {
|
||||
],
|
||||
});
|
||||
|
||||
const onStart = () => console.log(`[auth-server] http://${host}:${port}/.well-known/openid-configuration`);
|
||||
const onStart = () => console.log(`[e2e-auth-server] http://${host}:${port}/.well-known/openid-configuration`);
|
||||
const app = oidc.listen(port, host, onStart);
|
||||
return () => app.close();
|
||||
};
|
||||
15
e2e-auth-server/package.json
Normal file
15
e2e-auth-server/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "@immich/e2e-auth-server",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"main": "auth-server.ts",
|
||||
"scripts": {
|
||||
"start": "tsx startup.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jose": "^5.6.3",
|
||||
"@types/oidc-provider": "^9.0.0",
|
||||
"oidc-provider": "^9.0.0",
|
||||
"tsx": "^4.20.6"
|
||||
}
|
||||
}
|
||||
8
e2e-auth-server/startup.ts
Normal file
8
e2e-auth-server/startup.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import setup from './auth-server'
|
||||
|
||||
const teardown = await setup()
|
||||
process.on('exit', () => {
|
||||
teardown()
|
||||
console.log('[e2e-auth-server] stopped')
|
||||
process.exit(0)
|
||||
})
|
||||
@@ -1,6 +1,12 @@
|
||||
name: immich-e2e
|
||||
|
||||
services:
|
||||
e2e-auth-server:
|
||||
build:
|
||||
context: ../e2e-auth-server
|
||||
ports:
|
||||
- 2286:2286
|
||||
|
||||
immich-server:
|
||||
container_name: immich-e2e-server
|
||||
image: immich-server:latest
|
||||
@@ -27,8 +33,6 @@ services:
|
||||
- IMMICH_IGNORE_MOUNT_CHECK_ERRORS=true
|
||||
volumes:
|
||||
- ./test-assets:/test-assets
|
||||
extra_hosts:
|
||||
- 'auth-server:host-gateway'
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_started
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
"@eslint/js": "^9.8.0",
|
||||
"@faker-js/faker": "^10.1.0",
|
||||
"@immich/cli": "file:../cli",
|
||||
"@immich/e2e-auth-server": "file:../e2e-auth-server",
|
||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||
"@playwright/test": "^1.44.1",
|
||||
"@socket.io/component-emitter": "^3.1.2",
|
||||
"@types/luxon": "^3.4.2",
|
||||
"@types/node": "^24.10.4",
|
||||
"@types/oidc-provider": "^9.0.0",
|
||||
"@types/pg": "^8.15.1",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/supertest": "^6.0.2",
|
||||
@@ -38,9 +38,7 @@
|
||||
"eslint-plugin-unicorn": "^62.0.0",
|
||||
"exiftool-vendored": "^34.3.0",
|
||||
"globals": "^16.0.0",
|
||||
"jose": "^5.6.3",
|
||||
"luxon": "^3.4.4",
|
||||
"oidc-provider": "^9.0.0",
|
||||
"pg": "^8.11.3",
|
||||
"pngjs": "^7.0.0",
|
||||
"prettier": "^3.7.4",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { OAuthClient, OAuthUser } from '@immich/e2e-auth-server';
|
||||
import {
|
||||
LoginResponseDto,
|
||||
SystemConfigOAuthDto,
|
||||
@@ -8,13 +9,12 @@ import {
|
||||
} from '@immich/sdk';
|
||||
import { createHash, randomBytes } from 'node:crypto';
|
||||
import { errorDto } from 'src/responses';
|
||||
import { OAuthClient, OAuthUser } from 'src/setup/auth-server';
|
||||
import { app, asBearerAuth, baseUrl, utils } from 'src/utils';
|
||||
import request from 'supertest';
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
const authServer = {
|
||||
internal: 'http://auth-server:2286',
|
||||
internal: 'http://e2e-auth-server:2286',
|
||||
external: 'http://127.0.0.1:2286',
|
||||
};
|
||||
|
||||
|
||||
@@ -346,6 +346,8 @@ export function toAssetResponseDto(asset: MockTimelineAsset, owner?: UserRespons
|
||||
duplicateId: null,
|
||||
resized: true,
|
||||
checksum: asset.checksum,
|
||||
width: exifInfo.exifImageWidth ?? 1,
|
||||
height: exifInfo.exifImageHeight ?? 1,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AssetResponseDto } from '@immich/sdk';
|
||||
import { BrowserContext, Page, Request, Route } from '@playwright/test';
|
||||
import { basename } from 'node:path';
|
||||
import {
|
||||
@@ -63,15 +64,33 @@ export const setupTimelineMockApiRoutes = async (
|
||||
});
|
||||
|
||||
await context.route('**/api/assets/*', async (route, request) => {
|
||||
const url = new URL(request.url());
|
||||
const pathname = url.pathname;
|
||||
const assetId = basename(pathname);
|
||||
const asset = getAsset(timelineRestData, assetId);
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: asset,
|
||||
});
|
||||
if (request.method() === 'GET') {
|
||||
const url = new URL(request.url());
|
||||
const pathname = url.pathname;
|
||||
const assetId = basename(pathname);
|
||||
let asset = getAsset(timelineRestData, assetId);
|
||||
if (changes.assetDeletions.includes(asset!.id)) {
|
||||
asset = {
|
||||
...asset,
|
||||
isTrashed: true,
|
||||
} as AssetResponseDto;
|
||||
}
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: asset,
|
||||
});
|
||||
}
|
||||
await route.fallback();
|
||||
});
|
||||
|
||||
await context.route('**/api/assets', async (route, request) => {
|
||||
if (request.method() === 'DELETE') {
|
||||
return route.fulfill({
|
||||
status: 204,
|
||||
});
|
||||
}
|
||||
await route.fallback();
|
||||
});
|
||||
|
||||
await context.route('**/api/assets/*/ocr', async (route) => {
|
||||
@@ -117,17 +136,28 @@ export const setupTimelineMockApiRoutes = async (
|
||||
});
|
||||
|
||||
await context.route('**/api/albums/**', async (route, request) => {
|
||||
const pattern = /\/api\/albums\/(?<albumId>[^/?]+)/;
|
||||
const match = request.url().match(pattern);
|
||||
if (!match) {
|
||||
return route.continue();
|
||||
const albumsMatch = request.url().match(/\/api\/albums\/(?<albumId>[^/?]+)/);
|
||||
if (albumsMatch) {
|
||||
const album = getAlbum(timelineRestData, testContext.adminId, albumsMatch.groups?.albumId, changes);
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: album,
|
||||
});
|
||||
}
|
||||
const album = getAlbum(timelineRestData, testContext.adminId, match.groups?.albumId, changes);
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: album,
|
||||
});
|
||||
return route.fallback();
|
||||
});
|
||||
|
||||
await context.route('**/api/albums**', async (route, request) => {
|
||||
const allAlbums = request.url().match(/\/api\/albums\?assetId=(?<assetId>[^&]+)/);
|
||||
if (allAlbums) {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: [],
|
||||
});
|
||||
}
|
||||
return route.fallback();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
156
e2e/src/web/specs/asset-viewer/asset-viewer.parallel-e2e-spec.ts
Normal file
156
e2e/src/web/specs/asset-viewer/asset-viewer.parallel-e2e-spec.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { test } from '@playwright/test';
|
||||
import {
|
||||
Changes,
|
||||
createDefaultTimelineConfig,
|
||||
generateTimelineData,
|
||||
SeededRandom,
|
||||
selectRandom,
|
||||
TimelineAssetConfig,
|
||||
TimelineData,
|
||||
} from 'src/generators/timeline';
|
||||
import { setupBaseMockApiRoutes } from 'src/mock-network/base-network';
|
||||
import { setupTimelineMockApiRoutes, TimelineTestContext } from 'src/mock-network/timeline-network';
|
||||
import { utils } from 'src/utils';
|
||||
import { assetViewerUtils, cancelAllPollers } from 'src/web/specs/timeline/utils';
|
||||
|
||||
test.describe.configure({ mode: 'parallel' });
|
||||
test.describe('asset-viewer', () => {
|
||||
const rng = new SeededRandom(529);
|
||||
let adminUserId: string;
|
||||
let timelineRestData: TimelineData;
|
||||
const assets: TimelineAssetConfig[] = [];
|
||||
const yearMonths: string[] = [];
|
||||
const testContext = new TimelineTestContext();
|
||||
const changes: Changes = {
|
||||
albumAdditions: [],
|
||||
assetDeletions: [],
|
||||
assetArchivals: [],
|
||||
assetFavorites: [],
|
||||
};
|
||||
|
||||
test.beforeAll(async () => {
|
||||
utils.initSdk();
|
||||
adminUserId = faker.string.uuid();
|
||||
testContext.adminId = adminUserId;
|
||||
timelineRestData = generateTimelineData({ ...createDefaultTimelineConfig(), ownerId: adminUserId });
|
||||
for (const timeBucket of timelineRestData.buckets.values()) {
|
||||
assets.push(...timeBucket);
|
||||
}
|
||||
for (const yearMonth of timelineRestData.buckets.keys()) {
|
||||
const [year, month] = yearMonth.split('-');
|
||||
yearMonths.push(`${year}-${Number(month)}`);
|
||||
}
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ context }) => {
|
||||
await setupBaseMockApiRoutes(context, adminUserId);
|
||||
await setupTimelineMockApiRoutes(context, timelineRestData, changes, testContext);
|
||||
});
|
||||
|
||||
test.afterEach(() => {
|
||||
cancelAllPollers();
|
||||
testContext.slowBucket = false;
|
||||
changes.albumAdditions = [];
|
||||
changes.assetDeletions = [];
|
||||
changes.assetArchivals = [];
|
||||
changes.assetFavorites = [];
|
||||
});
|
||||
|
||||
test.describe('/photos/:id', () => {
|
||||
test('Delete photo advances to next', async ({ page }) => {
|
||||
const asset = selectRandom(assets, rng);
|
||||
await page.goto(`/photos/${asset.id}`);
|
||||
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||
await page.getByLabel('Delete').click();
|
||||
const index = assets.indexOf(asset);
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[index + 1]);
|
||||
});
|
||||
test('Delete photo advances to next (2x)', async ({ page }) => {
|
||||
const asset = selectRandom(assets, rng);
|
||||
await page.goto(`/photos/${asset.id}`);
|
||||
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||
await page.getByLabel('Delete').click();
|
||||
const index = assets.indexOf(asset);
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[index + 1]);
|
||||
await page.getByLabel('Delete').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[index + 2]);
|
||||
});
|
||||
test('Delete last photo advances to prev', async ({ page }) => {
|
||||
const asset = assets.at(-1)!;
|
||||
await page.goto(`/photos/${asset.id}`);
|
||||
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||
await page.getByLabel('Delete').click();
|
||||
const index = assets.indexOf(asset);
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[index - 1]);
|
||||
});
|
||||
test('Delete last photo advances to prev (2x)', async ({ page }) => {
|
||||
const asset = assets.at(-1)!;
|
||||
await page.goto(`/photos/${asset.id}`);
|
||||
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||
await page.getByLabel('Delete').click();
|
||||
const index = assets.indexOf(asset);
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[index - 1]);
|
||||
await page.getByLabel('Delete').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[index - 2]);
|
||||
});
|
||||
});
|
||||
test.describe('/trash/photos/:id', () => {
|
||||
test('Delete trashed photo advances to next', async ({ page }) => {
|
||||
const asset = selectRandom(assets, rng);
|
||||
const index = assets.indexOf(asset);
|
||||
const deletedAssets = assets.slice(index - 10, index + 10).map((asset) => asset.id);
|
||||
changes.assetDeletions.push(...deletedAssets);
|
||||
await page.goto(`/trash/photos/${asset.id}`);
|
||||
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||
await page.getByLabel('Delete').click();
|
||||
// confirm dialog
|
||||
await page.getByRole('button').getByText('Delete').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[index + 1]);
|
||||
});
|
||||
test('Delete trashed photo advances to next 2x', async ({ page }) => {
|
||||
const asset = selectRandom(assets, rng);
|
||||
const index = assets.indexOf(asset);
|
||||
const deletedAssets = assets.slice(index - 10, index + 10).map((asset) => asset.id);
|
||||
changes.assetDeletions.push(...deletedAssets);
|
||||
await page.goto(`/trash/photos/${asset.id}`);
|
||||
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||
await page.getByLabel('Delete').click();
|
||||
// confirm dialog
|
||||
await page.getByRole('button').getByText('Delete').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[index + 1]);
|
||||
await page.getByLabel('Delete').click();
|
||||
// confirm dialog
|
||||
await page.getByRole('button').getByText('Delete').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[index + 2]);
|
||||
});
|
||||
test('Delete trashed photo advances to prev', async ({ page }) => {
|
||||
const asset = selectRandom(assets, rng);
|
||||
const index = assets.indexOf(asset);
|
||||
const deletedAssets = assets.slice(index - 10, index + 10).map((asset) => asset.id);
|
||||
changes.assetDeletions.push(...deletedAssets);
|
||||
await page.goto(`/trash/photos/${assets[index + 9].id}`);
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[index + 9]);
|
||||
await page.getByLabel('Delete').click();
|
||||
// confirm dialog
|
||||
await page.getByRole('button').getByText('Delete').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[index + 8]);
|
||||
});
|
||||
test('Delete trashed photo advances to prev 2x', async ({ page }) => {
|
||||
const asset = selectRandom(assets, rng);
|
||||
const index = assets.indexOf(asset);
|
||||
const deletedAssets = assets.slice(index - 10, index + 10).map((asset) => asset.id);
|
||||
changes.assetDeletions.push(...deletedAssets);
|
||||
await page.goto(`/trash/photos/${assets[index + 9].id}`);
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[index + 9]);
|
||||
await page.getByLabel('Delete').click();
|
||||
// confirm dialog
|
||||
await page.getByRole('button').getByText('Delete').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[index + 8]);
|
||||
await page.getByLabel('Delete').click();
|
||||
// confirm dialog
|
||||
await page.getByRole('button').getByText('Delete').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[index + 7]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -463,7 +463,7 @@ test.describe('Timeline', () => {
|
||||
});
|
||||
changes.albumAdditions.push(...requestJson.ids);
|
||||
});
|
||||
await page.getByText('Done').click();
|
||||
await page.getByText('Add assets').click();
|
||||
await expect(put).resolves.toEqual({
|
||||
ids: [
|
||||
'c077ea7b-cfa1-45e4-8554-f86c00ee5658',
|
||||
|
||||
@@ -181,8 +181,12 @@ export const assetViewerUtils = {
|
||||
},
|
||||
async waitForViewerLoad(page: Page, asset: TimelineAssetConfig) {
|
||||
await page
|
||||
.locator(`img[draggable="false"][src="/api/assets/${asset.id}/thumbnail?size=preview&c=${asset.thumbhash}"]`)
|
||||
.or(page.locator(`video[poster="/api/assets/${asset.id}/thumbnail?size=preview&c=${asset.thumbhash}"]`))
|
||||
.locator(
|
||||
`img[draggable="false"][src="/api/assets/${asset.id}/thumbnail?size=preview&c=${asset.thumbhash}&edited=true"]`,
|
||||
)
|
||||
.or(
|
||||
page.locator(`video[poster="/api/assets/${asset.id}/thumbnail?size=preview&c=${asset.thumbhash}&edited=true"]`),
|
||||
)
|
||||
.waitFor();
|
||||
},
|
||||
async expectActiveAssetToBe(page: Page, assetId: string) {
|
||||
|
||||
@@ -56,7 +56,7 @@ test.describe('User Administration', () => {
|
||||
await expect(page.getByLabel('Admin User')).not.toBeChecked();
|
||||
await page.getByLabel('Admin User').click();
|
||||
await expect(page.getByLabel('Admin User')).toBeChecked();
|
||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
@@ -85,7 +85,7 @@ test.describe('User Administration', () => {
|
||||
await expect(page.getByLabel('Admin User')).toBeChecked();
|
||||
await page.getByLabel('Admin User').click();
|
||||
await expect(page.getByLabel('Admin User')).not.toBeChecked();
|
||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
// skip `docker compose up` if `make e2e` was already run
|
||||
const globalSetup: string[] = ['src/setup/auth-server.ts'];
|
||||
const globalSetup: string[] = [];
|
||||
try {
|
||||
await fetch('http://127.0.0.1:2285/api/server-info/ping');
|
||||
} catch {
|
||||
|
||||
17
i18n/en.json
17
i18n/en.json
@@ -18,6 +18,7 @@
|
||||
"add_a_title": "Add a title",
|
||||
"add_action": "Add action",
|
||||
"add_action_description": "Click to add an action to perform",
|
||||
"add_assets": "Add assets",
|
||||
"add_birthday": "Add a birthday",
|
||||
"add_endpoint": "Add endpoint",
|
||||
"add_exclusion_pattern": "Add exclusion pattern",
|
||||
@@ -478,6 +479,7 @@
|
||||
"album_summary": "Album summary",
|
||||
"album_updated": "Album updated",
|
||||
"album_updated_setting_description": "Receive an email notification when a shared album has new assets",
|
||||
"album_upload_assets": "Upload assets from your computer and add to album",
|
||||
"album_user_left": "Left {album}",
|
||||
"album_user_removed": "Removed {user}",
|
||||
"album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?",
|
||||
@@ -831,6 +833,9 @@
|
||||
"created_at": "Created",
|
||||
"creating_linked_albums": "Creating linked albums...",
|
||||
"crop": "Crop",
|
||||
"crop_aspect_ratio_fixed": "Fixed",
|
||||
"crop_aspect_ratio_free": "Free",
|
||||
"crop_aspect_ratio_original": "Original",
|
||||
"curated_object_page_title": "Things",
|
||||
"current_device": "Current device",
|
||||
"current_pin_code": "Current PIN code",
|
||||
@@ -964,9 +969,13 @@
|
||||
"editor": "Editor",
|
||||
"editor_close_without_save_prompt": "The changes will not be saved",
|
||||
"editor_close_without_save_title": "Close editor?",
|
||||
"editor_crop_tool_h2_aspect_ratios": "Aspect ratios",
|
||||
"editor_crop_tool_h2_rotation": "Rotation",
|
||||
"editor_mode": "Editor mode",
|
||||
"editor_confirm_reset_all_changes": "Are you sure you want to reset all changes?",
|
||||
"editor_flip_horizontal": "Flip horizontal",
|
||||
"editor_flip_vertical": "Flip vertical",
|
||||
"editor_orientation": "Orientation",
|
||||
"editor_reset_all_changes": "Reset changes",
|
||||
"editor_rotate_left": "Rotate 90° counterclockwise",
|
||||
"editor_rotate_right": "Rotate 90° clockwise",
|
||||
"email": "Email",
|
||||
"email_notifications": "Email notifications",
|
||||
"empty_folder": "This folder is empty",
|
||||
@@ -1457,6 +1466,8 @@
|
||||
"minimize": "Minimize",
|
||||
"minute": "Minute",
|
||||
"minutes": "Minutes",
|
||||
"mirror_horizontal": "Horizontal",
|
||||
"mirror_vertical": "Vertical",
|
||||
"missing": "Missing",
|
||||
"mobile_app": "Mobile App",
|
||||
"mobile_app_download_onboarding_note": "Download the companion mobile app using the following options",
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
||||
|
||||
typedef _AssetVideoDimension = ({double? width, double? height, bool isFlipped});
|
||||
|
||||
@@ -99,9 +98,7 @@ class AssetService {
|
||||
height = fetched?.height?.toDouble();
|
||||
}
|
||||
|
||||
final exif = await getExif(asset);
|
||||
final isFlipped = ExifDtoConverter.isOrientationFlipped(exif?.orientation);
|
||||
return (width: width, height: height, isFlipped: isFlipped);
|
||||
return (width: width, height: height, isFlipped: false);
|
||||
}
|
||||
|
||||
Future<List<(String, String)>> getPlaces(String userId) {
|
||||
|
||||
@@ -171,16 +171,8 @@ class RemoteAlbumService {
|
||||
|
||||
Future<List<RemoteAlbum>> _sortByNewestAsset(List<RemoteAlbum> albums) async {
|
||||
// map album IDs to their newest asset dates
|
||||
final Map<String, Future<DateTime?>> assetTimestampFutures = {};
|
||||
for (final album in albums) {
|
||||
assetTimestampFutures[album.id] = _repository.getNewestAssetTimestamp(album.id);
|
||||
}
|
||||
|
||||
// await all database queries
|
||||
final entries = await Future.wait(
|
||||
assetTimestampFutures.entries.map((entry) async => MapEntry(entry.key, await entry.value)),
|
||||
);
|
||||
final assetTimestamps = Map.fromEntries(entries);
|
||||
final albumIds = albums.map((e) => e.id).toList();
|
||||
final assetTimestamps = await _repository.getNewestAssetTimestampForAlbums(albumIds);
|
||||
|
||||
final sorted = albums.sorted((a, b) {
|
||||
final aDate = assetTimestamps[a.id] ?? DateTime.fromMillisecondsSinceEpoch(0);
|
||||
@@ -193,15 +185,8 @@ class RemoteAlbumService {
|
||||
|
||||
Future<List<RemoteAlbum>> _sortByOldestAsset(List<RemoteAlbum> albums) async {
|
||||
// map album IDs to their oldest asset dates
|
||||
final Map<String, Future<DateTime?>> assetTimestampFutures = {
|
||||
for (final album in albums) album.id: _repository.getOldestAssetTimestamp(album.id),
|
||||
};
|
||||
|
||||
// await all database queries
|
||||
final entries = await Future.wait(
|
||||
assetTimestampFutures.entries.map((entry) async => MapEntry(entry.key, await entry.value)),
|
||||
);
|
||||
final assetTimestamps = Map.fromEntries(entries);
|
||||
final albumIds = albums.map((e) => e.id).toList();
|
||||
final assetTimestamps = await _repository.getOldestAssetTimestampForAlbums(albumIds);
|
||||
|
||||
final sorted = albums.sorted((a, b) {
|
||||
final aDate = assetTimestamps[a.id] ?? DateTime.fromMillisecondsSinceEpoch(0);
|
||||
|
||||
@@ -321,26 +321,64 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
||||
}).watchSingleOrNull();
|
||||
}
|
||||
|
||||
Future<DateTime?> getNewestAssetTimestamp(String albumId) {
|
||||
final query = _db.remoteAlbumAssetEntity.selectOnly()
|
||||
..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId))
|
||||
..addColumns([_db.remoteAssetEntity.localDateTime.max()])
|
||||
..join([
|
||||
innerJoin(_db.remoteAssetEntity, _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId)),
|
||||
]);
|
||||
Future<Map<String, DateTime?>> getNewestAssetTimestampForAlbums(List<String> albumIds) async {
|
||||
if (albumIds.isEmpty) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return query.map((row) => row.read(_db.remoteAssetEntity.localDateTime.max())).getSingleOrNull();
|
||||
final results = <String, DateTime?>{};
|
||||
|
||||
// Chunk calls to avoid SQLite limit (default 999 variables)
|
||||
const chunkSize = 900;
|
||||
for (var i = 0; i < albumIds.length; i += chunkSize) {
|
||||
final end = (i + chunkSize < albumIds.length) ? i + chunkSize : albumIds.length;
|
||||
final subList = albumIds.sublist(i, end);
|
||||
|
||||
final query = _db.remoteAlbumAssetEntity.selectOnly()
|
||||
..where(_db.remoteAlbumAssetEntity.albumId.isIn(subList))
|
||||
..addColumns([_db.remoteAlbumAssetEntity.albumId, _db.remoteAssetEntity.localDateTime.max()])
|
||||
..join([
|
||||
innerJoin(_db.remoteAssetEntity, _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId)),
|
||||
])
|
||||
..groupBy([_db.remoteAlbumAssetEntity.albumId]);
|
||||
|
||||
final rows = await query.get();
|
||||
for (final row in rows) {
|
||||
results[row.read(_db.remoteAlbumAssetEntity.albumId)!] = row.read(_db.remoteAssetEntity.localDateTime.max());
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
Future<DateTime?> getOldestAssetTimestamp(String albumId) {
|
||||
final query = _db.remoteAlbumAssetEntity.selectOnly()
|
||||
..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId))
|
||||
..addColumns([_db.remoteAssetEntity.localDateTime.min()])
|
||||
..join([
|
||||
innerJoin(_db.remoteAssetEntity, _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId)),
|
||||
]);
|
||||
Future<Map<String, DateTime?>> getOldestAssetTimestampForAlbums(List<String> albumIds) async {
|
||||
if (albumIds.isEmpty) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return query.map((row) => row.read(_db.remoteAssetEntity.localDateTime.min())).getSingleOrNull();
|
||||
final results = <String, DateTime?>{};
|
||||
|
||||
// Chunk calls to avoid SQLite limit (default 999 variables)
|
||||
const chunkSize = 900;
|
||||
for (var i = 0; i < albumIds.length; i += chunkSize) {
|
||||
final end = (i + chunkSize < albumIds.length) ? i + chunkSize : albumIds.length;
|
||||
final subList = albumIds.sublist(i, end);
|
||||
|
||||
final query = _db.remoteAlbumAssetEntity.selectOnly()
|
||||
..where(_db.remoteAlbumAssetEntity.albumId.isIn(subList))
|
||||
..addColumns([_db.remoteAlbumAssetEntity.albumId, _db.remoteAssetEntity.localDateTime.min()])
|
||||
..join([
|
||||
innerJoin(_db.remoteAssetEntity, _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId)),
|
||||
])
|
||||
..groupBy([_db.remoteAlbumAssetEntity.albumId]);
|
||||
|
||||
final rows = await query.get();
|
||||
for (final row in rows) {
|
||||
results[row.read(_db.remoteAlbumAssetEntity.albumId)!] = row.read(_db.remoteAssetEntity.localDateTime.min());
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
Future<int> getCount() {
|
||||
|
||||
@@ -22,6 +22,7 @@ import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart' as api show AssetVisibility, AlbumUserRole, UserMetadataKey;
|
||||
import 'package:openapi/api.dart' hide AssetVisibility, AlbumUserRole, UserMetadataKey;
|
||||
@@ -194,6 +195,8 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
livePhotoVideoId: Value(asset.livePhotoVideoId),
|
||||
stackId: Value(asset.stackId),
|
||||
libraryId: Value(asset.libraryId),
|
||||
width: Value(asset.width),
|
||||
height: Value(asset.height),
|
||||
);
|
||||
|
||||
batch.insert(
|
||||
@@ -245,10 +248,21 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
|
||||
await _db.batch((batch) {
|
||||
for (final exif in data) {
|
||||
int? width;
|
||||
int? height;
|
||||
|
||||
if (ExifDtoConverter.isOrientationFlipped(exif.orientation)) {
|
||||
width = exif.exifImageHeight;
|
||||
height = exif.exifImageWidth;
|
||||
} else {
|
||||
width = exif.exifImageWidth;
|
||||
height = exif.exifImageHeight;
|
||||
}
|
||||
|
||||
batch.update(
|
||||
_db.remoteAssetEntity,
|
||||
RemoteAssetEntityCompanion(width: Value(exif.exifImageWidth), height: Value(exif.exifImageHeight)),
|
||||
where: (row) => row.id.equals(exif.assetId),
|
||||
RemoteAssetEntityCompanion(width: Value(width), height: Value(height)),
|
||||
where: (row) => row.id.equals(exif.assetId) & row.width.isNull() & row.height.isNull(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -42,6 +42,7 @@ import 'package:immich_mobile/utils/http_ssl_options.dart';
|
||||
import 'package:immich_mobile/utils/licenses.dart';
|
||||
import 'package:immich_mobile/utils/migration.dart';
|
||||
import 'package:immich_mobile/wm_executor.dart';
|
||||
import 'package:immich_ui/immich_ui.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:timezone/data/latest.dart';
|
||||
@@ -252,6 +253,13 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
|
||||
themeMode: ref.watch(immichThemeModeProvider),
|
||||
darkTheme: getThemeData(colorScheme: immichTheme.dark, locale: context.locale),
|
||||
theme: getThemeData(colorScheme: immichTheme.light, locale: context.locale),
|
||||
builder: (context, child) => ImmichTranslationProvider(
|
||||
translations: ImmichTranslations(
|
||||
submit: "submit".t(context: context),
|
||||
password: "password".t(context: context),
|
||||
),
|
||||
child: ImmichThemeProvider(colorScheme: context.colorScheme, child: child!),
|
||||
),
|
||||
routerConfig: router.config(
|
||||
deepLinkBuilder: _deepLinkBuilder,
|
||||
navigatorObservers: () => [AppNavigationObserver(ref: ref)],
|
||||
|
||||
@@ -370,6 +370,7 @@ class _MapWithMarker extends StatelessWidget {
|
||||
? PositionedAssetMarkerIcon(
|
||||
point: value.point,
|
||||
assetRemoteId: value.marker.assetRemoteId,
|
||||
assetThumbhash: '',
|
||||
durationInMilliseconds: value.shouldAnimate ? 100 : 0,
|
||||
onTap: onMarkerTapped,
|
||||
)
|
||||
|
||||
@@ -19,6 +19,17 @@ List<Widget> _showcaseBuilder(Function(ImmichVariant variant, ImmichColor color)
|
||||
return children;
|
||||
}
|
||||
|
||||
class _ComponentTitle extends StatelessWidget {
|
||||
final String title;
|
||||
|
||||
const _ComponentTitle(this.title);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(title, style: context.textTheme.titleLarge);
|
||||
}
|
||||
}
|
||||
|
||||
@RoutePage()
|
||||
class ImmichUIShowcasePage extends StatelessWidget {
|
||||
const ImmichUIShowcasePage({super.key});
|
||||
@@ -35,13 +46,51 @@ class ImmichUIShowcasePage extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("IconButton", style: context.textTheme.titleLarge),
|
||||
const _ComponentTitle("IconButton"),
|
||||
..._showcaseBuilder(
|
||||
(variant, color) =>
|
||||
ImmichIconButton(icon: Icons.favorite, color: color, variant: variant, onTap: () {}),
|
||||
ImmichIconButton(icon: Icons.favorite, color: color, variant: variant, onPressed: () {}),
|
||||
),
|
||||
const _ComponentTitle("CloseButton"),
|
||||
..._showcaseBuilder(
|
||||
(variant, color) => ImmichCloseButton(color: color, variant: variant, onPressed: () {}),
|
||||
),
|
||||
const _ComponentTitle("TextButton"),
|
||||
|
||||
ImmichTextButton(
|
||||
labelText: "Text Button",
|
||||
onPressed: () {},
|
||||
variant: ImmichVariant.filled,
|
||||
color: ImmichColor.primary,
|
||||
),
|
||||
ImmichTextButton(
|
||||
labelText: "Text Button",
|
||||
onPressed: () {},
|
||||
variant: ImmichVariant.filled,
|
||||
color: ImmichColor.primary,
|
||||
loading: true,
|
||||
),
|
||||
ImmichTextButton(
|
||||
labelText: "Text Button",
|
||||
onPressed: () {},
|
||||
variant: ImmichVariant.ghost,
|
||||
color: ImmichColor.primary,
|
||||
),
|
||||
ImmichTextButton(
|
||||
labelText: "Text Button",
|
||||
onPressed: () {},
|
||||
variant: ImmichVariant.ghost,
|
||||
color: ImmichColor.primary,
|
||||
loading: true,
|
||||
),
|
||||
const _ComponentTitle("Form"),
|
||||
ImmichForm(
|
||||
onSubmit: () {},
|
||||
child: const Column(
|
||||
spacing: 10,
|
||||
children: [ImmichTextInput(label: "Title", hintText: "Enter a title")],
|
||||
),
|
||||
),
|
||||
Text("CloseButton", style: context.textTheme.titleLarge),
|
||||
..._showcaseBuilder((variant, color) => ImmichCloseButton(color: color, variant: variant, onTap: () {})),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -37,7 +37,7 @@ class DriftCropImagePage extends HookWidget {
|
||||
icon: Icons.done_rounded,
|
||||
color: ImmichColor.primary,
|
||||
variant: ImmichVariant.ghost,
|
||||
onTap: () async {
|
||||
onPressed: () async {
|
||||
final croppedImage = await cropController.croppedImage();
|
||||
unawaited(context.pushRoute(DriftEditImageRoute(asset: asset, image: croppedImage, isEdited: true)));
|
||||
},
|
||||
@@ -79,13 +79,13 @@ class DriftCropImagePage extends HookWidget {
|
||||
icon: Icons.rotate_left,
|
||||
variant: ImmichVariant.ghost,
|
||||
color: ImmichColor.secondary,
|
||||
onTap: () => cropController.rotateLeft(),
|
||||
onPressed: () => cropController.rotateLeft(),
|
||||
),
|
||||
ImmichIconButton(
|
||||
icon: Icons.rotate_right,
|
||||
variant: ImmichVariant.ghost,
|
||||
color: ImmichColor.secondary,
|
||||
onTap: () => cropController.rotateRight(),
|
||||
onPressed: () => cropController.rotateRight(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -611,6 +611,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
filterQuality: FilterQuality.high,
|
||||
maxScale: 1.0,
|
||||
basePosition: Alignment.center,
|
||||
disableScaleGestures: true,
|
||||
child: SizedBox(
|
||||
width: ctx.width,
|
||||
height: ctx.height,
|
||||
|
||||
@@ -68,7 +68,7 @@ class _SheetLocationDetailsState extends ConsumerState<SheetLocationDetails> {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final remoteId = asset is LocalAsset ? asset.remoteId : (asset as RemoteAsset).id;
|
||||
final remoteAsset = asset as RemoteAsset;
|
||||
final locationName = _getLocationName(exifInfo);
|
||||
final coordinates = "${exifInfo?.latitude?.toStringAsFixed(4)}, ${exifInfo?.longitude?.toStringAsFixed(4)}";
|
||||
|
||||
@@ -92,7 +92,12 @@ class _SheetLocationDetailsState extends ConsumerState<SheetLocationDetails> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ExifMap(exifInfo: exifInfo!, markerId: remoteId, onMapCreated: _onMapCreated),
|
||||
ExifMap(
|
||||
exifInfo: exifInfo!,
|
||||
markerId: remoteAsset.id,
|
||||
markerAssetThumbhash: remoteAsset.thumbHash,
|
||||
onMapCreated: _onMapCreated,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (locationName != null)
|
||||
Padding(
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_
|
||||
import 'package:immich_mobile/providers/image/cache/remote_image_cache_manager.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
class RemoteThumbProvider extends CancellableImageProvider<RemoteThumbProvider>
|
||||
with CancellableImageProviderMixin<RemoteThumbProvider> {
|
||||
@@ -93,7 +94,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
||||
|
||||
final headers = ApiService.getRequestHeaders();
|
||||
final request = this.request = RemoteImageRequest(
|
||||
uri: getPreviewUrlForRemoteId(key.assetId),
|
||||
uri: getThumbnailUrlForRemoteId(key.assetId, type: AssetMediaSize.preview),
|
||||
headers: headers,
|
||||
cacheManager: cacheManager,
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/album.entity.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
@@ -10,14 +9,18 @@ String getThumbnailUrl(final Asset asset, {AssetMediaSize type = AssetMediaSize.
|
||||
}
|
||||
|
||||
String getThumbnailCacheKey(final Asset asset, {AssetMediaSize type = AssetMediaSize.thumbnail}) {
|
||||
return getThumbnailCacheKeyForRemoteId(asset.remoteId!, type: type);
|
||||
return getThumbnailCacheKeyForRemoteId(asset.remoteId!, asset.thumbhash!, type: type);
|
||||
}
|
||||
|
||||
String getThumbnailCacheKeyForRemoteId(final String id, {AssetMediaSize type = AssetMediaSize.thumbnail}) {
|
||||
String getThumbnailCacheKeyForRemoteId(
|
||||
final String id,
|
||||
final String thumbhash, {
|
||||
AssetMediaSize type = AssetMediaSize.thumbnail,
|
||||
}) {
|
||||
if (type == AssetMediaSize.thumbnail) {
|
||||
return 'thumbnail-image-$id';
|
||||
return 'thumbnail-image-$id-$thumbhash';
|
||||
} else {
|
||||
return '${id}_previewStage';
|
||||
return '${id}_${thumbhash}_previewStage';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,26 +35,25 @@ String getAlbumThumbNailCacheKey(final Album album, {AssetMediaSize type = Asset
|
||||
if (album.thumbnail.value?.remoteId == null) {
|
||||
return '';
|
||||
}
|
||||
return getThumbnailCacheKeyForRemoteId(album.thumbnail.value!.remoteId!, type: type);
|
||||
return getThumbnailCacheKeyForRemoteId(
|
||||
album.thumbnail.value!.remoteId!,
|
||||
album.thumbnail.value!.thumbhash!,
|
||||
type: type,
|
||||
);
|
||||
}
|
||||
|
||||
String getOriginalUrlForRemoteId(final String id) {
|
||||
return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/original';
|
||||
String getOriginalUrlForRemoteId(final String id, {bool edited = true}) {
|
||||
return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/original?edited=$edited';
|
||||
}
|
||||
|
||||
String getImageCacheKey(final Asset asset) {
|
||||
// Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id
|
||||
final isFromDto = asset.id == noDbId;
|
||||
return '${isFromDto ? asset.remoteId : asset.id}_fullStage';
|
||||
String getThumbnailUrlForRemoteId(
|
||||
final String id, {
|
||||
AssetMediaSize type = AssetMediaSize.thumbnail,
|
||||
bool edited = true,
|
||||
}) {
|
||||
return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail?size=${type.value}&edited=$edited';
|
||||
}
|
||||
|
||||
String getThumbnailUrlForRemoteId(final String id, {AssetMediaSize type = AssetMediaSize.thumbnail}) {
|
||||
return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail?size=${type.value}';
|
||||
}
|
||||
|
||||
String getPreviewUrlForRemoteId(final String id) =>
|
||||
'${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail?size=${AssetMediaSize.preview}';
|
||||
|
||||
String getPlaybackUrlForRemoteId(final String id) {
|
||||
return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/video/playback?';
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ class AssetLocation extends HookConsumerWidget {
|
||||
],
|
||||
),
|
||||
asset.isRemote ? const SizedBox.shrink() : const SizedBox(height: 16),
|
||||
ExifMap(exifInfo: exifInfo!, markerId: asset.remoteId),
|
||||
ExifMap(exifInfo: exifInfo!, markerId: asset.remoteId, markerAssetThumbhash: asset.thumbhash),
|
||||
const SizedBox(height: 16),
|
||||
getLocationName(),
|
||||
Text(
|
||||
|
||||
@@ -10,10 +10,20 @@ import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class ExifMap extends StatelessWidget {
|
||||
final ExifInfo exifInfo;
|
||||
// TODO: Pass in a BaseAsset instead of the ID and thumbhash when removing old timeline
|
||||
// This is currently structured this way because of the old timeline implementation
|
||||
// reusing this component
|
||||
final String? markerId;
|
||||
final String? markerAssetThumbhash;
|
||||
final MapCreatedCallback? onMapCreated;
|
||||
|
||||
const ExifMap({super.key, required this.exifInfo, this.markerId = 'marker', this.onMapCreated});
|
||||
const ExifMap({
|
||||
super.key,
|
||||
required this.exifInfo,
|
||||
this.markerAssetThumbhash,
|
||||
this.markerId = 'marker',
|
||||
this.onMapCreated,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -61,6 +71,7 @@ class ExifMap extends StatelessWidget {
|
||||
width: constraints.maxWidth,
|
||||
zoom: 12.0,
|
||||
assetMarkerRemoteId: markerId,
|
||||
assetThumbhash: markerAssetThumbhash,
|
||||
onTap: (tapPosition, latLong) async {
|
||||
Uri? uri = await createCoordinatesUri();
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||
@@ -29,12 +30,7 @@ import 'package:immich_mobile/utils/version_compatibility.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_logo.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_title_text.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
import 'package:immich_mobile/widgets/forms/login/email_input.dart';
|
||||
import 'package:immich_mobile/widgets/forms/login/loading_icon.dart';
|
||||
import 'package:immich_mobile/widgets/forms/login/login_button.dart';
|
||||
import 'package:immich_mobile/widgets/forms/login/o_auth_login_button.dart';
|
||||
import 'package:immich_mobile/widgets/forms/login/password_input.dart';
|
||||
import 'package:immich_mobile/widgets/forms/login/server_endpoint_input.dart';
|
||||
import 'package:immich_ui/immich_ui.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
@@ -45,16 +41,33 @@ class LoginForm extends HookConsumerWidget {
|
||||
|
||||
final log = Logger('LoginForm');
|
||||
|
||||
String? _validateUrl(String? url) {
|
||||
if (url == null || url.isEmpty) return null;
|
||||
|
||||
final parsedUrl = Uri.tryParse(url);
|
||||
if (parsedUrl == null || !parsedUrl.isAbsolute || !parsedUrl.scheme.startsWith("http") || parsedUrl.host.isEmpty) {
|
||||
return 'login_form_err_invalid_url'.tr();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String? _validateEmail(String? email) {
|
||||
if (email == null || email == '') return null;
|
||||
if (email.endsWith(' ')) return 'login_form_err_trailing_whitespace'.tr();
|
||||
if (email.startsWith(' ')) return 'login_form_err_leading_whitespace'.tr();
|
||||
if (email.contains(' ') || !email.contains('@')) {
|
||||
return 'login_form_err_invalid_email'.tr();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final emailController = useTextEditingController.fromValue(TextEditingValue.empty);
|
||||
final passwordController = useTextEditingController.fromValue(TextEditingValue.empty);
|
||||
final serverEndpointController = useTextEditingController.fromValue(TextEditingValue.empty);
|
||||
final emailFocusNode = useFocusNode();
|
||||
final passwordFocusNode = useFocusNode();
|
||||
final serverEndpointFocusNode = useFocusNode();
|
||||
final isLoading = useState<bool>(false);
|
||||
final isLoadingServer = useState<bool>(false);
|
||||
final isOauthEnable = useState<bool>(false);
|
||||
final isPasswordLoginEnable = useState<bool>(false);
|
||||
final oAuthButtonLabel = useState<String>('OAuth');
|
||||
@@ -96,7 +109,6 @@ class LoginForm extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
try {
|
||||
isLoadingServer.value = true;
|
||||
final endpoint = await ref.read(authProvider.notifier).validateServerUrl(serverUrl);
|
||||
|
||||
// Fetch and load server config and features
|
||||
@@ -120,7 +132,6 @@ class LoginForm extends HookConsumerWidget {
|
||||
);
|
||||
isOauthEnable.value = false;
|
||||
isPasswordLoginEnable.value = true;
|
||||
isLoadingServer.value = false;
|
||||
} on HandshakeException {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
@@ -130,7 +141,6 @@ class LoginForm extends HookConsumerWidget {
|
||||
);
|
||||
isOauthEnable.value = false;
|
||||
isPasswordLoginEnable.value = true;
|
||||
isLoadingServer.value = false;
|
||||
} catch (e) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
@@ -140,10 +150,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
);
|
||||
isOauthEnable.value = false;
|
||||
isPasswordLoginEnable.value = true;
|
||||
isLoadingServer.value = false;
|
||||
}
|
||||
|
||||
isLoadingServer.value = false;
|
||||
}
|
||||
|
||||
useEffect(() {
|
||||
@@ -230,8 +237,6 @@ class LoginForm extends HookConsumerWidget {
|
||||
login() async {
|
||||
TextInput.finishAutofillContext();
|
||||
|
||||
isLoading.value = true;
|
||||
|
||||
// Invalidate all api repository provider instance to take into account new access token
|
||||
invalidateAllApiRepositoryProviders(ref);
|
||||
|
||||
@@ -261,8 +266,6 @@ class LoginForm extends HookConsumerWidget {
|
||||
toastType: ToastType.error,
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,8 +309,6 @@ class LoginForm extends HookConsumerWidget {
|
||||
codeChallenge,
|
||||
);
|
||||
|
||||
isLoading.value = true;
|
||||
|
||||
// Invalidate all api repository provider instance to take into account new access token
|
||||
invalidateAllApiRepositoryProviders(ref);
|
||||
} catch (error, stack) {
|
||||
@@ -319,7 +320,6 @@ class LoginForm extends HookConsumerWidget {
|
||||
toastType: ToastType.error,
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
isLoading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -338,7 +338,6 @@ class LoginForm extends HookConsumerWidget {
|
||||
.saveAuthInfo(accessToken: loginResponseDto.accessToken);
|
||||
|
||||
if (isSuccess) {
|
||||
isLoading.value = false;
|
||||
final permission = ref.watch(galleryPermissionNotifier);
|
||||
final isBeta = Store.isBetaTimelineEnabled;
|
||||
if (!isBeta && (permission.isGranted || permission.isLimited)) {
|
||||
@@ -364,9 +363,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
toastType: ToastType.error,
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
} finally {}
|
||||
} else {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
@@ -374,66 +371,10 @@ class LoginForm extends HookConsumerWidget {
|
||||
toastType: ToastType.info,
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
isLoading.value = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
buildSelectServer() {
|
||||
const buttonRadius = 25.0;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
ServerEndpointInput(
|
||||
controller: serverEndpointController,
|
||||
focusNode: serverEndpointFocusNode,
|
||||
onSubmit: getServerAuthSettings,
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(buttonRadius),
|
||||
bottomLeft: Radius.circular(buttonRadius),
|
||||
),
|
||||
),
|
||||
),
|
||||
onPressed: () => context.pushRoute(const SettingsRoute()),
|
||||
icon: const Icon(Icons.settings_rounded),
|
||||
label: const Text(""),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 1),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(buttonRadius),
|
||||
bottomRight: Radius.circular(buttonRadius),
|
||||
),
|
||||
),
|
||||
),
|
||||
onPressed: isLoadingServer.value ? null : getServerAuthSettings,
|
||||
icon: const Icon(Icons.arrow_forward_rounded),
|
||||
label: const Text('next', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)).tr(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
if (isLoadingServer.value) const LoadingIcon(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
buildVersionCompatWarning() {
|
||||
checkVersionMismatch();
|
||||
|
||||
@@ -455,66 +396,102 @@ class LoginForm extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
buildLogin() {
|
||||
return AutofillGroup(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
buildVersionCompatWarning(),
|
||||
Text(
|
||||
sanitizeUrl(serverEndpointController.text),
|
||||
style: context.textTheme.displaySmall,
|
||||
textAlign: TextAlign.center,
|
||||
final serverSelectionOrLogin = serverEndpoint.value == null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(top: ImmichSpacing.md),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
ImmichForm(
|
||||
submitText: 'next'.t(context: context),
|
||||
submitIcon: Icons.arrow_forward_rounded,
|
||||
onSubmit: getServerAuthSettings,
|
||||
child: ImmichTextInput(
|
||||
controller: serverEndpointController,
|
||||
label: 'login_form_endpoint_url'.t(context: context),
|
||||
hintText: 'login_form_endpoint_hint'.t(context: context),
|
||||
validator: _validateUrl,
|
||||
keyboardAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.url,
|
||||
autofillHints: const [AutofillHints.url],
|
||||
onSubmit: (ctx, _) => ImmichForm.of(ctx).submit(),
|
||||
),
|
||||
),
|
||||
ImmichTextButton(
|
||||
labelText: 'settings'.t(context: context),
|
||||
icon: Icons.settings,
|
||||
variant: ImmichVariant.ghost,
|
||||
onPressed: () => context.pushRoute(const SettingsRoute()),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (isPasswordLoginEnable.value) ...[
|
||||
const SizedBox(height: 18),
|
||||
EmailInput(
|
||||
controller: emailController,
|
||||
focusNode: emailFocusNode,
|
||||
onSubmit: passwordFocusNode.requestFocus,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
PasswordInput(controller: passwordController, focusNode: passwordFocusNode, onSubmit: login),
|
||||
],
|
||||
|
||||
// Note: This used to have an AnimatedSwitcher, but was removed
|
||||
// because of https://github.com/flutter/flutter/issues/120874
|
||||
isLoading.value
|
||||
? const LoadingIcon()
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 18),
|
||||
if (isPasswordLoginEnable.value) LoginButton(onPressed: login),
|
||||
if (isOauthEnable.value) ...[
|
||||
if (isPasswordLoginEnable.value)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Divider(color: context.isDarkTheme ? Colors.white : Colors.black),
|
||||
),
|
||||
OAuthLoginButton(
|
||||
serverEndpointController: serverEndpointController,
|
||||
buttonLabel: oAuthButtonLabel.value,
|
||||
isLoading: isLoading,
|
||||
onPressed: oAuthLogin,
|
||||
)
|
||||
: AutofillGroup(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
buildVersionCompatWarning(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: ImmichSpacing.md),
|
||||
child: Text(
|
||||
sanitizeUrl(serverEndpointController.text),
|
||||
style: context.textTheme.displaySmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
if (isPasswordLoginEnable.value)
|
||||
ImmichForm(
|
||||
submitText: 'login'.t(context: context),
|
||||
submitIcon: Icons.login_rounded,
|
||||
onSubmit: login,
|
||||
child: Column(
|
||||
spacing: ImmichSpacing.md,
|
||||
children: [
|
||||
ImmichTextInput(
|
||||
controller: emailController,
|
||||
label: 'email'.t(context: context),
|
||||
hintText: 'login_form_email_hint'.t(context: context),
|
||||
validator: _validateEmail,
|
||||
keyboardAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
autofillHints: const [AutofillHints.email],
|
||||
onSubmit: (_, _) => passwordFocusNode.requestFocus(),
|
||||
),
|
||||
ImmichPasswordInput(
|
||||
controller: passwordController,
|
||||
focusNode: passwordFocusNode,
|
||||
label: 'password'.t(context: context),
|
||||
hintText: 'login_form_password_hint'.t(context: context),
|
||||
keyboardAction: TextInputAction.go,
|
||||
onSubmit: (ctx, _) => ImmichForm.of(ctx).submit(),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!isOauthEnable.value && !isPasswordLoginEnable.value) Center(child: const Text('login_disabled').tr()),
|
||||
const SizedBox(height: 12),
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => serverEndpoint.value = null,
|
||||
label: const Text('back').tr(),
|
||||
if (isOauthEnable.value)
|
||||
ImmichForm(
|
||||
submitText: oAuthButtonLabel.value,
|
||||
submitIcon: Icons.pin_outlined,
|
||||
onSubmit: oAuthLogin,
|
||||
child: isPasswordLoginEnable.value
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(left: 18.0, right: 18.0, top: 12.0),
|
||||
child: Divider(color: context.isDarkTheme ? Colors.white : Colors.black, height: 5),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
if (!isOauthEnable.value && !isPasswordLoginEnable.value)
|
||||
Center(child: const Text('login_disabled').tr()),
|
||||
ImmichTextButton(
|
||||
labelText: 'back'.t(context: context),
|
||||
icon: Icons.arrow_back,
|
||||
variant: ImmichVariant.ghost,
|
||||
onPressed: () => serverEndpoint.value = null,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final serverSelectionOrLogin = serverEndpoint.value == null ? buildSelectServer() : buildLogin();
|
||||
);
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
|
||||
class OAuthLoginButton extends ConsumerWidget {
|
||||
final TextEditingController serverEndpointController;
|
||||
final ValueNotifier<bool> isLoading;
|
||||
final String buttonLabel;
|
||||
final Function() onPressed;
|
||||
|
||||
const OAuthLoginButton({
|
||||
super.key,
|
||||
required this.serverEndpointController,
|
||||
required this.isLoading,
|
||||
required this.buttonLabel,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: context.primaryColor.withAlpha(230),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
),
|
||||
onPressed: onPressed,
|
||||
icon: const Icon(Icons.pin_rounded),
|
||||
label: Text(buttonLabel, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
class PasswordInput extends HookConsumerWidget {
|
||||
final TextEditingController controller;
|
||||
final FocusNode? focusNode;
|
||||
final Function()? onSubmit;
|
||||
|
||||
const PasswordInput({super.key, required this.controller, this.focusNode, this.onSubmit});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isPasswordVisible = useState<bool>(false);
|
||||
|
||||
return TextFormField(
|
||||
obscureText: !isPasswordVisible.value,
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'password'.tr(),
|
||||
border: const OutlineInputBorder(),
|
||||
hintText: 'login_form_password_hint'.tr(),
|
||||
hintStyle: const TextStyle(fontWeight: FontWeight.normal, fontSize: 14),
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () => isPasswordVisible.value = !isPasswordVisible.value,
|
||||
icon: Icon(isPasswordVisible.value ? Icons.visibility_off_sharp : Icons.visibility_sharp),
|
||||
),
|
||||
),
|
||||
autofillHints: const [AutofillHints.password],
|
||||
keyboardType: TextInputType.text,
|
||||
onFieldSubmitted: (_) => onSubmit?.call(),
|
||||
focusNode: focusNode,
|
||||
textInputAction: TextInputAction.go,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/utils/url_helper.dart';
|
||||
|
||||
class ServerEndpointInput extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final FocusNode focusNode;
|
||||
final Function()? onSubmit;
|
||||
|
||||
const ServerEndpointInput({super.key, required this.controller, required this.focusNode, this.onSubmit});
|
||||
|
||||
String? _validateInput(String? url) {
|
||||
if (url == null || url.isEmpty) return null;
|
||||
|
||||
final parsedUrl = Uri.tryParse(sanitizeUrl(url));
|
||||
if (parsedUrl == null || !parsedUrl.isAbsolute || !parsedUrl.scheme.startsWith("http") || parsedUrl.host.isEmpty) {
|
||||
return 'login_form_err_invalid_url'.tr();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0),
|
||||
child: TextFormField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'login_form_endpoint_url'.tr(),
|
||||
border: const OutlineInputBorder(),
|
||||
hintText: 'login_form_endpoint_hint'.tr(),
|
||||
errorMaxLines: 4,
|
||||
),
|
||||
validator: _validateInput,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
focusNode: focusNode,
|
||||
autofillHints: const [AutofillHints.url],
|
||||
keyboardType: TextInputType.url,
|
||||
autocorrect: false,
|
||||
onFieldSubmitted: (_) => onSubmit?.call(),
|
||||
textInputAction: TextInputAction.go,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ class MapThumbnail extends HookConsumerWidget {
|
||||
final Function(Point<double>, LatLng)? onTap;
|
||||
final LatLng centre;
|
||||
final String? assetMarkerRemoteId;
|
||||
final String? assetThumbhash;
|
||||
final bool showMarkerPin;
|
||||
final double zoom;
|
||||
final double height;
|
||||
@@ -35,6 +36,7 @@ class MapThumbnail extends HookConsumerWidget {
|
||||
this.onTap,
|
||||
this.zoom = 8,
|
||||
this.assetMarkerRemoteId,
|
||||
this.assetThumbhash,
|
||||
this.showMarkerPin = false,
|
||||
this.themeMode,
|
||||
this.showAttribution = true,
|
||||
@@ -109,8 +111,13 @@ class MapThumbnail extends HookConsumerWidget {
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: position,
|
||||
builder: (_, value, __) => value != null && assetMarkerRemoteId != null
|
||||
? PositionedAssetMarkerIcon(size: height / 2, point: value, assetRemoteId: assetMarkerRemoteId!)
|
||||
builder: (_, value, __) => value != null && assetMarkerRemoteId != null && assetThumbhash != null
|
||||
? PositionedAssetMarkerIcon(
|
||||
size: height / 2,
|
||||
point: value,
|
||||
assetRemoteId: assetMarkerRemoteId!,
|
||||
assetThumbhash: assetThumbhash!,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
class PositionedAssetMarkerIcon extends StatelessWidget {
|
||||
final Point<num> point;
|
||||
final String assetRemoteId;
|
||||
final String assetThumbhash;
|
||||
final double size;
|
||||
final int durationInMilliseconds;
|
||||
|
||||
@@ -18,6 +19,7 @@ class PositionedAssetMarkerIcon extends StatelessWidget {
|
||||
const PositionedAssetMarkerIcon({
|
||||
required this.point,
|
||||
required this.assetRemoteId,
|
||||
required this.assetThumbhash,
|
||||
this.size = 100,
|
||||
this.durationInMilliseconds = 100,
|
||||
this.onTap,
|
||||
@@ -35,7 +37,7 @@ class PositionedAssetMarkerIcon extends StatelessWidget {
|
||||
onTap: () => onTap?.call(),
|
||||
child: SizedBox.square(
|
||||
dimension: size,
|
||||
child: _AssetMarkerIcon(id: assetRemoteId, key: Key(assetRemoteId)),
|
||||
child: _AssetMarkerIcon(id: assetRemoteId, thumbhash: assetThumbhash, key: Key(assetRemoteId)),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -43,14 +45,15 @@ class PositionedAssetMarkerIcon extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _AssetMarkerIcon extends StatelessWidget {
|
||||
const _AssetMarkerIcon({required this.id, super.key});
|
||||
const _AssetMarkerIcon({required this.id, required this.thumbhash, super.key});
|
||||
|
||||
final String id;
|
||||
final String thumbhash;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final imageUrl = getThumbnailUrlForRemoteId(id);
|
||||
final cacheKey = getThumbnailCacheKeyForRemoteId(id);
|
||||
final cacheKey = getThumbnailCacheKeyForRemoteId(id, thumbhash);
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Stack(
|
||||
|
||||
22
mobile/openapi/README.md
generated
22
mobile/openapi/README.md
generated
@@ -100,8 +100,11 @@ Class | Method | HTTP request | Description
|
||||
*AssetsApi* | [**copyAsset**](doc//AssetsApi.md#copyasset) | **PUT** /assets/copy | Copy asset
|
||||
*AssetsApi* | [**deleteAssetMetadata**](doc//AssetsApi.md#deleteassetmetadata) | **DELETE** /assets/{id}/metadata/{key} | Delete asset metadata by key
|
||||
*AssetsApi* | [**deleteAssets**](doc//AssetsApi.md#deleteassets) | **DELETE** /assets | Delete assets
|
||||
*AssetsApi* | [**deleteBulkAssetMetadata**](doc//AssetsApi.md#deletebulkassetmetadata) | **DELETE** /assets/metadata | Delete asset metadata
|
||||
*AssetsApi* | [**downloadAsset**](doc//AssetsApi.md#downloadasset) | **GET** /assets/{id}/original | Download original asset
|
||||
*AssetsApi* | [**editAsset**](doc//AssetsApi.md#editasset) | **PUT** /assets/{id}/edits | Apply edits to an existing asset
|
||||
*AssetsApi* | [**getAllUserAssetsByDeviceId**](doc//AssetsApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | Retrieve assets by device ID
|
||||
*AssetsApi* | [**getAssetEdits**](doc//AssetsApi.md#getassetedits) | **GET** /assets/{id}/edits | Retrieve edits for an existing asset
|
||||
*AssetsApi* | [**getAssetInfo**](doc//AssetsApi.md#getassetinfo) | **GET** /assets/{id} | Retrieve an asset
|
||||
*AssetsApi* | [**getAssetMetadata**](doc//AssetsApi.md#getassetmetadata) | **GET** /assets/{id}/metadata | Get asset metadata
|
||||
*AssetsApi* | [**getAssetMetadataByKey**](doc//AssetsApi.md#getassetmetadatabykey) | **GET** /assets/{id}/metadata/{key} | Retrieve asset metadata by key
|
||||
@@ -109,11 +112,13 @@ Class | Method | HTTP request | Description
|
||||
*AssetsApi* | [**getAssetStatistics**](doc//AssetsApi.md#getassetstatistics) | **GET** /assets/statistics | Get asset statistics
|
||||
*AssetsApi* | [**getRandom**](doc//AssetsApi.md#getrandom) | **GET** /assets/random | Get random assets
|
||||
*AssetsApi* | [**playAssetVideo**](doc//AssetsApi.md#playassetvideo) | **GET** /assets/{id}/video/playback | Play asset video
|
||||
*AssetsApi* | [**removeAssetEdits**](doc//AssetsApi.md#removeassetedits) | **DELETE** /assets/{id}/edits | Remove edits from an existing asset
|
||||
*AssetsApi* | [**replaceAsset**](doc//AssetsApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace asset
|
||||
*AssetsApi* | [**runAssetJobs**](doc//AssetsApi.md#runassetjobs) | **POST** /assets/jobs | Run an asset job
|
||||
*AssetsApi* | [**updateAsset**](doc//AssetsApi.md#updateasset) | **PUT** /assets/{id} | Update an asset
|
||||
*AssetsApi* | [**updateAssetMetadata**](doc//AssetsApi.md#updateassetmetadata) | **PUT** /assets/{id}/metadata | Update asset metadata
|
||||
*AssetsApi* | [**updateAssets**](doc//AssetsApi.md#updateassets) | **PUT** /assets | Update assets
|
||||
*AssetsApi* | [**updateBulkAssetMetadata**](doc//AssetsApi.md#updatebulkassetmetadata) | **PUT** /assets/metadata | Upsert asset metadata
|
||||
*AssetsApi* | [**uploadAsset**](doc//AssetsApi.md#uploadasset) | **POST** /assets | Upload asset
|
||||
*AssetsApi* | [**viewAsset**](doc//AssetsApi.md#viewasset) | **GET** /assets/{id}/thumbnail | View asset thumbnail
|
||||
*AuthenticationApi* | [**changePassword**](doc//AuthenticationApi.md#changepassword) | **POST** /auth/change-password | Change password
|
||||
@@ -344,6 +349,13 @@ Class | Method | HTTP request | Description
|
||||
- [AssetCopyDto](doc//AssetCopyDto.md)
|
||||
- [AssetDeltaSyncDto](doc//AssetDeltaSyncDto.md)
|
||||
- [AssetDeltaSyncResponseDto](doc//AssetDeltaSyncResponseDto.md)
|
||||
- [AssetEditAction](doc//AssetEditAction.md)
|
||||
- [AssetEditActionCrop](doc//AssetEditActionCrop.md)
|
||||
- [AssetEditActionListDto](doc//AssetEditActionListDto.md)
|
||||
- [AssetEditActionListDtoEditsInner](doc//AssetEditActionListDtoEditsInner.md)
|
||||
- [AssetEditActionMirror](doc//AssetEditActionMirror.md)
|
||||
- [AssetEditActionRotate](doc//AssetEditActionRotate.md)
|
||||
- [AssetEditsDto](doc//AssetEditsDto.md)
|
||||
- [AssetFaceCreateDto](doc//AssetFaceCreateDto.md)
|
||||
- [AssetFaceDeleteDto](doc//AssetFaceDeleteDto.md)
|
||||
- [AssetFaceResponseDto](doc//AssetFaceResponseDto.md)
|
||||
@@ -358,7 +370,11 @@ Class | Method | HTTP request | Description
|
||||
- [AssetMediaResponseDto](doc//AssetMediaResponseDto.md)
|
||||
- [AssetMediaSize](doc//AssetMediaSize.md)
|
||||
- [AssetMediaStatus](doc//AssetMediaStatus.md)
|
||||
- [AssetMetadataKey](doc//AssetMetadataKey.md)
|
||||
- [AssetMetadataBulkDeleteDto](doc//AssetMetadataBulkDeleteDto.md)
|
||||
- [AssetMetadataBulkDeleteItemDto](doc//AssetMetadataBulkDeleteItemDto.md)
|
||||
- [AssetMetadataBulkResponseDto](doc//AssetMetadataBulkResponseDto.md)
|
||||
- [AssetMetadataBulkUpsertDto](doc//AssetMetadataBulkUpsertDto.md)
|
||||
- [AssetMetadataBulkUpsertItemDto](doc//AssetMetadataBulkUpsertItemDto.md)
|
||||
- [AssetMetadataResponseDto](doc//AssetMetadataResponseDto.md)
|
||||
- [AssetMetadataUpsertDto](doc//AssetMetadataUpsertDto.md)
|
||||
- [AssetMetadataUpsertItemDto](doc//AssetMetadataUpsertItemDto.md)
|
||||
@@ -387,6 +403,7 @@ Class | Method | HTTP request | Description
|
||||
- [CreateAlbumDto](doc//CreateAlbumDto.md)
|
||||
- [CreateLibraryDto](doc//CreateLibraryDto.md)
|
||||
- [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md)
|
||||
- [CropParameters](doc//CropParameters.md)
|
||||
- [DatabaseBackupConfig](doc//DatabaseBackupConfig.md)
|
||||
- [DownloadArchiveInfo](doc//DownloadArchiveInfo.md)
|
||||
- [DownloadInfoDto](doc//DownloadInfoDto.md)
|
||||
@@ -431,6 +448,8 @@ Class | Method | HTTP request | Description
|
||||
- [MemoryUpdateDto](doc//MemoryUpdateDto.md)
|
||||
- [MergePersonDto](doc//MergePersonDto.md)
|
||||
- [MetadataSearchDto](doc//MetadataSearchDto.md)
|
||||
- [MirrorAxis](doc//MirrorAxis.md)
|
||||
- [MirrorParameters](doc//MirrorParameters.md)
|
||||
- [NotificationCreateDto](doc//NotificationCreateDto.md)
|
||||
- [NotificationDeleteAllDto](doc//NotificationDeleteAllDto.md)
|
||||
- [NotificationDto](doc//NotificationDto.md)
|
||||
@@ -491,6 +510,7 @@ Class | Method | HTTP request | Description
|
||||
- [ReactionLevel](doc//ReactionLevel.md)
|
||||
- [ReactionType](doc//ReactionType.md)
|
||||
- [ReverseGeocodingStateResponseDto](doc//ReverseGeocodingStateResponseDto.md)
|
||||
- [RotateParameters](doc//RotateParameters.md)
|
||||
- [SearchAlbumResponseDto](doc//SearchAlbumResponseDto.md)
|
||||
- [SearchAssetResponseDto](doc//SearchAssetResponseDto.md)
|
||||
- [SearchExploreItem](doc//SearchExploreItem.md)
|
||||
|
||||
17
mobile/openapi/lib/api.dart
generated
17
mobile/openapi/lib/api.dart
generated
@@ -95,6 +95,13 @@ part 'model/asset_bulk_upload_check_result.dart';
|
||||
part 'model/asset_copy_dto.dart';
|
||||
part 'model/asset_delta_sync_dto.dart';
|
||||
part 'model/asset_delta_sync_response_dto.dart';
|
||||
part 'model/asset_edit_action.dart';
|
||||
part 'model/asset_edit_action_crop.dart';
|
||||
part 'model/asset_edit_action_list_dto.dart';
|
||||
part 'model/asset_edit_action_list_dto_edits_inner.dart';
|
||||
part 'model/asset_edit_action_mirror.dart';
|
||||
part 'model/asset_edit_action_rotate.dart';
|
||||
part 'model/asset_edits_dto.dart';
|
||||
part 'model/asset_face_create_dto.dart';
|
||||
part 'model/asset_face_delete_dto.dart';
|
||||
part 'model/asset_face_response_dto.dart';
|
||||
@@ -109,7 +116,11 @@ part 'model/asset_jobs_dto.dart';
|
||||
part 'model/asset_media_response_dto.dart';
|
||||
part 'model/asset_media_size.dart';
|
||||
part 'model/asset_media_status.dart';
|
||||
part 'model/asset_metadata_key.dart';
|
||||
part 'model/asset_metadata_bulk_delete_dto.dart';
|
||||
part 'model/asset_metadata_bulk_delete_item_dto.dart';
|
||||
part 'model/asset_metadata_bulk_response_dto.dart';
|
||||
part 'model/asset_metadata_bulk_upsert_dto.dart';
|
||||
part 'model/asset_metadata_bulk_upsert_item_dto.dart';
|
||||
part 'model/asset_metadata_response_dto.dart';
|
||||
part 'model/asset_metadata_upsert_dto.dart';
|
||||
part 'model/asset_metadata_upsert_item_dto.dart';
|
||||
@@ -138,6 +149,7 @@ part 'model/contributor_count_response_dto.dart';
|
||||
part 'model/create_album_dto.dart';
|
||||
part 'model/create_library_dto.dart';
|
||||
part 'model/create_profile_image_response_dto.dart';
|
||||
part 'model/crop_parameters.dart';
|
||||
part 'model/database_backup_config.dart';
|
||||
part 'model/download_archive_info.dart';
|
||||
part 'model/download_info_dto.dart';
|
||||
@@ -182,6 +194,8 @@ part 'model/memory_type.dart';
|
||||
part 'model/memory_update_dto.dart';
|
||||
part 'model/merge_person_dto.dart';
|
||||
part 'model/metadata_search_dto.dart';
|
||||
part 'model/mirror_axis.dart';
|
||||
part 'model/mirror_parameters.dart';
|
||||
part 'model/notification_create_dto.dart';
|
||||
part 'model/notification_delete_all_dto.dart';
|
||||
part 'model/notification_dto.dart';
|
||||
@@ -242,6 +256,7 @@ part 'model/ratings_update.dart';
|
||||
part 'model/reaction_level.dart';
|
||||
part 'model/reaction_type.dart';
|
||||
part 'model/reverse_geocoding_state_response_dto.dart';
|
||||
part 'model/rotate_parameters.dart';
|
||||
part 'model/search_album_response_dto.dart';
|
||||
part 'model/search_asset_response_dto.dart';
|
||||
part 'model/search_explore_item.dart';
|
||||
|
||||
334
mobile/openapi/lib/api/assets_api.dart
generated
334
mobile/openapi/lib/api/assets_api.dart
generated
@@ -186,12 +186,12 @@ class AssetsApi {
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AssetMetadataKey] key (required):
|
||||
Future<Response> deleteAssetMetadataWithHttpInfo(String id, AssetMetadataKey key,) async {
|
||||
/// * [String] key (required):
|
||||
Future<Response> deleteAssetMetadataWithHttpInfo(String id, String key,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/{id}/metadata/{key}'
|
||||
.replaceAll('{id}', id)
|
||||
.replaceAll('{key}', key.toString());
|
||||
.replaceAll('{key}', key);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
@@ -222,8 +222,8 @@ class AssetsApi {
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AssetMetadataKey] key (required):
|
||||
Future<void> deleteAssetMetadata(String id, AssetMetadataKey key,) async {
|
||||
/// * [String] key (required):
|
||||
Future<void> deleteAssetMetadata(String id, String key,) async {
|
||||
final response = await deleteAssetMetadataWithHttpInfo(id, key,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
@@ -278,6 +278,54 @@ class AssetsApi {
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete asset metadata
|
||||
///
|
||||
/// Delete metadata key-value pairs for multiple assets.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [AssetMetadataBulkDeleteDto] assetMetadataBulkDeleteDto (required):
|
||||
Future<Response> deleteBulkAssetMetadataWithHttpInfo(AssetMetadataBulkDeleteDto assetMetadataBulkDeleteDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/metadata';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = assetMetadataBulkDeleteDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'DELETE',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Delete asset metadata
|
||||
///
|
||||
/// Delete metadata key-value pairs for multiple assets.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [AssetMetadataBulkDeleteDto] assetMetadataBulkDeleteDto (required):
|
||||
Future<void> deleteBulkAssetMetadata(AssetMetadataBulkDeleteDto assetMetadataBulkDeleteDto,) async {
|
||||
final response = await deleteBulkAssetMetadataWithHttpInfo(assetMetadataBulkDeleteDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
/// Download original asset
|
||||
///
|
||||
/// Downloads the original file of the specified asset.
|
||||
@@ -288,10 +336,12 @@ class AssetsApi {
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [bool] edited:
|
||||
///
|
||||
/// * [String] key:
|
||||
///
|
||||
/// * [String] slug:
|
||||
Future<Response> downloadAssetWithHttpInfo(String id, { String? key, String? slug, }) async {
|
||||
Future<Response> downloadAssetWithHttpInfo(String id, { bool? edited, String? key, String? slug, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/{id}/original'
|
||||
.replaceAll('{id}', id);
|
||||
@@ -303,6 +353,9 @@ class AssetsApi {
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
if (edited != null) {
|
||||
queryParams.addAll(_queryParams('', 'edited', edited));
|
||||
}
|
||||
if (key != null) {
|
||||
queryParams.addAll(_queryParams('', 'key', key));
|
||||
}
|
||||
@@ -332,11 +385,13 @@ class AssetsApi {
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [bool] edited:
|
||||
///
|
||||
/// * [String] key:
|
||||
///
|
||||
/// * [String] slug:
|
||||
Future<MultipartFile?> downloadAsset(String id, { String? key, String? slug, }) async {
|
||||
final response = await downloadAssetWithHttpInfo(id, key: key, slug: slug, );
|
||||
Future<MultipartFile?> downloadAsset(String id, { bool? edited, String? key, String? slug, }) async {
|
||||
final response = await downloadAssetWithHttpInfo(id, edited: edited, key: key, slug: slug, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
@@ -350,6 +405,67 @@ class AssetsApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Apply edits to an existing asset
|
||||
///
|
||||
/// Apply a series of edit actions (crop, rotate, mirror) to the specified asset.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AssetEditActionListDto] assetEditActionListDto (required):
|
||||
Future<Response> editAssetWithHttpInfo(String id, AssetEditActionListDto assetEditActionListDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/{id}/edits'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = assetEditActionListDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Apply edits to an existing asset
|
||||
///
|
||||
/// Apply a series of edit actions (crop, rotate, mirror) to the specified asset.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AssetEditActionListDto] assetEditActionListDto (required):
|
||||
Future<AssetEditsDto?> editAsset(String id, AssetEditActionListDto assetEditActionListDto,) async {
|
||||
final response = await editAssetWithHttpInfo(id, assetEditActionListDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetEditsDto',) as AssetEditsDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Retrieve assets by device ID
|
||||
///
|
||||
/// Get all asset of a device that are in the database, ID only.
|
||||
@@ -410,6 +526,63 @@ class AssetsApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Retrieve edits for an existing asset
|
||||
///
|
||||
/// Retrieve a series of edit actions (crop, rotate, mirror) associated with the specified asset.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<Response> getAssetEditsWithHttpInfo(String id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/{id}/edits'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'GET',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Retrieve edits for an existing asset
|
||||
///
|
||||
/// Retrieve a series of edit actions (crop, rotate, mirror) associated with the specified asset.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<AssetEditsDto?> getAssetEdits(String id,) async {
|
||||
final response = await getAssetEditsWithHttpInfo(id,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetEditsDto',) as AssetEditsDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Retrieve an asset
|
||||
///
|
||||
/// Retrieve detailed information about a specific asset.
|
||||
@@ -552,12 +725,12 @@ class AssetsApi {
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AssetMetadataKey] key (required):
|
||||
Future<Response> getAssetMetadataByKeyWithHttpInfo(String id, AssetMetadataKey key,) async {
|
||||
/// * [String] key (required):
|
||||
Future<Response> getAssetMetadataByKeyWithHttpInfo(String id, String key,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/{id}/metadata/{key}'
|
||||
.replaceAll('{id}', id)
|
||||
.replaceAll('{key}', key.toString());
|
||||
.replaceAll('{key}', key);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
@@ -588,8 +761,8 @@ class AssetsApi {
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AssetMetadataKey] key (required):
|
||||
Future<AssetMetadataResponseDto?> getAssetMetadataByKey(String id, AssetMetadataKey key,) async {
|
||||
/// * [String] key (required):
|
||||
Future<AssetMetadataResponseDto?> getAssetMetadataByKey(String id, String key,) async {
|
||||
final response = await getAssetMetadataByKeyWithHttpInfo(id, key,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
@@ -873,6 +1046,55 @@ class AssetsApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Remove edits from an existing asset
|
||||
///
|
||||
/// Removes all edit actions (crop, rotate, mirror) associated with the specified asset.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<Response> removeAssetEditsWithHttpInfo(String id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/{id}/edits'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'DELETE',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Remove edits from an existing asset
|
||||
///
|
||||
/// Removes all edit actions (crop, rotate, mirror) associated with the specified asset.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<void> removeAssetEdits(String id,) async {
|
||||
final response = await removeAssetEditsWithHttpInfo(id,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace asset
|
||||
///
|
||||
/// Replace the asset with new file, without changing its id.
|
||||
@@ -1228,6 +1450,65 @@ class AssetsApi {
|
||||
}
|
||||
}
|
||||
|
||||
/// Upsert asset metadata
|
||||
///
|
||||
/// Upsert metadata key-value pairs for multiple assets.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [AssetMetadataBulkUpsertDto] assetMetadataBulkUpsertDto (required):
|
||||
Future<Response> updateBulkAssetMetadataWithHttpInfo(AssetMetadataBulkUpsertDto assetMetadataBulkUpsertDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/metadata';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = assetMetadataBulkUpsertDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upsert asset metadata
|
||||
///
|
||||
/// Upsert metadata key-value pairs for multiple assets.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [AssetMetadataBulkUpsertDto] assetMetadataBulkUpsertDto (required):
|
||||
Future<List<AssetMetadataBulkResponseDto>?> updateBulkAssetMetadata(AssetMetadataBulkUpsertDto assetMetadataBulkUpsertDto,) async {
|
||||
final response = await updateBulkAssetMetadataWithHttpInfo(assetMetadataBulkUpsertDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<AssetMetadataBulkResponseDto>') as List)
|
||||
.cast<AssetMetadataBulkResponseDto>()
|
||||
.toList(growable: false);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Upload asset
|
||||
///
|
||||
/// Uploads a new asset to the server.
|
||||
@@ -1246,8 +1527,6 @@ class AssetsApi {
|
||||
///
|
||||
/// * [DateTime] fileModifiedAt (required):
|
||||
///
|
||||
/// * [List<AssetMetadataUpsertItemDto>] metadata (required):
|
||||
///
|
||||
/// * [String] key:
|
||||
///
|
||||
/// * [String] slug:
|
||||
@@ -1263,10 +1542,12 @@ class AssetsApi {
|
||||
///
|
||||
/// * [String] livePhotoVideoId:
|
||||
///
|
||||
/// * [List<AssetMetadataUpsertItemDto>] metadata:
|
||||
///
|
||||
/// * [MultipartFile] sidecarData:
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
Future<Response> uploadAssetWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, List<AssetMetadataUpsertItemDto> metadata, { String? key, String? slug, String? xImmichChecksum, String? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
|
||||
Future<Response> uploadAssetWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, String? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List<AssetMetadataUpsertItemDto>? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets';
|
||||
|
||||
@@ -1373,8 +1654,6 @@ class AssetsApi {
|
||||
///
|
||||
/// * [DateTime] fileModifiedAt (required):
|
||||
///
|
||||
/// * [List<AssetMetadataUpsertItemDto>] metadata (required):
|
||||
///
|
||||
/// * [String] key:
|
||||
///
|
||||
/// * [String] slug:
|
||||
@@ -1390,11 +1669,13 @@ class AssetsApi {
|
||||
///
|
||||
/// * [String] livePhotoVideoId:
|
||||
///
|
||||
/// * [List<AssetMetadataUpsertItemDto>] metadata:
|
||||
///
|
||||
/// * [MultipartFile] sidecarData:
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
Future<AssetMediaResponseDto?> uploadAsset(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, List<AssetMetadataUpsertItemDto> metadata, { String? key, String? slug, String? xImmichChecksum, String? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
|
||||
final response = await uploadAssetWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, metadata, key: key, slug: slug, xImmichChecksum: xImmichChecksum, duration: duration, filename: filename, isFavorite: isFavorite, livePhotoVideoId: livePhotoVideoId, sidecarData: sidecarData, visibility: visibility, );
|
||||
Future<AssetMediaResponseDto?> uploadAsset(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, String? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List<AssetMetadataUpsertItemDto>? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
|
||||
final response = await uploadAssetWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, slug: slug, xImmichChecksum: xImmichChecksum, duration: duration, filename: filename, isFavorite: isFavorite, livePhotoVideoId: livePhotoVideoId, metadata: metadata, sidecarData: sidecarData, visibility: visibility, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
@@ -1418,12 +1699,14 @@ class AssetsApi {
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [bool] edited:
|
||||
///
|
||||
/// * [String] key:
|
||||
///
|
||||
/// * [AssetMediaSize] size:
|
||||
///
|
||||
/// * [String] slug:
|
||||
Future<Response> viewAssetWithHttpInfo(String id, { String? key, AssetMediaSize? size, String? slug, }) async {
|
||||
Future<Response> viewAssetWithHttpInfo(String id, { bool? edited, String? key, AssetMediaSize? size, String? slug, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/{id}/thumbnail'
|
||||
.replaceAll('{id}', id);
|
||||
@@ -1435,6 +1718,9 @@ class AssetsApi {
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
if (edited != null) {
|
||||
queryParams.addAll(_queryParams('', 'edited', edited));
|
||||
}
|
||||
if (key != null) {
|
||||
queryParams.addAll(_queryParams('', 'key', key));
|
||||
}
|
||||
@@ -1467,13 +1753,15 @@ class AssetsApi {
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [bool] edited:
|
||||
///
|
||||
/// * [String] key:
|
||||
///
|
||||
/// * [AssetMediaSize] size:
|
||||
///
|
||||
/// * [String] slug:
|
||||
Future<MultipartFile?> viewAsset(String id, { String? key, AssetMediaSize? size, String? slug, }) async {
|
||||
final response = await viewAssetWithHttpInfo(id, key: key, size: size, slug: slug, );
|
||||
Future<MultipartFile?> viewAsset(String id, { bool? edited, String? key, AssetMediaSize? size, String? slug, }) async {
|
||||
final response = await viewAssetWithHttpInfo(id, edited: edited, key: key, size: size, slug: slug, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
||||
34
mobile/openapi/lib/api_client.dart
generated
34
mobile/openapi/lib/api_client.dart
generated
@@ -238,6 +238,20 @@ class ApiClient {
|
||||
return AssetDeltaSyncDto.fromJson(value);
|
||||
case 'AssetDeltaSyncResponseDto':
|
||||
return AssetDeltaSyncResponseDto.fromJson(value);
|
||||
case 'AssetEditAction':
|
||||
return AssetEditActionTypeTransformer().decode(value);
|
||||
case 'AssetEditActionCrop':
|
||||
return AssetEditActionCrop.fromJson(value);
|
||||
case 'AssetEditActionListDto':
|
||||
return AssetEditActionListDto.fromJson(value);
|
||||
case 'AssetEditActionListDtoEditsInner':
|
||||
return AssetEditActionListDtoEditsInner.fromJson(value);
|
||||
case 'AssetEditActionMirror':
|
||||
return AssetEditActionMirror.fromJson(value);
|
||||
case 'AssetEditActionRotate':
|
||||
return AssetEditActionRotate.fromJson(value);
|
||||
case 'AssetEditsDto':
|
||||
return AssetEditsDto.fromJson(value);
|
||||
case 'AssetFaceCreateDto':
|
||||
return AssetFaceCreateDto.fromJson(value);
|
||||
case 'AssetFaceDeleteDto':
|
||||
@@ -266,8 +280,16 @@ class ApiClient {
|
||||
return AssetMediaSizeTypeTransformer().decode(value);
|
||||
case 'AssetMediaStatus':
|
||||
return AssetMediaStatusTypeTransformer().decode(value);
|
||||
case 'AssetMetadataKey':
|
||||
return AssetMetadataKeyTypeTransformer().decode(value);
|
||||
case 'AssetMetadataBulkDeleteDto':
|
||||
return AssetMetadataBulkDeleteDto.fromJson(value);
|
||||
case 'AssetMetadataBulkDeleteItemDto':
|
||||
return AssetMetadataBulkDeleteItemDto.fromJson(value);
|
||||
case 'AssetMetadataBulkResponseDto':
|
||||
return AssetMetadataBulkResponseDto.fromJson(value);
|
||||
case 'AssetMetadataBulkUpsertDto':
|
||||
return AssetMetadataBulkUpsertDto.fromJson(value);
|
||||
case 'AssetMetadataBulkUpsertItemDto':
|
||||
return AssetMetadataBulkUpsertItemDto.fromJson(value);
|
||||
case 'AssetMetadataResponseDto':
|
||||
return AssetMetadataResponseDto.fromJson(value);
|
||||
case 'AssetMetadataUpsertDto':
|
||||
@@ -324,6 +346,8 @@ class ApiClient {
|
||||
return CreateLibraryDto.fromJson(value);
|
||||
case 'CreateProfileImageResponseDto':
|
||||
return CreateProfileImageResponseDto.fromJson(value);
|
||||
case 'CropParameters':
|
||||
return CropParameters.fromJson(value);
|
||||
case 'DatabaseBackupConfig':
|
||||
return DatabaseBackupConfig.fromJson(value);
|
||||
case 'DownloadArchiveInfo':
|
||||
@@ -412,6 +436,10 @@ class ApiClient {
|
||||
return MergePersonDto.fromJson(value);
|
||||
case 'MetadataSearchDto':
|
||||
return MetadataSearchDto.fromJson(value);
|
||||
case 'MirrorAxis':
|
||||
return MirrorAxisTypeTransformer().decode(value);
|
||||
case 'MirrorParameters':
|
||||
return MirrorParameters.fromJson(value);
|
||||
case 'NotificationCreateDto':
|
||||
return NotificationCreateDto.fromJson(value);
|
||||
case 'NotificationDeleteAllDto':
|
||||
@@ -532,6 +560,8 @@ class ApiClient {
|
||||
return ReactionTypeTypeTransformer().decode(value);
|
||||
case 'ReverseGeocodingStateResponseDto':
|
||||
return ReverseGeocodingStateResponseDto.fromJson(value);
|
||||
case 'RotateParameters':
|
||||
return RotateParameters.fromJson(value);
|
||||
case 'SearchAlbumResponseDto':
|
||||
return SearchAlbumResponseDto.fromJson(value);
|
||||
case 'SearchAssetResponseDto':
|
||||
|
||||
9
mobile/openapi/lib/api_helper.dart
generated
9
mobile/openapi/lib/api_helper.dart
generated
@@ -58,6 +58,9 @@ String parameterToString(dynamic value) {
|
||||
if (value is AlbumUserRole) {
|
||||
return AlbumUserRoleTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AssetEditAction) {
|
||||
return AssetEditActionTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AssetJobName) {
|
||||
return AssetJobNameTypeTransformer().encode(value).toString();
|
||||
}
|
||||
@@ -67,9 +70,6 @@ String parameterToString(dynamic value) {
|
||||
if (value is AssetMediaStatus) {
|
||||
return AssetMediaStatusTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AssetMetadataKey) {
|
||||
return AssetMetadataKeyTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AssetOrder) {
|
||||
return AssetOrderTypeTransformer().encode(value).toString();
|
||||
}
|
||||
@@ -112,6 +112,9 @@ String parameterToString(dynamic value) {
|
||||
if (value is MemoryType) {
|
||||
return MemoryTypeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is MirrorAxis) {
|
||||
return MirrorAxisTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is NotificationLevel) {
|
||||
return NotificationLevelTypeTransformer().encode(value).toString();
|
||||
}
|
||||
|
||||
88
mobile/openapi/lib/model/asset_edit_action.dart
generated
Normal file
88
mobile/openapi/lib/model/asset_edit_action.dart
generated
Normal file
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
|
||||
class AssetEditAction {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetEditAction._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const crop = AssetEditAction._(r'crop');
|
||||
static const rotate = AssetEditAction._(r'rotate');
|
||||
static const mirror = AssetEditAction._(r'mirror');
|
||||
|
||||
/// List of all possible values in this [enum][AssetEditAction].
|
||||
static const values = <AssetEditAction>[
|
||||
crop,
|
||||
rotate,
|
||||
mirror,
|
||||
];
|
||||
|
||||
static AssetEditAction? fromJson(dynamic value) => AssetEditActionTypeTransformer().decode(value);
|
||||
|
||||
static List<AssetEditAction> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetEditAction>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetEditAction.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [AssetEditAction] to String,
|
||||
/// and [decode] dynamic data back to [AssetEditAction].
|
||||
class AssetEditActionTypeTransformer {
|
||||
factory AssetEditActionTypeTransformer() => _instance ??= const AssetEditActionTypeTransformer._();
|
||||
|
||||
const AssetEditActionTypeTransformer._();
|
||||
|
||||
String encode(AssetEditAction data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a AssetEditAction.
|
||||
///
|
||||
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||
///
|
||||
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||
/// and users are still using an old app with the old code.
|
||||
AssetEditAction? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'crop': return AssetEditAction.crop;
|
||||
case r'rotate': return AssetEditAction.rotate;
|
||||
case r'mirror': return AssetEditAction.mirror;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [AssetEditActionTypeTransformer] instance.
|
||||
static AssetEditActionTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
107
mobile/openapi/lib/model/asset_edit_action_crop.dart
generated
Normal file
107
mobile/openapi/lib/model/asset_edit_action_crop.dart
generated
Normal file
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class AssetEditActionCrop {
|
||||
/// Returns a new [AssetEditActionCrop] instance.
|
||||
AssetEditActionCrop({
|
||||
required this.action,
|
||||
required this.parameters,
|
||||
});
|
||||
|
||||
AssetEditAction action;
|
||||
|
||||
CropParameters parameters;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionCrop &&
|
||||
other.action == action &&
|
||||
other.parameters == parameters;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(action.hashCode) +
|
||||
(parameters.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetEditActionCrop[action=$action, parameters=$parameters]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'action'] = this.action;
|
||||
json[r'parameters'] = this.parameters;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetEditActionCrop] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetEditActionCrop? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AssetEditActionCrop");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetEditActionCrop(
|
||||
action: AssetEditAction.fromJson(json[r'action'])!,
|
||||
parameters: CropParameters.fromJson(json[r'parameters'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetEditActionCrop> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetEditActionCrop>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetEditActionCrop.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetEditActionCrop> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetEditActionCrop>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetEditActionCrop.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetEditActionCrop-objects as value to a dart map
|
||||
static Map<String, List<AssetEditActionCrop>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetEditActionCrop>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetEditActionCrop.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'action',
|
||||
'parameters',
|
||||
};
|
||||
}
|
||||
|
||||
100
mobile/openapi/lib/model/asset_edit_action_list_dto.dart
generated
Normal file
100
mobile/openapi/lib/model/asset_edit_action_list_dto.dart
generated
Normal file
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class AssetEditActionListDto {
|
||||
/// Returns a new [AssetEditActionListDto] instance.
|
||||
AssetEditActionListDto({
|
||||
this.edits = const [],
|
||||
});
|
||||
|
||||
/// list of edits
|
||||
List<AssetEditActionListDtoEditsInner> edits;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionListDto &&
|
||||
_deepEquality.equals(other.edits, edits);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(edits.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetEditActionListDto[edits=$edits]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'edits'] = this.edits;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetEditActionListDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetEditActionListDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AssetEditActionListDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetEditActionListDto(
|
||||
edits: AssetEditActionListDtoEditsInner.listFromJson(json[r'edits']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetEditActionListDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetEditActionListDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetEditActionListDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetEditActionListDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetEditActionListDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetEditActionListDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetEditActionListDto-objects as value to a dart map
|
||||
static Map<String, List<AssetEditActionListDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetEditActionListDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetEditActionListDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'edits',
|
||||
};
|
||||
}
|
||||
|
||||
107
mobile/openapi/lib/model/asset_edit_action_list_dto_edits_inner.dart
generated
Normal file
107
mobile/openapi/lib/model/asset_edit_action_list_dto_edits_inner.dart
generated
Normal file
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class AssetEditActionListDtoEditsInner {
|
||||
/// Returns a new [AssetEditActionListDtoEditsInner] instance.
|
||||
AssetEditActionListDtoEditsInner({
|
||||
required this.action,
|
||||
required this.parameters,
|
||||
});
|
||||
|
||||
AssetEditAction action;
|
||||
|
||||
MirrorParameters parameters;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionListDtoEditsInner &&
|
||||
other.action == action &&
|
||||
other.parameters == parameters;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(action.hashCode) +
|
||||
(parameters.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetEditActionListDtoEditsInner[action=$action, parameters=$parameters]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'action'] = this.action;
|
||||
json[r'parameters'] = this.parameters;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetEditActionListDtoEditsInner] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetEditActionListDtoEditsInner? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AssetEditActionListDtoEditsInner");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetEditActionListDtoEditsInner(
|
||||
action: AssetEditAction.fromJson(json[r'action'])!,
|
||||
parameters: MirrorParameters.fromJson(json[r'parameters'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetEditActionListDtoEditsInner> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetEditActionListDtoEditsInner>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetEditActionListDtoEditsInner.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetEditActionListDtoEditsInner> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetEditActionListDtoEditsInner>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetEditActionListDtoEditsInner.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetEditActionListDtoEditsInner-objects as value to a dart map
|
||||
static Map<String, List<AssetEditActionListDtoEditsInner>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetEditActionListDtoEditsInner>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetEditActionListDtoEditsInner.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'action',
|
||||
'parameters',
|
||||
};
|
||||
}
|
||||
|
||||
107
mobile/openapi/lib/model/asset_edit_action_mirror.dart
generated
Normal file
107
mobile/openapi/lib/model/asset_edit_action_mirror.dart
generated
Normal file
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class AssetEditActionMirror {
|
||||
/// Returns a new [AssetEditActionMirror] instance.
|
||||
AssetEditActionMirror({
|
||||
required this.action,
|
||||
required this.parameters,
|
||||
});
|
||||
|
||||
AssetEditAction action;
|
||||
|
||||
MirrorParameters parameters;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionMirror &&
|
||||
other.action == action &&
|
||||
other.parameters == parameters;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(action.hashCode) +
|
||||
(parameters.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetEditActionMirror[action=$action, parameters=$parameters]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'action'] = this.action;
|
||||
json[r'parameters'] = this.parameters;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetEditActionMirror] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetEditActionMirror? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AssetEditActionMirror");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetEditActionMirror(
|
||||
action: AssetEditAction.fromJson(json[r'action'])!,
|
||||
parameters: MirrorParameters.fromJson(json[r'parameters'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetEditActionMirror> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetEditActionMirror>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetEditActionMirror.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetEditActionMirror> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetEditActionMirror>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetEditActionMirror.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetEditActionMirror-objects as value to a dart map
|
||||
static Map<String, List<AssetEditActionMirror>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetEditActionMirror>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetEditActionMirror.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'action',
|
||||
'parameters',
|
||||
};
|
||||
}
|
||||
|
||||
107
mobile/openapi/lib/model/asset_edit_action_rotate.dart
generated
Normal file
107
mobile/openapi/lib/model/asset_edit_action_rotate.dart
generated
Normal file
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class AssetEditActionRotate {
|
||||
/// Returns a new [AssetEditActionRotate] instance.
|
||||
AssetEditActionRotate({
|
||||
required this.action,
|
||||
required this.parameters,
|
||||
});
|
||||
|
||||
AssetEditAction action;
|
||||
|
||||
RotateParameters parameters;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionRotate &&
|
||||
other.action == action &&
|
||||
other.parameters == parameters;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(action.hashCode) +
|
||||
(parameters.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetEditActionRotate[action=$action, parameters=$parameters]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'action'] = this.action;
|
||||
json[r'parameters'] = this.parameters;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetEditActionRotate] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetEditActionRotate? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AssetEditActionRotate");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetEditActionRotate(
|
||||
action: AssetEditAction.fromJson(json[r'action'])!,
|
||||
parameters: RotateParameters.fromJson(json[r'parameters'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetEditActionRotate> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetEditActionRotate>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetEditActionRotate.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetEditActionRotate> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetEditActionRotate>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetEditActionRotate.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetEditActionRotate-objects as value to a dart map
|
||||
static Map<String, List<AssetEditActionRotate>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetEditActionRotate>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetEditActionRotate.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'action',
|
||||
'parameters',
|
||||
};
|
||||
}
|
||||
|
||||
108
mobile/openapi/lib/model/asset_edits_dto.dart
generated
Normal file
108
mobile/openapi/lib/model/asset_edits_dto.dart
generated
Normal file
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class AssetEditsDto {
|
||||
/// Returns a new [AssetEditsDto] instance.
|
||||
AssetEditsDto({
|
||||
required this.assetId,
|
||||
this.edits = const [],
|
||||
});
|
||||
|
||||
String assetId;
|
||||
|
||||
/// list of edits
|
||||
List<AssetEditActionListDtoEditsInner> edits;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetEditsDto &&
|
||||
other.assetId == assetId &&
|
||||
_deepEquality.equals(other.edits, edits);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(assetId.hashCode) +
|
||||
(edits.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetEditsDto[assetId=$assetId, edits=$edits]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'assetId'] = this.assetId;
|
||||
json[r'edits'] = this.edits;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetEditsDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetEditsDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AssetEditsDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetEditsDto(
|
||||
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||
edits: AssetEditActionListDtoEditsInner.listFromJson(json[r'edits']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetEditsDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetEditsDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetEditsDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetEditsDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetEditsDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetEditsDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetEditsDto-objects as value to a dart map
|
||||
static Map<String, List<AssetEditsDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetEditsDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetEditsDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'assetId',
|
||||
'edits',
|
||||
};
|
||||
}
|
||||
|
||||
99
mobile/openapi/lib/model/asset_metadata_bulk_delete_dto.dart
generated
Normal file
99
mobile/openapi/lib/model/asset_metadata_bulk_delete_dto.dart
generated
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class AssetMetadataBulkDeleteDto {
|
||||
/// Returns a new [AssetMetadataBulkDeleteDto] instance.
|
||||
AssetMetadataBulkDeleteDto({
|
||||
this.items = const [],
|
||||
});
|
||||
|
||||
List<AssetMetadataBulkDeleteItemDto> items;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetMetadataBulkDeleteDto &&
|
||||
_deepEquality.equals(other.items, items);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(items.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetMetadataBulkDeleteDto[items=$items]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'items'] = this.items;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetMetadataBulkDeleteDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetMetadataBulkDeleteDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AssetMetadataBulkDeleteDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetMetadataBulkDeleteDto(
|
||||
items: AssetMetadataBulkDeleteItemDto.listFromJson(json[r'items']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetMetadataBulkDeleteDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetMetadataBulkDeleteDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetMetadataBulkDeleteDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetMetadataBulkDeleteDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetMetadataBulkDeleteDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetMetadataBulkDeleteDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetMetadataBulkDeleteDto-objects as value to a dart map
|
||||
static Map<String, List<AssetMetadataBulkDeleteDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetMetadataBulkDeleteDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetMetadataBulkDeleteDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'items',
|
||||
};
|
||||
}
|
||||
|
||||
107
mobile/openapi/lib/model/asset_metadata_bulk_delete_item_dto.dart
generated
Normal file
107
mobile/openapi/lib/model/asset_metadata_bulk_delete_item_dto.dart
generated
Normal file
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class AssetMetadataBulkDeleteItemDto {
|
||||
/// Returns a new [AssetMetadataBulkDeleteItemDto] instance.
|
||||
AssetMetadataBulkDeleteItemDto({
|
||||
required this.assetId,
|
||||
required this.key,
|
||||
});
|
||||
|
||||
String assetId;
|
||||
|
||||
String key;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetMetadataBulkDeleteItemDto &&
|
||||
other.assetId == assetId &&
|
||||
other.key == key;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(assetId.hashCode) +
|
||||
(key.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetMetadataBulkDeleteItemDto[assetId=$assetId, key=$key]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'assetId'] = this.assetId;
|
||||
json[r'key'] = this.key;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetMetadataBulkDeleteItemDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetMetadataBulkDeleteItemDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AssetMetadataBulkDeleteItemDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetMetadataBulkDeleteItemDto(
|
||||
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||
key: mapValueOfType<String>(json, r'key')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetMetadataBulkDeleteItemDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetMetadataBulkDeleteItemDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetMetadataBulkDeleteItemDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetMetadataBulkDeleteItemDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetMetadataBulkDeleteItemDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetMetadataBulkDeleteItemDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetMetadataBulkDeleteItemDto-objects as value to a dart map
|
||||
static Map<String, List<AssetMetadataBulkDeleteItemDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetMetadataBulkDeleteItemDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetMetadataBulkDeleteItemDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'assetId',
|
||||
'key',
|
||||
};
|
||||
}
|
||||
|
||||
123
mobile/openapi/lib/model/asset_metadata_bulk_response_dto.dart
generated
Normal file
123
mobile/openapi/lib/model/asset_metadata_bulk_response_dto.dart
generated
Normal file
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class AssetMetadataBulkResponseDto {
|
||||
/// Returns a new [AssetMetadataBulkResponseDto] instance.
|
||||
AssetMetadataBulkResponseDto({
|
||||
required this.assetId,
|
||||
required this.key,
|
||||
required this.updatedAt,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
String assetId;
|
||||
|
||||
String key;
|
||||
|
||||
DateTime updatedAt;
|
||||
|
||||
Object value;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetMetadataBulkResponseDto &&
|
||||
other.assetId == assetId &&
|
||||
other.key == key &&
|
||||
other.updatedAt == updatedAt &&
|
||||
other.value == value;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(assetId.hashCode) +
|
||||
(key.hashCode) +
|
||||
(updatedAt.hashCode) +
|
||||
(value.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetMetadataBulkResponseDto[assetId=$assetId, key=$key, updatedAt=$updatedAt, value=$value]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'assetId'] = this.assetId;
|
||||
json[r'key'] = this.key;
|
||||
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||
json[r'value'] = this.value;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetMetadataBulkResponseDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetMetadataBulkResponseDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AssetMetadataBulkResponseDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetMetadataBulkResponseDto(
|
||||
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||
key: mapValueOfType<String>(json, r'key')!,
|
||||
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||
value: mapValueOfType<Object>(json, r'value')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetMetadataBulkResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetMetadataBulkResponseDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetMetadataBulkResponseDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetMetadataBulkResponseDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetMetadataBulkResponseDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetMetadataBulkResponseDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetMetadataBulkResponseDto-objects as value to a dart map
|
||||
static Map<String, List<AssetMetadataBulkResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetMetadataBulkResponseDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetMetadataBulkResponseDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'assetId',
|
||||
'key',
|
||||
'updatedAt',
|
||||
'value',
|
||||
};
|
||||
}
|
||||
|
||||
99
mobile/openapi/lib/model/asset_metadata_bulk_upsert_dto.dart
generated
Normal file
99
mobile/openapi/lib/model/asset_metadata_bulk_upsert_dto.dart
generated
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class AssetMetadataBulkUpsertDto {
|
||||
/// Returns a new [AssetMetadataBulkUpsertDto] instance.
|
||||
AssetMetadataBulkUpsertDto({
|
||||
this.items = const [],
|
||||
});
|
||||
|
||||
List<AssetMetadataBulkUpsertItemDto> items;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetMetadataBulkUpsertDto &&
|
||||
_deepEquality.equals(other.items, items);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(items.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetMetadataBulkUpsertDto[items=$items]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'items'] = this.items;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetMetadataBulkUpsertDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetMetadataBulkUpsertDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AssetMetadataBulkUpsertDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetMetadataBulkUpsertDto(
|
||||
items: AssetMetadataBulkUpsertItemDto.listFromJson(json[r'items']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetMetadataBulkUpsertDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetMetadataBulkUpsertDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetMetadataBulkUpsertDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetMetadataBulkUpsertDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetMetadataBulkUpsertDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetMetadataBulkUpsertDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetMetadataBulkUpsertDto-objects as value to a dart map
|
||||
static Map<String, List<AssetMetadataBulkUpsertDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetMetadataBulkUpsertDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetMetadataBulkUpsertDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'items',
|
||||
};
|
||||
}
|
||||
|
||||
115
mobile/openapi/lib/model/asset_metadata_bulk_upsert_item_dto.dart
generated
Normal file
115
mobile/openapi/lib/model/asset_metadata_bulk_upsert_item_dto.dart
generated
Normal file
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class AssetMetadataBulkUpsertItemDto {
|
||||
/// Returns a new [AssetMetadataBulkUpsertItemDto] instance.
|
||||
AssetMetadataBulkUpsertItemDto({
|
||||
required this.assetId,
|
||||
required this.key,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
String assetId;
|
||||
|
||||
String key;
|
||||
|
||||
Object value;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetMetadataBulkUpsertItemDto &&
|
||||
other.assetId == assetId &&
|
||||
other.key == key &&
|
||||
other.value == value;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(assetId.hashCode) +
|
||||
(key.hashCode) +
|
||||
(value.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetMetadataBulkUpsertItemDto[assetId=$assetId, key=$key, value=$value]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'assetId'] = this.assetId;
|
||||
json[r'key'] = this.key;
|
||||
json[r'value'] = this.value;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AssetMetadataBulkUpsertItemDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AssetMetadataBulkUpsertItemDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AssetMetadataBulkUpsertItemDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetMetadataBulkUpsertItemDto(
|
||||
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||
key: mapValueOfType<String>(json, r'key')!,
|
||||
value: mapValueOfType<Object>(json, r'value')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AssetMetadataBulkUpsertItemDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetMetadataBulkUpsertItemDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetMetadataBulkUpsertItemDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AssetMetadataBulkUpsertItemDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetMetadataBulkUpsertItemDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AssetMetadataBulkUpsertItemDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AssetMetadataBulkUpsertItemDto-objects as value to a dart map
|
||||
static Map<String, List<AssetMetadataBulkUpsertItemDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetMetadataBulkUpsertItemDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AssetMetadataBulkUpsertItemDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'assetId',
|
||||
'key',
|
||||
'value',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class AssetMetadataResponseDto {
|
||||
required this.value,
|
||||
});
|
||||
|
||||
AssetMetadataKey key;
|
||||
String key;
|
||||
|
||||
DateTime updatedAt;
|
||||
|
||||
@@ -57,7 +57,7 @@ class AssetMetadataResponseDto {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetMetadataResponseDto(
|
||||
key: AssetMetadataKey.fromJson(json[r'key'])!,
|
||||
key: mapValueOfType<String>(json, r'key')!,
|
||||
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||
value: mapValueOfType<Object>(json, r'value')!,
|
||||
);
|
||||
|
||||
@@ -17,7 +17,7 @@ class AssetMetadataUpsertItemDto {
|
||||
required this.value,
|
||||
});
|
||||
|
||||
AssetMetadataKey key;
|
||||
String key;
|
||||
|
||||
Object value;
|
||||
|
||||
@@ -51,7 +51,7 @@ class AssetMetadataUpsertItemDto {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetMetadataUpsertItemDto(
|
||||
key: AssetMetadataKey.fromJson(json[r'key'])!,
|
||||
key: mapValueOfType<String>(json, r'key')!,
|
||||
value: mapValueOfType<Object>(json, r'value')!,
|
||||
);
|
||||
}
|
||||
|
||||
34
mobile/openapi/lib/model/asset_response_dto.dart
generated
34
mobile/openapi/lib/model/asset_response_dto.dart
generated
@@ -23,6 +23,7 @@ class AssetResponseDto {
|
||||
required this.fileCreatedAt,
|
||||
required this.fileModifiedAt,
|
||||
required this.hasMetadata,
|
||||
required this.height,
|
||||
required this.id,
|
||||
required this.isArchived,
|
||||
required this.isFavorite,
|
||||
@@ -45,6 +46,7 @@ class AssetResponseDto {
|
||||
this.unassignedFaces = const [],
|
||||
required this.updatedAt,
|
||||
required this.visibility,
|
||||
required this.width,
|
||||
});
|
||||
|
||||
/// base64 encoded sha1 hash
|
||||
@@ -77,6 +79,8 @@ class AssetResponseDto {
|
||||
|
||||
bool hasMetadata;
|
||||
|
||||
num? height;
|
||||
|
||||
String id;
|
||||
|
||||
bool isArchived;
|
||||
@@ -141,6 +145,8 @@ class AssetResponseDto {
|
||||
|
||||
AssetVisibility visibility;
|
||||
|
||||
num? width;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto &&
|
||||
other.checksum == checksum &&
|
||||
@@ -153,6 +159,7 @@ class AssetResponseDto {
|
||||
other.fileCreatedAt == fileCreatedAt &&
|
||||
other.fileModifiedAt == fileModifiedAt &&
|
||||
other.hasMetadata == hasMetadata &&
|
||||
other.height == height &&
|
||||
other.id == id &&
|
||||
other.isArchived == isArchived &&
|
||||
other.isFavorite == isFavorite &&
|
||||
@@ -174,7 +181,8 @@ class AssetResponseDto {
|
||||
other.type == type &&
|
||||
_deepEquality.equals(other.unassignedFaces, unassignedFaces) &&
|
||||
other.updatedAt == updatedAt &&
|
||||
other.visibility == visibility;
|
||||
other.visibility == visibility &&
|
||||
other.width == width;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
@@ -189,6 +197,7 @@ class AssetResponseDto {
|
||||
(fileCreatedAt.hashCode) +
|
||||
(fileModifiedAt.hashCode) +
|
||||
(hasMetadata.hashCode) +
|
||||
(height == null ? 0 : height!.hashCode) +
|
||||
(id.hashCode) +
|
||||
(isArchived.hashCode) +
|
||||
(isFavorite.hashCode) +
|
||||
@@ -210,10 +219,11 @@ class AssetResponseDto {
|
||||
(type.hashCode) +
|
||||
(unassignedFaces.hashCode) +
|
||||
(updatedAt.hashCode) +
|
||||
(visibility.hashCode);
|
||||
(visibility.hashCode) +
|
||||
(width == null ? 0 : width!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetResponseDto[checksum=$checksum, createdAt=$createdAt, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalMimeType=$originalMimeType, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, stack=$stack, tags=$tags, thumbhash=$thumbhash, type=$type, unassignedFaces=$unassignedFaces, updatedAt=$updatedAt, visibility=$visibility]';
|
||||
String toString() => 'AssetResponseDto[checksum=$checksum, createdAt=$createdAt, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, height=$height, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalMimeType=$originalMimeType, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, stack=$stack, tags=$tags, thumbhash=$thumbhash, type=$type, unassignedFaces=$unassignedFaces, updatedAt=$updatedAt, visibility=$visibility, width=$width]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -235,6 +245,11 @@ class AssetResponseDto {
|
||||
json[r'fileCreatedAt'] = this.fileCreatedAt.toUtc().toIso8601String();
|
||||
json[r'fileModifiedAt'] = this.fileModifiedAt.toUtc().toIso8601String();
|
||||
json[r'hasMetadata'] = this.hasMetadata;
|
||||
if (this.height != null) {
|
||||
json[r'height'] = this.height;
|
||||
} else {
|
||||
// json[r'height'] = null;
|
||||
}
|
||||
json[r'id'] = this.id;
|
||||
json[r'isArchived'] = this.isArchived;
|
||||
json[r'isFavorite'] = this.isFavorite;
|
||||
@@ -285,6 +300,11 @@ class AssetResponseDto {
|
||||
json[r'unassignedFaces'] = this.unassignedFaces;
|
||||
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||
json[r'visibility'] = this.visibility;
|
||||
if (this.width != null) {
|
||||
json[r'width'] = this.width;
|
||||
} else {
|
||||
// json[r'width'] = null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -307,6 +327,9 @@ class AssetResponseDto {
|
||||
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'')!,
|
||||
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'')!,
|
||||
hasMetadata: mapValueOfType<bool>(json, r'hasMetadata')!,
|
||||
height: json[r'height'] == null
|
||||
? null
|
||||
: num.parse('${json[r'height']}'),
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
isArchived: mapValueOfType<bool>(json, r'isArchived')!,
|
||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
|
||||
@@ -329,6 +352,9 @@ class AssetResponseDto {
|
||||
unassignedFaces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'unassignedFaces']),
|
||||
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||
visibility: AssetVisibility.fromJson(json[r'visibility'])!,
|
||||
width: json[r'width'] == null
|
||||
? null
|
||||
: num.parse('${json[r'width']}'),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@@ -384,6 +410,7 @@ class AssetResponseDto {
|
||||
'fileCreatedAt',
|
||||
'fileModifiedAt',
|
||||
'hasMetadata',
|
||||
'height',
|
||||
'id',
|
||||
'isArchived',
|
||||
'isFavorite',
|
||||
@@ -397,6 +424,7 @@ class AssetResponseDto {
|
||||
'type',
|
||||
'updatedAt',
|
||||
'visibility',
|
||||
'width',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
135
mobile/openapi/lib/model/crop_parameters.dart
generated
Normal file
135
mobile/openapi/lib/model/crop_parameters.dart
generated
Normal file
@@ -0,0 +1,135 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class CropParameters {
|
||||
/// Returns a new [CropParameters] instance.
|
||||
CropParameters({
|
||||
required this.height,
|
||||
required this.width,
|
||||
required this.x,
|
||||
required this.y,
|
||||
});
|
||||
|
||||
/// Height of the crop
|
||||
///
|
||||
/// Minimum value: 1
|
||||
num height;
|
||||
|
||||
/// Width of the crop
|
||||
///
|
||||
/// Minimum value: 1
|
||||
num width;
|
||||
|
||||
/// Top-Left X coordinate of crop
|
||||
///
|
||||
/// Minimum value: 0
|
||||
num x;
|
||||
|
||||
/// Top-Left Y coordinate of crop
|
||||
///
|
||||
/// Minimum value: 0
|
||||
num y;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is CropParameters &&
|
||||
other.height == height &&
|
||||
other.width == width &&
|
||||
other.x == x &&
|
||||
other.y == y;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(height.hashCode) +
|
||||
(width.hashCode) +
|
||||
(x.hashCode) +
|
||||
(y.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'CropParameters[height=$height, width=$width, x=$x, y=$y]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'height'] = this.height;
|
||||
json[r'width'] = this.width;
|
||||
json[r'x'] = this.x;
|
||||
json[r'y'] = this.y;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [CropParameters] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static CropParameters? fromJson(dynamic value) {
|
||||
upgradeDto(value, "CropParameters");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return CropParameters(
|
||||
height: num.parse('${json[r'height']}'),
|
||||
width: num.parse('${json[r'width']}'),
|
||||
x: num.parse('${json[r'x']}'),
|
||||
y: num.parse('${json[r'y']}'),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<CropParameters> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <CropParameters>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = CropParameters.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, CropParameters> mapFromJson(dynamic json) {
|
||||
final map = <String, CropParameters>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = CropParameters.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of CropParameters-objects as value to a dart map
|
||||
static Map<String, List<CropParameters>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<CropParameters>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = CropParameters.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'height',
|
||||
'width',
|
||||
'x',
|
||||
'y',
|
||||
};
|
||||
}
|
||||
|
||||
3
mobile/openapi/lib/model/job_name.dart
generated
3
mobile/openapi/lib/model/job_name.dart
generated
@@ -29,6 +29,7 @@ class JobName {
|
||||
static const assetDetectFaces = JobName._(r'AssetDetectFaces');
|
||||
static const assetDetectDuplicatesQueueAll = JobName._(r'AssetDetectDuplicatesQueueAll');
|
||||
static const assetDetectDuplicates = JobName._(r'AssetDetectDuplicates');
|
||||
static const assetEditThumbnailGeneration = JobName._(r'AssetEditThumbnailGeneration');
|
||||
static const assetEncodeVideoQueueAll = JobName._(r'AssetEncodeVideoQueueAll');
|
||||
static const assetEncodeVideo = JobName._(r'AssetEncodeVideo');
|
||||
static const assetEmptyTrash = JobName._(r'AssetEmptyTrash');
|
||||
@@ -87,6 +88,7 @@ class JobName {
|
||||
assetDetectFaces,
|
||||
assetDetectDuplicatesQueueAll,
|
||||
assetDetectDuplicates,
|
||||
assetEditThumbnailGeneration,
|
||||
assetEncodeVideoQueueAll,
|
||||
assetEncodeVideo,
|
||||
assetEmptyTrash,
|
||||
@@ -180,6 +182,7 @@ class JobNameTypeTransformer {
|
||||
case r'AssetDetectFaces': return JobName.assetDetectFaces;
|
||||
case r'AssetDetectDuplicatesQueueAll': return JobName.assetDetectDuplicatesQueueAll;
|
||||
case r'AssetDetectDuplicates': return JobName.assetDetectDuplicates;
|
||||
case r'AssetEditThumbnailGeneration': return JobName.assetEditThumbnailGeneration;
|
||||
case r'AssetEncodeVideoQueueAll': return JobName.assetEncodeVideoQueueAll;
|
||||
case r'AssetEncodeVideo': return JobName.assetEncodeVideo;
|
||||
case r'AssetEmptyTrash': return JobName.assetEmptyTrash;
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
|
||||
class AssetMetadataKey {
|
||||
/// Axis to mirror along
|
||||
class MirrorAxis {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetMetadataKey._(this.value);
|
||||
const MirrorAxis._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
@@ -23,20 +23,22 @@ class AssetMetadataKey {
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const mobileApp = AssetMetadataKey._(r'mobile-app');
|
||||
static const horizontal = MirrorAxis._(r'horizontal');
|
||||
static const vertical = MirrorAxis._(r'vertical');
|
||||
|
||||
/// List of all possible values in this [enum][AssetMetadataKey].
|
||||
static const values = <AssetMetadataKey>[
|
||||
mobileApp,
|
||||
/// List of all possible values in this [enum][MirrorAxis].
|
||||
static const values = <MirrorAxis>[
|
||||
horizontal,
|
||||
vertical,
|
||||
];
|
||||
|
||||
static AssetMetadataKey? fromJson(dynamic value) => AssetMetadataKeyTypeTransformer().decode(value);
|
||||
static MirrorAxis? fromJson(dynamic value) => MirrorAxisTypeTransformer().decode(value);
|
||||
|
||||
static List<AssetMetadataKey> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetMetadataKey>[];
|
||||
static List<MirrorAxis> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <MirrorAxis>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetMetadataKey.fromJson(row);
|
||||
final value = MirrorAxis.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
@@ -46,16 +48,16 @@ class AssetMetadataKey {
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [AssetMetadataKey] to String,
|
||||
/// and [decode] dynamic data back to [AssetMetadataKey].
|
||||
class AssetMetadataKeyTypeTransformer {
|
||||
factory AssetMetadataKeyTypeTransformer() => _instance ??= const AssetMetadataKeyTypeTransformer._();
|
||||
/// Transformation class that can [encode] an instance of [MirrorAxis] to String,
|
||||
/// and [decode] dynamic data back to [MirrorAxis].
|
||||
class MirrorAxisTypeTransformer {
|
||||
factory MirrorAxisTypeTransformer() => _instance ??= const MirrorAxisTypeTransformer._();
|
||||
|
||||
const AssetMetadataKeyTypeTransformer._();
|
||||
const MirrorAxisTypeTransformer._();
|
||||
|
||||
String encode(AssetMetadataKey data) => data.value;
|
||||
String encode(MirrorAxis data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a AssetMetadataKey.
|
||||
/// Decodes a [dynamic value][data] to a MirrorAxis.
|
||||
///
|
||||
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||
@@ -63,10 +65,11 @@ class AssetMetadataKeyTypeTransformer {
|
||||
///
|
||||
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||
/// and users are still using an old app with the old code.
|
||||
AssetMetadataKey? decode(dynamic data, {bool allowNull = true}) {
|
||||
MirrorAxis? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'mobile-app': return AssetMetadataKey.mobileApp;
|
||||
case r'horizontal': return MirrorAxis.horizontal;
|
||||
case r'vertical': return MirrorAxis.vertical;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
@@ -76,7 +79,7 @@ class AssetMetadataKeyTypeTransformer {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [AssetMetadataKeyTypeTransformer] instance.
|
||||
static AssetMetadataKeyTypeTransformer? _instance;
|
||||
/// Singleton [MirrorAxisTypeTransformer] instance.
|
||||
static MirrorAxisTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
100
mobile/openapi/lib/model/mirror_parameters.dart
generated
Normal file
100
mobile/openapi/lib/model/mirror_parameters.dart
generated
Normal file
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class MirrorParameters {
|
||||
/// Returns a new [MirrorParameters] instance.
|
||||
MirrorParameters({
|
||||
required this.axis,
|
||||
});
|
||||
|
||||
/// Axis to mirror along
|
||||
MirrorAxis axis;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is MirrorParameters &&
|
||||
other.axis == axis;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(axis.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'MirrorParameters[axis=$axis]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'axis'] = this.axis;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [MirrorParameters] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static MirrorParameters? fromJson(dynamic value) {
|
||||
upgradeDto(value, "MirrorParameters");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return MirrorParameters(
|
||||
axis: MirrorAxis.fromJson(json[r'axis'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<MirrorParameters> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <MirrorParameters>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = MirrorParameters.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, MirrorParameters> mapFromJson(dynamic json) {
|
||||
final map = <String, MirrorParameters>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = MirrorParameters.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of MirrorParameters-objects as value to a dart map
|
||||
static Map<String, List<MirrorParameters>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<MirrorParameters>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = MirrorParameters.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'axis',
|
||||
};
|
||||
}
|
||||
|
||||
12
mobile/openapi/lib/model/permission.dart
generated
12
mobile/openapi/lib/model/permission.dart
generated
@@ -43,6 +43,10 @@ class Permission {
|
||||
static const assetPeriodUpload = Permission._(r'asset.upload');
|
||||
static const assetPeriodReplace = Permission._(r'asset.replace');
|
||||
static const assetPeriodCopy = Permission._(r'asset.copy');
|
||||
static const assetPeriodDerive = Permission._(r'asset.derive');
|
||||
static const assetPeriodEditPeriodGet = Permission._(r'asset.edit.get');
|
||||
static const assetPeriodEditPeriodCreate = Permission._(r'asset.edit.create');
|
||||
static const assetPeriodEditPeriodDelete = Permission._(r'asset.edit.delete');
|
||||
static const albumPeriodCreate = Permission._(r'album.create');
|
||||
static const albumPeriodRead = Permission._(r'album.read');
|
||||
static const albumPeriodUpdate = Permission._(r'album.update');
|
||||
@@ -191,6 +195,10 @@ class Permission {
|
||||
assetPeriodUpload,
|
||||
assetPeriodReplace,
|
||||
assetPeriodCopy,
|
||||
assetPeriodDerive,
|
||||
assetPeriodEditPeriodGet,
|
||||
assetPeriodEditPeriodCreate,
|
||||
assetPeriodEditPeriodDelete,
|
||||
albumPeriodCreate,
|
||||
albumPeriodRead,
|
||||
albumPeriodUpdate,
|
||||
@@ -374,6 +382,10 @@ class PermissionTypeTransformer {
|
||||
case r'asset.upload': return Permission.assetPeriodUpload;
|
||||
case r'asset.replace': return Permission.assetPeriodReplace;
|
||||
case r'asset.copy': return Permission.assetPeriodCopy;
|
||||
case r'asset.derive': return Permission.assetPeriodDerive;
|
||||
case r'asset.edit.get': return Permission.assetPeriodEditPeriodGet;
|
||||
case r'asset.edit.create': return Permission.assetPeriodEditPeriodCreate;
|
||||
case r'asset.edit.delete': return Permission.assetPeriodEditPeriodDelete;
|
||||
case r'album.create': return Permission.albumPeriodCreate;
|
||||
case r'album.read': return Permission.albumPeriodRead;
|
||||
case r'album.update': return Permission.albumPeriodUpdate;
|
||||
|
||||
3
mobile/openapi/lib/model/queue_name.dart
generated
3
mobile/openapi/lib/model/queue_name.dart
generated
@@ -40,6 +40,7 @@ class QueueName {
|
||||
static const backupDatabase = QueueName._(r'backupDatabase');
|
||||
static const ocr = QueueName._(r'ocr');
|
||||
static const workflow = QueueName._(r'workflow');
|
||||
static const editor = QueueName._(r'editor');
|
||||
|
||||
/// List of all possible values in this [enum][QueueName].
|
||||
static const values = <QueueName>[
|
||||
@@ -60,6 +61,7 @@ class QueueName {
|
||||
backupDatabase,
|
||||
ocr,
|
||||
workflow,
|
||||
editor,
|
||||
];
|
||||
|
||||
static QueueName? fromJson(dynamic value) => QueueNameTypeTransformer().decode(value);
|
||||
@@ -115,6 +117,7 @@ class QueueNameTypeTransformer {
|
||||
case r'backupDatabase': return QueueName.backupDatabase;
|
||||
case r'ocr': return QueueName.ocr;
|
||||
case r'workflow': return QueueName.workflow;
|
||||
case r'editor': return QueueName.editor;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
|
||||
@@ -16,6 +16,7 @@ class QueuesResponseLegacyDto {
|
||||
required this.backgroundTask,
|
||||
required this.backupDatabase,
|
||||
required this.duplicateDetection,
|
||||
required this.editor,
|
||||
required this.faceDetection,
|
||||
required this.facialRecognition,
|
||||
required this.library_,
|
||||
@@ -38,6 +39,8 @@ class QueuesResponseLegacyDto {
|
||||
|
||||
QueueResponseLegacyDto duplicateDetection;
|
||||
|
||||
QueueResponseLegacyDto editor;
|
||||
|
||||
QueueResponseLegacyDto faceDetection;
|
||||
|
||||
QueueResponseLegacyDto facialRecognition;
|
||||
@@ -71,6 +74,7 @@ class QueuesResponseLegacyDto {
|
||||
other.backgroundTask == backgroundTask &&
|
||||
other.backupDatabase == backupDatabase &&
|
||||
other.duplicateDetection == duplicateDetection &&
|
||||
other.editor == editor &&
|
||||
other.faceDetection == faceDetection &&
|
||||
other.facialRecognition == facialRecognition &&
|
||||
other.library_ == library_ &&
|
||||
@@ -92,6 +96,7 @@ class QueuesResponseLegacyDto {
|
||||
(backgroundTask.hashCode) +
|
||||
(backupDatabase.hashCode) +
|
||||
(duplicateDetection.hashCode) +
|
||||
(editor.hashCode) +
|
||||
(faceDetection.hashCode) +
|
||||
(facialRecognition.hashCode) +
|
||||
(library_.hashCode) +
|
||||
@@ -108,13 +113,14 @@ class QueuesResponseLegacyDto {
|
||||
(workflow.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'QueuesResponseLegacyDto[backgroundTask=$backgroundTask, backupDatabase=$backupDatabase, duplicateDetection=$duplicateDetection, faceDetection=$faceDetection, facialRecognition=$facialRecognition, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]';
|
||||
String toString() => 'QueuesResponseLegacyDto[backgroundTask=$backgroundTask, backupDatabase=$backupDatabase, duplicateDetection=$duplicateDetection, editor=$editor, faceDetection=$faceDetection, facialRecognition=$facialRecognition, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'backgroundTask'] = this.backgroundTask;
|
||||
json[r'backupDatabase'] = this.backupDatabase;
|
||||
json[r'duplicateDetection'] = this.duplicateDetection;
|
||||
json[r'editor'] = this.editor;
|
||||
json[r'faceDetection'] = this.faceDetection;
|
||||
json[r'facialRecognition'] = this.facialRecognition;
|
||||
json[r'library'] = this.library_;
|
||||
@@ -144,6 +150,7 @@ class QueuesResponseLegacyDto {
|
||||
backgroundTask: QueueResponseLegacyDto.fromJson(json[r'backgroundTask'])!,
|
||||
backupDatabase: QueueResponseLegacyDto.fromJson(json[r'backupDatabase'])!,
|
||||
duplicateDetection: QueueResponseLegacyDto.fromJson(json[r'duplicateDetection'])!,
|
||||
editor: QueueResponseLegacyDto.fromJson(json[r'editor'])!,
|
||||
faceDetection: QueueResponseLegacyDto.fromJson(json[r'faceDetection'])!,
|
||||
facialRecognition: QueueResponseLegacyDto.fromJson(json[r'facialRecognition'])!,
|
||||
library_: QueueResponseLegacyDto.fromJson(json[r'library'])!,
|
||||
@@ -208,6 +215,7 @@ class QueuesResponseLegacyDto {
|
||||
'backgroundTask',
|
||||
'backupDatabase',
|
||||
'duplicateDetection',
|
||||
'editor',
|
||||
'faceDetection',
|
||||
'facialRecognition',
|
||||
'library',
|
||||
|
||||
100
mobile/openapi/lib/model/rotate_parameters.dart
generated
Normal file
100
mobile/openapi/lib/model/rotate_parameters.dart
generated
Normal file
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class RotateParameters {
|
||||
/// Returns a new [RotateParameters] instance.
|
||||
RotateParameters({
|
||||
required this.angle,
|
||||
});
|
||||
|
||||
/// Rotation angle in degrees
|
||||
num angle;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is RotateParameters &&
|
||||
other.angle == angle;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(angle.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'RotateParameters[angle=$angle]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'angle'] = this.angle;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [RotateParameters] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static RotateParameters? fromJson(dynamic value) {
|
||||
upgradeDto(value, "RotateParameters");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return RotateParameters(
|
||||
angle: num.parse('${json[r'angle']}'),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<RotateParameters> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <RotateParameters>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = RotateParameters.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, RotateParameters> mapFromJson(dynamic json) {
|
||||
final map = <String, RotateParameters>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = RotateParameters.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of RotateParameters-objects as value to a dart map
|
||||
static Map<String, List<RotateParameters>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<RotateParameters>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = RotateParameters.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'angle',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class SyncAssetMetadataDeleteV1 {
|
||||
|
||||
String assetId;
|
||||
|
||||
AssetMetadataKey key;
|
||||
String key;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SyncAssetMetadataDeleteV1 &&
|
||||
@@ -52,7 +52,7 @@ class SyncAssetMetadataDeleteV1 {
|
||||
|
||||
return SyncAssetMetadataDeleteV1(
|
||||
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||
key: AssetMetadataKey.fromJson(json[r'key'])!,
|
||||
key: mapValueOfType<String>(json, r'key')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -20,7 +20,7 @@ class SyncAssetMetadataV1 {
|
||||
|
||||
String assetId;
|
||||
|
||||
AssetMetadataKey key;
|
||||
String key;
|
||||
|
||||
Object value;
|
||||
|
||||
@@ -58,7 +58,7 @@ class SyncAssetMetadataV1 {
|
||||
|
||||
return SyncAssetMetadataV1(
|
||||
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||
key: AssetMetadataKey.fromJson(json[r'key'])!,
|
||||
key: mapValueOfType<String>(json, r'key')!,
|
||||
value: mapValueOfType<Object>(json, r'value')!,
|
||||
);
|
||||
}
|
||||
|
||||
30
mobile/openapi/lib/model/sync_asset_v1.dart
generated
30
mobile/openapi/lib/model/sync_asset_v1.dart
generated
@@ -18,6 +18,7 @@ class SyncAssetV1 {
|
||||
required this.duration,
|
||||
required this.fileCreatedAt,
|
||||
required this.fileModifiedAt,
|
||||
required this.height,
|
||||
required this.id,
|
||||
required this.isFavorite,
|
||||
required this.libraryId,
|
||||
@@ -29,6 +30,7 @@ class SyncAssetV1 {
|
||||
required this.thumbhash,
|
||||
required this.type,
|
||||
required this.visibility,
|
||||
required this.width,
|
||||
});
|
||||
|
||||
String checksum;
|
||||
@@ -41,6 +43,8 @@ class SyncAssetV1 {
|
||||
|
||||
DateTime? fileModifiedAt;
|
||||
|
||||
int? height;
|
||||
|
||||
String id;
|
||||
|
||||
bool isFavorite;
|
||||
@@ -63,6 +67,8 @@ class SyncAssetV1 {
|
||||
|
||||
AssetVisibility visibility;
|
||||
|
||||
int? width;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SyncAssetV1 &&
|
||||
other.checksum == checksum &&
|
||||
@@ -70,6 +76,7 @@ class SyncAssetV1 {
|
||||
other.duration == duration &&
|
||||
other.fileCreatedAt == fileCreatedAt &&
|
||||
other.fileModifiedAt == fileModifiedAt &&
|
||||
other.height == height &&
|
||||
other.id == id &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.libraryId == libraryId &&
|
||||
@@ -80,7 +87,8 @@ class SyncAssetV1 {
|
||||
other.stackId == stackId &&
|
||||
other.thumbhash == thumbhash &&
|
||||
other.type == type &&
|
||||
other.visibility == visibility;
|
||||
other.visibility == visibility &&
|
||||
other.width == width;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
@@ -90,6 +98,7 @@ class SyncAssetV1 {
|
||||
(duration == null ? 0 : duration!.hashCode) +
|
||||
(fileCreatedAt == null ? 0 : fileCreatedAt!.hashCode) +
|
||||
(fileModifiedAt == null ? 0 : fileModifiedAt!.hashCode) +
|
||||
(height == null ? 0 : height!.hashCode) +
|
||||
(id.hashCode) +
|
||||
(isFavorite.hashCode) +
|
||||
(libraryId == null ? 0 : libraryId!.hashCode) +
|
||||
@@ -100,10 +109,11 @@ class SyncAssetV1 {
|
||||
(stackId == null ? 0 : stackId!.hashCode) +
|
||||
(thumbhash == null ? 0 : thumbhash!.hashCode) +
|
||||
(type.hashCode) +
|
||||
(visibility.hashCode);
|
||||
(visibility.hashCode) +
|
||||
(width == null ? 0 : width!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SyncAssetV1[checksum=$checksum, deletedAt=$deletedAt, duration=$duration, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, id=$id, isFavorite=$isFavorite, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, ownerId=$ownerId, stackId=$stackId, thumbhash=$thumbhash, type=$type, visibility=$visibility]';
|
||||
String toString() => 'SyncAssetV1[checksum=$checksum, deletedAt=$deletedAt, duration=$duration, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, height=$height, id=$id, isFavorite=$isFavorite, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, ownerId=$ownerId, stackId=$stackId, thumbhash=$thumbhash, type=$type, visibility=$visibility, width=$width]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -127,6 +137,11 @@ class SyncAssetV1 {
|
||||
json[r'fileModifiedAt'] = this.fileModifiedAt!.toUtc().toIso8601String();
|
||||
} else {
|
||||
// json[r'fileModifiedAt'] = null;
|
||||
}
|
||||
if (this.height != null) {
|
||||
json[r'height'] = this.height;
|
||||
} else {
|
||||
// json[r'height'] = null;
|
||||
}
|
||||
json[r'id'] = this.id;
|
||||
json[r'isFavorite'] = this.isFavorite;
|
||||
@@ -159,6 +174,11 @@ class SyncAssetV1 {
|
||||
}
|
||||
json[r'type'] = this.type;
|
||||
json[r'visibility'] = this.visibility;
|
||||
if (this.width != null) {
|
||||
json[r'width'] = this.width;
|
||||
} else {
|
||||
// json[r'width'] = null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -176,6 +196,7 @@ class SyncAssetV1 {
|
||||
duration: mapValueOfType<String>(json, r'duration'),
|
||||
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r''),
|
||||
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r''),
|
||||
height: mapValueOfType<int>(json, r'height'),
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
|
||||
libraryId: mapValueOfType<String>(json, r'libraryId'),
|
||||
@@ -187,6 +208,7 @@ class SyncAssetV1 {
|
||||
thumbhash: mapValueOfType<String>(json, r'thumbhash'),
|
||||
type: AssetTypeEnum.fromJson(json[r'type'])!,
|
||||
visibility: AssetVisibility.fromJson(json[r'visibility'])!,
|
||||
width: mapValueOfType<int>(json, r'width'),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@@ -239,6 +261,7 @@ class SyncAssetV1 {
|
||||
'duration',
|
||||
'fileCreatedAt',
|
||||
'fileModifiedAt',
|
||||
'height',
|
||||
'id',
|
||||
'isFavorite',
|
||||
'libraryId',
|
||||
@@ -250,6 +273,7 @@ class SyncAssetV1 {
|
||||
'thumbhash',
|
||||
'type',
|
||||
'visibility',
|
||||
'width',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
10
mobile/openapi/lib/model/system_config_job_dto.dart
generated
10
mobile/openapi/lib/model/system_config_job_dto.dart
generated
@@ -14,6 +14,7 @@ class SystemConfigJobDto {
|
||||
/// Returns a new [SystemConfigJobDto] instance.
|
||||
SystemConfigJobDto({
|
||||
required this.backgroundTask,
|
||||
required this.editor,
|
||||
required this.faceDetection,
|
||||
required this.library_,
|
||||
required this.metadataExtraction,
|
||||
@@ -30,6 +31,8 @@ class SystemConfigJobDto {
|
||||
|
||||
JobSettingsDto backgroundTask;
|
||||
|
||||
JobSettingsDto editor;
|
||||
|
||||
JobSettingsDto faceDetection;
|
||||
|
||||
JobSettingsDto library_;
|
||||
@@ -57,6 +60,7 @@ class SystemConfigJobDto {
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SystemConfigJobDto &&
|
||||
other.backgroundTask == backgroundTask &&
|
||||
other.editor == editor &&
|
||||
other.faceDetection == faceDetection &&
|
||||
other.library_ == library_ &&
|
||||
other.metadataExtraction == metadataExtraction &&
|
||||
@@ -74,6 +78,7 @@ class SystemConfigJobDto {
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(backgroundTask.hashCode) +
|
||||
(editor.hashCode) +
|
||||
(faceDetection.hashCode) +
|
||||
(library_.hashCode) +
|
||||
(metadataExtraction.hashCode) +
|
||||
@@ -88,11 +93,12 @@ class SystemConfigJobDto {
|
||||
(workflow.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SystemConfigJobDto[backgroundTask=$backgroundTask, faceDetection=$faceDetection, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]';
|
||||
String toString() => 'SystemConfigJobDto[backgroundTask=$backgroundTask, editor=$editor, faceDetection=$faceDetection, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'backgroundTask'] = this.backgroundTask;
|
||||
json[r'editor'] = this.editor;
|
||||
json[r'faceDetection'] = this.faceDetection;
|
||||
json[r'library'] = this.library_;
|
||||
json[r'metadataExtraction'] = this.metadataExtraction;
|
||||
@@ -118,6 +124,7 @@ class SystemConfigJobDto {
|
||||
|
||||
return SystemConfigJobDto(
|
||||
backgroundTask: JobSettingsDto.fromJson(json[r'backgroundTask'])!,
|
||||
editor: JobSettingsDto.fromJson(json[r'editor'])!,
|
||||
faceDetection: JobSettingsDto.fromJson(json[r'faceDetection'])!,
|
||||
library_: JobSettingsDto.fromJson(json[r'library'])!,
|
||||
metadataExtraction: JobSettingsDto.fromJson(json[r'metadataExtraction'])!,
|
||||
@@ -178,6 +185,7 @@ class SystemConfigJobDto {
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'backgroundTask',
|
||||
'editor',
|
||||
'faceDetection',
|
||||
'library',
|
||||
'metadataExtraction',
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
export 'src/buttons/close_button.dart';
|
||||
export 'src/buttons/icon_button.dart';
|
||||
export 'src/components/close_button.dart';
|
||||
export 'src/components/form.dart';
|
||||
export 'src/components/icon_button.dart';
|
||||
export 'src/components/password_input.dart';
|
||||
export 'src/components/text_button.dart';
|
||||
export 'src/components/text_input.dart';
|
||||
export 'src/constants.dart';
|
||||
export 'src/theme.dart';
|
||||
export 'src/translation.dart';
|
||||
export 'src/types.dart';
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_ui/src/buttons/icon_button.dart';
|
||||
import 'package:immich_ui/src/types.dart';
|
||||
|
||||
import 'icon_button.dart';
|
||||
|
||||
class ImmichCloseButton extends StatelessWidget {
|
||||
final VoidCallback? onTap;
|
||||
final VoidCallback? onPressed;
|
||||
final ImmichVariant variant;
|
||||
final ImmichColor color;
|
||||
|
||||
const ImmichCloseButton({
|
||||
super.key,
|
||||
this.onTap,
|
||||
this.onPressed,
|
||||
this.color = ImmichColor.primary,
|
||||
this.variant = ImmichVariant.ghost,
|
||||
});
|
||||
@@ -20,6 +21,6 @@ class ImmichCloseButton extends StatelessWidget {
|
||||
icon: Icons.close,
|
||||
color: color,
|
||||
variant: variant,
|
||||
onTap: onTap ?? () => Navigator.of(context).pop(),
|
||||
onPressed: onPressed ?? () => Navigator.of(context).pop(),
|
||||
);
|
||||
}
|
||||
98
mobile/packages/ui/lib/src/components/form.dart
Normal file
98
mobile/packages/ui/lib/src/components/form.dart
Normal file
@@ -0,0 +1,98 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_ui/immich_ui.dart';
|
||||
import 'package:immich_ui/src/internal.dart';
|
||||
|
||||
class ImmichForm extends StatefulWidget {
|
||||
final String? submitText;
|
||||
final IconData? submitIcon;
|
||||
final FutureOr<void> Function()? onSubmit;
|
||||
final Widget child;
|
||||
|
||||
const ImmichForm({
|
||||
super.key,
|
||||
this.submitText,
|
||||
this.submitIcon,
|
||||
required this.onSubmit,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ImmichForm> createState() => ImmichFormState();
|
||||
|
||||
static ImmichFormState of(BuildContext context) {
|
||||
final scope = context.dependOnInheritedWidgetOfExactType<_ImmichFormScope>();
|
||||
if (scope == null) {
|
||||
throw FlutterError(
|
||||
'ImmichForm.of() called with a context that does not contain an ImmichForm.\n'
|
||||
'No ImmichForm ancestor could be found starting from the context that was passed to '
|
||||
'ImmichForm.of(). This usually happens when the context provided is '
|
||||
'from a widget above the ImmichForm.\n'
|
||||
'The context used was:\n'
|
||||
'$context',
|
||||
);
|
||||
}
|
||||
return scope._formState;
|
||||
}
|
||||
}
|
||||
|
||||
class ImmichFormState extends State<ImmichForm> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
bool _isLoading = false;
|
||||
|
||||
FutureOr<void> submit() async {
|
||||
final isValid = _formKey.currentState?.validate() ?? false;
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
await widget.onSubmit?.call();
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final submitText = widget.submitText ?? context.translations.submit;
|
||||
return _ImmichFormScope(
|
||||
formState: this,
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
spacing: ImmichSpacing.md,
|
||||
children: [
|
||||
widget.child,
|
||||
ImmichTextButton(
|
||||
labelText: submitText,
|
||||
icon: widget.submitIcon,
|
||||
variant: ImmichVariant.filled,
|
||||
loading: _isLoading,
|
||||
onPressed: submit,
|
||||
disabled: widget.onSubmit == null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ImmichFormScope extends InheritedWidget {
|
||||
const _ImmichFormScope({required super.child, required ImmichFormState formState}) : _formState = formState;
|
||||
|
||||
final ImmichFormState _formState;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(_ImmichFormScope oldWidget) => oldWidget._formState != _formState;
|
||||
}
|
||||
@@ -3,42 +3,48 @@ import 'package:immich_ui/src/types.dart';
|
||||
|
||||
class ImmichIconButton extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final VoidCallback onTap;
|
||||
final VoidCallback onPressed;
|
||||
final ImmichVariant variant;
|
||||
final ImmichColor color;
|
||||
final bool disabled;
|
||||
|
||||
const ImmichIconButton({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.onTap,
|
||||
required this.onPressed,
|
||||
this.color = ImmichColor.primary,
|
||||
this.variant = ImmichVariant.filled,
|
||||
this.disabled = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
final background = switch (variant) {
|
||||
ImmichVariant.filled => switch (color) {
|
||||
ImmichColor.primary => Theme.of(context).colorScheme.primary,
|
||||
ImmichColor.secondary => Theme.of(context).colorScheme.secondary,
|
||||
ImmichColor.primary => colorScheme.primary,
|
||||
ImmichColor.secondary => colorScheme.secondary,
|
||||
},
|
||||
ImmichVariant.ghost => Colors.transparent,
|
||||
};
|
||||
|
||||
final foreground = switch (variant) {
|
||||
ImmichVariant.filled => switch (color) {
|
||||
ImmichColor.primary => Theme.of(context).colorScheme.onPrimary,
|
||||
ImmichColor.secondary => Theme.of(context).colorScheme.onSecondary,
|
||||
ImmichColor.primary => colorScheme.onPrimary,
|
||||
ImmichColor.secondary => colorScheme.onSecondary,
|
||||
},
|
||||
ImmichVariant.ghost => switch (color) {
|
||||
ImmichColor.primary => Theme.of(context).colorScheme.primary,
|
||||
ImmichColor.secondary => Theme.of(context).colorScheme.secondary,
|
||||
ImmichColor.primary => colorScheme.primary,
|
||||
ImmichColor.secondary => colorScheme.secondary,
|
||||
},
|
||||
};
|
||||
|
||||
final effectiveOnPressed = disabled ? null : onPressed;
|
||||
|
||||
return IconButton(
|
||||
icon: Icon(icon),
|
||||
onPressed: onTap,
|
||||
onPressed: effectiveOnPressed,
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: background,
|
||||
foregroundColor: foreground,
|
||||
58
mobile/packages/ui/lib/src/components/password_input.dart
Normal file
58
mobile/packages/ui/lib/src/components/password_input.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_ui/src/components/text_input.dart';
|
||||
import 'package:immich_ui/src/internal.dart';
|
||||
|
||||
class ImmichPasswordInput extends StatefulWidget {
|
||||
final String? label;
|
||||
final String? hintText;
|
||||
final TextEditingController? controller;
|
||||
final FocusNode? focusNode;
|
||||
final String? Function(String?)? validator;
|
||||
final void Function(BuildContext, String)? onSubmit;
|
||||
final TextInputAction? keyboardAction;
|
||||
|
||||
const ImmichPasswordInput({
|
||||
super.key,
|
||||
this.controller,
|
||||
this.focusNode,
|
||||
this.label,
|
||||
this.hintText,
|
||||
this.validator,
|
||||
this.onSubmit,
|
||||
this.keyboardAction,
|
||||
});
|
||||
|
||||
@override
|
||||
State createState() => _ImmichPasswordInputState();
|
||||
}
|
||||
|
||||
class _ImmichPasswordInputState extends State<ImmichPasswordInput> {
|
||||
bool _visible = false;
|
||||
|
||||
void _toggleVisibility() {
|
||||
setState(() {
|
||||
_visible = !_visible;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ImmichTextInput(
|
||||
key: widget.key,
|
||||
label: widget.label ?? context.translations.password,
|
||||
hintText: widget.hintText,
|
||||
controller: widget.controller,
|
||||
focusNode: widget.focusNode,
|
||||
validator: widget.validator,
|
||||
onSubmit: widget.onSubmit,
|
||||
keyboardAction: widget.keyboardAction,
|
||||
obscureText: !_visible,
|
||||
suffixIcon: IconButton(
|
||||
onPressed: _toggleVisibility,
|
||||
icon: Icon(_visible ? Icons.visibility_off_rounded : Icons.visibility_rounded),
|
||||
),
|
||||
autofillHints: [AutofillHints.password],
|
||||
keyboardType: TextInputType.text,
|
||||
);
|
||||
}
|
||||
}
|
||||
87
mobile/packages/ui/lib/src/components/text_button.dart
Normal file
87
mobile/packages/ui/lib/src/components/text_button.dart
Normal file
@@ -0,0 +1,87 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_ui/src/constants.dart';
|
||||
import 'package:immich_ui/src/types.dart';
|
||||
|
||||
class ImmichTextButton extends StatelessWidget {
|
||||
final String labelText;
|
||||
final IconData? icon;
|
||||
final FutureOr<void> Function() onPressed;
|
||||
final ImmichVariant variant;
|
||||
final ImmichColor color;
|
||||
final bool expanded;
|
||||
final bool loading;
|
||||
final bool disabled;
|
||||
|
||||
const ImmichTextButton({
|
||||
super.key,
|
||||
required this.labelText,
|
||||
this.icon,
|
||||
required this.onPressed,
|
||||
this.variant = ImmichVariant.filled,
|
||||
this.color = ImmichColor.primary,
|
||||
this.expanded = true,
|
||||
this.loading = false,
|
||||
this.disabled = false,
|
||||
});
|
||||
|
||||
Widget _buildButton(ImmichVariant variant) {
|
||||
final Widget? effectiveIcon = loading
|
||||
? const SizedBox.square(
|
||||
dimension: ImmichIconSize.md,
|
||||
child: CircularProgressIndicator(strokeWidth: ImmichBorderWidth.lg),
|
||||
)
|
||||
: icon != null
|
||||
? Icon(icon, fontWeight: FontWeight.w600)
|
||||
: null;
|
||||
final hasIcon = effectiveIcon != null;
|
||||
|
||||
final label = Text(labelText, style: const TextStyle(fontSize: ImmichTextSize.body, fontWeight: FontWeight.bold));
|
||||
final style = ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: ImmichSpacing.md));
|
||||
|
||||
final effectiveOnPressed = disabled || loading ? null : onPressed;
|
||||
|
||||
switch (variant) {
|
||||
case ImmichVariant.filled:
|
||||
if (hasIcon) {
|
||||
return ElevatedButton.icon(
|
||||
style: style,
|
||||
onPressed: effectiveOnPressed,
|
||||
icon: effectiveIcon,
|
||||
label: label,
|
||||
);
|
||||
}
|
||||
|
||||
return ElevatedButton(
|
||||
style: style,
|
||||
onPressed: effectiveOnPressed,
|
||||
child: label,
|
||||
);
|
||||
case ImmichVariant.ghost:
|
||||
if (hasIcon) {
|
||||
return TextButton.icon(
|
||||
style: style,
|
||||
onPressed: effectiveOnPressed,
|
||||
icon: effectiveIcon,
|
||||
label: label,
|
||||
);
|
||||
}
|
||||
|
||||
return TextButton(
|
||||
style: style,
|
||||
onPressed: effectiveOnPressed,
|
||||
child: label,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final button = _buildButton(variant);
|
||||
if (expanded) {
|
||||
return SizedBox(width: double.infinity, child: button);
|
||||
}
|
||||
return button;
|
||||
}
|
||||
}
|
||||
88
mobile/packages/ui/lib/src/components/text_input.dart
Normal file
88
mobile/packages/ui/lib/src/components/text_input.dart
Normal file
@@ -0,0 +1,88 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ImmichTextInput extends StatefulWidget {
|
||||
final String label;
|
||||
final String? hintText;
|
||||
final TextEditingController? controller;
|
||||
final FocusNode? focusNode;
|
||||
final String? Function(String?)? validator;
|
||||
final void Function(BuildContext, String)? onSubmit;
|
||||
final TextInputType keyboardType;
|
||||
final TextInputAction? keyboardAction;
|
||||
final List<String>? autofillHints;
|
||||
final Widget? suffixIcon;
|
||||
final bool obscureText;
|
||||
|
||||
const ImmichTextInput({
|
||||
super.key,
|
||||
this.controller,
|
||||
this.focusNode,
|
||||
required this.label,
|
||||
this.hintText,
|
||||
this.validator,
|
||||
this.onSubmit,
|
||||
this.keyboardType = TextInputType.text,
|
||||
this.keyboardAction,
|
||||
this.autofillHints,
|
||||
this.suffixIcon,
|
||||
this.obscureText = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State createState() => _ImmichTextInputState();
|
||||
}
|
||||
|
||||
class _ImmichTextInputState extends State<ImmichTextInput> {
|
||||
late final FocusNode _focusNode;
|
||||
String? _error;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_focusNode = widget.focusNode ?? FocusNode();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (widget.focusNode == null) {
|
||||
_focusNode.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
String? _validateInput(String? value) {
|
||||
setState(() {
|
||||
_error = widget.validator?.call(value);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
bool get _hasError => _error != null && _error!.isNotEmpty;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeData = Theme.of(context);
|
||||
|
||||
return TextFormField(
|
||||
controller: widget.controller,
|
||||
focusNode: _focusNode,
|
||||
decoration: InputDecoration(
|
||||
hintText: widget.hintText,
|
||||
labelText: widget.label,
|
||||
labelStyle: themeData.inputDecorationTheme.labelStyle?.copyWith(
|
||||
color: _hasError ? themeData.colorScheme.error : null,
|
||||
),
|
||||
errorText: _error,
|
||||
suffixIcon: widget.suffixIcon,
|
||||
),
|
||||
obscureText: widget.obscureText,
|
||||
validator: _validateInput,
|
||||
keyboardType: widget.keyboardType,
|
||||
textInputAction: widget.keyboardAction,
|
||||
autofillHints: widget.autofillHints,
|
||||
onTap: () => setState(() => _error = null),
|
||||
onTapOutside: (_) => _focusNode.unfocus(),
|
||||
onFieldSubmitted: (value) => widget.onSubmit?.call(context, value),
|
||||
);
|
||||
}
|
||||
}
|
||||
199
mobile/packages/ui/lib/src/constants.dart
Normal file
199
mobile/packages/ui/lib/src/constants.dart
Normal file
@@ -0,0 +1,199 @@
|
||||
/// Spacing constants for gaps between widgets
|
||||
abstract class ImmichSpacing {
|
||||
const ImmichSpacing._();
|
||||
|
||||
/// Extra small spacing: 4.0
|
||||
static const double xs = 4.0;
|
||||
|
||||
/// Small spacing: 8.0
|
||||
static const double sm = 8.0;
|
||||
|
||||
/// Medium spacing (default): 12.0
|
||||
static const double md = 12.0;
|
||||
|
||||
/// Large spacing: 16.0
|
||||
static const double lg = 16.0;
|
||||
|
||||
/// Extra large spacing: 24.0
|
||||
static const double xl = 24.0;
|
||||
|
||||
/// Extra extra large spacing: 32.0
|
||||
static const double xxl = 32.0;
|
||||
|
||||
/// Extra extra extra large spacing: 48.0
|
||||
static const double xxxl = 48.0;
|
||||
}
|
||||
|
||||
/// Border radius constants for consistent rounded corners
|
||||
abstract class ImmichRadius {
|
||||
const ImmichRadius._();
|
||||
|
||||
/// No radius: 0.0
|
||||
static const double none = 0.0;
|
||||
|
||||
/// Extra small radius: 4.0
|
||||
static const double xs = 4.0;
|
||||
|
||||
/// Small radius: 8.0
|
||||
static const double sm = 8.0;
|
||||
|
||||
/// Medium radius (default): 12.0
|
||||
static const double md = 12.0;
|
||||
|
||||
/// Large radius: 16.0
|
||||
static const double lg = 16.0;
|
||||
|
||||
/// Extra large radius: 20.0
|
||||
static const double xl = 20.0;
|
||||
|
||||
/// Extra extra large radius: 24.0
|
||||
static const double xxl = 24.0;
|
||||
|
||||
/// Full circular radius: infinity
|
||||
static const double full = double.infinity;
|
||||
}
|
||||
|
||||
/// Icon size constants for consistent icon sizing
|
||||
abstract class ImmichIconSize {
|
||||
const ImmichIconSize._();
|
||||
|
||||
/// Extra small icon: 16.0
|
||||
static const double xs = 16.0;
|
||||
|
||||
/// Small icon: 20.0
|
||||
static const double sm = 20.0;
|
||||
|
||||
/// Medium icon (default): 24.0
|
||||
static const double md = 24.0;
|
||||
|
||||
/// Large icon: 32.0
|
||||
static const double lg = 32.0;
|
||||
|
||||
/// Extra large icon: 40.0
|
||||
static const double xl = 40.0;
|
||||
|
||||
/// Extra extra large icon: 48.0
|
||||
static const double xxl = 48.0;
|
||||
}
|
||||
|
||||
/// Animation duration constants for consistent timing
|
||||
abstract class ImmichDuration {
|
||||
const ImmichDuration._();
|
||||
|
||||
/// Extra fast: 100ms
|
||||
static const Duration extraFast = Duration(milliseconds: 100);
|
||||
|
||||
/// Fast: 150ms
|
||||
static const Duration fast = Duration(milliseconds: 150);
|
||||
|
||||
/// Normal: 200ms
|
||||
static const Duration normal = Duration(milliseconds: 200);
|
||||
|
||||
/// Moderate: 300ms
|
||||
static const Duration moderate = Duration(milliseconds: 300);
|
||||
|
||||
/// Slow: 500ms
|
||||
static const Duration slow = Duration(milliseconds: 500);
|
||||
|
||||
/// Extra slow: 700ms
|
||||
static const Duration extraSlow = Duration(milliseconds: 700);
|
||||
}
|
||||
|
||||
/// Elevation constants for consistent shadows and depth
|
||||
abstract class ImmichElevation {
|
||||
const ImmichElevation._();
|
||||
|
||||
/// No elevation: 0.0
|
||||
static const double none = 0.0;
|
||||
|
||||
/// Extra small elevation: 1.0
|
||||
static const double xs = 1.0;
|
||||
|
||||
/// Small elevation: 2.0
|
||||
static const double sm = 2.0;
|
||||
|
||||
/// Medium elevation: 4.0
|
||||
static const double md = 4.0;
|
||||
|
||||
/// Large elevation: 8.0
|
||||
static const double lg = 8.0;
|
||||
|
||||
/// Extra large elevation: 12.0
|
||||
static const double xl = 12.0;
|
||||
|
||||
/// Extra extra large elevation: 16.0
|
||||
static const double xxl = 16.0;
|
||||
}
|
||||
|
||||
/// Border width constants (similar to Tailwind's border-* scale)
|
||||
abstract class ImmichBorderWidth {
|
||||
const ImmichBorderWidth._();
|
||||
|
||||
/// No border: 0.0
|
||||
static const double none = 0.0;
|
||||
|
||||
/// Hairline border: 0.5
|
||||
static const double hairline = 0.5;
|
||||
|
||||
/// Default border: 1.0 (border)
|
||||
static const double base = 1.0;
|
||||
|
||||
/// Medium border: 2.0 (border-2)
|
||||
static const double md = 2.0;
|
||||
|
||||
/// Large border: 3.0 (border-4)
|
||||
static const double lg = 3.0;
|
||||
|
||||
/// Extra large border: 4.0
|
||||
static const double xl = 4.0;
|
||||
}
|
||||
|
||||
/// Text size constants with semantic HTML-like naming
|
||||
/// These follow a type scale for harmonious text hierarchy
|
||||
abstract class ImmichTextSize {
|
||||
const ImmichTextSize._();
|
||||
|
||||
/// Caption text: 10.0
|
||||
/// Use for: Tiny labels, legal text, metadata, timestamps
|
||||
static const double caption = 10.0;
|
||||
|
||||
/// Label text: 12.0
|
||||
/// Use for: Form labels, secondary text, helper text
|
||||
static const double label = 12.0;
|
||||
|
||||
/// Body text: 14.0 (default)
|
||||
/// Use for: Main body text, paragraphs, default UI text
|
||||
static const double body = 14.0;
|
||||
|
||||
/// Body emphasized: 16.0
|
||||
/// Use for: Emphasized body text, button labels, tabs
|
||||
static const double bodyLarge = 16.0;
|
||||
|
||||
/// Heading 6: 18.0 (smallest heading)
|
||||
/// Use for: Subtitles, card titles, section headers
|
||||
static const double h6 = 18.0;
|
||||
|
||||
/// Heading 5: 20.0
|
||||
/// Use for: Small headings, prominent labels
|
||||
static const double h5 = 20.0;
|
||||
|
||||
/// Heading 4: 24.0
|
||||
/// Use for: Page titles, dialog titles
|
||||
static const double h4 = 24.0;
|
||||
|
||||
/// Heading 3: 30.0
|
||||
/// Use for: Section headings, large headings
|
||||
static const double h3 = 30.0;
|
||||
|
||||
/// Heading 2: 36.0
|
||||
/// Use for: Major section headings
|
||||
static const double h2 = 36.0;
|
||||
|
||||
/// Heading 1: 48.0 (largest heading)
|
||||
/// Use for: Page hero headings, main titles
|
||||
static const double h1 = 48.0;
|
||||
|
||||
/// Display text: 60.0
|
||||
/// Use for: Hero numbers, splash screens, extra large display
|
||||
static const double display = 60.0;
|
||||
}
|
||||
6
mobile/packages/ui/lib/src/internal.dart
Normal file
6
mobile/packages/ui/lib/src/internal.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_ui/src/translation.dart';
|
||||
|
||||
extension TranslationHelper on BuildContext {
|
||||
ImmichTranslations get translations => ImmichTranslationProvider.of(this);
|
||||
}
|
||||
42
mobile/packages/ui/lib/src/theme.dart
Normal file
42
mobile/packages/ui/lib/src/theme.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_ui/src/constants.dart';
|
||||
|
||||
class ImmichThemeProvider extends StatelessWidget {
|
||||
final ColorScheme colorScheme;
|
||||
final Widget child;
|
||||
|
||||
const ImmichThemeProvider({super.key, required this.colorScheme, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
colorScheme: colorScheme,
|
||||
brightness: colorScheme.brightness,
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: colorScheme.primary),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: colorScheme.primary),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: colorScheme.error),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: colorScheme.error),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)),
|
||||
),
|
||||
labelStyle: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.w600),
|
||||
hintStyle: const TextStyle(fontSize: ImmichTextSize.body),
|
||||
errorStyle: TextStyle(color: colorScheme.error, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
31
mobile/packages/ui/lib/src/translation.dart
Normal file
31
mobile/packages/ui/lib/src/translation.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ImmichTranslations {
|
||||
late String submit;
|
||||
late String password;
|
||||
|
||||
ImmichTranslations({String? submit, String? password}) {
|
||||
this.submit = submit ?? 'Submit';
|
||||
this.password = password ?? 'Password';
|
||||
}
|
||||
}
|
||||
|
||||
class ImmichTranslationProvider extends InheritedWidget {
|
||||
final ImmichTranslations? translations;
|
||||
|
||||
const ImmichTranslationProvider({
|
||||
super.key,
|
||||
this.translations,
|
||||
required super.child,
|
||||
});
|
||||
|
||||
static ImmichTranslations of(BuildContext context) {
|
||||
final provider = context.dependOnInheritedWidgetOfExactType<ImmichTranslationProvider>();
|
||||
return provider?.translations ?? ImmichTranslations();
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(covariant ImmichTranslationProvider oldWidget) {
|
||||
return oldWidget.translations != translations;
|
||||
}
|
||||
}
|
||||
185
mobile/test/domain/repositories/sync_stream_repository_test.dart
Normal file
185
mobile/test/domain/repositories/sync_stream_repository_test.dart
Normal file
@@ -0,0 +1,185 @@
|
||||
import 'package:drift/drift.dart' as drift;
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
SyncUserV1 _createUser({String id = 'user-1'}) {
|
||||
return SyncUserV1(
|
||||
id: id,
|
||||
name: 'Test User',
|
||||
email: 'test@test.com',
|
||||
deletedAt: null,
|
||||
avatarColor: null,
|
||||
hasProfileImage: false,
|
||||
profileChangedAt: DateTime(2024, 1, 1),
|
||||
);
|
||||
}
|
||||
|
||||
SyncAssetV1 _createAsset({
|
||||
required String id,
|
||||
required String checksum,
|
||||
required String fileName,
|
||||
String ownerId = 'user-1',
|
||||
int? width,
|
||||
int? height,
|
||||
}) {
|
||||
return SyncAssetV1(
|
||||
id: id,
|
||||
checksum: checksum,
|
||||
originalFileName: fileName,
|
||||
type: AssetTypeEnum.IMAGE,
|
||||
ownerId: ownerId,
|
||||
isFavorite: false,
|
||||
fileCreatedAt: DateTime(2024, 1, 1),
|
||||
fileModifiedAt: DateTime(2024, 1, 1),
|
||||
localDateTime: DateTime(2024, 1, 1),
|
||||
visibility: AssetVisibility.timeline,
|
||||
width: width,
|
||||
height: height,
|
||||
deletedAt: null,
|
||||
duration: null,
|
||||
libraryId: null,
|
||||
livePhotoVideoId: null,
|
||||
stackId: null,
|
||||
thumbhash: null,
|
||||
);
|
||||
}
|
||||
|
||||
SyncAssetExifV1 _createExif({
|
||||
required String assetId,
|
||||
required int width,
|
||||
required int height,
|
||||
required String orientation,
|
||||
}) {
|
||||
return SyncAssetExifV1(
|
||||
assetId: assetId,
|
||||
exifImageWidth: width,
|
||||
exifImageHeight: height,
|
||||
orientation: orientation,
|
||||
city: null,
|
||||
country: null,
|
||||
dateTimeOriginal: null,
|
||||
description: null,
|
||||
exposureTime: null,
|
||||
fNumber: null,
|
||||
fileSizeInByte: null,
|
||||
focalLength: null,
|
||||
fps: null,
|
||||
iso: null,
|
||||
latitude: null,
|
||||
lensModel: null,
|
||||
longitude: null,
|
||||
make: null,
|
||||
model: null,
|
||||
modifyDate: null,
|
||||
profileDescription: null,
|
||||
projectionType: null,
|
||||
rating: null,
|
||||
state: null,
|
||||
timeZone: null,
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Drift db;
|
||||
late SyncStreamRepository sut;
|
||||
|
||||
setUp(() async {
|
||||
db = Drift(drift.DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
||||
sut = SyncStreamRepository(db);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await db.close();
|
||||
});
|
||||
|
||||
group('SyncStreamRepository - Dimension swapping based on orientation', () {
|
||||
test('swaps dimensions for asset with rotated orientation', () async {
|
||||
final flippedOrientations = ['5', '6', '7', '8', '90', '-90'];
|
||||
|
||||
for (final orientation in flippedOrientations) {
|
||||
final assetId = 'asset-$orientation-degrees';
|
||||
|
||||
await sut.updateUsersV1([_createUser()]);
|
||||
|
||||
final asset = _createAsset(
|
||||
id: assetId,
|
||||
checksum: 'checksum-$orientation',
|
||||
fileName: 'rotated_$orientation.jpg',
|
||||
);
|
||||
await sut.updateAssetsV1([asset]);
|
||||
|
||||
final exif = _createExif(
|
||||
assetId: assetId,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
orientation: orientation, // EXIF orientation value for 90 degrees CW
|
||||
);
|
||||
await sut.updateAssetsExifV1([exif]);
|
||||
|
||||
final query = db.remoteAssetEntity.select()..where((tbl) => tbl.id.equals(assetId));
|
||||
final result = await query.getSingle();
|
||||
|
||||
expect(result.width, equals(1080));
|
||||
expect(result.height, equals(1920));
|
||||
}
|
||||
});
|
||||
|
||||
test('does not swap dimensions for asset with normal orientation', () async {
|
||||
final nonFlippedOrientations = ['1', '2', '3', '4'];
|
||||
for (final orientation in nonFlippedOrientations) {
|
||||
final assetId = 'asset-$orientation-degrees';
|
||||
|
||||
await sut.updateUsersV1([_createUser()]);
|
||||
|
||||
final asset = _createAsset(id: assetId, checksum: 'checksum-$orientation', fileName: 'normal_$orientation.jpg');
|
||||
await sut.updateAssetsV1([asset]);
|
||||
|
||||
final exif = _createExif(
|
||||
assetId: assetId,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
orientation: orientation, // EXIF orientation value for normal
|
||||
);
|
||||
await sut.updateAssetsExifV1([exif]);
|
||||
|
||||
final query = db.remoteAssetEntity.select()..where((tbl) => tbl.id.equals(assetId));
|
||||
final result = await query.getSingle();
|
||||
|
||||
expect(result.width, equals(1920));
|
||||
expect(result.height, equals(1080));
|
||||
}
|
||||
});
|
||||
|
||||
test('does not update dimensions if asset already has width and height', () async {
|
||||
const assetId = 'asset-with-dimensions';
|
||||
const existingWidth = 1920;
|
||||
const existingHeight = 1080;
|
||||
const exifWidth = 3840;
|
||||
const exifHeight = 2160;
|
||||
|
||||
await sut.updateUsersV1([_createUser()]);
|
||||
|
||||
final asset = _createAsset(
|
||||
id: assetId,
|
||||
checksum: 'checksum-with-dims',
|
||||
fileName: 'with_dimensions.jpg',
|
||||
width: existingWidth,
|
||||
height: existingHeight,
|
||||
);
|
||||
await sut.updateAssetsV1([asset]);
|
||||
|
||||
final exif = _createExif(assetId: assetId, width: exifWidth, height: exifHeight, orientation: '6');
|
||||
await sut.updateAssetsExifV1([exif]);
|
||||
|
||||
// Verify the asset still has original dimensions (not updated from EXIF)
|
||||
final query = db.remoteAssetEntity.select()..where((tbl) => tbl.id.equals(assetId));
|
||||
final result = await query.getSingle();
|
||||
|
||||
expect(result.width, equals(existingWidth), reason: 'Width should remain as originally set');
|
||||
expect(result.height, equals(existingHeight), reason: 'Height should remain as originally set');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -18,30 +18,34 @@ void main() {
|
||||
mockAlbumApiRepo = MockDriftAlbumApiRepository();
|
||||
sut = RemoteAlbumService(mockRemoteAlbumRepo, mockAlbumApiRepo);
|
||||
|
||||
when(() => mockRemoteAlbumRepo.getNewestAssetTimestamp(any())).thenAnswer((invocation) {
|
||||
// Simulate a timestamp for the newest asset in the album
|
||||
final albumID = invocation.positionalArguments[0] as String;
|
||||
|
||||
if (albumID == '1') {
|
||||
return Future.value(DateTime(2023, 1, 1));
|
||||
} else if (albumID == '2') {
|
||||
return Future.value(DateTime(2023, 2, 1));
|
||||
when(() => mockRemoteAlbumRepo.getNewestAssetTimestampForAlbums(any())).thenAnswer((invocation) async {
|
||||
final albumIds = invocation.positionalArguments[0] as List<String>;
|
||||
final result = <String, DateTime?>{};
|
||||
for (final id in albumIds) {
|
||||
if (id == '1') {
|
||||
result[id] = DateTime(2023, 1, 1);
|
||||
} else if (id == '2') {
|
||||
result[id] = DateTime(2023, 2, 1);
|
||||
} else {
|
||||
result[id] = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
}
|
||||
}
|
||||
|
||||
return Future.value(DateTime.fromMillisecondsSinceEpoch(0));
|
||||
return result;
|
||||
});
|
||||
|
||||
when(() => mockRemoteAlbumRepo.getOldestAssetTimestamp(any())).thenAnswer((invocation) {
|
||||
// Simulate a timestamp for the oldest asset in the album
|
||||
final albumID = invocation.positionalArguments[0] as String;
|
||||
|
||||
if (albumID == '1') {
|
||||
return Future.value(DateTime(2019, 1, 1));
|
||||
} else if (albumID == '2') {
|
||||
return Future.value(DateTime(2019, 2, 1));
|
||||
when(() => mockRemoteAlbumRepo.getOldestAssetTimestampForAlbums(any())).thenAnswer((invocation) async {
|
||||
final albumIds = invocation.positionalArguments[0] as List<String>;
|
||||
final result = <String, DateTime?>{};
|
||||
for (final id in albumIds) {
|
||||
if (id == '1') {
|
||||
result[id] = DateTime(2019, 1, 1);
|
||||
} else if (id == '2') {
|
||||
result[id] = DateTime(2019, 2, 1);
|
||||
} else {
|
||||
result[id] = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
}
|
||||
}
|
||||
|
||||
return Future.value(DateTime.fromMillisecondsSinceEpoch(0));
|
||||
return result;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -166,8 +166,8 @@ void main() {
|
||||
expect(result, 1080 / 1920);
|
||||
});
|
||||
|
||||
test('handles various flipped EXIF orientations correctly', () async {
|
||||
final flippedOrientations = ['5', '6', '7', '8', '90', '-90'];
|
||||
test('should not flip remote asset dimensions', () async {
|
||||
final flippedOrientations = ['1', '2', '3', '4', '5', '6', '7', '8', '90', '-90'];
|
||||
|
||||
for (final orientation in flippedOrientations) {
|
||||
final remoteAsset = TestUtils.createRemoteAsset(id: 'remote-$orientation', width: 1920, height: 1080);
|
||||
@@ -178,23 +178,7 @@ void main() {
|
||||
|
||||
final result = await sut.getAspectRatio(remoteAsset);
|
||||
|
||||
expect(result, 1080 / 1920, reason: 'Orientation $orientation should flip dimensions');
|
||||
}
|
||||
});
|
||||
|
||||
test('handles various non-flipped EXIF orientations correctly', () async {
|
||||
final nonFlippedOrientations = ['1', '2', '3', '4'];
|
||||
|
||||
for (final orientation in nonFlippedOrientations) {
|
||||
final remoteAsset = TestUtils.createRemoteAsset(id: 'remote-$orientation', width: 1920, height: 1080);
|
||||
|
||||
final exif = ExifInfo(orientation: orientation);
|
||||
|
||||
when(() => mockRemoteAssetRepository.getExif('remote-$orientation')).thenAnswer((_) async => exif);
|
||||
|
||||
final result = await sut.getAspectRatio(remoteAsset);
|
||||
|
||||
expect(result, 1920 / 1080, reason: 'Orientation $orientation should NOT flip dimensions');
|
||||
expect(result, 1920 / 1080, reason: 'Should not flipped remote asset dimensions for orientation $orientation');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
22
mobile/test/fixtures/sync_stream.stub.dart
vendored
22
mobile/test/fixtures/sync_stream.stub.dart
vendored
@@ -94,25 +94,11 @@ abstract final class SyncStreamStub {
|
||||
required String ack,
|
||||
DateTime? trashedAt,
|
||||
}) {
|
||||
return _assetV1(
|
||||
id: id,
|
||||
checksum: checksum,
|
||||
deletedAt: trashedAt ?? DateTime(2025, 1, 1),
|
||||
ack: ack,
|
||||
);
|
||||
return _assetV1(id: id, checksum: checksum, deletedAt: trashedAt ?? DateTime(2025, 1, 1), ack: ack);
|
||||
}
|
||||
|
||||
static SyncEvent assetModified({
|
||||
required String id,
|
||||
required String checksum,
|
||||
required String ack,
|
||||
}) {
|
||||
return _assetV1(
|
||||
id: id,
|
||||
checksum: checksum,
|
||||
deletedAt: null,
|
||||
ack: ack,
|
||||
);
|
||||
static SyncEvent assetModified({required String id, required String checksum, required String ack}) {
|
||||
return _assetV1(id: id, checksum: checksum, deletedAt: null, ack: ack);
|
||||
}
|
||||
|
||||
static SyncEvent _assetV1({
|
||||
@@ -140,6 +126,8 @@ abstract final class SyncStreamStub {
|
||||
thumbhash: null,
|
||||
type: AssetTypeEnum.IMAGE,
|
||||
visibility: AssetVisibility.timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
),
|
||||
ack: ack,
|
||||
);
|
||||
|
||||
@@ -45,5 +45,17 @@ void main() {
|
||||
addDefault(value, keys, defaultValue);
|
||||
expect(value['alpha']['beta'], 'gamma');
|
||||
});
|
||||
|
||||
test('addDefault with null', () {
|
||||
dynamic value = jsonDecode("""
|
||||
{
|
||||
"download": {
|
||||
"archiveSize": 4294967296,
|
||||
"includeEmbeddedVideos": false
|
||||
}
|
||||
}
|
||||
""");
|
||||
expect(value['download']['unknownKey'], isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2528,6 +2528,16 @@
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AssetMediaResponseDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Asset is a duplicate"
|
||||
},
|
||||
"201": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
@@ -2536,7 +2546,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
"description": "Asset uploaded successfully"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
@@ -2906,6 +2916,112 @@
|
||||
"x-immich-state": "Stable"
|
||||
}
|
||||
},
|
||||
"/assets/metadata": {
|
||||
"delete": {
|
||||
"description": "Delete metadata key-value pairs for multiple assets.",
|
||||
"operationId": "deleteBulkAssetMetadata",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AssetMetadataBulkDeleteDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"summary": "Delete asset metadata",
|
||||
"tags": [
|
||||
"Assets"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v1",
|
||||
"state": "Added"
|
||||
},
|
||||
{
|
||||
"version": "v2.5.0",
|
||||
"state": "Beta"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "asset.update",
|
||||
"x-immich-state": "Beta"
|
||||
},
|
||||
"put": {
|
||||
"description": "Upsert metadata key-value pairs for multiple assets.",
|
||||
"operationId": "updateBulkAssetMetadata",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AssetMetadataBulkUpsertDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/AssetMetadataBulkResponseDto"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"summary": "Upsert asset metadata",
|
||||
"tags": [
|
||||
"Assets"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v1",
|
||||
"state": "Added"
|
||||
},
|
||||
{
|
||||
"version": "v2.5.0",
|
||||
"state": "Beta"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "asset.update",
|
||||
"x-immich-state": "Beta"
|
||||
}
|
||||
},
|
||||
"/assets/random": {
|
||||
"get": {
|
||||
"deprecated": true,
|
||||
@@ -3187,6 +3303,173 @@
|
||||
"x-immich-state": "Stable"
|
||||
}
|
||||
},
|
||||
"/assets/{id}/edits": {
|
||||
"delete": {
|
||||
"description": "Removes all edit actions (crop, rotate, mirror) associated with the specified asset.",
|
||||
"operationId": "removeAssetEdits",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"summary": "Remove edits from an existing asset",
|
||||
"tags": [
|
||||
"Assets"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v2.5.0",
|
||||
"state": "Added"
|
||||
},
|
||||
{
|
||||
"version": "v2.5.0",
|
||||
"state": "Beta"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "asset.edit.delete",
|
||||
"x-immich-state": "Beta"
|
||||
},
|
||||
"get": {
|
||||
"description": "Retrieve a series of edit actions (crop, rotate, mirror) associated with the specified asset.",
|
||||
"operationId": "getAssetEdits",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AssetEditsDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"summary": "Retrieve edits for an existing asset",
|
||||
"tags": [
|
||||
"Assets"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v2.5.0",
|
||||
"state": "Added"
|
||||
},
|
||||
{
|
||||
"version": "v2.5.0",
|
||||
"state": "Beta"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "asset.edit.get",
|
||||
"x-immich-state": "Beta"
|
||||
},
|
||||
"put": {
|
||||
"description": "Apply a series of edit actions (crop, rotate, mirror) to the specified asset.",
|
||||
"operationId": "editAsset",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AssetEditActionListDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AssetEditsDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"summary": "Apply edits to an existing asset",
|
||||
"tags": [
|
||||
"Assets"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v2.5.0",
|
||||
"state": "Added"
|
||||
},
|
||||
{
|
||||
"version": "v2.5.0",
|
||||
"state": "Beta"
|
||||
}
|
||||
],
|
||||
"x-immich-permission": "asset.edit.create",
|
||||
"x-immich-state": "Beta"
|
||||
}
|
||||
},
|
||||
"/assets/{id}/metadata": {
|
||||
"get": {
|
||||
"description": "Retrieve all metadata key-value pairs associated with the specified asset.",
|
||||
@@ -3340,7 +3623,7 @@
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AssetMetadataKey"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -3399,7 +3682,7 @@
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AssetMetadataKey"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -3516,6 +3799,15 @@
|
||||
"description": "Downloads the original file of the specified asset.",
|
||||
"operationId": "downloadAsset",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "edited",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
@@ -3637,7 +3929,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
"description": "Asset replaced successfully"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
@@ -3676,6 +3968,15 @@
|
||||
"description": "Retrieve the thumbnail image for the specified asset.",
|
||||
"operationId": "viewAsset",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "edited",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
@@ -15170,6 +15471,128 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AssetEditAction": {
|
||||
"enum": [
|
||||
"crop",
|
||||
"rotate",
|
||||
"mirror"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AssetEditActionCrop": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetEditAction"
|
||||
}
|
||||
]
|
||||
},
|
||||
"parameters": {
|
||||
"$ref": "#/components/schemas/CropParameters"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action",
|
||||
"parameters"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AssetEditActionListDto": {
|
||||
"properties": {
|
||||
"edits": {
|
||||
"description": "list of edits",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetEditActionCrop"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetEditActionRotate"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetEditActionMirror"
|
||||
}
|
||||
]
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"edits"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AssetEditActionMirror": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetEditAction"
|
||||
}
|
||||
]
|
||||
},
|
||||
"parameters": {
|
||||
"$ref": "#/components/schemas/MirrorParameters"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action",
|
||||
"parameters"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AssetEditActionRotate": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetEditAction"
|
||||
}
|
||||
]
|
||||
},
|
||||
"parameters": {
|
||||
"$ref": "#/components/schemas/RotateParameters"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action",
|
||||
"parameters"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AssetEditsDto": {
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
},
|
||||
"edits": {
|
||||
"description": "list of edits",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetEditActionCrop"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetEditActionRotate"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetEditActionMirror"
|
||||
}
|
||||
]
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"assetId",
|
||||
"edits"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AssetFaceCreateDto": {
|
||||
"properties": {
|
||||
"assetId": {
|
||||
@@ -15499,8 +15922,7 @@
|
||||
"deviceAssetId",
|
||||
"deviceId",
|
||||
"fileCreatedAt",
|
||||
"fileModifiedAt",
|
||||
"metadata"
|
||||
"fileModifiedAt"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -15575,20 +15997,98 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AssetMetadataKey": {
|
||||
"enum": [
|
||||
"mobile-app"
|
||||
"AssetMetadataBulkDeleteDto": {
|
||||
"properties": {
|
||||
"items": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/AssetMetadataBulkDeleteItemDto"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"items"
|
||||
],
|
||||
"type": "string"
|
||||
"type": "object"
|
||||
},
|
||||
"AssetMetadataBulkDeleteItemDto": {
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"assetId",
|
||||
"key"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AssetMetadataBulkResponseDto": {
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"assetId",
|
||||
"key",
|
||||
"updatedAt",
|
||||
"value"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AssetMetadataBulkUpsertDto": {
|
||||
"properties": {
|
||||
"items": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/AssetMetadataBulkUpsertItemDto"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"items"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AssetMetadataBulkUpsertItemDto": {
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"assetId",
|
||||
"key",
|
||||
"value"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AssetMetadataResponseDto": {
|
||||
"properties": {
|
||||
"key": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetMetadataKey"
|
||||
}
|
||||
]
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"format": "date-time",
|
||||
@@ -15622,11 +16122,7 @@
|
||||
"AssetMetadataUpsertItemDto": {
|
||||
"properties": {
|
||||
"key": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetMetadataKey"
|
||||
}
|
||||
]
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "object"
|
||||
@@ -15770,6 +16266,10 @@
|
||||
"hasMetadata": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"height": {
|
||||
"nullable": true,
|
||||
"type": "number"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -15890,6 +16390,10 @@
|
||||
"$ref": "#/components/schemas/AssetVisibility"
|
||||
}
|
||||
]
|
||||
},
|
||||
"width": {
|
||||
"nullable": true,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -15901,6 +16405,7 @@
|
||||
"fileCreatedAt",
|
||||
"fileModifiedAt",
|
||||
"hasMetadata",
|
||||
"height",
|
||||
"id",
|
||||
"isArchived",
|
||||
"isFavorite",
|
||||
@@ -15913,7 +16418,8 @@
|
||||
"thumbhash",
|
||||
"type",
|
||||
"updatedAt",
|
||||
"visibility"
|
||||
"visibility",
|
||||
"width"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -16277,6 +16783,37 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CropParameters": {
|
||||
"properties": {
|
||||
"height": {
|
||||
"description": "Height of the crop",
|
||||
"minimum": 1,
|
||||
"type": "number"
|
||||
},
|
||||
"width": {
|
||||
"description": "Width of the crop",
|
||||
"minimum": 1,
|
||||
"type": "number"
|
||||
},
|
||||
"x": {
|
||||
"description": "Top-Left X coordinate of crop",
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"y": {
|
||||
"description": "Top-Left Y coordinate of crop",
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"height",
|
||||
"width",
|
||||
"x",
|
||||
"y"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"DatabaseBackupConfig": {
|
||||
"properties": {
|
||||
"cronExpression": {
|
||||
@@ -16676,6 +17213,7 @@
|
||||
"AssetDetectFaces",
|
||||
"AssetDetectDuplicatesQueueAll",
|
||||
"AssetDetectDuplicates",
|
||||
"AssetEditThumbnailGeneration",
|
||||
"AssetEncodeVideoQueueAll",
|
||||
"AssetEncodeVideo",
|
||||
"AssetEmptyTrash",
|
||||
@@ -17431,6 +17969,30 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"MirrorAxis": {
|
||||
"description": "Axis to mirror along",
|
||||
"enum": [
|
||||
"horizontal",
|
||||
"vertical"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MirrorParameters": {
|
||||
"properties": {
|
||||
"axis": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/MirrorAxis"
|
||||
}
|
||||
],
|
||||
"description": "Axis to mirror along"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"axis"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"NotificationCreateDto": {
|
||||
"properties": {
|
||||
"data": {
|
||||
@@ -17911,6 +18473,10 @@
|
||||
"asset.upload",
|
||||
"asset.replace",
|
||||
"asset.copy",
|
||||
"asset.derive",
|
||||
"asset.edit.get",
|
||||
"asset.edit.create",
|
||||
"asset.edit.delete",
|
||||
"album.create",
|
||||
"album.read",
|
||||
"album.update",
|
||||
@@ -18624,7 +19190,8 @@
|
||||
"notifications",
|
||||
"backupDatabase",
|
||||
"ocr",
|
||||
"workflow"
|
||||
"workflow",
|
||||
"editor"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -18731,6 +19298,9 @@
|
||||
"duplicateDetection": {
|
||||
"$ref": "#/components/schemas/QueueResponseLegacyDto"
|
||||
},
|
||||
"editor": {
|
||||
"$ref": "#/components/schemas/QueueResponseLegacyDto"
|
||||
},
|
||||
"faceDetection": {
|
||||
"$ref": "#/components/schemas/QueueResponseLegacyDto"
|
||||
},
|
||||
@@ -18778,6 +19348,7 @@
|
||||
"backgroundTask",
|
||||
"backupDatabase",
|
||||
"duplicateDetection",
|
||||
"editor",
|
||||
"faceDetection",
|
||||
"facialRecognition",
|
||||
"library",
|
||||
@@ -18990,6 +19561,18 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"RotateParameters": {
|
||||
"properties": {
|
||||
"angle": {
|
||||
"description": "Rotation angle in degrees",
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"angle"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SearchAlbumResponseDto": {
|
||||
"properties": {
|
||||
"count": {
|
||||
@@ -20651,11 +21234,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetMetadataKey"
|
||||
}
|
||||
]
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -20670,11 +21249,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetMetadataKey"
|
||||
}
|
||||
]
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "object"
|
||||
@@ -20711,6 +21286,10 @@
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"height": {
|
||||
"nullable": true,
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -20757,6 +21336,10 @@
|
||||
"$ref": "#/components/schemas/AssetVisibility"
|
||||
}
|
||||
]
|
||||
},
|
||||
"width": {
|
||||
"nullable": true,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -20765,6 +21348,7 @@
|
||||
"duration",
|
||||
"fileCreatedAt",
|
||||
"fileModifiedAt",
|
||||
"height",
|
||||
"id",
|
||||
"isFavorite",
|
||||
"libraryId",
|
||||
@@ -20775,7 +21359,8 @@
|
||||
"stackId",
|
||||
"thumbhash",
|
||||
"type",
|
||||
"visibility"
|
||||
"visibility",
|
||||
"width"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -21628,6 +22213,9 @@
|
||||
"backgroundTask": {
|
||||
"$ref": "#/components/schemas/JobSettingsDto"
|
||||
},
|
||||
"editor": {
|
||||
"$ref": "#/components/schemas/JobSettingsDto"
|
||||
},
|
||||
"faceDetection": {
|
||||
"$ref": "#/components/schemas/JobSettingsDto"
|
||||
},
|
||||
@@ -21667,6 +22255,7 @@
|
||||
},
|
||||
"required": [
|
||||
"backgroundTask",
|
||||
"editor",
|
||||
"faceDetection",
|
||||
"library",
|
||||
"metadataExtraction",
|
||||
|
||||
@@ -349,6 +349,7 @@ export type AssetResponseDto = {
|
||||
/** The UTC timestamp when the file was last modified on the filesystem. This reflects the last time the physical file was changed, which may be different from when the photo was originally taken. */
|
||||
fileModifiedAt: string;
|
||||
hasMetadata: boolean;
|
||||
height: number | null;
|
||||
id: string;
|
||||
isArchived: boolean;
|
||||
isFavorite: boolean;
|
||||
@@ -373,6 +374,7 @@ export type AssetResponseDto = {
|
||||
/** The UTC timestamp when the asset record was last updated in the database. This is automatically maintained by the database and reflects when any field in the asset was last modified. */
|
||||
updatedAt: string;
|
||||
visibility: AssetVisibility;
|
||||
width: number | null;
|
||||
};
|
||||
export type ContributorCountResponseDto = {
|
||||
assetCount: number;
|
||||
@@ -471,7 +473,7 @@ export type AssetBulkDeleteDto = {
|
||||
ids: string[];
|
||||
};
|
||||
export type AssetMetadataUpsertItemDto = {
|
||||
key: AssetMetadataKey;
|
||||
key: string;
|
||||
value: object;
|
||||
};
|
||||
export type AssetMediaCreateDto = {
|
||||
@@ -484,7 +486,7 @@ export type AssetMediaCreateDto = {
|
||||
filename?: string;
|
||||
isFavorite?: boolean;
|
||||
livePhotoVideoId?: string;
|
||||
metadata: AssetMetadataUpsertItemDto[];
|
||||
metadata?: AssetMetadataUpsertItemDto[];
|
||||
sidecarData?: Blob;
|
||||
visibility?: AssetVisibility;
|
||||
};
|
||||
@@ -543,6 +545,27 @@ export type AssetJobsDto = {
|
||||
assetIds: string[];
|
||||
name: AssetJobName;
|
||||
};
|
||||
export type AssetMetadataBulkDeleteItemDto = {
|
||||
assetId: string;
|
||||
key: string;
|
||||
};
|
||||
export type AssetMetadataBulkDeleteDto = {
|
||||
items: AssetMetadataBulkDeleteItemDto[];
|
||||
};
|
||||
export type AssetMetadataBulkUpsertItemDto = {
|
||||
assetId: string;
|
||||
key: string;
|
||||
value: object;
|
||||
};
|
||||
export type AssetMetadataBulkUpsertDto = {
|
||||
items: AssetMetadataBulkUpsertItemDto[];
|
||||
};
|
||||
export type AssetMetadataBulkResponseDto = {
|
||||
assetId: string;
|
||||
key: string;
|
||||
updatedAt: string;
|
||||
value: object;
|
||||
};
|
||||
export type UpdateAssetDto = {
|
||||
dateTimeOriginal?: string;
|
||||
description?: string;
|
||||
@@ -553,8 +576,47 @@ export type UpdateAssetDto = {
|
||||
rating?: number;
|
||||
visibility?: AssetVisibility;
|
||||
};
|
||||
export type CropParameters = {
|
||||
/** Height of the crop */
|
||||
height: number;
|
||||
/** Width of the crop */
|
||||
width: number;
|
||||
/** Top-Left X coordinate of crop */
|
||||
x: number;
|
||||
/** Top-Left Y coordinate of crop */
|
||||
y: number;
|
||||
};
|
||||
export type AssetEditActionCrop = {
|
||||
action: AssetEditAction;
|
||||
parameters: CropParameters;
|
||||
};
|
||||
export type RotateParameters = {
|
||||
/** Rotation angle in degrees */
|
||||
angle: number;
|
||||
};
|
||||
export type AssetEditActionRotate = {
|
||||
action: AssetEditAction;
|
||||
parameters: RotateParameters;
|
||||
};
|
||||
export type MirrorParameters = {
|
||||
/** Axis to mirror along */
|
||||
axis: MirrorAxis;
|
||||
};
|
||||
export type AssetEditActionMirror = {
|
||||
action: AssetEditAction;
|
||||
parameters: MirrorParameters;
|
||||
};
|
||||
export type AssetEditsDto = {
|
||||
assetId: string;
|
||||
/** list of edits */
|
||||
edits: (AssetEditActionCrop | AssetEditActionRotate | AssetEditActionMirror)[];
|
||||
};
|
||||
export type AssetEditActionListDto = {
|
||||
/** list of edits */
|
||||
edits: (AssetEditActionCrop | AssetEditActionRotate | AssetEditActionMirror)[];
|
||||
};
|
||||
export type AssetMetadataResponseDto = {
|
||||
key: AssetMetadataKey;
|
||||
key: string;
|
||||
updatedAt: string;
|
||||
value: object;
|
||||
};
|
||||
@@ -728,6 +790,7 @@ export type QueuesResponseLegacyDto = {
|
||||
backgroundTask: QueueResponseLegacyDto;
|
||||
backupDatabase: QueueResponseLegacyDto;
|
||||
duplicateDetection: QueueResponseLegacyDto;
|
||||
editor: QueueResponseLegacyDto;
|
||||
faceDetection: QueueResponseLegacyDto;
|
||||
facialRecognition: QueueResponseLegacyDto;
|
||||
library: QueueResponseLegacyDto;
|
||||
@@ -1463,6 +1526,7 @@ export type JobSettingsDto = {
|
||||
};
|
||||
export type SystemConfigJobDto = {
|
||||
backgroundTask: JobSettingsDto;
|
||||
editor: JobSettingsDto;
|
||||
faceDetection: JobSettingsDto;
|
||||
library: JobSettingsDto;
|
||||
metadataExtraction: JobSettingsDto;
|
||||
@@ -2369,6 +2433,9 @@ export function uploadAsset({ key, slug, xImmichChecksum, assetMediaCreateDto }:
|
||||
assetMediaCreateDto: AssetMediaCreateDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: AssetMediaResponseDto;
|
||||
} | {
|
||||
status: 201;
|
||||
data: AssetMediaResponseDto;
|
||||
}>(`/assets${QS.query(QS.explode({
|
||||
@@ -2462,6 +2529,33 @@ export function runAssetJobs({ assetJobsDto }: {
|
||||
body: assetJobsDto
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* Delete asset metadata
|
||||
*/
|
||||
export function deleteBulkAssetMetadata({ assetMetadataBulkDeleteDto }: {
|
||||
assetMetadataBulkDeleteDto: AssetMetadataBulkDeleteDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchText("/assets/metadata", oazapfts.json({
|
||||
...opts,
|
||||
method: "DELETE",
|
||||
body: assetMetadataBulkDeleteDto
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* Upsert asset metadata
|
||||
*/
|
||||
export function updateBulkAssetMetadata({ assetMetadataBulkUpsertDto }: {
|
||||
assetMetadataBulkUpsertDto: AssetMetadataBulkUpsertDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: AssetMetadataBulkResponseDto[];
|
||||
}>("/assets/metadata", oazapfts.json({
|
||||
...opts,
|
||||
method: "PUT",
|
||||
body: assetMetadataBulkUpsertDto
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* Get random assets
|
||||
*/
|
||||
@@ -2530,6 +2624,46 @@ export function updateAsset({ id, updateAssetDto }: {
|
||||
body: updateAssetDto
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* Remove edits from an existing asset
|
||||
*/
|
||||
export function removeAssetEdits({ id }: {
|
||||
id: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchText(`/assets/${encodeURIComponent(id)}/edits`, {
|
||||
...opts,
|
||||
method: "DELETE"
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* Retrieve edits for an existing asset
|
||||
*/
|
||||
export function getAssetEdits({ id }: {
|
||||
id: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: AssetEditsDto;
|
||||
}>(`/assets/${encodeURIComponent(id)}/edits`, {
|
||||
...opts
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* Apply edits to an existing asset
|
||||
*/
|
||||
export function editAsset({ id, assetEditActionListDto }: {
|
||||
id: string;
|
||||
assetEditActionListDto: AssetEditActionListDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: AssetEditsDto;
|
||||
}>(`/assets/${encodeURIComponent(id)}/edits`, oazapfts.json({
|
||||
...opts,
|
||||
method: "PUT",
|
||||
body: assetEditActionListDto
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* Get asset metadata
|
||||
*/
|
||||
@@ -2564,7 +2698,7 @@ export function updateAssetMetadata({ id, assetMetadataUpsertDto }: {
|
||||
*/
|
||||
export function deleteAssetMetadata({ id, key }: {
|
||||
id: string;
|
||||
key: AssetMetadataKey;
|
||||
key: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchText(`/assets/${encodeURIComponent(id)}/metadata/${encodeURIComponent(key)}`, {
|
||||
...opts,
|
||||
@@ -2576,7 +2710,7 @@ export function deleteAssetMetadata({ id, key }: {
|
||||
*/
|
||||
export function getAssetMetadataByKey({ id, key }: {
|
||||
id: string;
|
||||
key: AssetMetadataKey;
|
||||
key: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
@@ -2601,7 +2735,8 @@ export function getAssetOcr({ id }: {
|
||||
/**
|
||||
* Download original asset
|
||||
*/
|
||||
export function downloadAsset({ id, key, slug }: {
|
||||
export function downloadAsset({ edited, id, key, slug }: {
|
||||
edited?: boolean;
|
||||
id: string;
|
||||
key?: string;
|
||||
slug?: string;
|
||||
@@ -2610,6 +2745,7 @@ export function downloadAsset({ id, key, slug }: {
|
||||
status: 200;
|
||||
data: Blob;
|
||||
}>(`/assets/${encodeURIComponent(id)}/original${QS.query(QS.explode({
|
||||
edited,
|
||||
key,
|
||||
slug
|
||||
}))}`, {
|
||||
@@ -2640,7 +2776,8 @@ export function replaceAsset({ id, key, slug, assetMediaReplaceDto }: {
|
||||
/**
|
||||
* View asset thumbnail
|
||||
*/
|
||||
export function viewAsset({ id, key, size, slug }: {
|
||||
export function viewAsset({ edited, id, key, size, slug }: {
|
||||
edited?: boolean;
|
||||
id: string;
|
||||
key?: string;
|
||||
size?: AssetMediaSize;
|
||||
@@ -2650,6 +2787,7 @@ export function viewAsset({ id, key, size, slug }: {
|
||||
status: 200;
|
||||
data: Blob;
|
||||
}>(`/assets/${encodeURIComponent(id)}/thumbnail${QS.query(QS.explode({
|
||||
edited,
|
||||
key,
|
||||
size,
|
||||
slug
|
||||
@@ -5237,6 +5375,10 @@ export enum Permission {
|
||||
AssetUpload = "asset.upload",
|
||||
AssetReplace = "asset.replace",
|
||||
AssetCopy = "asset.copy",
|
||||
AssetDerive = "asset.derive",
|
||||
AssetEditGet = "asset.edit.get",
|
||||
AssetEditCreate = "asset.edit.create",
|
||||
AssetEditDelete = "asset.edit.delete",
|
||||
AlbumCreate = "album.create",
|
||||
AlbumRead = "album.read",
|
||||
AlbumUpdate = "album.update",
|
||||
@@ -5363,9 +5505,6 @@ export enum Permission {
|
||||
AdminSessionRead = "adminSession.read",
|
||||
AdminAuthUnlinkAll = "adminAuth.unlinkAll"
|
||||
}
|
||||
export enum AssetMetadataKey {
|
||||
MobileApp = "mobile-app"
|
||||
}
|
||||
export enum AssetMediaStatus {
|
||||
Created = "created",
|
||||
Replaced = "replaced",
|
||||
@@ -5385,6 +5524,15 @@ export enum AssetJobName {
|
||||
RegenerateThumbnail = "regenerate-thumbnail",
|
||||
TranscodeVideo = "transcode-video"
|
||||
}
|
||||
export enum AssetEditAction {
|
||||
Crop = "crop",
|
||||
Rotate = "rotate",
|
||||
Mirror = "mirror"
|
||||
}
|
||||
export enum MirrorAxis {
|
||||
Horizontal = "horizontal",
|
||||
Vertical = "vertical"
|
||||
}
|
||||
export enum AssetMediaSize {
|
||||
Fullsize = "fullsize",
|
||||
Preview = "preview",
|
||||
@@ -5415,7 +5563,8 @@ export enum QueueName {
|
||||
Notifications = "notifications",
|
||||
BackupDatabase = "backupDatabase",
|
||||
Ocr = "ocr",
|
||||
Workflow = "workflow"
|
||||
Workflow = "workflow",
|
||||
Editor = "editor"
|
||||
}
|
||||
export enum QueueCommand {
|
||||
Start = "start",
|
||||
@@ -5460,6 +5609,7 @@ export enum JobName {
|
||||
AssetDetectFaces = "AssetDetectFaces",
|
||||
AssetDetectDuplicatesQueueAll = "AssetDetectDuplicatesQueueAll",
|
||||
AssetDetectDuplicates = "AssetDetectDuplicates",
|
||||
AssetEditThumbnailGeneration = "AssetEditThumbnailGeneration",
|
||||
AssetEncodeVideoQueueAll = "AssetEncodeVideoQueueAll",
|
||||
AssetEncodeVideo = "AssetEncodeVideo",
|
||||
AssetEmptyTrash = "AssetEmptyTrash",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user