Compare commits

..

2 Commits

Author SHA1 Message Date
Jonathan Jogenfors 8888166928 fix comments 2026-02-27 14:00:27 +01:00
Jonathan Jogenfors 6982987f3f feat: add offline library statistics 2026-02-21 00:00:49 +01:00
485 changed files with 12993 additions and 20764 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
contents: read contents: read
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with: with:
persist-credentials: false persist-credentials: false
+1 -1
View File
@@ -511,7 +511,7 @@ jobs:
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Install Playwright Browsers - name: Install Playwright Browsers
run: pnpm exec playwright install chromium --only-shell run: npx playwright install chromium --only-shell
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Docker build - name: Docker build
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300 run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
+1 -1
View File
@@ -52,7 +52,7 @@ attach-server:
docker exec -it docker_immich-server_1 sh docker exec -it docker_immich-server_1 sh
renovate: renovate:
LOG_LEVEL=debug pnpm exec renovate --platform=local --repository-cache=reset LOG_LEVEL=debug npx renovate --platform=local --repository-cache=reset
# Directories that need to be created for volumes or build output # Directories that need to be created for volumes or build output
VOLUME_DIRS = \ VOLUME_DIRS = \
+6 -6
View File
@@ -13,7 +13,7 @@
"cli" "cli"
], ],
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.0", "@eslint/js": "^9.8.0",
"@immich/sdk": "workspace:*", "@immich/sdk": "workspace:*",
"@types/byte-size": "^8.1.0", "@types/byte-size": "^8.1.0",
"@types/cli-progress": "^3.11.0", "@types/cli-progress": "^3.11.0",
@@ -25,11 +25,11 @@
"byte-size": "^9.0.0", "byte-size": "^9.0.0",
"cli-progress": "^3.12.0", "cli-progress": "^3.12.0",
"commander": "^12.0.0", "commander": "^12.0.0",
"eslint": "^10.0.0", "eslint": "^9.14.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^63.0.0", "eslint-plugin-unicorn": "^62.0.0",
"globals": "^17.0.0", "globals": "^16.0.0",
"mock-fs": "^5.2.0", "mock-fs": "^5.2.0",
"prettier": "^3.7.4", "prettier": "^3.7.4",
"prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-organize-imports": "^4.0.0",
@@ -45,8 +45,8 @@
"build": "vite build", "build": "vite build",
"build:dev": "vite build --sourcemap true", "build:dev": "vite build --sourcemap true",
"lint": "eslint \"src/**/*.ts\" --max-warnings 0", "lint": "eslint \"src/**/*.ts\" --max-warnings 0",
"lint:fix": "pnpm run lint --fix", "lint:fix": "npm run lint -- --fix",
"prepack": "pnpm run build", "prepack": "npm run build",
"test": "vitest", "test": "vitest",
"test:cov": "vitest --coverage", "test:cov": "vitest --coverage",
"format": "prettier --check .", "format": "prettier --check .",
-4
View File
@@ -80,10 +80,6 @@ There is an automatic scan job that is scheduled to run once a day. Its schedule
This job also cleans up any libraries stuck in deletion. It is possible to trigger the cleanup by clicking "Scan all libraries" in the library management page. This job also cleans up any libraries stuck in deletion. It is possible to trigger the cleanup by clicking "Scan all libraries" in the library management page.
### Deleting a Library
When deleting an external library, all assets inside are immediately deleted along with the library. Note that while a library can take a long time to fully delete in the background, it is immediately removed from the library list. If the deletion process is interrupted (for example, due to server restart), it will be cleaned up in the next nightly cron job. The cleanup process can also be manually initiated by clicking the "Scan All Libraries" button in the library list.
## Usage ## Usage
Let's show a concrete example where we add an existing gallery to Immich. Here, we have the following folders we want to add: Let's show a concrete example where we add an existing gallery to Immich. Here, we have the following folders we want to add:
+1 -1
View File
@@ -8,7 +8,7 @@
"format:fix": "prettier --write .", "format:fix": "prettier --write .",
"start": "docusaurus start --port 3005", "start": "docusaurus start --port 3005",
"copy:openapi": "jq -c < ../open-api/immich-openapi-specs.json > ./static/openapi.json || exit 0", "copy:openapi": "jq -c < ../open-api/immich-openapi-specs.json > ./static/openapi.json || exit 0",
"build": "pnpm run copy:openapi && docusaurus build", "build": "npm run copy:openapi && docusaurus build",
"swizzle": "docusaurus swizzle", "swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy", "deploy": "docusaurus deploy",
"clear": "docusaurus clear", "clear": "docusaurus clear",
+12 -12
View File
@@ -8,23 +8,23 @@
"test": "vitest --run", "test": "vitest --run",
"test:watch": "vitest", "test:watch": "vitest",
"test:maintenance": "vitest --run --config vitest.maintenance.config.ts", "test:maintenance": "vitest --run --config vitest.maintenance.config.ts",
"test:web": "pnpm exec playwright test --project=web", "test:web": "npx playwright test --project=web",
"test:web:maintenance": "pnpm exec playwright test --project=maintenance", "test:web:maintenance": "npx playwright test --project=maintenance",
"test:web:ui": "pnpm exec playwright test --project=ui", "test:web:ui": "npx playwright test --project=ui",
"start:web": "pnpm exec playwright test --ui --project=web", "start:web": "npx playwright test --ui --project=web",
"start:web:maintenance": "pnpm exec playwright test --ui --project=maintenance", "start:web:maintenance": "npx playwright test --ui --project=maintenance",
"start:web:ui": "pnpm exec playwright test --ui --project=ui", "start:web:ui": "npx playwright test --ui --project=ui",
"format": "prettier --check .", "format": "prettier --check .",
"format:fix": "prettier --write .", "format:fix": "prettier --write .",
"lint": "eslint \"src/**/*.ts\" --max-warnings 0", "lint": "eslint \"src/**/*.ts\" --max-warnings 0",
"lint:fix": "pnpm run lint --fix", "lint:fix": "npm run lint -- --fix",
"check": "tsc --noEmit" "check": "tsc --noEmit"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.0", "@eslint/js": "^9.8.0",
"@faker-js/faker": "^10.1.0", "@faker-js/faker": "^10.1.0",
"@immich/cli": "workspace:*", "@immich/cli": "workspace:*",
"@immich/e2e-auth-server": "workspace:*", "@immich/e2e-auth-server": "workspace:*",
@@ -37,12 +37,12 @@
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"eslint": "^10.0.0", "eslint": "^9.14.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^63.0.0", "eslint-plugin-unicorn": "^62.0.0",
"exiftool-vendored": "^35.0.0", "exiftool-vendored": "^34.3.0",
"globals": "^17.0.0", "globals": "^16.0.0",
"luxon": "^3.4.4", "luxon": "^3.4.4",
"pg": "^8.11.3", "pg": "^8.11.3",
"pngjs": "^7.0.0", "pngjs": "^7.0.0",
@@ -99,7 +99,7 @@ describe('/admin/maintenance', () => {
}, },
{ {
interval: 500, interval: 500,
timeout: 60_000, timeout: 10_000,
}, },
) )
.toBeTruthy(); .toBeTruthy();
@@ -190,7 +190,7 @@ describe('/admin/maintenance', () => {
}, },
{ {
interval: 500, interval: 500,
timeout: 60_000, timeout: 10_000,
}, },
) )
.toBeFalsy(); .toBeFalsy();
@@ -1,669 +0,0 @@
import {
AssetMediaResponseDto,
IntegrityReportResponseDto,
LoginResponseDto,
ManualJobName,
QueueCommand,
QueueName,
} from '@immich/sdk';
import { readFile } from 'node:fs/promises';
import { app, testAssetDir, utils } from 'src/utils';
import request from 'supertest';
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
const assetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
const asset1Filepath = `${testAssetDir}/albums/nature/el_torcal_rocks.jpg`;
const asset2Filepath = `${testAssetDir}/albums/nature/wood_anemones.jpg`;
describe('/admin/integrity', () => {
let admin: LoginResponseDto;
let asset: AssetMediaResponseDto;
let user1: LoginResponseDto;
let asset1: AssetMediaResponseDto;
let user2: LoginResponseDto;
let asset2: AssetMediaResponseDto;
beforeAll(async () => {
await utils.resetDatabase();
admin = await utils.adminSetup();
user1 = await utils.userSetup(admin.accessToken, {
email: '1@example.com',
name: '1',
password: '1',
});
user2 = await utils.userSetup(admin.accessToken, {
email: '2@example.com',
name: '2',
password: '2',
});
for (const queue of Object.values(QueueName)) {
if (queue === QueueName.IntegrityCheck) {
continue;
}
await utils.queueCommand(admin.accessToken, queue, {
command: QueueCommand.Pause,
});
}
asset = await utils.createAsset(admin.accessToken, {
assetData: {
filename: 'asset.jpg',
bytes: await readFile(assetFilepath),
},
});
asset1 = await utils.createAsset(user1.accessToken, {
assetData: {
filename: 'asset.jpg',
bytes: await readFile(asset1Filepath),
},
});
asset2 = await utils.createAsset(user2.accessToken, {
assetData: {
filename: 'asset.jpg',
bytes: await readFile(asset2Filepath),
},
});
await utils.mkFolder('/data/bak');
await utils.copyFolder(`/data/upload/${admin.userId}`, `/data/bak/${admin.userId}`);
for (const queue of Object.values(QueueName)) {
if (queue === QueueName.IntegrityCheck) {
continue;
}
await utils.queueCommand(admin.accessToken, queue, {
command: QueueCommand.Empty,
});
await utils.queueCommand(admin.accessToken, queue, {
command: QueueCommand.Resume,
});
}
});
afterEach(async () => {
await utils.deleteFolder(`/data/upload/${admin.userId}`);
await utils.copyFolder(`/data/bak/${admin.userId}`, `/data/upload/${admin.userId}`);
});
describe('POST /summary (& jobs)', async () => {
it.sequential('reports no issues', async () => {
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFiles,
});
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatch,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFilesDeleteAll,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual({
missing_file: 0,
untracked_file: 0,
checksum_mismatch: 0,
});
});
it.sequential('should detect an untracked file (job: check untracked files)', async () => {
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
untracked_file: 1,
}),
);
});
it.sequential('should detect outdated untracked file reports (job: refresh untracked files)', async () => {
// these should not be detected:
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked2.png`);
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked3.png`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFilesRefresh,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
untracked_file: 0,
}),
);
});
it.sequential('should delete untracked files (job: delete all untracked file reports)', async () => {
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFilesDeleteAll,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
untracked_file: 0,
}),
);
});
it.sequential('should detect a missing file and not a checksum mismatch (job: check missing files)', async () => {
await utils.deleteFolder(`/data/upload/${admin.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
missing_file: 1,
checksum_mismatch: 0,
}),
);
});
it.sequential('should detect outdated missing file reports (job: refresh missing files)', async () => {
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFilesRefresh,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
missing_file: 0,
checksum_mismatch: 0,
}),
);
});
it.sequential('should delete assets with missing files (job: delete all missing file reports)', async () => {
await utils.deleteFolder(`/data/upload/${user1.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status: listStatus, body: listBody } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(listStatus).toBe(200);
expect(listBody).toEqual(
expect.objectContaining({
missing_file: 1,
}),
);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFilesDeleteAll,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
missing_file: 0,
}),
);
await expect(utils.getAssetInfo(user1.accessToken, asset1.id)).resolves.toEqual(
expect.objectContaining({
isTrashed: true,
}),
);
});
it.sequential('should detect a checksum mismatch (job: check file checksums)', async () => {
await utils.truncateFolder(`/data/upload/${admin.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatch,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
checksum_mismatch: 1,
}),
);
});
it.sequential('should detect outdated checksum mismatch reports (job: refresh file checksums)', async () => {
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatchRefresh,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
checksum_mismatch: 0,
}),
);
});
it.sequential(
'should delete assets with mismatched checksum (job: delete all checksum mismatch reports)',
async () => {
await utils.truncateFolder(`/data/upload/${user2.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatch,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status: listStatus, body: listBody } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(listStatus).toBe(200);
expect(listBody).toEqual(
expect.objectContaining({
checksum_mismatch: 1,
}),
);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatchDeleteAll,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
checksum_mismatch: 0,
}),
);
await expect(utils.getAssetInfo(user2.accessToken, asset2.id)).resolves.toEqual(
expect.objectContaining({
isTrashed: true,
}),
);
},
);
});
describe('POST /report', async () => {
it.sequential('reports untracked files', async () => {
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/report?type=untracked_file')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual({
nextCursor: undefined,
items: expect.arrayContaining([
{
id: expect.any(String),
type: 'untracked_file',
path: `/data/upload/${admin.userId}/untracked1.png`,
assetId: null,
fileAssetId: null,
createdAt: expect.any(String),
},
]),
});
});
it.sequential('reports missing files', async () => {
await utils.deleteFolder(`/data/upload/${admin.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/report?type=missing_file')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual({
nextCursor: undefined,
items: expect.arrayContaining([
{
id: expect.any(String),
type: 'missing_file',
path: expect.any(String),
assetId: asset.id,
fileAssetId: null,
createdAt: expect.any(String),
},
]),
});
});
it.sequential('reports checksum mismatched files', async () => {
await utils.truncateFolder(`/data/upload/${admin.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatch,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/report?type=checksum_mismatch')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual({
nextCursor: undefined,
items: expect.arrayContaining([
{
id: expect.any(String),
type: 'checksum_mismatch',
path: expect.any(String),
assetId: asset.id,
fileAssetId: null,
createdAt: expect.any(String),
},
]),
});
});
});
describe('DELETE /report/:id', async () => {
it.sequential('delete untracked files', async () => {
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status: listStatus, body: listBody } = await request(app)
.get('/admin/integrity/report?type=untracked_file')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(listStatus).toBe(200);
const report = (listBody as IntegrityReportResponseDto).items.find(
(item) => item.path === `/data/upload/${admin.userId}/untracked1.png`,
)!;
const { status } = await request(app)
.delete(`/admin/integrity/report/${report.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status: listStatus2, body: listBody2 } = await request(app)
.get('/admin/integrity/report?type=untracked_file')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(listStatus2).toBe(200);
expect(listBody2).not.toBe(
expect.objectContaining({
items: expect.arrayContaining([
expect.objectContaining({
id: report.id,
}),
]),
}),
);
});
it.sequential('delete assets missing files', async () => {
await utils.deleteFolder(`/data/upload/${admin.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status: listStatus, body: listBody } = await request(app)
.get('/admin/integrity/report?type=missing_file')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(listStatus).toBe(200);
expect(listBody.items.length).toBe(1);
const report = (listBody as IntegrityReportResponseDto).items[0];
const { status } = await request(app)
.delete(`/admin/integrity/report/${report.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status: listStatus2, body: listBody2 } = await request(app)
.get('/admin/integrity/report?type=missing_file')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(listStatus2).toBe(200);
expect(listBody2.items.length).toBe(0);
});
it.sequential('delete assets with failing checksum', async () => {
await utils.truncateFolder(`/data/upload/${admin.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatch,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status: listStatus, body: listBody } = await request(app)
.get('/admin/integrity/report?type=checksum_mismatch')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(listStatus).toBe(200);
expect(listBody.items.length).toBe(1);
const report = (listBody as IntegrityReportResponseDto).items[0];
const { status } = await request(app)
.delete(`/admin/integrity/report/${report.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatch,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status: listStatus2, body: listBody2 } = await request(app)
.get('/admin/integrity/report?type=checksum_mismatch')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(listStatus2).toBe(200);
expect(listBody2.items.length).toBe(0);
});
});
describe('GET /report/:type/csv', () => {
it.sequential('exports untracked files as csv', async () => {
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, headers, text } = await request(app)
.get('/admin/integrity/report/untracked_file/csv')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(headers['content-type']).toContain('text/csv');
expect(headers['content-disposition']).toContain('.csv');
expect(text).toContain('id,type,assetId,fileAssetId,path');
expect(text).toContain(`untracked_file`);
expect(text).toContain(`/data/upload/${admin.userId}/untracked1.png`);
});
});
describe('GET /report/:id/file', () => {
it.sequential('downloads untracked file', async () => {
await utils.putTextFile('untracked-content', `/data/upload/${admin.userId}/untracked1.png`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { body: listBody } = await request(app)
.get('/admin/integrity/report?type=untracked_file')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
const report = (listBody as IntegrityReportResponseDto).items.find(
(item) => item.path === `/data/upload/${admin.userId}/untracked1.png`,
)!;
const { status, headers, body } = await request(app)
.get(`/admin/integrity/report/${report.id}/file`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.buffer(true)
.send();
expect(status).toBe(200);
expect(headers['content-type']).toContain('application/octet-stream');
expect(body.toString()).toBe('untracked-content');
});
});
});
-41
View File
@@ -1,41 +0,0 @@
import { LoginResponseDto, ManualJobName, QueueName } from '@immich/sdk';
import { expect, test } from '@playwright/test';
import { utils } from 'src/utils';
test.describe.configure({ mode: 'serial' });
test.describe('Integrity', () => {
let admin: LoginResponseDto;
test.beforeAll(async () => {
utils.initSdk();
await utils.resetDatabase();
admin = await utils.adminSetup();
});
test('run integrity jobs to update stats', async ({ context, page }) => {
await utils.setAuthCookies(context, admin.accessToken);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
await page.goto('/admin/maintenance');
const count = page.getByText('Untracked Files').locator('..').locator('..').locator('div').nth(1);
const previousCount = Number.parseInt((await count.textContent()) ?? '');
await utils.mkFolder(`/data/upload/${admin.userId}`);
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
const checkButton = page.getByText('Integrity Report').locator('..').getByRole('button', { name: 'Check All' });
await checkButton.click();
await expect(checkButton).toBeEnabled();
await expect(count).toContainText((previousCount + 1).toString());
});
});
+2 -1
View File
@@ -45,7 +45,8 @@ test.describe('Shared Links', () => {
await page.goto(`/share/${sharedLink.key}`); await page.goto(`/share/${sharedLink.key}`);
await page.getByRole('heading', { name: 'Test Album' }).waitFor(); await page.getByRole('heading', { name: 'Test Album' }).waitFor();
await page.locator(`[data-asset-id="${asset.id}"]`).hover(); await page.locator(`[data-asset-id="${asset.id}"]`).hover();
await page.waitForSelector(`[data-asset-id="${asset.id}"] [role="checkbox"]`); await page.waitForSelector('[data-group] svg');
await page.getByRole('checkbox').click();
await Promise.all([page.waitForEvent('download'), page.getByRole('button', { name: 'Download' }).click()]); await Promise.all([page.waitForEvent('download'), page.getByRole('button', { name: 'Download' }).click()]);
}); });
@@ -438,7 +438,7 @@ test.describe('Timeline', () => {
const asset = getAsset(timelineRestData, album.assetIds[0])!; const asset = getAsset(timelineRestData, album.assetIds[0])!;
await pageUtils.goToAsset(page, asset.fileCreatedAt); await pageUtils.goToAsset(page, asset.fileCreatedAt);
await thumbnailUtils.expectInViewport(page, asset.id); await thumbnailUtils.expectInViewport(page, asset.id);
await thumbnailUtils.expectSelectedDisabled(page, asset.id); await thumbnailUtils.expectSelectedReadonly(page, asset.id);
}); });
test('Add photos to album', async ({ page }) => { test('Add photos to album', async ({ page }) => {
const album = timelineRestData.album; const album = timelineRestData.album;
@@ -447,7 +447,7 @@ test.describe('Timeline', () => {
const asset = getAsset(timelineRestData, album.assetIds[0])!; const asset = getAsset(timelineRestData, album.assetIds[0])!;
await pageUtils.goToAsset(page, asset.fileCreatedAt); await pageUtils.goToAsset(page, asset.fileCreatedAt);
await thumbnailUtils.expectInViewport(page, asset.id); await thumbnailUtils.expectInViewport(page, asset.id);
await thumbnailUtils.expectSelectedDisabled(page, asset.id); await thumbnailUtils.expectSelectedReadonly(page, asset.id);
await pageUtils.selectDay(page, 'Tue, Feb 27, 2024'); await pageUtils.selectDay(page, 'Tue, Feb 27, 2024');
const put = pageRoutePromise(page, `**/api/albums/${album.id}/assets`, async (route, request) => { const put = pageRoutePromise(page, `**/api/albums/${album.id}/assets`, async (route, request) => {
const requestJson = request.postDataJSON(); const requestJson = request.postDataJSON();
+2 -2
View File
@@ -102,9 +102,9 @@ export const thumbnailUtils = {
async expectThumbnailIsNotArchive(page: Page, assetId: string) { async expectThumbnailIsNotArchive(page: Page, assetId: string) {
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(0); await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(0);
}, },
async expectSelectedDisabled(page: Page, assetId: string) { async expectSelectedReadonly(page: Page, assetId: string) {
await expect( await expect(
page.locator(`[data-thumbnail-focus-container][data-asset="${assetId}"][data-selected][data-disabled]`), page.locator(`[data-thumbnail-focus-container][data-asset="${assetId}"][data-selected]`),
).toBeVisible(); ).toBeVisible();
}, },
async expectTimelineHasOnScreenAssets(page: Page) { async expectTimelineHasOnScreenAssets(page: Page) {
+3 -46
View File
@@ -195,7 +195,6 @@ export const utils = {
'user', 'user',
'system_metadata', 'system_metadata',
'tag', 'tag',
'integrity_report',
]; ];
const sql: string[] = []; const sql: string[] = [];
@@ -587,54 +586,10 @@ export const utils = {
mkdirSync(`${testAssetDir}/temp`, { recursive: true }); mkdirSync(`${testAssetDir}/temp`, { recursive: true });
}, },
putFile(source: string, dest: string) {
return executeCommand('docker', ['cp', source, `immich-e2e-server:${dest}`]).promise;
},
async putTextFile(contents: string, dest: string) {
const dir = await mkdtemp(join(tmpdir(), 'test-'));
const fn = join(dir, 'file');
await pipeline(Readable.from(contents), createWriteStream(fn));
return executeCommand('docker', ['cp', fn, `immich-e2e-server:${dest}`]).promise;
},
async move(source: string, dest: string) { async move(source: string, dest: string) {
return executeCommand('docker', ['exec', 'immich-e2e-server', 'mv', source, dest]).promise; return executeCommand('docker', ['exec', 'immich-e2e-server', 'mv', source, dest]).promise;
}, },
async copyFolder(source: string, dest: string) {
return executeCommand('docker', ['exec', 'immich-e2e-server', 'cp', '-r', source, dest]).promise;
},
async deleteFile(path: string) {
return executeCommand('docker', ['exec', 'immich-e2e-server', 'rm', path]).promise;
},
async deleteFolder(path: string) {
return executeCommand('docker', ['exec', 'immich-e2e-server', 'rm', '-r', path]).promise;
},
async truncateFolder(path: string) {
return executeCommand('docker', [
'exec',
'immich-e2e-server',
'find',
path,
'-type',
'f',
'-exec',
'truncate',
'-s',
'1',
'{}',
';',
]).promise;
},
async mkFolder(path: string) {
return executeCommand('docker', ['exec', 'immich-e2e-server', 'mkdir', '-p', path]).promise;
},
createBackup: async (accessToken: string) => { createBackup: async (accessToken: string) => {
await utils.createJob(accessToken, { await utils.createJob(accessToken, {
name: ManualJobName.BackupDatabase, name: ManualJobName.BackupDatabase,
@@ -649,8 +604,10 @@ export const utils = {
resetBackups: async (accessToken: string) => { resetBackups: async (accessToken: string) => {
const { backups } = await listDatabaseBackups({ headers: asBearerAuth(accessToken) }); const { backups } = await listDatabaseBackups({ headers: asBearerAuth(accessToken) });
const backupFiles = backups.map((b) => b.filename);
await deleteDatabaseBackup( await deleteDatabaseBackup(
{ databaseBackupDeleteDto: { backups: backups.map((dto) => dto.filename) } }, { databaseBackupDeleteDto: { backups: backupFiles } },
{ headers: asBearerAuth(accessToken) }, { headers: asBearerAuth(accessToken) },
); );
}, },
-19
View File
@@ -81,7 +81,6 @@
"cron_expression_description": "Set the scanning interval using the cron format. For more information please refer to e.g. <link>Crontab Guru</link>", "cron_expression_description": "Set the scanning interval using the cron format. For more information please refer to e.g. <link>Crontab Guru</link>",
"cron_expression_presets": "Cron expression presets", "cron_expression_presets": "Cron expression presets",
"disable_login": "Disable login", "disable_login": "Disable login",
"download_csv": "Download CSV",
"duplicate_detection_job_description": "Run machine learning on assets to detect similar images. Relies on Smart Search", "duplicate_detection_job_description": "Run machine learning on assets to detect similar images. Relies on Smart Search",
"exclusion_pattern_description": "Exclusion patterns lets you ignore files and folders when scanning your library. This is useful if you have folders that contain files you don't want to import, such as RAW files.", "exclusion_pattern_description": "Exclusion patterns lets you ignore files and folders when scanning your library. This is useful if you have folders that contain files you don't want to import, such as RAW files.",
"export_config_as_json_description": "Download the current system config as a JSON file", "export_config_as_json_description": "Download the current system config as a JSON file",
@@ -194,17 +193,6 @@
"maintenance_delete_backup": "Delete Backup", "maintenance_delete_backup": "Delete Backup",
"maintenance_delete_backup_description": "This file will be irrevocably deleted.", "maintenance_delete_backup_description": "This file will be irrevocably deleted.",
"maintenance_delete_error": "Failed to delete backup.", "maintenance_delete_error": "Failed to delete backup.",
"maintenance_integrity_check_all": "Check All",
"maintenance_integrity_checksum_mismatch": "Checksum Mismatch",
"maintenance_integrity_checksum_mismatch_job": "Check for checksum mismatches",
"maintenance_integrity_checksum_mismatch_refresh_job": "Refresh checksum mismatch reports",
"maintenance_integrity_missing_file": "Missing Files",
"maintenance_integrity_missing_file_job": "Check for missing files",
"maintenance_integrity_missing_file_refresh_job": "Refresh missing file reports",
"maintenance_integrity_report": "Integrity Report",
"maintenance_integrity_untracked_file": "Untracked Files",
"maintenance_integrity_untracked_file_job": "Check for untracked files",
"maintenance_integrity_untracked_file_refresh_job": "Refresh untracked file reports",
"maintenance_restore_backup": "Restore Backup", "maintenance_restore_backup": "Restore Backup",
"maintenance_restore_backup_description": "Immich will be wiped and restored from the chosen backup. A backup will be created before continuing.", "maintenance_restore_backup_description": "Immich will be wiped and restored from the chosen backup. A backup will be created before continuing.",
"maintenance_restore_backup_different_version": "This backup was created with a different version of Immich!", "maintenance_restore_backup_different_version": "This backup was created with a different version of Immich!",
@@ -1209,7 +1197,6 @@
"failed": "Failed", "failed": "Failed",
"failed_count": "Failed: {count}", "failed_count": "Failed: {count}",
"failed_to_authenticate": "Failed to authenticate", "failed_to_authenticate": "Failed to authenticate",
"failed_to_delete_file": "Failed to delete file",
"failed_to_load_assets": "Failed to load assets", "failed_to_load_assets": "Failed to load assets",
"failed_to_load_folder": "Failed to load folder", "failed_to_load_folder": "Failed to load folder",
"favorite": "Favorite", "favorite": "Favorite",
@@ -1339,7 +1326,6 @@
"individual_share": "Individual share", "individual_share": "Individual share",
"individual_shares": "Individual shares", "individual_shares": "Individual shares",
"info": "Info", "info": "Info",
"integrity_checks": "Integrity Checks",
"interval": { "interval": {
"day_at_onepm": "Every day at 1pm", "day_at_onepm": "Every day at 1pm",
"hours": "Every {hours, plural, one {hour} other {{hours, number} hours}}", "hours": "Every {hours, plural, one {hour} other {{hours, number} hours}}",
@@ -1406,7 +1392,6 @@
"link_to_oauth": "Link to OAuth", "link_to_oauth": "Link to OAuth",
"linked_oauth_account": "Linked OAuth account", "linked_oauth_account": "Linked OAuth account",
"list": "List", "list": "List",
"load_more": "Load More",
"loading": "Loading", "loading": "Loading",
"loading_search_results_failed": "Loading search results failed", "loading_search_results_failed": "Loading search results failed",
"local": "Local", "local": "Local",
@@ -2041,9 +2026,6 @@
"set_profile_picture": "Set profile picture", "set_profile_picture": "Set profile picture",
"set_slideshow_to_fullscreen": "Set Slideshow to fullscreen", "set_slideshow_to_fullscreen": "Set Slideshow to fullscreen",
"set_stack_primary_asset": "Set as primary asset", "set_stack_primary_asset": "Set as primary asset",
"setting_image_navigation_enable_subtitle": "If enabled, you can navigate to the previous/next image by tapping the leftmost/rightmost quarter of the screen.",
"setting_image_navigation_enable_title": "Tap to Navigate",
"setting_image_navigation_title": "Image Navigation",
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
"setting_image_viewer_original_title": "Load original image", "setting_image_viewer_original_title": "Load original image",
@@ -2322,7 +2304,6 @@
"unstack_action_prompt": "{count} unstacked", "unstack_action_prompt": "{count} unstacked",
"unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}", "unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}",
"unsupported_field_type": "Unsupported field type", "unsupported_field_type": "Unsupported field type",
"unsupported_file_type": "File {file} can't be uploaded because its file type {type} is not supported.",
"untagged": "Untagged", "untagged": "Untagged",
"untitled_workflow": "Untitled workflow", "untitled_workflow": "Untitled workflow",
"up_next": "Up next", "up_next": "Up next",
+17 -3
View File
@@ -654,6 +654,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/70/25/fab23259a52ece5670dcb8452e1af34b89e6135ecc17cd4b54b4b479eac6/fsspec-2023.12.2-py3-none-any.whl", hash = "sha256:d800d87f72189a745fa3d6b033b9dc4a34ad069f60ca60b943a63599f5501960", size = 168979, upload-time = "2023-12-11T21:19:52.446Z" }, { url = "https://files.pythonhosted.org/packages/70/25/fab23259a52ece5670dcb8452e1af34b89e6135ecc17cd4b54b4b479eac6/fsspec-2023.12.2-py3-none-any.whl", hash = "sha256:d800d87f72189a745fa3d6b033b9dc4a34ad069f60ca60b943a63599f5501960", size = 168979, upload-time = "2023-12-11T21:19:52.446Z" },
] ]
[[package]]
name = "ftfy"
version = "6.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "wcwidth" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a5/d3/8650919bc3c7c6e90ee3fa7fd618bf373cbbe55dff043bd67353dbb20cd8/ftfy-6.3.1.tar.gz", hash = "sha256:9b3c3d90f84fb267fe64d375a07b7f8912d817cf86009ae134aa03e1819506ec", size = 308927, upload-time = "2024-10-26T00:50:35.149Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ab/6e/81d47999aebc1b155f81eca4477a616a70f238a2549848c38983f3c22a82/ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083", size = 44821, upload-time = "2024-10-26T00:50:33.425Z" },
]
[[package]] [[package]]
name = "gevent" name = "gevent"
version = "24.10.3" version = "24.10.3"
@@ -776,14 +788,14 @@ wheels = [
[[package]] [[package]]
name = "gunicorn" name = "gunicorn"
version = "25.1.0" version = "23.0.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "packaging" }, { name = "packaging" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/66/13/ef67f59f6a7896fdc2c1d62b5665c5219d6b0a9a1784938eb9a28e55e128/gunicorn-25.1.0.tar.gz", hash = "sha256:1426611d959fa77e7de89f8c0f32eed6aa03ee735f98c01efba3e281b1c47616", size = 594377, upload-time = "2026-02-13T11:09:58.989Z" } sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/da/73/4ad5b1f6a2e21cf1e85afdaad2b7b1a933985e2f5d679147a1953aaa192c/gunicorn-25.1.0-py3-none-any.whl", hash = "sha256:d0b1236ccf27f72cfe14bce7caadf467186f19e865094ca84221424e839b8b8b", size = 197067, upload-time = "2026-02-13T11:09:57.146Z" }, { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" },
] ]
[[package]] [[package]]
@@ -927,6 +939,7 @@ source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aiocache" }, { name = "aiocache" },
{ name = "fastapi" }, { name = "fastapi" },
{ name = "ftfy" },
{ name = "gunicorn" }, { name = "gunicorn" },
{ name = "huggingface-hub" }, { name = "huggingface-hub" },
{ name = "insightface" }, { name = "insightface" },
@@ -1005,6 +1018,7 @@ types = [
requires-dist = [ requires-dist = [
{ name = "aiocache", specifier = ">=0.12.1,<1.0" }, { name = "aiocache", specifier = ">=0.12.1,<1.0" },
{ name = "fastapi", specifier = ">=0.95.2,<1.0" }, { name = "fastapi", specifier = ">=0.95.2,<1.0" },
{ name = "ftfy", specifier = ">=6.1.1" },
{ name = "gunicorn", specifier = ">=21.1.0" }, { name = "gunicorn", specifier = ">=21.1.0" },
{ name = "huggingface-hub", specifier = ">=0.20.1,<1.0" }, { name = "huggingface-hub", specifier = ">=0.20.1,<1.0" },
{ name = "insightface", specifier = ">=0.7.3,<1.0" }, { name = "insightface", specifier = ">=0.7.3,<1.0" },
+1 -1
View File
@@ -16,7 +16,7 @@ config_roots = [
[tools] [tools]
node = "24.13.1" node = "24.13.1"
flutter = "3.35.7" flutter = "3.35.7"
pnpm = "10.30.0" pnpm = "10.29.3"
terragrunt = "0.98.0" terragrunt = "0.98.0"
opentofu = "1.11.4" opentofu = "1.11.4"
java = "21.0.2" java = "21.0.2"
@@ -48,6 +48,7 @@ fun Bitmap.toNativeBuffer(): Map<String, Long> {
try { try {
val buffer = NativeBuffer.wrap(pointer, size) val buffer = NativeBuffer.wrap(pointer, size)
copyPixelsToBuffer(buffer) copyPixelsToBuffer(buffer)
recycle()
return mapOf( return mapOf(
"pointer" to pointer, "pointer" to pointer,
"width" to width.toLong(), "width" to width.toLong(),
@@ -56,9 +57,8 @@ fun Bitmap.toNativeBuffer(): Map<String, Long> {
) )
} catch (e: Exception) { } catch (e: Exception) {
NativeBuffer.free(pointer) NativeBuffer.free(pointer)
throw e
} finally {
recycle() recycle()
throw e
} }
} }
File diff suppressed because one or more lines are too long
-2
View File
@@ -18,5 +18,3 @@ enum ActionSource { timeline, viewer }
enum CleanupStep { selectDate, scan, delete } enum CleanupStep { selectDate, scan, delete }
enum AssetKeepType { none, photosOnly, videosOnly } enum AssetKeepType { none, photosOnly, videosOnly }
enum AssetDateAggregation { start, end }
@@ -73,9 +73,6 @@ enum StoreKey<T> {
autoPlayVideo<bool>._(139), autoPlayVideo<bool>._(139),
albumGridView<bool>._(140), albumGridView<bool>._(140),
// Image viewer navigation settings
tapToNavigate<bool>._(141),
// Experimental stuff // Experimental stuff
photoManagerCustomFilter<bool>._(1000), photoManagerCustomFilter<bool>._(1000),
betaPromptShown<bool>._(1001), betaPromptShown<bool>._(1001),
@@ -43,8 +43,8 @@ class RemoteAlbumService {
AlbumSortMode.title => albums.sortedBy((album) => album.name), AlbumSortMode.title => albums.sortedBy((album) => album.name),
AlbumSortMode.lastModified => albums.sortedBy((album) => album.updatedAt), AlbumSortMode.lastModified => albums.sortedBy((album) => album.updatedAt),
AlbumSortMode.assetCount => albums.sortedBy((album) => album.assetCount), AlbumSortMode.assetCount => albums.sortedBy((album) => album.assetCount),
AlbumSortMode.mostRecent => await _sortByAssetDate(albums, aggregation: AssetDateAggregation.end), AlbumSortMode.mostRecent => await _sortByNewestAsset(albums),
AlbumSortMode.mostOldest => await _sortByAssetDate(albums, aggregation: AssetDateAggregation.start), AlbumSortMode.mostOldest => await _sortByOldestAsset(albums),
}; };
final effectiveOrder = isReverse ? sortMode.defaultOrder.reverse() : sortMode.defaultOrder; final effectiveOrder = isReverse ? sortMode.defaultOrder.reverse() : sortMode.defaultOrder;
@@ -172,25 +172,46 @@ class RemoteAlbumService {
return _repository.getAlbumsContainingAsset(assetId); return _repository.getAlbumsContainingAsset(assetId);
} }
Future<List<RemoteAlbum>> _sortByAssetDate( Future<List<RemoteAlbum>> _sortByNewestAsset(List<RemoteAlbum> albums) async {
List<RemoteAlbum> albums, { // map album IDs to their newest asset dates
required AssetDateAggregation aggregation, final Map<String, Future<DateTime?>> assetTimestampFutures = {};
}) async { for (final album in albums) {
if (albums.isEmpty) return []; assetTimestampFutures[album.id] = _repository.getNewestAssetTimestamp(album.id);
final albumIds = albums.map((e) => e.id).toList();
final sortedIds = await _repository.getSortedAlbumIds(albumIds, aggregation: aggregation);
final albumMap = Map<String, RemoteAlbum>.fromEntries(albums.map((a) => MapEntry(a.id, a)));
final sortedAlbums = sortedIds.map((id) => albumMap[id]).whereType<RemoteAlbum>().toList();
if (sortedAlbums.length < albums.length) {
final returnedIdSet = sortedIds.toSet();
final emptyAlbums = albums.where((a) => !returnedIdSet.contains(a.id));
sortedAlbums.addAll(emptyAlbums);
} }
return sortedAlbums; // await all database queries
final entries = await Future.wait(
assetTimestampFutures.entries.map((entry) async => MapEntry(entry.key, await entry.value)),
);
final assetTimestamps = Map.fromEntries(entries);
final sorted = albums.sorted((a, b) {
final aDate = assetTimestamps[a.id] ?? DateTime.fromMillisecondsSinceEpoch(0);
final bDate = assetTimestamps[b.id] ?? DateTime.fromMillisecondsSinceEpoch(0);
return aDate.compareTo(bDate);
});
return sorted;
}
Future<List<RemoteAlbum>> _sortByOldestAsset(List<RemoteAlbum> albums) async {
// map album IDs to their oldest asset dates
final Map<String, Future<DateTime?>> assetTimestampFutures = {
for (final album in albums) album.id: _repository.getOldestAssetTimestamp(album.id),
};
// await all database queries
final entries = await Future.wait(
assetTimestampFutures.entries.map((entry) async => MapEntry(entry.key, await entry.value)),
);
final assetTimestamps = Map.fromEntries(entries);
final sorted = albums.sorted((a, b) {
final aDate = assetTimestamps[a.id] ?? DateTime.fromMillisecondsSinceEpoch(0);
final bDate = assetTimestamps[b.id] ?? DateTime.fromMillisecondsSinceEpoch(0);
return aDate.compareTo(bDate);
});
return sorted;
} }
} }
@@ -68,12 +68,12 @@ class SyncStreamService {
return false; return false;
} }
final serverSemVer = SemVer(major: serverVersion.major, minor: serverVersion.minor, patch: serverVersion.patch_); final semVer = SemVer(major: serverVersion.major, minor: serverVersion.minor, patch: serverVersion.patch_);
final value = Store.get(StoreKey.syncMigrationStatus, "[]"); final value = Store.get(StoreKey.syncMigrationStatus, "[]");
final migrations = (jsonDecode(value) as List).cast<String>(); final migrations = (jsonDecode(value) as List).cast<String>();
int previousLength = migrations.length; int previousLength = migrations.length;
await _runPreSyncTasks(migrations, serverSemVer); await _runPreSyncTasks(migrations, semVer);
if (migrations.length != previousLength) { if (migrations.length != previousLength) {
_logger.info("Updated pre-sync migration status: $migrations"); _logger.info("Updated pre-sync migration status: $migrations");
@@ -82,14 +82,10 @@ class SyncStreamService {
// Start the sync stream and handle events // Start the sync stream and handle events
bool shouldReset = false; bool shouldReset = false;
await _syncApiRepository.streamChanges( await _syncApiRepository.streamChanges(_handleEvents, onReset: () => shouldReset = true);
_handleEvents,
serverVersion: serverSemVer,
onReset: () => shouldReset = true,
);
if (shouldReset) { if (shouldReset) {
_logger.info("Resetting sync state as requested by server"); _logger.info("Resetting sync state as requested by server");
await _syncApiRepository.streamChanges(_handleEvents, serverVersion: serverSemVer); await _syncApiRepository.streamChanges(_handleEvents);
} }
previousLength = migrations.length; previousLength = migrations.length;
@@ -286,8 +282,6 @@ class SyncStreamService {
return _syncStreamRepository.deletePeopleV1(data.cast()); return _syncStreamRepository.deletePeopleV1(data.cast());
case SyncEntityType.assetFaceV1: case SyncEntityType.assetFaceV1:
return _syncStreamRepository.updateAssetFacesV1(data.cast()); return _syncStreamRepository.updateAssetFacesV1(data.cast());
case SyncEntityType.assetFaceV2:
return _syncStreamRepository.updateAssetFacesV2(data.cast());
case SyncEntityType.assetFaceDeleteV1: case SyncEntityType.assetFaceDeleteV1:
return _syncStreamRepository.deleteAssetFacesV1(data.cast()); return _syncStreamRepository.deleteAssetFacesV1(data.cast());
default: default:
@@ -28,10 +28,6 @@ class AssetFaceEntity extends Table with DriftDefaultsMixin {
TextColumn get sourceType => text()(); TextColumn get sourceType => text()();
BoolColumn get isVisible => boolean().withDefault(const Constant(true))();
DateTimeColumn get deletedAt => dateTime().nullable()();
@override @override
Set<Column> get primaryKey => {id}; Set<Column> get primaryKey => {id};
} }
+68 -202
View File
@@ -5,12 +5,11 @@ import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.da
as i1; as i1;
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart' import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart'
as i2; as i2;
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3;
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart' import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
as i4; as i3;
import 'package:drift/internal/modular.dart' as i5; import 'package:drift/internal/modular.dart' as i4;
import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart' import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart'
as i6; as i5;
typedef $$AssetFaceEntityTableCreateCompanionBuilder = typedef $$AssetFaceEntityTableCreateCompanionBuilder =
i1.AssetFaceEntityCompanion Function({ i1.AssetFaceEntityCompanion Function({
@@ -24,8 +23,6 @@ typedef $$AssetFaceEntityTableCreateCompanionBuilder =
required int boundingBoxX2, required int boundingBoxX2,
required int boundingBoxY2, required int boundingBoxY2,
required String sourceType, required String sourceType,
i0.Value<bool> isVisible,
i0.Value<DateTime?> deletedAt,
}); });
typedef $$AssetFaceEntityTableUpdateCompanionBuilder = typedef $$AssetFaceEntityTableUpdateCompanionBuilder =
i1.AssetFaceEntityCompanion Function({ i1.AssetFaceEntityCompanion Function({
@@ -39,8 +36,6 @@ typedef $$AssetFaceEntityTableUpdateCompanionBuilder =
i0.Value<int> boundingBoxX2, i0.Value<int> boundingBoxX2,
i0.Value<int> boundingBoxY2, i0.Value<int> boundingBoxY2,
i0.Value<String> sourceType, i0.Value<String> sourceType,
i0.Value<bool> isVisible,
i0.Value<DateTime?> deletedAt,
}); });
final class $$AssetFaceEntityTableReferences final class $$AssetFaceEntityTableReferences
@@ -56,29 +51,29 @@ final class $$AssetFaceEntityTableReferences
super.$_typedResult, super.$_typedResult,
); );
static i4.$RemoteAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) => static i3.$RemoteAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) =>
i5.ReadDatabaseContainer(db) i4.ReadDatabaseContainer(db)
.resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity') .resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity')
.createAlias( .createAlias(
i0.$_aliasNameGenerator( i0.$_aliasNameGenerator(
i5.ReadDatabaseContainer(db) i4.ReadDatabaseContainer(db)
.resultSet<i1.$AssetFaceEntityTable>('asset_face_entity') .resultSet<i1.$AssetFaceEntityTable>('asset_face_entity')
.assetId, .assetId,
i5.ReadDatabaseContainer( i4.ReadDatabaseContainer(
db, db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity').id, ).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity').id,
), ),
); );
i4.$$RemoteAssetEntityTableProcessedTableManager get assetId { i3.$$RemoteAssetEntityTableProcessedTableManager get assetId {
final $_column = $_itemColumn<String>('asset_id')!; final $_column = $_itemColumn<String>('asset_id')!;
final manager = i4 final manager = i3
.$$RemoteAssetEntityTableTableManager( .$$RemoteAssetEntityTableTableManager(
$_db, $_db,
i5.ReadDatabaseContainer( i4.ReadDatabaseContainer(
$_db, $_db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'), ).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
) )
.filter((f) => f.id.sqlEquals($_column)); .filter((f) => f.id.sqlEquals($_column));
final item = $_typedResult.readTableOrNull(_assetIdTable($_db)); final item = $_typedResult.readTableOrNull(_assetIdTable($_db));
@@ -88,29 +83,29 @@ final class $$AssetFaceEntityTableReferences
); );
} }
static i6.$PersonEntityTable _personIdTable(i0.GeneratedDatabase db) => static i5.$PersonEntityTable _personIdTable(i0.GeneratedDatabase db) =>
i5.ReadDatabaseContainer(db) i4.ReadDatabaseContainer(db)
.resultSet<i6.$PersonEntityTable>('person_entity') .resultSet<i5.$PersonEntityTable>('person_entity')
.createAlias( .createAlias(
i0.$_aliasNameGenerator( i0.$_aliasNameGenerator(
i5.ReadDatabaseContainer(db) i4.ReadDatabaseContainer(db)
.resultSet<i1.$AssetFaceEntityTable>('asset_face_entity') .resultSet<i1.$AssetFaceEntityTable>('asset_face_entity')
.personId, .personId,
i5.ReadDatabaseContainer( i4.ReadDatabaseContainer(
db, db,
).resultSet<i6.$PersonEntityTable>('person_entity').id, ).resultSet<i5.$PersonEntityTable>('person_entity').id,
), ),
); );
i6.$$PersonEntityTableProcessedTableManager? get personId { i5.$$PersonEntityTableProcessedTableManager? get personId {
final $_column = $_itemColumn<String>('person_id'); final $_column = $_itemColumn<String>('person_id');
if ($_column == null) return null; if ($_column == null) return null;
final manager = i6 final manager = i5
.$$PersonEntityTableTableManager( .$$PersonEntityTableTableManager(
$_db, $_db,
i5.ReadDatabaseContainer( i4.ReadDatabaseContainer(
$_db, $_db,
).resultSet<i6.$PersonEntityTable>('person_entity'), ).resultSet<i5.$PersonEntityTable>('person_entity'),
) )
.filter((f) => f.id.sqlEquals($_column)); .filter((f) => f.id.sqlEquals($_column));
final item = $_typedResult.readTableOrNull(_personIdTable($_db)); final item = $_typedResult.readTableOrNull(_personIdTable($_db));
@@ -170,34 +165,24 @@ class $$AssetFaceEntityTableFilterComposer
builder: (column) => i0.ColumnFilters(column), builder: (column) => i0.ColumnFilters(column),
); );
i0.ColumnFilters<bool> get isVisible => $composableBuilder( i3.$$RemoteAssetEntityTableFilterComposer get assetId {
column: $table.isVisible, final i3.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder(
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<DateTime> get deletedAt => $composableBuilder(
column: $table.deletedAt,
builder: (column) => i0.ColumnFilters(column),
);
i4.$$RemoteAssetEntityTableFilterComposer get assetId {
final i4.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder(
composer: this, composer: this,
getCurrentColumn: (t) => t.assetId, getCurrentColumn: (t) => t.assetId,
referencedTable: i5.ReadDatabaseContainer( referencedTable: i4.ReadDatabaseContainer(
$db, $db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'), ).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
getReferencedColumn: (t) => t.id, getReferencedColumn: (t) => t.id,
builder: builder:
( (
joinBuilder, { joinBuilder, {
$addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer, $removeJoinBuilderFromRootComposer,
}) => i4.$$RemoteAssetEntityTableFilterComposer( }) => i3.$$RemoteAssetEntityTableFilterComposer(
$db: $db, $db: $db,
$table: i5.ReadDatabaseContainer( $table: i4.ReadDatabaseContainer(
$db, $db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'), ).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder, joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer:
@@ -207,24 +192,24 @@ class $$AssetFaceEntityTableFilterComposer
return composer; return composer;
} }
i6.$$PersonEntityTableFilterComposer get personId { i5.$$PersonEntityTableFilterComposer get personId {
final i6.$$PersonEntityTableFilterComposer composer = $composerBuilder( final i5.$$PersonEntityTableFilterComposer composer = $composerBuilder(
composer: this, composer: this,
getCurrentColumn: (t) => t.personId, getCurrentColumn: (t) => t.personId,
referencedTable: i5.ReadDatabaseContainer( referencedTable: i4.ReadDatabaseContainer(
$db, $db,
).resultSet<i6.$PersonEntityTable>('person_entity'), ).resultSet<i5.$PersonEntityTable>('person_entity'),
getReferencedColumn: (t) => t.id, getReferencedColumn: (t) => t.id,
builder: builder:
( (
joinBuilder, { joinBuilder, {
$addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer, $removeJoinBuilderFromRootComposer,
}) => i6.$$PersonEntityTableFilterComposer( }) => i5.$$PersonEntityTableFilterComposer(
$db: $db, $db: $db,
$table: i5.ReadDatabaseContainer( $table: i4.ReadDatabaseContainer(
$db, $db,
).resultSet<i6.$PersonEntityTable>('person_entity'), ).resultSet<i5.$PersonEntityTable>('person_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder, joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer:
@@ -284,35 +269,25 @@ class $$AssetFaceEntityTableOrderingComposer
builder: (column) => i0.ColumnOrderings(column), builder: (column) => i0.ColumnOrderings(column),
); );
i0.ColumnOrderings<bool> get isVisible => $composableBuilder( i3.$$RemoteAssetEntityTableOrderingComposer get assetId {
column: $table.isVisible, final i3.$$RemoteAssetEntityTableOrderingComposer composer =
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<DateTime> get deletedAt => $composableBuilder(
column: $table.deletedAt,
builder: (column) => i0.ColumnOrderings(column),
);
i4.$$RemoteAssetEntityTableOrderingComposer get assetId {
final i4.$$RemoteAssetEntityTableOrderingComposer composer =
$composerBuilder( $composerBuilder(
composer: this, composer: this,
getCurrentColumn: (t) => t.assetId, getCurrentColumn: (t) => t.assetId,
referencedTable: i5.ReadDatabaseContainer( referencedTable: i4.ReadDatabaseContainer(
$db, $db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'), ).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
getReferencedColumn: (t) => t.id, getReferencedColumn: (t) => t.id,
builder: builder:
( (
joinBuilder, { joinBuilder, {
$addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer, $removeJoinBuilderFromRootComposer,
}) => i4.$$RemoteAssetEntityTableOrderingComposer( }) => i3.$$RemoteAssetEntityTableOrderingComposer(
$db: $db, $db: $db,
$table: i5.ReadDatabaseContainer( $table: i4.ReadDatabaseContainer(
$db, $db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'), ).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder, joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer:
@@ -322,24 +297,24 @@ class $$AssetFaceEntityTableOrderingComposer
return composer; return composer;
} }
i6.$$PersonEntityTableOrderingComposer get personId { i5.$$PersonEntityTableOrderingComposer get personId {
final i6.$$PersonEntityTableOrderingComposer composer = $composerBuilder( final i5.$$PersonEntityTableOrderingComposer composer = $composerBuilder(
composer: this, composer: this,
getCurrentColumn: (t) => t.personId, getCurrentColumn: (t) => t.personId,
referencedTable: i5.ReadDatabaseContainer( referencedTable: i4.ReadDatabaseContainer(
$db, $db,
).resultSet<i6.$PersonEntityTable>('person_entity'), ).resultSet<i5.$PersonEntityTable>('person_entity'),
getReferencedColumn: (t) => t.id, getReferencedColumn: (t) => t.id,
builder: builder:
( (
joinBuilder, { joinBuilder, {
$addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer, $removeJoinBuilderFromRootComposer,
}) => i6.$$PersonEntityTableOrderingComposer( }) => i5.$$PersonEntityTableOrderingComposer(
$db: $db, $db: $db,
$table: i5.ReadDatabaseContainer( $table: i4.ReadDatabaseContainer(
$db, $db,
).resultSet<i6.$PersonEntityTable>('person_entity'), ).resultSet<i5.$PersonEntityTable>('person_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder, joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer:
@@ -397,31 +372,25 @@ class $$AssetFaceEntityTableAnnotationComposer
builder: (column) => column, builder: (column) => column,
); );
i0.GeneratedColumn<bool> get isVisible => i3.$$RemoteAssetEntityTableAnnotationComposer get assetId {
$composableBuilder(column: $table.isVisible, builder: (column) => column); final i3.$$RemoteAssetEntityTableAnnotationComposer composer =
i0.GeneratedColumn<DateTime> get deletedAt =>
$composableBuilder(column: $table.deletedAt, builder: (column) => column);
i4.$$RemoteAssetEntityTableAnnotationComposer get assetId {
final i4.$$RemoteAssetEntityTableAnnotationComposer composer =
$composerBuilder( $composerBuilder(
composer: this, composer: this,
getCurrentColumn: (t) => t.assetId, getCurrentColumn: (t) => t.assetId,
referencedTable: i5.ReadDatabaseContainer( referencedTable: i4.ReadDatabaseContainer(
$db, $db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'), ).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
getReferencedColumn: (t) => t.id, getReferencedColumn: (t) => t.id,
builder: builder:
( (
joinBuilder, { joinBuilder, {
$addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer, $removeJoinBuilderFromRootComposer,
}) => i4.$$RemoteAssetEntityTableAnnotationComposer( }) => i3.$$RemoteAssetEntityTableAnnotationComposer(
$db: $db, $db: $db,
$table: i5.ReadDatabaseContainer( $table: i4.ReadDatabaseContainer(
$db, $db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity'), ).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder, joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer:
@@ -431,24 +400,24 @@ class $$AssetFaceEntityTableAnnotationComposer
return composer; return composer;
} }
i6.$$PersonEntityTableAnnotationComposer get personId { i5.$$PersonEntityTableAnnotationComposer get personId {
final i6.$$PersonEntityTableAnnotationComposer composer = $composerBuilder( final i5.$$PersonEntityTableAnnotationComposer composer = $composerBuilder(
composer: this, composer: this,
getCurrentColumn: (t) => t.personId, getCurrentColumn: (t) => t.personId,
referencedTable: i5.ReadDatabaseContainer( referencedTable: i4.ReadDatabaseContainer(
$db, $db,
).resultSet<i6.$PersonEntityTable>('person_entity'), ).resultSet<i5.$PersonEntityTable>('person_entity'),
getReferencedColumn: (t) => t.id, getReferencedColumn: (t) => t.id,
builder: builder:
( (
joinBuilder, { joinBuilder, {
$addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer, $removeJoinBuilderFromRootComposer,
}) => i6.$$PersonEntityTableAnnotationComposer( }) => i5.$$PersonEntityTableAnnotationComposer(
$db: $db, $db: $db,
$table: i5.ReadDatabaseContainer( $table: i4.ReadDatabaseContainer(
$db, $db,
).resultSet<i6.$PersonEntityTable>('person_entity'), ).resultSet<i5.$PersonEntityTable>('person_entity'),
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder, joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer:
@@ -499,8 +468,6 @@ class $$AssetFaceEntityTableTableManager
i0.Value<int> boundingBoxX2 = const i0.Value.absent(), i0.Value<int> boundingBoxX2 = const i0.Value.absent(),
i0.Value<int> boundingBoxY2 = const i0.Value.absent(), i0.Value<int> boundingBoxY2 = const i0.Value.absent(),
i0.Value<String> sourceType = const i0.Value.absent(), i0.Value<String> sourceType = const i0.Value.absent(),
i0.Value<bool> isVisible = const i0.Value.absent(),
i0.Value<DateTime?> deletedAt = const i0.Value.absent(),
}) => i1.AssetFaceEntityCompanion( }) => i1.AssetFaceEntityCompanion(
id: id, id: id,
assetId: assetId, assetId: assetId,
@@ -512,8 +479,6 @@ class $$AssetFaceEntityTableTableManager
boundingBoxX2: boundingBoxX2, boundingBoxX2: boundingBoxX2,
boundingBoxY2: boundingBoxY2, boundingBoxY2: boundingBoxY2,
sourceType: sourceType, sourceType: sourceType,
isVisible: isVisible,
deletedAt: deletedAt,
), ),
createCompanionCallback: createCompanionCallback:
({ ({
@@ -527,8 +492,6 @@ class $$AssetFaceEntityTableTableManager
required int boundingBoxX2, required int boundingBoxX2,
required int boundingBoxY2, required int boundingBoxY2,
required String sourceType, required String sourceType,
i0.Value<bool> isVisible = const i0.Value.absent(),
i0.Value<DateTime?> deletedAt = const i0.Value.absent(),
}) => i1.AssetFaceEntityCompanion.insert( }) => i1.AssetFaceEntityCompanion.insert(
id: id, id: id,
assetId: assetId, assetId: assetId,
@@ -540,8 +503,6 @@ class $$AssetFaceEntityTableTableManager
boundingBoxX2: boundingBoxX2, boundingBoxX2: boundingBoxX2,
boundingBoxY2: boundingBoxY2, boundingBoxY2: boundingBoxY2,
sourceType: sourceType, sourceType: sourceType,
isVisible: isVisible,
deletedAt: deletedAt,
), ),
withReferenceMapper: (p0) => p0 withReferenceMapper: (p0) => p0
.map( .map(
@@ -748,33 +709,6 @@ class $AssetFaceEntityTable extends i2.AssetFaceEntity
type: i0.DriftSqlType.string, type: i0.DriftSqlType.string,
requiredDuringInsert: true, requiredDuringInsert: true,
); );
static const i0.VerificationMeta _isVisibleMeta = const i0.VerificationMeta(
'isVisible',
);
@override
late final i0.GeneratedColumn<bool> isVisible = i0.GeneratedColumn<bool>(
'is_visible',
aliasedName,
false,
type: i0.DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
'CHECK ("is_visible" IN (0, 1))',
),
defaultValue: const i3.Constant(true),
);
static const i0.VerificationMeta _deletedAtMeta = const i0.VerificationMeta(
'deletedAt',
);
@override
late final i0.GeneratedColumn<DateTime> deletedAt =
i0.GeneratedColumn<DateTime>(
'deleted_at',
aliasedName,
true,
type: i0.DriftSqlType.dateTime,
requiredDuringInsert: false,
);
@override @override
List<i0.GeneratedColumn> get $columns => [ List<i0.GeneratedColumn> get $columns => [
id, id,
@@ -787,8 +721,6 @@ class $AssetFaceEntityTable extends i2.AssetFaceEntity
boundingBoxX2, boundingBoxX2,
boundingBoxY2, boundingBoxY2,
sourceType, sourceType,
isVisible,
deletedAt,
]; ];
@override @override
String get aliasedName => _alias ?? actualTableName; String get aliasedName => _alias ?? actualTableName;
@@ -892,18 +824,6 @@ class $AssetFaceEntityTable extends i2.AssetFaceEntity
} else if (isInserting) { } else if (isInserting) {
context.missing(_sourceTypeMeta); context.missing(_sourceTypeMeta);
} }
if (data.containsKey('is_visible')) {
context.handle(
_isVisibleMeta,
isVisible.isAcceptableOrUnknown(data['is_visible']!, _isVisibleMeta),
);
}
if (data.containsKey('deleted_at')) {
context.handle(
_deletedAtMeta,
deletedAt.isAcceptableOrUnknown(data['deleted_at']!, _deletedAtMeta),
);
}
return context; return context;
} }
@@ -953,14 +873,6 @@ class $AssetFaceEntityTable extends i2.AssetFaceEntity
i0.DriftSqlType.string, i0.DriftSqlType.string,
data['${effectivePrefix}source_type'], data['${effectivePrefix}source_type'],
)!, )!,
isVisible: attachedDatabase.typeMapping.read(
i0.DriftSqlType.bool,
data['${effectivePrefix}is_visible'],
)!,
deletedAt: attachedDatabase.typeMapping.read(
i0.DriftSqlType.dateTime,
data['${effectivePrefix}deleted_at'],
),
); );
} }
@@ -987,8 +899,6 @@ class AssetFaceEntityData extends i0.DataClass
final int boundingBoxX2; final int boundingBoxX2;
final int boundingBoxY2; final int boundingBoxY2;
final String sourceType; final String sourceType;
final bool isVisible;
final DateTime? deletedAt;
const AssetFaceEntityData({ const AssetFaceEntityData({
required this.id, required this.id,
required this.assetId, required this.assetId,
@@ -1000,8 +910,6 @@ class AssetFaceEntityData extends i0.DataClass
required this.boundingBoxX2, required this.boundingBoxX2,
required this.boundingBoxY2, required this.boundingBoxY2,
required this.sourceType, required this.sourceType,
required this.isVisible,
this.deletedAt,
}); });
@override @override
Map<String, i0.Expression> toColumns(bool nullToAbsent) { Map<String, i0.Expression> toColumns(bool nullToAbsent) {
@@ -1018,10 +926,6 @@ class AssetFaceEntityData extends i0.DataClass
map['bounding_box_x2'] = i0.Variable<int>(boundingBoxX2); map['bounding_box_x2'] = i0.Variable<int>(boundingBoxX2);
map['bounding_box_y2'] = i0.Variable<int>(boundingBoxY2); map['bounding_box_y2'] = i0.Variable<int>(boundingBoxY2);
map['source_type'] = i0.Variable<String>(sourceType); map['source_type'] = i0.Variable<String>(sourceType);
map['is_visible'] = i0.Variable<bool>(isVisible);
if (!nullToAbsent || deletedAt != null) {
map['deleted_at'] = i0.Variable<DateTime>(deletedAt);
}
return map; return map;
} }
@@ -1041,8 +945,6 @@ class AssetFaceEntityData extends i0.DataClass
boundingBoxX2: serializer.fromJson<int>(json['boundingBoxX2']), boundingBoxX2: serializer.fromJson<int>(json['boundingBoxX2']),
boundingBoxY2: serializer.fromJson<int>(json['boundingBoxY2']), boundingBoxY2: serializer.fromJson<int>(json['boundingBoxY2']),
sourceType: serializer.fromJson<String>(json['sourceType']), sourceType: serializer.fromJson<String>(json['sourceType']),
isVisible: serializer.fromJson<bool>(json['isVisible']),
deletedAt: serializer.fromJson<DateTime?>(json['deletedAt']),
); );
} }
@override @override
@@ -1059,8 +961,6 @@ class AssetFaceEntityData extends i0.DataClass
'boundingBoxX2': serializer.toJson<int>(boundingBoxX2), 'boundingBoxX2': serializer.toJson<int>(boundingBoxX2),
'boundingBoxY2': serializer.toJson<int>(boundingBoxY2), 'boundingBoxY2': serializer.toJson<int>(boundingBoxY2),
'sourceType': serializer.toJson<String>(sourceType), 'sourceType': serializer.toJson<String>(sourceType),
'isVisible': serializer.toJson<bool>(isVisible),
'deletedAt': serializer.toJson<DateTime?>(deletedAt),
}; };
} }
@@ -1075,8 +975,6 @@ class AssetFaceEntityData extends i0.DataClass
int? boundingBoxX2, int? boundingBoxX2,
int? boundingBoxY2, int? boundingBoxY2,
String? sourceType, String? sourceType,
bool? isVisible,
i0.Value<DateTime?> deletedAt = const i0.Value.absent(),
}) => i1.AssetFaceEntityData( }) => i1.AssetFaceEntityData(
id: id ?? this.id, id: id ?? this.id,
assetId: assetId ?? this.assetId, assetId: assetId ?? this.assetId,
@@ -1088,8 +986,6 @@ class AssetFaceEntityData extends i0.DataClass
boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2,
boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2,
sourceType: sourceType ?? this.sourceType, sourceType: sourceType ?? this.sourceType,
isVisible: isVisible ?? this.isVisible,
deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt,
); );
AssetFaceEntityData copyWithCompanion(i1.AssetFaceEntityCompanion data) { AssetFaceEntityData copyWithCompanion(i1.AssetFaceEntityCompanion data) {
return AssetFaceEntityData( return AssetFaceEntityData(
@@ -1117,8 +1013,6 @@ class AssetFaceEntityData extends i0.DataClass
sourceType: data.sourceType.present sourceType: data.sourceType.present
? data.sourceType.value ? data.sourceType.value
: this.sourceType, : this.sourceType,
isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible,
deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt,
); );
} }
@@ -1134,9 +1028,7 @@ class AssetFaceEntityData extends i0.DataClass
..write('boundingBoxY1: $boundingBoxY1, ') ..write('boundingBoxY1: $boundingBoxY1, ')
..write('boundingBoxX2: $boundingBoxX2, ') ..write('boundingBoxX2: $boundingBoxX2, ')
..write('boundingBoxY2: $boundingBoxY2, ') ..write('boundingBoxY2: $boundingBoxY2, ')
..write('sourceType: $sourceType, ') ..write('sourceType: $sourceType')
..write('isVisible: $isVisible, ')
..write('deletedAt: $deletedAt')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@@ -1153,8 +1045,6 @@ class AssetFaceEntityData extends i0.DataClass
boundingBoxX2, boundingBoxX2,
boundingBoxY2, boundingBoxY2,
sourceType, sourceType,
isVisible,
deletedAt,
); );
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@@ -1169,9 +1059,7 @@ class AssetFaceEntityData extends i0.DataClass
other.boundingBoxY1 == this.boundingBoxY1 && other.boundingBoxY1 == this.boundingBoxY1 &&
other.boundingBoxX2 == this.boundingBoxX2 && other.boundingBoxX2 == this.boundingBoxX2 &&
other.boundingBoxY2 == this.boundingBoxY2 && other.boundingBoxY2 == this.boundingBoxY2 &&
other.sourceType == this.sourceType && other.sourceType == this.sourceType);
other.isVisible == this.isVisible &&
other.deletedAt == this.deletedAt);
} }
class AssetFaceEntityCompanion class AssetFaceEntityCompanion
@@ -1186,8 +1074,6 @@ class AssetFaceEntityCompanion
final i0.Value<int> boundingBoxX2; final i0.Value<int> boundingBoxX2;
final i0.Value<int> boundingBoxY2; final i0.Value<int> boundingBoxY2;
final i0.Value<String> sourceType; final i0.Value<String> sourceType;
final i0.Value<bool> isVisible;
final i0.Value<DateTime?> deletedAt;
const AssetFaceEntityCompanion({ const AssetFaceEntityCompanion({
this.id = const i0.Value.absent(), this.id = const i0.Value.absent(),
this.assetId = const i0.Value.absent(), this.assetId = const i0.Value.absent(),
@@ -1199,8 +1085,6 @@ class AssetFaceEntityCompanion
this.boundingBoxX2 = const i0.Value.absent(), this.boundingBoxX2 = const i0.Value.absent(),
this.boundingBoxY2 = const i0.Value.absent(), this.boundingBoxY2 = const i0.Value.absent(),
this.sourceType = const i0.Value.absent(), this.sourceType = const i0.Value.absent(),
this.isVisible = const i0.Value.absent(),
this.deletedAt = const i0.Value.absent(),
}); });
AssetFaceEntityCompanion.insert({ AssetFaceEntityCompanion.insert({
required String id, required String id,
@@ -1213,8 +1097,6 @@ class AssetFaceEntityCompanion
required int boundingBoxX2, required int boundingBoxX2,
required int boundingBoxY2, required int boundingBoxY2,
required String sourceType, required String sourceType,
this.isVisible = const i0.Value.absent(),
this.deletedAt = const i0.Value.absent(),
}) : id = i0.Value(id), }) : id = i0.Value(id),
assetId = i0.Value(assetId), assetId = i0.Value(assetId),
imageWidth = i0.Value(imageWidth), imageWidth = i0.Value(imageWidth),
@@ -1235,8 +1117,6 @@ class AssetFaceEntityCompanion
i0.Expression<int>? boundingBoxX2, i0.Expression<int>? boundingBoxX2,
i0.Expression<int>? boundingBoxY2, i0.Expression<int>? boundingBoxY2,
i0.Expression<String>? sourceType, i0.Expression<String>? sourceType,
i0.Expression<bool>? isVisible,
i0.Expression<DateTime>? deletedAt,
}) { }) {
return i0.RawValuesInsertable({ return i0.RawValuesInsertable({
if (id != null) 'id': id, if (id != null) 'id': id,
@@ -1249,8 +1129,6 @@ class AssetFaceEntityCompanion
if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2,
if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2,
if (sourceType != null) 'source_type': sourceType, if (sourceType != null) 'source_type': sourceType,
if (isVisible != null) 'is_visible': isVisible,
if (deletedAt != null) 'deleted_at': deletedAt,
}); });
} }
@@ -1265,8 +1143,6 @@ class AssetFaceEntityCompanion
i0.Value<int>? boundingBoxX2, i0.Value<int>? boundingBoxX2,
i0.Value<int>? boundingBoxY2, i0.Value<int>? boundingBoxY2,
i0.Value<String>? sourceType, i0.Value<String>? sourceType,
i0.Value<bool>? isVisible,
i0.Value<DateTime?>? deletedAt,
}) { }) {
return i1.AssetFaceEntityCompanion( return i1.AssetFaceEntityCompanion(
id: id ?? this.id, id: id ?? this.id,
@@ -1279,8 +1155,6 @@ class AssetFaceEntityCompanion
boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2,
boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2,
sourceType: sourceType ?? this.sourceType, sourceType: sourceType ?? this.sourceType,
isVisible: isVisible ?? this.isVisible,
deletedAt: deletedAt ?? this.deletedAt,
); );
} }
@@ -1317,12 +1191,6 @@ class AssetFaceEntityCompanion
if (sourceType.present) { if (sourceType.present) {
map['source_type'] = i0.Variable<String>(sourceType.value); map['source_type'] = i0.Variable<String>(sourceType.value);
} }
if (isVisible.present) {
map['is_visible'] = i0.Variable<bool>(isVisible.value);
}
if (deletedAt.present) {
map['deleted_at'] = i0.Variable<DateTime>(deletedAt.value);
}
return map; return map;
} }
@@ -1338,9 +1206,7 @@ class AssetFaceEntityCompanion
..write('boundingBoxY1: $boundingBoxY1, ') ..write('boundingBoxY1: $boundingBoxY1, ')
..write('boundingBoxX2: $boundingBoxX2, ') ..write('boundingBoxX2: $boundingBoxX2, ')
..write('boundingBoxY2: $boundingBoxY2, ') ..write('boundingBoxY2: $boundingBoxY2, ')
..write('sourceType: $sourceType, ') ..write('sourceType: $sourceType')
..write('isVisible: $isVisible, ')
..write('deletedAt: $deletedAt')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@@ -97,7 +97,7 @@ class Drift extends $Drift implements IDatabaseRepository {
} }
@override @override
int get schemaVersion => 20; int get schemaVersion => 19;
@override @override
MigrationStrategy get migration => MigrationStrategy( MigrationStrategy get migration => MigrationStrategy(
@@ -226,10 +226,6 @@ class Drift extends $Drift implements IDatabaseRepository {
await m.createIndex(v19.idxRemoteAssetLocalDateTimeMonth); await m.createIndex(v19.idxRemoteAssetLocalDateTimeMonth);
await m.createIndex(v19.idxStackPrimaryAssetId); await m.createIndex(v19.idxStackPrimaryAssetId);
}, },
from19To20: (m, v20) async {
await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.isVisible);
await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.deletedAt);
},
), ),
); );
@@ -8360,550 +8360,6 @@ final class Schema19 extends i0.VersionedSchema {
); );
} }
final class Schema20 extends i0.VersionedSchema {
Schema20({required super.database}) : super(version: 20);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
userEntity,
remoteAssetEntity,
stackEntity,
localAssetEntity,
remoteAlbumEntity,
localAlbumEntity,
localAlbumAssetEntity,
idxLocalAlbumAssetAlbumAsset,
idxRemoteAlbumOwnerId,
idxLocalAssetChecksum,
idxLocalAssetCloudId,
idxStackPrimaryAssetId,
idxRemoteAssetOwnerChecksum,
uQRemoteAssetsOwnerChecksum,
uQRemoteAssetsOwnerLibraryChecksum,
idxRemoteAssetChecksum,
idxRemoteAssetStackId,
idxRemoteAssetLocalDateTimeDay,
idxRemoteAssetLocalDateTimeMonth,
authUserEntity,
userMetadataEntity,
partnerEntity,
remoteExifEntity,
remoteAlbumAssetEntity,
remoteAlbumUserEntity,
remoteAssetCloudIdEntity,
memoryEntity,
memoryAssetEntity,
personEntity,
assetFaceEntity,
storeEntity,
trashedLocalAssetEntity,
idxPartnerSharedWithId,
idxLatLng,
idxRemoteAlbumAssetAlbumAsset,
idxRemoteAssetCloudId,
idxPersonOwnerId,
idxAssetFacePersonId,
idxAssetFaceAssetId,
idxTrashedLocalAssetChecksum,
idxTrashedLocalAssetAlbum,
];
late final Shape20 userEntity = Shape20(
source: i0.VersionedTable(
entityName: 'user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_1,
_column_3,
_column_84,
_column_85,
_column_91,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape28 remoteAssetEntity = Shape28(
source: i0.VersionedTable(
entityName: 'remote_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_1,
_column_8,
_column_9,
_column_5,
_column_10,
_column_11,
_column_12,
_column_0,
_column_13,
_column_14,
_column_15,
_column_16,
_column_17,
_column_18,
_column_19,
_column_20,
_column_21,
_column_86,
_column_101,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape3 stackEntity = Shape3(
source: i0.VersionedTable(
entityName: 'stack_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [_column_0, _column_9, _column_5, _column_15, _column_75],
attachedDatabase: database,
),
alias: null,
);
late final Shape26 localAssetEntity = Shape26(
source: i0.VersionedTable(
entityName: 'local_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_1,
_column_8,
_column_9,
_column_5,
_column_10,
_column_11,
_column_12,
_column_0,
_column_22,
_column_14,
_column_23,
_column_98,
_column_96,
_column_46,
_column_47,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape9 remoteAlbumEntity = Shape9(
source: i0.VersionedTable(
entityName: 'remote_album_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_1,
_column_56,
_column_9,
_column_5,
_column_15,
_column_57,
_column_58,
_column_59,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape19 localAlbumEntity = Shape19(
source: i0.VersionedTable(
entityName: 'local_album_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_1,
_column_5,
_column_31,
_column_32,
_column_90,
_column_33,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape22 localAlbumAssetEntity = Shape22(
source: i0.VersionedTable(
entityName: 'local_album_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
columns: [_column_34, _column_35, _column_33],
attachedDatabase: database,
),
alias: null,
);
final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index(
'idx_local_album_asset_album_asset',
'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)',
);
final i1.Index idxRemoteAlbumOwnerId = i1.Index(
'idx_remote_album_owner_id',
'CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)',
);
final i1.Index idxLocalAssetChecksum = i1.Index(
'idx_local_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
);
final i1.Index idxLocalAssetCloudId = i1.Index(
'idx_local_asset_cloud_id',
'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)',
);
final i1.Index idxStackPrimaryAssetId = i1.Index(
'idx_stack_primary_asset_id',
'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)',
);
final i1.Index idxRemoteAssetOwnerChecksum = i1.Index(
'idx_remote_asset_owner_checksum',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)',
);
final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
'UQ_remote_assets_owner_checksum',
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
);
final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
'UQ_remote_assets_owner_library_checksum',
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
);
final i1.Index idxRemoteAssetChecksum = i1.Index(
'idx_remote_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
);
final i1.Index idxRemoteAssetStackId = i1.Index(
'idx_remote_asset_stack_id',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)',
);
final i1.Index idxRemoteAssetLocalDateTimeDay = i1.Index(
'idx_remote_asset_local_date_time_day',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))',
);
final i1.Index idxRemoteAssetLocalDateTimeMonth = i1.Index(
'idx_remote_asset_local_date_time_month',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))',
);
late final Shape21 authUserEntity = Shape21(
source: i0.VersionedTable(
entityName: 'auth_user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_1,
_column_3,
_column_2,
_column_84,
_column_85,
_column_92,
_column_93,
_column_7,
_column_94,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape4 userMetadataEntity = Shape4(
source: i0.VersionedTable(
entityName: 'user_metadata_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(user_id, "key")'],
columns: [_column_25, _column_26, _column_27],
attachedDatabase: database,
),
alias: null,
);
late final Shape5 partnerEntity = Shape5(
source: i0.VersionedTable(
entityName: 'partner_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
columns: [_column_28, _column_29, _column_30],
attachedDatabase: database,
),
alias: null,
);
late final Shape8 remoteExifEntity = Shape8(
source: i0.VersionedTable(
entityName: 'remote_exif_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id)'],
columns: [
_column_36,
_column_37,
_column_38,
_column_39,
_column_40,
_column_41,
_column_11,
_column_10,
_column_42,
_column_43,
_column_44,
_column_45,
_column_46,
_column_47,
_column_48,
_column_49,
_column_50,
_column_51,
_column_52,
_column_53,
_column_54,
_column_55,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape7 remoteAlbumAssetEntity = Shape7(
source: i0.VersionedTable(
entityName: 'remote_album_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
columns: [_column_36, _column_60],
attachedDatabase: database,
),
alias: null,
);
late final Shape10 remoteAlbumUserEntity = Shape10(
source: i0.VersionedTable(
entityName: 'remote_album_user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
columns: [_column_60, _column_25, _column_61],
attachedDatabase: database,
),
alias: null,
);
late final Shape27 remoteAssetCloudIdEntity = Shape27(
source: i0.VersionedTable(
entityName: 'remote_asset_cloud_id_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id)'],
columns: [
_column_36,
_column_99,
_column_100,
_column_96,
_column_46,
_column_47,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape11 memoryEntity = Shape11(
source: i0.VersionedTable(
entityName: 'memory_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_9,
_column_5,
_column_18,
_column_15,
_column_8,
_column_62,
_column_63,
_column_64,
_column_65,
_column_66,
_column_67,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape12 memoryAssetEntity = Shape12(
source: i0.VersionedTable(
entityName: 'memory_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
columns: [_column_36, _column_68],
attachedDatabase: database,
),
alias: null,
);
late final Shape14 personEntity = Shape14(
source: i0.VersionedTable(
entityName: 'person_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_9,
_column_5,
_column_15,
_column_1,
_column_69,
_column_71,
_column_72,
_column_73,
_column_74,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape29 assetFaceEntity = Shape29(
source: i0.VersionedTable(
entityName: 'asset_face_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_36,
_column_76,
_column_77,
_column_78,
_column_79,
_column_80,
_column_81,
_column_82,
_column_83,
_column_102,
_column_18,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape18 storeEntity = Shape18(
source: i0.VersionedTable(
entityName: 'store_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [_column_87, _column_88, _column_89],
attachedDatabase: database,
),
alias: null,
);
late final Shape25 trashedLocalAssetEntity = Shape25(
source: i0.VersionedTable(
entityName: 'trashed_local_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id, album_id)'],
columns: [
_column_1,
_column_8,
_column_9,
_column_5,
_column_10,
_column_11,
_column_12,
_column_0,
_column_95,
_column_22,
_column_14,
_column_23,
_column_97,
],
attachedDatabase: database,
),
alias: null,
);
final i1.Index idxPartnerSharedWithId = i1.Index(
'idx_partner_shared_with_id',
'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)',
);
final i1.Index idxLatLng = i1.Index(
'idx_lat_lng',
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
);
final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index(
'idx_remote_album_asset_album_asset',
'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)',
);
final i1.Index idxRemoteAssetCloudId = i1.Index(
'idx_remote_asset_cloud_id',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)',
);
final i1.Index idxPersonOwnerId = i1.Index(
'idx_person_owner_id',
'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)',
);
final i1.Index idxAssetFacePersonId = i1.Index(
'idx_asset_face_person_id',
'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)',
);
final i1.Index idxAssetFaceAssetId = i1.Index(
'idx_asset_face_asset_id',
'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)',
);
final i1.Index idxTrashedLocalAssetChecksum = i1.Index(
'idx_trashed_local_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)',
);
final i1.Index idxTrashedLocalAssetAlbum = i1.Index(
'idx_trashed_local_asset_album',
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)',
);
}
class Shape29 extends i0.VersionedTable {
Shape29({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get id =>
columnsByName['id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get assetId =>
columnsByName['asset_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get personId =>
columnsByName['person_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get imageWidth =>
columnsByName['image_width']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get imageHeight =>
columnsByName['image_height']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get boundingBoxX1 =>
columnsByName['bounding_box_x1']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get boundingBoxY1 =>
columnsByName['bounding_box_y1']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get boundingBoxX2 =>
columnsByName['bounding_box_x2']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get boundingBoxY2 =>
columnsByName['bounding_box_y2']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get sourceType =>
columnsByName['source_type']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<bool> get isVisible =>
columnsByName['is_visible']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<DateTime> get deletedAt =>
columnsByName['deleted_at']! as i1.GeneratedColumn<DateTime>;
}
i1.GeneratedColumn<bool> _column_102(String aliasedName) =>
i1.GeneratedColumn<bool>(
'is_visible',
aliasedName,
false,
type: i1.DriftSqlType.bool,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'CHECK ("is_visible" IN (0, 1))',
),
defaultValue: const CustomExpression('1'),
);
i0.MigrationStepWithVersion migrationSteps({ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2, required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3, required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
@@ -8923,7 +8379,6 @@ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema17 schema) from16To17, required Future<void> Function(i1.Migrator m, Schema17 schema) from16To17,
required Future<void> Function(i1.Migrator m, Schema18 schema) from17To18, required Future<void> Function(i1.Migrator m, Schema18 schema) from17To18,
required Future<void> Function(i1.Migrator m, Schema19 schema) from18To19, required Future<void> Function(i1.Migrator m, Schema19 schema) from18To19,
required Future<void> Function(i1.Migrator m, Schema20 schema) from19To20,
}) { }) {
return (currentVersion, database) async { return (currentVersion, database) async {
switch (currentVersion) { switch (currentVersion) {
@@ -9017,11 +8472,6 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema); final migrator = i1.Migrator(database, schema);
await from18To19(migrator, schema); await from18To19(migrator, schema);
return 19; return 19;
case 19:
final schema = Schema20(database: database);
final migrator = i1.Migrator(database, schema);
await from19To20(migrator, schema);
return 20;
default: default:
throw ArgumentError.value('Unknown migration from $currentVersion'); throw ArgumentError.value('Unknown migration from $currentVersion');
} }
@@ -9047,7 +8497,6 @@ i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema17 schema) from16To17, required Future<void> Function(i1.Migrator m, Schema17 schema) from16To17,
required Future<void> Function(i1.Migrator m, Schema18 schema) from17To18, required Future<void> Function(i1.Migrator m, Schema18 schema) from17To18,
required Future<void> Function(i1.Migrator m, Schema19 schema) from18To19, required Future<void> Function(i1.Migrator m, Schema19 schema) from18To19,
required Future<void> Function(i1.Migrator m, Schema20 schema) from19To20,
}) => i0.VersionedSchema.stepByStepHelper( }) => i0.VersionedSchema.stepByStepHelper(
step: migrationSteps( step: migrationSteps(
from1To2: from1To2, from1To2: from1To2,
@@ -9068,6 +8517,5 @@ i1.OnUpgrade stepByStep({
from16To17: from16To17, from16To17: from16To17,
from17To18: from17To18, from17To18: from17To18,
from18To19: from18To19, from18To19: from18To19,
from19To20: from19To20,
), ),
); );
@@ -184,8 +184,7 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
} }
if (keepFavorites) { if (keepFavorites) {
whereClause = whereClause = whereClause & _db.localAssetEntity.isFavorite.equals(false);
whereClause & _db.localAssetEntity.isFavorite.equals(false) & _db.remoteAssetEntity.isFavorite.equals(false);
} }
query.where(whereClause); query.where(whereClause);
@@ -16,15 +16,9 @@ class DriftPeopleRepository extends DriftDatabaseRepository {
} }
Future<List<DriftPerson>> getAssetPeople(String assetId) async { Future<List<DriftPerson>> getAssetPeople(String assetId) async {
final query = final query = _db.select(_db.assetFaceEntity).join([
_db.select(_db.assetFaceEntity).join([ innerJoin(_db.personEntity, _db.personEntity.id.equalsExp(_db.assetFaceEntity.personId)),
innerJoin(_db.personEntity, _db.personEntity.id.equalsExp(_db.assetFaceEntity.personId)), ])..where(_db.assetFaceEntity.assetId.equals(assetId) & _db.personEntity.isHidden.equals(false));
])..where(
_db.assetFaceEntity.assetId.equals(assetId) &
_db.assetFaceEntity.isVisible.equals(true) &
_db.assetFaceEntity.deletedAt.isNull() &
_db.personEntity.isHidden.equals(false),
);
return query.map((row) { return query.map((row) {
final person = row.readTable(_db.personEntity); final person = row.readTable(_db.personEntity);
@@ -45,9 +39,7 @@ class DriftPeopleRepository extends DriftDatabaseRepository {
..where( ..where(
people.isHidden.equals(false) & people.isHidden.equals(false) &
assets.deletedAt.isNull() & assets.deletedAt.isNull() &
assets.visibility.equalsValue(AssetVisibility.timeline) & assets.visibility.equalsValue(AssetVisibility.timeline),
faces.isVisible.equals(true) &
faces.deletedAt.isNull(),
) )
..groupBy([people.id], having: faces.id.count().isBiggerOrEqualValue(3) | people.name.equals('').not()) ..groupBy([people.id], having: faces.id.count().isBiggerOrEqualValue(3) | people.name.equals('').not())
..orderBy([ ..orderBy([
@@ -1,8 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
@@ -323,32 +321,26 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
}).watchSingleOrNull(); }).watchSingleOrNull();
} }
Future<List<String>> getSortedAlbumIds(List<String> albumIds, {required AssetDateAggregation aggregation}) async { Future<DateTime?> getNewestAssetTimestamp(String albumId) {
if (albumIds.isEmpty) return []; final query = _db.remoteAlbumAssetEntity.selectOnly()
..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId))
..addColumns([_db.remoteAssetEntity.localDateTime.max()])
..join([
innerJoin(_db.remoteAssetEntity, _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId)),
]);
final jsonIds = jsonEncode(albumIds); return query.map((row) => row.read(_db.remoteAssetEntity.localDateTime.max())).getSingleOrNull();
final sqlAgg = aggregation == AssetDateAggregation.start ? 'MIN' : 'MAX'; }
final rows = await _db Future<DateTime?> getOldestAssetTimestamp(String albumId) {
.customSelect( final query = _db.remoteAlbumAssetEntity.selectOnly()
''' ..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId))
SELECT ..addColumns([_db.remoteAssetEntity.localDateTime.min()])
raae.album_id, ..join([
$sqlAgg(rae.local_date_time) AS asset_date innerJoin(_db.remoteAssetEntity, _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId)),
FROM json_each(?) ids ]);
INNER JOIN remote_album_asset_entity raae
ON raae.album_id = ids.value
INNER JOIN remote_asset_entity rae
ON rae.id = raae.asset_id
GROUP BY raae.album_id
ORDER BY asset_date ASC
''',
variables: [Variable<String>(jsonIds)],
readsFrom: {_db.remoteAlbumAssetEntity, _db.remoteAssetEntity},
)
.get();
return rows.map((row) => row.read<String>('album_id')).toList(); return query.map((row) => row.read(_db.remoteAssetEntity.localDateTime.min())).getSingleOrNull();
} }
Future<int> getCount() { Future<int> getCount() {
@@ -7,7 +7,6 @@ import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/utils/semver.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
@@ -26,7 +25,6 @@ class SyncApiRepository {
Future<void> streamChanges( Future<void> streamChanges(
Future<void> Function(List<SyncEvent>, Function() abort, Function() reset) onData, { Future<void> Function(List<SyncEvent>, Function() abort, Function() reset) onData, {
required SemVer serverVersion,
Function()? onReset, Function()? onReset,
int batchSize = kSyncEventBatchSize, int batchSize = kSyncEventBatchSize,
http.Client? httpClient, http.Client? httpClient,
@@ -66,8 +64,7 @@ class SyncApiRepository {
SyncRequestType.partnerStacksV1, SyncRequestType.partnerStacksV1,
SyncRequestType.userMetadataV1, SyncRequestType.userMetadataV1,
SyncRequestType.peopleV1, SyncRequestType.peopleV1,
if (serverVersion < const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetFacesV1, SyncRequestType.assetFacesV1,
if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetFacesV2,
], ],
reset: shouldReset, reset: shouldReset,
).toJson(), ).toJson(),
@@ -193,7 +190,6 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
SyncEntityType.personV1: SyncPersonV1.fromJson, SyncEntityType.personV1: SyncPersonV1.fromJson,
SyncEntityType.personDeleteV1: SyncPersonDeleteV1.fromJson, SyncEntityType.personDeleteV1: SyncPersonDeleteV1.fromJson,
SyncEntityType.assetFaceV1: SyncAssetFaceV1.fromJson, SyncEntityType.assetFaceV1: SyncAssetFaceV1.fromJson,
SyncEntityType.assetFaceV2: SyncAssetFaceV2.fromJson,
SyncEntityType.assetFaceDeleteV1: SyncAssetFaceDeleteV1.fromJson, SyncEntityType.assetFaceDeleteV1: SyncAssetFaceDeleteV1.fromJson,
SyncEntityType.syncCompleteV1: _SyncEmptyDto.fromJson, SyncEntityType.syncCompleteV1: _SyncEmptyDto.fromJson,
}; };
@@ -652,37 +652,6 @@ class SyncStreamRepository extends DriftDatabaseRepository {
} }
} }
Future<void> updateAssetFacesV2(Iterable<SyncAssetFaceV2> data) async {
try {
await _db.batch((batch) {
for (final assetFace in data) {
final companion = AssetFaceEntityCompanion(
assetId: Value(assetFace.assetId),
personId: Value(assetFace.personId),
imageWidth: Value(assetFace.imageWidth),
imageHeight: Value(assetFace.imageHeight),
boundingBoxX1: Value(assetFace.boundingBoxX1),
boundingBoxY1: Value(assetFace.boundingBoxY1),
boundingBoxX2: Value(assetFace.boundingBoxX2),
boundingBoxY2: Value(assetFace.boundingBoxY2),
sourceType: Value(assetFace.sourceType),
deletedAt: Value(assetFace.deletedAt),
isVisible: Value(assetFace.isVisible),
);
batch.insert(
_db.assetFaceEntity,
companion.copyWith(id: Value(assetFace.id)),
onConflict: DoUpdate((_) => companion),
);
}
});
} catch (error, stack) {
_logger.severe('Error: updateAssetFacesV2', error, stack);
rethrow;
}
}
Future<void> deleteAssetFacesV1(Iterable<SyncAssetFaceDeleteV1> data) async { Future<void> deleteAssetFacesV1(Iterable<SyncAssetFaceDeleteV1> data) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
@@ -323,7 +323,6 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
row.deletedAt.isNull() & row.ownerId.equals(userId) & row.visibility.equalsValue(AssetVisibility.archive), row.deletedAt.isNull() & row.ownerId.equals(userId) & row.visibility.equalsValue(AssetVisibility.archive),
groupBy: groupBy, groupBy: groupBy,
origin: TimelineOrigin.archive, origin: TimelineOrigin.archive,
joinLocal: true,
); );
TimelineQuery locked(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder( TimelineQuery locked(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
@@ -422,9 +421,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAssetEntity.deletedAt.isNull() &
_db.remoteAssetEntity.ownerId.equals(userId) & _db.remoteAssetEntity.ownerId.equals(userId) &
_db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) & _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) &
_db.assetFaceEntity.personId.equals(personId) & _db.assetFaceEntity.personId.equals(personId),
_db.assetFaceEntity.isVisible.equals(true) &
_db.assetFaceEntity.deletedAt.isNull(),
); );
return query.map((row) { return query.map((row) {
@@ -449,9 +446,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAssetEntity.deletedAt.isNull() &
_db.remoteAssetEntity.ownerId.equals(userId) & _db.remoteAssetEntity.ownerId.equals(userId) &
_db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) & _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) &
_db.assetFaceEntity.personId.equals(personId) & _db.assetFaceEntity.personId.equals(personId),
_db.assetFaceEntity.isVisible.equals(true) &
_db.assetFaceEntity.deletedAt.isNull(),
) )
..groupBy([dateExp]) ..groupBy([dateExp])
..orderBy([OrderingTerm.desc(dateExp)]); ..orderBy([OrderingTerm.desc(dateExp)]);
@@ -481,9 +476,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAssetEntity.deletedAt.isNull() &
_db.remoteAssetEntity.ownerId.equals(userId) & _db.remoteAssetEntity.ownerId.equals(userId) &
_db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) & _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) &
_db.assetFaceEntity.personId.equals(personId) & _db.assetFaceEntity.personId.equals(personId),
_db.assetFaceEntity.isVisible.equals(true) &
_db.assetFaceEntity.deletedAt.isNull(),
) )
..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)]) ..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)])
..limit(count, offset: offset); ..limit(count, offset: offset);
@@ -14,7 +14,6 @@ class SharedLink {
final String key; final String key;
final bool showMetadata; final bool showMetadata;
final SharedLinkSource type; final SharedLinkSource type;
final String? slug;
const SharedLink({ const SharedLink({
required this.id, required this.id,
@@ -28,7 +27,6 @@ class SharedLink {
required this.key, required this.key,
required this.showMetadata, required this.showMetadata,
required this.type, required this.type,
required this.slug,
}); });
SharedLink copyWith({ SharedLink copyWith({
@@ -43,7 +41,6 @@ class SharedLink {
String? key, String? key,
bool? showMetadata, bool? showMetadata,
SharedLinkSource? type, SharedLinkSource? type,
String? slug,
}) { }) {
return SharedLink( return SharedLink(
id: id ?? this.id, id: id ?? this.id,
@@ -57,7 +54,6 @@ class SharedLink {
key: key ?? this.key, key: key ?? this.key,
showMetadata: showMetadata ?? this.showMetadata, showMetadata: showMetadata ?? this.showMetadata,
type: type ?? this.type, type: type ?? this.type,
slug: slug ?? this.slug,
); );
} }
@@ -70,7 +66,6 @@ class SharedLink {
expiresAt = dto.expiresAt, expiresAt = dto.expiresAt,
key = dto.key, key = dto.key,
showMetadata = dto.showMetadata, showMetadata = dto.showMetadata,
slug = dto.slug,
type = dto.type == SharedLinkType.ALBUM ? SharedLinkSource.album : SharedLinkSource.individual, type = dto.type == SharedLinkType.ALBUM ? SharedLinkSource.album : SharedLinkSource.individual,
title = dto.type == SharedLinkType.ALBUM title = dto.type == SharedLinkType.ALBUM
? dto.album?.albumName.toUpperCase() ?? "UNKNOWN SHARE" ? dto.album?.albumName.toUpperCase() ?? "UNKNOWN SHARE"
@@ -83,7 +78,7 @@ class SharedLink {
@override @override
String toString() => String toString() =>
'SharedLink(id=$id, title=$title, thumbAssetId=$thumbAssetId, allowDownload=$allowDownload, allowUpload=$allowUpload, description=$description, password=$password, expiresAt=$expiresAt, key=$key, showMetadata=$showMetadata, type=$type, slug=$slug)'; 'SharedLink(id=$id, title=$title, thumbAssetId=$thumbAssetId, allowDownload=$allowDownload, allowUpload=$allowUpload, description=$description, password=$password, expiresAt=$expiresAt, key=$key, showMetadata=$showMetadata, type=$type)';
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@@ -99,8 +94,7 @@ class SharedLink {
other.expiresAt == expiresAt && other.expiresAt == expiresAt &&
other.key == key && other.key == key &&
other.showMetadata == showMetadata && other.showMetadata == showMetadata &&
other.type == type && other.type == type;
other.slug == slug;
@override @override
int get hashCode => int get hashCode =>
@@ -114,6 +108,5 @@ class SharedLink {
expiresAt.hashCode ^ expiresAt.hashCode ^
key.hashCode ^ key.hashCode ^
showMetadata.hashCode ^ showMetadata.hashCode ^
type.hashCode ^ type.hashCode;
slug.hashCode;
} }
@@ -221,37 +221,8 @@ class GalleryViewerPage extends HookConsumerWidget {
onDragUpdate: (_, details, __) { onDragUpdate: (_, details, __) {
handleSwipeUpDown(details); handleSwipeUpDown(details);
}, },
onTapDown: (ctx, tapDownDetails, _) { onTapDown: (_, __, ___) {
final tapToNavigate = ref.read(appSettingsServiceProvider).getSetting<bool>(AppSettingsEnum.tapToNavigate); ref.read(showControlsProvider.notifier).toggle();
if (!tapToNavigate) {
ref.read(showControlsProvider.notifier).toggle();
return;
}
double tapX = tapDownDetails.globalPosition.dx;
double screenWidth = ctx.width;
// We want to change images if the user taps in the leftmost or
// rightmost quarter of the screen
bool tappedLeftSide = tapX < screenWidth / 4;
bool tappedRightSide = tapX > screenWidth * (3 / 4);
int? currentPage = controller.page?.toInt();
int maxPage = renderList.totalAssets - 1;
if (tappedLeftSide && currentPage != null) {
// Nested if because we don't want to fallback to show/hide controls
if (currentPage != 0) {
controller.jumpToPage(currentPage - 1);
}
} else if (tappedRightSide && currentPage != null) {
// Nested if because we don't want to fallback to show/hide controls
if (currentPage != maxPage) {
controller.jumpToPage(currentPage + 1);
}
} else {
ref.read(showControlsProvider.notifier).toggle();
}
}, },
onLongPressStart: asset.isMotionPhoto onLongPressStart: asset.isMotionPhoto
? (_, __, ___) { ? (_, __, ___) {
@@ -109,43 +109,9 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
if (context.router.current.name == SplashScreenRoute.name) { if (context.router.current.name == SplashScreenRoute.name) {
final needBetaMigration = Store.get(StoreKey.needBetaMigration, false); final needBetaMigration = Store.get(StoreKey.needBetaMigration, false);
if (needBetaMigration) { if (needBetaMigration) {
bool migrate =
(await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text("New Timeline Experience"),
content: const Text(
"The old timeline has been deprecated and will be removed in an upcoming release. Would you like to switch to the new timeline now?",
),
actions: [
TextButton(onPressed: () => Navigator.of(ctx).pop(false), child: const Text("No")),
ElevatedButton(onPressed: () => Navigator.of(ctx).pop(true), child: const Text("Yes")),
],
),
)) ??
false;
if (migrate != true) {
migrate =
(await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text("Are you sure?"),
content: const Text(
"If you choose to remain on the old timeline, you will be automatically migrated to the new timeline in an upcoming release. Would you like to switch now?",
),
actions: [
TextButton(onPressed: () => Navigator.of(ctx).pop(false), child: const Text("No")),
ElevatedButton(onPressed: () => Navigator.of(ctx).pop(true), child: const Text("Yes")),
],
),
)) ??
false;
}
await Store.put(StoreKey.needBetaMigration, false); await Store.put(StoreKey.needBetaMigration, false);
if (migrate) { unawaited(context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: true)]));
unawaited(context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: true)])); return;
return;
}
} }
unawaited(context.replaceRoute(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute())); unawaited(context.replaceRoute(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute()));
+20 -2
View File
@@ -1,4 +1,6 @@
import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
@@ -10,7 +12,6 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/album/album.provider.dart';
import 'package:immich_mobile/repositories/file_media.repository.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/utils/image_converter.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
@@ -29,10 +30,27 @@ class EditImagePage extends ConsumerWidget {
final bool isEdited; final bool isEdited;
const EditImagePage({super.key, required this.asset, required this.image, required this.isEdited}); const EditImagePage({super.key, required this.asset, required this.image, required this.isEdited});
Future<Uint8List> _imageToUint8List(Image image) async {
final Completer<Uint8List> completer = Completer();
image.image
.resolve(const ImageConfiguration())
.addListener(
ImageStreamListener((ImageInfo info, bool _) {
info.image.toByteData(format: ImageByteFormat.png).then((byteData) {
if (byteData != null) {
completer.complete(byteData.buffer.asUint8List());
} else {
completer.completeError('Failed to convert image to bytes');
}
});
}, onError: (exception, stackTrace) => completer.completeError(exception)),
);
return completer.future;
}
Future<void> _saveEditedImage(BuildContext context, Asset asset, Image image, WidgetRef ref) async { Future<void> _saveEditedImage(BuildContext context, Asset asset, Image image, WidgetRef ref) async {
try { try {
final Uint8List imageData = await imageToUint8List(image); final Uint8List imageData = await _imageToUint8List(image);
await ref await ref
.read(fileMediaRepositoryProvider) .read(fileMediaRepositoryProvider)
.saveImage(imageData, title: "${p.withoutExtension(asset.fileName)}_edited.jpg"); .saveImage(imageData, title: "${p.withoutExtension(asset.fileName)}_edited.jpg");
@@ -29,8 +29,6 @@ class SharedLinkEditPage extends HookConsumerWidget {
final descriptionController = useTextEditingController(text: existingLink?.description ?? ""); final descriptionController = useTextEditingController(text: existingLink?.description ?? "");
final descriptionFocusNode = useFocusNode(); final descriptionFocusNode = useFocusNode();
final passwordController = useTextEditingController(text: existingLink?.password ?? ""); final passwordController = useTextEditingController(text: existingLink?.password ?? "");
final slugController = useTextEditingController(text: existingLink?.slug ?? "");
final slugFocusNode = useFocusNode();
final showMetadata = useState(existingLink?.showMetadata ?? true); final showMetadata = useState(existingLink?.showMetadata ?? true);
final allowDownload = useState(existingLink?.allowDownload ?? true); final allowDownload = useState(existingLink?.allowDownload ?? true);
final allowUpload = useState(existingLink?.allowUpload ?? false); final allowUpload = useState(existingLink?.allowUpload ?? false);
@@ -110,26 +108,6 @@ class SharedLinkEditPage extends HookConsumerWidget {
); );
} }
Widget buildSlugField() {
return TextField(
controller: slugController,
enabled: newShareLink.value.isEmpty,
focusNode: slugFocusNode,
textInputAction: TextInputAction.done,
autofocus: false,
decoration: InputDecoration(
labelText: 'custom_url'.tr(),
labelStyle: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary),
floatingLabelBehavior: FloatingLabelBehavior.always,
border: const OutlineInputBorder(),
hintText: 'custom_url'.tr(),
hintStyle: const TextStyle(fontWeight: FontWeight.normal, fontSize: 14),
disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5))),
),
onTapOutside: (_) => slugFocusNode.unfocus(),
);
}
Widget buildShowMetaButton() { Widget buildShowMetaButton() {
return SwitchListTile.adaptive( return SwitchListTile.adaptive(
value: showMetadata.value, value: showMetadata.value,
@@ -283,7 +261,6 @@ class SharedLinkEditPage extends HookConsumerWidget {
allowUpload: allowUpload.value, allowUpload: allowUpload.value,
description: descriptionController.text.isEmpty ? null : descriptionController.text, description: descriptionController.text.isEmpty ? null : descriptionController.text,
password: passwordController.text.isEmpty ? null : passwordController.text, password: passwordController.text.isEmpty ? null : passwordController.text,
slug: slugController.text.isEmpty ? null : slugController.text,
expiresAt: expiryAfter.value == 0 ? null : calculateExpiry(), expiresAt: expiryAfter.value == 0 ? null : calculateExpiry(),
); );
ref.invalidate(sharedLinksStateProvider); ref.invalidate(sharedLinksStateProvider);
@@ -297,10 +274,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
} }
if (newLink != null && serverUrl != null) { if (newLink != null && serverUrl != null) {
final hasSlug = newLink.slug?.isNotEmpty == true; newShareLink.value = "${serverUrl}share/${newLink.key}";
final urlPath = hasSlug ? newLink.slug : newLink.key;
final basePath = hasSlug ? 's' : 'share';
newShareLink.value = "$serverUrl$basePath/$urlPath";
copyLinkToClipboard(); copyLinkToClipboard();
} else if (newLink == null) { } else if (newLink == null) {
ImmichToast.show( ImmichToast.show(
@@ -318,7 +292,6 @@ class SharedLinkEditPage extends HookConsumerWidget {
bool? meta; bool? meta;
String? desc; String? desc;
String? password; String? password;
String? slug;
DateTime? expiry; DateTime? expiry;
bool? changeExpiry; bool? changeExpiry;
@@ -342,12 +315,6 @@ class SharedLinkEditPage extends HookConsumerWidget {
password = passwordController.text; password = passwordController.text;
} }
if (slugController.text != (existingLink!.slug ?? "")) {
slug = slugController.text.isEmpty ? null : slugController.text;
} else {
slug = existingLink!.slug;
}
if (editExpiry.value) { if (editExpiry.value) {
expiry = expiryAfter.value == 0 ? null : calculateExpiry(); expiry = expiryAfter.value == 0 ? null : calculateExpiry();
changeExpiry = true; changeExpiry = true;
@@ -362,7 +329,6 @@ class SharedLinkEditPage extends HookConsumerWidget {
allowUpload: upload, allowUpload: upload,
description: desc, description: desc,
password: password, password: password,
slug: slug,
expiresAt: expiry, expiresAt: expiry,
changeExpiry: changeExpiry, changeExpiry: changeExpiry,
); );
@@ -383,7 +349,6 @@ class SharedLinkEditPage extends HookConsumerWidget {
Padding(padding: const EdgeInsets.all(padding), child: buildLinkTitle()), Padding(padding: const EdgeInsets.all(padding), child: buildLinkTitle()),
Padding(padding: const EdgeInsets.all(padding), child: buildDescriptionField()), Padding(padding: const EdgeInsets.all(padding), child: buildDescriptionField()),
Padding(padding: const EdgeInsets.all(padding), child: buildPasswordField()), Padding(padding: const EdgeInsets.all(padding), child: buildPasswordField()),
Padding(padding: const EdgeInsets.all(padding), child: buildSlugField()),
Padding( Padding(
padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding), padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding),
child: buildShowMetaButton(), child: buildShowMetaButton(),
@@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:ui';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:cancellation_token_http/http.dart'; import 'package:cancellation_token_http/http.dart';
@@ -13,7 +14,6 @@ import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/repositories/file_media.repository.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/foreground_upload.service.dart'; import 'package:immich_mobile/services/foreground_upload.service.dart';
import 'package:immich_mobile/utils/image_converter.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
@@ -33,6 +33,23 @@ class DriftEditImagePage extends ConsumerWidget {
final bool isEdited; final bool isEdited;
const DriftEditImagePage({super.key, required this.asset, required this.image, required this.isEdited}); const DriftEditImagePage({super.key, required this.asset, required this.image, required this.isEdited});
Future<Uint8List> _imageToUint8List(Image image) async {
final Completer<Uint8List> completer = Completer();
image.image
.resolve(const ImageConfiguration())
.addListener(
ImageStreamListener((ImageInfo info, bool _) {
info.image.toByteData(format: ImageByteFormat.png).then((byteData) {
if (byteData != null) {
completer.complete(byteData.buffer.asUint8List());
} else {
completer.completeError('Failed to convert image to bytes');
}
});
}, onError: (exception, stackTrace) => completer.completeError(exception)),
);
return completer.future;
}
void _exitEditing(BuildContext context) { void _exitEditing(BuildContext context) {
// this assumes that the only way to get to this page is from the AssetViewerRoute // this assumes that the only way to get to this page is from the AssetViewerRoute
@@ -41,7 +58,7 @@ class DriftEditImagePage extends ConsumerWidget {
Future<void> _saveEditedImage(BuildContext context, BaseAsset asset, Image image, WidgetRef ref) async { Future<void> _saveEditedImage(BuildContext context, BaseAsset asset, Image image, WidgetRef ref) async {
try { try {
final Uint8List imageData = await imageToUint8List(image); final Uint8List imageData = await _imageToUint8List(image);
LocalAsset? localAsset; LocalAsset? localAsset;
try { try {
@@ -1,177 +0,0 @@
import 'dart:async';
import 'package:auto_route/auto_route.dart';
import 'package:crop_image/crop_image.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image_picker/image_picker.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/backup/backup.provider.dart';
import 'package:immich_mobile/providers/upload_profile_image.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/utils/image_converter.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_ui/immich_ui.dart';
@RoutePage()
class ProfilePictureCropPage extends ConsumerStatefulWidget {
final BaseAsset asset;
const ProfilePictureCropPage({super.key, required this.asset});
@override
ConsumerState<ProfilePictureCropPage> createState() => _ProfilePictureCropPageState();
}
class _ProfilePictureCropPageState extends ConsumerState<ProfilePictureCropPage> {
late final CropController _cropController;
bool _isLoading = false;
bool _didInitCropController = false;
@override
void initState() {
super.initState();
_cropController = CropController(defaultCrop: const Rect.fromLTRB(0, 0, 1, 1));
// Lock aspect ratio to 1:1 for circular/square crop
// CropController depends on CropImage initializing its bitmap size.
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted || _didInitCropController) {
return;
}
_didInitCropController = true;
_cropController.crop = const Rect.fromLTRB(0.1, 0.1, 0.9, 0.9);
_cropController.aspectRatio = 1.0;
});
}
@override
void dispose() {
_cropController.dispose();
super.dispose();
}
Future<void> _handleDone() async {
if (_isLoading) return;
setState(() {
_isLoading = true;
});
try {
final croppedImage = await _cropController.croppedImage();
final pngBytes = await imageToUint8List(croppedImage);
final xFile = XFile.fromData(pngBytes, mimeType: 'image/png');
final success = await ref
.read(uploadProfileImageProvider.notifier)
.upload(xFile, fileName: 'profile-picture.png');
if (!context.mounted) return;
if (success) {
final profileImagePath = ref.read(uploadProfileImageProvider).profileImagePath;
ref.read(authProvider.notifier).updateUserProfileImagePath(profileImagePath);
final user = ref.read(currentUserProvider);
if (user != null) {
unawaited(ref.read(currentUserProvider.notifier).refresh());
}
unawaited(ref.read(backupProvider.notifier).updateDiskInfo());
ImmichToast.show(
context: context,
msg: 'profile_picture_set'.tr(),
gravity: ToastGravity.BOTTOM,
toastType: ToastType.success,
);
if (context.mounted) {
unawaited(context.maybePop());
}
} else {
ImmichToast.show(
context: context,
msg: 'errors.unable_to_set_profile_picture'.tr(),
toastType: ToastType.error,
gravity: ToastGravity.BOTTOM,
);
}
} catch (e) {
if (!context.mounted) return;
ImmichToast.show(
context: context,
msg: 'errors.unable_to_set_profile_picture'.tr(),
toastType: ToastType.error,
gravity: ToastGravity.BOTTOM,
);
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
@override
Widget build(BuildContext context) {
// Create Image widget from asset
final image = Image(image: getFullImageProvider(widget.asset));
return Scaffold(
appBar: AppBar(
backgroundColor: context.scaffoldBackgroundColor,
title: Text("set_profile_picture".tr()),
leading: _isLoading ? null : const ImmichCloseButton(),
actions: [
if (_isLoading)
const Padding(
padding: EdgeInsets.all(16.0),
child: SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2)),
)
else
ImmichIconButton(
icon: Icons.done_rounded,
color: ImmichColor.primary,
variant: ImmichVariant.ghost,
onPressed: _handleDone,
),
],
),
backgroundColor: context.scaffoldBackgroundColor,
body: SafeArea(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: context.height * 0.7, maxWidth: context.width * 0.9),
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(7)),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.2),
spreadRadius: 2,
blurRadius: 10,
offset: const Offset(0, 3),
),
],
),
child: ClipRRect(
child: CropImage(controller: _cropController, image: image, gridColor: Colors.white),
),
),
),
);
},
),
),
);
}
}
@@ -5,7 +5,6 @@ import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/asset_grid/trash_delete_dialog.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart';
/// This delete action has the following behavior: /// This delete action has the following behavior:
@@ -23,18 +22,6 @@ class DeleteTrashActionButton extends ConsumerWidget {
return; return;
} }
final selectCount = ref.watch(multiSelectProvider.select((s) => s.selectedAssets.length));
final confirmDelete =
await showDialog<bool>(
context: context,
builder: (context) => TrashDeleteDialog(count: selectCount),
) ??
false;
if (!confirmDelete) {
return;
}
final result = await ref.read(actionProvider.notifier).deleteRemoteAndLocal(source); final result = await ref.read(actionProvider.notifier).deleteRemoteAndLocal(source);
ref.read(multiSelectProvider.notifier).reset(); ref.read(multiSelectProvider.notifier).reset();
@@ -1,56 +0,0 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
class SetAlbumCoverActionButton extends ConsumerWidget {
final String albumId;
final ActionSource source;
final bool iconOnly;
final bool menuItem;
const SetAlbumCoverActionButton({
super.key,
required this.albumId,
required this.source,
this.iconOnly = false,
this.menuItem = false,
});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
return;
}
final result = await ref.read(actionProvider.notifier).setAlbumCover(source, albumId);
ref.read(multiSelectProvider.notifier).reset();
final successMessage = 'album_cover_updated'.t(context: context);
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.image_outlined,
label: 'set_as_album_cover'.t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context, ref),
maxWidth: 100,
);
}
}
@@ -1,35 +0,0 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/routing/router.dart';
class SetProfilePictureActionButton extends ConsumerWidget {
final BaseAsset asset;
final bool iconOnly;
final bool menuItem;
const SetProfilePictureActionButton({super.key, required this.asset, this.iconOnly = false, this.menuItem = false});
void _onTap(BuildContext context) {
if (!context.mounted) {
return;
}
context.pushRoute(ProfilePictureCropRoute(asset: asset));
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton(
iconData: Icons.account_circle_outlined,
label: "set_as_profile_picture".t(context: context),
iconOnly: iconOnly,
menuItem: menuItem,
onPressed: () => _onTap(context),
maxWidth: 100,
);
}
}
@@ -16,10 +16,8 @@ import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.sta
import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart';
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart'; import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset_viewer/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
@@ -31,9 +29,8 @@ enum _DragIntent { none, scroll, dismiss }
class AssetPage extends ConsumerStatefulWidget { class AssetPage extends ConsumerStatefulWidget {
final int index; final int index;
final int heroOffset; final int heroOffset;
final void Function(int direction)? onTapNavigate;
const AssetPage({super.key, required this.index, required this.heroOffset, this.onTapNavigate}); const AssetPage({super.key, required this.index, required this.heroOffset});
@override @override
ConsumerState createState() => _AssetPageState(); ConsumerState createState() => _AssetPageState();
@@ -53,13 +50,14 @@ class _AssetPageState extends ConsumerState<AssetPage> {
final _scrollController = ScrollController(); final _scrollController = ScrollController();
late final _proxyScrollController = ProxyScrollController(scrollController: _scrollController); late final _proxyScrollController = ProxyScrollController(scrollController: _scrollController);
final ValueNotifier<PhotoViewScaleState> _videoScaleStateNotifier = ValueNotifier(PhotoViewScaleState.initial);
double _snapOffset = 0.0; double _snapOffset = 0.0;
double _lastScrollOffset = 0.0;
DragStartDetails? _dragStart; DragStartDetails? _dragStart;
_DragIntent _dragIntent = _DragIntent.none; _DragIntent _dragIntent = _DragIntent.none;
Drag? _drag; Drag? _drag;
bool _shouldPopOnDrag = false;
@override @override
void initState() { void initState() {
@@ -80,7 +78,6 @@ class _AssetPageState extends ConsumerState<AssetPage> {
_proxyScrollController.dispose(); _proxyScrollController.dispose();
_scaleBoundarySub?.cancel(); _scaleBoundarySub?.cancel();
_eventSubscription?.cancel(); _eventSubscription?.cancel();
_videoScaleStateNotifier.dispose();
super.dispose(); super.dispose();
} }
@@ -94,6 +91,7 @@ class _AssetPageState extends ConsumerState<AssetPage> {
void _showDetails() { void _showDetails() {
if (!_proxyScrollController.hasClients || _snapOffset <= 0) return; if (!_proxyScrollController.hasClients || _snapOffset <= 0) return;
_lastScrollOffset = _proxyScrollController.offset;
_proxyScrollController.animateTo(_snapOffset, duration: Durations.medium2, curve: Curves.easeOutCubic); _proxyScrollController.animateTo(_snapOffset, duration: Durations.medium2, curve: Curves.easeOutCubic);
} }
@@ -107,15 +105,18 @@ class _AssetPageState extends ConsumerState<AssetPage> {
void _onScroll() { void _onScroll() {
final offset = _proxyScrollController.offset; final offset = _proxyScrollController.offset;
if (offset > SnapScrollPhysics.minSnapDistance) { if (offset > SnapScrollPhysics.minSnapDistance && offset > _lastScrollOffset) {
_viewer.setShowingDetails(true); _viewer.setShowingDetails(true);
} else if (offset < SnapScrollPhysics.minSnapDistance - kTouchSlop) { } else if (offset < SnapScrollPhysics.minSnapDistance - kTouchSlop) {
_viewer.setShowingDetails(false); _viewer.setShowingDetails(false);
} }
_lastScrollOffset = offset;
} }
void _beginDrag(DragStartDetails details) { void _beginDrag(DragStartDetails details) {
_dragStart = details; _dragStart = details;
_shouldPopOnDrag = false;
_lastScrollOffset = _proxyScrollController.hasClients ? _proxyScrollController.offset : 0.0;
if (_viewController != null) { if (_viewController != null) {
_initialPhotoViewState = _viewController!.value; _initialPhotoViewState = _viewController!.value;
@@ -157,7 +158,6 @@ class _AssetPageState extends ConsumerState<AssetPage> {
void _endDrag(DragEndDetails details) { void _endDrag(DragEndDetails details) {
if (_dragStart == null) return; if (_dragStart == null) return;
final start = _dragStart;
_dragStart = null; _dragStart = null;
final intent = _dragIntent; final intent = _dragIntent;
@@ -173,8 +173,7 @@ class _AssetPageState extends ConsumerState<AssetPage> {
_drag?.end(details); _drag?.end(details);
_drag = null; _drag = null;
case _DragIntent.dismiss: case _DragIntent.dismiss:
const popThreshold = 75.0; if (_shouldPopOnDrag) {
if (details.localPosition.dy - start!.localPosition.dy > popThreshold) {
context.maybePop(); context.maybePop();
return; return;
} }
@@ -193,6 +192,7 @@ class _AssetPageState extends ConsumerState<AssetPage> {
PhotoViewControllerBase controller, PhotoViewControllerBase controller,
PhotoViewScaleStateController scaleStateController, PhotoViewScaleStateController scaleStateController,
) { ) {
_viewController = controller;
if (!_showingDetails && _isZoomed) return; if (!_showingDetails && _isZoomed) return;
_beginDrag(details); _beginDrag(details);
} }
@@ -206,8 +206,12 @@ class _AssetPageState extends ConsumerState<AssetPage> {
void _handleDragDown(BuildContext context, Offset delta) { void _handleDragDown(BuildContext context, Offset delta) {
const dragRatio = 0.2; const dragRatio = 0.2;
const popThreshold = 75.0;
_shouldPopOnDrag = delta.dy > popThreshold;
final distance = delta.dy.abs(); final distance = delta.dy.abs();
final maxScaleDistance = context.height * 0.5; final maxScaleDistance = context.height * 0.5;
final scaleReduction = (distance / maxScaleDistance).clamp(0.0, dragRatio); final scaleReduction = (distance / maxScaleDistance).clamp(0.0, dragRatio);
final initialScale = _viewController?.initialScale ?? _initialPhotoViewState.scale; final initialScale = _viewController?.initialScale ?? _initialPhotoViewState.scale;
@@ -220,39 +224,17 @@ class _AssetPageState extends ConsumerState<AssetPage> {
} }
void _onTapUp(BuildContext context, TapUpDetails details, PhotoViewControllerValue controllerValue) { void _onTapUp(BuildContext context, TapUpDetails details, PhotoViewControllerValue controllerValue) {
if (_showingDetails || _dragStart != null) return; if (!_showingDetails && _dragStart == null) _viewer.toggleControls();
final tapToNavigate = ref.read(appSettingsServiceProvider).getSetting<bool>(AppSettingsEnum.tapToNavigate);
if (!tapToNavigate) {
_viewer.toggleControls();
return;
}
final tapX = details.globalPosition.dx;
final screenWidth = context.width;
// Navigate if the user taps in the leftmost or rightmost quarter of the screen
final tappedLeftSide = tapX < screenWidth / 4;
final tappedRightSide = tapX > screenWidth * (3 / 4);
if (tappedLeftSide) {
widget.onTapNavigate?.call(-1);
} else if (tappedRightSide) {
widget.onTapNavigate?.call(1);
} else {
_viewer.toggleControls();
}
} }
void _onLongPress(BuildContext context, LongPressStartDetails details, PhotoViewControllerValue controllerValue) => void _onLongPress(BuildContext context, LongPressStartDetails details, PhotoViewControllerValue controllerValue) =>
ref.read(isPlayingMotionVideoProvider.notifier).playing = true; ref.read(isPlayingMotionVideoProvider.notifier).playing = true;
void _onScaleStateChanged(PhotoViewScaleState scaleState) { void _onScaleStateChanged(PhotoViewScaleState scaleState) {
_isZoomed = _isZoomed = switch (scaleState) {
scaleState == PhotoViewScaleState.zoomedIn || PhotoViewScaleState.zoomedIn || PhotoViewScaleState.covering => true,
scaleState == PhotoViewScaleState.covering || _ => false,
_videoScaleStateNotifier.value == PhotoViewScaleState.zoomedIn || };
_videoScaleStateNotifier.value == PhotoViewScaleState.covering;
_viewer.setZoomed(_isZoomed); _viewer.setZoomed(_isZoomed);
if (scaleState != PhotoViewScaleState.initial) { if (scaleState != PhotoViewScaleState.initial) {
@@ -334,33 +316,34 @@ class _AssetPageState extends ConsumerState<AssetPage> {
} }
return PhotoView.customChild( return PhotoView.customChild(
key: ValueKey(displayAsset),
onDragStart: _onDragStart, onDragStart: _onDragStart,
onDragUpdate: _onDragUpdate, onDragUpdate: _onDragUpdate,
onDragEnd: _onDragEnd, onDragEnd: _onDragEnd,
onDragCancel: _onDragCancel, onDragCancel: _onDragCancel,
onTapUp: _onTapUp,
heroAttributes: heroAttributes, heroAttributes: heroAttributes,
filterQuality: FilterQuality.high, filterQuality: FilterQuality.high,
maxScale: 1.0,
basePosition: Alignment.center, basePosition: Alignment.center,
disableScaleGestures: true, disableScaleGestures: true,
minScale: PhotoViewComputedScale.contained, scaleStateChangedCallback: _onScaleStateChanged,
initialScale: PhotoViewComputedScale.contained,
tightMode: true,
onPageBuild: _onPageBuild, onPageBuild: _onPageBuild,
enablePanAlways: true, enablePanAlways: true,
backgroundDecoration: backgroundDecoration, backgroundDecoration: backgroundDecoration,
child: NativeVideoViewer( child: SizedBox(
key: ValueKey(displayAsset), width: context.width,
asset: displayAsset, height: context.height,
scaleStateNotifier: _videoScaleStateNotifier, child: NativeVideoViewer(
disableScaleGestures: showingDetails,
image: Image(
key: ValueKey(displayAsset.heroTag), key: ValueKey(displayAsset.heroTag),
image: getFullImageProvider(displayAsset, size: context.sizeData), asset: displayAsset,
height: context.height, image: Image(
width: context.width, key: ValueKey(displayAsset),
fit: BoxFit.contain, image: getFullImageProvider(displayAsset, size: context.sizeData),
alignment: Alignment.center, fit: BoxFit.contain,
height: context.height,
width: context.width,
alignment: Alignment.center,
),
), ),
), ),
); );
@@ -96,16 +96,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
bool _assetReloadRequested = false; bool _assetReloadRequested = false;
void _onTapNavigate(int direction) {
final page = _pageController.page?.toInt();
if (page == null) return;
final target = page + direction;
final maxPage = ref.read(timelineServiceProvider).totalAssets - 1;
if (target >= 0 && target <= maxPage) {
_pageController.jumpToPage(target);
}
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -280,8 +270,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
: const FastClampingScrollPhysics(), : const FastClampingScrollPhysics(),
itemCount: ref.read(timelineServiceProvider).totalAssets, itemCount: ref.read(timelineServiceProvider).totalAssets,
onPageChanged: (index) => _onAssetChanged(index), onPageChanged: (index) => _onAssetChanged(index),
itemBuilder: (context, index) => itemBuilder: (context, index) => AssetPage(index: index, heroOffset: _heroOffset),
AssetPage(index: index, heroOffset: _heroOffset, onTapNavigate: _onTapNavigate),
), ),
), ),
if (!CurrentPlatform.isIOS) if (!CurrentPlatform.isIOS)
@@ -9,7 +9,6 @@ import 'package:immich_mobile/domain/models/setting.model.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/services/setting.service.dart'; import 'package:immich_mobile/domain/services/setting.service.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
@@ -26,7 +25,6 @@ import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/debounce.dart'; import 'package:immich_mobile/utils/debounce.dart';
import 'package:immich_mobile/utils/hooks/interval_hook.dart'; import 'package:immich_mobile/utils/hooks/interval_hook.dart';
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:native_video_player/native_video_player.dart'; import 'package:native_video_player/native_video_player.dart';
import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:wakelock_plus/wakelock_plus.dart';
@@ -54,8 +52,6 @@ class NativeVideoViewer extends HookConsumerWidget {
final bool showControls; final bool showControls;
final int playbackDelayFactor; final int playbackDelayFactor;
final Widget image; final Widget image;
final ValueNotifier<PhotoViewScaleState>? scaleStateNotifier;
final bool disableScaleGestures;
const NativeVideoViewer({ const NativeVideoViewer({
super.key, super.key,
@@ -63,8 +59,6 @@ class NativeVideoViewer extends HookConsumerWidget {
required this.image, required this.image,
this.showControls = true, this.showControls = true,
this.playbackDelayFactor = 1, this.playbackDelayFactor = 1,
this.scaleStateNotifier,
this.disableScaleGestures = false,
}); });
@override @override
@@ -144,7 +138,6 @@ class NativeVideoViewer extends HookConsumerWidget {
final videoSource = useMemoized<Future<VideoSource?>>(() => createSource()); final videoSource = useMemoized<Future<VideoSource?>>(() => createSource());
final aspectRatio = useState<double?>(null); final aspectRatio = useState<double?>(null);
useMemoized(() async { useMemoized(() async {
if (!context.mounted || aspectRatio.value != null) { if (!context.mounted || aspectRatio.value != null) {
return null; return null;
@@ -320,20 +313,6 @@ class NativeVideoViewer extends HookConsumerWidget {
Timer(const Duration(milliseconds: 200), checkIfBuffering); Timer(const Duration(milliseconds: 200), checkIfBuffering);
} }
Size? videoContextSize(double? videoAspectRatio, BuildContext? context) {
Size? videoContextSize;
if (videoAspectRatio == null || context == null) {
return null;
}
final contextAspectRatio = context.width / context.height;
if (videoAspectRatio > contextAspectRatio) {
videoContextSize = Size(context.width, context.width / aspectRatio.value!);
} else {
videoContextSize = Size(context.height * aspectRatio.value!, context.height);
}
return videoContextSize;
}
ref.listen(currentAssetNotifier, (_, value) { ref.listen(currentAssetNotifier, (_, value) {
final playerController = controller.value; final playerController = controller.value;
if (playerController != null && value != asset) { if (playerController != null && value != asset) {
@@ -414,31 +393,26 @@ class NativeVideoViewer extends HookConsumerWidget {
} }
}); });
return SizedBox( return Stack(
width: context.width, children: [
height: context.height, // This remains under the video to avoid flickering
child: Stack( // For motion videos, this is the image portion of the asset
children: [ Center(key: ValueKey(asset.heroTag), child: image),
// Hide thumbnail once video is visible to avoid it showing in background when zooming out on video. if (aspectRatio.value != null && !isCasting)
if (!isVisible.value || controller.value == null) Center(key: ValueKey(asset.heroTag), child: image), Visibility.maintain(
if (aspectRatio.value != null && !isCasting && isCurrent) key: ValueKey(asset),
Visibility.maintain( visible: isVisible.value,
child: Center(
key: ValueKey(asset), key: ValueKey(asset),
visible: isVisible.value, child: AspectRatio(
child: PhotoView.customChild(
key: ValueKey(asset), key: ValueKey(asset),
enableRotation: false, aspectRatio: aspectRatio.value!,
disableScaleGestures: disableScaleGestures, child: isCurrent ? NativeVideoPlayerView(key: ValueKey(asset), onViewReady: initController) : null,
// Transparent to avoid a black flash when viewer becomes visible but video isn't loaded yet.
backgroundDecoration: const BoxDecoration(color: Colors.transparent),
scaleStateChangedCallback: (state) => scaleStateNotifier?.value = state,
childSize: videoContextSize(aspectRatio.value, context),
child: NativeVideoPlayerView(key: ValueKey(asset), onViewReady: initController),
), ),
), ),
if (showControls) const Center(child: VideoViewerControls()), ),
], if (showControls) const Center(child: VideoViewerControls()),
), ],
); );
} }
@@ -81,35 +81,27 @@ class VideoViewerControls extends HookConsumerWidget {
} }
} }
void toggleControlsVisibility() {
if (showBuffering) {
return;
}
if (showControls) {
ref.read(assetViewerProvider.notifier).setControls(false);
} else {
showControlsAndStartHideTimer();
}
}
return GestureDetector( return GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.opaque,
onTap: toggleControlsVisibility, onTap: showControlsAndStartHideTimer,
child: IgnorePointer( child: AbsorbPointer(
ignoring: !showControls, absorbing: !showControls,
child: Stack( child: Stack(
children: [ children: [
if (showBuffering) if (showBuffering)
const Center(child: DelayedLoadingIndicator(fadeInDuration: Duration(milliseconds: 400))) const Center(child: DelayedLoadingIndicator(fadeInDuration: Duration(milliseconds: 400)))
else else
CenterPlayButton( GestureDetector(
backgroundColor: Colors.black54, onTap: () => ref.read(assetViewerProvider.notifier).setControls(false),
iconColor: Colors.white, child: CenterPlayButton(
isFinished: state == VideoPlaybackState.completed, backgroundColor: Colors.black54,
isPlaying: iconColor: Colors.white,
state == VideoPlaybackState.playing || (cast.isCasting && cast.castState == CastState.playing), isFinished: state == VideoPlaybackState.completed,
show: assetIsVideo && showControls, isPlaying:
onPressed: togglePlay, state == VideoPlaybackState.playing || (cast.isCasting && cast.castState == CastState.playing),
show: assetIsVideo && showControls,
onPressed: togglePlay,
),
), ),
], ],
), ),
@@ -13,7 +13,6 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/edit_location_
import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_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/move_to_lock_folder_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_album_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/set_album_cover.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart';
@@ -114,8 +113,6 @@ class _RemoteAlbumBottomSheetState extends ConsumerState<RemoteAlbumBottomSheet>
], ],
if (multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline), if (multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline),
if (ownsAlbum) RemoveFromAlbumActionButton(source: ActionSource.timeline, albumId: widget.album.id), if (ownsAlbum) RemoveFromAlbumActionButton(source: ActionSource.timeline, albumId: widget.album.id),
if (ownsAlbum && multiselect.selectedAssets.length == 1)
SetAlbumCoverActionButton(source: ActionSource.timeline, albumId: widget.album.id),
], ],
slivers: ownsAlbum slivers: ownsAlbum
? [ ? [
@@ -25,7 +25,7 @@ class _DriftPersonNameEditFormState extends ConsumerState<DriftPersonBirthdayEdi
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_selectedDate = widget.person.birthDate ?? DateTime(DateTime.now().year - 30, 1, 1); _selectedDate = widget.person.birthDate ?? DateTime.now();
} }
void saveBirthday() async { void saveBirthday() async {
@@ -90,7 +90,6 @@ class _DriftPersonNameEditFormState extends ConsumerState<DriftPersonBirthdayEdi
selectedDate: _selectedDate, selectedDate: _selectedDate,
locale: context.locale, locale: context.locale,
minimumDate: DateTime(1800, 1, 1), minimumDate: DateTime(1800, 1, 1),
maximumDate: DateTime.now(),
onDateTimeChanged: (DateTime value) { onDateTimeChanged: (DateTime value) {
setState(() { setState(() {
_selectedDate = value; _selectedDate = value;
@@ -29,7 +29,38 @@ import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart'; import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
import 'package:immich_mobile/widgets/common/selection_sliver_app_bar.dart'; import 'package:immich_mobile/widgets/common/selection_sliver_app_bar.dart';
class Timeline extends StatelessWidget { class _TimelineRestorationState extends ChangeNotifier {
int? _restoreAssetIndex;
bool _shouldRestoreAssetPosition = false;
int? get restoreAssetIndex => _restoreAssetIndex;
bool get shouldRestoreAssetPosition => _shouldRestoreAssetPosition;
void setRestoreAssetIndex(int? index) {
_restoreAssetIndex = index;
notifyListeners();
}
void setShouldRestoreAssetPosition(bool should) {
_shouldRestoreAssetPosition = should;
notifyListeners();
}
void clearRestoreAssetIndex() {
_restoreAssetIndex = null;
notifyListeners();
}
}
class _TimelineRestorationProvider extends InheritedNotifier<_TimelineRestorationState> {
const _TimelineRestorationProvider({required super.notifier, required super.child});
static _TimelineRestorationState of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<_TimelineRestorationProvider>()!.notifier!;
}
}
class Timeline extends StatefulWidget {
const Timeline({ const Timeline({
super.key, super.key,
this.topSliverWidget, this.topSliverWidget,
@@ -59,38 +90,68 @@ class Timeline extends StatelessWidget {
final bool readOnly; final bool readOnly;
final bool persistentBottomBar; final bool persistentBottomBar;
@override
State<Timeline> createState() => _TimelineState();
}
class _TimelineState extends State<Timeline> {
double? _lastWidth;
late final _TimelineRestorationState _restorationState;
@override
void initState() {
super.initState();
_restorationState = _TimelineRestorationState();
}
@override
void dispose() {
_restorationState.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
floatingActionButton: const DownloadStatusFloatingButton(), floatingActionButton: const DownloadStatusFloatingButton(),
body: LayoutBuilder( body: LayoutBuilder(
builder: (_, constraints) => ProviderScope( builder: (_, constraints) {
overrides: [ if (_lastWidth != null && _lastWidth != constraints.maxWidth) {
timelineArgsProvider.overrideWith( _restorationState.setShouldRestoreAssetPosition(true);
(ref) => TimelineArgs( }
maxWidth: constraints.maxWidth, _lastWidth = constraints.maxWidth;
maxHeight: constraints.maxHeight, return _TimelineRestorationProvider(
columnCount: ref.watch(settingsProvider.select((s) => s.get(Setting.tilesPerRow))), notifier: _restorationState,
showStorageIndicator: showStorageIndicator, child: ProviderScope(
withStack: withStack, key: ValueKey(_lastWidth),
groupBy: groupBy, overrides: [
timelineArgsProvider.overrideWith(
(ref) => TimelineArgs(
maxWidth: constraints.maxWidth,
maxHeight: constraints.maxHeight,
columnCount: ref.watch(settingsProvider.select((s) => s.get(Setting.tilesPerRow))),
showStorageIndicator: widget.showStorageIndicator,
withStack: widget.withStack,
groupBy: widget.groupBy,
),
),
if (widget.readOnly) readonlyModeProvider.overrideWith(() => _AlwaysReadOnlyNotifier()),
],
child: _SliverTimeline(
key: const ValueKey('_sliver_timeline'),
topSliverWidget: widget.topSliverWidget,
topSliverWidgetHeight: widget.topSliverWidgetHeight,
appBar: widget.appBar,
bottomSheet: widget.bottomSheet,
withScrubber: widget.withScrubber,
persistentBottomBar: widget.persistentBottomBar,
snapToMonth: widget.snapToMonth,
initialScrollOffset: widget.initialScrollOffset,
), ),
), ),
if (readOnly) readonlyModeProvider.overrideWith(() => _AlwaysReadOnlyNotifier()), );
], },
child: _SliverTimeline(
topSliverWidget: topSliverWidget,
topSliverWidgetHeight: topSliverWidgetHeight,
appBar: appBar,
bottomSheet: bottomSheet,
withScrubber: withScrubber,
persistentBottomBar: persistentBottomBar,
snapToMonth: snapToMonth,
initialScrollOffset: initialScrollOffset,
maxWidth: constraints.maxWidth,
),
),
), ),
); );
} }
@@ -109,6 +170,7 @@ class _AlwaysReadOnlyNotifier extends ReadOnlyModeNotifier {
class _SliverTimeline extends ConsumerStatefulWidget { class _SliverTimeline extends ConsumerStatefulWidget {
const _SliverTimeline({ const _SliverTimeline({
super.key,
this.topSliverWidget, this.topSliverWidget,
this.topSliverWidgetHeight, this.topSliverWidgetHeight,
this.appBar, this.appBar,
@@ -117,7 +179,6 @@ class _SliverTimeline extends ConsumerStatefulWidget {
this.persistentBottomBar = false, this.persistentBottomBar = false,
this.snapToMonth = true, this.snapToMonth = true,
this.initialScrollOffset, this.initialScrollOffset,
this.maxWidth,
}); });
final Widget? topSliverWidget; final Widget? topSliverWidget;
@@ -128,7 +189,6 @@ class _SliverTimeline extends ConsumerStatefulWidget {
final bool persistentBottomBar; final bool persistentBottomBar;
final bool snapToMonth; final bool snapToMonth;
final double? initialScrollOffset; final double? initialScrollOffset;
final double? maxWidth;
@override @override
ConsumerState createState() => _SliverTimelineState(); ConsumerState createState() => _SliverTimelineState();
@@ -147,7 +207,6 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
int _perRow = 4; int _perRow = 4;
double _scaleFactor = 3.0; double _scaleFactor = 3.0;
double _baseScaleFactor = 3.0; double _baseScaleFactor = 3.0;
int? _restoreAssetIndex;
@override @override
void initState() { void initState() {
@@ -166,20 +225,6 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
ref.listenManual(multiSelectProvider.select((s) => s.isEnabled), _onMultiSelectionToggled); ref.listenManual(multiSelectProvider.select((s) => s.isEnabled), _onMultiSelectionToggled);
} }
@override
void didUpdateWidget(covariant _SliverTimeline oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.maxWidth != oldWidget.maxWidth) {
final asyncSegments = ref.read(timelineSegmentProvider);
asyncSegments.whenData((segments) {
final index = _getCurrentAssetIndex(segments);
// Refresh to wait for new segments to be generated with the updated width before restoring the scroll position
final _ = ref.refresh(timelineArgsProvider);
_restoreAssetIndex = index;
});
}
}
void _onEvent(Event event) { void _onEvent(Event event) {
switch (event) { switch (event) {
case ScrollToTopEvent(): case ScrollToTopEvent():
@@ -197,14 +242,21 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
} }
} }
void _onMultiSelectionToggled(_, bool isEnabled) {
EventStream.shared.emit(MultiSelectToggleEvent(isEnabled));
}
void _restoreAssetPosition(_) { void _restoreAssetPosition(_) {
if (_restoreAssetIndex == null) return; final restorationState = _TimelineRestorationProvider.of(context);
if (!restorationState.shouldRestoreAssetPosition || restorationState.restoreAssetIndex == null) return;
final asyncSegments = ref.read(timelineSegmentProvider); final asyncSegments = ref.read(timelineSegmentProvider);
asyncSegments.whenData((segments) { asyncSegments.whenData((segments) {
final targetSegment = segments.lastWhereOrNull((segment) => segment.firstAssetIndex <= _restoreAssetIndex!); final targetSegment = segments.lastWhereOrNull(
(segment) => segment.firstAssetIndex <= restorationState.restoreAssetIndex!,
);
if (targetSegment != null) { if (targetSegment != null) {
final assetIndexInSegment = _restoreAssetIndex! - targetSegment.firstAssetIndex; final assetIndexInSegment = restorationState.restoreAssetIndex! - targetSegment.firstAssetIndex;
final newColumnCount = ref.read(timelineArgsProvider).columnCount; final newColumnCount = ref.read(timelineArgsProvider).columnCount;
final rowIndexInSegment = (assetIndexInSegment / newColumnCount).floor(); final rowIndexInSegment = (assetIndexInSegment / newColumnCount).floor();
final targetRowIndex = targetSegment.firstIndex + 1 + rowIndexInSegment; final targetRowIndex = targetSegment.firstIndex + 1 + rowIndexInSegment;
@@ -216,11 +268,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
}); });
} }
}); });
_restoreAssetIndex = null; restorationState.clearRestoreAssetIndex();
}
void _onMultiSelectionToggled(_, bool isEnabled) {
EventStream.shared.emit(MultiSelectToggleEvent(isEnabled));
} }
int? _getCurrentAssetIndex(List<Segment> segments) { int? _getCurrentAssetIndex(List<Segment> segments) {
@@ -430,56 +478,67 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
return PrimaryScrollController( return PrimaryScrollController(
controller: _scrollController, controller: _scrollController,
child: RawGestureDetector( child: NotificationListener<ScrollEndNotification>(
gestures: { onNotification: (notification) {
CustomScaleGestureRecognizer: GestureRecognizerFactoryWithHandlers<CustomScaleGestureRecognizer>( final currentIndex = _getCurrentAssetIndex(segments);
() => CustomScaleGestureRecognizer(), if (currentIndex != null && mounted) {
(CustomScaleGestureRecognizer scale) { _TimelineRestorationProvider.of(context).setRestoreAssetIndex(currentIndex);
scale.onStart = (details) { }
_baseScaleFactor = _scaleFactor; return false;
};
scale.onUpdate = (details) {
final newScaleFactor = math.max(math.min(5.0, _baseScaleFactor * details.scale), 1.0);
final newPerRow = 7 - newScaleFactor.toInt();
if (newPerRow != _perRow) {
final targetAssetIndex = _getCurrentAssetIndex(segments);
setState(() {
_scaleFactor = newScaleFactor;
_perRow = newPerRow;
_restoreAssetIndex = targetAssetIndex;
});
ref.read(settingsProvider.notifier).set(Setting.tilesPerRow, _perRow);
}
};
},
),
}, },
child: TimelineDragRegion( child: RawGestureDetector(
onStart: !isReadonlyModeEnabled ? _setDragStartIndex : null, gestures: {
onAssetEnter: _handleDragAssetEnter, CustomScaleGestureRecognizer: GestureRecognizerFactoryWithHandlers<CustomScaleGestureRecognizer>(
onEnd: !isReadonlyModeEnabled ? _stopDrag : null, () => CustomScaleGestureRecognizer(),
onScroll: _dragScroll, (CustomScaleGestureRecognizer scale) {
onScrollStart: () { scale.onStart = (details) {
// Minimize the bottom sheet when drag selection starts _baseScaleFactor = _scaleFactor;
ref.read(timelineStateProvider.notifier).setScrolling(true); };
scale.onUpdate = (details) {
final newScaleFactor = math.max(math.min(5.0, _baseScaleFactor * details.scale), 1.0);
final newPerRow = 7 - newScaleFactor.toInt();
final targetAssetIndex = _getCurrentAssetIndex(segments);
if (newPerRow != _perRow) {
final restorationState = _TimelineRestorationProvider.of(context);
setState(() {
_scaleFactor = newScaleFactor;
_perRow = newPerRow;
});
restorationState.setRestoreAssetIndex(targetAssetIndex);
restorationState.setShouldRestoreAssetPosition(true);
ref.read(settingsProvider.notifier).set(Setting.tilesPerRow, _perRow);
}
};
},
),
}, },
child: Stack( child: TimelineDragRegion(
children: [ onStart: !isReadonlyModeEnabled ? _setDragStartIndex : null,
timeline, onAssetEnter: _handleDragAssetEnter,
if (isBottomWidgetVisible) onEnd: !isReadonlyModeEnabled ? _stopDrag : null,
Positioned( onScroll: _dragScroll,
top: MediaQuery.paddingOf(context).top, onScrollStart: () {
left: 25, // Minimize the bottom sheet when drag selection starts
child: const SizedBox( ref.read(timelineStateProvider.notifier).setScrolling(true);
height: kToolbarHeight, },
child: Center(child: _MultiSelectStatusButton()), child: Stack(
children: [
timeline,
if (isMultiSelectStatusVisible)
Positioned(
top: MediaQuery.paddingOf(context).top,
left: 25,
child: const SizedBox(
height: kToolbarHeight,
child: Center(child: _MultiSelectStatusButton()),
),
), ),
), if (isBottomWidgetVisible) widget.bottomSheet!,
if (isBottomWidgetVisible) widget.bottomSheet!, ],
], ),
), ),
), ),
), ),
@@ -343,22 +343,6 @@ class ActionNotifier extends Notifier<void> {
} }
} }
Future<ActionResult> setAlbumCover(ActionSource source, String albumId) async {
final assets = _getAssets(source);
final asset = assets.first;
if (asset is! RemoteAsset) {
return const ActionResult(count: 1, success: false, error: 'Asset must be remote');
}
try {
await _service.setAlbumCover(albumId, asset.id);
return const ActionResult(count: 1, success: true);
} catch (error, stack) {
_logger.severe('Failed to set album cover', error, stack);
return ActionResult(count: 1, success: false, error: error.toString());
}
}
Future<ActionResult> updateDescription(ActionSource source, String description) async { Future<ActionResult> updateDescription(ActionSource source, String description) async {
final ids = _getRemoteIdsForSource(source); final ids = _getRemoteIdsForSource(source);
if (ids.length != 1) { if (ids.length != 1) {
@@ -61,10 +61,10 @@ class UploadProfileImageNotifier extends StateNotifier<UploadProfileImageState>
final UserService _userService; final UserService _userService;
Future<bool> upload(XFile file, {String? fileName}) async { Future<bool> upload(XFile file) async {
state = state.copyWith(status: UploadProfileStatus.loading); state = state.copyWith(status: UploadProfileStatus.loading);
var profileImagePath = await _userService.createProfileImage(fileName ?? file.name, await file.readAsBytes()); var profileImagePath = await _userService.createProfileImage(file.name, await file.readAsBytes());
if (profileImagePath != null) { if (profileImagePath != null) {
dPrint(() => "Successfully upload profile image"); dPrint(() => "Successfully upload profile image");
-2
View File
@@ -106,7 +106,6 @@ import 'package:immich_mobile/presentation/pages/drift_trash.page.dart';
import 'package:immich_mobile/presentation/pages/drift_user_selection.page.dart'; import 'package:immich_mobile/presentation/pages/drift_user_selection.page.dart';
import 'package:immich_mobile/presentation/pages/drift_video.page.dart'; import 'package:immich_mobile/presentation/pages/drift_video.page.dart';
import 'package:immich_mobile/presentation/pages/editing/drift_crop.page.dart'; import 'package:immich_mobile/presentation/pages/editing/drift_crop.page.dart';
import 'package:immich_mobile/presentation/pages/profile/profile_picture_crop.page.dart';
import 'package:immich_mobile/presentation/pages/editing/drift_edit.page.dart'; import 'package:immich_mobile/presentation/pages/editing/drift_edit.page.dart';
import 'package:immich_mobile/presentation/pages/editing/drift_filter.page.dart'; import 'package:immich_mobile/presentation/pages/editing/drift_filter.page.dart';
import 'package:immich_mobile/presentation/pages/local_timeline.page.dart'; import 'package:immich_mobile/presentation/pages/local_timeline.page.dart';
@@ -199,7 +198,6 @@ class AppRouter extends RootStackRouter {
AutoRoute(page: EditImageRoute.page), AutoRoute(page: EditImageRoute.page),
AutoRoute(page: CropImageRoute.page), AutoRoute(page: CropImageRoute.page),
AutoRoute(page: FilterImageRoute.page), AutoRoute(page: FilterImageRoute.page),
AutoRoute(page: ProfilePictureCropRoute.page),
CustomRoute( CustomRoute(
page: FavoritesRoute.page, page: FavoritesRoute.page,
guards: [_authGuard, _duplicateGuard], guards: [_authGuard, _duplicateGuard],
-38
View File
@@ -2443,44 +2443,6 @@ class PlacesCollectionRouteArgs {
} }
} }
/// generated route for
/// [ProfilePictureCropPage]
class ProfilePictureCropRoute
extends PageRouteInfo<ProfilePictureCropRouteArgs> {
ProfilePictureCropRoute({
Key? key,
required BaseAsset asset,
List<PageRouteInfo>? children,
}) : super(
ProfilePictureCropRoute.name,
args: ProfilePictureCropRouteArgs(key: key, asset: asset),
initialChildren: children,
);
static const String name = 'ProfilePictureCropRoute';
static PageInfo page = PageInfo(
name,
builder: (data) {
final args = data.argsAs<ProfilePictureCropRouteArgs>();
return ProfilePictureCropPage(key: args.key, asset: args.asset);
},
);
}
class ProfilePictureCropRouteArgs {
const ProfilePictureCropRouteArgs({this.key, required this.asset});
final Key? key;
final BaseAsset asset;
@override
String toString() {
return 'ProfilePictureCropRouteArgs{key: $key, asset: $asset}';
}
}
/// generated route for /// generated route for
/// [RecentlyTakenPage] /// [RecentlyTakenPage]
class RecentlyTakenRoute extends PageRouteInfo<void> { class RecentlyTakenRoute extends PageRouteInfo<void> {
-6
View File
@@ -240,12 +240,6 @@ class ActionService {
return _downloadRepository.downloadAllAssets(assets); return _downloadRepository.downloadAllAssets(assets);
} }
Future<bool> setAlbumCover(String albumId, String assetId) async {
final updatedAlbum = await _albumApiRepository.updateAlbum(albumId, thumbnailAssetId: assetId);
await _remoteAlbumRepository.update(updatedAlbum);
return true;
}
Future<int> _deleteLocalAssets(List<String> localIds) async { Future<int> _deleteLocalAssets(List<String> localIds) async {
final deletedIds = await _assetMediaRepository.deleteAll(localIds); final deletedIds = await _assetMediaRepository.deleteAll(localIds);
if (deletedIds.isEmpty) { if (deletedIds.isEmpty) {
@@ -35,7 +35,6 @@ enum AppSettingsEnum<T> {
loopVideo<bool>(StoreKey.loopVideo, "loopVideo", true), loopVideo<bool>(StoreKey.loopVideo, "loopVideo", true),
loadOriginalVideo<bool>(StoreKey.loadOriginalVideo, "loadOriginalVideo", false), loadOriginalVideo<bool>(StoreKey.loadOriginalVideo, "loadOriginalVideo", false),
autoPlayVideo<bool>(StoreKey.autoPlayVideo, "autoPlayVideo", true), autoPlayVideo<bool>(StoreKey.autoPlayVideo, "autoPlayVideo", true),
tapToNavigate<bool>(StoreKey.tapToNavigate, "tapToNavigate", false),
mapThemeMode<int>(StoreKey.mapThemeMode, null, 0), mapThemeMode<int>(StoreKey.mapThemeMode, null, 0),
mapShowFavoriteOnly<bool>(StoreKey.mapShowFavoriteOnly, null, false), mapShowFavoriteOnly<bool>(StoreKey.mapShowFavoriteOnly, null, false),
mapIncludeArchived<bool>(StoreKey.mapIncludeArchived, null, false), mapIncludeArchived<bool>(StoreKey.mapIncludeArchived, null, false),
@@ -37,7 +37,6 @@ class SharedLinkService {
required bool allowUpload, required bool allowUpload,
String? description, String? description,
String? password, String? password,
String? slug,
String? albumId, String? albumId,
List<String>? assetIds, List<String>? assetIds,
DateTime? expiresAt, DateTime? expiresAt,
@@ -55,7 +54,6 @@ class SharedLinkService {
expiresAt: expiresAt, expiresAt: expiresAt,
description: description, description: description,
password: password, password: password,
slug: slug,
); );
} else if (assetIds != null) { } else if (assetIds != null) {
dto = SharedLinkCreateDto( dto = SharedLinkCreateDto(
@@ -66,7 +64,6 @@ class SharedLinkService {
expiresAt: expiresAt, expiresAt: expiresAt,
description: description, description: description,
password: password, password: password,
slug: slug,
assetIds: assetIds, assetIds: assetIds,
); );
} }
@@ -91,7 +88,6 @@ class SharedLinkService {
bool? changeExpiry = false, bool? changeExpiry = false,
String? description, String? description,
String? password, String? password,
String? slug,
DateTime? expiresAt, DateTime? expiresAt,
}) async { }) async {
try { try {
@@ -104,7 +100,6 @@ class SharedLinkService {
expiresAt: expiresAt, expiresAt: expiresAt,
description: description, description: description,
password: password, password: password,
slug: slug,
changeExpiryTime: changeExpiry, changeExpiryTime: changeExpiry,
), ),
); );
+1 -27
View File
@@ -20,11 +20,9 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/like_activity_
import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_folder_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/remove_from_album_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/remove_from_lock_folder_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/set_album_cover.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/set_profile_picture_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
@@ -44,7 +42,6 @@ class ActionButtonContext {
final bool isCasting; final bool isCasting;
final TimelineOrigin timelineOrigin; final TimelineOrigin timelineOrigin;
final ThemeData? originalTheme; final ThemeData? originalTheme;
final int selectedCount;
const ActionButtonContext({ const ActionButtonContext({
required this.asset, required this.asset,
@@ -59,7 +56,6 @@ class ActionButtonContext {
this.isCasting = false, this.isCasting = false,
this.timelineOrigin = TimelineOrigin.main, this.timelineOrigin = TimelineOrigin.main,
this.originalTheme, this.originalTheme,
this.selectedCount = 1,
}); });
} }
@@ -69,9 +65,7 @@ enum ActionButtonType {
share, share,
shareLink, shareLink,
cast, cast,
setAlbumCover,
similarPhotos, similarPhotos,
setProfilePicture,
viewInTimeline, viewInTimeline,
download, download,
upload, upload,
@@ -140,11 +134,6 @@ enum ActionButtonType {
context.isOwner && // context.isOwner && //
!context.isInLockedView && // !context.isInLockedView && //
context.currentAlbum != null, context.currentAlbum != null,
ActionButtonType.setAlbumCover =>
context.isOwner && //
!context.isInLockedView && //
context.currentAlbum != null && //
context.selectedCount == 1,
ActionButtonType.unstack => ActionButtonType.unstack =>
context.isOwner && // context.isOwner && //
!context.isInLockedView && // !context.isInLockedView && //
@@ -157,10 +146,6 @@ enum ActionButtonType {
ActionButtonType.similarPhotos => ActionButtonType.similarPhotos =>
!context.isInLockedView && // !context.isInLockedView && //
context.asset is RemoteAsset, context.asset is RemoteAsset,
ActionButtonType.setProfilePicture =>
!context.isInLockedView && //
context.asset is RemoteAsset && //
context.isOwner,
ActionButtonType.openInfo => true, ActionButtonType.openInfo => true,
ActionButtonType.viewInTimeline => ActionButtonType.viewInTimeline =>
context.timelineOrigin != TimelineOrigin.main && context.timelineOrigin != TimelineOrigin.main &&
@@ -228,12 +213,6 @@ enum ActionButtonType {
iconOnly: iconOnly, iconOnly: iconOnly,
menuItem: menuItem, menuItem: menuItem,
), ),
ActionButtonType.setAlbumCover => SetAlbumCoverActionButton(
albumId: context.currentAlbum!.id,
source: context.source,
iconOnly: iconOnly,
menuItem: menuItem,
),
ActionButtonType.likeActivity => LikeActivityActionButton(iconOnly: iconOnly, menuItem: menuItem), ActionButtonType.likeActivity => LikeActivityActionButton(iconOnly: iconOnly, menuItem: menuItem),
ActionButtonType.unstack => UnStackActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem), ActionButtonType.unstack => UnStackActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem),
ActionButtonType.similarPhotos => SimilarPhotosActionButton( ActionButtonType.similarPhotos => SimilarPhotosActionButton(
@@ -241,11 +220,6 @@ enum ActionButtonType {
iconOnly: iconOnly, iconOnly: iconOnly,
menuItem: menuItem, menuItem: menuItem,
), ),
ActionButtonType.setProfilePicture => SetProfilePictureActionButton(
asset: context.asset,
iconOnly: iconOnly,
menuItem: menuItem,
),
ActionButtonType.openInfo => BaseActionButton( ActionButtonType.openInfo => BaseActionButton(
label: 'info'.tr(), label: 'info'.tr(),
iconData: Icons.info_outline, iconData: Icons.info_outline,
@@ -277,7 +251,7 @@ enum ActionButtonType {
int get kebabMenuGroup => switch (this) { int get kebabMenuGroup => switch (this) {
// 0: info // 0: info
ActionButtonType.openInfo => 0, ActionButtonType.openInfo => 0,
// 10: move, remove, and delete // 10: move,remove, and delete
ActionButtonType.trash => 10, ActionButtonType.trash => 10,
ActionButtonType.deletePermanent => 10, ActionButtonType.deletePermanent => 10,
ActionButtonType.removeFromLockFolder => 10, ActionButtonType.removeFromLockFolder => 10,
-28
View File
@@ -1,28 +0,0 @@
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/material.dart';
/// Converts a Flutter [Image] widget to a [Uint8List] in PNG format.
///
/// This function resolves the image stream and converts it to byte data.
/// Returns a [Future] that completes with the image bytes or completes with an error
/// if the conversion fails.
Future<Uint8List> imageToUint8List(Image image) async {
final Completer<Uint8List> completer = Completer();
image.image
.resolve(const ImageConfiguration())
.addListener(
ImageStreamListener((ImageInfo info, bool _) {
info.image.toByteData(format: ImageByteFormat.png).then((byteData) {
if (byteData != null) {
completer.complete(byteData.buffer.asUint8List());
} else {
completer.completeError('Failed to convert image to bytes');
}
});
}, onError: (exception, stackTrace) => completer.completeError(exception)),
);
return completer.future;
}
+2 -5
View File
@@ -30,10 +30,11 @@ import 'package:immich_mobile/utils/datetime_helpers.dart';
import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/debug_print.dart';
import 'package:immich_mobile/utils/diff.dart'; import 'package:immich_mobile/utils/diff.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
// ignore: import_rule_photo_manager // ignore: import_rule_photo_manager
import 'package:photo_manager/photo_manager.dart'; import 'package:photo_manager/photo_manager.dart';
const int targetVersion = 22; const int targetVersion = 21;
Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async { Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
final hasVersion = Store.tryGet(StoreKey.version) != null; final hasVersion = Store.tryGet(StoreKey.version) != null;
@@ -99,10 +100,6 @@ Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
} }
} }
if (version < 22 && !Store.isBetaTimelineEnabled) {
await Store.put(StoreKey.needBetaMigration, true);
}
if (targetVersion >= 12) { if (targetVersion >= 12) {
await Store.put(StoreKey.version, targetVersion); await Store.put(StoreKey.version, targetVersion);
return; return;
-58
View File
@@ -1,58 +0,0 @@
sealed class Option<T> {
const Option();
const factory Option.some(T value) = Some<T>;
const factory Option.none() = None<T>;
factory Option.fromNullable(T? value) => value != null ? Some(value) : None<T>();
@pragma('vm:prefer-inline')
bool get isSome => this is Some<T>;
@pragma('vm:prefer-inline')
bool get isNone => this is None<T>;
@pragma('vm:prefer-inline')
T? get unwrapOrNull => switch (this) {
Some(:final value) => value,
None() => null,
};
U fold<U>(U Function(T value) onSome, U Function() onNone) => switch (this) {
Some(:final value) => onSome(value),
None() => onNone(),
};
@override
String toString() => switch (this) {
Some(:final value) => 'Some($value)',
None() => 'None',
};
}
final class Some<T> extends Option<T> {
final T value;
const Some(this.value);
@override
bool operator ==(Object other) => other is Some<T> && other.value == value;
@override
int get hashCode => value.hashCode;
}
final class None<T> extends Option<T> {
const None();
@override
bool operator ==(Object other) => other is None<T>;
@override
int get hashCode => 0;
}
extension ObjectOptionExtension<T> on T? {
Option<T> toOption() => Option.fromNullable(this);
}
@@ -1,47 +0,0 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_ui/immich_ui.dart';
class TrashDeleteDialog extends StatelessWidget {
const TrashDeleteDialog({super.key, required this.count});
final int count;
@override
Widget build(BuildContext context) {
return AlertDialog(
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))),
title: Text(context.t.permanently_delete),
content: ImmichFormattedText(context.t.permanently_delete_assets_prompt(count: count)),
actions: [
SizedBox(
width: double.infinity,
height: 48,
child: FilledButton(
onPressed: () => context.pop(false),
style: FilledButton.styleFrom(
backgroundColor: context.colorScheme.surfaceDim,
foregroundColor: context.primaryColor,
),
child: Text(context.t.cancel, style: const TextStyle(fontWeight: FontWeight.bold)),
),
),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
height: 48,
child: FilledButton(
onPressed: () => context.pop(true),
style: FilledButton.styleFrom(
backgroundColor: context.colorScheme.errorContainer,
foregroundColor: context.colorScheme.onErrorContainer,
),
child: Text(context.t.delete, style: const TextStyle(fontWeight: FontWeight.bold)),
),
),
],
);
}
}
@@ -21,20 +21,23 @@ class CenterPlayButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Center( return ColoredBox(
child: UnconstrainedBox( color: Colors.transparent,
child: AnimatedOpacity( child: Center(
opacity: show ? 1.0 : 0.0, child: UnconstrainedBox(
duration: const Duration(milliseconds: 100), child: AnimatedOpacity(
child: DecoratedBox( opacity: show ? 1.0 : 0.0,
decoration: BoxDecoration(color: backgroundColor, shape: BoxShape.circle), duration: const Duration(milliseconds: 100),
child: IconButton( child: DecoratedBox(
iconSize: 32, decoration: BoxDecoration(color: backgroundColor, shape: BoxShape.circle),
padding: const EdgeInsets.all(12.0), child: IconButton(
icon: isFinished iconSize: 32,
? Icon(Icons.replay, color: iconColor) padding: const EdgeInsets.all(12.0),
: AnimatedPlayPause(color: iconColor, playing: isPlaying), icon: isFinished
onPressed: onPressed, ? Icon(Icons.replay, color: iconColor)
: AnimatedPlayPause(color: iconColor, playing: isPlaying),
onPressed: onPressed,
),
), ),
), ),
), ),
@@ -135,7 +135,7 @@ class AdvancedSettings extends HookConsumerWidget {
title: "advanced_settings_enable_alternate_media_filter_title".tr(), title: "advanced_settings_enable_alternate_media_filter_title".tr(),
subtitle: "advanced_settings_enable_alternate_media_filter_subtitle".tr(), subtitle: "advanced_settings_enable_alternate_media_filter_subtitle".tr(),
), ),
if (!Store.isBetaTimelineEnabled) const BetaTimelineListTile(), const BetaTimelineListTile(),
if (Store.isBetaTimelineEnabled) if (Store.isBetaTimelineEnabled)
SettingsSwitchListTile( SettingsSwitchListTile(
valueNotifier: readonlyModeEnabled, valueNotifier: readonlyModeEnabled,
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:immich_mobile/widgets/settings/asset_viewer_settings/image_viewer_quality_setting.dart'; import 'package:immich_mobile/widgets/settings/asset_viewer_settings/image_viewer_quality_setting.dart';
import 'package:immich_mobile/widgets/settings/asset_viewer_settings/image_viewer_tap_to_navigate_setting.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
import 'video_viewer_settings.dart'; import 'video_viewer_settings.dart';
@@ -9,11 +8,7 @@ class AssetViewerSettings extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final assetViewerSetting = [ final assetViewerSetting = [const ImageViewerQualitySetting(), const VideoViewerSettings()];
const ImageViewerQualitySetting(),
const ImageViewerTapToNavigateSetting(),
const VideoViewerSettings(),
];
return SettingsSubPageScaffold(settings: assetViewerSetting, showDivider: true); return SettingsSubPageScaffold(settings: assetViewerSetting, showDivider: true);
} }
@@ -1,30 +0,0 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
class ImageViewerTapToNavigateSetting extends HookConsumerWidget {
const ImageViewerTapToNavigateSetting({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final tapToNavigate = useAppSettingsState(AppSettingsEnum.tapToNavigate);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SettingsSubTitle(title: "setting_image_navigation_title".tr()),
SettingsSwitchListTile(
valueNotifier: tapToNavigate,
title: "setting_image_navigation_enable_title".tr(),
subtitle: "setting_image_navigation_enable_subtitle".tr(),
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
),
],
);
}
}
@@ -78,10 +78,7 @@ class SharedLinkItem extends ConsumerWidget {
return; return;
} }
final hasSlug = sharedLink.slug?.isNotEmpty == true; Clipboard.setData(ClipboardData(text: "${serverUrl}share/${sharedLink.key}")).then((_) {
final urlPath = hasSlug ? sharedLink.slug : sharedLink.key;
final basePath = hasSlug ? 's' : 'share';
Clipboard.setData(ClipboardData(text: "$serverUrl$basePath/$urlPath")).then((_) {
context.scaffoldMessenger.showSnackBar( context.scaffoldMessenger.showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text(
+6 -18
View File
@@ -171,12 +171,7 @@ Class | Method | HTTP request | Description
*LibrariesApi* | [**scanLibrary**](doc//LibrariesApi.md#scanlibrary) | **POST** /libraries/{id}/scan | Scan a library *LibrariesApi* | [**scanLibrary**](doc//LibrariesApi.md#scanlibrary) | **POST** /libraries/{id}/scan | Scan a library
*LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} | Update a library *LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} | Update a library
*LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate | Validate library settings *LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate | Validate library settings
*MaintenanceAdminApi* | [**deleteIntegrityReport**](doc//MaintenanceAdminApi.md#deleteintegrityreport) | **DELETE** /admin/integrity/report/{id} | Delete integrity report item
*MaintenanceAdminApi* | [**detectPriorInstall**](doc//MaintenanceAdminApi.md#detectpriorinstall) | **GET** /admin/maintenance/detect-install | Detect existing install *MaintenanceAdminApi* | [**detectPriorInstall**](doc//MaintenanceAdminApi.md#detectpriorinstall) | **GET** /admin/maintenance/detect-install | Detect existing install
*MaintenanceAdminApi* | [**getIntegrityReport**](doc//MaintenanceAdminApi.md#getintegrityreport) | **GET** /admin/integrity/report | Get integrity report by type
*MaintenanceAdminApi* | [**getIntegrityReportCsv**](doc//MaintenanceAdminApi.md#getintegrityreportcsv) | **GET** /admin/integrity/report/{type}/csv | Export integrity report by type as CSV
*MaintenanceAdminApi* | [**getIntegrityReportFile**](doc//MaintenanceAdminApi.md#getintegrityreportfile) | **GET** /admin/integrity/report/{id}/file | Download flagged file
*MaintenanceAdminApi* | [**getIntegrityReportSummary**](doc//MaintenanceAdminApi.md#getintegrityreportsummary) | **GET** /admin/integrity/summary | Get integrity report summary
*MaintenanceAdminApi* | [**getMaintenanceStatus**](doc//MaintenanceAdminApi.md#getmaintenancestatus) | **GET** /admin/maintenance/status | Get maintenance mode status *MaintenanceAdminApi* | [**getMaintenanceStatus**](doc//MaintenanceAdminApi.md#getmaintenancestatus) | **GET** /admin/maintenance/status | Get maintenance mode status
*MaintenanceAdminApi* | [**maintenanceLogin**](doc//MaintenanceAdminApi.md#maintenancelogin) | **POST** /admin/maintenance/login | Log into maintenance mode *MaintenanceAdminApi* | [**maintenanceLogin**](doc//MaintenanceAdminApi.md#maintenancelogin) | **POST** /admin/maintenance/login | Log into maintenance mode
*MaintenanceAdminApi* | [**setMaintenanceMode**](doc//MaintenanceAdminApi.md#setmaintenancemode) | **POST** /admin/maintenance | Set maintenance mode *MaintenanceAdminApi* | [**setMaintenanceMode**](doc//MaintenanceAdminApi.md#setmaintenancemode) | **POST** /admin/maintenance | Set maintenance mode
@@ -363,11 +358,12 @@ Class | Method | HTTP request | Description
- [AssetDeltaSyncDto](doc//AssetDeltaSyncDto.md) - [AssetDeltaSyncDto](doc//AssetDeltaSyncDto.md)
- [AssetDeltaSyncResponseDto](doc//AssetDeltaSyncResponseDto.md) - [AssetDeltaSyncResponseDto](doc//AssetDeltaSyncResponseDto.md)
- [AssetEditAction](doc//AssetEditAction.md) - [AssetEditAction](doc//AssetEditAction.md)
- [AssetEditActionItemDto](doc//AssetEditActionItemDto.md) - [AssetEditActionCrop](doc//AssetEditActionCrop.md)
- [AssetEditActionItemDtoParameters](doc//AssetEditActionItemDtoParameters.md) - [AssetEditActionListDto](doc//AssetEditActionListDto.md)
- [AssetEditActionItemResponseDto](doc//AssetEditActionItemResponseDto.md) - [AssetEditActionListDtoEditsInner](doc//AssetEditActionListDtoEditsInner.md)
- [AssetEditsCreateDto](doc//AssetEditsCreateDto.md) - [AssetEditActionMirror](doc//AssetEditActionMirror.md)
- [AssetEditsResponseDto](doc//AssetEditsResponseDto.md) - [AssetEditActionRotate](doc//AssetEditActionRotate.md)
- [AssetEditsDto](doc//AssetEditsDto.md)
- [AssetFaceCreateDto](doc//AssetFaceCreateDto.md) - [AssetFaceCreateDto](doc//AssetFaceCreateDto.md)
- [AssetFaceDeleteDto](doc//AssetFaceDeleteDto.md) - [AssetFaceDeleteDto](doc//AssetFaceDeleteDto.md)
- [AssetFaceResponseDto](doc//AssetFaceResponseDto.md) - [AssetFaceResponseDto](doc//AssetFaceResponseDto.md)
@@ -436,10 +432,6 @@ Class | Method | HTTP request | Description
- [FoldersResponse](doc//FoldersResponse.md) - [FoldersResponse](doc//FoldersResponse.md)
- [FoldersUpdate](doc//FoldersUpdate.md) - [FoldersUpdate](doc//FoldersUpdate.md)
- [ImageFormat](doc//ImageFormat.md) - [ImageFormat](doc//ImageFormat.md)
- [IntegrityReportDto](doc//IntegrityReportDto.md)
- [IntegrityReportResponseDto](doc//IntegrityReportResponseDto.md)
- [IntegrityReportSummaryResponseDto](doc//IntegrityReportSummaryResponseDto.md)
- [IntegrityReportType](doc//IntegrityReportType.md)
- [JobCreateDto](doc//JobCreateDto.md) - [JobCreateDto](doc//JobCreateDto.md)
- [JobName](doc//JobName.md) - [JobName](doc//JobName.md)
- [JobSettingsDto](doc//JobSettingsDto.md) - [JobSettingsDto](doc//JobSettingsDto.md)
@@ -588,7 +580,6 @@ Class | Method | HTTP request | Description
- [SyncAssetExifV1](doc//SyncAssetExifV1.md) - [SyncAssetExifV1](doc//SyncAssetExifV1.md)
- [SyncAssetFaceDeleteV1](doc//SyncAssetFaceDeleteV1.md) - [SyncAssetFaceDeleteV1](doc//SyncAssetFaceDeleteV1.md)
- [SyncAssetFaceV1](doc//SyncAssetFaceV1.md) - [SyncAssetFaceV1](doc//SyncAssetFaceV1.md)
- [SyncAssetFaceV2](doc//SyncAssetFaceV2.md)
- [SyncAssetMetadataDeleteV1](doc//SyncAssetMetadataDeleteV1.md) - [SyncAssetMetadataDeleteV1](doc//SyncAssetMetadataDeleteV1.md)
- [SyncAssetMetadataV1](doc//SyncAssetMetadataV1.md) - [SyncAssetMetadataV1](doc//SyncAssetMetadataV1.md)
- [SyncAssetV1](doc//SyncAssetV1.md) - [SyncAssetV1](doc//SyncAssetV1.md)
@@ -617,9 +608,6 @@ Class | Method | HTTP request | Description
- [SystemConfigGeneratedFullsizeImageDto](doc//SystemConfigGeneratedFullsizeImageDto.md) - [SystemConfigGeneratedFullsizeImageDto](doc//SystemConfigGeneratedFullsizeImageDto.md)
- [SystemConfigGeneratedImageDto](doc//SystemConfigGeneratedImageDto.md) - [SystemConfigGeneratedImageDto](doc//SystemConfigGeneratedImageDto.md)
- [SystemConfigImageDto](doc//SystemConfigImageDto.md) - [SystemConfigImageDto](doc//SystemConfigImageDto.md)
- [SystemConfigIntegrityChecks](doc//SystemConfigIntegrityChecks.md)
- [SystemConfigIntegrityChecksumJob](doc//SystemConfigIntegrityChecksumJob.md)
- [SystemConfigIntegrityJob](doc//SystemConfigIntegrityJob.md)
- [SystemConfigJobDto](doc//SystemConfigJobDto.md) - [SystemConfigJobDto](doc//SystemConfigJobDto.md)
- [SystemConfigLibraryDto](doc//SystemConfigLibraryDto.md) - [SystemConfigLibraryDto](doc//SystemConfigLibraryDto.md)
- [SystemConfigLibraryScanDto](doc//SystemConfigLibraryScanDto.md) - [SystemConfigLibraryScanDto](doc//SystemConfigLibraryScanDto.md)
+6 -13
View File
@@ -97,11 +97,12 @@ part 'model/asset_copy_dto.dart';
part 'model/asset_delta_sync_dto.dart'; part 'model/asset_delta_sync_dto.dart';
part 'model/asset_delta_sync_response_dto.dart'; part 'model/asset_delta_sync_response_dto.dart';
part 'model/asset_edit_action.dart'; part 'model/asset_edit_action.dart';
part 'model/asset_edit_action_item_dto.dart'; part 'model/asset_edit_action_crop.dart';
part 'model/asset_edit_action_item_dto_parameters.dart'; part 'model/asset_edit_action_list_dto.dart';
part 'model/asset_edit_action_item_response_dto.dart'; part 'model/asset_edit_action_list_dto_edits_inner.dart';
part 'model/asset_edits_create_dto.dart'; part 'model/asset_edit_action_mirror.dart';
part 'model/asset_edits_response_dto.dart'; part 'model/asset_edit_action_rotate.dart';
part 'model/asset_edits_dto.dart';
part 'model/asset_face_create_dto.dart'; part 'model/asset_face_create_dto.dart';
part 'model/asset_face_delete_dto.dart'; part 'model/asset_face_delete_dto.dart';
part 'model/asset_face_response_dto.dart'; part 'model/asset_face_response_dto.dart';
@@ -170,10 +171,6 @@ part 'model/facial_recognition_config.dart';
part 'model/folders_response.dart'; part 'model/folders_response.dart';
part 'model/folders_update.dart'; part 'model/folders_update.dart';
part 'model/image_format.dart'; part 'model/image_format.dart';
part 'model/integrity_report_dto.dart';
part 'model/integrity_report_response_dto.dart';
part 'model/integrity_report_summary_response_dto.dart';
part 'model/integrity_report_type.dart';
part 'model/job_create_dto.dart'; part 'model/job_create_dto.dart';
part 'model/job_name.dart'; part 'model/job_name.dart';
part 'model/job_settings_dto.dart'; part 'model/job_settings_dto.dart';
@@ -322,7 +319,6 @@ part 'model/sync_asset_delete_v1.dart';
part 'model/sync_asset_exif_v1.dart'; part 'model/sync_asset_exif_v1.dart';
part 'model/sync_asset_face_delete_v1.dart'; part 'model/sync_asset_face_delete_v1.dart';
part 'model/sync_asset_face_v1.dart'; part 'model/sync_asset_face_v1.dart';
part 'model/sync_asset_face_v2.dart';
part 'model/sync_asset_metadata_delete_v1.dart'; part 'model/sync_asset_metadata_delete_v1.dart';
part 'model/sync_asset_metadata_v1.dart'; part 'model/sync_asset_metadata_v1.dart';
part 'model/sync_asset_v1.dart'; part 'model/sync_asset_v1.dart';
@@ -351,9 +347,6 @@ part 'model/system_config_faces_dto.dart';
part 'model/system_config_generated_fullsize_image_dto.dart'; part 'model/system_config_generated_fullsize_image_dto.dart';
part 'model/system_config_generated_image_dto.dart'; part 'model/system_config_generated_image_dto.dart';
part 'model/system_config_image_dto.dart'; part 'model/system_config_image_dto.dart';
part 'model/system_config_integrity_checks.dart';
part 'model/system_config_integrity_checksum_job.dart';
part 'model/system_config_integrity_job.dart';
part 'model/system_config_job_dto.dart'; part 'model/system_config_job_dto.dart';
part 'model/system_config_library_dto.dart'; part 'model/system_config_library_dto.dart';
part 'model/system_config_library_scan_dto.dart'; part 'model/system_config_library_scan_dto.dart';
+9 -9
View File
@@ -421,14 +421,14 @@ class AssetsApi {
/// ///
/// * [String] id (required): /// * [String] id (required):
/// ///
/// * [AssetEditsCreateDto] assetEditsCreateDto (required): /// * [AssetEditActionListDto] assetEditActionListDto (required):
Future<Response> editAssetWithHttpInfo(String id, AssetEditsCreateDto assetEditsCreateDto,) async { Future<Response> editAssetWithHttpInfo(String id, AssetEditActionListDto assetEditActionListDto,) async {
// ignore: prefer_const_declarations // ignore: prefer_const_declarations
final apiPath = r'/assets/{id}/edits' final apiPath = r'/assets/{id}/edits'
.replaceAll('{id}', id); .replaceAll('{id}', id);
// ignore: prefer_final_locals // ignore: prefer_final_locals
Object? postBody = assetEditsCreateDto; Object? postBody = assetEditActionListDto;
final queryParams = <QueryParam>[]; final queryParams = <QueryParam>[];
final headerParams = <String, String>{}; final headerParams = <String, String>{};
@@ -456,9 +456,9 @@ class AssetsApi {
/// ///
/// * [String] id (required): /// * [String] id (required):
/// ///
/// * [AssetEditsCreateDto] assetEditsCreateDto (required): /// * [AssetEditActionListDto] assetEditActionListDto (required):
Future<AssetEditsResponseDto?> editAsset(String id, AssetEditsCreateDto assetEditsCreateDto,) async { Future<AssetEditsDto?> editAsset(String id, AssetEditActionListDto assetEditActionListDto,) async {
final response = await editAssetWithHttpInfo(id, assetEditsCreateDto,); final response = await editAssetWithHttpInfo(id, assetEditActionListDto,);
if (response.statusCode >= HttpStatus.badRequest) { if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response)); throw ApiException(response.statusCode, await _decodeBodyBytes(response));
} }
@@ -466,7 +466,7 @@ class AssetsApi {
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input" // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string. // FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetEditsResponseDto',) as AssetEditsResponseDto; return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetEditsDto',) as AssetEditsDto;
} }
return null; return null;
@@ -576,7 +576,7 @@ class AssetsApi {
/// Parameters: /// Parameters:
/// ///
/// * [String] id (required): /// * [String] id (required):
Future<AssetEditsResponseDto?> getAssetEdits(String id,) async { Future<AssetEditsDto?> getAssetEdits(String id,) async {
final response = await getAssetEditsWithHttpInfo(id,); final response = await getAssetEditsWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) { if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response)); throw ApiException(response.statusCode, await _decodeBodyBytes(response));
@@ -585,7 +585,7 @@ class AssetsApi {
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input" // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string. // FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetEditsResponseDto',) as AssetEditsResponseDto; return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetEditsDto',) as AssetEditsDto;
} }
return null; return null;
-287
View File
@@ -16,55 +16,6 @@ class MaintenanceAdminApi {
final ApiClient apiClient; final ApiClient apiClient;
/// Delete integrity report item
///
/// Delete a given report item and perform corresponding deletion (e.g. trash asset, delete file)
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] id (required):
Future<Response> deleteIntegrityReportWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final apiPath = r'/admin/integrity/report/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'DELETE',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Delete integrity report item
///
/// Delete a given report item and perform corresponding deletion (e.g. trash asset, delete file)
///
/// Parameters:
///
/// * [String] id (required):
Future<void> deleteIntegrityReport(String id,) async {
final response = await deleteIntegrityReportWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Detect existing install /// Detect existing install
/// ///
/// Collect integrity checks and other heuristics about local data. /// Collect integrity checks and other heuristics about local data.
@@ -113,244 +64,6 @@ class MaintenanceAdminApi {
return null; return null;
} }
/// Get integrity report by type
///
/// Get all flagged items by integrity report type
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [IntegrityReportType] type (required):
///
/// * [String] cursor:
/// Cursor for pagination
///
/// * [num] limit:
/// Number of items per page
Future<Response> getIntegrityReportWithHttpInfo(IntegrityReportType type, { String? cursor, num? limit, }) async {
// ignore: prefer_const_declarations
final apiPath = r'/admin/integrity/report';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (cursor != null) {
queryParams.addAll(_queryParams('', 'cursor', cursor));
}
if (limit != null) {
queryParams.addAll(_queryParams('', 'limit', limit));
}
queryParams.addAll(_queryParams('', 'type', type));
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Get integrity report by type
///
/// Get all flagged items by integrity report type
///
/// Parameters:
///
/// * [IntegrityReportType] type (required):
///
/// * [String] cursor:
/// Cursor for pagination
///
/// * [num] limit:
/// Number of items per page
Future<IntegrityReportResponseDto?> getIntegrityReport(IntegrityReportType type, { String? cursor, num? limit, }) async {
final response = await getIntegrityReportWithHttpInfo(type, cursor: cursor, limit: limit, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'IntegrityReportResponseDto',) as IntegrityReportResponseDto;
}
return null;
}
/// Export integrity report by type as CSV
///
/// Get all integrity report entries for a given type as a CSV
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [IntegrityReportType] type (required):
Future<Response> getIntegrityReportCsvWithHttpInfo(IntegrityReportType type,) async {
// ignore: prefer_const_declarations
final apiPath = r'/admin/integrity/report/{type}/csv'
.replaceAll('{type}', type.toString());
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Export integrity report by type as CSV
///
/// Get all integrity report entries for a given type as a CSV
///
/// Parameters:
///
/// * [IntegrityReportType] type (required):
Future<MultipartFile?> getIntegrityReportCsv(IntegrityReportType type,) async {
final response = await getIntegrityReportCsvWithHttpInfo(type,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile;
}
return null;
}
/// Download flagged file
///
/// Download the untracked/broken file if one exists
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] id (required):
Future<Response> getIntegrityReportFileWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final apiPath = r'/admin/integrity/report/{id}/file'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Download flagged file
///
/// Download the untracked/broken file if one exists
///
/// Parameters:
///
/// * [String] id (required):
Future<MultipartFile?> getIntegrityReportFile(String id,) async {
final response = await getIntegrityReportFileWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile;
}
return null;
}
/// Get integrity report summary
///
/// Get a count of the items flagged in each integrity report
///
/// Note: This method returns the HTTP [Response].
Future<Response> getIntegrityReportSummaryWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/admin/integrity/summary';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Get integrity report summary
///
/// Get a count of the items flagged in each integrity report
Future<IntegrityReportSummaryResponseDto?> getIntegrityReportSummary() async {
final response = await getIntegrityReportSummaryWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'IntegrityReportSummaryResponseDto',) as IntegrityReportSummaryResponseDto;
}
return null;
}
/// Get maintenance mode status /// Get maintenance mode status
/// ///
/// Fetch information about the currently running maintenance action. /// Fetch information about the currently running maintenance action.
+12 -26
View File
@@ -240,16 +240,18 @@ class ApiClient {
return AssetDeltaSyncResponseDto.fromJson(value); return AssetDeltaSyncResponseDto.fromJson(value);
case 'AssetEditAction': case 'AssetEditAction':
return AssetEditActionTypeTransformer().decode(value); return AssetEditActionTypeTransformer().decode(value);
case 'AssetEditActionItemDto': case 'AssetEditActionCrop':
return AssetEditActionItemDto.fromJson(value); return AssetEditActionCrop.fromJson(value);
case 'AssetEditActionItemDtoParameters': case 'AssetEditActionListDto':
return AssetEditActionItemDtoParameters.fromJson(value); return AssetEditActionListDto.fromJson(value);
case 'AssetEditActionItemResponseDto': case 'AssetEditActionListDtoEditsInner':
return AssetEditActionItemResponseDto.fromJson(value); return AssetEditActionListDtoEditsInner.fromJson(value);
case 'AssetEditsCreateDto': case 'AssetEditActionMirror':
return AssetEditsCreateDto.fromJson(value); return AssetEditActionMirror.fromJson(value);
case 'AssetEditsResponseDto': case 'AssetEditActionRotate':
return AssetEditsResponseDto.fromJson(value); return AssetEditActionRotate.fromJson(value);
case 'AssetEditsDto':
return AssetEditsDto.fromJson(value);
case 'AssetFaceCreateDto': case 'AssetFaceCreateDto':
return AssetFaceCreateDto.fromJson(value); return AssetFaceCreateDto.fromJson(value);
case 'AssetFaceDeleteDto': case 'AssetFaceDeleteDto':
@@ -386,14 +388,6 @@ class ApiClient {
return FoldersUpdate.fromJson(value); return FoldersUpdate.fromJson(value);
case 'ImageFormat': case 'ImageFormat':
return ImageFormatTypeTransformer().decode(value); return ImageFormatTypeTransformer().decode(value);
case 'IntegrityReportDto':
return IntegrityReportDto.fromJson(value);
case 'IntegrityReportResponseDto':
return IntegrityReportResponseDto.fromJson(value);
case 'IntegrityReportSummaryResponseDto':
return IntegrityReportSummaryResponseDto.fromJson(value);
case 'IntegrityReportType':
return IntegrityReportTypeTypeTransformer().decode(value);
case 'JobCreateDto': case 'JobCreateDto':
return JobCreateDto.fromJson(value); return JobCreateDto.fromJson(value);
case 'JobName': case 'JobName':
@@ -690,8 +684,6 @@ class ApiClient {
return SyncAssetFaceDeleteV1.fromJson(value); return SyncAssetFaceDeleteV1.fromJson(value);
case 'SyncAssetFaceV1': case 'SyncAssetFaceV1':
return SyncAssetFaceV1.fromJson(value); return SyncAssetFaceV1.fromJson(value);
case 'SyncAssetFaceV2':
return SyncAssetFaceV2.fromJson(value);
case 'SyncAssetMetadataDeleteV1': case 'SyncAssetMetadataDeleteV1':
return SyncAssetMetadataDeleteV1.fromJson(value); return SyncAssetMetadataDeleteV1.fromJson(value);
case 'SyncAssetMetadataV1': case 'SyncAssetMetadataV1':
@@ -748,12 +740,6 @@ class ApiClient {
return SystemConfigGeneratedImageDto.fromJson(value); return SystemConfigGeneratedImageDto.fromJson(value);
case 'SystemConfigImageDto': case 'SystemConfigImageDto':
return SystemConfigImageDto.fromJson(value); return SystemConfigImageDto.fromJson(value);
case 'SystemConfigIntegrityChecks':
return SystemConfigIntegrityChecks.fromJson(value);
case 'SystemConfigIntegrityChecksumJob':
return SystemConfigIntegrityChecksumJob.fromJson(value);
case 'SystemConfigIntegrityJob':
return SystemConfigIntegrityJob.fromJson(value);
case 'SystemConfigJobDto': case 'SystemConfigJobDto':
return SystemConfigJobDto.fromJson(value); return SystemConfigJobDto.fromJson(value);
case 'SystemConfigLibraryDto': case 'SystemConfigLibraryDto':
-3
View File
@@ -94,9 +94,6 @@ String parameterToString(dynamic value) {
if (value is ImageFormat) { if (value is ImageFormat) {
return ImageFormatTypeTransformer().encode(value).toString(); return ImageFormatTypeTransformer().encode(value).toString();
} }
if (value is IntegrityReportType) {
return IntegrityReportTypeTypeTransformer().encode(value).toString();
}
if (value is JobName) { if (value is JobName) {
return JobNameTypeTransformer().encode(value).toString(); return JobNameTypeTransformer().encode(value).toString();
} }
@@ -10,9 +10,9 @@
part of openapi.api; part of openapi.api;
class AssetEditActionItemDto { class AssetEditActionCrop {
/// Returns a new [AssetEditActionItemDto] instance. /// Returns a new [AssetEditActionCrop] instance.
AssetEditActionItemDto({ AssetEditActionCrop({
required this.action, required this.action,
required this.parameters, required this.parameters,
}); });
@@ -20,10 +20,10 @@ class AssetEditActionItemDto {
/// Type of edit action to perform /// Type of edit action to perform
AssetEditAction action; AssetEditAction action;
AssetEditActionItemDtoParameters parameters; CropParameters parameters;
@override @override
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionItemDto && bool operator ==(Object other) => identical(this, other) || other is AssetEditActionCrop &&
other.action == action && other.action == action &&
other.parameters == parameters; other.parameters == parameters;
@@ -34,7 +34,7 @@ class AssetEditActionItemDto {
(parameters.hashCode); (parameters.hashCode);
@override @override
String toString() => 'AssetEditActionItemDto[action=$action, parameters=$parameters]'; String toString() => 'AssetEditActionCrop[action=$action, parameters=$parameters]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -43,27 +43,27 @@ class AssetEditActionItemDto {
return json; return json;
} }
/// Returns a new [AssetEditActionItemDto] instance and imports its values from /// Returns a new [AssetEditActionCrop] instance and imports its values from
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetEditActionItemDto? fromJson(dynamic value) { static AssetEditActionCrop? fromJson(dynamic value) {
upgradeDto(value, "AssetEditActionItemDto"); upgradeDto(value, "AssetEditActionCrop");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return AssetEditActionItemDto( return AssetEditActionCrop(
action: AssetEditAction.fromJson(json[r'action'])!, action: AssetEditAction.fromJson(json[r'action'])!,
parameters: AssetEditActionItemDtoParameters.fromJson(json[r'parameters'])!, parameters: CropParameters.fromJson(json[r'parameters'])!,
); );
} }
return null; return null;
} }
static List<AssetEditActionItemDto> listFromJson(dynamic json, {bool growable = false,}) { static List<AssetEditActionCrop> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetEditActionItemDto>[]; final result = <AssetEditActionCrop>[];
if (json is List && json.isNotEmpty) { if (json is List && json.isNotEmpty) {
for (final row in json) { for (final row in json) {
final value = AssetEditActionItemDto.fromJson(row); final value = AssetEditActionCrop.fromJson(row);
if (value != null) { if (value != null) {
result.add(value); result.add(value);
} }
@@ -72,12 +72,12 @@ class AssetEditActionItemDto {
return result.toList(growable: growable); return result.toList(growable: growable);
} }
static Map<String, AssetEditActionItemDto> mapFromJson(dynamic json) { static Map<String, AssetEditActionCrop> mapFromJson(dynamic json) {
final map = <String, AssetEditActionItemDto>{}; final map = <String, AssetEditActionCrop>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) { for (final entry in json.entries) {
final value = AssetEditActionItemDto.fromJson(entry.value); final value = AssetEditActionCrop.fromJson(entry.value);
if (value != null) { if (value != null) {
map[entry.key] = value; map[entry.key] = value;
} }
@@ -86,14 +86,14 @@ class AssetEditActionItemDto {
return map; return map;
} }
// maps a json object with a list of AssetEditActionItemDto-objects as value to a dart map // maps a json object with a list of AssetEditActionCrop-objects as value to a dart map
static Map<String, List<AssetEditActionItemDto>> mapListFromJson(dynamic json, {bool growable = false,}) { static Map<String, List<AssetEditActionCrop>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetEditActionItemDto>>{}; final map = <String, List<AssetEditActionCrop>>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments // ignore: parameter_assignments
json = json.cast<String, dynamic>(); json = json.cast<String, dynamic>();
for (final entry in json.entries) { for (final entry in json.entries) {
map[entry.key] = AssetEditActionItemDto.listFromJson(entry.value, growable: growable,); map[entry.key] = AssetEditActionCrop.listFromJson(entry.value, growable: growable,);
} }
} }
return map; return map;
@@ -1,153 +0,0 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class AssetEditActionItemDtoParameters {
/// Returns a new [AssetEditActionItemDtoParameters] instance.
AssetEditActionItemDtoParameters({
required this.height,
required this.width,
required this.x,
required this.y,
required this.angle,
required this.axis,
});
/// Height of the crop
///
/// Minimum value: 1
num height;
/// Width of the crop
///
/// Minimum value: 1
num width;
/// Top-Left X coordinate of crop
///
/// Minimum value: 0
num x;
/// Top-Left Y coordinate of crop
///
/// Minimum value: 0
num y;
/// Rotation angle in degrees
num angle;
/// Axis to mirror along
MirrorAxis axis;
@override
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionItemDtoParameters &&
other.height == height &&
other.width == width &&
other.x == x &&
other.y == y &&
other.angle == angle &&
other.axis == axis;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(height.hashCode) +
(width.hashCode) +
(x.hashCode) +
(y.hashCode) +
(angle.hashCode) +
(axis.hashCode);
@override
String toString() => 'AssetEditActionItemDtoParameters[height=$height, width=$width, x=$x, y=$y, angle=$angle, axis=$axis]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'height'] = this.height;
json[r'width'] = this.width;
json[r'x'] = this.x;
json[r'y'] = this.y;
json[r'angle'] = this.angle;
json[r'axis'] = this.axis;
return json;
}
/// Returns a new [AssetEditActionItemDtoParameters] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static AssetEditActionItemDtoParameters? fromJson(dynamic value) {
upgradeDto(value, "AssetEditActionItemDtoParameters");
if (value is Map) {
final json = value.cast<String, dynamic>();
return AssetEditActionItemDtoParameters(
height: num.parse('${json[r'height']}'),
width: num.parse('${json[r'width']}'),
x: num.parse('${json[r'x']}'),
y: num.parse('${json[r'y']}'),
angle: num.parse('${json[r'angle']}'),
axis: MirrorAxis.fromJson(json[r'axis'])!,
);
}
return null;
}
static List<AssetEditActionItemDtoParameters> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetEditActionItemDtoParameters>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetEditActionItemDtoParameters.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, AssetEditActionItemDtoParameters> mapFromJson(dynamic json) {
final map = <String, AssetEditActionItemDtoParameters>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = AssetEditActionItemDtoParameters.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of AssetEditActionItemDtoParameters-objects as value to a dart map
static Map<String, List<AssetEditActionItemDtoParameters>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetEditActionItemDtoParameters>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = AssetEditActionItemDtoParameters.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'height',
'width',
'x',
'y',
'angle',
'axis',
};
}
@@ -10,17 +10,17 @@
part of openapi.api; part of openapi.api;
class AssetEditsCreateDto { class AssetEditActionListDto {
/// Returns a new [AssetEditsCreateDto] instance. /// Returns a new [AssetEditActionListDto] instance.
AssetEditsCreateDto({ AssetEditActionListDto({
this.edits = const [], this.edits = const [],
}); });
/// List of edit actions to apply (crop, rotate, or mirror) /// List of edit actions to apply (crop, rotate, or mirror)
List<AssetEditActionItemDto> edits; List<AssetEditActionListDtoEditsInner> edits;
@override @override
bool operator ==(Object other) => identical(this, other) || other is AssetEditsCreateDto && bool operator ==(Object other) => identical(this, other) || other is AssetEditActionListDto &&
_deepEquality.equals(other.edits, edits); _deepEquality.equals(other.edits, edits);
@override @override
@@ -29,7 +29,7 @@ class AssetEditsCreateDto {
(edits.hashCode); (edits.hashCode);
@override @override
String toString() => 'AssetEditsCreateDto[edits=$edits]'; String toString() => 'AssetEditActionListDto[edits=$edits]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -37,26 +37,26 @@ class AssetEditsCreateDto {
return json; return json;
} }
/// Returns a new [AssetEditsCreateDto] instance and imports its values from /// Returns a new [AssetEditActionListDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetEditsCreateDto? fromJson(dynamic value) { static AssetEditActionListDto? fromJson(dynamic value) {
upgradeDto(value, "AssetEditsCreateDto"); upgradeDto(value, "AssetEditActionListDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return AssetEditsCreateDto( return AssetEditActionListDto(
edits: AssetEditActionItemDto.listFromJson(json[r'edits']), edits: AssetEditActionListDtoEditsInner.listFromJson(json[r'edits']),
); );
} }
return null; return null;
} }
static List<AssetEditsCreateDto> listFromJson(dynamic json, {bool growable = false,}) { static List<AssetEditActionListDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetEditsCreateDto>[]; final result = <AssetEditActionListDto>[];
if (json is List && json.isNotEmpty) { if (json is List && json.isNotEmpty) {
for (final row in json) { for (final row in json) {
final value = AssetEditsCreateDto.fromJson(row); final value = AssetEditActionListDto.fromJson(row);
if (value != null) { if (value != null) {
result.add(value); result.add(value);
} }
@@ -65,12 +65,12 @@ class AssetEditsCreateDto {
return result.toList(growable: growable); return result.toList(growable: growable);
} }
static Map<String, AssetEditsCreateDto> mapFromJson(dynamic json) { static Map<String, AssetEditActionListDto> mapFromJson(dynamic json) {
final map = <String, AssetEditsCreateDto>{}; final map = <String, AssetEditActionListDto>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) { for (final entry in json.entries) {
final value = AssetEditsCreateDto.fromJson(entry.value); final value = AssetEditActionListDto.fromJson(entry.value);
if (value != null) { if (value != null) {
map[entry.key] = value; map[entry.key] = value;
} }
@@ -79,14 +79,14 @@ class AssetEditsCreateDto {
return map; return map;
} }
// maps a json object with a list of AssetEditsCreateDto-objects as value to a dart map // maps a json object with a list of AssetEditActionListDto-objects as value to a dart map
static Map<String, List<AssetEditsCreateDto>> mapListFromJson(dynamic json, {bool growable = false,}) { static Map<String, List<AssetEditActionListDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetEditsCreateDto>>{}; final map = <String, List<AssetEditActionListDto>>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments // ignore: parameter_assignments
json = json.cast<String, dynamic>(); json = json.cast<String, dynamic>();
for (final entry in json.entries) { for (final entry in json.entries) {
map[entry.key] = AssetEditsCreateDto.listFromJson(entry.value, growable: growable,); map[entry.key] = AssetEditActionListDto.listFromJson(entry.value, growable: growable,);
} }
} }
return map; return map;
@@ -10,67 +10,60 @@
part of openapi.api; part of openapi.api;
class AssetEditActionItemResponseDto { class AssetEditActionListDtoEditsInner {
/// Returns a new [AssetEditActionItemResponseDto] instance. /// Returns a new [AssetEditActionListDtoEditsInner] instance.
AssetEditActionItemResponseDto({ AssetEditActionListDtoEditsInner({
required this.action, required this.action,
required this.id,
required this.parameters, required this.parameters,
}); });
/// Type of edit action to perform /// Type of edit action to perform
AssetEditAction action; AssetEditAction action;
String id; MirrorParameters parameters;
AssetEditActionItemDtoParameters parameters;
@override @override
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionItemResponseDto && bool operator ==(Object other) => identical(this, other) || other is AssetEditActionListDtoEditsInner &&
other.action == action && other.action == action &&
other.id == id &&
other.parameters == parameters; other.parameters == parameters;
@override @override
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(action.hashCode) + (action.hashCode) +
(id.hashCode) +
(parameters.hashCode); (parameters.hashCode);
@override @override
String toString() => 'AssetEditActionItemResponseDto[action=$action, id=$id, parameters=$parameters]'; String toString() => 'AssetEditActionListDtoEditsInner[action=$action, parameters=$parameters]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
json[r'action'] = this.action; json[r'action'] = this.action;
json[r'id'] = this.id;
json[r'parameters'] = this.parameters; json[r'parameters'] = this.parameters;
return json; return json;
} }
/// Returns a new [AssetEditActionItemResponseDto] instance and imports its values from /// Returns a new [AssetEditActionListDtoEditsInner] instance and imports its values from
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetEditActionItemResponseDto? fromJson(dynamic value) { static AssetEditActionListDtoEditsInner? fromJson(dynamic value) {
upgradeDto(value, "AssetEditActionItemResponseDto"); upgradeDto(value, "AssetEditActionListDtoEditsInner");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return AssetEditActionItemResponseDto( return AssetEditActionListDtoEditsInner(
action: AssetEditAction.fromJson(json[r'action'])!, action: AssetEditAction.fromJson(json[r'action'])!,
id: mapValueOfType<String>(json, r'id')!, parameters: MirrorParameters.fromJson(json[r'parameters'])!,
parameters: AssetEditActionItemDtoParameters.fromJson(json[r'parameters'])!,
); );
} }
return null; return null;
} }
static List<AssetEditActionItemResponseDto> listFromJson(dynamic json, {bool growable = false,}) { static List<AssetEditActionListDtoEditsInner> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetEditActionItemResponseDto>[]; final result = <AssetEditActionListDtoEditsInner>[];
if (json is List && json.isNotEmpty) { if (json is List && json.isNotEmpty) {
for (final row in json) { for (final row in json) {
final value = AssetEditActionItemResponseDto.fromJson(row); final value = AssetEditActionListDtoEditsInner.fromJson(row);
if (value != null) { if (value != null) {
result.add(value); result.add(value);
} }
@@ -79,12 +72,12 @@ class AssetEditActionItemResponseDto {
return result.toList(growable: growable); return result.toList(growable: growable);
} }
static Map<String, AssetEditActionItemResponseDto> mapFromJson(dynamic json) { static Map<String, AssetEditActionListDtoEditsInner> mapFromJson(dynamic json) {
final map = <String, AssetEditActionItemResponseDto>{}; final map = <String, AssetEditActionListDtoEditsInner>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) { for (final entry in json.entries) {
final value = AssetEditActionItemResponseDto.fromJson(entry.value); final value = AssetEditActionListDtoEditsInner.fromJson(entry.value);
if (value != null) { if (value != null) {
map[entry.key] = value; map[entry.key] = value;
} }
@@ -93,14 +86,14 @@ class AssetEditActionItemResponseDto {
return map; return map;
} }
// maps a json object with a list of AssetEditActionItemResponseDto-objects as value to a dart map // maps a json object with a list of AssetEditActionListDtoEditsInner-objects as value to a dart map
static Map<String, List<AssetEditActionItemResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) { static Map<String, List<AssetEditActionListDtoEditsInner>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetEditActionItemResponseDto>>{}; final map = <String, List<AssetEditActionListDtoEditsInner>>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments // ignore: parameter_assignments
json = json.cast<String, dynamic>(); json = json.cast<String, dynamic>();
for (final entry in json.entries) { for (final entry in json.entries) {
map[entry.key] = AssetEditActionItemResponseDto.listFromJson(entry.value, growable: growable,); map[entry.key] = AssetEditActionListDtoEditsInner.listFromJson(entry.value, growable: growable,);
} }
} }
return map; return map;
@@ -109,7 +102,6 @@ class AssetEditActionItemResponseDto {
/// The list of required keys that must be present in a JSON. /// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{ static const requiredKeys = <String>{
'action', 'action',
'id',
'parameters', 'parameters',
}; };
} }
+108
View File
@@ -0,0 +1,108 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class AssetEditActionMirror {
/// Returns a new [AssetEditActionMirror] instance.
AssetEditActionMirror({
required this.action,
required this.parameters,
});
/// Type of edit action to perform
AssetEditAction action;
MirrorParameters parameters;
@override
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionMirror &&
other.action == action &&
other.parameters == parameters;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(action.hashCode) +
(parameters.hashCode);
@override
String toString() => 'AssetEditActionMirror[action=$action, parameters=$parameters]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'action'] = this.action;
json[r'parameters'] = this.parameters;
return json;
}
/// Returns a new [AssetEditActionMirror] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static AssetEditActionMirror? fromJson(dynamic value) {
upgradeDto(value, "AssetEditActionMirror");
if (value is Map) {
final json = value.cast<String, dynamic>();
return AssetEditActionMirror(
action: AssetEditAction.fromJson(json[r'action'])!,
parameters: MirrorParameters.fromJson(json[r'parameters'])!,
);
}
return null;
}
static List<AssetEditActionMirror> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetEditActionMirror>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetEditActionMirror.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, AssetEditActionMirror> mapFromJson(dynamic json) {
final map = <String, AssetEditActionMirror>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = AssetEditActionMirror.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of AssetEditActionMirror-objects as value to a dart map
static Map<String, List<AssetEditActionMirror>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetEditActionMirror>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = AssetEditActionMirror.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'action',
'parameters',
};
}
+108
View File
@@ -0,0 +1,108 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class AssetEditActionRotate {
/// Returns a new [AssetEditActionRotate] instance.
AssetEditActionRotate({
required this.action,
required this.parameters,
});
/// Type of edit action to perform
AssetEditAction action;
RotateParameters parameters;
@override
bool operator ==(Object other) => identical(this, other) || other is AssetEditActionRotate &&
other.action == action &&
other.parameters == parameters;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(action.hashCode) +
(parameters.hashCode);
@override
String toString() => 'AssetEditActionRotate[action=$action, parameters=$parameters]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'action'] = this.action;
json[r'parameters'] = this.parameters;
return json;
}
/// Returns a new [AssetEditActionRotate] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static AssetEditActionRotate? fromJson(dynamic value) {
upgradeDto(value, "AssetEditActionRotate");
if (value is Map) {
final json = value.cast<String, dynamic>();
return AssetEditActionRotate(
action: AssetEditAction.fromJson(json[r'action'])!,
parameters: RotateParameters.fromJson(json[r'parameters'])!,
);
}
return null;
}
static List<AssetEditActionRotate> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetEditActionRotate>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetEditActionRotate.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, AssetEditActionRotate> mapFromJson(dynamic json) {
final map = <String, AssetEditActionRotate>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = AssetEditActionRotate.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of AssetEditActionRotate-objects as value to a dart map
static Map<String, List<AssetEditActionRotate>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetEditActionRotate>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = AssetEditActionRotate.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'action',
'parameters',
};
}
@@ -10,21 +10,21 @@
part of openapi.api; part of openapi.api;
class AssetEditsResponseDto { class AssetEditsDto {
/// Returns a new [AssetEditsResponseDto] instance. /// Returns a new [AssetEditsDto] instance.
AssetEditsResponseDto({ AssetEditsDto({
required this.assetId, required this.assetId,
this.edits = const [], this.edits = const [],
}); });
/// Asset ID these edits belong to /// Asset ID to apply edits to
String assetId; String assetId;
/// List of edit actions applied to the asset /// List of edit actions to apply (crop, rotate, or mirror)
List<AssetEditActionItemResponseDto> edits; List<AssetEditActionListDtoEditsInner> edits;
@override @override
bool operator ==(Object other) => identical(this, other) || other is AssetEditsResponseDto && bool operator ==(Object other) => identical(this, other) || other is AssetEditsDto &&
other.assetId == assetId && other.assetId == assetId &&
_deepEquality.equals(other.edits, edits); _deepEquality.equals(other.edits, edits);
@@ -35,7 +35,7 @@ class AssetEditsResponseDto {
(edits.hashCode); (edits.hashCode);
@override @override
String toString() => 'AssetEditsResponseDto[assetId=$assetId, edits=$edits]'; String toString() => 'AssetEditsDto[assetId=$assetId, edits=$edits]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -44,27 +44,27 @@ class AssetEditsResponseDto {
return json; return json;
} }
/// Returns a new [AssetEditsResponseDto] instance and imports its values from /// Returns a new [AssetEditsDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise. /// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static AssetEditsResponseDto? fromJson(dynamic value) { static AssetEditsDto? fromJson(dynamic value) {
upgradeDto(value, "AssetEditsResponseDto"); upgradeDto(value, "AssetEditsDto");
if (value is Map) { if (value is Map) {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return AssetEditsResponseDto( return AssetEditsDto(
assetId: mapValueOfType<String>(json, r'assetId')!, assetId: mapValueOfType<String>(json, r'assetId')!,
edits: AssetEditActionItemResponseDto.listFromJson(json[r'edits']), edits: AssetEditActionListDtoEditsInner.listFromJson(json[r'edits']),
); );
} }
return null; return null;
} }
static List<AssetEditsResponseDto> listFromJson(dynamic json, {bool growable = false,}) { static List<AssetEditsDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetEditsResponseDto>[]; final result = <AssetEditsDto>[];
if (json is List && json.isNotEmpty) { if (json is List && json.isNotEmpty) {
for (final row in json) { for (final row in json) {
final value = AssetEditsResponseDto.fromJson(row); final value = AssetEditsDto.fromJson(row);
if (value != null) { if (value != null) {
result.add(value); result.add(value);
} }
@@ -73,12 +73,12 @@ class AssetEditsResponseDto {
return result.toList(growable: growable); return result.toList(growable: growable);
} }
static Map<String, AssetEditsResponseDto> mapFromJson(dynamic json) { static Map<String, AssetEditsDto> mapFromJson(dynamic json) {
final map = <String, AssetEditsResponseDto>{}; final map = <String, AssetEditsDto>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) { for (final entry in json.entries) {
final value = AssetEditsResponseDto.fromJson(entry.value); final value = AssetEditsDto.fromJson(entry.value);
if (value != null) { if (value != null) {
map[entry.key] = value; map[entry.key] = value;
} }
@@ -87,14 +87,14 @@ class AssetEditsResponseDto {
return map; return map;
} }
// maps a json object with a list of AssetEditsResponseDto-objects as value to a dart map // maps a json object with a list of AssetEditsDto-objects as value to a dart map
static Map<String, List<AssetEditsResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) { static Map<String, List<AssetEditsDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetEditsResponseDto>>{}; final map = <String, List<AssetEditsDto>>{};
if (json is Map && json.isNotEmpty) { if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments // ignore: parameter_assignments
json = json.cast<String, dynamic>(); json = json.cast<String, dynamic>();
for (final entry in json.entries) { for (final entry in json.entries) {
map[entry.key] = AssetEditsResponseDto.listFromJson(entry.value, growable: growable,); map[entry.key] = AssetEditsDto.listFromJson(entry.value, growable: growable,);
} }
} }
return map; return map;
+1 -1
View File
@@ -156,7 +156,7 @@ class AssetResponseDto {
List<TagResponseDto> tags; List<TagResponseDto> tags;
/// Thumbhash for thumbnail generation (base64) also used as the c query param for thumbnail cache busting. /// Thumbhash for thumbnail generation
String? thumbhash; String? thumbhash;
/// Asset type /// Asset type
-115
View File
@@ -1,115 +0,0 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class IntegrityReportDto {
/// Returns a new [IntegrityReportDto] instance.
IntegrityReportDto({
required this.id,
required this.path,
required this.type,
});
String id;
String path;
IntegrityReportType type;
@override
bool operator ==(Object other) => identical(this, other) || other is IntegrityReportDto &&
other.id == id &&
other.path == path &&
other.type == type;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(id.hashCode) +
(path.hashCode) +
(type.hashCode);
@override
String toString() => 'IntegrityReportDto[id=$id, path=$path, type=$type]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'id'] = this.id;
json[r'path'] = this.path;
json[r'type'] = this.type;
return json;
}
/// Returns a new [IntegrityReportDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static IntegrityReportDto? fromJson(dynamic value) {
upgradeDto(value, "IntegrityReportDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return IntegrityReportDto(
id: mapValueOfType<String>(json, r'id')!,
path: mapValueOfType<String>(json, r'path')!,
type: IntegrityReportType.fromJson(json[r'type'])!,
);
}
return null;
}
static List<IntegrityReportDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <IntegrityReportDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = IntegrityReportDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, IntegrityReportDto> mapFromJson(dynamic json) {
final map = <String, IntegrityReportDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = IntegrityReportDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of IntegrityReportDto-objects as value to a dart map
static Map<String, List<IntegrityReportDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<IntegrityReportDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = IntegrityReportDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'id',
'path',
'type',
};
}
@@ -1,116 +0,0 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class IntegrityReportResponseDto {
/// Returns a new [IntegrityReportResponseDto] instance.
IntegrityReportResponseDto({
this.items = const [],
this.nextCursor,
});
List<IntegrityReportDto> items;
///
/// 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.
///
String? nextCursor;
@override
bool operator ==(Object other) => identical(this, other) || other is IntegrityReportResponseDto &&
_deepEquality.equals(other.items, items) &&
other.nextCursor == nextCursor;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(items.hashCode) +
(nextCursor == null ? 0 : nextCursor!.hashCode);
@override
String toString() => 'IntegrityReportResponseDto[items=$items, nextCursor=$nextCursor]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'items'] = this.items;
if (this.nextCursor != null) {
json[r'nextCursor'] = this.nextCursor;
} else {
// json[r'nextCursor'] = null;
}
return json;
}
/// Returns a new [IntegrityReportResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static IntegrityReportResponseDto? fromJson(dynamic value) {
upgradeDto(value, "IntegrityReportResponseDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return IntegrityReportResponseDto(
items: IntegrityReportDto.listFromJson(json[r'items']),
nextCursor: mapValueOfType<String>(json, r'nextCursor'),
);
}
return null;
}
static List<IntegrityReportResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <IntegrityReportResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = IntegrityReportResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, IntegrityReportResponseDto> mapFromJson(dynamic json) {
final map = <String, IntegrityReportResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = IntegrityReportResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of IntegrityReportResponseDto-objects as value to a dart map
static Map<String, List<IntegrityReportResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<IntegrityReportResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = IntegrityReportResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'items',
};
}
@@ -1,115 +0,0 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class IntegrityReportSummaryResponseDto {
/// Returns a new [IntegrityReportSummaryResponseDto] instance.
IntegrityReportSummaryResponseDto({
required this.checksumMismatch,
required this.missingFile,
required this.untrackedFile,
});
int checksumMismatch;
int missingFile;
int untrackedFile;
@override
bool operator ==(Object other) => identical(this, other) || other is IntegrityReportSummaryResponseDto &&
other.checksumMismatch == checksumMismatch &&
other.missingFile == missingFile &&
other.untrackedFile == untrackedFile;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(checksumMismatch.hashCode) +
(missingFile.hashCode) +
(untrackedFile.hashCode);
@override
String toString() => 'IntegrityReportSummaryResponseDto[checksumMismatch=$checksumMismatch, missingFile=$missingFile, untrackedFile=$untrackedFile]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'checksum_mismatch'] = this.checksumMismatch;
json[r'missing_file'] = this.missingFile;
json[r'untracked_file'] = this.untrackedFile;
return json;
}
/// Returns a new [IntegrityReportSummaryResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static IntegrityReportSummaryResponseDto? fromJson(dynamic value) {
upgradeDto(value, "IntegrityReportSummaryResponseDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return IntegrityReportSummaryResponseDto(
checksumMismatch: mapValueOfType<int>(json, r'checksum_mismatch')!,
missingFile: mapValueOfType<int>(json, r'missing_file')!,
untrackedFile: mapValueOfType<int>(json, r'untracked_file')!,
);
}
return null;
}
static List<IntegrityReportSummaryResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <IntegrityReportSummaryResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = IntegrityReportSummaryResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, IntegrityReportSummaryResponseDto> mapFromJson(dynamic json) {
final map = <String, IntegrityReportSummaryResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = IntegrityReportSummaryResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of IntegrityReportSummaryResponseDto-objects as value to a dart map
static Map<String, List<IntegrityReportSummaryResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<IntegrityReportSummaryResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = IntegrityReportSummaryResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'checksum_mismatch',
'missing_file',
'untracked_file',
};
}
-88
View File
@@ -1,88 +0,0 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class IntegrityReportType {
/// Instantiate a new enum with the provided [value].
const IntegrityReportType._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const untrackedFile = IntegrityReportType._(r'untracked_file');
static const missingFile = IntegrityReportType._(r'missing_file');
static const checksumMismatch = IntegrityReportType._(r'checksum_mismatch');
/// List of all possible values in this [enum][IntegrityReportType].
static const values = <IntegrityReportType>[
untrackedFile,
missingFile,
checksumMismatch,
];
static IntegrityReportType? fromJson(dynamic value) => IntegrityReportTypeTypeTransformer().decode(value);
static List<IntegrityReportType> listFromJson(dynamic json, {bool growable = false,}) {
final result = <IntegrityReportType>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = IntegrityReportType.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [IntegrityReportType] to String,
/// and [decode] dynamic data back to [IntegrityReportType].
class IntegrityReportTypeTypeTransformer {
factory IntegrityReportTypeTypeTransformer() => _instance ??= const IntegrityReportTypeTypeTransformer._();
const IntegrityReportTypeTypeTransformer._();
String encode(IntegrityReportType data) => data.value;
/// Decodes a [dynamic value][data] to a IntegrityReportType.
///
/// 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.
IntegrityReportType? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'untracked_file': return IntegrityReportType.untrackedFile;
case r'missing_file': return IntegrityReportType.missingFile;
case r'checksum_mismatch': return IntegrityReportType.checksumMismatch;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [IntegrityReportTypeTypeTransformer] instance.
static IntegrityReportTypeTypeTransformer? _instance;
}
-30
View File
@@ -79,16 +79,6 @@ class JobName {
static const ocrQueueAll = JobName._(r'OcrQueueAll'); static const ocrQueueAll = JobName._(r'OcrQueueAll');
static const ocr = JobName._(r'Ocr'); static const ocr = JobName._(r'Ocr');
static const workflowRun = JobName._(r'WorkflowRun'); static const workflowRun = JobName._(r'WorkflowRun');
static const integrityUntrackedFilesQueueAll = JobName._(r'IntegrityUntrackedFilesQueueAll');
static const integrityUntrackedFiles = JobName._(r'IntegrityUntrackedFiles');
static const integrityUntrackedRefresh = JobName._(r'IntegrityUntrackedRefresh');
static const integrityMissingFilesQueueAll = JobName._(r'IntegrityMissingFilesQueueAll');
static const integrityMissingFiles = JobName._(r'IntegrityMissingFiles');
static const integrityMissingFilesRefresh = JobName._(r'IntegrityMissingFilesRefresh');
static const integrityChecksumFiles = JobName._(r'IntegrityChecksumFiles');
static const integrityChecksumFilesRefresh = JobName._(r'IntegrityChecksumFilesRefresh');
static const integrityDeleteReportType = JobName._(r'IntegrityDeleteReportType');
static const integrityDeleteReports = JobName._(r'IntegrityDeleteReports');
/// List of all possible values in this [enum][JobName]. /// List of all possible values in this [enum][JobName].
static const values = <JobName>[ static const values = <JobName>[
@@ -148,16 +138,6 @@ class JobName {
ocrQueueAll, ocrQueueAll,
ocr, ocr,
workflowRun, workflowRun,
integrityUntrackedFilesQueueAll,
integrityUntrackedFiles,
integrityUntrackedRefresh,
integrityMissingFilesQueueAll,
integrityMissingFiles,
integrityMissingFilesRefresh,
integrityChecksumFiles,
integrityChecksumFilesRefresh,
integrityDeleteReportType,
integrityDeleteReports,
]; ];
static JobName? fromJson(dynamic value) => JobNameTypeTransformer().decode(value); static JobName? fromJson(dynamic value) => JobNameTypeTransformer().decode(value);
@@ -252,16 +232,6 @@ class JobNameTypeTransformer {
case r'OcrQueueAll': return JobName.ocrQueueAll; case r'OcrQueueAll': return JobName.ocrQueueAll;
case r'Ocr': return JobName.ocr; case r'Ocr': return JobName.ocr;
case r'WorkflowRun': return JobName.workflowRun; case r'WorkflowRun': return JobName.workflowRun;
case r'IntegrityUntrackedFilesQueueAll': return JobName.integrityUntrackedFilesQueueAll;
case r'IntegrityUntrackedFiles': return JobName.integrityUntrackedFiles;
case r'IntegrityUntrackedRefresh': return JobName.integrityUntrackedRefresh;
case r'IntegrityMissingFilesQueueAll': return JobName.integrityMissingFilesQueueAll;
case r'IntegrityMissingFiles': return JobName.integrityMissingFiles;
case r'IntegrityMissingFilesRefresh': return JobName.integrityMissingFilesRefresh;
case r'IntegrityChecksumFiles': return JobName.integrityChecksumFiles;
case r'IntegrityChecksumFilesRefresh': return JobName.integrityChecksumFilesRefresh;
case r'IntegrityDeleteReportType': return JobName.integrityDeleteReportType;
case r'IntegrityDeleteReports': return JobName.integrityDeleteReports;
default: default:
if (!allowNull) { if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data'); throw ArgumentError('Unknown enum value to decode: $data');
+10 -1
View File
@@ -13,12 +13,16 @@ part of openapi.api;
class LibraryStatsResponseDto { class LibraryStatsResponseDto {
/// Returns a new [LibraryStatsResponseDto] instance. /// Returns a new [LibraryStatsResponseDto] instance.
LibraryStatsResponseDto({ LibraryStatsResponseDto({
this.offline = 0,
this.photos = 0, this.photos = 0,
this.total = 0, this.total = 0,
this.usage = 0, this.usage = 0,
this.videos = 0, this.videos = 0,
}); });
/// Number of offline assets
int offline;
/// Number of photos /// Number of photos
int photos; int photos;
@@ -33,6 +37,7 @@ class LibraryStatsResponseDto {
@override @override
bool operator ==(Object other) => identical(this, other) || other is LibraryStatsResponseDto && bool operator ==(Object other) => identical(this, other) || other is LibraryStatsResponseDto &&
other.offline == offline &&
other.photos == photos && other.photos == photos &&
other.total == total && other.total == total &&
other.usage == usage && other.usage == usage &&
@@ -41,16 +46,18 @@ class LibraryStatsResponseDto {
@override @override
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(offline.hashCode) +
(photos.hashCode) + (photos.hashCode) +
(total.hashCode) + (total.hashCode) +
(usage.hashCode) + (usage.hashCode) +
(videos.hashCode); (videos.hashCode);
@override @override
String toString() => 'LibraryStatsResponseDto[photos=$photos, total=$total, usage=$usage, videos=$videos]'; String toString() => 'LibraryStatsResponseDto[offline=$offline, photos=$photos, total=$total, usage=$usage, videos=$videos]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
json[r'offline'] = this.offline;
json[r'photos'] = this.photos; json[r'photos'] = this.photos;
json[r'total'] = this.total; json[r'total'] = this.total;
json[r'usage'] = this.usage; json[r'usage'] = this.usage;
@@ -67,6 +74,7 @@ class LibraryStatsResponseDto {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return LibraryStatsResponseDto( return LibraryStatsResponseDto(
offline: mapValueOfType<int>(json, r'offline')!,
photos: mapValueOfType<int>(json, r'photos')!, photos: mapValueOfType<int>(json, r'photos')!,
total: mapValueOfType<int>(json, r'total')!, total: mapValueOfType<int>(json, r'total')!,
usage: mapValueOfType<int>(json, r'usage')!, usage: mapValueOfType<int>(json, r'usage')!,
@@ -118,6 +126,7 @@ class LibraryStatsResponseDto {
/// The list of required keys that must be present in a JSON. /// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{ static const requiredKeys = <String>{
'offline',
'photos', 'photos',
'total', 'total',
'usage', 'usage',
-27
View File
@@ -29,15 +29,6 @@ class ManualJobName {
static const memoryCleanup = ManualJobName._(r'memory-cleanup'); static const memoryCleanup = ManualJobName._(r'memory-cleanup');
static const memoryCreate = ManualJobName._(r'memory-create'); static const memoryCreate = ManualJobName._(r'memory-create');
static const backupDatabase = ManualJobName._(r'backup-database'); static const backupDatabase = ManualJobName._(r'backup-database');
static const integrityMissingFiles = ManualJobName._(r'integrity-missing-files');
static const integrityUntrackedFiles = ManualJobName._(r'integrity-untracked-files');
static const integrityChecksumMismatch = ManualJobName._(r'integrity-checksum-mismatch');
static const integrityMissingFilesRefresh = ManualJobName._(r'integrity-missing-files-refresh');
static const integrityUntrackedFilesRefresh = ManualJobName._(r'integrity-untracked-files-refresh');
static const integrityChecksumMismatchRefresh = ManualJobName._(r'integrity-checksum-mismatch-refresh');
static const integrityMissingFilesDeleteAll = ManualJobName._(r'integrity-missing-files-delete-all');
static const integrityUntrackedFilesDeleteAll = ManualJobName._(r'integrity-untracked-files-delete-all');
static const integrityChecksumMismatchDeleteAll = ManualJobName._(r'integrity-checksum-mismatch-delete-all');
/// List of all possible values in this [enum][ManualJobName]. /// List of all possible values in this [enum][ManualJobName].
static const values = <ManualJobName>[ static const values = <ManualJobName>[
@@ -47,15 +38,6 @@ class ManualJobName {
memoryCleanup, memoryCleanup,
memoryCreate, memoryCreate,
backupDatabase, backupDatabase,
integrityMissingFiles,
integrityUntrackedFiles,
integrityChecksumMismatch,
integrityMissingFilesRefresh,
integrityUntrackedFilesRefresh,
integrityChecksumMismatchRefresh,
integrityMissingFilesDeleteAll,
integrityUntrackedFilesDeleteAll,
integrityChecksumMismatchDeleteAll,
]; ];
static ManualJobName? fromJson(dynamic value) => ManualJobNameTypeTransformer().decode(value); static ManualJobName? fromJson(dynamic value) => ManualJobNameTypeTransformer().decode(value);
@@ -100,15 +82,6 @@ class ManualJobNameTypeTransformer {
case r'memory-cleanup': return ManualJobName.memoryCleanup; case r'memory-cleanup': return ManualJobName.memoryCleanup;
case r'memory-create': return ManualJobName.memoryCreate; case r'memory-create': return ManualJobName.memoryCreate;
case r'backup-database': return ManualJobName.backupDatabase; case r'backup-database': return ManualJobName.backupDatabase;
case r'integrity-missing-files': return ManualJobName.integrityMissingFiles;
case r'integrity-untracked-files': return ManualJobName.integrityUntrackedFiles;
case r'integrity-checksum-mismatch': return ManualJobName.integrityChecksumMismatch;
case r'integrity-missing-files-refresh': return ManualJobName.integrityMissingFilesRefresh;
case r'integrity-untracked-files-refresh': return ManualJobName.integrityUntrackedFilesRefresh;
case r'integrity-checksum-mismatch-refresh': return ManualJobName.integrityChecksumMismatchRefresh;
case r'integrity-missing-files-delete-all': return ManualJobName.integrityMissingFilesDeleteAll;
case r'integrity-untracked-files-delete-all': return ManualJobName.integrityUntrackedFilesDeleteAll;
case r'integrity-checksum-mismatch-delete-all': return ManualJobName.integrityChecksumMismatchDeleteAll;
default: default:
if (!allowNull) { if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data'); throw ArgumentError('Unknown enum value to decode: $data');
+1 -37
View File
@@ -15,11 +15,9 @@ class MemoryCreateDto {
MemoryCreateDto({ MemoryCreateDto({
this.assetIds = const [], this.assetIds = const [],
required this.data, required this.data,
this.hideAt,
this.isSaved, this.isSaved,
required this.memoryAt, required this.memoryAt,
this.seenAt, this.seenAt,
this.showAt,
required this.type, required this.type,
}); });
@@ -28,15 +26,6 @@ class MemoryCreateDto {
OnThisDayDto data; OnThisDayDto data;
/// Date when memory should be hidden
///
/// 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.
///
DateTime? hideAt;
/// Is memory saved /// Is memory saved
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// Please note: This property should have been non-nullable! Since the specification file
@@ -58,15 +47,6 @@ class MemoryCreateDto {
/// ///
DateTime? seenAt; DateTime? seenAt;
/// Date when memory should be shown
///
/// 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.
///
DateTime? showAt;
/// Memory type /// Memory type
MemoryType type; MemoryType type;
@@ -74,11 +54,9 @@ class MemoryCreateDto {
bool operator ==(Object other) => identical(this, other) || other is MemoryCreateDto && bool operator ==(Object other) => identical(this, other) || other is MemoryCreateDto &&
_deepEquality.equals(other.assetIds, assetIds) && _deepEquality.equals(other.assetIds, assetIds) &&
other.data == data && other.data == data &&
other.hideAt == hideAt &&
other.isSaved == isSaved && other.isSaved == isSaved &&
other.memoryAt == memoryAt && other.memoryAt == memoryAt &&
other.seenAt == seenAt && other.seenAt == seenAt &&
other.showAt == showAt &&
other.type == type; other.type == type;
@override @override
@@ -86,25 +64,18 @@ class MemoryCreateDto {
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(assetIds.hashCode) + (assetIds.hashCode) +
(data.hashCode) + (data.hashCode) +
(hideAt == null ? 0 : hideAt!.hashCode) +
(isSaved == null ? 0 : isSaved!.hashCode) + (isSaved == null ? 0 : isSaved!.hashCode) +
(memoryAt.hashCode) + (memoryAt.hashCode) +
(seenAt == null ? 0 : seenAt!.hashCode) + (seenAt == null ? 0 : seenAt!.hashCode) +
(showAt == null ? 0 : showAt!.hashCode) +
(type.hashCode); (type.hashCode);
@override @override
String toString() => 'MemoryCreateDto[assetIds=$assetIds, data=$data, hideAt=$hideAt, isSaved=$isSaved, memoryAt=$memoryAt, seenAt=$seenAt, showAt=$showAt, type=$type]'; String toString() => 'MemoryCreateDto[assetIds=$assetIds, data=$data, isSaved=$isSaved, memoryAt=$memoryAt, seenAt=$seenAt, type=$type]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
json[r'assetIds'] = this.assetIds; json[r'assetIds'] = this.assetIds;
json[r'data'] = this.data; json[r'data'] = this.data;
if (this.hideAt != null) {
json[r'hideAt'] = this.hideAt!.toUtc().toIso8601String();
} else {
// json[r'hideAt'] = null;
}
if (this.isSaved != null) { if (this.isSaved != null) {
json[r'isSaved'] = this.isSaved; json[r'isSaved'] = this.isSaved;
} else { } else {
@@ -115,11 +86,6 @@ class MemoryCreateDto {
json[r'seenAt'] = this.seenAt!.toUtc().toIso8601String(); json[r'seenAt'] = this.seenAt!.toUtc().toIso8601String();
} else { } else {
// json[r'seenAt'] = null; // json[r'seenAt'] = null;
}
if (this.showAt != null) {
json[r'showAt'] = this.showAt!.toUtc().toIso8601String();
} else {
// json[r'showAt'] = null;
} }
json[r'type'] = this.type; json[r'type'] = this.type;
return json; return json;
@@ -138,11 +104,9 @@ class MemoryCreateDto {
? (json[r'assetIds'] as Iterable).cast<String>().toList(growable: false) ? (json[r'assetIds'] as Iterable).cast<String>().toList(growable: false)
: const [], : const [],
data: OnThisDayDto.fromJson(json[r'data'])!, data: OnThisDayDto.fromJson(json[r'data'])!,
hideAt: mapDateTime(json, r'hideAt', r''),
isSaved: mapValueOfType<bool>(json, r'isSaved'), isSaved: mapValueOfType<bool>(json, r'isSaved'),
memoryAt: mapDateTime(json, r'memoryAt', r'')!, memoryAt: mapDateTime(json, r'memoryAt', r'')!,
seenAt: mapDateTime(json, r'seenAt', r''), seenAt: mapDateTime(json, r'seenAt', r''),
showAt: mapDateTime(json, r'showAt', r''),
type: MemoryType.fromJson(json[r'type'])!, type: MemoryType.fromJson(json[r'type'])!,
); );
} }
-3
View File
@@ -40,7 +40,6 @@ class QueueName {
static const backupDatabase = QueueName._(r'backupDatabase'); static const backupDatabase = QueueName._(r'backupDatabase');
static const ocr = QueueName._(r'ocr'); static const ocr = QueueName._(r'ocr');
static const workflow = QueueName._(r'workflow'); static const workflow = QueueName._(r'workflow');
static const integrityCheck = QueueName._(r'integrityCheck');
static const editor = QueueName._(r'editor'); static const editor = QueueName._(r'editor');
/// List of all possible values in this [enum][QueueName]. /// List of all possible values in this [enum][QueueName].
@@ -62,7 +61,6 @@ class QueueName {
backupDatabase, backupDatabase,
ocr, ocr,
workflow, workflow,
integrityCheck,
editor, editor,
]; ];
@@ -119,7 +117,6 @@ class QueueNameTypeTransformer {
case r'backupDatabase': return QueueName.backupDatabase; case r'backupDatabase': return QueueName.backupDatabase;
case r'ocr': return QueueName.ocr; case r'ocr': return QueueName.ocr;
case r'workflow': return QueueName.workflow; case r'workflow': return QueueName.workflow;
case r'integrityCheck': return QueueName.integrityCheck;
case r'editor': return QueueName.editor; case r'editor': return QueueName.editor;
default: default:
if (!allowNull) { if (!allowNull) {
+1 -9
View File
@@ -19,7 +19,6 @@ class QueuesResponseLegacyDto {
required this.editor, required this.editor,
required this.faceDetection, required this.faceDetection,
required this.facialRecognition, required this.facialRecognition,
required this.integrityCheck,
required this.library_, required this.library_,
required this.metadataExtraction, required this.metadataExtraction,
required this.migration, required this.migration,
@@ -46,8 +45,6 @@ class QueuesResponseLegacyDto {
QueueResponseLegacyDto facialRecognition; QueueResponseLegacyDto facialRecognition;
QueueResponseLegacyDto integrityCheck;
QueueResponseLegacyDto library_; QueueResponseLegacyDto library_;
QueueResponseLegacyDto metadataExtraction; QueueResponseLegacyDto metadataExtraction;
@@ -80,7 +77,6 @@ class QueuesResponseLegacyDto {
other.editor == editor && other.editor == editor &&
other.faceDetection == faceDetection && other.faceDetection == faceDetection &&
other.facialRecognition == facialRecognition && other.facialRecognition == facialRecognition &&
other.integrityCheck == integrityCheck &&
other.library_ == library_ && other.library_ == library_ &&
other.metadataExtraction == metadataExtraction && other.metadataExtraction == metadataExtraction &&
other.migration == migration && other.migration == migration &&
@@ -103,7 +99,6 @@ class QueuesResponseLegacyDto {
(editor.hashCode) + (editor.hashCode) +
(faceDetection.hashCode) + (faceDetection.hashCode) +
(facialRecognition.hashCode) + (facialRecognition.hashCode) +
(integrityCheck.hashCode) +
(library_.hashCode) + (library_.hashCode) +
(metadataExtraction.hashCode) + (metadataExtraction.hashCode) +
(migration.hashCode) + (migration.hashCode) +
@@ -118,7 +113,7 @@ class QueuesResponseLegacyDto {
(workflow.hashCode); (workflow.hashCode);
@override @override
String toString() => 'QueuesResponseLegacyDto[backgroundTask=$backgroundTask, backupDatabase=$backupDatabase, duplicateDetection=$duplicateDetection, editor=$editor, faceDetection=$faceDetection, facialRecognition=$facialRecognition, integrityCheck=$integrityCheck, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]'; String toString() => 'QueuesResponseLegacyDto[backgroundTask=$backgroundTask, backupDatabase=$backupDatabase, duplicateDetection=$duplicateDetection, editor=$editor, faceDetection=$faceDetection, facialRecognition=$facialRecognition, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -128,7 +123,6 @@ class QueuesResponseLegacyDto {
json[r'editor'] = this.editor; json[r'editor'] = this.editor;
json[r'faceDetection'] = this.faceDetection; json[r'faceDetection'] = this.faceDetection;
json[r'facialRecognition'] = this.facialRecognition; json[r'facialRecognition'] = this.facialRecognition;
json[r'integrityCheck'] = this.integrityCheck;
json[r'library'] = this.library_; json[r'library'] = this.library_;
json[r'metadataExtraction'] = this.metadataExtraction; json[r'metadataExtraction'] = this.metadataExtraction;
json[r'migration'] = this.migration; json[r'migration'] = this.migration;
@@ -159,7 +153,6 @@ class QueuesResponseLegacyDto {
editor: QueueResponseLegacyDto.fromJson(json[r'editor'])!, editor: QueueResponseLegacyDto.fromJson(json[r'editor'])!,
faceDetection: QueueResponseLegacyDto.fromJson(json[r'faceDetection'])!, faceDetection: QueueResponseLegacyDto.fromJson(json[r'faceDetection'])!,
facialRecognition: QueueResponseLegacyDto.fromJson(json[r'facialRecognition'])!, facialRecognition: QueueResponseLegacyDto.fromJson(json[r'facialRecognition'])!,
integrityCheck: QueueResponseLegacyDto.fromJson(json[r'integrityCheck'])!,
library_: QueueResponseLegacyDto.fromJson(json[r'library'])!, library_: QueueResponseLegacyDto.fromJson(json[r'library'])!,
metadataExtraction: QueueResponseLegacyDto.fromJson(json[r'metadataExtraction'])!, metadataExtraction: QueueResponseLegacyDto.fromJson(json[r'metadataExtraction'])!,
migration: QueueResponseLegacyDto.fromJson(json[r'migration'])!, migration: QueueResponseLegacyDto.fromJson(json[r'migration'])!,
@@ -225,7 +218,6 @@ class QueuesResponseLegacyDto {
'editor', 'editor',
'faceDetection', 'faceDetection',
'facialRecognition', 'facialRecognition',
'integrityCheck',
'library', 'library',
'metadataExtraction', 'metadataExtraction',
'migration', 'migration',
-201
View File
@@ -1,201 +0,0 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SyncAssetFaceV2 {
/// Returns a new [SyncAssetFaceV2] instance.
SyncAssetFaceV2({
required this.assetId,
required this.boundingBoxX1,
required this.boundingBoxX2,
required this.boundingBoxY1,
required this.boundingBoxY2,
required this.deletedAt,
required this.id,
required this.imageHeight,
required this.imageWidth,
required this.isVisible,
required this.personId,
required this.sourceType,
});
/// Asset ID
String assetId;
int boundingBoxX1;
int boundingBoxX2;
int boundingBoxY1;
int boundingBoxY2;
/// Face deleted at
DateTime? deletedAt;
/// Asset face ID
String id;
int imageHeight;
int imageWidth;
/// Is the face visible in the asset
bool isVisible;
/// Person ID
String? personId;
/// Source type
String sourceType;
@override
bool operator ==(Object other) => identical(this, other) || other is SyncAssetFaceV2 &&
other.assetId == assetId &&
other.boundingBoxX1 == boundingBoxX1 &&
other.boundingBoxX2 == boundingBoxX2 &&
other.boundingBoxY1 == boundingBoxY1 &&
other.boundingBoxY2 == boundingBoxY2 &&
other.deletedAt == deletedAt &&
other.id == id &&
other.imageHeight == imageHeight &&
other.imageWidth == imageWidth &&
other.isVisible == isVisible &&
other.personId == personId &&
other.sourceType == sourceType;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(assetId.hashCode) +
(boundingBoxX1.hashCode) +
(boundingBoxX2.hashCode) +
(boundingBoxY1.hashCode) +
(boundingBoxY2.hashCode) +
(deletedAt == null ? 0 : deletedAt!.hashCode) +
(id.hashCode) +
(imageHeight.hashCode) +
(imageWidth.hashCode) +
(isVisible.hashCode) +
(personId == null ? 0 : personId!.hashCode) +
(sourceType.hashCode);
@override
String toString() => 'SyncAssetFaceV2[assetId=$assetId, boundingBoxX1=$boundingBoxX1, boundingBoxX2=$boundingBoxX2, boundingBoxY1=$boundingBoxY1, boundingBoxY2=$boundingBoxY2, deletedAt=$deletedAt, id=$id, imageHeight=$imageHeight, imageWidth=$imageWidth, isVisible=$isVisible, personId=$personId, sourceType=$sourceType]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'assetId'] = this.assetId;
json[r'boundingBoxX1'] = this.boundingBoxX1;
json[r'boundingBoxX2'] = this.boundingBoxX2;
json[r'boundingBoxY1'] = this.boundingBoxY1;
json[r'boundingBoxY2'] = this.boundingBoxY2;
if (this.deletedAt != null) {
json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String();
} else {
// json[r'deletedAt'] = null;
}
json[r'id'] = this.id;
json[r'imageHeight'] = this.imageHeight;
json[r'imageWidth'] = this.imageWidth;
json[r'isVisible'] = this.isVisible;
if (this.personId != null) {
json[r'personId'] = this.personId;
} else {
// json[r'personId'] = null;
}
json[r'sourceType'] = this.sourceType;
return json;
}
/// Returns a new [SyncAssetFaceV2] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static SyncAssetFaceV2? fromJson(dynamic value) {
upgradeDto(value, "SyncAssetFaceV2");
if (value is Map) {
final json = value.cast<String, dynamic>();
return SyncAssetFaceV2(
assetId: mapValueOfType<String>(json, r'assetId')!,
boundingBoxX1: mapValueOfType<int>(json, r'boundingBoxX1')!,
boundingBoxX2: mapValueOfType<int>(json, r'boundingBoxX2')!,
boundingBoxY1: mapValueOfType<int>(json, r'boundingBoxY1')!,
boundingBoxY2: mapValueOfType<int>(json, r'boundingBoxY2')!,
deletedAt: mapDateTime(json, r'deletedAt', r''),
id: mapValueOfType<String>(json, r'id')!,
imageHeight: mapValueOfType<int>(json, r'imageHeight')!,
imageWidth: mapValueOfType<int>(json, r'imageWidth')!,
isVisible: mapValueOfType<bool>(json, r'isVisible')!,
personId: mapValueOfType<String>(json, r'personId'),
sourceType: mapValueOfType<String>(json, r'sourceType')!,
);
}
return null;
}
static List<SyncAssetFaceV2> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SyncAssetFaceV2>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SyncAssetFaceV2.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, SyncAssetFaceV2> mapFromJson(dynamic json) {
final map = <String, SyncAssetFaceV2>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = SyncAssetFaceV2.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of SyncAssetFaceV2-objects as value to a dart map
static Map<String, List<SyncAssetFaceV2>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SyncAssetFaceV2>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = SyncAssetFaceV2.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'assetId',
'boundingBoxX1',
'boundingBoxX2',
'boundingBoxY1',
'boundingBoxY2',
'deletedAt',
'id',
'imageHeight',
'imageWidth',
'isVisible',
'personId',
'sourceType',
};
}
-3
View File
@@ -64,7 +64,6 @@ class SyncEntityType {
static const personV1 = SyncEntityType._(r'PersonV1'); static const personV1 = SyncEntityType._(r'PersonV1');
static const personDeleteV1 = SyncEntityType._(r'PersonDeleteV1'); static const personDeleteV1 = SyncEntityType._(r'PersonDeleteV1');
static const assetFaceV1 = SyncEntityType._(r'AssetFaceV1'); static const assetFaceV1 = SyncEntityType._(r'AssetFaceV1');
static const assetFaceV2 = SyncEntityType._(r'AssetFaceV2');
static const assetFaceDeleteV1 = SyncEntityType._(r'AssetFaceDeleteV1'); static const assetFaceDeleteV1 = SyncEntityType._(r'AssetFaceDeleteV1');
static const userMetadataV1 = SyncEntityType._(r'UserMetadataV1'); static const userMetadataV1 = SyncEntityType._(r'UserMetadataV1');
static const userMetadataDeleteV1 = SyncEntityType._(r'UserMetadataDeleteV1'); static const userMetadataDeleteV1 = SyncEntityType._(r'UserMetadataDeleteV1');
@@ -115,7 +114,6 @@ class SyncEntityType {
personV1, personV1,
personDeleteV1, personDeleteV1,
assetFaceV1, assetFaceV1,
assetFaceV2,
assetFaceDeleteV1, assetFaceDeleteV1,
userMetadataV1, userMetadataV1,
userMetadataDeleteV1, userMetadataDeleteV1,
@@ -201,7 +199,6 @@ class SyncEntityTypeTypeTransformer {
case r'PersonV1': return SyncEntityType.personV1; case r'PersonV1': return SyncEntityType.personV1;
case r'PersonDeleteV1': return SyncEntityType.personDeleteV1; case r'PersonDeleteV1': return SyncEntityType.personDeleteV1;
case r'AssetFaceV1': return SyncEntityType.assetFaceV1; case r'AssetFaceV1': return SyncEntityType.assetFaceV1;
case r'AssetFaceV2': return SyncEntityType.assetFaceV2;
case r'AssetFaceDeleteV1': return SyncEntityType.assetFaceDeleteV1; case r'AssetFaceDeleteV1': return SyncEntityType.assetFaceDeleteV1;
case r'UserMetadataV1': return SyncEntityType.userMetadataV1; case r'UserMetadataV1': return SyncEntityType.userMetadataV1;
case r'UserMetadataDeleteV1': return SyncEntityType.userMetadataDeleteV1; case r'UserMetadataDeleteV1': return SyncEntityType.userMetadataDeleteV1;
-3
View File
@@ -42,7 +42,6 @@ class SyncRequestType {
static const usersV1 = SyncRequestType._(r'UsersV1'); static const usersV1 = SyncRequestType._(r'UsersV1');
static const peopleV1 = SyncRequestType._(r'PeopleV1'); static const peopleV1 = SyncRequestType._(r'PeopleV1');
static const assetFacesV1 = SyncRequestType._(r'AssetFacesV1'); static const assetFacesV1 = SyncRequestType._(r'AssetFacesV1');
static const assetFacesV2 = SyncRequestType._(r'AssetFacesV2');
static const userMetadataV1 = SyncRequestType._(r'UserMetadataV1'); static const userMetadataV1 = SyncRequestType._(r'UserMetadataV1');
/// List of all possible values in this [enum][SyncRequestType]. /// List of all possible values in this [enum][SyncRequestType].
@@ -66,7 +65,6 @@ class SyncRequestType {
usersV1, usersV1,
peopleV1, peopleV1,
assetFacesV1, assetFacesV1,
assetFacesV2,
userMetadataV1, userMetadataV1,
]; ];
@@ -125,7 +123,6 @@ class SyncRequestTypeTypeTransformer {
case r'UsersV1': return SyncRequestType.usersV1; case r'UsersV1': return SyncRequestType.usersV1;
case r'PeopleV1': return SyncRequestType.peopleV1; case r'PeopleV1': return SyncRequestType.peopleV1;
case r'AssetFacesV1': return SyncRequestType.assetFacesV1; case r'AssetFacesV1': return SyncRequestType.assetFacesV1;
case r'AssetFacesV2': return SyncRequestType.assetFacesV2;
case r'UserMetadataV1': return SyncRequestType.userMetadataV1; case r'UserMetadataV1': return SyncRequestType.userMetadataV1;
default: default:
if (!allowNull) { if (!allowNull) {
+1 -9
View File
@@ -16,7 +16,6 @@ class SystemConfigDto {
required this.backup, required this.backup,
required this.ffmpeg, required this.ffmpeg,
required this.image, required this.image,
required this.integrityChecks,
required this.job, required this.job,
required this.library_, required this.library_,
required this.logging, required this.logging,
@@ -43,8 +42,6 @@ class SystemConfigDto {
SystemConfigImageDto image; SystemConfigImageDto image;
SystemConfigIntegrityChecks integrityChecks;
SystemConfigJobDto job; SystemConfigJobDto job;
SystemConfigLibraryDto library_; SystemConfigLibraryDto library_;
@@ -86,7 +83,6 @@ class SystemConfigDto {
other.backup == backup && other.backup == backup &&
other.ffmpeg == ffmpeg && other.ffmpeg == ffmpeg &&
other.image == image && other.image == image &&
other.integrityChecks == integrityChecks &&
other.job == job && other.job == job &&
other.library_ == library_ && other.library_ == library_ &&
other.logging == logging && other.logging == logging &&
@@ -112,7 +108,6 @@ class SystemConfigDto {
(backup.hashCode) + (backup.hashCode) +
(ffmpeg.hashCode) + (ffmpeg.hashCode) +
(image.hashCode) + (image.hashCode) +
(integrityChecks.hashCode) +
(job.hashCode) + (job.hashCode) +
(library_.hashCode) + (library_.hashCode) +
(logging.hashCode) + (logging.hashCode) +
@@ -133,14 +128,13 @@ class SystemConfigDto {
(user.hashCode); (user.hashCode);
@override @override
String toString() => 'SystemConfigDto[backup=$backup, ffmpeg=$ffmpeg, image=$image, integrityChecks=$integrityChecks, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, metadata=$metadata, newVersionCheck=$newVersionCheck, nightlyTasks=$nightlyTasks, notifications=$notifications, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, server=$server, storageTemplate=$storageTemplate, templates=$templates, theme=$theme, trash=$trash, user=$user]'; String toString() => 'SystemConfigDto[backup=$backup, ffmpeg=$ffmpeg, image=$image, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, metadata=$metadata, newVersionCheck=$newVersionCheck, nightlyTasks=$nightlyTasks, notifications=$notifications, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, server=$server, storageTemplate=$storageTemplate, templates=$templates, theme=$theme, trash=$trash, user=$user]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
json[r'backup'] = this.backup; json[r'backup'] = this.backup;
json[r'ffmpeg'] = this.ffmpeg; json[r'ffmpeg'] = this.ffmpeg;
json[r'image'] = this.image; json[r'image'] = this.image;
json[r'integrityChecks'] = this.integrityChecks;
json[r'job'] = this.job; json[r'job'] = this.job;
json[r'library'] = this.library_; json[r'library'] = this.library_;
json[r'logging'] = this.logging; json[r'logging'] = this.logging;
@@ -174,7 +168,6 @@ class SystemConfigDto {
backup: SystemConfigBackupsDto.fromJson(json[r'backup'])!, backup: SystemConfigBackupsDto.fromJson(json[r'backup'])!,
ffmpeg: SystemConfigFFmpegDto.fromJson(json[r'ffmpeg'])!, ffmpeg: SystemConfigFFmpegDto.fromJson(json[r'ffmpeg'])!,
image: SystemConfigImageDto.fromJson(json[r'image'])!, image: SystemConfigImageDto.fromJson(json[r'image'])!,
integrityChecks: SystemConfigIntegrityChecks.fromJson(json[r'integrityChecks'])!,
job: SystemConfigJobDto.fromJson(json[r'job'])!, job: SystemConfigJobDto.fromJson(json[r'job'])!,
library_: SystemConfigLibraryDto.fromJson(json[r'library'])!, library_: SystemConfigLibraryDto.fromJson(json[r'library'])!,
logging: SystemConfigLoggingDto.fromJson(json[r'logging'])!, logging: SystemConfigLoggingDto.fromJson(json[r'logging'])!,
@@ -243,7 +236,6 @@ class SystemConfigDto {
'backup', 'backup',
'ffmpeg', 'ffmpeg',
'image', 'image',
'integrityChecks',
'job', 'job',
'library', 'library',
'logging', 'logging',
@@ -1,115 +0,0 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SystemConfigIntegrityChecks {
/// Returns a new [SystemConfigIntegrityChecks] instance.
SystemConfigIntegrityChecks({
required this.checksumFiles,
required this.missingFiles,
required this.untrackedFiles,
});
SystemConfigIntegrityChecksumJob checksumFiles;
SystemConfigIntegrityJob missingFiles;
SystemConfigIntegrityJob untrackedFiles;
@override
bool operator ==(Object other) => identical(this, other) || other is SystemConfigIntegrityChecks &&
other.checksumFiles == checksumFiles &&
other.missingFiles == missingFiles &&
other.untrackedFiles == untrackedFiles;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(checksumFiles.hashCode) +
(missingFiles.hashCode) +
(untrackedFiles.hashCode);
@override
String toString() => 'SystemConfigIntegrityChecks[checksumFiles=$checksumFiles, missingFiles=$missingFiles, untrackedFiles=$untrackedFiles]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'checksumFiles'] = this.checksumFiles;
json[r'missingFiles'] = this.missingFiles;
json[r'untrackedFiles'] = this.untrackedFiles;
return json;
}
/// Returns a new [SystemConfigIntegrityChecks] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static SystemConfigIntegrityChecks? fromJson(dynamic value) {
upgradeDto(value, "SystemConfigIntegrityChecks");
if (value is Map) {
final json = value.cast<String, dynamic>();
return SystemConfigIntegrityChecks(
checksumFiles: SystemConfigIntegrityChecksumJob.fromJson(json[r'checksumFiles'])!,
missingFiles: SystemConfigIntegrityJob.fromJson(json[r'missingFiles'])!,
untrackedFiles: SystemConfigIntegrityJob.fromJson(json[r'untrackedFiles'])!,
);
}
return null;
}
static List<SystemConfigIntegrityChecks> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SystemConfigIntegrityChecks>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SystemConfigIntegrityChecks.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, SystemConfigIntegrityChecks> mapFromJson(dynamic json) {
final map = <String, SystemConfigIntegrityChecks>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = SystemConfigIntegrityChecks.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of SystemConfigIntegrityChecks-objects as value to a dart map
static Map<String, List<SystemConfigIntegrityChecks>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SystemConfigIntegrityChecks>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = SystemConfigIntegrityChecks.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'checksumFiles',
'missingFiles',
'untrackedFiles',
};
}
@@ -1,123 +0,0 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SystemConfigIntegrityChecksumJob {
/// Returns a new [SystemConfigIntegrityChecksumJob] instance.
SystemConfigIntegrityChecksumJob({
required this.cronExpression,
required this.enabled,
required this.percentageLimit,
required this.timeLimit,
});
String cronExpression;
bool enabled;
num percentageLimit;
num timeLimit;
@override
bool operator ==(Object other) => identical(this, other) || other is SystemConfigIntegrityChecksumJob &&
other.cronExpression == cronExpression &&
other.enabled == enabled &&
other.percentageLimit == percentageLimit &&
other.timeLimit == timeLimit;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(cronExpression.hashCode) +
(enabled.hashCode) +
(percentageLimit.hashCode) +
(timeLimit.hashCode);
@override
String toString() => 'SystemConfigIntegrityChecksumJob[cronExpression=$cronExpression, enabled=$enabled, percentageLimit=$percentageLimit, timeLimit=$timeLimit]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'cronExpression'] = this.cronExpression;
json[r'enabled'] = this.enabled;
json[r'percentageLimit'] = this.percentageLimit;
json[r'timeLimit'] = this.timeLimit;
return json;
}
/// Returns a new [SystemConfigIntegrityChecksumJob] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static SystemConfigIntegrityChecksumJob? fromJson(dynamic value) {
upgradeDto(value, "SystemConfigIntegrityChecksumJob");
if (value is Map) {
final json = value.cast<String, dynamic>();
return SystemConfigIntegrityChecksumJob(
cronExpression: mapValueOfType<String>(json, r'cronExpression')!,
enabled: mapValueOfType<bool>(json, r'enabled')!,
percentageLimit: num.parse('${json[r'percentageLimit']}'),
timeLimit: num.parse('${json[r'timeLimit']}'),
);
}
return null;
}
static List<SystemConfigIntegrityChecksumJob> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SystemConfigIntegrityChecksumJob>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SystemConfigIntegrityChecksumJob.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, SystemConfigIntegrityChecksumJob> mapFromJson(dynamic json) {
final map = <String, SystemConfigIntegrityChecksumJob>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = SystemConfigIntegrityChecksumJob.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of SystemConfigIntegrityChecksumJob-objects as value to a dart map
static Map<String, List<SystemConfigIntegrityChecksumJob>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SystemConfigIntegrityChecksumJob>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = SystemConfigIntegrityChecksumJob.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'cronExpression',
'enabled',
'percentageLimit',
'timeLimit',
};
}
-107
View File
@@ -1,107 +0,0 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SystemConfigIntegrityJob {
/// Returns a new [SystemConfigIntegrityJob] instance.
SystemConfigIntegrityJob({
required this.cronExpression,
required this.enabled,
});
String cronExpression;
bool enabled;
@override
bool operator ==(Object other) => identical(this, other) || other is SystemConfigIntegrityJob &&
other.cronExpression == cronExpression &&
other.enabled == enabled;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(cronExpression.hashCode) +
(enabled.hashCode);
@override
String toString() => 'SystemConfigIntegrityJob[cronExpression=$cronExpression, enabled=$enabled]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'cronExpression'] = this.cronExpression;
json[r'enabled'] = this.enabled;
return json;
}
/// Returns a new [SystemConfigIntegrityJob] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static SystemConfigIntegrityJob? fromJson(dynamic value) {
upgradeDto(value, "SystemConfigIntegrityJob");
if (value is Map) {
final json = value.cast<String, dynamic>();
return SystemConfigIntegrityJob(
cronExpression: mapValueOfType<String>(json, r'cronExpression')!,
enabled: mapValueOfType<bool>(json, r'enabled')!,
);
}
return null;
}
static List<SystemConfigIntegrityJob> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SystemConfigIntegrityJob>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SystemConfigIntegrityJob.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, SystemConfigIntegrityJob> mapFromJson(dynamic json) {
final map = <String, SystemConfigIntegrityJob>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = SystemConfigIntegrityJob.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of SystemConfigIntegrityJob-objects as value to a dart map
static Map<String, List<SystemConfigIntegrityJob>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SystemConfigIntegrityJob>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = SystemConfigIntegrityJob.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'cronExpression',
'enabled',
};
}
+1 -9
View File
@@ -16,7 +16,6 @@ class SystemConfigJobDto {
required this.backgroundTask, required this.backgroundTask,
required this.editor, required this.editor,
required this.faceDetection, required this.faceDetection,
required this.integrityCheck,
required this.library_, required this.library_,
required this.metadataExtraction, required this.metadataExtraction,
required this.migration, required this.migration,
@@ -36,8 +35,6 @@ class SystemConfigJobDto {
JobSettingsDto faceDetection; JobSettingsDto faceDetection;
JobSettingsDto integrityCheck;
JobSettingsDto library_; JobSettingsDto library_;
JobSettingsDto metadataExtraction; JobSettingsDto metadataExtraction;
@@ -65,7 +62,6 @@ class SystemConfigJobDto {
other.backgroundTask == backgroundTask && other.backgroundTask == backgroundTask &&
other.editor == editor && other.editor == editor &&
other.faceDetection == faceDetection && other.faceDetection == faceDetection &&
other.integrityCheck == integrityCheck &&
other.library_ == library_ && other.library_ == library_ &&
other.metadataExtraction == metadataExtraction && other.metadataExtraction == metadataExtraction &&
other.migration == migration && other.migration == migration &&
@@ -84,7 +80,6 @@ class SystemConfigJobDto {
(backgroundTask.hashCode) + (backgroundTask.hashCode) +
(editor.hashCode) + (editor.hashCode) +
(faceDetection.hashCode) + (faceDetection.hashCode) +
(integrityCheck.hashCode) +
(library_.hashCode) + (library_.hashCode) +
(metadataExtraction.hashCode) + (metadataExtraction.hashCode) +
(migration.hashCode) + (migration.hashCode) +
@@ -98,14 +93,13 @@ class SystemConfigJobDto {
(workflow.hashCode); (workflow.hashCode);
@override @override
String toString() => 'SystemConfigJobDto[backgroundTask=$backgroundTask, editor=$editor, faceDetection=$faceDetection, integrityCheck=$integrityCheck, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]'; String toString() => 'SystemConfigJobDto[backgroundTask=$backgroundTask, editor=$editor, faceDetection=$faceDetection, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
json[r'backgroundTask'] = this.backgroundTask; json[r'backgroundTask'] = this.backgroundTask;
json[r'editor'] = this.editor; json[r'editor'] = this.editor;
json[r'faceDetection'] = this.faceDetection; json[r'faceDetection'] = this.faceDetection;
json[r'integrityCheck'] = this.integrityCheck;
json[r'library'] = this.library_; json[r'library'] = this.library_;
json[r'metadataExtraction'] = this.metadataExtraction; json[r'metadataExtraction'] = this.metadataExtraction;
json[r'migration'] = this.migration; json[r'migration'] = this.migration;
@@ -132,7 +126,6 @@ class SystemConfigJobDto {
backgroundTask: JobSettingsDto.fromJson(json[r'backgroundTask'])!, backgroundTask: JobSettingsDto.fromJson(json[r'backgroundTask'])!,
editor: JobSettingsDto.fromJson(json[r'editor'])!, editor: JobSettingsDto.fromJson(json[r'editor'])!,
faceDetection: JobSettingsDto.fromJson(json[r'faceDetection'])!, faceDetection: JobSettingsDto.fromJson(json[r'faceDetection'])!,
integrityCheck: JobSettingsDto.fromJson(json[r'integrityCheck'])!,
library_: JobSettingsDto.fromJson(json[r'library'])!, library_: JobSettingsDto.fromJson(json[r'library'])!,
metadataExtraction: JobSettingsDto.fromJson(json[r'metadataExtraction'])!, metadataExtraction: JobSettingsDto.fromJson(json[r'metadataExtraction'])!,
migration: JobSettingsDto.fromJson(json[r'migration'])!, migration: JobSettingsDto.fromJson(json[r'migration'])!,
@@ -194,7 +187,6 @@ class SystemConfigJobDto {
'backgroundTask', 'backgroundTask',
'editor', 'editor',
'faceDetection', 'faceDetection',
'integrityCheck',
'library', 'library',
'metadataExtraction', 'metadataExtraction',
'migration', 'migration',
+1 -1
View File
@@ -1,6 +1,6 @@
export 'src/components/close_button.dart'; export 'src/components/close_button.dart';
export 'src/components/form.dart'; export 'src/components/form.dart';
export 'src/components/formatted_text.dart'; export 'src/components/html_text.dart';
export 'src/components/icon_button.dart'; export 'src/components/icon_button.dart';
export 'src/components/password_input.dart'; export 'src/components/password_input.dart';
export 'src/components/text_button.dart'; export 'src/components/text_button.dart';

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