mirror of
https://github.com/immich-app/immich.git
synced 2026-03-19 08:38:36 -07:00
Compare commits
6 Commits
fix/nullab
...
refactor/z
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bec91e9979 | ||
|
|
ddbff0bc3c | ||
|
|
48187e7c71 | ||
|
|
81c5eb1bcd | ||
|
|
1461b0477e | ||
|
|
d6081b081d |
2
.github/workflows/check-openapi.yml
vendored
2
.github/workflows/check-openapi.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check for breaking API changes
|
||||
uses: oasdiff/oasdiff-action/breaking@748daafaf3aac877a36307f842a48d55db938ac8 # v0.0.31
|
||||
uses: oasdiff/oasdiff-action/breaking@65fef71494258f00f911d7a71edb0482c5378899 # v0.0.30
|
||||
with:
|
||||
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
||||
revision: open-api/immich-openapi-specs.json
|
||||
|
||||
80
.github/workflows/check-pr-template.yml
vendored
80
.github/workflows/check-pr-template.yml
vendored
@@ -1,80 +0,0 @@
|
||||
name: Check PR Template
|
||||
|
||||
on:
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers]
|
||||
types: [opened, edited]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
parse:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.pull_request.head.repo.fork == true }}
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
uses_template: ${{ steps.check.outputs.uses_template }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
sparse-checkout: .github/pull_request_template.md
|
||||
sparse-checkout-cone-mode: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check required sections
|
||||
id: check
|
||||
env:
|
||||
BODY: ${{ github.event.pull_request.body }}
|
||||
run: |
|
||||
OK=true
|
||||
while IFS= read -r header; do
|
||||
printf '%s\n' "$BODY" | grep -qF "$header" || OK=false
|
||||
done < <(sed '/<!--/,/-->/d' .github/pull_request_template.md | grep "^## ")
|
||||
echo "uses_template=$OK" >> "$GITHUB_OUTPUT"
|
||||
|
||||
act:
|
||||
runs-on: ubuntu-latest
|
||||
needs: parse
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Close PR
|
||||
if: ${{ needs.parse.outputs.uses_template == 'false' && github.event.pull_request.state != 'closed' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
NODE_ID: ${{ github.event.pull_request.node_id }}
|
||||
run: |
|
||||
gh api graphql \
|
||||
-f prId="$NODE_ID" \
|
||||
-f body="This PR has been automatically closed as the description doesn't follow our template. After you edit it to match the template, the PR will automatically be reopened." \
|
||||
-f query='
|
||||
mutation CommentAndClosePR($prId: ID!, $body: String!) {
|
||||
addComment(input: {
|
||||
subjectId: $prId,
|
||||
body: $body
|
||||
}) {
|
||||
__typename
|
||||
}
|
||||
closePullRequest(input: {
|
||||
pullRequestId: $prId
|
||||
}) {
|
||||
__typename
|
||||
}
|
||||
}'
|
||||
|
||||
- name: Reopen PR (sections now present, PR closed)
|
||||
if: ${{ needs.parse.outputs.uses_template == 'true' && github.event.pull_request.state == 'closed' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
NODE_ID: ${{ github.event.pull_request.node_id }}
|
||||
run: |
|
||||
gh api graphql \
|
||||
-f prId="$NODE_ID" \
|
||||
-f query='
|
||||
mutation ReopenPR($prId: ID!) {
|
||||
reopenPullRequest(input: {
|
||||
pullRequestId: $prId
|
||||
}) {
|
||||
__typename
|
||||
}
|
||||
}'
|
||||
4
.github/workflows/cli.yml
vendored
4
.github/workflows/cli.yml
vendored
@@ -42,10 +42,10 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './cli/.nvmrc'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -57,7 +57,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
|
||||
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
|
||||
uses: github/codeql-action/autobuild@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -83,6 +83,6 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
|
||||
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
with:
|
||||
category: '/language:${{matrix.language}}'
|
||||
|
||||
4
.github/workflows/docs-build.yml
vendored
4
.github/workflows/docs-build.yml
vendored
@@ -67,10 +67,10 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './docs/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
4
.github/workflows/fix-format.yml
vendored
4
.github/workflows/fix-format.yml
vendored
@@ -29,10 +29,10 @@ jobs:
|
||||
persist-credentials: true
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
6
.github/workflows/prepare-release.yml
vendored
6
.github/workflows/prepare-release.yml
vendored
@@ -63,13 +63,13 @@ jobs:
|
||||
ref: main
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@6ee6290f1cbc4156c0bdd66691b2c144ef8df19a # v7.4.0
|
||||
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
4
.github/workflows/sdk.yml
vendored
4
.github/workflows/sdk.yml
vendored
@@ -30,10 +30,10 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
|
||||
# Setup .npmrc file to publish to npm
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './open-api/typescript-sdk/.nvmrc'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
54
.github/workflows/test.yml
vendored
54
.github/workflows/test.yml
vendored
@@ -75,9 +75,9 @@ jobs:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -119,9 +119,9 @@ jobs:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './cli/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -166,9 +166,9 @@ jobs:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './cli/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -208,9 +208,9 @@ jobs:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './web/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -252,9 +252,9 @@ jobs:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './web/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -290,9 +290,9 @@ jobs:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './web/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -338,9 +338,9 @@ jobs:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './e2e/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -385,9 +385,9 @@ jobs:
|
||||
submodules: 'recursive'
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -424,9 +424,9 @@ jobs:
|
||||
submodules: 'recursive'
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './e2e/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -496,9 +496,9 @@ jobs:
|
||||
submodules: 'recursive'
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './e2e/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -620,7 +620,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@6ee6290f1cbc4156c0bdd66691b2c144ef8df19a # v7.4.0
|
||||
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
|
||||
with:
|
||||
python-version: 3.11
|
||||
- name: Install dependencies
|
||||
@@ -661,9 +661,9 @@ jobs:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './.github/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -712,9 +712,9 @@ jobs:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
@@ -774,9 +774,9 @@ jobs:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version-file: './server/.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
@@ -4,7 +4,7 @@ import path from 'node:path';
|
||||
import { setTimeout as sleep } from 'node:timers/promises';
|
||||
import { describe, expect, it, MockedFunction, vi } from 'vitest';
|
||||
|
||||
import { Action, checkBulkUpload, defaults, getSupportedMediaTypes, Reason } from '@immich/sdk';
|
||||
import { AssetRejectReason, AssetUploadAction, checkBulkUpload, defaults, getSupportedMediaTypes } from '@immich/sdk';
|
||||
import createFetchMock from 'vitest-fetch-mock';
|
||||
|
||||
import {
|
||||
@@ -120,7 +120,7 @@ describe('checkForDuplicates', () => {
|
||||
vi.mocked(checkBulkUpload).mockResolvedValue({
|
||||
results: [
|
||||
{
|
||||
action: Action.Accept,
|
||||
action: AssetUploadAction.Accept,
|
||||
id: testFilePath,
|
||||
},
|
||||
],
|
||||
@@ -144,10 +144,10 @@ describe('checkForDuplicates', () => {
|
||||
vi.mocked(checkBulkUpload).mockResolvedValue({
|
||||
results: [
|
||||
{
|
||||
action: Action.Reject,
|
||||
action: AssetUploadAction.Reject,
|
||||
id: testFilePath,
|
||||
assetId: 'fc5621b1-86f6-44a1-9905-403e607df9f5',
|
||||
reason: Reason.Duplicate,
|
||||
reason: AssetRejectReason.Duplicate,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -167,7 +167,7 @@ describe('checkForDuplicates', () => {
|
||||
vi.mocked(checkBulkUpload).mockResolvedValue({
|
||||
results: [
|
||||
{
|
||||
action: Action.Accept,
|
||||
action: AssetUploadAction.Accept,
|
||||
id: testFilePath,
|
||||
},
|
||||
],
|
||||
@@ -187,7 +187,7 @@ describe('checkForDuplicates', () => {
|
||||
mocked.mockResolvedValue({
|
||||
results: [
|
||||
{
|
||||
action: Action.Accept,
|
||||
action: AssetUploadAction.Accept,
|
||||
id: testFilePath,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
Action,
|
||||
AssetBulkUploadCheckItem,
|
||||
AssetBulkUploadCheckResult,
|
||||
AssetMediaResponseDto,
|
||||
AssetMediaStatus,
|
||||
AssetUploadAction,
|
||||
Permission,
|
||||
addAssetsToAlbum,
|
||||
checkBulkUpload,
|
||||
@@ -234,7 +234,7 @@ export const checkForDuplicates = async (files: string[], { concurrency, skipHas
|
||||
const results = response.results as AssetBulkUploadCheckResults;
|
||||
|
||||
for (const { id: filepath, assetId, action } of results) {
|
||||
if (action === Action.Accept) {
|
||||
if (action === AssetUploadAction.Accept) {
|
||||
newFiles.push(filepath);
|
||||
} else {
|
||||
// rejects are always duplicates
|
||||
|
||||
@@ -10,7 +10,6 @@ export enum OAuthClient {
|
||||
export enum OAuthUser {
|
||||
NO_EMAIL = 'no-email',
|
||||
NO_NAME = 'no-name',
|
||||
ID_TOKEN_CLAIMS = 'id-token-claims',
|
||||
WITH_QUOTA = 'with-quota',
|
||||
WITH_USERNAME = 'with-username',
|
||||
WITH_ROLE = 'with-role',
|
||||
@@ -53,25 +52,12 @@ const withDefaultClaims = (sub: string) => ({
|
||||
email_verified: true,
|
||||
});
|
||||
|
||||
const getClaims = (sub: string, use?: string) => {
|
||||
if (sub === OAuthUser.ID_TOKEN_CLAIMS) {
|
||||
return {
|
||||
sub,
|
||||
email: `oauth-${sub}@immich.app`,
|
||||
email_verified: true,
|
||||
name: use === 'id_token' ? 'ID Token User' : 'Userinfo User',
|
||||
};
|
||||
}
|
||||
return claims.find((user) => user.sub === sub) || withDefaultClaims(sub);
|
||||
};
|
||||
const getClaims = (sub: string) => claims.find((user) => user.sub === sub) || withDefaultClaims(sub);
|
||||
|
||||
const setup = async () => {
|
||||
const { privateKey, publicKey } = await generateKeyPair('RS256');
|
||||
|
||||
const redirectUris = [
|
||||
'http://127.0.0.1:2285/auth/login',
|
||||
'https://photos.immich.app/oauth/mobile-redirect',
|
||||
];
|
||||
const redirectUris = ['http://127.0.0.1:2285/auth/login', 'https://photos.immich.app/oauth/mobile-redirect'];
|
||||
const port = 2286;
|
||||
const host = '0.0.0.0';
|
||||
const oidc = new Provider(`http://${host}:${port}`, {
|
||||
@@ -80,10 +66,7 @@ const setup = async () => {
|
||||
console.error(error);
|
||||
ctx.body = 'Internal Server Error';
|
||||
},
|
||||
findAccount: (ctx, sub) => ({
|
||||
accountId: sub,
|
||||
claims: (use) => getClaims(sub, use),
|
||||
}),
|
||||
findAccount: (ctx, sub) => ({ accountId: sub, claims: () => getClaims(sub) }),
|
||||
scopes: ['openid', 'email', 'profile'],
|
||||
claims: {
|
||||
openid: ['sub'],
|
||||
@@ -111,7 +94,6 @@ const setup = async () => {
|
||||
state: 'oidc.state',
|
||||
},
|
||||
},
|
||||
conformIdTokenClaims: false,
|
||||
pkce: {
|
||||
required: () => false,
|
||||
},
|
||||
@@ -143,10 +125,7 @@ const setup = async () => {
|
||||
],
|
||||
});
|
||||
|
||||
const onStart = () =>
|
||||
console.log(
|
||||
`[e2e-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();
|
||||
};
|
||||
|
||||
@@ -95,8 +95,8 @@ describe('/asset', () => {
|
||||
utils.createAsset(user1.accessToken),
|
||||
utils.createAsset(user1.accessToken, {
|
||||
isFavorite: true,
|
||||
fileCreatedAt: yesterday.toISO(),
|
||||
fileModifiedAt: yesterday.toISO(),
|
||||
fileCreatedAt: yesterday.toUTC().toISO(),
|
||||
fileModifiedAt: yesterday.toUTC().toISO(),
|
||||
assetData: { filename: 'example.mp4' },
|
||||
}),
|
||||
utils.createAsset(user1.accessToken),
|
||||
@@ -435,7 +435,8 @@ describe('/asset', () => {
|
||||
it('should require access', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.put(`/assets/${user2Assets[0].id}`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||
.send({});
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.noPermission);
|
||||
});
|
||||
|
||||
@@ -110,7 +110,7 @@ describe('/libraries', () => {
|
||||
});
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(["All importPaths's elements must be unique"]));
|
||||
expect(body).toEqual(errorDto.badRequest(['[importPaths] Array must have unique items']));
|
||||
});
|
||||
|
||||
it('should not create an external library with duplicate exclusion patterns', async () => {
|
||||
@@ -125,7 +125,7 @@ describe('/libraries', () => {
|
||||
});
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(["All exclusionPatterns's elements must be unique"]));
|
||||
expect(body).toEqual(errorDto.badRequest(['[exclusionPatterns] Array must have unique items']));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -157,7 +157,7 @@ describe('/libraries', () => {
|
||||
.send({ name: '' });
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['name should not be empty']));
|
||||
expect(body).toEqual(errorDto.badRequest(['[name] Too small: expected string to have >=1 characters']));
|
||||
});
|
||||
|
||||
it('should change the import paths', async () => {
|
||||
@@ -181,7 +181,7 @@ describe('/libraries', () => {
|
||||
.send({ importPaths: [''] });
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['each value in importPaths should not be empty']));
|
||||
expect(body).toEqual(errorDto.badRequest(['[importPaths] Array items must not be empty']));
|
||||
});
|
||||
|
||||
it('should reject duplicate import paths', async () => {
|
||||
@@ -191,7 +191,7 @@ describe('/libraries', () => {
|
||||
.send({ importPaths: ['/path', '/path'] });
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(["All importPaths's elements must be unique"]));
|
||||
expect(body).toEqual(errorDto.badRequest(['[importPaths] Array must have unique items']));
|
||||
});
|
||||
|
||||
it('should change the exclusion pattern', async () => {
|
||||
@@ -215,7 +215,7 @@ describe('/libraries', () => {
|
||||
.send({ exclusionPatterns: ['**/*.jpg', '**/*.jpg'] });
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(["All exclusionPatterns's elements must be unique"]));
|
||||
expect(body).toEqual(errorDto.badRequest(['[exclusionPatterns] Array must have unique items']));
|
||||
});
|
||||
|
||||
it('should reject an empty exclusion pattern', async () => {
|
||||
@@ -225,7 +225,7 @@ describe('/libraries', () => {
|
||||
.send({ exclusionPatterns: [''] });
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['each value in exclusionPatterns should not be empty']));
|
||||
expect(body).toEqual(errorDto.badRequest(['[exclusionPatterns] Array items must not be empty']));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ describe('/map', () => {
|
||||
.get('/map/reverse-geocode?lon=123')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['lat must be a number between -90 and 90']));
|
||||
expect(body).toEqual(errorDto.badRequest(['[lat] Invalid input: expected number, received NaN']));
|
||||
});
|
||||
|
||||
it('should throw an error if a lat is not a number', async () => {
|
||||
@@ -117,7 +117,7 @@ describe('/map', () => {
|
||||
.get('/map/reverse-geocode?lat=abc&lon=123.456')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['lat must be a number between -90 and 90']));
|
||||
expect(body).toEqual(errorDto.badRequest(['[lat] Invalid input: expected number, received NaN']));
|
||||
});
|
||||
|
||||
it('should throw an error if a lat is out of range', async () => {
|
||||
@@ -125,7 +125,7 @@ describe('/map', () => {
|
||||
.get('/map/reverse-geocode?lat=91&lon=123.456')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['lat must be a number between -90 and 90']));
|
||||
expect(body).toEqual(errorDto.badRequest(['[lat] Too big: expected number to be <=90']));
|
||||
});
|
||||
|
||||
it('should throw an error if a lon is not provided', async () => {
|
||||
@@ -133,7 +133,7 @@ describe('/map', () => {
|
||||
.get('/map/reverse-geocode?lat=75')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['lon must be a number between -180 and 180']));
|
||||
expect(body).toEqual(errorDto.badRequest(['[lon] Invalid input: expected number, received NaN']));
|
||||
});
|
||||
|
||||
const reverseGeocodeTestCases = [
|
||||
|
||||
@@ -101,7 +101,7 @@ describe(`/oauth`, () => {
|
||||
it(`should throw an error if a redirect uri is not provided`, async () => {
|
||||
const { status, body } = await request(app).post('/oauth/authorize').send({});
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['redirectUri must be a string', 'redirectUri should not be empty']));
|
||||
expect(body).toEqual(errorDto.badRequest(['[redirectUri] Invalid input: expected string, received undefined']));
|
||||
});
|
||||
|
||||
it('should return a redirect uri', async () => {
|
||||
@@ -123,13 +123,13 @@ describe(`/oauth`, () => {
|
||||
it(`should throw an error if a url is not provided`, async () => {
|
||||
const { status, body } = await request(app).post('/oauth/callback').send({});
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['url must be a string', 'url should not be empty']));
|
||||
expect(body).toEqual(errorDto.badRequest(['[url] Invalid input: expected string, received undefined']));
|
||||
});
|
||||
|
||||
it(`should throw an error if the url is empty`, async () => {
|
||||
const { status, body } = await request(app).post('/oauth/callback').send({ url: '' });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['url should not be empty']));
|
||||
expect(body).toEqual(errorDto.badRequest(['[url] Too small: expected string to have >=1 characters']));
|
||||
});
|
||||
|
||||
it(`should throw an error if the state is not provided`, async () => {
|
||||
@@ -380,23 +380,4 @@ describe(`/oauth`, () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('idTokenClaims', () => {
|
||||
it('should use claims from the ID token if IDP includes them', async () => {
|
||||
await setupOAuth(admin.accessToken, {
|
||||
enabled: true,
|
||||
clientId: OAuthClient.DEFAULT,
|
||||
clientSecret: OAuthClient.DEFAULT,
|
||||
});
|
||||
const callbackParams = await loginWithOAuth(OAuthUser.ID_TOKEN_CLAIMS);
|
||||
const { status, body } = await request(app).post('/oauth/callback').send(callbackParams);
|
||||
expect(status).toBe(201);
|
||||
expect(body).toMatchObject({
|
||||
accessToken: expect.any(String),
|
||||
name: 'ID Token User',
|
||||
userEmail: 'oauth-id-token-claims@immich.app',
|
||||
userId: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -309,7 +309,7 @@ describe('/tags', () => {
|
||||
.get(`/tags/${uuidDto.invalid}`)
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
|
||||
expect(body).toEqual(errorDto.badRequest(['[id] Invalid UUID']));
|
||||
});
|
||||
|
||||
it('should get tag details', async () => {
|
||||
@@ -427,7 +427,7 @@ describe('/tags', () => {
|
||||
.delete(`/tags/${uuidDto.invalid}`)
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
|
||||
expect(body).toEqual(errorDto.badRequest(['[id] Invalid UUID']));
|
||||
});
|
||||
|
||||
it('should delete a tag', async () => {
|
||||
|
||||
@@ -287,7 +287,8 @@ describe('/admin/users', () => {
|
||||
it('should delete user', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.delete(`/admin/users/${userToDelete.userId}`)
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send({});
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body).toMatchObject({
|
||||
|
||||
@@ -178,7 +178,9 @@ describe('/users', () => {
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['download.archiveSize must be an integer number']));
|
||||
expect(body).toEqual(
|
||||
errorDto.badRequest(['[download.archiveSize] Invalid input: expected int, received number']),
|
||||
);
|
||||
});
|
||||
|
||||
it('should update download archive size', async () => {
|
||||
@@ -204,7 +206,9 @@ describe('/users', () => {
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['download.includeEmbeddedVideos must be a boolean value']));
|
||||
expect(body).toEqual(
|
||||
errorDto.badRequest(['[download.includeEmbeddedVideos] Invalid input: expected boolean, received number']),
|
||||
);
|
||||
});
|
||||
|
||||
it('should update download include embedded videos', async () => {
|
||||
|
||||
@@ -77,7 +77,7 @@ export function generateAsset(
|
||||
latitude: hasGPS ? faker.location.latitude() : null,
|
||||
longitude: hasGPS ? faker.location.longitude() : null,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
stack: null,
|
||||
stack: undefined,
|
||||
fileSizeInByte: faker.number.int({ min: 510, max: 5_000_000 }),
|
||||
checksum: faker.string.alphanumeric({ length: 5 }),
|
||||
};
|
||||
|
||||
@@ -334,7 +334,7 @@ export function toAssetResponseDto(asset: MockTimelineAsset, owner?: UserRespons
|
||||
isArchived: false,
|
||||
isTrashed: asset.isTrashed,
|
||||
visibility: asset.visibility,
|
||||
duration: asset.duration,
|
||||
duration: asset.duration || '0:00:00.00000',
|
||||
exifInfo,
|
||||
livePhotoVideoId: asset.livePhotoVideoId,
|
||||
tags: [],
|
||||
|
||||
@@ -52,7 +52,7 @@ export type MockTimelineAsset = {
|
||||
latitude: number | null;
|
||||
longitude: number | null;
|
||||
visibility: AssetVisibility;
|
||||
stack: null;
|
||||
stack: undefined;
|
||||
checksum: string;
|
||||
fileSizeInByte: number;
|
||||
};
|
||||
|
||||
@@ -69,7 +69,7 @@ export const createMockStackAsset = (ownerId: string): AssetResponseDto => {
|
||||
tags: [],
|
||||
people: [],
|
||||
unassignedFaces: [],
|
||||
stack: null,
|
||||
stack: undefined,
|
||||
isOffline: false,
|
||||
hasMetadata: true,
|
||||
duplicateId: null,
|
||||
|
||||
@@ -1651,7 +1651,6 @@
|
||||
"only_favorites": "Only favorites",
|
||||
"open": "Open",
|
||||
"open_calendar": "Open calendar",
|
||||
"open_in_browser": "Open in browser",
|
||||
"open_in_map_view": "Open in map view",
|
||||
"open_in_openstreetmap": "Open in OpenStreetMap",
|
||||
"open_the_search_filters": "Open the search filters",
|
||||
|
||||
@@ -113,8 +113,6 @@ dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
implementation 'org.chromium.net:cronet-embedded:143.7445.0'
|
||||
implementation("androidx.media3:media3-datasource-okhttp:1.9.2")
|
||||
implementation("androidx.media3:media3-datasource-cronet:1.9.2")
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
|
||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||
implementation "androidx.concurrent:concurrent-futures:$concurrent_version"
|
||||
|
||||
@@ -12,7 +12,6 @@ import app.alextran.immich.connectivity.ConnectivityApiImpl
|
||||
import app.alextran.immich.core.HttpClientManager
|
||||
import app.alextran.immich.core.ImmichPlugin
|
||||
import app.alextran.immich.core.NetworkApiPlugin
|
||||
import me.albemala.native_video_player.NativeVideoPlayerPlugin
|
||||
import app.alextran.immich.images.LocalImageApi
|
||||
import app.alextran.immich.images.LocalImagesImpl
|
||||
import app.alextran.immich.images.RemoteImageApi
|
||||
@@ -32,7 +31,6 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
companion object {
|
||||
fun registerPlugins(ctx: Context, flutterEngine: FlutterEngine) {
|
||||
HttpClientManager.initialize(ctx)
|
||||
NativeVideoPlayerPlugin.dataSourceFactory = HttpClientManager::createDataSourceFactory
|
||||
flutterEngine.plugins.add(NetworkApiPlugin())
|
||||
|
||||
val messenger = flutterEngine.dartExecutor.binaryMessenger
|
||||
|
||||
@@ -3,13 +3,7 @@ package app.alextran.immich.core
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.security.KeyChain
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.core.content.edit
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.datasource.DataSource
|
||||
import androidx.media3.datasource.ResolvingDataSource
|
||||
import androidx.media3.datasource.cronet.CronetDataSource
|
||||
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
||||
import app.alextran.immich.BuildConfig
|
||||
import app.alextran.immich.NativeBuffer
|
||||
import okhttp3.Cache
|
||||
@@ -22,22 +16,15 @@ import okhttp3.Headers
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import org.chromium.net.CronetEngine
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.net.Authenticator
|
||||
import java.net.CookieHandler
|
||||
import java.net.PasswordAuthentication
|
||||
import java.net.Socket
|
||||
import java.net.URI
|
||||
import java.security.KeyStore
|
||||
import java.security.Principal
|
||||
import java.security.PrivateKey
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import javax.net.ssl.SSLContext
|
||||
@@ -69,7 +56,6 @@ private enum class AuthCookie(val cookieName: String, val httpOnly: Boolean) {
|
||||
*/
|
||||
object HttpClientManager {
|
||||
private const val CACHE_SIZE_BYTES = 100L * 1024 * 1024 // 100MiB
|
||||
const val MEDIA_CACHE_SIZE_BYTES = 1024L * 1024 * 1024 // 1GiB
|
||||
private const val KEEP_ALIVE_CONNECTIONS = 10
|
||||
private const val KEEP_ALIVE_DURATION_MINUTES = 5L
|
||||
private const val MAX_REQUESTS_PER_HOST = 64
|
||||
@@ -81,11 +67,6 @@ object HttpClientManager {
|
||||
private lateinit var appContext: Context
|
||||
private lateinit var prefs: SharedPreferences
|
||||
|
||||
var cronetEngine: CronetEngine? = null
|
||||
private set
|
||||
private lateinit var cronetStorageDir: File
|
||||
val cronetExecutor: ExecutorService = Executors.newFixedThreadPool(4)
|
||||
|
||||
private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
|
||||
|
||||
var keyChainAlias: String? = null
|
||||
@@ -108,25 +89,6 @@ object HttpClientManager {
|
||||
keyChainAlias = prefs.getString(PREFS_CERT_ALIAS, null)
|
||||
|
||||
cookieJar.init(prefs)
|
||||
System.setProperty("http.agent", USER_AGENT)
|
||||
Authenticator.setDefault(object : Authenticator() {
|
||||
override fun getPasswordAuthentication(): PasswordAuthentication? {
|
||||
val url = requestingURL ?: return null
|
||||
if (url.userInfo.isNullOrEmpty()) return null
|
||||
val parts = url.userInfo.split(":", limit = 2)
|
||||
return PasswordAuthentication(parts[0], parts.getOrElse(1) { "" }.toCharArray())
|
||||
}
|
||||
})
|
||||
CookieHandler.setDefault(object : CookieHandler() {
|
||||
override fun get(uri: URI, requestHeaders: Map<String, List<String>>): Map<String, List<String>> {
|
||||
val httpUrl = uri.toString().toHttpUrlOrNull() ?: return emptyMap()
|
||||
val cookies = cookieJar.loadForRequest(httpUrl)
|
||||
if (cookies.isEmpty()) return emptyMap()
|
||||
return mapOf("Cookie" to listOf(cookies.joinToString("; ") { "${it.name}=${it.value}" }))
|
||||
}
|
||||
|
||||
override fun put(uri: URI, responseHeaders: Map<String, List<String>>) {}
|
||||
})
|
||||
|
||||
val savedHeaders = prefs.getString(PREFS_HEADERS, null)
|
||||
if (savedHeaders != null) {
|
||||
@@ -145,10 +107,6 @@ object HttpClientManager {
|
||||
|
||||
val cacheDir = File(File(context.cacheDir, "okhttp"), "api")
|
||||
client = build(cacheDir)
|
||||
|
||||
cronetStorageDir = File(context.cacheDir, "cronet").apply { mkdirs() }
|
||||
cronetEngine = buildCronetEngine()
|
||||
|
||||
initialized = true
|
||||
}
|
||||
}
|
||||
@@ -265,53 +223,6 @@ object HttpClientManager {
|
||||
?.joinToString("; ") { "${it.name}=${it.value}" }
|
||||
}
|
||||
|
||||
fun getAuthHeaders(url: String): Map<String, String> {
|
||||
val result = mutableMapOf<String, String>()
|
||||
headers.forEach { (key, value) -> result[key] = value }
|
||||
loadCookieHeader(url)?.let { result["Cookie"] = it }
|
||||
url.toHttpUrlOrNull()?.let { httpUrl ->
|
||||
if (httpUrl.username.isNotEmpty()) {
|
||||
result["Authorization"] = Credentials.basic(httpUrl.username, httpUrl.password)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun rebuildCronetEngine(): CronetEngine {
|
||||
val old = cronetEngine!!
|
||||
cronetEngine = buildCronetEngine()
|
||||
return old
|
||||
}
|
||||
|
||||
val cronetStoragePath: File get() = cronetStorageDir
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
fun createDataSourceFactory(headers: Map<String, String>): DataSource.Factory {
|
||||
return if (isMtls) {
|
||||
OkHttpDataSource.Factory(client.newBuilder().cache(null).build())
|
||||
} else {
|
||||
ResolvingDataSource.Factory(
|
||||
CronetDataSource.Factory(cronetEngine!!, cronetExecutor)
|
||||
) { dataSpec ->
|
||||
val newHeaders = dataSpec.httpRequestHeaders.toMutableMap()
|
||||
newHeaders.putAll(getAuthHeaders(dataSpec.uri.toString()))
|
||||
newHeaders["Cache-Control"] = "no-store"
|
||||
dataSpec.buildUpon().setHttpRequestHeaders(newHeaders).build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildCronetEngine(): CronetEngine {
|
||||
return CronetEngine.Builder(appContext)
|
||||
.enableHttp2(true)
|
||||
.enableQuic(true)
|
||||
.enableBrotli(true)
|
||||
.setStoragePath(cronetStorageDir.absolutePath)
|
||||
.setUserAgent(USER_AGENT)
|
||||
.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK, MEDIA_CACHE_SIZE_BYTES)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun build(cacheDir: File): OkHttpClient {
|
||||
val connectionPool = ConnectionPool(
|
||||
maxIdleConnections = KEEP_ALIVE_CONNECTIONS,
|
||||
|
||||
@@ -7,6 +7,7 @@ import app.alextran.immich.INITIAL_BUFFER_SIZE
|
||||
import app.alextran.immich.NativeBuffer
|
||||
import app.alextran.immich.NativeByteBuffer
|
||||
import app.alextran.immich.core.HttpClientManager
|
||||
import app.alextran.immich.core.USER_AGENT
|
||||
import kotlinx.coroutines.*
|
||||
import okhttp3.Cache
|
||||
import okhttp3.Call
|
||||
@@ -14,6 +15,9 @@ import okhttp3.Callback
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.Credentials
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.chromium.net.CronetEngine
|
||||
import org.chromium.net.CronetException
|
||||
import org.chromium.net.UrlRequest
|
||||
import org.chromium.net.UrlResponseInfo
|
||||
@@ -27,6 +31,10 @@ import java.nio.file.Path
|
||||
import java.nio.file.SimpleFileVisitor
|
||||
import java.nio.file.attribute.BasicFileAttributes
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
|
||||
private const val CACHE_SIZE_BYTES = 1024L * 1024 * 1024
|
||||
|
||||
private class RemoteRequest(val cancellationSignal: CancellationSignal)
|
||||
|
||||
@@ -93,6 +101,7 @@ class RemoteImagesImpl(context: Context) : RemoteImageApi {
|
||||
}
|
||||
|
||||
private object ImageFetcherManager {
|
||||
private lateinit var appContext: Context
|
||||
private lateinit var cacheDir: File
|
||||
private lateinit var fetcher: ImageFetcher
|
||||
private var initialized = false
|
||||
@@ -101,6 +110,7 @@ private object ImageFetcherManager {
|
||||
if (initialized) return
|
||||
synchronized(this) {
|
||||
if (initialized) return
|
||||
appContext = context.applicationContext
|
||||
cacheDir = context.cacheDir
|
||||
fetcher = build()
|
||||
HttpClientManager.addClientChangedListener(::invalidate)
|
||||
@@ -133,7 +143,7 @@ private object ImageFetcherManager {
|
||||
return if (HttpClientManager.isMtls) {
|
||||
OkHttpImageFetcher.create(cacheDir)
|
||||
} else {
|
||||
CronetImageFetcher()
|
||||
CronetImageFetcher(appContext, cacheDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,11 +161,19 @@ private sealed interface ImageFetcher {
|
||||
fun clearCache(onCleared: (Result<Long>) -> Unit)
|
||||
}
|
||||
|
||||
private class CronetImageFetcher : ImageFetcher {
|
||||
private class CronetImageFetcher(context: Context, cacheDir: File) : ImageFetcher {
|
||||
private val ctx = context
|
||||
private var engine: CronetEngine
|
||||
private val executor = Executors.newFixedThreadPool(4)
|
||||
private val stateLock = Any()
|
||||
private var activeCount = 0
|
||||
private var draining = false
|
||||
private var onCacheCleared: ((Result<Long>) -> Unit)? = null
|
||||
private val storageDir = File(cacheDir, "cronet").apply { mkdirs() }
|
||||
|
||||
init {
|
||||
engine = build(context)
|
||||
}
|
||||
|
||||
override fun fetch(
|
||||
url: String,
|
||||
@@ -172,16 +190,30 @@ private class CronetImageFetcher : ImageFetcher {
|
||||
}
|
||||
|
||||
val callback = FetchCallback(onSuccess, onFailure, ::onComplete)
|
||||
val requestBuilder = HttpClientManager.cronetEngine!!
|
||||
.newUrlRequestBuilder(url, callback, HttpClientManager.cronetExecutor)
|
||||
HttpClientManager.getAuthHeaders(url).forEach { (key, value) ->
|
||||
requestBuilder.addHeader(key, value)
|
||||
val requestBuilder = engine.newUrlRequestBuilder(url, callback, executor)
|
||||
HttpClientManager.headers.forEach { (key, value) -> requestBuilder.addHeader(key, value) }
|
||||
HttpClientManager.loadCookieHeader(url)?.let { requestBuilder.addHeader("Cookie", it) }
|
||||
url.toHttpUrlOrNull()?.let { httpUrl ->
|
||||
if (httpUrl.username.isNotEmpty()) {
|
||||
requestBuilder.addHeader("Authorization", Credentials.basic(httpUrl.username, httpUrl.password))
|
||||
}
|
||||
}
|
||||
val request = requestBuilder.build()
|
||||
signal.setOnCancelListener(request::cancel)
|
||||
request.start()
|
||||
}
|
||||
|
||||
private fun build(ctx: Context): CronetEngine {
|
||||
return CronetEngine.Builder(ctx)
|
||||
.enableHttp2(true)
|
||||
.enableQuic(true)
|
||||
.enableBrotli(true)
|
||||
.setStoragePath(storageDir.absolutePath)
|
||||
.setUserAgent(USER_AGENT)
|
||||
.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK, CACHE_SIZE_BYTES)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun onComplete() {
|
||||
val didDrain = synchronized(stateLock) {
|
||||
activeCount--
|
||||
@@ -204,16 +236,19 @@ private class CronetImageFetcher : ImageFetcher {
|
||||
}
|
||||
|
||||
private fun onDrained() {
|
||||
engine.shutdown()
|
||||
val onCacheCleared = synchronized(stateLock) {
|
||||
val onCacheCleared = onCacheCleared
|
||||
this.onCacheCleared = null
|
||||
onCacheCleared
|
||||
}
|
||||
if (onCacheCleared != null) {
|
||||
val oldEngine = HttpClientManager.rebuildCronetEngine()
|
||||
oldEngine.shutdown()
|
||||
if (onCacheCleared == null) {
|
||||
executor.shutdown()
|
||||
} else {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val result = runCatching { deleteFolderAndGetSize(HttpClientManager.cronetStoragePath.toPath()) }
|
||||
val result = runCatching { deleteFolderAndGetSize(storageDir.toPath()) }
|
||||
// Cronet is very good at self-repair, so it shouldn't fail here regardless of clear result
|
||||
engine = build(ctx)
|
||||
synchronized(stateLock) { draining = false }
|
||||
onCacheCleared(result)
|
||||
}
|
||||
@@ -340,7 +375,7 @@ private class OkHttpImageFetcher private constructor(
|
||||
val dir = File(cacheDir, "okhttp")
|
||||
|
||||
val client = HttpClientManager.getClient().newBuilder()
|
||||
.cache(Cache(File(dir, "thumbnails"), HttpClientManager.MEDIA_CACHE_SIZE_BYTES))
|
||||
.cache(Cache(File(dir, "thumbnails"), CACHE_SIZE_BYTES))
|
||||
.build()
|
||||
|
||||
return OkHttpImageFetcher(client)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import BackgroundTasks
|
||||
import Flutter
|
||||
import native_video_player
|
||||
import network_info_plus
|
||||
import path_provider_foundation
|
||||
import permission_handler_apple
|
||||
@@ -19,8 +18,6 @@ import UIKit
|
||||
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
|
||||
}
|
||||
|
||||
SwiftNativeVideoPlayerPlugin.cookieStorage = URLSessionManager.cookieStorage
|
||||
URLSessionManager.patchBackgroundDownloader()
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
|
||||
AppDelegate.registerPlugins(with: controller.engine, controller: controller)
|
||||
|
||||
@@ -51,7 +51,7 @@ class URLSessionManager: NSObject {
|
||||
diskCapacity: 1024 * 1024 * 1024,
|
||||
directory: cacheDir
|
||||
)
|
||||
static let userAgent: String = {
|
||||
private static let userAgent: String = {
|
||||
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "unknown"
|
||||
return "Immich_iOS_\(version)"
|
||||
}()
|
||||
@@ -158,49 +158,6 @@ class URLSessionManager: NSObject {
|
||||
|
||||
return URLSession(configuration: config, delegate: delegate, delegateQueue: nil)
|
||||
}
|
||||
|
||||
/// Patches background_downloader's URLSession to use shared auth configuration.
|
||||
/// Must be called before background_downloader creates its session (i.e. early in app startup).
|
||||
static func patchBackgroundDownloader() {
|
||||
// Swizzle URLSessionConfiguration.background(withIdentifier:) to inject shared config
|
||||
let originalSel = NSSelectorFromString("backgroundSessionConfigurationWithIdentifier:")
|
||||
let swizzledSel = #selector(URLSessionConfiguration.immich_background(withIdentifier:))
|
||||
if let original = class_getClassMethod(URLSessionConfiguration.self, originalSel),
|
||||
let swizzled = class_getClassMethod(URLSessionConfiguration.self, swizzledSel) {
|
||||
method_exchangeImplementations(original, swizzled)
|
||||
}
|
||||
|
||||
// Add auth challenge handling to background_downloader's UrlSessionDelegate
|
||||
guard let targetClass = NSClassFromString("background_downloader.UrlSessionDelegate") else { return }
|
||||
|
||||
let sessionBlock: @convention(block) (AnyObject, URLSession, URLAuthenticationChallenge,
|
||||
@escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void
|
||||
= { _, session, challenge, completion in
|
||||
URLSessionManager.shared.delegate.handleChallenge(session, challenge, completion)
|
||||
}
|
||||
class_replaceMethod(targetClass,
|
||||
NSSelectorFromString("URLSession:didReceiveChallenge:completionHandler:"),
|
||||
imp_implementationWithBlock(sessionBlock), "v@:@@@?")
|
||||
|
||||
let taskBlock: @convention(block) (AnyObject, URLSession, URLSessionTask, URLAuthenticationChallenge,
|
||||
@escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void
|
||||
= { _, session, task, challenge, completion in
|
||||
URLSessionManager.shared.delegate.handleChallenge(session, challenge, completion, task: task)
|
||||
}
|
||||
class_replaceMethod(targetClass,
|
||||
NSSelectorFromString("URLSession:task:didReceiveChallenge:completionHandler:"),
|
||||
imp_implementationWithBlock(taskBlock), "v@:@@@@?")
|
||||
}
|
||||
}
|
||||
|
||||
private extension URLSessionConfiguration {
|
||||
@objc dynamic class func immich_background(withIdentifier id: String) -> URLSessionConfiguration {
|
||||
// After swizzle, this calls the original implementation
|
||||
let config = immich_background(withIdentifier: id)
|
||||
config.httpCookieStorage = URLSessionManager.cookieStorage
|
||||
config.httpAdditionalHeaders = ["User-Agent": URLSessionManager.userAgent]
|
||||
return config
|
||||
}
|
||||
}
|
||||
|
||||
class URLSessionManagerDelegate: NSObject, URLSessionTaskDelegate, URLSessionWebSocketDelegate {
|
||||
@@ -211,7 +168,7 @@ class URLSessionManagerDelegate: NSObject, URLSessionTaskDelegate, URLSessionWeb
|
||||
) {
|
||||
handleChallenge(session, challenge, completionHandler)
|
||||
}
|
||||
|
||||
|
||||
func urlSession(
|
||||
_ session: URLSession,
|
||||
task: URLSessionTask,
|
||||
@@ -220,7 +177,7 @@ class URLSessionManagerDelegate: NSObject, URLSessionTaskDelegate, URLSessionWeb
|
||||
) {
|
||||
handleChallenge(session, challenge, completionHandler, task: task)
|
||||
}
|
||||
|
||||
|
||||
func handleChallenge(
|
||||
_ session: URLSession,
|
||||
_ challenge: URLAuthenticationChallenge,
|
||||
@@ -233,7 +190,7 @@ class URLSessionManagerDelegate: NSObject, URLSessionTaskDelegate, URLSessionWeb
|
||||
default: completionHandler(.performDefaultHandling, nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func handleClientCertificate(
|
||||
_ session: URLSession,
|
||||
completion: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
|
||||
@@ -243,7 +200,7 @@ class URLSessionManagerDelegate: NSObject, URLSessionTaskDelegate, URLSessionWeb
|
||||
kSecAttrLabel as String: CLIENT_CERT_LABEL,
|
||||
kSecReturnRef as String: true,
|
||||
]
|
||||
|
||||
|
||||
var item: CFTypeRef?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
||||
if status == errSecSuccess, let identity = item {
|
||||
@@ -257,7 +214,7 @@ class URLSessionManagerDelegate: NSObject, URLSessionTaskDelegate, URLSessionWeb
|
||||
}
|
||||
completion(.performDefaultHandling, nil)
|
||||
}
|
||||
|
||||
|
||||
private func handleBasicAuth(
|
||||
_ session: URLSession,
|
||||
task: URLSessionTask?,
|
||||
|
||||
@@ -69,7 +69,7 @@ extension on AssetResponseDto {
|
||||
api.AssetVisibility.locked => AssetVisibility.locked,
|
||||
_ => AssetVisibility.timeline,
|
||||
},
|
||||
durationInSeconds: duration?.toDuration()?.inSeconds ?? 0,
|
||||
durationInSeconds: duration.toDuration()?.inSeconds ?? 0,
|
||||
height: height?.toInt(),
|
||||
width: width?.toInt(),
|
||||
isFavorite: isFavorite,
|
||||
|
||||
@@ -80,12 +80,14 @@ Future<void> _processCloudIdMappingsInBatches(
|
||||
AssetMetadataBulkUpsertItemDto(
|
||||
assetId: mapping.remoteAssetId,
|
||||
key: kMobileMetadataKey,
|
||||
value: RemoteAssetMobileAppMetadata(
|
||||
cloudId: mapping.localAsset.cloudId,
|
||||
createdAt: mapping.localAsset.createdAt.toIso8601String(),
|
||||
adjustmentTime: mapping.localAsset.adjustmentTime?.toIso8601String(),
|
||||
latitude: mapping.localAsset.latitude?.toString(),
|
||||
longitude: mapping.localAsset.longitude?.toString(),
|
||||
value: Map<String, Object>.from(
|
||||
RemoteAssetMobileAppMetadata(
|
||||
cloudId: mapping.localAsset.cloudId,
|
||||
createdAt: mapping.localAsset.createdAt.toIso8601String(),
|
||||
adjustmentTime: mapping.localAsset.adjustmentTime?.toIso8601String(),
|
||||
latitude: mapping.localAsset.latitude?.toString(),
|
||||
longitude: mapping.localAsset.longitude?.toString(),
|
||||
).toJson(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ class Asset {
|
||||
fileCreatedAt = remote.fileCreatedAt,
|
||||
fileModifiedAt = remote.fileModifiedAt,
|
||||
updatedAt = remote.updatedAt,
|
||||
durationInSeconds = remote.duration?.toDuration()?.inSeconds ?? 0,
|
||||
durationInSeconds = remote.duration.toDuration()?.inSeconds ?? 0,
|
||||
type = remote.type.toAssetType(),
|
||||
fileName = remote.originalFileName,
|
||||
height = remote.exifInfo?.exifImageHeight?.toInt(),
|
||||
|
||||
@@ -14,13 +14,13 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
Future<void> performArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async {
|
||||
if (!context.mounted) return;
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).archive(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
if (source == ActionSource.viewer) {
|
||||
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
||||
}
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).archive(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
final successMessage = 'archive_action_prompt'.t(context: context, args: {'count': result.count.toString()});
|
||||
|
||||
if (context.mounted) {
|
||||
|
||||
@@ -57,13 +57,13 @@ class DeleteActionButton extends ConsumerWidget {
|
||||
if (confirm != true) return;
|
||||
}
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).trashRemoteAndDeleteLocal(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
if (source == ActionSource.viewer) {
|
||||
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
||||
}
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).trashRemoteAndDeleteLocal(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
final successMessage = 'delete_action_prompt'.t(context: context, args: {'count': result.count.toString()});
|
||||
|
||||
if (context.mounted) {
|
||||
|
||||
@@ -35,13 +35,13 @@ class DeletePermanentActionButton extends ConsumerWidget {
|
||||
false;
|
||||
if (!confirm) return;
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).deleteRemoteAndLocal(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
if (source == ActionSource.viewer) {
|
||||
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
||||
}
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).deleteRemoteAndLocal(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
final successMessage = 'delete_permanently_action_prompt'.t(
|
||||
context: context,
|
||||
args: {'count': result.count.toString()},
|
||||
|
||||
@@ -14,13 +14,13 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
Future<void> performMoveToLockFolderAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async {
|
||||
if (!context.mounted) return;
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).moveToLockFolder(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
if (source == ActionSource.viewer) {
|
||||
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
||||
}
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).moveToLockFolder(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
final successMessage = 'move_to_lock_folder_action_prompt'.t(
|
||||
context: context,
|
||||
args: {'count': result.count.toString()},
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class OpenInBrowserActionButton extends ConsumerWidget {
|
||||
final String remoteId;
|
||||
final TimelineOrigin origin;
|
||||
final bool iconOnly;
|
||||
final bool menuItem;
|
||||
final Color? iconColor;
|
||||
|
||||
const OpenInBrowserActionButton({
|
||||
super.key,
|
||||
required this.remoteId,
|
||||
required this.origin,
|
||||
this.iconOnly = false,
|
||||
this.menuItem = false,
|
||||
this.iconColor,
|
||||
});
|
||||
|
||||
void _onTap() async {
|
||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint).replaceFirst('/api', '');
|
||||
|
||||
String originPath = '';
|
||||
switch (origin) {
|
||||
case TimelineOrigin.favorite:
|
||||
originPath = '/favorites';
|
||||
break;
|
||||
case TimelineOrigin.trash:
|
||||
originPath = '/trash';
|
||||
break;
|
||||
case TimelineOrigin.archive:
|
||||
originPath = '/archive';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
final url = '$serverEndpoint$originPath/photos/$remoteId';
|
||||
if (await canLaunchUrl(Uri.parse(url))) {
|
||||
await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return BaseActionButton(
|
||||
label: 'open_in_browser'.t(context: context),
|
||||
iconData: Icons.open_in_browser,
|
||||
iconColor: iconColor,
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
onPressed: _onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -29,13 +29,13 @@ class RemoveFromAlbumActionButton extends ConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).removeFromAlbum(source, albumId);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
if (source == ActionSource.viewer) {
|
||||
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
||||
}
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).removeFromAlbum(source, albumId);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
final successMessage = 'remove_from_album_action_prompt'.t(
|
||||
context: context,
|
||||
args: {'count': result.count.toString()},
|
||||
|
||||
@@ -25,13 +25,13 @@ class TrashActionButton extends ConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).trash(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
if (source == ActionSource.viewer) {
|
||||
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
||||
}
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).trash(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
final successMessage = 'trash_action_prompt'.t(context: context, args: {'count': result.count.toString()});
|
||||
|
||||
if (context.mounted) {
|
||||
|
||||
@@ -16,13 +16,13 @@ import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||
Future<void> performUnArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async {
|
||||
if (!context.mounted) return;
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).unArchive(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
if (source == ActionSource.viewer) {
|
||||
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
||||
}
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).unArchive(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
final successMessage = 'unarchive_action_prompt'.t(context: context, args: {'count': result.count.toString()});
|
||||
|
||||
if (context.mounted) {
|
||||
|
||||
@@ -81,17 +81,19 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
late final _preloader = AssetPreloader(timelineService: ref.read(timelineServiceProvider), mounted: () => mounted);
|
||||
|
||||
late int _currentPage = widget.initialIndex;
|
||||
late int _totalAssets = ref.read(timelineServiceProvider).totalAssets;
|
||||
|
||||
StreamSubscription? _reloadSubscription;
|
||||
KeepAliveLink? _stackChildrenKeepAlive;
|
||||
|
||||
bool _assetReloadRequested = false;
|
||||
|
||||
void _onTapNavigate(int direction) {
|
||||
final page = _pageController.page?.toInt();
|
||||
if (page == null) return;
|
||||
final target = page + direction;
|
||||
final maxPage = _totalAssets - 1;
|
||||
final maxPage = ref.read(timelineServiceProvider).totalAssets - 1;
|
||||
if (target >= 0 && target <= maxPage) {
|
||||
_currentPage = target;
|
||||
_pageController.jumpToPage(target);
|
||||
_onAssetChanged(target);
|
||||
}
|
||||
@@ -139,6 +141,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
|
||||
final page = _pageController.page?.round();
|
||||
if (page != null && page != _currentPage) {
|
||||
_currentPage = page;
|
||||
_onAssetChanged(page);
|
||||
}
|
||||
return false;
|
||||
@@ -150,9 +153,8 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
}
|
||||
|
||||
void _onAssetChanged(int index) async {
|
||||
_currentPage = index;
|
||||
|
||||
final asset = await ref.read(timelineServiceProvider).getAssetAsync(index);
|
||||
final timelineService = ref.read(timelineServiceProvider);
|
||||
final asset = await timelineService.getAssetAsync(index);
|
||||
if (asset == null) return;
|
||||
|
||||
AssetViewer._setAsset(ref, asset);
|
||||
@@ -191,20 +193,11 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
case TimelineReloadEvent():
|
||||
_onTimelineReloadEvent();
|
||||
case ViewerReloadAssetEvent():
|
||||
_onViewerReloadEvent();
|
||||
_assetReloadRequested = true;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
void _onViewerReloadEvent() {
|
||||
if (_totalAssets <= 1) return;
|
||||
|
||||
final index = _pageController.page?.round() ?? 0;
|
||||
final target = index >= _totalAssets - 1 ? index - 1 : index + 1;
|
||||
_pageController.animateToPage(target, duration: Durations.medium1, curve: Curves.easeInOut);
|
||||
_onAssetChanged(target);
|
||||
}
|
||||
|
||||
void _onTimelineReloadEvent() {
|
||||
final timelineService = ref.read(timelineServiceProvider);
|
||||
final totalAssets = timelineService.totalAssets;
|
||||
@@ -214,24 +207,43 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
return;
|
||||
}
|
||||
|
||||
var index = _pageController.page?.round() ?? 0;
|
||||
final currentAsset = ref.read(assetViewerProvider).currentAsset;
|
||||
final assetIndex = currentAsset != null ? timelineService.getIndex(currentAsset.heroTag) : null;
|
||||
final index = (assetIndex ?? _currentPage).clamp(0, totalAssets - 1);
|
||||
if (currentAsset != null) {
|
||||
final newIndex = timelineService.getIndex(currentAsset.heroTag);
|
||||
if (newIndex != null && newIndex != index) {
|
||||
index = newIndex;
|
||||
_currentPage = index;
|
||||
_pageController.jumpToPage(index);
|
||||
}
|
||||
}
|
||||
|
||||
if (index != _currentPage) {
|
||||
if (index >= totalAssets) {
|
||||
index = totalAssets - 1;
|
||||
_currentPage = index;
|
||||
_pageController.jumpToPage(index);
|
||||
_onAssetChanged(index);
|
||||
} else if (currentAsset != null && assetIndex == null) {
|
||||
_onAssetChanged(index);
|
||||
}
|
||||
|
||||
if (_totalAssets != totalAssets) {
|
||||
setState(() {
|
||||
_totalAssets = totalAssets;
|
||||
});
|
||||
if (_assetReloadRequested) {
|
||||
_assetReloadRequested = false;
|
||||
_onAssetReloadEvent(index);
|
||||
}
|
||||
}
|
||||
|
||||
void _onAssetReloadEvent(int index) async {
|
||||
final timelineService = ref.read(timelineServiceProvider);
|
||||
|
||||
final newAsset = await timelineService.getAssetAsync(index);
|
||||
if (newAsset == null) return;
|
||||
|
||||
final currentAsset = ref.read(assetViewerProvider).currentAsset;
|
||||
|
||||
// Do not reload if the asset has not changed
|
||||
if (newAsset.heroTag == currentAsset?.heroTag) return;
|
||||
|
||||
_onAssetChanged(index);
|
||||
}
|
||||
|
||||
void _setSystemUIMode(bool controls, bool details) {
|
||||
final mode = !controls || (CurrentPlatform.isIOS && details)
|
||||
? SystemUiMode.immersiveSticky
|
||||
@@ -289,7 +301,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
: CurrentPlatform.isIOS
|
||||
? const FastScrollPhysics()
|
||||
: const FastClampingScrollPhysics(),
|
||||
itemCount: _totalAssets,
|
||||
itemCount: ref.read(timelineServiceProvider).totalAssets,
|
||||
itemBuilder: (context, index) =>
|
||||
AssetPage(index: index, heroOffset: _heroOffset, onTapNavigate: _onTapNavigate),
|
||||
),
|
||||
|
||||
@@ -97,7 +97,7 @@ class AlbumApiRepository extends ApiRepository {
|
||||
for (final result in response) {
|
||||
if (result.success) {
|
||||
added.add(result.id);
|
||||
} else if (result.error == BulkIdResponseDtoErrorEnum.duplicate) {
|
||||
} else if (result.error == BulkIdErrorReason.duplicate) {
|
||||
duplicates.add(result.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,10 @@ class ApiService {
|
||||
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
|
||||
urls.add(serverEndpoint);
|
||||
}
|
||||
final serverUrl = Store.tryGet(StoreKey.serverUrl);
|
||||
if (serverUrl != null && serverUrl.isNotEmpty) {
|
||||
urls.add(serverUrl);
|
||||
}
|
||||
final localEndpoint = Store.tryGet(StoreKey.localEndpoint);
|
||||
if (localEndpoint != null && localEndpoint.isNotEmpty) {
|
||||
urls.add(localEndpoint);
|
||||
|
||||
@@ -18,7 +18,6 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permane
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/like_activity_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/open_in_browser_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/set_album_cover.widget.dart';
|
||||
@@ -76,7 +75,6 @@ enum ActionButtonType {
|
||||
viewInTimeline,
|
||||
download,
|
||||
upload,
|
||||
openInBrowser,
|
||||
unstack,
|
||||
archive,
|
||||
unarchive,
|
||||
@@ -151,7 +149,6 @@ enum ActionButtonType {
|
||||
context.isOwner && //
|
||||
!context.isInLockedView && //
|
||||
context.isStacked,
|
||||
ActionButtonType.openInBrowser => context.asset.hasRemote && !context.isInLockedView,
|
||||
ActionButtonType.likeActivity =>
|
||||
!context.isInLockedView &&
|
||||
context.currentAlbum != null &&
|
||||
@@ -239,13 +236,6 @@ enum ActionButtonType {
|
||||
),
|
||||
ActionButtonType.likeActivity => LikeActivityActionButton(iconOnly: iconOnly, menuItem: menuItem),
|
||||
ActionButtonType.unstack => UnStackActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem),
|
||||
ActionButtonType.openInBrowser => OpenInBrowserActionButton(
|
||||
remoteId: context.asset.remoteId!,
|
||||
origin: context.timelineOrigin,
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
iconColor: context.originalTheme?.iconTheme.color,
|
||||
),
|
||||
ActionButtonType.similarPhotos => SimilarPhotosActionButton(
|
||||
assetId: (context.asset as RemoteAsset).id,
|
||||
iconOnly: iconOnly,
|
||||
|
||||
@@ -5,13 +5,13 @@ dynamic upgradeDto(dynamic value, String targetType) {
|
||||
case 'UserPreferencesResponseDto':
|
||||
if (value is Map) {
|
||||
addDefault(value, 'download.includeEmbeddedVideos', false);
|
||||
addDefault(value, 'folders', FoldersResponse().toJson());
|
||||
addDefault(value, 'memories', MemoriesResponse().toJson());
|
||||
addDefault(value, 'ratings', RatingsResponse().toJson());
|
||||
addDefault(value, 'people', PeopleResponse().toJson());
|
||||
addDefault(value, 'tags', TagsResponse().toJson());
|
||||
addDefault(value, 'sharedLinks', SharedLinksResponse().toJson());
|
||||
addDefault(value, 'cast', CastResponse().toJson());
|
||||
addDefault(value, 'folders', FoldersResponse(enabled: false, sidebarWeb: false).toJson());
|
||||
addDefault(value, 'memories', MemoriesResponse(enabled: true, duration: 5).toJson());
|
||||
addDefault(value, 'ratings', RatingsResponse(enabled: false).toJson());
|
||||
addDefault(value, 'people', PeopleResponse(enabled: true, sidebarWeb: false).toJson());
|
||||
addDefault(value, 'tags', TagsResponse(enabled: false, sidebarWeb: false).toJson());
|
||||
addDefault(value, 'sharedLinks', SharedLinksResponse(enabled: true, sidebarWeb: false).toJson());
|
||||
addDefault(value, 'cast', CastResponse(gCastEnabled: false).toJson());
|
||||
addDefault(value, 'albums', {'defaultAssetOrder': 'desc'});
|
||||
}
|
||||
break;
|
||||
|
||||
8
mobile/openapi/README.md
generated
8
mobile/openapi/README.md
generated
@@ -370,6 +370,7 @@ Class | Method | HTTP request | Description
|
||||
- [AssetFaceUpdateItem](doc//AssetFaceUpdateItem.md)
|
||||
- [AssetFaceWithoutPersonResponseDto](doc//AssetFaceWithoutPersonResponseDto.md)
|
||||
- [AssetFullSyncDto](doc//AssetFullSyncDto.md)
|
||||
- [AssetIdErrorReason](doc//AssetIdErrorReason.md)
|
||||
- [AssetIdsDto](doc//AssetIdsDto.md)
|
||||
- [AssetIdsResponseDto](doc//AssetIdsResponseDto.md)
|
||||
- [AssetJobName](doc//AssetJobName.md)
|
||||
@@ -387,10 +388,12 @@ Class | Method | HTTP request | Description
|
||||
- [AssetMetadataUpsertItemDto](doc//AssetMetadataUpsertItemDto.md)
|
||||
- [AssetOcrResponseDto](doc//AssetOcrResponseDto.md)
|
||||
- [AssetOrder](doc//AssetOrder.md)
|
||||
- [AssetRejectReason](doc//AssetRejectReason.md)
|
||||
- [AssetResponseDto](doc//AssetResponseDto.md)
|
||||
- [AssetStackResponseDto](doc//AssetStackResponseDto.md)
|
||||
- [AssetStatsResponseDto](doc//AssetStatsResponseDto.md)
|
||||
- [AssetTypeEnum](doc//AssetTypeEnum.md)
|
||||
- [AssetUploadAction](doc//AssetUploadAction.md)
|
||||
- [AssetVisibility](doc//AssetVisibility.md)
|
||||
- [AudioCodec](doc//AudioCodec.md)
|
||||
- [AuthStatusResponseDto](doc//AuthStatusResponseDto.md)
|
||||
@@ -437,7 +440,6 @@ Class | Method | HTTP request | Description
|
||||
- [LibraryResponseDto](doc//LibraryResponseDto.md)
|
||||
- [LibraryStatsResponseDto](doc//LibraryStatsResponseDto.md)
|
||||
- [LicenseKeyDto](doc//LicenseKeyDto.md)
|
||||
- [LicenseResponseDto](doc//LicenseResponseDto.md)
|
||||
- [LogLevel](doc//LogLevel.md)
|
||||
- [LoginCredentialDto](doc//LoginCredentialDto.md)
|
||||
- [LoginResponseDto](doc//LoginResponseDto.md)
|
||||
@@ -501,6 +503,10 @@ Class | Method | HTTP request | Description
|
||||
- [PluginActionResponseDto](doc//PluginActionResponseDto.md)
|
||||
- [PluginContextType](doc//PluginContextType.md)
|
||||
- [PluginFilterResponseDto](doc//PluginFilterResponseDto.md)
|
||||
- [PluginJsonSchema](doc//PluginJsonSchema.md)
|
||||
- [PluginJsonSchemaProperty](doc//PluginJsonSchemaProperty.md)
|
||||
- [PluginJsonSchemaPropertyAdditionalProperties](doc//PluginJsonSchemaPropertyAdditionalProperties.md)
|
||||
- [PluginJsonSchemaType](doc//PluginJsonSchemaType.md)
|
||||
- [PluginResponseDto](doc//PluginResponseDto.md)
|
||||
- [PluginTriggerResponseDto](doc//PluginTriggerResponseDto.md)
|
||||
- [PluginTriggerType](doc//PluginTriggerType.md)
|
||||
|
||||
8
mobile/openapi/lib/api.dart
generated
8
mobile/openapi/lib/api.dart
generated
@@ -109,6 +109,7 @@ part 'model/asset_face_update_dto.dart';
|
||||
part 'model/asset_face_update_item.dart';
|
||||
part 'model/asset_face_without_person_response_dto.dart';
|
||||
part 'model/asset_full_sync_dto.dart';
|
||||
part 'model/asset_id_error_reason.dart';
|
||||
part 'model/asset_ids_dto.dart';
|
||||
part 'model/asset_ids_response_dto.dart';
|
||||
part 'model/asset_job_name.dart';
|
||||
@@ -126,10 +127,12 @@ part 'model/asset_metadata_upsert_dto.dart';
|
||||
part 'model/asset_metadata_upsert_item_dto.dart';
|
||||
part 'model/asset_ocr_response_dto.dart';
|
||||
part 'model/asset_order.dart';
|
||||
part 'model/asset_reject_reason.dart';
|
||||
part 'model/asset_response_dto.dart';
|
||||
part 'model/asset_stack_response_dto.dart';
|
||||
part 'model/asset_stats_response_dto.dart';
|
||||
part 'model/asset_type_enum.dart';
|
||||
part 'model/asset_upload_action.dart';
|
||||
part 'model/asset_visibility.dart';
|
||||
part 'model/audio_codec.dart';
|
||||
part 'model/auth_status_response_dto.dart';
|
||||
@@ -176,7 +179,6 @@ part 'model/job_settings_dto.dart';
|
||||
part 'model/library_response_dto.dart';
|
||||
part 'model/library_stats_response_dto.dart';
|
||||
part 'model/license_key_dto.dart';
|
||||
part 'model/license_response_dto.dart';
|
||||
part 'model/log_level.dart';
|
||||
part 'model/login_credential_dto.dart';
|
||||
part 'model/login_response_dto.dart';
|
||||
@@ -240,6 +242,10 @@ part 'model/places_response_dto.dart';
|
||||
part 'model/plugin_action_response_dto.dart';
|
||||
part 'model/plugin_context_type.dart';
|
||||
part 'model/plugin_filter_response_dto.dart';
|
||||
part 'model/plugin_json_schema.dart';
|
||||
part 'model/plugin_json_schema_property.dart';
|
||||
part 'model/plugin_json_schema_property_additional_properties.dart';
|
||||
part 'model/plugin_json_schema_type.dart';
|
||||
part 'model/plugin_response_dto.dart';
|
||||
part 'model/plugin_trigger_response_dto.dart';
|
||||
part 'model/plugin_trigger_type.dart';
|
||||
|
||||
4
mobile/openapi/lib/api/activities_api.dart
generated
4
mobile/openapi/lib/api/activities_api.dart
generated
@@ -136,10 +136,8 @@ class ActivitiesApi {
|
||||
/// Asset ID (if activity is for an asset)
|
||||
///
|
||||
/// * [ReactionLevel] level:
|
||||
/// Filter by activity level
|
||||
///
|
||||
/// * [ReactionType] type:
|
||||
/// Filter by activity type
|
||||
///
|
||||
/// * [String] userId:
|
||||
/// Filter by user ID
|
||||
@@ -195,10 +193,8 @@ class ActivitiesApi {
|
||||
/// Asset ID (if activity is for an asset)
|
||||
///
|
||||
/// * [ReactionLevel] level:
|
||||
/// Filter by activity level
|
||||
///
|
||||
/// * [ReactionType] type:
|
||||
/// Filter by activity type
|
||||
///
|
||||
/// * [String] userId:
|
||||
/// Filter by user ID
|
||||
|
||||
6
mobile/openapi/lib/api/assets_api.dart
generated
6
mobile/openapi/lib/api/assets_api.dart
generated
@@ -864,7 +864,6 @@ class AssetsApi {
|
||||
/// Filter by trash status
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Filter by visibility
|
||||
Future<Response> getAssetStatisticsWithHttpInfo({ bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/statistics';
|
||||
@@ -913,7 +912,6 @@ class AssetsApi {
|
||||
/// Filter by trash status
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Filter by visibility
|
||||
Future<AssetStatsResponseDto?> getAssetStatistics({ bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async {
|
||||
final response = await getAssetStatisticsWithHttpInfo( isFavorite: isFavorite, isTrashed: isTrashed, visibility: visibility, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -1592,7 +1590,6 @@ class AssetsApi {
|
||||
/// Sidecar file data
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Asset visibility
|
||||
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';
|
||||
@@ -1731,7 +1728,6 @@ class AssetsApi {
|
||||
/// Sidecar file data
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Asset 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) {
|
||||
@@ -1763,7 +1759,6 @@ class AssetsApi {
|
||||
/// * [String] key:
|
||||
///
|
||||
/// * [AssetMediaSize] size:
|
||||
/// Asset media size
|
||||
///
|
||||
/// * [String] slug:
|
||||
Future<Response> viewAssetWithHttpInfo(String id, { bool? edited, String? key, AssetMediaSize? size, String? slug, }) async {
|
||||
@@ -1819,7 +1814,6 @@ class AssetsApi {
|
||||
/// * [String] key:
|
||||
///
|
||||
/// * [AssetMediaSize] size:
|
||||
/// Asset media size
|
||||
///
|
||||
/// * [String] slug:
|
||||
Future<MultipartFile?> viewAsset(String id, { bool? edited, String? key, AssetMediaSize? size, String? slug, }) async {
|
||||
|
||||
@@ -218,6 +218,7 @@ class DatabaseBackupsAdminApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [MultipartFile] file:
|
||||
/// Database backup file
|
||||
Future<Response> uploadDatabaseBackupWithHttpInfo({ MultipartFile? file, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/admin/database-backups/upload';
|
||||
@@ -260,6 +261,7 @@ class DatabaseBackupsAdminApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [MultipartFile] file:
|
||||
/// Database backup file
|
||||
Future<void> uploadDatabaseBackup({ MultipartFile? file, }) async {
|
||||
final response = await uploadDatabaseBackupWithHttpInfo( file: file, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
2
mobile/openapi/lib/api/deprecated_api.dart
generated
2
mobile/openapi/lib/api/deprecated_api.dart
generated
@@ -520,7 +520,6 @@ class DeprecatedApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [QueueCommandDto] queueCommandDto (required):
|
||||
Future<Response> runQueueCommandLegacyWithHttpInfo(QueueName name, QueueCommandDto queueCommandDto,) async {
|
||||
@@ -556,7 +555,6 @@ class DeprecatedApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [QueueCommandDto] queueCommandDto (required):
|
||||
Future<QueueResponseLegacyDto?> runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async {
|
||||
|
||||
2
mobile/openapi/lib/api/jobs_api.dart
generated
2
mobile/openapi/lib/api/jobs_api.dart
generated
@@ -121,7 +121,6 @@ class JobsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [QueueCommandDto] queueCommandDto (required):
|
||||
Future<Response> runQueueCommandLegacyWithHttpInfo(QueueName name, QueueCommandDto queueCommandDto,) async {
|
||||
@@ -157,7 +156,6 @@ class JobsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [QueueCommandDto] queueCommandDto (required):
|
||||
Future<QueueResponseLegacyDto?> runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async {
|
||||
|
||||
8
mobile/openapi/lib/api/memories_api.dart
generated
8
mobile/openapi/lib/api/memories_api.dart
generated
@@ -260,13 +260,11 @@ class MemoriesApi {
|
||||
/// Include trashed memories
|
||||
///
|
||||
/// * [MemorySearchOrder] order:
|
||||
/// Sort order
|
||||
///
|
||||
/// * [int] size:
|
||||
/// Number of memories to return
|
||||
///
|
||||
/// * [MemoryType] type:
|
||||
/// Memory type
|
||||
Future<Response> memoriesStatisticsWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/memories/statistics';
|
||||
@@ -327,13 +325,11 @@ class MemoriesApi {
|
||||
/// Include trashed memories
|
||||
///
|
||||
/// * [MemorySearchOrder] order:
|
||||
/// Sort order
|
||||
///
|
||||
/// * [int] size:
|
||||
/// Number of memories to return
|
||||
///
|
||||
/// * [MemoryType] type:
|
||||
/// Memory type
|
||||
Future<MemoryStatisticsResponseDto?> memoriesStatistics({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async {
|
||||
final response = await memoriesStatisticsWithHttpInfo( for_: for_, isSaved: isSaved, isTrashed: isTrashed, order: order, size: size, type: type, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -431,13 +427,11 @@ class MemoriesApi {
|
||||
/// Include trashed memories
|
||||
///
|
||||
/// * [MemorySearchOrder] order:
|
||||
/// Sort order
|
||||
///
|
||||
/// * [int] size:
|
||||
/// Number of memories to return
|
||||
///
|
||||
/// * [MemoryType] type:
|
||||
/// Memory type
|
||||
Future<Response> searchMemoriesWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/memories';
|
||||
@@ -498,13 +492,11 @@ class MemoriesApi {
|
||||
/// Include trashed memories
|
||||
///
|
||||
/// * [MemorySearchOrder] order:
|
||||
/// Sort order
|
||||
///
|
||||
/// * [int] size:
|
||||
/// Number of memories to return
|
||||
///
|
||||
/// * [MemoryType] type:
|
||||
/// Memory type
|
||||
Future<List<MemoryResponseDto>?> searchMemories({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async {
|
||||
final response = await searchMemoriesWithHttpInfo( for_: for_, isSaved: isSaved, isTrashed: isTrashed, order: order, size: size, type: type, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
4
mobile/openapi/lib/api/notifications_api.dart
generated
4
mobile/openapi/lib/api/notifications_api.dart
generated
@@ -182,10 +182,8 @@ class NotificationsApi {
|
||||
/// Filter by notification ID
|
||||
///
|
||||
/// * [NotificationLevel] level:
|
||||
/// Filter by notification level
|
||||
///
|
||||
/// * [NotificationType] type:
|
||||
/// Filter by notification type
|
||||
///
|
||||
/// * [bool] unread:
|
||||
/// Filter by unread status
|
||||
@@ -237,10 +235,8 @@ class NotificationsApi {
|
||||
/// Filter by notification ID
|
||||
///
|
||||
/// * [NotificationLevel] level:
|
||||
/// Filter by notification level
|
||||
///
|
||||
/// * [NotificationType] type:
|
||||
/// Filter by notification type
|
||||
///
|
||||
/// * [bool] unread:
|
||||
/// Filter by unread status
|
||||
|
||||
2
mobile/openapi/lib/api/partners_api.dart
generated
2
mobile/openapi/lib/api/partners_api.dart
generated
@@ -138,7 +138,6 @@ class PartnersApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [PartnerDirection] direction (required):
|
||||
/// Partner direction
|
||||
Future<Response> getPartnersWithHttpInfo(PartnerDirection direction,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/partners';
|
||||
@@ -173,7 +172,6 @@ class PartnersApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [PartnerDirection] direction (required):
|
||||
/// Partner direction
|
||||
Future<List<PartnerResponseDto>?> getPartners(PartnerDirection direction,) async {
|
||||
final response = await getPartnersWithHttpInfo(direction,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
8
mobile/openapi/lib/api/queues_api.dart
generated
8
mobile/openapi/lib/api/queues_api.dart
generated
@@ -25,7 +25,6 @@ class QueuesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [QueueDeleteDto] queueDeleteDto (required):
|
||||
Future<Response> emptyQueueWithHttpInfo(QueueName name, QueueDeleteDto queueDeleteDto,) async {
|
||||
@@ -61,7 +60,6 @@ class QueuesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [QueueDeleteDto] queueDeleteDto (required):
|
||||
Future<void> emptyQueue(QueueName name, QueueDeleteDto queueDeleteDto,) async {
|
||||
@@ -80,7 +78,6 @@ class QueuesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
Future<Response> getQueueWithHttpInfo(QueueName name,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/queues/{name}'
|
||||
@@ -114,7 +111,6 @@ class QueuesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
Future<QueueResponseDto?> getQueue(QueueName name,) async {
|
||||
final response = await getQueueWithHttpInfo(name,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -139,7 +135,6 @@ class QueuesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [List<QueueJobStatus>] status:
|
||||
/// Filter jobs by status
|
||||
@@ -180,7 +175,6 @@ class QueuesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [List<QueueJobStatus>] status:
|
||||
/// Filter jobs by status
|
||||
@@ -262,7 +256,6 @@ class QueuesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [QueueUpdateDto] queueUpdateDto (required):
|
||||
Future<Response> updateQueueWithHttpInfo(QueueName name, QueueUpdateDto queueUpdateDto,) async {
|
||||
@@ -298,7 +291,6 @@ class QueuesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [QueueUpdateDto] queueUpdateDto (required):
|
||||
Future<QueueResponseDto?> updateQueue(QueueName name, QueueUpdateDto queueUpdateDto,) async {
|
||||
|
||||
6
mobile/openapi/lib/api/search_api.dart
generated
6
mobile/openapi/lib/api/search_api.dart
generated
@@ -127,7 +127,6 @@ class SearchApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [SearchSuggestionType] type (required):
|
||||
/// Suggestion type
|
||||
///
|
||||
/// * [String] country:
|
||||
/// Filter by country
|
||||
@@ -198,7 +197,6 @@ class SearchApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [SearchSuggestionType] type (required):
|
||||
/// Suggestion type
|
||||
///
|
||||
/// * [String] country:
|
||||
/// Filter by country
|
||||
@@ -434,7 +432,6 @@ class SearchApi {
|
||||
/// Filter by trash date (before)
|
||||
///
|
||||
/// * [AssetTypeEnum] type:
|
||||
/// Asset type filter
|
||||
///
|
||||
/// * [DateTime] updatedAfter:
|
||||
/// Filter by update date (after)
|
||||
@@ -443,7 +440,6 @@ class SearchApi {
|
||||
/// Filter by update date (before)
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Filter by visibility
|
||||
///
|
||||
/// * [bool] withDeleted:
|
||||
/// Include deleted assets
|
||||
@@ -657,7 +653,6 @@ class SearchApi {
|
||||
/// Filter by trash date (before)
|
||||
///
|
||||
/// * [AssetTypeEnum] type:
|
||||
/// Asset type filter
|
||||
///
|
||||
/// * [DateTime] updatedAfter:
|
||||
/// Filter by update date (after)
|
||||
@@ -666,7 +661,6 @@ class SearchApi {
|
||||
/// Filter by update date (before)
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Filter by visibility
|
||||
///
|
||||
/// * [bool] withDeleted:
|
||||
/// Include deleted assets
|
||||
|
||||
8
mobile/openapi/lib/api/server_api.dart
generated
8
mobile/openapi/lib/api/server_api.dart
generated
@@ -281,7 +281,7 @@ class ServerApi {
|
||||
/// Get product key
|
||||
///
|
||||
/// Retrieve information about whether the server currently has a product key registered.
|
||||
Future<LicenseResponseDto?> getServerLicense() async {
|
||||
Future<UserLicense?> getServerLicense() async {
|
||||
final response = await getServerLicenseWithHttpInfo();
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
@@ -290,7 +290,7 @@ class ServerApi {
|
||||
// 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), 'LicenseResponseDto',) as LicenseResponseDto;
|
||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserLicense',) as UserLicense;
|
||||
|
||||
}
|
||||
return null;
|
||||
@@ -724,7 +724,7 @@ class ServerApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [LicenseKeyDto] licenseKeyDto (required):
|
||||
Future<LicenseResponseDto?> setServerLicense(LicenseKeyDto licenseKeyDto,) async {
|
||||
Future<UserLicense?> setServerLicense(LicenseKeyDto licenseKeyDto,) async {
|
||||
final response = await setServerLicenseWithHttpInfo(licenseKeyDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
@@ -733,7 +733,7 @@ class ServerApi {
|
||||
// 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), 'LicenseResponseDto',) as LicenseResponseDto;
|
||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserLicense',) as UserLicense;
|
||||
|
||||
}
|
||||
return null;
|
||||
|
||||
4
mobile/openapi/lib/api/timeline_api.dart
generated
4
mobile/openapi/lib/api/timeline_api.dart
generated
@@ -25,7 +25,7 @@ class TimelineApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] timeBucket (required):
|
||||
/// Time bucket identifier in YYYY-MM-DD format (e.g., \"2024-01-01\" for January 2024)
|
||||
/// Time bucket identifier in YYYY-MM-DD format
|
||||
///
|
||||
/// * [String] albumId:
|
||||
/// Filter assets belonging to a specific album
|
||||
@@ -142,7 +142,7 @@ class TimelineApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] timeBucket (required):
|
||||
/// Time bucket identifier in YYYY-MM-DD format (e.g., \"2024-01-01\" for January 2024)
|
||||
/// Time bucket identifier in YYYY-MM-DD format
|
||||
///
|
||||
/// * [String] albumId:
|
||||
/// Filter assets belonging to a specific album
|
||||
|
||||
2
mobile/openapi/lib/api/users_admin_api.dart
generated
2
mobile/openapi/lib/api/users_admin_api.dart
generated
@@ -324,7 +324,6 @@ class UsersAdminApi {
|
||||
/// Filter by trash status
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Filter by visibility
|
||||
Future<Response> getUserStatisticsAdminWithHttpInfo(String id, { bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/admin/users/{id}/statistics'
|
||||
@@ -376,7 +375,6 @@ class UsersAdminApi {
|
||||
/// Filter by trash status
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Filter by visibility
|
||||
Future<AssetStatsResponseDto?> getUserStatisticsAdmin(String id, { bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async {
|
||||
final response = await getUserStatisticsAdminWithHttpInfo(id, isFavorite: isFavorite, isTrashed: isTrashed, visibility: visibility, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
8
mobile/openapi/lib/api/users_api.dart
generated
8
mobile/openapi/lib/api/users_api.dart
generated
@@ -447,7 +447,7 @@ class UsersApi {
|
||||
/// Retrieve user product key
|
||||
///
|
||||
/// Retrieve information about whether the current user has a registered product key.
|
||||
Future<LicenseResponseDto?> getUserLicense() async {
|
||||
Future<UserLicense?> getUserLicense() async {
|
||||
final response = await getUserLicenseWithHttpInfo();
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
@@ -456,7 +456,7 @@ class UsersApi {
|
||||
// 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), 'LicenseResponseDto',) as LicenseResponseDto;
|
||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserLicense',) as UserLicense;
|
||||
|
||||
}
|
||||
return null;
|
||||
@@ -602,7 +602,7 @@ class UsersApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [LicenseKeyDto] licenseKeyDto (required):
|
||||
Future<LicenseResponseDto?> setUserLicense(LicenseKeyDto licenseKeyDto,) async {
|
||||
Future<UserLicense?> setUserLicense(LicenseKeyDto licenseKeyDto,) async {
|
||||
final response = await setUserLicenseWithHttpInfo(licenseKeyDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
@@ -611,7 +611,7 @@ class UsersApi {
|
||||
// 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), 'LicenseResponseDto',) as LicenseResponseDto;
|
||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserLicense',) as UserLicense;
|
||||
|
||||
}
|
||||
return null;
|
||||
|
||||
16
mobile/openapi/lib/api_client.dart
generated
16
mobile/openapi/lib/api_client.dart
generated
@@ -264,6 +264,8 @@ class ApiClient {
|
||||
return AssetFaceWithoutPersonResponseDto.fromJson(value);
|
||||
case 'AssetFullSyncDto':
|
||||
return AssetFullSyncDto.fromJson(value);
|
||||
case 'AssetIdErrorReason':
|
||||
return AssetIdErrorReasonTypeTransformer().decode(value);
|
||||
case 'AssetIdsDto':
|
||||
return AssetIdsDto.fromJson(value);
|
||||
case 'AssetIdsResponseDto':
|
||||
@@ -298,6 +300,8 @@ class ApiClient {
|
||||
return AssetOcrResponseDto.fromJson(value);
|
||||
case 'AssetOrder':
|
||||
return AssetOrderTypeTransformer().decode(value);
|
||||
case 'AssetRejectReason':
|
||||
return AssetRejectReasonTypeTransformer().decode(value);
|
||||
case 'AssetResponseDto':
|
||||
return AssetResponseDto.fromJson(value);
|
||||
case 'AssetStackResponseDto':
|
||||
@@ -306,6 +310,8 @@ class ApiClient {
|
||||
return AssetStatsResponseDto.fromJson(value);
|
||||
case 'AssetTypeEnum':
|
||||
return AssetTypeEnumTypeTransformer().decode(value);
|
||||
case 'AssetUploadAction':
|
||||
return AssetUploadActionTypeTransformer().decode(value);
|
||||
case 'AssetVisibility':
|
||||
return AssetVisibilityTypeTransformer().decode(value);
|
||||
case 'AudioCodec':
|
||||
@@ -398,8 +404,6 @@ class ApiClient {
|
||||
return LibraryStatsResponseDto.fromJson(value);
|
||||
case 'LicenseKeyDto':
|
||||
return LicenseKeyDto.fromJson(value);
|
||||
case 'LicenseResponseDto':
|
||||
return LicenseResponseDto.fromJson(value);
|
||||
case 'LogLevel':
|
||||
return LogLevelTypeTransformer().decode(value);
|
||||
case 'LoginCredentialDto':
|
||||
@@ -526,6 +530,14 @@ class ApiClient {
|
||||
return PluginContextTypeTypeTransformer().decode(value);
|
||||
case 'PluginFilterResponseDto':
|
||||
return PluginFilterResponseDto.fromJson(value);
|
||||
case 'PluginJsonSchema':
|
||||
return PluginJsonSchema.fromJson(value);
|
||||
case 'PluginJsonSchemaProperty':
|
||||
return PluginJsonSchemaProperty.fromJson(value);
|
||||
case 'PluginJsonSchemaPropertyAdditionalProperties':
|
||||
return PluginJsonSchemaPropertyAdditionalProperties.fromJson(value);
|
||||
case 'PluginJsonSchemaType':
|
||||
return PluginJsonSchemaTypeTypeTransformer().decode(value);
|
||||
case 'PluginResponseDto':
|
||||
return PluginResponseDto.fromJson(value);
|
||||
case 'PluginTriggerResponseDto':
|
||||
|
||||
12
mobile/openapi/lib/api_helper.dart
generated
12
mobile/openapi/lib/api_helper.dart
generated
@@ -61,6 +61,9 @@ String parameterToString(dynamic value) {
|
||||
if (value is AssetEditAction) {
|
||||
return AssetEditActionTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AssetIdErrorReason) {
|
||||
return AssetIdErrorReasonTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AssetJobName) {
|
||||
return AssetJobNameTypeTransformer().encode(value).toString();
|
||||
}
|
||||
@@ -73,9 +76,15 @@ String parameterToString(dynamic value) {
|
||||
if (value is AssetOrder) {
|
||||
return AssetOrderTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AssetRejectReason) {
|
||||
return AssetRejectReasonTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AssetTypeEnum) {
|
||||
return AssetTypeEnumTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AssetUploadAction) {
|
||||
return AssetUploadActionTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AssetVisibility) {
|
||||
return AssetVisibilityTypeTransformer().encode(value).toString();
|
||||
}
|
||||
@@ -133,6 +142,9 @@ String parameterToString(dynamic value) {
|
||||
if (value is PluginContextType) {
|
||||
return PluginContextTypeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is PluginJsonSchemaType) {
|
||||
return PluginJsonSchemaTypeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is PluginTriggerType) {
|
||||
return PluginTriggerTypeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ class ActivityCreateDto {
|
||||
///
|
||||
String? comment;
|
||||
|
||||
/// Activity type (like or comment)
|
||||
ReactionType type;
|
||||
|
||||
@override
|
||||
|
||||
@@ -21,10 +21,8 @@ class ActivityResponseDto {
|
||||
required this.user,
|
||||
});
|
||||
|
||||
/// Asset ID (if activity is for an asset)
|
||||
String? assetId;
|
||||
|
||||
/// Comment text (for comment activities)
|
||||
String? comment;
|
||||
|
||||
/// Creation date
|
||||
@@ -33,7 +31,6 @@ class ActivityResponseDto {
|
||||
/// Activity ID
|
||||
String id;
|
||||
|
||||
/// Activity type
|
||||
ReactionType type;
|
||||
|
||||
UserResponseDto user;
|
||||
@@ -72,7 +69,9 @@ class ActivityResponseDto {
|
||||
} else {
|
||||
// json[r'comment'] = null;
|
||||
}
|
||||
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
||||
json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||
? this.createdAt.millisecondsSinceEpoch
|
||||
: this.createdAt.toUtc().toIso8601String();
|
||||
json[r'id'] = this.id;
|
||||
json[r'type'] = this.type;
|
||||
json[r'user'] = this.user;
|
||||
@@ -90,7 +89,7 @@ class ActivityResponseDto {
|
||||
return ActivityResponseDto(
|
||||
assetId: mapValueOfType<String>(json, r'assetId'),
|
||||
comment: mapValueOfType<String>(json, r'comment'),
|
||||
createdAt: mapDateTime(json, r'createdAt', r'')!,
|
||||
createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!,
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
type: ReactionType.fromJson(json[r'type'])!,
|
||||
user: UserResponseDto.fromJson(json[r'user'])!,
|
||||
|
||||
@@ -18,9 +18,15 @@ class ActivityStatisticsResponseDto {
|
||||
});
|
||||
|
||||
/// Number of comments
|
||||
///
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 9007199254740991
|
||||
int comments;
|
||||
|
||||
/// Number of likes
|
||||
///
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 9007199254740991
|
||||
int likes;
|
||||
|
||||
@override
|
||||
|
||||
5
mobile/openapi/lib/model/album_response_dto.dart
generated
5
mobile/openapi/lib/model/album_response_dto.dart
generated
@@ -37,12 +37,14 @@ class AlbumResponseDto {
|
||||
/// Album name
|
||||
String albumName;
|
||||
|
||||
/// Thumbnail asset ID
|
||||
String? albumThumbnailAssetId;
|
||||
|
||||
List<AlbumUserResponseDto> albumUsers;
|
||||
|
||||
/// Number of assets
|
||||
///
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 9007199254740991
|
||||
int assetCount;
|
||||
|
||||
List<AssetResponseDto> assets;
|
||||
@@ -82,7 +84,6 @@ class AlbumResponseDto {
|
||||
///
|
||||
DateTime? lastModifiedAssetTimestamp;
|
||||
|
||||
/// Asset sort order
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
||||
@@ -19,12 +19,21 @@ class AlbumStatisticsResponseDto {
|
||||
});
|
||||
|
||||
/// Number of non-shared albums
|
||||
///
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 9007199254740991
|
||||
int notShared;
|
||||
|
||||
/// Number of owned albums
|
||||
///
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 9007199254740991
|
||||
int owned;
|
||||
|
||||
/// Number of shared albums
|
||||
///
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 9007199254740991
|
||||
int shared;
|
||||
|
||||
@override
|
||||
|
||||
19
mobile/openapi/lib/model/album_user_add_dto.dart
generated
19
mobile/openapi/lib/model/album_user_add_dto.dart
generated
@@ -13,12 +13,17 @@ part of openapi.api;
|
||||
class AlbumUserAddDto {
|
||||
/// Returns a new [AlbumUserAddDto] instance.
|
||||
AlbumUserAddDto({
|
||||
this.role = AlbumUserRole.editor,
|
||||
this.role,
|
||||
required this.userId,
|
||||
});
|
||||
|
||||
/// Album user role
|
||||
AlbumUserRole role;
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
AlbumUserRole? role;
|
||||
|
||||
/// User ID
|
||||
String userId;
|
||||
@@ -31,7 +36,7 @@ class AlbumUserAddDto {
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(role.hashCode) +
|
||||
(role == null ? 0 : role!.hashCode) +
|
||||
(userId.hashCode);
|
||||
|
||||
@override
|
||||
@@ -39,7 +44,11 @@ class AlbumUserAddDto {
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.role != null) {
|
||||
json[r'role'] = this.role;
|
||||
} else {
|
||||
// json[r'role'] = null;
|
||||
}
|
||||
json[r'userId'] = this.userId;
|
||||
return json;
|
||||
}
|
||||
@@ -53,7 +62,7 @@ class AlbumUserAddDto {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AlbumUserAddDto(
|
||||
role: AlbumUserRole.fromJson(json[r'role']) ?? AlbumUserRole.editor,
|
||||
role: AlbumUserRole.fromJson(json[r'role']),
|
||||
userId: mapValueOfType<String>(json, r'userId')!,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ class AlbumUserCreateDto {
|
||||
required this.userId,
|
||||
});
|
||||
|
||||
/// Album user role
|
||||
AlbumUserRole role;
|
||||
|
||||
/// User ID
|
||||
|
||||
@@ -17,7 +17,6 @@ class AlbumUserResponseDto {
|
||||
required this.user,
|
||||
});
|
||||
|
||||
/// Album user role
|
||||
AlbumUserRole role;
|
||||
|
||||
UserResponseDto user;
|
||||
|
||||
@@ -17,7 +17,6 @@ class AlbumsAddAssetsResponseDto {
|
||||
required this.success,
|
||||
});
|
||||
|
||||
/// Error reason
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
||||
3
mobile/openapi/lib/model/albums_response.dart
generated
3
mobile/openapi/lib/model/albums_response.dart
generated
@@ -13,10 +13,9 @@ part of openapi.api;
|
||||
class AlbumsResponse {
|
||||
/// Returns a new [AlbumsResponse] instance.
|
||||
AlbumsResponse({
|
||||
this.defaultAssetOrder = AssetOrder.desc,
|
||||
required this.defaultAssetOrder,
|
||||
});
|
||||
|
||||
/// Default asset order for albums
|
||||
AssetOrder defaultAssetOrder;
|
||||
|
||||
@override
|
||||
|
||||
1
mobile/openapi/lib/model/albums_update.dart
generated
1
mobile/openapi/lib/model/albums_update.dart
generated
@@ -16,7 +16,6 @@ class AlbumsUpdate {
|
||||
this.defaultAssetOrder,
|
||||
});
|
||||
|
||||
/// Default asset order for albums
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
||||
12
mobile/openapi/lib/model/api_key_response_dto.dart
generated
12
mobile/openapi/lib/model/api_key_response_dto.dart
generated
@@ -57,11 +57,15 @@ class APIKeyResponseDto {
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
||||
json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||
? this.createdAt.millisecondsSinceEpoch
|
||||
: this.createdAt.toUtc().toIso8601String();
|
||||
json[r'id'] = this.id;
|
||||
json[r'name'] = this.name;
|
||||
json[r'permissions'] = this.permissions;
|
||||
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||
json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||
? this.updatedAt.millisecondsSinceEpoch
|
||||
: this.updatedAt.toUtc().toIso8601String();
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -74,11 +78,11 @@ class APIKeyResponseDto {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return APIKeyResponseDto(
|
||||
createdAt: mapDateTime(json, r'createdAt', r'')!,
|
||||
createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!,
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
name: mapValueOfType<String>(json, r'name')!,
|
||||
permissions: Permission.listFromJson(json[r'permissions']),
|
||||
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||
updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
16
mobile/openapi/lib/model/asset_bulk_update_dto.dart
generated
16
mobile/openapi/lib/model/asset_bulk_update_dto.dart
generated
@@ -53,7 +53,6 @@ class AssetBulkUpdateDto {
|
||||
///
|
||||
String? description;
|
||||
|
||||
/// Duplicate ID
|
||||
String? duplicateId;
|
||||
|
||||
/// Asset IDs to update
|
||||
@@ -70,6 +69,9 @@ class AssetBulkUpdateDto {
|
||||
|
||||
/// Latitude coordinate
|
||||
///
|
||||
/// Minimum value: -90
|
||||
/// Maximum value: 90
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
@@ -79,6 +81,9 @@ class AssetBulkUpdateDto {
|
||||
|
||||
/// Longitude coordinate
|
||||
///
|
||||
/// Minimum value: -180
|
||||
/// Maximum value: 180
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
@@ -86,11 +91,9 @@ class AssetBulkUpdateDto {
|
||||
///
|
||||
num? longitude;
|
||||
|
||||
/// Rating in range [1-5], or null for unrated
|
||||
///
|
||||
/// Minimum value: -1
|
||||
/// Maximum value: 5
|
||||
num? rating;
|
||||
int? rating;
|
||||
|
||||
/// Time zone (IANA timezone)
|
||||
///
|
||||
@@ -101,7 +104,6 @@ class AssetBulkUpdateDto {
|
||||
///
|
||||
String? timeZone;
|
||||
|
||||
/// Asset visibility
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -217,9 +219,7 @@ class AssetBulkUpdateDto {
|
||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
||||
latitude: num.parse('${json[r'latitude']}'),
|
||||
longitude: num.parse('${json[r'longitude']}'),
|
||||
rating: json[r'rating'] == null
|
||||
? null
|
||||
: num.parse('${json[r'rating']}'),
|
||||
rating: mapValueOfType<int>(json, r'rating'),
|
||||
timeZone: mapValueOfType<String>(json, r'timeZone'),
|
||||
visibility: AssetVisibility.fromJson(json[r'visibility']),
|
||||
);
|
||||
|
||||
@@ -20,8 +20,7 @@ class AssetBulkUploadCheckResult {
|
||||
this.reason,
|
||||
});
|
||||
|
||||
/// Upload action
|
||||
AssetBulkUploadCheckResultActionEnum action;
|
||||
AssetUploadAction action;
|
||||
|
||||
/// Existing asset ID if duplicate
|
||||
///
|
||||
@@ -44,8 +43,13 @@ class AssetBulkUploadCheckResult {
|
||||
///
|
||||
bool? isTrashed;
|
||||
|
||||
/// Rejection reason if rejected
|
||||
AssetBulkUploadCheckResultReasonEnum? reason;
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
AssetRejectReason? reason;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetBulkUploadCheckResult &&
|
||||
@@ -98,11 +102,11 @@ class AssetBulkUploadCheckResult {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetBulkUploadCheckResult(
|
||||
action: AssetBulkUploadCheckResultActionEnum.fromJson(json[r'action'])!,
|
||||
action: AssetUploadAction.fromJson(json[r'action'])!,
|
||||
assetId: mapValueOfType<String>(json, r'assetId'),
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
isTrashed: mapValueOfType<bool>(json, r'isTrashed'),
|
||||
reason: AssetBulkUploadCheckResultReasonEnum.fromJson(json[r'reason']),
|
||||
reason: AssetRejectReason.fromJson(json[r'reason']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@@ -155,151 +159,3 @@ class AssetBulkUploadCheckResult {
|
||||
};
|
||||
}
|
||||
|
||||
/// Upload action
|
||||
class AssetBulkUploadCheckResultActionEnum {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetBulkUploadCheckResultActionEnum._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const accept = AssetBulkUploadCheckResultActionEnum._(r'accept');
|
||||
static const reject = AssetBulkUploadCheckResultActionEnum._(r'reject');
|
||||
|
||||
/// List of all possible values in this [enum][AssetBulkUploadCheckResultActionEnum].
|
||||
static const values = <AssetBulkUploadCheckResultActionEnum>[
|
||||
accept,
|
||||
reject,
|
||||
];
|
||||
|
||||
static AssetBulkUploadCheckResultActionEnum? fromJson(dynamic value) => AssetBulkUploadCheckResultActionEnumTypeTransformer().decode(value);
|
||||
|
||||
static List<AssetBulkUploadCheckResultActionEnum> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetBulkUploadCheckResultActionEnum>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetBulkUploadCheckResultActionEnum.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [AssetBulkUploadCheckResultActionEnum] to String,
|
||||
/// and [decode] dynamic data back to [AssetBulkUploadCheckResultActionEnum].
|
||||
class AssetBulkUploadCheckResultActionEnumTypeTransformer {
|
||||
factory AssetBulkUploadCheckResultActionEnumTypeTransformer() => _instance ??= const AssetBulkUploadCheckResultActionEnumTypeTransformer._();
|
||||
|
||||
const AssetBulkUploadCheckResultActionEnumTypeTransformer._();
|
||||
|
||||
String encode(AssetBulkUploadCheckResultActionEnum data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a AssetBulkUploadCheckResultActionEnum.
|
||||
///
|
||||
/// 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.
|
||||
AssetBulkUploadCheckResultActionEnum? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'accept': return AssetBulkUploadCheckResultActionEnum.accept;
|
||||
case r'reject': return AssetBulkUploadCheckResultActionEnum.reject;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [AssetBulkUploadCheckResultActionEnumTypeTransformer] instance.
|
||||
static AssetBulkUploadCheckResultActionEnumTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
|
||||
/// Rejection reason if rejected
|
||||
class AssetBulkUploadCheckResultReasonEnum {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetBulkUploadCheckResultReasonEnum._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const duplicate = AssetBulkUploadCheckResultReasonEnum._(r'duplicate');
|
||||
static const unsupportedFormat = AssetBulkUploadCheckResultReasonEnum._(r'unsupported-format');
|
||||
|
||||
/// List of all possible values in this [enum][AssetBulkUploadCheckResultReasonEnum].
|
||||
static const values = <AssetBulkUploadCheckResultReasonEnum>[
|
||||
duplicate,
|
||||
unsupportedFormat,
|
||||
];
|
||||
|
||||
static AssetBulkUploadCheckResultReasonEnum? fromJson(dynamic value) => AssetBulkUploadCheckResultReasonEnumTypeTransformer().decode(value);
|
||||
|
||||
static List<AssetBulkUploadCheckResultReasonEnum> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetBulkUploadCheckResultReasonEnum>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetBulkUploadCheckResultReasonEnum.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [AssetBulkUploadCheckResultReasonEnum] to String,
|
||||
/// and [decode] dynamic data back to [AssetBulkUploadCheckResultReasonEnum].
|
||||
class AssetBulkUploadCheckResultReasonEnumTypeTransformer {
|
||||
factory AssetBulkUploadCheckResultReasonEnumTypeTransformer() => _instance ??= const AssetBulkUploadCheckResultReasonEnumTypeTransformer._();
|
||||
|
||||
const AssetBulkUploadCheckResultReasonEnumTypeTransformer._();
|
||||
|
||||
String encode(AssetBulkUploadCheckResultReasonEnum data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a AssetBulkUploadCheckResultReasonEnum.
|
||||
///
|
||||
/// 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.
|
||||
AssetBulkUploadCheckResultReasonEnum? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'duplicate': return AssetBulkUploadCheckResultReasonEnum.duplicate;
|
||||
case r'unsupported-format': return AssetBulkUploadCheckResultReasonEnum.unsupportedFormat;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [AssetBulkUploadCheckResultReasonEnumTypeTransformer] instance.
|
||||
static AssetBulkUploadCheckResultReasonEnumTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -39,7 +39,9 @@ class AssetDeltaSyncDto {
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'updatedAfter'] = this.updatedAfter.toUtc().toIso8601String();
|
||||
json[r'updatedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||
? this.updatedAfter.millisecondsSinceEpoch
|
||||
: this.updatedAfter.toUtc().toIso8601String();
|
||||
json[r'userIds'] = this.userIds;
|
||||
return json;
|
||||
}
|
||||
@@ -53,7 +55,7 @@ class AssetDeltaSyncDto {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetDeltaSyncDto(
|
||||
updatedAfter: mapDateTime(json, r'updatedAfter', r'')!,
|
||||
updatedAfter: mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!,
|
||||
userIds: json[r'userIds'] is Iterable
|
||||
? (json[r'userIds'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const [],
|
||||
|
||||
@@ -24,7 +24,6 @@ class AssetDeltaSyncResponseDto {
|
||||
/// Whether full sync is needed
|
||||
bool needsFullSync;
|
||||
|
||||
/// Upserted assets
|
||||
List<AssetResponseDto> upserted;
|
||||
|
||||
@override
|
||||
|
||||
@@ -17,7 +17,6 @@ class AssetEditActionItemDto {
|
||||
required this.parameters,
|
||||
});
|
||||
|
||||
/// Type of edit action to perform
|
||||
AssetEditAction action;
|
||||
|
||||
AssetEditActionItemDtoParameters parameters;
|
||||
|
||||
@@ -44,7 +44,6 @@ class AssetEditActionItemDtoParameters {
|
||||
/// Rotation angle in degrees
|
||||
num angle;
|
||||
|
||||
/// Axis to mirror along
|
||||
MirrorAxis axis;
|
||||
|
||||
@override
|
||||
|
||||
@@ -18,9 +18,9 @@ class AssetEditActionItemResponseDto {
|
||||
required this.parameters,
|
||||
});
|
||||
|
||||
/// Type of edit action to perform
|
||||
AssetEditAction action;
|
||||
|
||||
/// Asset edit ID
|
||||
String id;
|
||||
|
||||
AssetEditActionItemDtoParameters parameters;
|
||||
|
||||
18
mobile/openapi/lib/model/asset_face_create_dto.dart
generated
18
mobile/openapi/lib/model/asset_face_create_dto.dart
generated
@@ -27,24 +27,42 @@ class AssetFaceCreateDto {
|
||||
String assetId;
|
||||
|
||||
/// Face bounding box height
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int height;
|
||||
|
||||
/// Image height in pixels
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int imageHeight;
|
||||
|
||||
/// Image width in pixels
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int imageWidth;
|
||||
|
||||
/// Person ID
|
||||
String personId;
|
||||
|
||||
/// Face bounding box width
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int width;
|
||||
|
||||
/// Face bounding box X coordinate
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int x;
|
||||
|
||||
/// Face bounding box Y coordinate
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int y;
|
||||
|
||||
@override
|
||||
|
||||
@@ -25,30 +25,46 @@ class AssetFaceResponseDto {
|
||||
});
|
||||
|
||||
/// Bounding box X1 coordinate
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int boundingBoxX1;
|
||||
|
||||
/// Bounding box X2 coordinate
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int boundingBoxX2;
|
||||
|
||||
/// Bounding box Y1 coordinate
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int boundingBoxY1;
|
||||
|
||||
/// Bounding box Y2 coordinate
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int boundingBoxY2;
|
||||
|
||||
/// Face ID
|
||||
String id;
|
||||
|
||||
/// Image height in pixels
|
||||
///
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 9007199254740991
|
||||
int imageHeight;
|
||||
|
||||
/// Image width in pixels
|
||||
///
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 9007199254740991
|
||||
int imageWidth;
|
||||
|
||||
/// Person associated with face
|
||||
PersonResponseDto? person;
|
||||
PersonResponseDto person;
|
||||
|
||||
/// Face detection source type
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -79,7 +95,7 @@ class AssetFaceResponseDto {
|
||||
(id.hashCode) +
|
||||
(imageHeight.hashCode) +
|
||||
(imageWidth.hashCode) +
|
||||
(person == null ? 0 : person!.hashCode) +
|
||||
(person.hashCode) +
|
||||
(sourceType == null ? 0 : sourceType!.hashCode);
|
||||
|
||||
@override
|
||||
@@ -94,11 +110,7 @@ class AssetFaceResponseDto {
|
||||
json[r'id'] = this.id;
|
||||
json[r'imageHeight'] = this.imageHeight;
|
||||
json[r'imageWidth'] = this.imageWidth;
|
||||
if (this.person != null) {
|
||||
json[r'person'] = this.person;
|
||||
} else {
|
||||
// json[r'person'] = null;
|
||||
}
|
||||
if (this.sourceType != null) {
|
||||
json[r'sourceType'] = this.sourceType;
|
||||
} else {
|
||||
@@ -123,7 +135,7 @@ class AssetFaceResponseDto {
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
imageHeight: mapValueOfType<int>(json, r'imageHeight')!,
|
||||
imageWidth: mapValueOfType<int>(json, r'imageWidth')!,
|
||||
person: PersonResponseDto.fromJson(json[r'person']),
|
||||
person: PersonResponseDto.fromJson(json[r'person'])!,
|
||||
sourceType: SourceType.fromJson(json[r'sourceType']),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,27 +24,44 @@ class AssetFaceWithoutPersonResponseDto {
|
||||
});
|
||||
|
||||
/// Bounding box X1 coordinate
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int boundingBoxX1;
|
||||
|
||||
/// Bounding box X2 coordinate
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int boundingBoxX2;
|
||||
|
||||
/// Bounding box Y1 coordinate
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int boundingBoxY1;
|
||||
|
||||
/// Bounding box Y2 coordinate
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int boundingBoxY2;
|
||||
|
||||
/// Face ID
|
||||
String id;
|
||||
|
||||
/// Image height in pixels
|
||||
///
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 9007199254740991
|
||||
int imageHeight;
|
||||
|
||||
/// Image width in pixels
|
||||
///
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 9007199254740991
|
||||
int imageWidth;
|
||||
|
||||
/// Face detection source type
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
||||
@@ -31,6 +31,7 @@ class AssetFullSyncDto {
|
||||
/// Maximum number of assets to return
|
||||
///
|
||||
/// Minimum value: 1
|
||||
/// Maximum value: 9007199254740991
|
||||
int limit;
|
||||
|
||||
/// Sync assets updated until this date
|
||||
@@ -71,7 +72,9 @@ class AssetFullSyncDto {
|
||||
// json[r'lastId'] = null;
|
||||
}
|
||||
json[r'limit'] = this.limit;
|
||||
json[r'updatedUntil'] = this.updatedUntil.toUtc().toIso8601String();
|
||||
json[r'updatedUntil'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||
? this.updatedUntil.millisecondsSinceEpoch
|
||||
: this.updatedUntil.toUtc().toIso8601String();
|
||||
if (this.userId != null) {
|
||||
json[r'userId'] = this.userId;
|
||||
} else {
|
||||
@@ -91,7 +94,7 @@ class AssetFullSyncDto {
|
||||
return AssetFullSyncDto(
|
||||
lastId: mapValueOfType<String>(json, r'lastId'),
|
||||
limit: mapValueOfType<int>(json, r'limit')!,
|
||||
updatedUntil: mapDateTime(json, r'updatedUntil', r'')!,
|
||||
updatedUntil: mapDateTime(json, r'updatedUntil', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!,
|
||||
userId: mapValueOfType<String>(json, r'userId'),
|
||||
);
|
||||
}
|
||||
|
||||
88
mobile/openapi/lib/model/asset_id_error_reason.dart
generated
Normal file
88
mobile/openapi/lib/model/asset_id_error_reason.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;
|
||||
|
||||
/// Error reason if failed
|
||||
class AssetIdErrorReason {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetIdErrorReason._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const duplicate = AssetIdErrorReason._(r'duplicate');
|
||||
static const noPermission = AssetIdErrorReason._(r'no_permission');
|
||||
static const notFound = AssetIdErrorReason._(r'not_found');
|
||||
|
||||
/// List of all possible values in this [enum][AssetIdErrorReason].
|
||||
static const values = <AssetIdErrorReason>[
|
||||
duplicate,
|
||||
noPermission,
|
||||
notFound,
|
||||
];
|
||||
|
||||
static AssetIdErrorReason? fromJson(dynamic value) => AssetIdErrorReasonTypeTransformer().decode(value);
|
||||
|
||||
static List<AssetIdErrorReason> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetIdErrorReason>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetIdErrorReason.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [AssetIdErrorReason] to String,
|
||||
/// and [decode] dynamic data back to [AssetIdErrorReason].
|
||||
class AssetIdErrorReasonTypeTransformer {
|
||||
factory AssetIdErrorReasonTypeTransformer() => _instance ??= const AssetIdErrorReasonTypeTransformer._();
|
||||
|
||||
const AssetIdErrorReasonTypeTransformer._();
|
||||
|
||||
String encode(AssetIdErrorReason data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a AssetIdErrorReason.
|
||||
///
|
||||
/// 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.
|
||||
AssetIdErrorReason? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'duplicate': return AssetIdErrorReason.duplicate;
|
||||
case r'no_permission': return AssetIdErrorReason.noPermission;
|
||||
case r'not_found': return AssetIdErrorReason.notFound;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [AssetIdErrorReasonTypeTransformer] instance.
|
||||
static AssetIdErrorReasonTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
88
mobile/openapi/lib/model/asset_ids_response_dto.dart
generated
88
mobile/openapi/lib/model/asset_ids_response_dto.dart
generated
@@ -21,8 +21,13 @@ class AssetIdsResponseDto {
|
||||
/// Asset ID
|
||||
String assetId;
|
||||
|
||||
/// Error reason if failed
|
||||
AssetIdsResponseDtoErrorEnum? error;
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
AssetIdErrorReason? error;
|
||||
|
||||
/// Whether operation succeeded
|
||||
bool success;
|
||||
@@ -65,7 +70,7 @@ class AssetIdsResponseDto {
|
||||
|
||||
return AssetIdsResponseDto(
|
||||
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||
error: AssetIdsResponseDtoErrorEnum.fromJson(json[r'error']),
|
||||
error: AssetIdErrorReason.fromJson(json[r'error']),
|
||||
success: mapValueOfType<bool>(json, r'success')!,
|
||||
);
|
||||
}
|
||||
@@ -119,80 +124,3 @@ class AssetIdsResponseDto {
|
||||
};
|
||||
}
|
||||
|
||||
/// Error reason if failed
|
||||
class AssetIdsResponseDtoErrorEnum {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetIdsResponseDtoErrorEnum._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const duplicate = AssetIdsResponseDtoErrorEnum._(r'duplicate');
|
||||
static const noPermission = AssetIdsResponseDtoErrorEnum._(r'no_permission');
|
||||
static const notFound = AssetIdsResponseDtoErrorEnum._(r'not_found');
|
||||
|
||||
/// List of all possible values in this [enum][AssetIdsResponseDtoErrorEnum].
|
||||
static const values = <AssetIdsResponseDtoErrorEnum>[
|
||||
duplicate,
|
||||
noPermission,
|
||||
notFound,
|
||||
];
|
||||
|
||||
static AssetIdsResponseDtoErrorEnum? fromJson(dynamic value) => AssetIdsResponseDtoErrorEnumTypeTransformer().decode(value);
|
||||
|
||||
static List<AssetIdsResponseDtoErrorEnum> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetIdsResponseDtoErrorEnum>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetIdsResponseDtoErrorEnum.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [AssetIdsResponseDtoErrorEnum] to String,
|
||||
/// and [decode] dynamic data back to [AssetIdsResponseDtoErrorEnum].
|
||||
class AssetIdsResponseDtoErrorEnumTypeTransformer {
|
||||
factory AssetIdsResponseDtoErrorEnumTypeTransformer() => _instance ??= const AssetIdsResponseDtoErrorEnumTypeTransformer._();
|
||||
|
||||
const AssetIdsResponseDtoErrorEnumTypeTransformer._();
|
||||
|
||||
String encode(AssetIdsResponseDtoErrorEnum data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a AssetIdsResponseDtoErrorEnum.
|
||||
///
|
||||
/// 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.
|
||||
AssetIdsResponseDtoErrorEnum? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'duplicate': return AssetIdsResponseDtoErrorEnum.duplicate;
|
||||
case r'no_permission': return AssetIdsResponseDtoErrorEnum.noPermission;
|
||||
case r'not_found': return AssetIdsResponseDtoErrorEnum.notFound;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [AssetIdsResponseDtoErrorEnumTypeTransformer] instance.
|
||||
static AssetIdsResponseDtoErrorEnumTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
1
mobile/openapi/lib/model/asset_jobs_dto.dart
generated
1
mobile/openapi/lib/model/asset_jobs_dto.dart
generated
@@ -20,7 +20,6 @@ class AssetJobsDto {
|
||||
/// Asset IDs
|
||||
List<String> assetIds;
|
||||
|
||||
/// Job name
|
||||
AssetJobName name;
|
||||
|
||||
@override
|
||||
|
||||
@@ -20,7 +20,6 @@ class AssetMediaResponseDto {
|
||||
/// Asset media ID
|
||||
String id;
|
||||
|
||||
/// Upload status
|
||||
AssetMediaStatus status;
|
||||
|
||||
@override
|
||||
|
||||
2
mobile/openapi/lib/model/asset_media_size.dart
generated
2
mobile/openapi/lib/model/asset_media_size.dart
generated
@@ -10,7 +10,7 @@
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
|
||||
/// Asset media size
|
||||
class AssetMediaSize {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetMediaSize._(this.value);
|
||||
|
||||
@@ -16,7 +16,7 @@ class AssetMetadataBulkResponseDto {
|
||||
required this.assetId,
|
||||
required this.key,
|
||||
required this.updatedAt,
|
||||
required this.value,
|
||||
this.value = const {},
|
||||
});
|
||||
|
||||
/// Asset ID
|
||||
@@ -29,14 +29,14 @@ class AssetMetadataBulkResponseDto {
|
||||
DateTime updatedAt;
|
||||
|
||||
/// Metadata value (object)
|
||||
Object value;
|
||||
Map<String, 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;
|
||||
_deepEquality.equals(other.value, value);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
@@ -53,7 +53,9 @@ class AssetMetadataBulkResponseDto {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'assetId'] = this.assetId;
|
||||
json[r'key'] = this.key;
|
||||
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||
json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||
? this.updatedAt.millisecondsSinceEpoch
|
||||
: this.updatedAt.toUtc().toIso8601String();
|
||||
json[r'value'] = this.value;
|
||||
return json;
|
||||
}
|
||||
@@ -69,8 +71,8 @@ class AssetMetadataBulkResponseDto {
|
||||
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')!,
|
||||
updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!,
|
||||
value: mapCastOfType<String, Object>(json, r'value')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -15,7 +15,7 @@ class AssetMetadataBulkUpsertItemDto {
|
||||
AssetMetadataBulkUpsertItemDto({
|
||||
required this.assetId,
|
||||
required this.key,
|
||||
required this.value,
|
||||
this.value = const {},
|
||||
});
|
||||
|
||||
/// Asset ID
|
||||
@@ -25,13 +25,13 @@ class AssetMetadataBulkUpsertItemDto {
|
||||
String key;
|
||||
|
||||
/// Metadata value (object)
|
||||
Object value;
|
||||
Map<String, Object> value;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetMetadataBulkUpsertItemDto &&
|
||||
other.assetId == assetId &&
|
||||
other.key == key &&
|
||||
other.value == value;
|
||||
_deepEquality.equals(other.value, value);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
@@ -62,7 +62,7 @@ class AssetMetadataBulkUpsertItemDto {
|
||||
return AssetMetadataBulkUpsertItemDto(
|
||||
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||
key: mapValueOfType<String>(json, r'key')!,
|
||||
value: mapValueOfType<Object>(json, r'value')!,
|
||||
value: mapCastOfType<String, Object>(json, r'value')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -15,7 +15,7 @@ class AssetMetadataResponseDto {
|
||||
AssetMetadataResponseDto({
|
||||
required this.key,
|
||||
required this.updatedAt,
|
||||
required this.value,
|
||||
this.value = const {},
|
||||
});
|
||||
|
||||
/// Metadata key
|
||||
@@ -25,13 +25,13 @@ class AssetMetadataResponseDto {
|
||||
DateTime updatedAt;
|
||||
|
||||
/// Metadata value (object)
|
||||
Object value;
|
||||
Map<String, Object> value;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetMetadataResponseDto &&
|
||||
other.key == key &&
|
||||
other.updatedAt == updatedAt &&
|
||||
other.value == value;
|
||||
_deepEquality.equals(other.value, value);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
@@ -46,7 +46,9 @@ class AssetMetadataResponseDto {
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'key'] = this.key;
|
||||
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||
json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||
? this.updatedAt.millisecondsSinceEpoch
|
||||
: this.updatedAt.toUtc().toIso8601String();
|
||||
json[r'value'] = this.value;
|
||||
return json;
|
||||
}
|
||||
@@ -61,8 +63,8 @@ class AssetMetadataResponseDto {
|
||||
|
||||
return AssetMetadataResponseDto(
|
||||
key: mapValueOfType<String>(json, r'key')!,
|
||||
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||
value: mapValueOfType<Object>(json, r'value')!,
|
||||
updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!,
|
||||
value: mapCastOfType<String, Object>(json, r'value')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -14,19 +14,19 @@ class AssetMetadataUpsertItemDto {
|
||||
/// Returns a new [AssetMetadataUpsertItemDto] instance.
|
||||
AssetMetadataUpsertItemDto({
|
||||
required this.key,
|
||||
required this.value,
|
||||
this.value = const {},
|
||||
});
|
||||
|
||||
/// Metadata key
|
||||
String key;
|
||||
|
||||
/// Metadata value (object)
|
||||
Object value;
|
||||
Map<String, Object> value;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetMetadataUpsertItemDto &&
|
||||
other.key == key &&
|
||||
other.value == value;
|
||||
_deepEquality.equals(other.value, value);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
@@ -54,7 +54,7 @@ class AssetMetadataUpsertItemDto {
|
||||
|
||||
return AssetMetadataUpsertItemDto(
|
||||
key: mapValueOfType<String>(json, r'key')!,
|
||||
value: mapValueOfType<Object>(json, r'value')!,
|
||||
value: mapCastOfType<String, Object>(json, r'value')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
85
mobile/openapi/lib/model/asset_reject_reason.dart
generated
Normal file
85
mobile/openapi/lib/model/asset_reject_reason.dart
generated
Normal file
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
/// Rejection reason if rejected
|
||||
class AssetRejectReason {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetRejectReason._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const duplicate = AssetRejectReason._(r'duplicate');
|
||||
static const unsupportedFormat = AssetRejectReason._(r'unsupported-format');
|
||||
|
||||
/// List of all possible values in this [enum][AssetRejectReason].
|
||||
static const values = <AssetRejectReason>[
|
||||
duplicate,
|
||||
unsupportedFormat,
|
||||
];
|
||||
|
||||
static AssetRejectReason? fromJson(dynamic value) => AssetRejectReasonTypeTransformer().decode(value);
|
||||
|
||||
static List<AssetRejectReason> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetRejectReason>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetRejectReason.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [AssetRejectReason] to String,
|
||||
/// and [decode] dynamic data back to [AssetRejectReason].
|
||||
class AssetRejectReasonTypeTransformer {
|
||||
factory AssetRejectReasonTypeTransformer() => _instance ??= const AssetRejectReasonTypeTransformer._();
|
||||
|
||||
const AssetRejectReasonTypeTransformer._();
|
||||
|
||||
String encode(AssetRejectReason data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a AssetRejectReason.
|
||||
///
|
||||
/// 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.
|
||||
AssetRejectReason? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'duplicate': return AssetRejectReason.duplicate;
|
||||
case r'unsupported-format': return AssetRejectReason.unsupportedFormat;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [AssetRejectReasonTypeTransformer] instance.
|
||||
static AssetRejectReasonTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
27
mobile/openapi/lib/model/asset_response_dto.dart
generated
27
mobile/openapi/lib/model/asset_response_dto.dart
generated
@@ -62,11 +62,10 @@ class AssetResponseDto {
|
||||
/// Device ID
|
||||
String deviceId;
|
||||
|
||||
/// Duplicate group ID
|
||||
String? duplicateId;
|
||||
|
||||
/// Video/gif duration in hh:mm:ss.SSS format (null for static images)
|
||||
String? duration;
|
||||
/// Video duration (for videos)
|
||||
String duration;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
@@ -85,7 +84,7 @@ class AssetResponseDto {
|
||||
/// Whether asset has metadata
|
||||
bool hasMetadata;
|
||||
|
||||
/// Asset height
|
||||
/// Minimum value: 0
|
||||
num? height;
|
||||
|
||||
/// Asset ID
|
||||
@@ -106,10 +105,8 @@ class AssetResponseDto {
|
||||
/// Is trashed
|
||||
bool isTrashed;
|
||||
|
||||
/// Library ID
|
||||
String? libraryId;
|
||||
|
||||
/// Live photo video ID
|
||||
String? livePhotoVideoId;
|
||||
|
||||
/// The local date and time when the photo/video was taken, derived from EXIF metadata. This represents the photographer's local time regardless of timezone, stored as a timezone-agnostic timestamp. Used for timeline grouping by \"local\" days and months.
|
||||
@@ -152,6 +149,12 @@ class AssetResponseDto {
|
||||
///
|
||||
bool? resized;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
AssetStackResponseDto? stack;
|
||||
|
||||
List<TagResponseDto> tags;
|
||||
@@ -159,7 +162,6 @@ class AssetResponseDto {
|
||||
/// Thumbhash for thumbnail generation (base64) also used as the c query param for thumbnail cache busting.
|
||||
String? thumbhash;
|
||||
|
||||
/// Asset type
|
||||
AssetTypeEnum type;
|
||||
|
||||
List<AssetFaceWithoutPersonResponseDto> unassignedFaces;
|
||||
@@ -167,10 +169,9 @@ class 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.
|
||||
DateTime updatedAt;
|
||||
|
||||
/// Asset visibility
|
||||
AssetVisibility visibility;
|
||||
|
||||
/// Asset width
|
||||
/// Minimum value: 0
|
||||
num? width;
|
||||
|
||||
@override
|
||||
@@ -219,7 +220,7 @@ class AssetResponseDto {
|
||||
(deviceAssetId.hashCode) +
|
||||
(deviceId.hashCode) +
|
||||
(duplicateId == null ? 0 : duplicateId!.hashCode) +
|
||||
(duration == null ? 0 : duration!.hashCode) +
|
||||
(duration.hashCode) +
|
||||
(exifInfo == null ? 0 : exifInfo!.hashCode) +
|
||||
(fileCreatedAt.hashCode) +
|
||||
(fileModifiedAt.hashCode) +
|
||||
@@ -264,11 +265,7 @@ class AssetResponseDto {
|
||||
} else {
|
||||
// json[r'duplicateId'] = null;
|
||||
}
|
||||
if (this.duration != null) {
|
||||
json[r'duration'] = this.duration;
|
||||
} else {
|
||||
// json[r'duration'] = null;
|
||||
}
|
||||
if (this.exifInfo != null) {
|
||||
json[r'exifInfo'] = this.exifInfo;
|
||||
} else {
|
||||
@@ -355,7 +352,7 @@ class AssetResponseDto {
|
||||
deviceAssetId: mapValueOfType<String>(json, r'deviceAssetId')!,
|
||||
deviceId: mapValueOfType<String>(json, r'deviceId')!,
|
||||
duplicateId: mapValueOfType<String>(json, r'duplicateId'),
|
||||
duration: mapValueOfType<String>(json, r'duration'),
|
||||
duration: mapValueOfType<String>(json, r'duration')!,
|
||||
exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']),
|
||||
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'')!,
|
||||
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'')!,
|
||||
|
||||
@@ -19,6 +19,9 @@ class AssetStackResponseDto {
|
||||
});
|
||||
|
||||
/// Number of assets in stack
|
||||
///
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 9007199254740991
|
||||
int assetCount;
|
||||
|
||||
/// Stack ID
|
||||
|
||||
@@ -19,12 +19,21 @@ class AssetStatsResponseDto {
|
||||
});
|
||||
|
||||
/// Number of images
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int images;
|
||||
|
||||
/// Total number of assets
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int total;
|
||||
|
||||
/// Number of videos
|
||||
///
|
||||
/// Minimum value: -9007199254740991
|
||||
/// Maximum value: 9007199254740991
|
||||
int videos;
|
||||
|
||||
@override
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user