mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 532567ab2a |
@@ -24,7 +24,7 @@ Immich has three main clients:
|
|||||||
3. CLI - Command-line utility for bulk upload
|
3. CLI - Command-line utility for bulk upload
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
All three clients use [OpenAPI](/api.md) to auto-generate rest clients for easy integration. For more information about this process, see [OpenAPI](/api.md).
|
All three clients use [OpenAPI](./open-api.md) to auto-generate rest clients for easy integration. For more information about this process, see [OpenAPI](./open-api.md).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
### Mobile App
|
### Mobile App
|
||||||
@@ -71,7 +71,7 @@ An incoming HTTP request is mapped to a controller (`src/controllers`). Controll
|
|||||||
|
|
||||||
### Domain Transfer Objects (DTOs)
|
### Domain Transfer Objects (DTOs)
|
||||||
|
|
||||||
The server uses [Domain Transfer Objects](https://en.wikipedia.org/wiki/Data_transfer_object) as public interfaces for the inputs (query, params, and body) and outputs (response) for each endpoint. DTOs translate to [OpenAPI](/api.md) schemas and control the generated code used by each client.
|
The server uses [Domain Transfer Objects](https://en.wikipedia.org/wiki/Data_transfer_object) as public interfaces for the inputs (query, params, and body) and outputs (response) for each endpoint. DTOs translate to [OpenAPI](./open-api.md) schemas and control the generated code used by each client.
|
||||||
|
|
||||||
### Background Jobs
|
### Background Jobs
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# API
|
# OpenAPI
|
||||||
|
|
||||||
Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generate API documentation. To view the published docs see [here](https://api.immich.app/).
|
Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generate API documentation. To view the published docs see [here](https://api.immich.app/).
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ You can use `dart fix --apply` and `dcm fix lib` to potentially correct some iss
|
|||||||
|
|
||||||
## OpenAPI
|
## OpenAPI
|
||||||
|
|
||||||
The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/api.md) for more details.
|
The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/developer/open-api.md) for more details.
|
||||||
|
|
||||||
## Database Migrations
|
## Database Migrations
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ You do not need to redo any machine learning jobs after enabling hardware accele
|
|||||||
- The GPU must be supported by ROCm. If it isn't officially supported, you can attempt to use the `HSA_OVERRIDE_GFX_VERSION` environmental variable: `HSA_OVERRIDE_GFX_VERSION=<a supported version, e.g. 10.3.0>`. If this doesn't work, you might need to also set `HSA_USE_SVM=0`.
|
- The GPU must be supported by ROCm. If it isn't officially supported, you can attempt to use the `HSA_OVERRIDE_GFX_VERSION` environmental variable: `HSA_OVERRIDE_GFX_VERSION=<a supported version, e.g. 10.3.0>`. If this doesn't work, you might need to also set `HSA_USE_SVM=0`.
|
||||||
- The ROCm image is quite large and requires at least 35GiB of free disk space. However, pulling later updates to the service through Docker will generally only amount to a few hundred megabytes as the rest will be cached.
|
- The ROCm image is quite large and requires at least 35GiB of free disk space. However, pulling later updates to the service through Docker will generally only amount to a few hundred megabytes as the rest will be cached.
|
||||||
- This backend is new and may experience some issues. For example, GPU power consumption can be higher than usual after running inference, even if the machine learning service is idle. In this case, it will only go back to normal after being idle for 5 minutes (configurable with the [MACHINE_LEARNING_MODEL_TTL](/install/environment-variables) setting).
|
- This backend is new and may experience some issues. For example, GPU power consumption can be higher than usual after running inference, even if the machine learning service is idle. In this case, it will only go back to normal after being idle for 5 minutes (configurable with the [MACHINE_LEARNING_MODEL_TTL](/install/environment-variables) setting).
|
||||||
- MIGraphX is a new backend for AMD cards, which compiles models at runtime. As such, the first few inferences will be slow.
|
|
||||||
|
|
||||||
#### OpenVINO
|
#### OpenVINO
|
||||||
|
|
||||||
|
|||||||
+48
-70
@@ -6,7 +6,7 @@ const prism = require('prism-react-renderer');
|
|||||||
/** @type {import('@docusaurus/types').Config} */
|
/** @type {import('@docusaurus/types').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
title: 'Immich',
|
title: 'Immich',
|
||||||
tagline: 'Self-hosted photo and video management solution',
|
tagline: 'High performance self-hosted photo and video backup solution directly from your mobile phone',
|
||||||
url: 'https://docs.immich.app',
|
url: 'https://docs.immich.app',
|
||||||
baseUrl: '/',
|
baseUrl: '/',
|
||||||
onBrokenLinks: 'throw',
|
onBrokenLinks: 'throw',
|
||||||
@@ -93,15 +93,35 @@ const config = {
|
|||||||
position: 'right',
|
position: 'right',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: 'https://immich.app/',
|
to: '/overview/quick-start',
|
||||||
position: 'right',
|
position: 'right',
|
||||||
label: 'Home',
|
label: 'Docs',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: 'https://immich.app/roadmap',
|
||||||
|
position: 'right',
|
||||||
|
label: 'Roadmap',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: 'https://api.immich.app/',
|
||||||
|
position: 'right',
|
||||||
|
label: 'API',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: 'https://immich.store',
|
||||||
|
position: 'right',
|
||||||
|
label: 'Merch',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: 'https://github.com/immich-app/immich',
|
href: 'https://github.com/immich-app/immich',
|
||||||
label: 'GitHub',
|
label: 'GitHub',
|
||||||
position: 'right',
|
position: 'right',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
href: 'https://discord.immich.app',
|
||||||
|
label: 'Discord',
|
||||||
|
position: 'right',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'html',
|
type: 'html',
|
||||||
position: 'right',
|
position: 'right',
|
||||||
@@ -114,78 +134,19 @@ const config = {
|
|||||||
style: 'light',
|
style: 'light',
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
title: 'Download',
|
title: 'Overview',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: 'Android',
|
label: 'Quick start',
|
||||||
href: 'https://get.immich.app/android',
|
to: '/overview/quick-start',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'iOS',
|
label: 'Installation',
|
||||||
href: 'https://get.immich.app/ios',
|
to: '/install/requirements',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Server',
|
label: 'Contributing',
|
||||||
href: 'https://immich.app/download',
|
to: '/overview/support-the-project',
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Company',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: 'FUTO',
|
|
||||||
href: 'https://futo.tech/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Purchase',
|
|
||||||
href: 'https://buy.immich.app/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Merch',
|
|
||||||
href: 'https://immich.store/',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Sites',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: 'Home',
|
|
||||||
href: 'https://immich.app',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'My Immich',
|
|
||||||
href: 'https://my.immich.app/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Awesome Immich',
|
|
||||||
href: 'https://awesome.immich.app/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Immich API',
|
|
||||||
href: 'https://api.immich.app/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Immich Data',
|
|
||||||
href: 'https://data.immich.app/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Immich Datasets',
|
|
||||||
href: 'https://datasets.immich.app/',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Miscellaneous',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: 'Roadmap',
|
|
||||||
href: 'https://immich.app/roadmap',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Cursed Knowledge',
|
|
||||||
href: 'https://immich.app/cursed-knowledge',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Privacy Policy',
|
label: 'Privacy Policy',
|
||||||
@@ -194,7 +155,24 @@ const config = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Social',
|
title: 'Documentation',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Roadmap',
|
||||||
|
href: 'https://immich.app/roadmap',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'API',
|
||||||
|
href: 'https://api.immich.app/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Cursed Knowledge',
|
||||||
|
href: 'https://immich.app/cursed-knowledge',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Links',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: 'GitHub',
|
label: 'GitHub',
|
||||||
|
|||||||
Vendored
-1
@@ -23,7 +23,6 @@
|
|||||||
/features/storage-template /administration/storage-template 307
|
/features/storage-template /administration/storage-template 307
|
||||||
/features/user-management /administration/user-management 307
|
/features/user-management /administration/user-management 307
|
||||||
/developer/contributing /developer/pr-checklist 307
|
/developer/contributing /developer/pr-checklist 307
|
||||||
/developer/open-api /api 307
|
|
||||||
/guides/machine-learning /guides/remote-machine-learning 307
|
/guides/machine-learning /guides/remote-machine-learning 307
|
||||||
/administration/password-login /administration/system-settings 307
|
/administration/password-login /administration/system-settings 307
|
||||||
/features/search /features/searching 307
|
/features/search /features/searching 307
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { AssetMediaResponseDto, LoginResponseDto } from '@immich/sdk';
|
||||||
|
import { expect, Page, test } from '@playwright/test';
|
||||||
|
import { utils } from 'src/utils';
|
||||||
|
|
||||||
|
async function ensureDetailPanelVisible(page: Page) {
|
||||||
|
await page.waitForSelector('#immich-asset-viewer');
|
||||||
|
|
||||||
|
const isVisible = await page.locator('#detail-panel').isVisible();
|
||||||
|
if (!isVisible) {
|
||||||
|
await page.keyboard.press('i');
|
||||||
|
await page.waitForSelector('#detail-panel');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test.describe('Asset Viewer stack', () => {
|
||||||
|
let admin: LoginResponseDto;
|
||||||
|
let assetOne: AssetMediaResponseDto;
|
||||||
|
let assetTwo: AssetMediaResponseDto;
|
||||||
|
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
utils.initSdk();
|
||||||
|
await utils.resetDatabase();
|
||||||
|
admin = await utils.adminSetup();
|
||||||
|
await utils.updateMyPreferences(admin.accessToken, { tags: { enabled: true } });
|
||||||
|
|
||||||
|
assetOne = await utils.createAsset(admin.accessToken);
|
||||||
|
assetTwo = await utils.createAsset(admin.accessToken);
|
||||||
|
await utils.createStack(admin.accessToken, [assetOne.id, assetTwo.id]);
|
||||||
|
|
||||||
|
const tags = await utils.upsertTags(admin.accessToken, ['test/1', 'test/2']);
|
||||||
|
const tagOne = tags.find((tag) => tag.value === 'test/1')!;
|
||||||
|
const tagTwo = tags.find((tag) => tag.value === 'test/2')!;
|
||||||
|
await utils.tagAssets(admin.accessToken, tagOne.id, [assetOne.id]);
|
||||||
|
await utils.tagAssets(admin.accessToken, tagTwo.id, [assetTwo.id]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stack slideshow is visible', async ({ page, context }) => {
|
||||||
|
await utils.setAuthCookies(context, admin.accessToken);
|
||||||
|
await page.goto(`/photos/${assetOne.id}`);
|
||||||
|
|
||||||
|
const stackAssets = page.locator('#stack-slideshow [data-asset]');
|
||||||
|
await expect(stackAssets.first()).toBeVisible();
|
||||||
|
await expect(stackAssets.nth(1)).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('tags of primary asset are visible', async ({ page, context }) => {
|
||||||
|
await utils.setAuthCookies(context, admin.accessToken);
|
||||||
|
await page.goto(`/photos/${assetOne.id}`);
|
||||||
|
await ensureDetailPanelVisible(page);
|
||||||
|
|
||||||
|
const tags = page.getByTestId('detail-panel-tags').getByRole('link');
|
||||||
|
await expect(tags.first()).toHaveText('test/1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('tags of second asset are visible', async ({ page, context }) => {
|
||||||
|
await utils.setAuthCookies(context, admin.accessToken);
|
||||||
|
await page.goto(`/photos/${assetOne.id}`);
|
||||||
|
await ensureDetailPanelVisible(page);
|
||||||
|
|
||||||
|
const stackAssets = page.locator('#stack-slideshow [data-asset]');
|
||||||
|
await stackAssets.nth(1).click();
|
||||||
|
|
||||||
|
const tags = page.getByTestId('detail-panel-tags').getByRole('link');
|
||||||
|
await expect(tags.first()).toHaveText('test/2');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
import { faker } from '@faker-js/faker';
|
|
||||||
import { AssetTypeEnum, AssetVisibility, type AssetResponseDto, type StackResponseDto } from '@immich/sdk';
|
|
||||||
import { BrowserContext } from '@playwright/test';
|
|
||||||
import { randomPreview, randomThumbnail } from 'src/ui/generators/timeline';
|
|
||||||
|
|
||||||
export type MockStack = {
|
|
||||||
id: string;
|
|
||||||
primaryAssetId: string;
|
|
||||||
assets: AssetResponseDto[];
|
|
||||||
brokenAssetIds: Set<string>;
|
|
||||||
assetMap: Map<string, AssetResponseDto>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createMockStackAsset = (ownerId: string): AssetResponseDto => {
|
|
||||||
const assetId = faker.string.uuid();
|
|
||||||
const now = new Date().toISOString();
|
|
||||||
return {
|
|
||||||
id: assetId,
|
|
||||||
deviceAssetId: `device-${assetId}`,
|
|
||||||
ownerId,
|
|
||||||
owner: {
|
|
||||||
id: ownerId,
|
|
||||||
email: 'admin@immich.cloud',
|
|
||||||
name: 'Admin',
|
|
||||||
profileImagePath: '',
|
|
||||||
profileChangedAt: now,
|
|
||||||
avatarColor: 'blue' as never,
|
|
||||||
},
|
|
||||||
libraryId: `library-${ownerId}`,
|
|
||||||
deviceId: `device-${ownerId}`,
|
|
||||||
type: AssetTypeEnum.Image,
|
|
||||||
originalPath: `/original/${assetId}.jpg`,
|
|
||||||
originalFileName: `${assetId}.jpg`,
|
|
||||||
originalMimeType: 'image/jpeg',
|
|
||||||
thumbhash: null,
|
|
||||||
fileCreatedAt: now,
|
|
||||||
fileModifiedAt: now,
|
|
||||||
localDateTime: now,
|
|
||||||
updatedAt: now,
|
|
||||||
createdAt: now,
|
|
||||||
isFavorite: false,
|
|
||||||
isArchived: false,
|
|
||||||
isTrashed: false,
|
|
||||||
visibility: AssetVisibility.Timeline,
|
|
||||||
duration: '0:00:00.00000',
|
|
||||||
exifInfo: {
|
|
||||||
make: null,
|
|
||||||
model: null,
|
|
||||||
exifImageWidth: 3000,
|
|
||||||
exifImageHeight: 4000,
|
|
||||||
fileSizeInByte: null,
|
|
||||||
orientation: null,
|
|
||||||
dateTimeOriginal: now,
|
|
||||||
modifyDate: null,
|
|
||||||
timeZone: null,
|
|
||||||
lensModel: null,
|
|
||||||
fNumber: null,
|
|
||||||
focalLength: null,
|
|
||||||
iso: null,
|
|
||||||
exposureTime: null,
|
|
||||||
latitude: null,
|
|
||||||
longitude: null,
|
|
||||||
city: null,
|
|
||||||
country: null,
|
|
||||||
state: null,
|
|
||||||
description: null,
|
|
||||||
},
|
|
||||||
livePhotoVideoId: null,
|
|
||||||
tags: [],
|
|
||||||
people: [],
|
|
||||||
unassignedFaces: [],
|
|
||||||
stack: null,
|
|
||||||
isOffline: false,
|
|
||||||
hasMetadata: true,
|
|
||||||
duplicateId: null,
|
|
||||||
resized: true,
|
|
||||||
checksum: faker.string.alphanumeric({ length: 28 }),
|
|
||||||
width: 3000,
|
|
||||||
height: 4000,
|
|
||||||
isEdited: false,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createMockStack = (
|
|
||||||
primaryAssetDto: AssetResponseDto,
|
|
||||||
additionalAssets: AssetResponseDto[],
|
|
||||||
brokenAssetIds?: Set<string>,
|
|
||||||
): MockStack => {
|
|
||||||
const stackId = faker.string.uuid();
|
|
||||||
const allAssets = [primaryAssetDto, ...additionalAssets];
|
|
||||||
const resolvedBrokenIds = brokenAssetIds ?? new Set(additionalAssets.map((a) => a.id));
|
|
||||||
const assetMap = new Map(allAssets.map((a) => [a.id, a]));
|
|
||||||
|
|
||||||
primaryAssetDto.stack = {
|
|
||||||
id: stackId,
|
|
||||||
assetCount: allAssets.length,
|
|
||||||
primaryAssetId: primaryAssetDto.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: stackId,
|
|
||||||
primaryAssetId: primaryAssetDto.id,
|
|
||||||
assets: allAssets,
|
|
||||||
brokenAssetIds: resolvedBrokenIds,
|
|
||||||
assetMap,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setupBrokenAssetMockApiRoutes = async (context: BrowserContext, mockStack: MockStack) => {
|
|
||||||
await context.route('**/api/stacks/*', async (route, request) => {
|
|
||||||
if (request.method() !== 'GET') {
|
|
||||||
return route.fallback();
|
|
||||||
}
|
|
||||||
const stackResponse: StackResponseDto = {
|
|
||||||
id: mockStack.id,
|
|
||||||
primaryAssetId: mockStack.primaryAssetId,
|
|
||||||
assets: mockStack.assets,
|
|
||||||
};
|
|
||||||
return route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
contentType: 'application/json',
|
|
||||||
json: stackResponse,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await context.route('**/api/assets/*', async (route, request) => {
|
|
||||||
if (request.method() !== 'GET') {
|
|
||||||
return route.fallback();
|
|
||||||
}
|
|
||||||
const url = new URL(request.url());
|
|
||||||
const segments = url.pathname.split('/');
|
|
||||||
const assetId = segments.at(-1);
|
|
||||||
if (assetId && mockStack.assetMap.has(assetId)) {
|
|
||||||
return route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
contentType: 'application/json',
|
|
||||||
json: mockStack.assetMap.get(assetId),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return route.fallback();
|
|
||||||
});
|
|
||||||
|
|
||||||
await context.route('**/api/assets/*/thumbnail?size=*', async (route, request) => {
|
|
||||||
if (!route.request().serviceWorker()) {
|
|
||||||
return route.continue();
|
|
||||||
}
|
|
||||||
const pattern = /\/api\/assets\/(?<assetId>[^/]+)\/thumbnail\?size=(?<size>preview|thumbnail)/;
|
|
||||||
const match = request.url().match(pattern);
|
|
||||||
if (!match?.groups || !mockStack.assetMap.has(match.groups.assetId)) {
|
|
||||||
return route.fallback();
|
|
||||||
}
|
|
||||||
if (mockStack.brokenAssetIds.has(match.groups.assetId)) {
|
|
||||||
return route.fulfill({ status: 404 });
|
|
||||||
}
|
|
||||||
const asset = mockStack.assetMap.get(match.groups.assetId)!;
|
|
||||||
const ratio = (asset.exifInfo?.exifImageWidth ?? 3000) / (asset.exifInfo?.exifImageHeight ?? 4000);
|
|
||||||
const body =
|
|
||||||
match.groups.size === 'preview'
|
|
||||||
? await randomPreview(match.groups.assetId, ratio)
|
|
||||||
: await randomThumbnail(match.groups.assetId, ratio);
|
|
||||||
return route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
headers: { 'content-type': 'image/jpeg' },
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
import { BrowserContext } from '@playwright/test';
|
|
||||||
import { randomThumbnail } from 'src/ui/generators/timeline';
|
|
||||||
|
|
||||||
// Minimal valid H.264 MP4 (8x8px, 1 frame) that browsers can decode to get videoWidth/videoHeight
|
|
||||||
const MINIMAL_MP4_BASE64 =
|
|
||||||
'AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAr9tZGF0AAACoAYF//+c' +
|
|
||||||
'3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDEyNSAtIEguMjY0L01QRUctNCBBVkMgY29kZWMg' +
|
|
||||||
'LSBDb3B5bGVmdCAyMDAzLTIwMTIgLSBodHRwOi8vd3d3LnZpZGVvbGFuLm9yZy94MjY0Lmh0bWwg' +
|
|
||||||
'LSBvcHRpb25zOiBjYWJhYz0xIHJlZj0zIGRlYmxvY2s9MTowOjAgYW5hbHlzZT0weDM6MHgxMTMg' +
|
|
||||||
'bWU9aGV4IHN1Ym1lPTcgcHN5PTEgcHN5X3JkPTEuMDA6MC4wMCBtaXhlZF9yZWY9MSBtZV9yYW5n' +
|
|
||||||
'ZT0xNiBjaHJvbWFfbWU9MSB0cmVsbGlzPTEgOHg4ZGN0PTEgY3FtPTAgZGVhZHpvbmU9MjEsMTEg' +
|
|
||||||
'ZmFzdF9wc2tpcD0xIGNocm9tYV9xcF9vZmZzZXQ9LTIgdGhyZWFkcz02IGxvb2thaGVhZF90aHJl' +
|
|
||||||
'YWRzPTEgc2xpY2VkX3RocmVhZHM9MCBucj0wIGRlY2ltYXRlPTEgaW50ZXJsYWNlZD0wIGJsdXJh' +
|
|
||||||
'eV9jb21wYXQ9MCBjb25zdHJhaW5lZF9pbnRyYT0wIGJmcmFtZXM9MyBiX3B5cmFtaWQ9MiBiX2Fk' +
|
|
||||||
'YXB0PTEgYl9iaWFzPTAgZGlyZWN0PTEgd2VpZ2h0Yj0xIG9wZW5fZ29wPTAgd2VpZ2h0cD0yIGtl' +
|
|
||||||
'eWludD0yNTAga2V5aW50X21pbj0yNCBzY2VuZWN1dD00MCBpbnRyYV9yZWZyZXNoPTAgcmNfbG9v' +
|
|
||||||
'a2FoZWFkPTQwIHJjPWNyZiBtYnRyZWU9MSBjcmY9MjMuMCBxY29tcD0wLjYwIHFwbWluPTAgcXBt' +
|
|
||||||
'YXg9NjkgcXBzdGVwPTQgaXBfcmF0aW89MS40MCBhcT0xOjEuMDAAgAAAAA9liIQAV/0TAAYdeBTX' +
|
|
||||||
'zg8AAALvbW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAACoAAQAAAQAAAAAAAAAAAAAAAAEAAAAA' +
|
|
||||||
'AAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAA' +
|
|
||||||
'Ahl0cmFrAAAAXHRraGQAAAAPAAAAAAAAAAAAAAABAAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAEAAAAA' +
|
|
||||||
'AAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAgAAAAIAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAA' +
|
|
||||||
'AAEAAAAqAAAAAAABAAAAAAGRbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAAAwAAAAAgBVxAAAAAAA' +
|
|
||||||
'LWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABWaWRlb0hhbmRsZXIAAAABPG1pbmYAAAAUdm1oZAAA' +
|
|
||||||
'AAEAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAPxzdGJsAAAAmHN0' +
|
|
||||||
'c2QAAAAAAAAAAQAAAIhhdmMxAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAgACABIAAAASAAAAAAAAAAB' +
|
|
||||||
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGP//AAAAMmF2Y0MBZAAK/+EAGWdkAAqs' +
|
|
||||||
'2V+WXAWyAAADAAIAAAMAYB4kSywBAAZo6+PLIsAAAAAYc3R0cwAAAAAAAAABAAAAAQAAAgAAAAAcc3Rz' +
|
|
||||||
'YwAAAAAAAAABAAAAAQAAAAEAAAABAAAAFHN0c3oAAAAAAAACtwAAAAEAAAAUc3RjbwAAAAAAAAABAAAA' +
|
|
||||||
'MAAAAGJ1ZHRhAAAAWm1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALWls' +
|
|
||||||
'c3QAAAAlqXRvbwAAAB1kYXRhAAAAAQAAAABMYXZmNTQuNjMuMTA0';
|
|
||||||
|
|
||||||
export const MINIMAL_MP4_BUFFER = Buffer.from(MINIMAL_MP4_BASE64, 'base64');
|
|
||||||
|
|
||||||
export type MockPerson = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
birthDate: string | null;
|
|
||||||
isHidden: boolean;
|
|
||||||
thumbnailPath: string;
|
|
||||||
updatedAt: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createMockPeople = (count: number): MockPerson[] => {
|
|
||||||
const names = [
|
|
||||||
'Alice Johnson',
|
|
||||||
'Bob Smith',
|
|
||||||
'Charlie Brown',
|
|
||||||
'Diana Prince',
|
|
||||||
'Eve Adams',
|
|
||||||
'Frank Castle',
|
|
||||||
'Grace Lee',
|
|
||||||
'Hank Pym',
|
|
||||||
'Iris West',
|
|
||||||
'Jack Ryan',
|
|
||||||
];
|
|
||||||
return Array.from({ length: count }, (_, index) => ({
|
|
||||||
id: `person-${index}`,
|
|
||||||
name: names[index % names.length],
|
|
||||||
birthDate: null,
|
|
||||||
isHidden: false,
|
|
||||||
thumbnailPath: `/upload/thumbs/person-${index}.jpeg`,
|
|
||||||
updatedAt: '2025-01-01T00:00:00.000Z',
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
export type FaceCreateCapture = {
|
|
||||||
requests: Array<{
|
|
||||||
assetId: string;
|
|
||||||
personId: string;
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
imageWidth: number;
|
|
||||||
imageHeight: number;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setupFaceEditorMockApiRoutes = async (
|
|
||||||
context: BrowserContext,
|
|
||||||
mockPeople: MockPerson[],
|
|
||||||
faceCreateCapture: FaceCreateCapture,
|
|
||||||
) => {
|
|
||||||
await context.route('**/api/people?*', async (route, request) => {
|
|
||||||
if (request.method() !== 'GET') {
|
|
||||||
return route.fallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
return route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
contentType: 'application/json',
|
|
||||||
json: {
|
|
||||||
hasNextPage: false,
|
|
||||||
hidden: 0,
|
|
||||||
people: mockPeople,
|
|
||||||
total: mockPeople.length,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await context.route('**/api/faces', async (route, request) => {
|
|
||||||
if (request.method() !== 'POST') {
|
|
||||||
return route.fallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = request.postDataJSON();
|
|
||||||
faceCreateCapture.requests.push(body);
|
|
||||||
|
|
||||||
return route.fulfill({
|
|
||||||
status: 201,
|
|
||||||
contentType: 'text/plain',
|
|
||||||
body: 'OK',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await context.route('**/api/people/*/thumbnail', async (route) => {
|
|
||||||
if (!route.request().serviceWorker()) {
|
|
||||||
return route.continue();
|
|
||||||
}
|
|
||||||
return route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
headers: { 'content-type': 'image/jpeg' },
|
|
||||||
body: await randomThumbnail('person-thumb', 1),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
TimelineData,
|
TimelineData,
|
||||||
} from 'src/ui/generators/timeline';
|
} from 'src/ui/generators/timeline';
|
||||||
import { sleep } from 'src/ui/specs/timeline/utils';
|
import { sleep } from 'src/ui/specs/timeline/utils';
|
||||||
import { MINIMAL_MP4_BUFFER } from './face-editor-network';
|
|
||||||
|
|
||||||
export class TimelineTestContext {
|
export class TimelineTestContext {
|
||||||
slowBucket = false;
|
slowBucket = false;
|
||||||
@@ -136,14 +135,6 @@ export const setupTimelineMockApiRoutes = async (
|
|||||||
return route.continue();
|
return route.continue();
|
||||||
});
|
});
|
||||||
|
|
||||||
await context.route('**/api/assets/*/video/playback*', async (route) => {
|
|
||||||
return route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
headers: { 'content-type': 'video/mp4' },
|
|
||||||
body: MINIMAL_MP4_BUFFER,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await context.route('**/api/albums/**', async (route, request) => {
|
await context.route('**/api/albums/**', async (route, request) => {
|
||||||
const albumsMatch = request.url().match(/\/api\/albums\/(?<albumId>[^/?]+)/);
|
const albumsMatch = request.url().match(/\/api\/albums\/(?<albumId>[^/?]+)/);
|
||||||
if (albumsMatch) {
|
if (albumsMatch) {
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
|
||||||
import { toAssetResponseDto } from 'src/ui/generators/timeline';
|
|
||||||
import {
|
|
||||||
createMockStack,
|
|
||||||
createMockStackAsset,
|
|
||||||
MockStack,
|
|
||||||
setupBrokenAssetMockApiRoutes,
|
|
||||||
} from 'src/ui/mock-network/broken-asset-network';
|
|
||||||
import { assetViewerUtils } from '../timeline/utils';
|
|
||||||
import { setupAssetViewerFixture } from './utils';
|
|
||||||
|
|
||||||
test.describe.configure({ mode: 'parallel' });
|
|
||||||
test.describe('broken-asset responsiveness', () => {
|
|
||||||
const fixture = setupAssetViewerFixture(889);
|
|
||||||
let mockStack: MockStack;
|
|
||||||
|
|
||||||
test.beforeAll(async () => {
|
|
||||||
const primaryAssetDto = toAssetResponseDto(fixture.primaryAsset);
|
|
||||||
|
|
||||||
const brokenAssets = [
|
|
||||||
createMockStackAsset(fixture.adminUserId),
|
|
||||||
createMockStackAsset(fixture.adminUserId),
|
|
||||||
createMockStackAsset(fixture.adminUserId),
|
|
||||||
];
|
|
||||||
|
|
||||||
mockStack = createMockStack(primaryAssetDto, brokenAssets);
|
|
||||||
});
|
|
||||||
|
|
||||||
test.beforeEach(async ({ context }) => {
|
|
||||||
await setupBrokenAssetMockApiRoutes(context, mockStack);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('broken asset in stack strip hides icon at small size', async ({ page }) => {
|
|
||||||
await page.goto(`/photos/${fixture.primaryAsset.id}`);
|
|
||||||
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
|
|
||||||
|
|
||||||
const stackSlideshow = page.locator('#stack-slideshow');
|
|
||||||
await expect(stackSlideshow).toBeVisible();
|
|
||||||
|
|
||||||
const brokenAssets = stackSlideshow.locator('[data-broken-asset]');
|
|
||||||
await expect(brokenAssets.first()).toBeVisible();
|
|
||||||
await expect(brokenAssets).toHaveCount(mockStack.brokenAssetIds.size);
|
|
||||||
|
|
||||||
for (const brokenAsset of await brokenAssets.all()) {
|
|
||||||
await expect(brokenAsset.locator('svg')).not.toBeVisible();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('broken asset in stack strip uses text-xs class', async ({ page }) => {
|
|
||||||
await page.goto(`/photos/${fixture.primaryAsset.id}`);
|
|
||||||
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
|
|
||||||
|
|
||||||
const stackSlideshow = page.locator('#stack-slideshow');
|
|
||||||
await expect(stackSlideshow).toBeVisible();
|
|
||||||
|
|
||||||
const brokenAssets = stackSlideshow.locator('[data-broken-asset]');
|
|
||||||
await expect(brokenAssets.first()).toBeVisible();
|
|
||||||
|
|
||||||
for (const brokenAsset of await brokenAssets.all()) {
|
|
||||||
const messageSpan = brokenAsset.locator('span');
|
|
||||||
await expect(messageSpan).toHaveClass(/text-xs/);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('broken asset in main viewer shows icon and uses text-base', async ({ context, page }) => {
|
|
||||||
await context.route(
|
|
||||||
(url) => url.pathname.includes(`/api/assets/${fixture.primaryAsset.id}/thumbnail`),
|
|
||||||
async (route) => {
|
|
||||||
return route.fulfill({ status: 404 });
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
await page.goto(`/photos/${fixture.primaryAsset.id}`);
|
|
||||||
await page.waitForSelector('#immich-asset-viewer');
|
|
||||||
|
|
||||||
const viewerBrokenAsset = page.locator('#immich-asset-viewer #broken-asset [data-broken-asset]');
|
|
||||||
await expect(viewerBrokenAsset).toBeVisible();
|
|
||||||
|
|
||||||
await expect(viewerBrokenAsset.locator('svg')).toBeVisible();
|
|
||||||
|
|
||||||
const messageSpan = viewerBrokenAsset.locator('span');
|
|
||||||
await expect(messageSpan).toHaveClass(/text-base/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
import { expect, Page, test } from '@playwright/test';
|
|
||||||
import { SeededRandom, selectRandom, TimelineAssetConfig } from 'src/ui/generators/timeline';
|
|
||||||
import {
|
|
||||||
createMockPeople,
|
|
||||||
FaceCreateCapture,
|
|
||||||
MockPerson,
|
|
||||||
setupFaceEditorMockApiRoutes,
|
|
||||||
} from 'src/ui/mock-network/face-editor-network';
|
|
||||||
import { assetViewerUtils } from '../timeline/utils';
|
|
||||||
import { setupAssetViewerFixture } from './utils';
|
|
||||||
|
|
||||||
const waitForSelectorTransition = async (page: Page) => {
|
|
||||||
await page.waitForFunction(
|
|
||||||
() => {
|
|
||||||
const selector = document.querySelector('#face-selector') as HTMLElement | null;
|
|
||||||
if (!selector) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return selector.getAnimations({ subtree: false }).every((animation) => animation.playState === 'finished');
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
{ timeout: 1000, polling: 50 },
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const openFaceEditor = async (page: Page, asset: TimelineAssetConfig) => {
|
|
||||||
await page.goto(`/photos/${asset.id}`);
|
|
||||||
await assetViewerUtils.waitForViewerLoad(page, asset);
|
|
||||||
await page.keyboard.press('i');
|
|
||||||
await page.locator('#detail-panel').waitFor({ state: 'visible' });
|
|
||||||
await page.getByLabel('Tag people').click();
|
|
||||||
await page.locator('#face-selector').waitFor({ state: 'visible' });
|
|
||||||
await waitForSelectorTransition(page);
|
|
||||||
};
|
|
||||||
|
|
||||||
test.describe.configure({ mode: 'parallel' });
|
|
||||||
test.describe('face-editor', () => {
|
|
||||||
const fixture = setupAssetViewerFixture(777);
|
|
||||||
const rng = new SeededRandom(777);
|
|
||||||
let mockPeople: MockPerson[];
|
|
||||||
let faceCreateCapture: FaceCreateCapture;
|
|
||||||
|
|
||||||
test.beforeAll(async () => {
|
|
||||||
mockPeople = createMockPeople(8);
|
|
||||||
});
|
|
||||||
|
|
||||||
test.beforeEach(async ({ context }) => {
|
|
||||||
faceCreateCapture = { requests: [] };
|
|
||||||
await setupFaceEditorMockApiRoutes(context, mockPeople, faceCreateCapture);
|
|
||||||
});
|
|
||||||
|
|
||||||
type ScreenRect = { top: number; left: number; width: number; height: number };
|
|
||||||
|
|
||||||
const getFaceBoxRect = async (page: Page): Promise<ScreenRect> => {
|
|
||||||
const dataEl = page.locator('#face-editor-data');
|
|
||||||
await expect(dataEl).toHaveAttribute('data-face-left', /^-?\d+/);
|
|
||||||
await expect(dataEl).toHaveAttribute('data-face-top', /^-?\d+/);
|
|
||||||
await expect(dataEl).toHaveAttribute('data-face-width', /^[1-9]/);
|
|
||||||
await expect(dataEl).toHaveAttribute('data-face-height', /^[1-9]/);
|
|
||||||
const canvasBox = await page.locator('#face-editor').boundingBox();
|
|
||||||
if (!canvasBox) {
|
|
||||||
throw new Error('Canvas element not found');
|
|
||||||
}
|
|
||||||
const left = Number(await dataEl.getAttribute('data-face-left'));
|
|
||||||
const top = Number(await dataEl.getAttribute('data-face-top'));
|
|
||||||
const width = Number(await dataEl.getAttribute('data-face-width'));
|
|
||||||
const height = Number(await dataEl.getAttribute('data-face-height'));
|
|
||||||
return {
|
|
||||||
top: canvasBox.y + top,
|
|
||||||
left: canvasBox.x + left,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSelectorRect = async (page: Page): Promise<ScreenRect> => {
|
|
||||||
const box = await page.locator('#face-selector').boundingBox();
|
|
||||||
if (!box) {
|
|
||||||
throw new Error('Face selector element not found');
|
|
||||||
}
|
|
||||||
return { top: box.y, left: box.x, width: box.width, height: box.height };
|
|
||||||
};
|
|
||||||
|
|
||||||
const computeOverlapArea = (a: ScreenRect, b: ScreenRect): number => {
|
|
||||||
const overlapX = Math.max(0, Math.min(a.left + a.width, b.left + b.width) - Math.max(a.left, b.left));
|
|
||||||
const overlapY = Math.max(0, Math.min(a.top + a.height, b.top + b.height) - Math.max(a.top, b.top));
|
|
||||||
return overlapX * overlapY;
|
|
||||||
};
|
|
||||||
|
|
||||||
const dragFaceBox = async (page: Page, deltaX: number, deltaY: number) => {
|
|
||||||
const faceBox = await getFaceBoxRect(page);
|
|
||||||
const centerX = faceBox.left + faceBox.width / 2;
|
|
||||||
const centerY = faceBox.top + faceBox.height / 2;
|
|
||||||
await page.mouse.move(centerX, centerY);
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(centerX + deltaX, centerY + deltaY, { steps: 5 });
|
|
||||||
await page.mouse.up();
|
|
||||||
await page.waitForTimeout(300);
|
|
||||||
};
|
|
||||||
|
|
||||||
test('Face editor opens with person list', async ({ page }) => {
|
|
||||||
const asset = selectRandom(fixture.assets, rng);
|
|
||||||
await openFaceEditor(page, asset);
|
|
||||||
|
|
||||||
await expect(page.locator('#face-selector')).toBeVisible();
|
|
||||||
await expect(page.locator('#face-editor')).toBeVisible();
|
|
||||||
|
|
||||||
for (const person of mockPeople) {
|
|
||||||
await expect(page.locator('#face-selector').getByText(person.name)).toBeVisible();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Search filters people by name', async ({ page }) => {
|
|
||||||
const asset = selectRandom(fixture.assets, rng);
|
|
||||||
await openFaceEditor(page, asset);
|
|
||||||
|
|
||||||
const searchInput = page.locator('#face-selector input');
|
|
||||||
await searchInput.fill('Alice');
|
|
||||||
|
|
||||||
await expect(page.locator('#face-selector').getByText('Alice Johnson')).toBeVisible();
|
|
||||||
await expect(page.locator('#face-selector').getByText('Bob Smith')).toBeHidden();
|
|
||||||
|
|
||||||
await searchInput.clear();
|
|
||||||
|
|
||||||
for (const person of mockPeople) {
|
|
||||||
await expect(page.locator('#face-selector').getByText(person.name)).toBeVisible();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Search with no results shows empty message', async ({ page }) => {
|
|
||||||
const asset = selectRandom(fixture.assets, rng);
|
|
||||||
await openFaceEditor(page, asset);
|
|
||||||
|
|
||||||
const searchInput = page.locator('#face-selector input');
|
|
||||||
await searchInput.fill('Nonexistent Person XYZ');
|
|
||||||
|
|
||||||
for (const person of mockPeople) {
|
|
||||||
await expect(page.locator('#face-selector').getByText(person.name)).toBeHidden();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Selecting a person shows confirmation dialog', async ({ page }) => {
|
|
||||||
const asset = selectRandom(fixture.assets, rng);
|
|
||||||
await openFaceEditor(page, asset);
|
|
||||||
|
|
||||||
const personToTag = mockPeople[0];
|
|
||||||
await page.locator('#face-selector').getByText(personToTag.name).click();
|
|
||||||
|
|
||||||
await expect(page.getByRole('dialog')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Confirming tag calls createFace API and closes editor', async ({ page }) => {
|
|
||||||
const asset = selectRandom(fixture.assets, rng);
|
|
||||||
await openFaceEditor(page, asset);
|
|
||||||
|
|
||||||
const personToTag = mockPeople[0];
|
|
||||||
await page.locator('#face-selector').getByText(personToTag.name).click();
|
|
||||||
|
|
||||||
await expect(page.getByRole('dialog')).toBeVisible();
|
|
||||||
await page.getByRole('button', { name: /confirm/i }).click();
|
|
||||||
|
|
||||||
await expect(page.locator('#face-selector')).toBeHidden();
|
|
||||||
await expect(page.locator('#face-editor')).toBeHidden();
|
|
||||||
|
|
||||||
expect(faceCreateCapture.requests).toHaveLength(1);
|
|
||||||
expect(faceCreateCapture.requests[0].assetId).toBe(asset.id);
|
|
||||||
expect(faceCreateCapture.requests[0].personId).toBe(personToTag.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Cancel button closes face editor', async ({ page }) => {
|
|
||||||
const asset = selectRandom(fixture.assets, rng);
|
|
||||||
await openFaceEditor(page, asset);
|
|
||||||
|
|
||||||
await expect(page.locator('#face-selector')).toBeVisible();
|
|
||||||
await expect(page.locator('#face-editor')).toBeVisible();
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: /cancel/i }).click();
|
|
||||||
|
|
||||||
await expect(page.locator('#face-selector')).toBeHidden();
|
|
||||||
await expect(page.locator('#face-editor')).toBeHidden();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Selector does not overlap face box on initial open', async ({ page }) => {
|
|
||||||
const asset = selectRandom(fixture.assets, rng);
|
|
||||||
await openFaceEditor(page, asset);
|
|
||||||
|
|
||||||
const faceBox = await getFaceBoxRect(page);
|
|
||||||
const selectorBox = await getSelectorRect(page);
|
|
||||||
const overlap = computeOverlapArea(faceBox, selectorBox);
|
|
||||||
|
|
||||||
expect(overlap).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Selector repositions without overlap after dragging face box down', async ({ page }) => {
|
|
||||||
const asset = selectRandom(fixture.assets, rng);
|
|
||||||
await openFaceEditor(page, asset);
|
|
||||||
|
|
||||||
await dragFaceBox(page, 0, 150);
|
|
||||||
|
|
||||||
const faceBox = await getFaceBoxRect(page);
|
|
||||||
const selectorBox = await getSelectorRect(page);
|
|
||||||
const overlap = computeOverlapArea(faceBox, selectorBox);
|
|
||||||
|
|
||||||
expect(overlap).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Selector repositions without overlap after dragging face box right', async ({ page }) => {
|
|
||||||
const asset = selectRandom(fixture.assets, rng);
|
|
||||||
await openFaceEditor(page, asset);
|
|
||||||
|
|
||||||
await dragFaceBox(page, 200, 0);
|
|
||||||
|
|
||||||
const faceBox = await getFaceBoxRect(page);
|
|
||||||
const selectorBox = await getSelectorRect(page);
|
|
||||||
const overlap = computeOverlapArea(faceBox, selectorBox);
|
|
||||||
|
|
||||||
expect(overlap).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Selector repositions without overlap after dragging face box to top-left corner', async ({ page }) => {
|
|
||||||
const asset = selectRandom(fixture.assets, rng);
|
|
||||||
await openFaceEditor(page, asset);
|
|
||||||
|
|
||||||
await dragFaceBox(page, -300, -300);
|
|
||||||
|
|
||||||
const faceBox = await getFaceBoxRect(page);
|
|
||||||
const selectorBox = await getSelectorRect(page);
|
|
||||||
const overlap = computeOverlapArea(faceBox, selectorBox);
|
|
||||||
|
|
||||||
expect(overlap).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Selector repositions without overlap after dragging face box to bottom-right', async ({ page }) => {
|
|
||||||
const asset = selectRandom(fixture.assets, rng);
|
|
||||||
await openFaceEditor(page, asset);
|
|
||||||
|
|
||||||
await dragFaceBox(page, 300, 300);
|
|
||||||
|
|
||||||
const faceBox = await getFaceBoxRect(page);
|
|
||||||
const selectorBox = await getSelectorRect(page);
|
|
||||||
const overlap = computeOverlapArea(faceBox, selectorBox);
|
|
||||||
|
|
||||||
expect(overlap).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Selector stays within viewport bounds', async ({ page }) => {
|
|
||||||
const asset = selectRandom(fixture.assets, rng);
|
|
||||||
await openFaceEditor(page, asset);
|
|
||||||
|
|
||||||
const viewportSize = page.viewportSize()!;
|
|
||||||
const selectorBox = await getSelectorRect(page);
|
|
||||||
|
|
||||||
expect(selectorBox.top).toBeGreaterThanOrEqual(0);
|
|
||||||
expect(selectorBox.left).toBeGreaterThanOrEqual(0);
|
|
||||||
expect(selectorBox.top + selectorBox.height).toBeLessThanOrEqual(viewportSize.height);
|
|
||||||
expect(selectorBox.left + selectorBox.width).toBeLessThanOrEqual(viewportSize.width);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Selector stays within viewport after dragging to edge', async ({ page }) => {
|
|
||||||
const asset = selectRandom(fixture.assets, rng);
|
|
||||||
await openFaceEditor(page, asset);
|
|
||||||
|
|
||||||
await dragFaceBox(page, -400, -400);
|
|
||||||
|
|
||||||
const viewportSize = page.viewportSize()!;
|
|
||||||
const selectorBox = await getSelectorRect(page);
|
|
||||||
|
|
||||||
expect(selectorBox.top).toBeGreaterThanOrEqual(0);
|
|
||||||
expect(selectorBox.left).toBeGreaterThanOrEqual(0);
|
|
||||||
expect(selectorBox.top + selectorBox.height).toBeLessThanOrEqual(viewportSize.height);
|
|
||||||
expect(selectorBox.left + selectorBox.width).toBeLessThanOrEqual(viewportSize.width);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Face box is draggable on the canvas', async ({ page }) => {
|
|
||||||
const asset = selectRandom(fixture.assets, rng);
|
|
||||||
await openFaceEditor(page, asset);
|
|
||||||
|
|
||||||
const beforeDrag = await getFaceBoxRect(page);
|
|
||||||
await dragFaceBox(page, 100, 50);
|
|
||||||
const afterDrag = await getFaceBoxRect(page);
|
|
||||||
|
|
||||||
expect(afterDrag.left).toBeGreaterThan(beforeDrag.left + 50);
|
|
||||||
expect(afterDrag.top).toBeGreaterThan(beforeDrag.top + 20);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import { faker } from '@faker-js/faker';
|
|
||||||
import type { AssetResponseDto } from '@immich/sdk';
|
|
||||||
import { expect, test } from '@playwright/test';
|
|
||||||
import { toAssetResponseDto } from 'src/ui/generators/timeline';
|
|
||||||
import {
|
|
||||||
createMockStack,
|
|
||||||
createMockStackAsset,
|
|
||||||
MockStack,
|
|
||||||
setupBrokenAssetMockApiRoutes,
|
|
||||||
} from 'src/ui/mock-network/broken-asset-network';
|
|
||||||
import { assetViewerUtils } from '../timeline/utils';
|
|
||||||
import { enableTagsPreference, ensureDetailPanelVisible, setupAssetViewerFixture } from './utils';
|
|
||||||
|
|
||||||
test.describe.configure({ mode: 'parallel' });
|
|
||||||
test.describe('asset-viewer stack', () => {
|
|
||||||
const fixture = setupAssetViewerFixture(888);
|
|
||||||
let mockStack: MockStack;
|
|
||||||
let primaryAssetDto: AssetResponseDto;
|
|
||||||
let secondAssetDto: AssetResponseDto;
|
|
||||||
|
|
||||||
test.beforeAll(async () => {
|
|
||||||
primaryAssetDto = toAssetResponseDto(fixture.primaryAsset);
|
|
||||||
primaryAssetDto.tags = [
|
|
||||||
{
|
|
||||||
id: faker.string.uuid(),
|
|
||||||
name: '1',
|
|
||||||
value: 'test/1',
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
updatedAt: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
secondAssetDto = createMockStackAsset(fixture.adminUserId);
|
|
||||||
secondAssetDto.tags = [
|
|
||||||
{
|
|
||||||
id: faker.string.uuid(),
|
|
||||||
name: '2',
|
|
||||||
value: 'test/2',
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
updatedAt: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
mockStack = createMockStack(primaryAssetDto, [secondAssetDto], new Set());
|
|
||||||
});
|
|
||||||
|
|
||||||
test.beforeEach(async ({ context }) => {
|
|
||||||
await setupBrokenAssetMockApiRoutes(context, mockStack);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('stack slideshow is visible', async ({ page }) => {
|
|
||||||
await page.goto(`/photos/${fixture.primaryAsset.id}`);
|
|
||||||
await assetViewerUtils.waitForViewerLoad(page, fixture.primaryAsset);
|
|
||||||
|
|
||||||
const stackSlideshow = page.locator('#stack-slideshow');
|
|
||||||
await expect(stackSlideshow).toBeVisible();
|
|
||||||
|
|
||||||
const stackAssets = stackSlideshow.locator('[data-asset]');
|
|
||||||
await expect(stackAssets).toHaveCount(mockStack.assets.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('tags of primary asset are visible', async ({ context, page }) => {
|
|
||||||
await enableTagsPreference(context);
|
|
||||||
|
|
||||||
await page.goto(`/photos/${fixture.primaryAsset.id}`);
|
|
||||||
await ensureDetailPanelVisible(page);
|
|
||||||
|
|
||||||
const tags = page.getByTestId('detail-panel-tags').getByRole('link');
|
|
||||||
await expect(tags.first()).toHaveText('test/1');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('tags of second asset are visible', async ({ context, page }) => {
|
|
||||||
await enableTagsPreference(context);
|
|
||||||
|
|
||||||
await page.goto(`/photos/${fixture.primaryAsset.id}`);
|
|
||||||
await ensureDetailPanelVisible(page);
|
|
||||||
|
|
||||||
const stackAssets = page.locator('#stack-slideshow [data-asset]');
|
|
||||||
await stackAssets.nth(1).click();
|
|
||||||
|
|
||||||
const tags = page.getByTestId('detail-panel-tags').getByRole('link');
|
|
||||||
await expect(tags.first()).toHaveText('test/2');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
import { faker } from '@faker-js/faker';
|
|
||||||
import type { AssetResponseDto } from '@immich/sdk';
|
|
||||||
import { BrowserContext, Page, test } from '@playwright/test';
|
|
||||||
import {
|
|
||||||
Changes,
|
|
||||||
createDefaultTimelineConfig,
|
|
||||||
generateTimelineData,
|
|
||||||
SeededRandom,
|
|
||||||
selectRandom,
|
|
||||||
TimelineAssetConfig,
|
|
||||||
TimelineData,
|
|
||||||
toAssetResponseDto,
|
|
||||||
} from 'src/ui/generators/timeline';
|
|
||||||
import { setupBaseMockApiRoutes } from 'src/ui/mock-network/base-network';
|
|
||||||
import { setupTimelineMockApiRoutes, TimelineTestContext } from 'src/ui/mock-network/timeline-network';
|
|
||||||
import { utils } from 'src/utils';
|
|
||||||
|
|
||||||
export type AssetViewerTestFixture = {
|
|
||||||
adminUserId: string;
|
|
||||||
timelineRestData: TimelineData;
|
|
||||||
assets: TimelineAssetConfig[];
|
|
||||||
testContext: TimelineTestContext;
|
|
||||||
changes: Changes;
|
|
||||||
primaryAsset: TimelineAssetConfig;
|
|
||||||
primaryAssetDto: AssetResponseDto;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function setupAssetViewerFixture(seed: number): AssetViewerTestFixture {
|
|
||||||
const rng = new SeededRandom(seed);
|
|
||||||
const testContext = new TimelineTestContext();
|
|
||||||
|
|
||||||
const fixture: AssetViewerTestFixture = {
|
|
||||||
adminUserId: undefined!,
|
|
||||||
timelineRestData: undefined!,
|
|
||||||
assets: [],
|
|
||||||
testContext,
|
|
||||||
changes: {
|
|
||||||
albumAdditions: [],
|
|
||||||
assetDeletions: [],
|
|
||||||
assetArchivals: [],
|
|
||||||
assetFavorites: [],
|
|
||||||
},
|
|
||||||
primaryAsset: undefined!,
|
|
||||||
primaryAssetDto: undefined!,
|
|
||||||
};
|
|
||||||
|
|
||||||
test.beforeAll(async () => {
|
|
||||||
test.fail(
|
|
||||||
process.env.PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS !== '1',
|
|
||||||
'This test requires env var: PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1',
|
|
||||||
);
|
|
||||||
utils.initSdk();
|
|
||||||
fixture.adminUserId = faker.string.uuid();
|
|
||||||
testContext.adminId = fixture.adminUserId;
|
|
||||||
fixture.timelineRestData = generateTimelineData({
|
|
||||||
...createDefaultTimelineConfig(),
|
|
||||||
ownerId: fixture.adminUserId,
|
|
||||||
});
|
|
||||||
for (const timeBucket of fixture.timelineRestData.buckets.values()) {
|
|
||||||
fixture.assets.push(...timeBucket);
|
|
||||||
}
|
|
||||||
|
|
||||||
fixture.primaryAsset = selectRandom(
|
|
||||||
fixture.assets.filter((a) => a.isImage),
|
|
||||||
rng,
|
|
||||||
);
|
|
||||||
fixture.primaryAssetDto = toAssetResponseDto(fixture.primaryAsset);
|
|
||||||
});
|
|
||||||
|
|
||||||
test.beforeEach(async ({ context }) => {
|
|
||||||
await setupBaseMockApiRoutes(context, fixture.adminUserId);
|
|
||||||
await setupTimelineMockApiRoutes(context, fixture.timelineRestData, fixture.changes, fixture.testContext);
|
|
||||||
});
|
|
||||||
|
|
||||||
test.afterEach(() => {
|
|
||||||
fixture.testContext.slowBucket = false;
|
|
||||||
fixture.changes.albumAdditions = [];
|
|
||||||
fixture.changes.assetDeletions = [];
|
|
||||||
fixture.changes.assetArchivals = [];
|
|
||||||
fixture.changes.assetFavorites = [];
|
|
||||||
});
|
|
||||||
|
|
||||||
return fixture;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function ensureDetailPanelVisible(page: Page) {
|
|
||||||
await page.waitForSelector('#immich-asset-viewer');
|
|
||||||
|
|
||||||
const isVisible = await page.locator('#detail-panel').isVisible();
|
|
||||||
if (!isVisible) {
|
|
||||||
await page.keyboard.press('i');
|
|
||||||
await page.waitForSelector('#detail-panel');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function enableTagsPreference(context: BrowserContext) {
|
|
||||||
await context.route('**/users/me/preferences', async (route) => {
|
|
||||||
return route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
contentType: 'application/json',
|
|
||||||
json: {
|
|
||||||
albums: { defaultAssetOrder: 'desc' },
|
|
||||||
folders: { enabled: false, sidebarWeb: false },
|
|
||||||
memories: { enabled: true, duration: 5 },
|
|
||||||
people: { enabled: true, sidebarWeb: false },
|
|
||||||
sharedLinks: { enabled: true, sidebarWeb: false },
|
|
||||||
ratings: { enabled: false },
|
|
||||||
tags: { enabled: true, sidebarWeb: false },
|
|
||||||
emailNotifications: { enabled: true, albumInvite: true, albumUpdate: true },
|
|
||||||
download: { archiveSize: 4_294_967_296, includeEmbeddedVideos: false },
|
|
||||||
purchase: { showSupportBadge: true, hideBuyButtonUntil: '2100-02-12T00:00:00.000Z' },
|
|
||||||
cast: { gCastEnabled: false },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
+3
-8
@@ -1074,7 +1074,6 @@
|
|||||||
"failed_to_update_notification_status": "Failed to update notification status",
|
"failed_to_update_notification_status": "Failed to update notification status",
|
||||||
"incorrect_email_or_password": "Incorrect email or password",
|
"incorrect_email_or_password": "Incorrect email or password",
|
||||||
"library_folder_already_exists": "This import path already exists.",
|
"library_folder_already_exists": "This import path already exists.",
|
||||||
"page_not_found": "Page not found :/",
|
|
||||||
"paths_validation_failed": "{paths, plural, one {# path} other {# paths}} failed validation",
|
"paths_validation_failed": "{paths, plural, one {# path} other {# paths}} failed validation",
|
||||||
"profile_picture_transparent_pixels": "Profile pictures cannot have transparent pixels. Please zoom in and/or move the image.",
|
"profile_picture_transparent_pixels": "Profile pictures cannot have transparent pixels. Please zoom in and/or move the image.",
|
||||||
"quota_higher_than_disk_size": "You set a quota higher than the disk size",
|
"quota_higher_than_disk_size": "You set a quota higher than the disk size",
|
||||||
@@ -1810,8 +1809,9 @@
|
|||||||
"rate_asset": "Rate Asset",
|
"rate_asset": "Rate Asset",
|
||||||
"rating": "Star rating",
|
"rating": "Star rating",
|
||||||
"rating_clear": "Clear rating",
|
"rating_clear": "Clear rating",
|
||||||
"rating_count": "{count, plural, =0 {Unrated} one {# star} other {# stars}}",
|
"rating_count": "{count, plural, one {# star} other {# stars}}",
|
||||||
"rating_description": "Display the EXIF rating in the info panel",
|
"rating_description": "Display the EXIF rating in the info panel",
|
||||||
|
"rating_set": "Rating set to {rating, plural, one {# star} other {# stars}}",
|
||||||
"reaction_options": "Reaction options",
|
"reaction_options": "Reaction options",
|
||||||
"read_changelog": "Read Changelog",
|
"read_changelog": "Read Changelog",
|
||||||
"readonly_mode_disabled": "Read-only mode disabled",
|
"readonly_mode_disabled": "Read-only mode disabled",
|
||||||
@@ -1883,10 +1883,7 @@
|
|||||||
"reset_pin_code_success": "Successfully reset PIN code",
|
"reset_pin_code_success": "Successfully reset PIN code",
|
||||||
"reset_pin_code_with_password": "You can always reset your PIN code with your password",
|
"reset_pin_code_with_password": "You can always reset your PIN code with your password",
|
||||||
"reset_sqlite": "Reset SQLite Database",
|
"reset_sqlite": "Reset SQLite Database",
|
||||||
"reset_sqlite_clear_app_data": "Clear Data",
|
"reset_sqlite_confirmation": "Are you sure you want to reset the SQLite database? You will need to log out and log in again to resync the data",
|
||||||
"reset_sqlite_confirmation": "Are you sure you want to clear the app data? This will remove all settings and sign you out.",
|
|
||||||
"reset_sqlite_confirmation_note": "Note: You will need to restart the app after clearing.",
|
|
||||||
"reset_sqlite_done": "App data has been cleared. Please restart Immich and log in again.",
|
|
||||||
"reset_sqlite_success": "Successfully reset the SQLite database",
|
"reset_sqlite_success": "Successfully reset the SQLite database",
|
||||||
"reset_to_default": "Reset to default",
|
"reset_to_default": "Reset to default",
|
||||||
"resolution": "Resolution",
|
"resolution": "Resolution",
|
||||||
@@ -1914,7 +1911,6 @@
|
|||||||
"saved_settings": "Saved settings",
|
"saved_settings": "Saved settings",
|
||||||
"say_something": "Say something",
|
"say_something": "Say something",
|
||||||
"scaffold_body_error_occurred": "Error occurred",
|
"scaffold_body_error_occurred": "Error occurred",
|
||||||
"scaffold_body_error_unrecoverable": "An unrecoverable error has occurred. Please share the error and stack trace on Discord or GitHub so we can help. If advised, you can clear the app data below.",
|
|
||||||
"scan": "Scan",
|
"scan": "Scan",
|
||||||
"scan_all_libraries": "Scan All Libraries",
|
"scan_all_libraries": "Scan All Libraries",
|
||||||
"scan_library": "Scan",
|
"scan_library": "Scan",
|
||||||
@@ -2311,7 +2307,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",
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
3.13
|
|
||||||
+50
-10
@@ -22,7 +22,48 @@ FROM builder-cpu AS builder-rknn
|
|||||||
|
|
||||||
# Warning: 25GiB+ disk space required to pull this image
|
# Warning: 25GiB+ disk space required to pull this image
|
||||||
# TODO: find a way to reduce the image size
|
# TODO: find a way to reduce the image size
|
||||||
FROM rocm/dev-ubuntu-24.04:7.2-complete@sha256:86e11093b4a7ec2a79b1b6701d10e840a6994f21c7e05929b51eb9be361c683a AS builder-rocm
|
FROM rocm/dev-ubuntu-24.04:6.4.4-complete@sha256:31418ac10a3769a71eaef330c07280d1d999d7074621339b8f93c484c35f6078 AS builder-rocm
|
||||||
|
|
||||||
|
# renovate: datasource=github-releases depName=Microsoft/onnxruntime
|
||||||
|
ARG ONNXRUNTIME_VERSION="v1.22.1"
|
||||||
|
WORKDIR /code
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends wget git
|
||||||
|
RUN wget -nv https://github.com/Kitware/CMake/releases/download/v3.31.9/cmake-3.31.9-linux-x86_64.sh && \
|
||||||
|
chmod +x cmake-3.31.9-linux-x86_64.sh && \
|
||||||
|
mkdir -p /code/cmake-3.31.9-linux-x86_64 && \
|
||||||
|
./cmake-3.31.9-linux-x86_64.sh --skip-license --prefix=/code/cmake-3.31.9-linux-x86_64 && \
|
||||||
|
rm cmake-3.31.9-linux-x86_64.sh
|
||||||
|
|
||||||
|
RUN git clone --single-branch --branch "${ONNXRUNTIME_VERSION}" --recursive "https://github.com/Microsoft/onnxruntime" onnxruntime
|
||||||
|
WORKDIR /code/onnxruntime
|
||||||
|
# Fix for multi-threading based on comments in https://github.com/microsoft/onnxruntime/pull/19567
|
||||||
|
# TODO: find a way to fix this without disabling algo caching
|
||||||
|
COPY ./patches/* /tmp/
|
||||||
|
RUN git apply /tmp/*.patch
|
||||||
|
|
||||||
|
RUN /bin/sh ./dockerfiles/scripts/install_common_deps.sh
|
||||||
|
|
||||||
|
ENV PATH=/opt/rocm-venv/bin:/code/cmake-3.31.9-linux-x86_64/bin:${PATH}
|
||||||
|
ENV CCACHE_DIR="/ccache"
|
||||||
|
# Note: the `parallel` setting uses a substantial amount of RAM
|
||||||
|
RUN --mount=type=cache,target=/ccache \
|
||||||
|
./build.sh \
|
||||||
|
--allow_running_as_root \
|
||||||
|
--config Release \
|
||||||
|
--build_wheel \
|
||||||
|
--update \
|
||||||
|
--build \
|
||||||
|
--parallel 48 \
|
||||||
|
--cmake_extra_defines \
|
||||||
|
ONNXRUNTIME_VERSION="${ONNXRUNTIME_VERSION}" \
|
||||||
|
CMAKE_HIP_ARCHITECTURES="gfx900;gfx906;gfx908;gfx90a;gfx940;gfx941;gfx942;gfx1030;gfx1100;gfx1101;gfx1102;gfx1200;gfx1201" \
|
||||||
|
--skip_tests \
|
||||||
|
--use_rocm \
|
||||||
|
--rocm_home=/opt/rocm \
|
||||||
|
--use_cache \
|
||||||
|
--compile_no_warning_as_error
|
||||||
|
RUN mv /code/onnxruntime/build/Linux/Release/dist/*.whl /opt/
|
||||||
|
|
||||||
FROM builder-${DEVICE} AS builder
|
FROM builder-${DEVICE} AS builder
|
||||||
|
|
||||||
@@ -38,6 +79,9 @@ RUN --mount=type=cache,target=/root/.cache/uv \
|
|||||||
--mount=type=bind,source=uv.lock,target=uv.lock \
|
--mount=type=bind,source=uv.lock,target=uv.lock \
|
||||||
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||||
uv sync --frozen --extra ${DEVICE} --no-dev --no-editable --no-install-project --compile-bytecode --no-progress --active --link-mode copy
|
uv sync --frozen --extra ${DEVICE} --no-dev --no-editable --no-install-project --compile-bytecode --no-progress --active --link-mode copy
|
||||||
|
RUN if [ "$DEVICE" = "rocm" ]; then \
|
||||||
|
uv pip install /opt/onnxruntime_rocm-*.whl; \
|
||||||
|
fi
|
||||||
|
|
||||||
FROM python:3.11-slim-bookworm@sha256:04cd27899595a99dfe77709d96f08876bf2ee99139ee2f0fe9ac948005034e5b AS prod-cpu
|
FROM python:3.11-slim-bookworm@sha256:04cd27899595a99dfe77709d96f08876bf2ee99139ee2f0fe9ac948005034e5b AS prod-cpu
|
||||||
|
|
||||||
@@ -48,14 +92,14 @@ FROM python:3.13-slim-trixie@sha256:3de9a8d7aedbb7984dc18f2dff178a7850f16c1ae7c3
|
|||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
|
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
|
||||||
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.28.4/intel-igc-core-2_2.28.4+20760_amd64.deb && \
|
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.27.10/intel-igc-core-2_2.27.10+20617_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.28.4/intel-igc-opencl-2_2.28.4+20760_amd64.deb && \
|
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.27.10/intel-igc-opencl-2_2.27.10+20617_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/compute-runtime/releases/download/26.05.37020.3/intel-opencl-icd_26.05.37020.3-0_amd64.deb && \
|
wget -nv https://github.com/intel/compute-runtime/releases/download/26.01.36711.4/intel-opencl-icd_26.01.36711.4-0_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb && \
|
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb && \
|
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb && \
|
wget -nv https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb && \
|
||||||
# TODO: Figure out how to get renovate to manage this differently versioned libigdgmm file
|
# TODO: Figure out how to get renovate to manage this differently versioned libigdgmm file
|
||||||
wget -nv https://github.com/intel/compute-runtime/releases/download/26.05.37020.3/libigdgmm12_22.9.0_amd64.deb && \
|
wget -nv https://github.com/intel/compute-runtime/releases/download/26.01.36711.4/libigdgmm12_22.9.0_amd64.deb && \
|
||||||
dpkg -i *.deb && \
|
dpkg -i *.deb && \
|
||||||
rm *.deb && \
|
rm *.deb && \
|
||||||
apt-get remove wget -yqq && \
|
apt-get remove wget -yqq && \
|
||||||
@@ -76,11 +120,7 @@ COPY --from=builder-cuda /usr/local/bin/python3 /usr/local/bin/python3
|
|||||||
COPY --from=builder-cuda /usr/local/lib/python3.11 /usr/local/lib/python3.11
|
COPY --from=builder-cuda /usr/local/lib/python3.11 /usr/local/lib/python3.11
|
||||||
COPY --from=builder-cuda /usr/local/lib/libpython3.11.so /usr/local/lib/libpython3.11.so
|
COPY --from=builder-cuda /usr/local/lib/libpython3.11.so /usr/local/lib/libpython3.11.so
|
||||||
|
|
||||||
FROM rocm/dev-ubuntu-24.04:7.2-complete@sha256:86e11093b4a7ec2a79b1b6701d10e840a6994f21c7e05929b51eb9be361c683a AS prod-rocm
|
FROM rocm/dev-ubuntu-24.04:6.4.4-complete@sha256:31418ac10a3769a71eaef330c07280d1d999d7074621339b8f93c484c35f6078 AS prod-rocm
|
||||||
|
|
||||||
RUN apt-get update && apt-get install --no-install-recommends -yqq migraphx miopen-hip && \
|
|
||||||
apt-get clean && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
FROM prod-cpu AS prod-armnn
|
FROM prod-cpu AS prod-armnn
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ class Settings(BaseSettings):
|
|||||||
preload: PreloadModelData | None = None
|
preload: PreloadModelData | None = None
|
||||||
max_batch_size: MaxBatchSize | None = None
|
max_batch_size: MaxBatchSize | None = None
|
||||||
openvino_precision: ModelPrecision = ModelPrecision.FP32
|
openvino_precision: ModelPrecision = ModelPrecision.FP32
|
||||||
rocm_precision: ModelPrecision = ModelPrecision.FP32
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_id(self) -> str:
|
def device_id(self) -> str:
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ _PADDLE_MODELS = {
|
|||||||
|
|
||||||
SUPPORTED_PROVIDERS = [
|
SUPPORTED_PROVIDERS = [
|
||||||
"CUDAExecutionProvider",
|
"CUDAExecutionProvider",
|
||||||
"MIGraphXExecutionProvider",
|
"ROCMExecutionProvider",
|
||||||
"OpenVINOExecutionProvider",
|
"OpenVINOExecutionProvider",
|
||||||
"CoreMLExecutionProvider",
|
"CoreMLExecutionProvider",
|
||||||
"CPUExecutionProvider",
|
"CPUExecutionProvider",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import onnxruntime as ort
|
|||||||
from numpy.typing import NDArray
|
from numpy.typing import NDArray
|
||||||
|
|
||||||
from immich_ml.models.constants import SUPPORTED_PROVIDERS
|
from immich_ml.models.constants import SUPPORTED_PROVIDERS
|
||||||
from immich_ml.schemas import ModelPrecision, SessionNode
|
from immich_ml.schemas import SessionNode
|
||||||
|
|
||||||
from ..config import log, settings
|
from ..config import log, settings
|
||||||
|
|
||||||
@@ -90,17 +90,8 @@ class OrtSession:
|
|||||||
match provider:
|
match provider:
|
||||||
case "CPUExecutionProvider":
|
case "CPUExecutionProvider":
|
||||||
options = {"arena_extend_strategy": "kSameAsRequested"}
|
options = {"arena_extend_strategy": "kSameAsRequested"}
|
||||||
case "CUDAExecutionProvider":
|
case "CUDAExecutionProvider" | "ROCMExecutionProvider":
|
||||||
options = {"arena_extend_strategy": "kSameAsRequested", "device_id": settings.device_id}
|
options = {"arena_extend_strategy": "kSameAsRequested", "device_id": settings.device_id}
|
||||||
case "MIGraphXExecutionProvider":
|
|
||||||
migraphx_dir = self.model_path.parent / "migraphx"
|
|
||||||
# MIGraphX does not create the underlying folder and will crash if it does not exist
|
|
||||||
migraphx_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
options = {
|
|
||||||
"device_id": settings.device_id,
|
|
||||||
"migraphx_model_cache_dir": migraphx_dir.as_posix(),
|
|
||||||
"migraphx_fp16_enable": "1" if settings.rocm_precision == ModelPrecision.FP16 else "0",
|
|
||||||
}
|
|
||||||
case "OpenVINOExecutionProvider":
|
case "OpenVINOExecutionProvider":
|
||||||
openvino_dir = self.model_path.parent / "openvino"
|
openvino_dir = self.model_path.parent / "openvino"
|
||||||
device = f"GPU.{settings.device_id}"
|
device = f"GPU.{settings.device_id}"
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
commit 16839b58d9b3c3162a67ce5d776b36d4d24e801f
|
||||||
|
Author: mertalev <101130780+mertalev@users.noreply.github.com>
|
||||||
|
Date: Wed Mar 5 11:25:38 2025 -0500
|
||||||
|
|
||||||
|
disable algo caching (attributed to @dmnieto in https://github.com/microsoft/onnxruntime/pull/19567)
|
||||||
|
|
||||||
|
diff --git a/onnxruntime/core/providers/rocm/nn/conv.cc b/onnxruntime/core/providers/rocm/nn/conv.cc
|
||||||
|
index d7f47d07a8..4060a2af52 100644
|
||||||
|
--- a/onnxruntime/core/providers/rocm/nn/conv.cc
|
||||||
|
+++ b/onnxruntime/core/providers/rocm/nn/conv.cc
|
||||||
|
@@ -127,7 +127,6 @@ Status Conv<T, NHWC>::UpdateState(OpKernelContext* context, bool bias_expected)
|
||||||
|
|
||||||
|
if (w_dims_changed) {
|
||||||
|
s_.last_w_dims = gsl::make_span(w_dims);
|
||||||
|
- s_.cached_benchmark_fwd_results.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
ORT_RETURN_IF_ERROR(conv_attrs_.ValidateInputShape(X->Shape(), W->Shape(), channels_last, channels_last));
|
||||||
|
@@ -277,35 +276,6 @@ Status Conv<T, NHWC>::UpdateState(OpKernelContext* context, bool bias_expected)
|
||||||
|
HIP_CALL_THROW(hipMalloc(&s_.b_zero, malloc_size));
|
||||||
|
HIP_CALL_THROW(hipMemsetAsync(s_.b_zero, 0, malloc_size, Stream(context)));
|
||||||
|
}
|
||||||
|
-
|
||||||
|
- if (!s_.cached_benchmark_fwd_results.contains(x_dims_miopen)) {
|
||||||
|
- miopenConvAlgoPerf_t perf;
|
||||||
|
- int algo_count = 1;
|
||||||
|
- const ROCMExecutionProvider* rocm_ep = static_cast<const ROCMExecutionProvider*>(this->Info().GetExecutionProvider());
|
||||||
|
- static constexpr int num_algos = MIOPEN_CONVOLUTION_FWD_ALGO_COUNT;
|
||||||
|
- size_t max_ws_size = rocm_ep->GetMiopenConvUseMaxWorkspace() ? GetMaxWorkspaceSize(GetMiopenHandle(context), s_, kAllAlgos, num_algos, rocm_ep->GetDeviceId())
|
||||||
|
- : AlgoSearchWorkspaceSize;
|
||||||
|
- IAllocatorUniquePtr<void> algo_search_workspace = GetTransientScratchBuffer<void>(max_ws_size);
|
||||||
|
- MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionForwardAlgorithm(
|
||||||
|
- GetMiopenHandle(context),
|
||||||
|
- s_.x_tensor,
|
||||||
|
- s_.x_data,
|
||||||
|
- s_.w_desc,
|
||||||
|
- s_.w_data,
|
||||||
|
- s_.conv_desc,
|
||||||
|
- s_.y_tensor,
|
||||||
|
- s_.y_data,
|
||||||
|
- 1, // requestedAlgoCount
|
||||||
|
- &algo_count, // returnedAlgoCount
|
||||||
|
- &perf,
|
||||||
|
- algo_search_workspace.get(),
|
||||||
|
- max_ws_size,
|
||||||
|
- false)); // Do not do exhaustive algo search.
|
||||||
|
- s_.cached_benchmark_fwd_results.insert(x_dims_miopen, {perf.fwd_algo, perf.memory});
|
||||||
|
- }
|
||||||
|
- const auto& perf = s_.cached_benchmark_fwd_results.at(x_dims_miopen);
|
||||||
|
- s_.fwd_algo = perf.fwd_algo;
|
||||||
|
- s_.workspace_bytes = perf.memory;
|
||||||
|
} else {
|
||||||
|
// set Y
|
||||||
|
s_.Y = context->Output(0, TensorShape(s_.y_dims));
|
||||||
|
@@ -319,6 +289,31 @@ Status Conv<T, NHWC>::UpdateState(OpKernelContext* context, bool bias_expected)
|
||||||
|
s_.y_data = reinterpret_cast<HipT*>(s_.Y->MutableData<T>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ miopenConvAlgoPerf_t perf;
|
||||||
|
+ int algo_count = 1;
|
||||||
|
+ const ROCMExecutionProvider* rocm_ep = static_cast<const ROCMExecutionProvider*>(this->Info().GetExecutionProvider());
|
||||||
|
+ static constexpr int num_algos = MIOPEN_CONVOLUTION_FWD_ALGO_COUNT;
|
||||||
|
+ size_t max_ws_size = rocm_ep->GetMiopenConvUseMaxWorkspace() ? GetMaxWorkspaceSize(GetMiopenHandle(context), s_, kAllAlgos, num_algos, rocm_ep->GetDeviceId())
|
||||||
|
+ : AlgoSearchWorkspaceSize;
|
||||||
|
+ IAllocatorUniquePtr<void> algo_search_workspace = GetTransientScratchBuffer<void>(max_ws_size);
|
||||||
|
+ MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionForwardAlgorithm(
|
||||||
|
+ GetMiopenHandle(context),
|
||||||
|
+ s_.x_tensor,
|
||||||
|
+ s_.x_data,
|
||||||
|
+ s_.w_desc,
|
||||||
|
+ s_.w_data,
|
||||||
|
+ s_.conv_desc,
|
||||||
|
+ s_.y_tensor,
|
||||||
|
+ s_.y_data,
|
||||||
|
+ 1, // requestedAlgoCount
|
||||||
|
+ &algo_count, // returnedAlgoCount
|
||||||
|
+ &perf,
|
||||||
|
+ algo_search_workspace.get(),
|
||||||
|
+ max_ws_size,
|
||||||
|
+ false)); // Do not do exhaustive algo search.
|
||||||
|
+ s_.fwd_algo = perf.fwd_algo;
|
||||||
|
+ s_.workspace_bytes = perf.memory;
|
||||||
|
return Status::OK();
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/onnxruntime/core/providers/rocm/nn/conv.h b/onnxruntime/core/providers/rocm/nn/conv.h
|
||||||
|
index bc9846203e..d54218f258 100644
|
||||||
|
--- a/onnxruntime/core/providers/rocm/nn/conv.h
|
||||||
|
+++ b/onnxruntime/core/providers/rocm/nn/conv.h
|
||||||
|
@@ -108,9 +108,6 @@ class lru_unordered_map {
|
||||||
|
list_type lru_list_;
|
||||||
|
};
|
||||||
|
|
||||||
|
-// cached miopen descriptors
|
||||||
|
-constexpr size_t MAX_CACHED_ALGO_PERF_RESULTS = 10000;
|
||||||
|
-
|
||||||
|
template <typename AlgoPerfType>
|
||||||
|
struct MiopenConvState {
|
||||||
|
// if x/w dims changed, update algo and miopenTensors
|
||||||
|
@@ -148,9 +145,6 @@ struct MiopenConvState {
|
||||||
|
decltype(AlgoPerfType().memory) memory;
|
||||||
|
};
|
||||||
|
|
||||||
|
- lru_unordered_map<TensorShapeVector, PerfFwdResultParams, vector_hash> cached_benchmark_fwd_results{MAX_CACHED_ALGO_PERF_RESULTS};
|
||||||
|
- lru_unordered_map<TensorShapeVector, PerfBwdResultParams, vector_hash> cached_benchmark_bwd_results{MAX_CACHED_ALGO_PERF_RESULTS};
|
||||||
|
-
|
||||||
|
// Some properties needed to support asymmetric padded Conv nodes
|
||||||
|
bool post_slicing_required;
|
||||||
|
TensorShapeVector slice_starts;
|
||||||
|
diff --git a/onnxruntime/core/providers/rocm/nn/conv_transpose.cc b/onnxruntime/core/providers/rocm/nn/conv_transpose.cc
|
||||||
|
index 7447113fdf..a662e35b2e 100644
|
||||||
|
--- a/onnxruntime/core/providers/rocm/nn/conv_transpose.cc
|
||||||
|
+++ b/onnxruntime/core/providers/rocm/nn/conv_transpose.cc
|
||||||
|
@@ -76,7 +76,6 @@ Status ConvTranspose<T, NHWC>::DoConvTranspose(OpKernelContext* context, bool dy
|
||||||
|
|
||||||
|
if (w_dims_changed) {
|
||||||
|
s_.last_w_dims = gsl::make_span(w_dims);
|
||||||
|
- s_.cached_benchmark_bwd_results.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
ConvTransposeAttributes::Prepare p;
|
||||||
|
@@ -126,35 +125,29 @@ Status ConvTranspose<T, NHWC>::DoConvTranspose(OpKernelContext* context, bool dy
|
||||||
|
}
|
||||||
|
|
||||||
|
y_data = reinterpret_cast<HipT*>(p.Y->MutableData<T>());
|
||||||
|
-
|
||||||
|
- if (!s_.cached_benchmark_bwd_results.contains(x_dims)) {
|
||||||
|
- IAllocatorUniquePtr<void> algo_search_workspace = GetScratchBuffer<void>(AlgoSearchWorkspaceSize, context->GetComputeStream());
|
||||||
|
-
|
||||||
|
- miopenConvAlgoPerf_t perf;
|
||||||
|
- int algo_count = 1;
|
||||||
|
- MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionBackwardDataAlgorithm(
|
||||||
|
- GetMiopenHandle(context),
|
||||||
|
- s_.x_tensor,
|
||||||
|
- x_data,
|
||||||
|
- s_.w_desc,
|
||||||
|
- w_data,
|
||||||
|
- s_.conv_desc,
|
||||||
|
- s_.y_tensor,
|
||||||
|
- y_data,
|
||||||
|
- 1,
|
||||||
|
- &algo_count,
|
||||||
|
- &perf,
|
||||||
|
- algo_search_workspace.get(),
|
||||||
|
- AlgoSearchWorkspaceSize,
|
||||||
|
- false));
|
||||||
|
- s_.cached_benchmark_bwd_results.insert(x_dims, {perf.bwd_data_algo, perf.memory});
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- const auto& perf = s_.cached_benchmark_bwd_results.at(x_dims);
|
||||||
|
- s_.bwd_data_algo = perf.bwd_data_algo;
|
||||||
|
- s_.workspace_bytes = perf.memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ IAllocatorUniquePtr<void> algo_search_workspace = GetScratchBuffer<void>(AlgoSearchWorkspaceSize, context->GetComputeStream());
|
||||||
|
+ miopenConvAlgoPerf_t perf;
|
||||||
|
+ int algo_count = 1;
|
||||||
|
+ MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionBackwardDataAlgorithm(
|
||||||
|
+ GetMiopenHandle(context),
|
||||||
|
+ s_.x_tensor,
|
||||||
|
+ x_data,
|
||||||
|
+ s_.w_desc,
|
||||||
|
+ w_data,
|
||||||
|
+ s_.conv_desc,
|
||||||
|
+ s_.y_tensor,
|
||||||
|
+ y_data,
|
||||||
|
+ 1,
|
||||||
|
+ &algo_count,
|
||||||
|
+ &perf,
|
||||||
|
+ algo_search_workspace.get(),
|
||||||
|
+ AlgoSearchWorkspaceSize,
|
||||||
|
+ false));
|
||||||
|
+ s_.bwd_data_algo = perf.bwd_data_algo;
|
||||||
|
+ s_.workspace_bytes = perf.memory;
|
||||||
|
+
|
||||||
|
// The following block will be executed in case there has been no change in the shapes of the
|
||||||
|
// input and the filter compared to the previous run
|
||||||
|
if (!y_data) {
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
diff --git a/dockerfiles/scripts/install_common_deps.sh b/dockerfiles/scripts/install_common_deps.sh
|
||||||
|
index bbb672a99e..0dc652fbda 100644
|
||||||
|
--- a/dockerfiles/scripts/install_common_deps.sh
|
||||||
|
+++ b/dockerfiles/scripts/install_common_deps.sh
|
||||||
|
@@ -8,16 +8,23 @@ apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
curl \
|
||||||
|
libcurl4-openssl-dev \
|
||||||
|
libssl-dev \
|
||||||
|
- python3-dev
|
||||||
|
+ python3-dev \
|
||||||
|
+ ccache
|
||||||
|
|
||||||
|
# Dependencies: conda
|
||||||
|
-wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-4.5.11-Linux-x86_64.sh -O ~/miniconda.sh --no-check-certificate && /bin/bash ~/miniconda.sh -b -p /opt/miniconda
|
||||||
|
+wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-py312_25.9.1-1-Linux-x86_64.sh -O ~/miniconda.sh && /bin/bash ~/miniconda.sh -b -p /opt/miniconda
|
||||||
|
rm ~/miniconda.sh
|
||||||
|
/opt/miniconda/bin/conda clean -ya
|
||||||
|
|
||||||
|
-pip install numpy
|
||||||
|
-pip install packaging
|
||||||
|
-pip install "wheel>=0.35.1"
|
||||||
|
+# Dependencies: venv and packages
|
||||||
|
+/opt/miniconda/bin/python3 -m venv /opt/rocm-venv
|
||||||
|
+/opt/rocm-venv/bin/pip install --no-cache-dir --upgrade pip
|
||||||
|
+/opt/rocm-venv/bin/pip install --no-cache-dir \
|
||||||
|
+ "numpy==2.3.4" \
|
||||||
|
+ "packaging==25.0" \
|
||||||
|
+ "wheel==0.45.1" \
|
||||||
|
+ "setuptools==80.9.0"
|
||||||
|
+
|
||||||
|
rm -rf /opt/miniconda/pkgs
|
||||||
|
|
||||||
|
# Dependencies: cmake
|
||||||
@@ -49,10 +49,10 @@ dev = ["locust>=2.15.1", { include-group = "test" }, { include-group = "lint" }]
|
|||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
cpu = ["onnxruntime>=1.23.2,<2"]
|
cpu = ["onnxruntime>=1.23.2,<2"]
|
||||||
cuda = ["onnxruntime-gpu>=1.23.2,<2"]
|
cuda = ["onnxruntime-gpu>=1.23.2,<2"]
|
||||||
openvino = ["onnxruntime-openvino>=1.24.1,<2"]
|
openvino = ["onnxruntime-openvino>=1.23.0,<2"]
|
||||||
armnn = ["onnxruntime>=1.23.2,<2"]
|
armnn = ["onnxruntime>=1.23.2,<2"]
|
||||||
rknn = ["onnxruntime>=1.23.2,<2", "rknn-toolkit-lite2>=2.3.0,<3"]
|
rknn = ["onnxruntime>=1.23.2,<2", "rknn-toolkit-lite2>=2.3.0,<3"]
|
||||||
rocm = ["onnxruntime-migraphx>=1.23.2,<2"]
|
rocm = []
|
||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
compile-bytecode = true
|
compile-bytecode = true
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ class TestOrtSession:
|
|||||||
OV_EP = ["OpenVINOExecutionProvider", "CPUExecutionProvider"]
|
OV_EP = ["OpenVINOExecutionProvider", "CPUExecutionProvider"]
|
||||||
CUDA_EP_OUT_OF_ORDER = ["CPUExecutionProvider", "CUDAExecutionProvider"]
|
CUDA_EP_OUT_OF_ORDER = ["CPUExecutionProvider", "CUDAExecutionProvider"]
|
||||||
TRT_EP = ["TensorrtExecutionProvider", "CUDAExecutionProvider", "CPUExecutionProvider"]
|
TRT_EP = ["TensorrtExecutionProvider", "CUDAExecutionProvider", "CPUExecutionProvider"]
|
||||||
ROCM_EP = ["MIGraphXExecutionProvider", "CPUExecutionProvider"]
|
ROCM_EP = ["ROCMExecutionProvider", "CPUExecutionProvider"]
|
||||||
COREML_EP = ["CoreMLExecutionProvider", "CPUExecutionProvider"]
|
COREML_EP = ["CoreMLExecutionProvider", "CPUExecutionProvider"]
|
||||||
|
|
||||||
@pytest.mark.providers(CPU_EP)
|
@pytest.mark.providers(CPU_EP)
|
||||||
@@ -289,38 +289,12 @@ class TestOrtSession:
|
|||||||
|
|
||||||
assert session.provider_options == [{"arena_extend_strategy": "kSameAsRequested", "device_id": "1"}]
|
assert session.provider_options == [{"arena_extend_strategy": "kSameAsRequested", "device_id": "1"}]
|
||||||
|
|
||||||
def test_sets_provider_options_for_rocm(self, mocker: MockerFixture) -> None:
|
def test_sets_provider_options_for_rocm(self) -> None:
|
||||||
model_path = "/cache/ViT-B-32__openai/textual/model.onnx"
|
|
||||||
os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1"
|
os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1"
|
||||||
mkdir = mocker.patch("immich_ml.sessions.ort.Path.mkdir")
|
|
||||||
|
|
||||||
session = OrtSession(model_path, providers=["MIGraphXExecutionProvider"])
|
session = OrtSession("ViT-B-32__openai", providers=["ROCMExecutionProvider"])
|
||||||
|
|
||||||
assert session.provider_options == [
|
assert session.provider_options == [{"arena_extend_strategy": "kSameAsRequested", "device_id": "1"}]
|
||||||
{
|
|
||||||
"device_id": "1",
|
|
||||||
"migraphx_model_cache_dir": "/cache/ViT-B-32__openai/textual/migraphx",
|
|
||||||
"migraphx_fp16_enable": "0",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
mkdir.assert_called_once_with(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
def test_sets_rocm_to_fp16_if_enabled(self, path: mock.Mock, mocker: MockerFixture) -> None:
|
|
||||||
model_path = "/cache/ViT-B-32__openai/textual/model.onnx"
|
|
||||||
os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1"
|
|
||||||
mocker.patch.object(settings, "rocm_precision", ModelPrecision.FP16)
|
|
||||||
mkdir = mocker.patch("immich_ml.sessions.ort.Path.mkdir")
|
|
||||||
|
|
||||||
session = OrtSession(model_path, providers=["MIGraphXExecutionProvider"])
|
|
||||||
|
|
||||||
assert session.provider_options == [
|
|
||||||
{
|
|
||||||
"device_id": "1",
|
|
||||||
"migraphx_model_cache_dir": "/cache/ViT-B-32__openai/textual/migraphx",
|
|
||||||
"migraphx_fp16_enable": "1",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
mkdir.assert_called_once_with(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
def test_sets_provider_options_kwarg(self) -> None:
|
def test_sets_provider_options_kwarg(self) -> None:
|
||||||
session = OrtSession(
|
session = OrtSession(
|
||||||
|
|||||||
Generated
+66
-77
@@ -262,6 +262,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coloredlogs"
|
||||||
|
version = "15.0.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "humanfriendly" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorlog"
|
name = "colorlog"
|
||||||
version = "6.9.0"
|
version = "6.9.0"
|
||||||
@@ -874,6 +886,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" },
|
{ url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humanfriendly"
|
||||||
|
version = "10.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pyreadline3", marker = "sys_platform == 'win32'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.11"
|
version = "3.11"
|
||||||
@@ -936,9 +960,6 @@ rknn = [
|
|||||||
{ name = "onnxruntime" },
|
{ name = "onnxruntime" },
|
||||||
{ name = "rknn-toolkit-lite2" },
|
{ name = "rknn-toolkit-lite2" },
|
||||||
]
|
]
|
||||||
rocm = [
|
|
||||||
{ name = "onnxruntime-migraphx" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
@@ -992,8 +1013,7 @@ requires-dist = [
|
|||||||
{ name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" },
|
{ name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" },
|
||||||
{ name = "onnxruntime", marker = "extra == 'rknn'", specifier = ">=1.23.2,<2" },
|
{ name = "onnxruntime", marker = "extra == 'rknn'", specifier = ">=1.23.2,<2" },
|
||||||
{ name = "onnxruntime-gpu", marker = "extra == 'cuda'", specifier = ">=1.23.2,<2" },
|
{ name = "onnxruntime-gpu", marker = "extra == 'cuda'", specifier = ">=1.23.2,<2" },
|
||||||
{ name = "onnxruntime-migraphx", marker = "extra == 'rocm'", specifier = ">=1.23.2,<2" },
|
{ name = "onnxruntime-openvino", marker = "extra == 'openvino'", specifier = ">=1.23.0,<2" },
|
||||||
{ name = "onnxruntime-openvino", marker = "extra == 'openvino'", specifier = ">=1.24.1,<2" },
|
|
||||||
{ name = "opencv-python-headless", specifier = ">=4.7.0.72,<5.0" },
|
{ name = "opencv-python-headless", specifier = ">=4.7.0.72,<5.0" },
|
||||||
{ name = "orjson", specifier = ">=3.9.5" },
|
{ name = "orjson", specifier = ">=3.9.5" },
|
||||||
{ name = "pillow", specifier = ">=12.1.1,<12.2" },
|
{ name = "pillow", specifier = ">=12.1.1,<12.2" },
|
||||||
@@ -1409,55 +1429,32 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "msgpack"
|
name = "msgpack"
|
||||||
version = "1.1.2"
|
version = "1.0.7"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/c2/d5/5662032db1571110b5b51647aed4b56dfbd01bfae789fa566a2be1f385d1/msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87", size = 166311, upload-time = "2023-09-28T13:20:36.726Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2e86a607e558d22985d856948c12a3fa7b42efad264dca8a3ebbcfa2735d786c", size = 82271, upload-time = "2025-10-08T09:14:49.967Z" },
|
{ url = "https://files.pythonhosted.org/packages/f9/b3/309de40dc7406b7f3492332c5ee2b492a593c2a9bb97ea48ebf2f5279999/msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84", size = 305096, upload-time = "2023-09-28T13:18:49.678Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0", size = 84914, upload-time = "2025-10-08T09:14:50.958Z" },
|
{ url = "https://files.pythonhosted.org/packages/15/56/a677cd761a2cefb2e3ffe7e684633294dccb161d78e8ea6da9277e45b4a2/msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93", size = 235210, upload-time = "2023-09-28T13:18:51.039Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/71/46/b817349db6886d79e57a966346cf0902a426375aadc1e8e7a86a75e22f19/msgpack-1.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61c8aa3bd513d87c72ed0b37b53dd5c5a0f58f2ff9f26e1555d3bd7948fb7296", size = 416962, upload-time = "2025-10-08T09:14:51.997Z" },
|
{ url = "https://files.pythonhosted.org/packages/f5/4e/1ab4a982cbd90f988e49f849fc1212f2c04a59870c59daabf8950617e2aa/msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8", size = 231952, upload-time = "2023-09-28T13:18:52.871Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef", size = 426183, upload-time = "2025-10-08T09:14:53.477Z" },
|
{ url = "https://files.pythonhosted.org/packages/6d/74/bd02044eb628c7361ad2bd8c1a6147af5c6c2bbceb77b3b1da20f4a8a9c5/msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46", size = 549511, upload-time = "2023-09-28T13:18:54.422Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/25/98/6a19f030b3d2ea906696cedd1eb251708e50a5891d0978b012cb6107234c/msgpack-1.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7bc8813f88417599564fafa59fd6f95be417179f76b40325b500b3c98409757c", size = 411454, upload-time = "2025-10-08T09:14:54.648Z" },
|
{ url = "https://files.pythonhosted.org/packages/df/09/dee50913ba5cc047f7fd7162f09453a676e7935c84b3bf3a398e12108677/msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b", size = 557980, upload-time = "2023-09-28T13:18:56.058Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b7/cd/9098fcb6adb32187a70b7ecaabf6339da50553351558f37600e53a4a2a23/msgpack-1.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bafca952dc13907bdfdedfc6a5f579bf4f292bdd506fadb38389afa3ac5b208e", size = 422341, upload-time = "2025-10-08T09:14:56.328Z" },
|
{ url = "https://files.pythonhosted.org/packages/26/a5/78a7d87f5f8ffe4c32167afa15d4957db649bab4822f909d8d765339bbab/msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e", size = 545547, upload-time = "2023-09-28T13:18:57.396Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e6/ae/270cecbcf36c1dc85ec086b33a51a4d7d08fc4f404bdbc15b582255d05ff/msgpack-1.1.2-cp311-cp311-win32.whl", hash = "sha256:602b6740e95ffc55bfb078172d279de3773d7b7db1f703b2f1323566b878b90e", size = 64747, upload-time = "2025-10-08T09:14:57.882Z" },
|
{ url = "https://files.pythonhosted.org/packages/d4/53/698c10913947f97f6fe7faad86a34e6aa1b66cea2df6f99105856bd346d9/msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002", size = 554669, upload-time = "2023-09-28T13:18:58.957Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68", size = 71633, upload-time = "2025-10-08T09:14:59.177Z" },
|
{ url = "https://files.pythonhosted.org/packages/f5/3f/9730c6cb574b15d349b80cd8523a7df4b82058528339f952ea1c32ac8a10/msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c", size = 583353, upload-time = "2023-09-28T13:19:01.186Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/73/4d/7c4e2b3d9b1106cd0aa6cb56cc57c6267f59fa8bfab7d91df5adc802c847/msgpack-1.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:86f8136dfa5c116365a8a651a7d7484b65b13339731dd6faebb9a0242151c406", size = 64755, upload-time = "2025-10-08T09:15:00.48Z" },
|
{ url = "https://files.pythonhosted.org/packages/4c/bc/dc184d943692671149848438fb3bed3a3de288ce7998cb91bc98f40f201b/msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e", size = 557455, upload-time = "2023-09-28T13:19:03.201Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" },
|
{ url = "https://files.pythonhosted.org/packages/cf/7b/1bc69d4a56c8d2f4f2dfbe4722d40344af9a85b6fb3b09cfb350ba6a42f6/msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1", size = 216367, upload-time = "2023-09-28T13:19:04.554Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" },
|
{ url = "https://files.pythonhosted.org/packages/b4/3d/c8dd23050eefa3d9b9c5b8329ed3308c2f2f80f65825e9ea4b7fa621cdab/msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82", size = 222860, upload-time = "2023-09-28T13:19:06.397Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" },
|
{ url = "https://files.pythonhosted.org/packages/d7/47/20dff6b4512cf3575550c8801bc53fe7d540f4efef9c5c37af51760fcdcf/msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b", size = 305759, upload-time = "2023-09-28T13:19:08.148Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" },
|
{ url = "https://files.pythonhosted.org/packages/6f/8a/34f1726d2c9feccec3d946776e9bce8f20ae09d8b91899fc20b296c942af/msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4", size = 235330, upload-time = "2023-09-28T13:19:09.417Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" },
|
{ url = "https://files.pythonhosted.org/packages/9c/f6/e64c72577d6953789c3cb051b059a4b56317056b3c65013952338ed8a34e/msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee", size = 232537, upload-time = "2023-09-28T13:19:10.898Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" },
|
{ url = "https://files.pythonhosted.org/packages/89/75/1ed3a96e12941873fd957e016cc40c0c178861a872bd45e75b9a188eb422/msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5", size = 546561, upload-time = "2023-09-28T13:19:12.779Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" },
|
{ url = "https://files.pythonhosted.org/packages/e5/0a/c6a1390f9c6a31da0fecbbfdb86b1cb39ad302d9e24f9cca3d9e14c364f0/msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672", size = 559009, upload-time = "2023-09-28T13:19:14.373Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" },
|
{ url = "https://files.pythonhosted.org/packages/a5/74/99f6077754665613ea1f37b3d91c10129f6976b7721ab4d0973023808e5a/msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075", size = 543882, upload-time = "2023-09-28T13:19:16.277Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" },
|
{ url = "https://files.pythonhosted.org/packages/9c/7e/dc0dc8de2bf27743b31691149258f9b1bd4bf3c44c105df3df9b97081cd1/msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba", size = 546949, upload-time = "2023-09-28T13:19:18.114Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf", size = 81212, upload-time = "2025-10-08T09:15:14.552Z" },
|
{ url = "https://files.pythonhosted.org/packages/78/61/91bae9474def032f6c333d62889bbeda9e1554c6b123375ceeb1767efd78/msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c", size = 579836, upload-time = "2023-09-28T13:19:19.729Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7", size = 84315, upload-time = "2025-10-08T09:15:15.543Z" },
|
{ url = "https://files.pythonhosted.org/packages/5d/4d/d98592099d4f18945f89cf3e634dc0cb128bb33b1b93f85a84173d35e181/msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5", size = 556587, upload-time = "2023-09-28T13:19:21.666Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999", size = 412721, upload-time = "2025-10-08T09:15:16.567Z" },
|
{ url = "https://files.pythonhosted.org/packages/5e/44/6556ffe169bf2c0e974e2ea25fb82a7e55ebcf52a81b03a5e01820de5f84/msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9", size = 216509, upload-time = "2023-09-28T13:19:23.161Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e", size = 424657, upload-time = "2025-10-08T09:15:17.825Z" },
|
{ url = "https://files.pythonhosted.org/packages/dc/c1/63903f30d51d165e132e5221a2a4a1bbfab7508b68131c871d70bffac78a/msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf", size = 223287, upload-time = "2023-09-28T13:19:25.097Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162", size = 402668, upload-time = "2025-10-08T09:15:19.003Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794", size = 419040, upload-time = "2025-10-08T09:15:20.183Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c", size = 65037, upload-time = "2025-10-08T09:15:21.416Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9", size = 72631, upload-time = "2025-10-08T09:15:22.431Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118, upload-time = "2025-10-08T09:15:23.402Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/22/71/201105712d0a2ff07b7873ed3c220292fb2ea5120603c00c4b634bcdafb3/msgpack-1.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e23ce8d5f7aa6ea6d2a2b326b4ba46c985dbb204523759984430db7114f8aa00", size = 81127, upload-time = "2025-10-08T09:15:24.408Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1b/9f/38ff9e57a2eade7bf9dfee5eae17f39fc0e998658050279cbb14d97d36d9/msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c15b7d74c939ebe620dd8e559384be806204d73b4f9356320632d783d1f7939", size = 84981, upload-time = "2025-10-08T09:15:25.812Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8e/a9/3536e385167b88c2cc8f4424c49e28d49a6fc35206d4a8060f136e71f94c/msgpack-1.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e2cb7b9031568a2a5c73aa077180f93dd2e95b4f8d3b8e14a73ae94a9e667e", size = 411885, upload-time = "2025-10-08T09:15:27.22Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2f/40/dc34d1a8d5f1e51fc64640b62b191684da52ca469da9cd74e84936ffa4a6/msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:180759d89a057eab503cf62eeec0aa61c4ea1200dee709f3a8e9397dbb3b6931", size = 419658, upload-time = "2025-10-08T09:15:28.4Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3b/ef/2b92e286366500a09a67e03496ee8b8ba00562797a52f3c117aa2b29514b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:04fb995247a6e83830b62f0b07bf36540c213f6eac8e851166d8d86d83cbd014", size = 403290, upload-time = "2025-10-08T09:15:29.764Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/78/90/e0ea7990abea5764e4655b8177aa7c63cdfa89945b6e7641055800f6c16b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8e22ab046fa7ede9e36eeb4cfad44d46450f37bb05d5ec482b02868f451c95e2", size = 415234, upload-time = "2025-10-08T09:15:31.022Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/72/4e/9390aed5db983a2310818cd7d3ec0aecad45e1f7007e0cda79c79507bb0d/msgpack-1.1.2-cp314-cp314-win32.whl", hash = "sha256:80a0ff7d4abf5fecb995fcf235d4064b9a9a8a40a3ab80999e6ac1e30b702717", size = 66391, upload-time = "2025-10-08T09:15:32.265Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:9ade919fac6a3e7260b7f64cea89df6bec59104987cbea34d34a2fa15d74310b", size = 73787, upload-time = "2025-10-08T09:15:33.219Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6a/b0/9d9f667ab48b16ad4115c1935d94023b82b3198064cb84a123e97f7466c1/msgpack-1.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:59415c6076b1e30e563eb732e23b994a61c159cec44deaf584e5cc1dd662f2af", size = 66453, upload-time = "2025-10-08T09:15:34.225Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/16/67/93f80545eb1792b61a217fa7f06d5e5cb9e0055bed867f43e2b8e012e137/msgpack-1.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:897c478140877e5307760b0ea66e0932738879e7aa68144d9b78ea4c8302a84a", size = 85264, upload-time = "2025-10-08T09:15:35.61Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/87/1c/33c8a24959cf193966ef11a6f6a2995a65eb066bd681fd085afd519a57ce/msgpack-1.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a668204fa43e6d02f89dbe79a30b0d67238d9ec4c5bd8a940fc3a004a47b721b", size = 89076, upload-time = "2025-10-08T09:15:36.619Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fc/6b/62e85ff7193663fbea5c0254ef32f0c77134b4059f8da89b958beb7696f3/msgpack-1.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5559d03930d3aa0f3aacb4c42c776af1a2ace2611871c84a75afe436695e6245", size = 435242, upload-time = "2025-10-08T09:15:37.647Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c1/47/5c74ecb4cc277cf09f64e913947871682ffa82b3b93c8dad68083112f412/msgpack-1.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70c5a7a9fea7f036b716191c29047374c10721c389c21e9ffafad04df8c52c90", size = 432509, upload-time = "2025-10-08T09:15:38.794Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/24/a4/e98ccdb56dc4e98c929a3f150de1799831c0a800583cde9fa022fa90602d/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2cb069d8b981abc72b41aea1c580ce92d57c673ec61af4c500153a626cb9e20", size = 415957, upload-time = "2025-10-08T09:15:40.238Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/da/28/6951f7fb67bc0a4e184a6b38ab71a92d9ba58080b27a77d3e2fb0be5998f/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d62ce1f483f355f61adb5433ebfd8868c5f078d1a52d042b0a998682b4fa8c27", size = 422910, upload-time = "2025-10-08T09:15:41.505Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f0/03/42106dcded51f0a0b5284d3ce30a671e7bd3f7318d122b2ead66ad289fed/msgpack-1.1.2-cp314-cp314t-win32.whl", hash = "sha256:1d1418482b1ee984625d88aa9585db570180c286d942da463533b238b98b812b", size = 75197, upload-time = "2025-10-08T09:15:42.954Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/15/86/d0071e94987f8db59d4eeb386ddc64d0bb9b10820a8d82bcd3e53eeb2da6/msgpack-1.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5a46bf7e831d09470ad92dff02b8b1ac92175ca36b087f904a0519857c6be3ff", size = 85772, upload-time = "2025-10-08T09:15:43.954Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/81/f2/08ace4142eb281c12701fc3b93a10795e4d4dc7f753911d836675050f886/msgpack-1.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d99ef64f349d5ec3293688e91486c5fdb925ed03807f64d98d205d2713c60b46", size = 70868, upload-time = "2025-10-08T09:15:44.959Z" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1704,29 +1701,12 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/6a/94/a3b20276261f5e64dbd72bda656af988282cff01f18c2685953600e2f810/onnxruntime_gpu-1.24.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2cee7e12b0f4813c62f9a48df83fd01d066cc970400c832252cf3c155a6957", size = 252633096, upload-time = "2026-02-05T17:24:53.248Z" },
|
{ url = "https://files.pythonhosted.org/packages/6a/94/a3b20276261f5e64dbd72bda656af988282cff01f18c2685953600e2f810/onnxruntime_gpu-1.24.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2cee7e12b0f4813c62f9a48df83fd01d066cc970400c832252cf3c155a6957", size = 252633096, upload-time = "2026-02-05T17:24:53.248Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "onnxruntime-migraphx"
|
|
||||||
version = "1.24.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "flatbuffers" },
|
|
||||||
{ name = "numpy" },
|
|
||||||
{ name = "packaging" },
|
|
||||||
{ name = "protobuf" },
|
|
||||||
{ name = "sympy" },
|
|
||||||
]
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/dd/da/ca7ebc1a8d1193c97ceb9a05fad50f675eb955dc51beb7eb9ba89c8e7db0/onnxruntime_migraphx-1.24.2-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:a2b434fb8880cac2b268950bdf279f33741d29c1f1c5461d27af835e8e288043", size = 20339710, upload-time = "2026-02-21T07:25:13.17Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fa/2e/8c83ec45a9365b4256495ca55eea30da7f03b02177b6da423c7da1ff5f6a/onnxruntime_migraphx-1.24.2-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:ec814818da952bda3062e26f56c88bb713c00491ef91f86716c8d7346f9bc31b", size = 20341883, upload-time = "2026-02-21T07:25:17.86Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9f/52/4776ac68dbc46ca02c9a14cc9e5c496017f47a18cedf606cc38f4911b96a/onnxruntime_migraphx-1.24.2-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:20e497538362170af639b03a40249d7ed61b873ac354f20d732b90252206e320", size = 20342422, upload-time = "2026-02-21T07:25:22.526Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/76/44/db9035204a3363f9c0a4822c68e9a7520c13ef8d261f96b89b1375106dab/onnxruntime_migraphx-1.24.2-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:9d7f1b1a2b9651143a2080b4f42ee99eead02023de1855d1b8a02199a9c179aa", size = 20343783, upload-time = "2026-02-21T07:25:29.155Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "onnxruntime-openvino"
|
name = "onnxruntime-openvino"
|
||||||
version = "1.24.1"
|
version = "1.23.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "coloredlogs" },
|
||||||
{ name = "flatbuffers" },
|
{ name = "flatbuffers" },
|
||||||
{ name = "numpy" },
|
{ name = "numpy" },
|
||||||
{ name = "packaging" },
|
{ name = "packaging" },
|
||||||
@@ -1734,12 +1714,12 @@ dependencies = [
|
|||||||
{ name = "sympy" },
|
{ name = "sympy" },
|
||||||
]
|
]
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/99/16/69ca742f0b65c40d4de3ff44bb6abc23c47b23e932bc901116176ae69922/onnxruntime_openvino-1.24.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:3007c803634cc69c6d52af1dea7ce729d9bb62b9a11070fd2f959119199007a8", size = 84430935, upload-time = "2026-02-26T13:44:32.193Z" },
|
{ url = "https://files.pythonhosted.org/packages/5a/10/adcd4ac68ffc8dee003553125ef5c091be822e2d7c1077d0bb85690baa9c/onnxruntime_openvino-1.23.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:91938837e6e92e30c63d12fad68a8a4959c40d2eade2bd60f38bdd5b6392f8d3", size = 70481480, upload-time = "2025-10-14T15:19:45.882Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/aa/73/619bb416bbfc40aebdd493fd6800d2637359294fe683d8a6bae3ff8d869a/onnxruntime_openvino-1.24.1-cp311-cp311-win_amd64.whl", hash = "sha256:8042698232bf67f1f6b219c2b07728d7ae7ddff17d8524588de3675480609aef", size = 13655357, upload-time = "2026-02-26T13:44:35.555Z" },
|
{ url = "https://files.pythonhosted.org/packages/97/95/25f28d6fecf300aa0af393e96af9e00cc676e5dab650ab84f2122610df50/onnxruntime_openvino-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:8f05d2d6a804fb70d3f4329d777ac62439773dcc2df827dd5f42644b10bf1fea", size = 13117353, upload-time = "2025-10-14T15:19:49.014Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/50/cf/17ba72de2df0fcba349937d2788f154397bbc2d1a2d67772a97e26f6bc5f/onnxruntime_openvino-1.24.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d617fac2f59a6ab5ea59a788c3e1592240a129642519aaeaa774761dfe35150e", size = 84433207, upload-time = "2026-02-26T13:44:41.395Z" },
|
{ url = "https://files.pythonhosted.org/packages/42/0c/8d97419dfeedf419c5fe5293f3dbc59284855a63ad22e71f46c0010c9dc4/onnxruntime_openvino-1.23.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b963ea19bf9856f3d6b2f719d451f2eeae482a8f69c729906465aa4f27f4d39c", size = 70483359, upload-time = "2025-10-14T15:19:52.88Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/59/37/d301f2c68b19a9485ed5db3047e0fb52478f3e73eb08c7d2a7c61be7cc1c/onnxruntime_openvino-1.24.1-cp312-cp312-win_amd64.whl", hash = "sha256:f186335a9c9b255633275290da7521d3d4d14c7773fee3127bfa040234d3fa5a", size = 13658075, upload-time = "2026-02-26T13:44:44.905Z" },
|
{ url = "https://files.pythonhosted.org/packages/29/30/ff6111b16ffb4187c462824aa4e95acc20fdd90f856d44a339d56c6dacd6/onnxruntime_openvino-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:937e52657f94c56990a6e5bd4c3705bd6e970834c7c94e23d300dde6848f2889", size = 13117933, upload-time = "2025-10-14T15:19:58.319Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/08/07/f225999919f56506b603aaa3ff837ad563ab26f86906ed7fa7e5abcd849e/onnxruntime_openvino-1.24.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:2c3bb73e68ac27f4891af8a595c1faf574ec68b772e6583c90a0b997a1822782", size = 84433183, upload-time = "2026-02-26T13:44:50.254Z" },
|
{ url = "https://files.pythonhosted.org/packages/ce/48/e42f618a8ec5fcf825fed4fdc8125f7105256cc6020b84567ecb88d5e2b7/onnxruntime_openvino-1.23.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:2e93b9a8323e196b7433866054a59260f2206ab6fb0e7223dda91da71f1db8c5", size = 70483088, upload-time = "2025-10-14T15:20:02.425Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3e/92/46ae2cd565961a89189900f385bb2f13a9fa731ea4674001d23720fbb1e0/onnxruntime_openvino-1.24.1-cp313-cp313-win_amd64.whl", hash = "sha256:434bf49aa71393c577a456c9d76c98e6d6958a833fa0876793e3d5437b5a511a", size = 13658485, upload-time = "2026-02-26T13:44:53.889Z" },
|
{ url = "https://files.pythonhosted.org/packages/4a/f9/a531dc497dc113dc14df9a9de5aacb1676cadebc3ec6cc7cd3ca65cb3db0/onnxruntime_openvino-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:0ebbf70929de4ce269371cb255536bbedef588932d744da0b40e66c38a620f35", size = 13118206, upload-time = "2025-10-14T15:20:05.587Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2179,6 +2159,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/39/92/8486ede85fcc088f1b3dba4ce92dd29d126fd96b0008ea213167940a2475/pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb", size = 103139, upload-time = "2023-07-30T15:06:59.829Z" },
|
{ url = "https://files.pythonhosted.org/packages/39/92/8486ede85fcc088f1b3dba4ce92dd29d126fd96b0008ea213167940a2475/pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb", size = 103139, upload-time = "2023-07-30T15:06:59.829Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyreadline3"
|
||||||
|
version = "3.4.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d7/86/3d61a61f36a0067874a00cb4dceb9028d34b6060e47828f7fc86fb9f7ee9/pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae", size = 86465, upload-time = "2022-01-24T20:05:11.66Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/56/fc/a3c13ded7b3057680c8ae95a9b6cc83e63657c38e0005c400a5d018a33a7/pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb", size = 95203, upload-time = "2022-01-24T20:05:10.442Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "9.0.2"
|
version = "9.0.2"
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ private open class LocalImagesPigeonCodec : StandardMessageCodec() {
|
|||||||
|
|
||||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||||
interface LocalImageApi {
|
interface LocalImageApi {
|
||||||
fun requestImage(assetId: String, requestId: Long, width: Long, height: Long, isVideo: Boolean, preferEncoded: Boolean, callback: (Result<Map<String, Long>?>) -> Unit)
|
fun requestImage(assetId: String, requestId: Long, width: Long, height: Long, isVideo: Boolean, callback: (Result<Map<String, Long>?>) -> Unit)
|
||||||
fun cancelRequest(requestId: Long)
|
fun cancelRequest(requestId: Long)
|
||||||
fun getThumbhash(thumbhash: String, callback: (Result<Map<String, Long>>) -> Unit)
|
fun getThumbhash(thumbhash: String, callback: (Result<Map<String, Long>>) -> Unit)
|
||||||
|
|
||||||
@@ -82,8 +82,7 @@ interface LocalImageApi {
|
|||||||
val widthArg = args[2] as Long
|
val widthArg = args[2] as Long
|
||||||
val heightArg = args[3] as Long
|
val heightArg = args[3] as Long
|
||||||
val isVideoArg = args[4] as Boolean
|
val isVideoArg = args[4] as Boolean
|
||||||
val preferEncodedArg = args[5] as Boolean
|
api.requestImage(assetIdArg, requestIdArg, widthArg, heightArg, isVideoArg) { result: Result<Map<String, Long>?> ->
|
||||||
api.requestImage(assetIdArg, requestIdArg, widthArg, heightArg, isVideoArg, preferEncodedArg) { result: Result<Map<String, Long>?> ->
|
|
||||||
val error = result.exceptionOrNull()
|
val error = result.exceptionOrNull()
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
reply.reply(LocalImagesPigeonUtils.wrapError(error))
|
reply.reply(LocalImagesPigeonUtils.wrapError(error))
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import android.util.Size
|
|||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import app.alextran.immich.NativeBuffer
|
import app.alextran.immich.NativeBuffer
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
import java.io.IOException
|
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.Priority
|
import com.bumptech.glide.Priority
|
||||||
@@ -100,17 +99,12 @@ class LocalImagesImpl(context: Context) : LocalImageApi {
|
|||||||
width: Long,
|
width: Long,
|
||||||
height: Long,
|
height: Long,
|
||||||
isVideo: Boolean,
|
isVideo: Boolean,
|
||||||
preferEncoded: Boolean,
|
|
||||||
callback: (Result<Map<String, Long>?>) -> Unit
|
callback: (Result<Map<String, Long>?>) -> Unit
|
||||||
) {
|
) {
|
||||||
val signal = CancellationSignal()
|
val signal = CancellationSignal()
|
||||||
val task = threadPool.submit {
|
val task = threadPool.submit {
|
||||||
try {
|
try {
|
||||||
if (preferEncoded) {
|
getThumbnailBufferInternal(assetId, width, height, isVideo, callback, signal)
|
||||||
getEncodedImageInternal(assetId, callback, signal)
|
|
||||||
} else {
|
|
||||||
getThumbnailBufferInternal(assetId, width, height, isVideo, callback, signal)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
when (e) {
|
when (e) {
|
||||||
is OperationCanceledException -> callback(CANCELLED)
|
is OperationCanceledException -> callback(CANCELLED)
|
||||||
@@ -139,35 +133,6 @@ class LocalImagesImpl(context: Context) : LocalImageApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getEncodedImageInternal(
|
|
||||||
assetId: String,
|
|
||||||
callback: (Result<Map<String, Long>?>) -> Unit,
|
|
||||||
signal: CancellationSignal
|
|
||||||
) {
|
|
||||||
signal.throwIfCanceled()
|
|
||||||
val id = assetId.toLong()
|
|
||||||
val uri = ContentUris.withAppendedId(Images.Media.EXTERNAL_CONTENT_URI, id)
|
|
||||||
|
|
||||||
signal.throwIfCanceled()
|
|
||||||
val bytes = resolver.openInputStream(uri)?.use { it.readBytes() }
|
|
||||||
?: throw IOException("Could not read image data for $assetId")
|
|
||||||
|
|
||||||
signal.throwIfCanceled()
|
|
||||||
val pointer = NativeBuffer.allocate(bytes.size)
|
|
||||||
try {
|
|
||||||
val buffer = NativeBuffer.wrap(pointer, bytes.size)
|
|
||||||
buffer.put(bytes)
|
|
||||||
signal.throwIfCanceled()
|
|
||||||
callback(Result.success(mapOf(
|
|
||||||
"pointer" to pointer,
|
|
||||||
"length" to bytes.size.toLong()
|
|
||||||
)))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
NativeBuffer.free(pointer)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getThumbnailBufferInternal(
|
private fun getThumbnailBufferInternal(
|
||||||
assetId: String,
|
assetId: String,
|
||||||
width: Long,
|
width: Long,
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ private open class RemoteImagesPigeonCodec : StandardMessageCodec() {
|
|||||||
|
|
||||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||||
interface RemoteImageApi {
|
interface RemoteImageApi {
|
||||||
fun requestImage(url: String, headers: Map<String, String>, requestId: Long, preferEncoded: Boolean, callback: (Result<Map<String, Long>?>) -> Unit)
|
fun requestImage(url: String, headers: Map<String, String>, requestId: Long, callback: (Result<Map<String, Long>?>) -> Unit)
|
||||||
fun cancelRequest(requestId: Long)
|
fun cancelRequest(requestId: Long)
|
||||||
fun clearCache(callback: (Result<Long>) -> Unit)
|
fun clearCache(callback: (Result<Long>) -> Unit)
|
||||||
|
|
||||||
@@ -68,8 +68,7 @@ interface RemoteImageApi {
|
|||||||
val urlArg = args[0] as String
|
val urlArg = args[0] as String
|
||||||
val headersArg = args[1] as Map<String, String>
|
val headersArg = args[1] as Map<String, String>
|
||||||
val requestIdArg = args[2] as Long
|
val requestIdArg = args[2] as Long
|
||||||
val preferEncodedArg = args[3] as Boolean
|
api.requestImage(urlArg, headersArg, requestIdArg) { result: Result<Map<String, Long>?> ->
|
||||||
api.requestImage(urlArg, headersArg, requestIdArg, preferEncodedArg) { result: Result<Map<String, Long>?> ->
|
|
||||||
val error = result.exceptionOrNull()
|
val error = result.exceptionOrNull()
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
reply.reply(RemoteImagesPigeonUtils.wrapError(error))
|
reply.reply(RemoteImagesPigeonUtils.wrapError(error))
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ class RemoteImagesImpl(context: Context) : RemoteImageApi {
|
|||||||
url: String,
|
url: String,
|
||||||
headers: Map<String, String>,
|
headers: Map<String, String>,
|
||||||
requestId: Long,
|
requestId: Long,
|
||||||
@Suppress("UNUSED_PARAMETER") preferEncoded: Boolean, // always returns encoded; setting has no effect on Android
|
|
||||||
callback: (Result<Map<String, Long>?>) -> Unit
|
callback: (Result<Map<String, Long>?>) -> Unit
|
||||||
) {
|
) {
|
||||||
val signal = CancellationSignal()
|
val signal = CancellationSignal()
|
||||||
|
|||||||
@@ -78,21 +78,6 @@ class FlutterError (
|
|||||||
val details: Any? = null
|
val details: Any? = null
|
||||||
) : Throwable()
|
) : Throwable()
|
||||||
|
|
||||||
enum class PlatformAssetPlaybackStyle(val raw: Int) {
|
|
||||||
UNKNOWN(0),
|
|
||||||
IMAGE(1),
|
|
||||||
VIDEO(2),
|
|
||||||
IMAGE_ANIMATED(3),
|
|
||||||
LIVE_PHOTO(4),
|
|
||||||
VIDEO_LOOPING(5);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun ofRaw(raw: Int): PlatformAssetPlaybackStyle? {
|
|
||||||
return values().firstOrNull { it.raw == raw }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Generated class from Pigeon that represents data sent in messages. */
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
data class PlatformAsset (
|
data class PlatformAsset (
|
||||||
val id: String,
|
val id: String,
|
||||||
@@ -107,8 +92,7 @@ data class PlatformAsset (
|
|||||||
val isFavorite: Boolean,
|
val isFavorite: Boolean,
|
||||||
val adjustmentTime: Long? = null,
|
val adjustmentTime: Long? = null,
|
||||||
val latitude: Double? = null,
|
val latitude: Double? = null,
|
||||||
val longitude: Double? = null,
|
val longitude: Double? = null
|
||||||
val playbackStyle: PlatformAssetPlaybackStyle
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
companion object {
|
companion object {
|
||||||
@@ -126,8 +110,7 @@ data class PlatformAsset (
|
|||||||
val adjustmentTime = pigeonVar_list[10] as Long?
|
val adjustmentTime = pigeonVar_list[10] as Long?
|
||||||
val latitude = pigeonVar_list[11] as Double?
|
val latitude = pigeonVar_list[11] as Double?
|
||||||
val longitude = pigeonVar_list[12] as Double?
|
val longitude = pigeonVar_list[12] as Double?
|
||||||
val playbackStyle = pigeonVar_list[13] as PlatformAssetPlaybackStyle
|
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, adjustmentTime, latitude, longitude)
|
||||||
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, adjustmentTime, latitude, longitude, playbackStyle)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun toList(): List<Any?> {
|
fun toList(): List<Any?> {
|
||||||
@@ -145,7 +128,6 @@ data class PlatformAsset (
|
|||||||
adjustmentTime,
|
adjustmentTime,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
playbackStyle,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -308,31 +290,26 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
|
|||||||
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
129.toByte() -> {
|
129.toByte() -> {
|
||||||
return (readValue(buffer) as Long?)?.let {
|
|
||||||
PlatformAssetPlaybackStyle.ofRaw(it.toInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
130.toByte() -> {
|
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
PlatformAsset.fromList(it)
|
PlatformAsset.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
131.toByte() -> {
|
130.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
PlatformAlbum.fromList(it)
|
PlatformAlbum.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
132.toByte() -> {
|
131.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
SyncDelta.fromList(it)
|
SyncDelta.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
133.toByte() -> {
|
132.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
HashResult.fromList(it)
|
HashResult.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
134.toByte() -> {
|
133.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
CloudIdResult.fromList(it)
|
CloudIdResult.fromList(it)
|
||||||
}
|
}
|
||||||
@@ -342,28 +319,24 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
|
|||||||
}
|
}
|
||||||
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
|
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
|
||||||
when (value) {
|
when (value) {
|
||||||
is PlatformAssetPlaybackStyle -> {
|
|
||||||
stream.write(129)
|
|
||||||
writeValue(stream, value.raw)
|
|
||||||
}
|
|
||||||
is PlatformAsset -> {
|
is PlatformAsset -> {
|
||||||
stream.write(130)
|
stream.write(129)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is PlatformAlbum -> {
|
is PlatformAlbum -> {
|
||||||
stream.write(131)
|
stream.write(130)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is SyncDelta -> {
|
is SyncDelta -> {
|
||||||
stream.write(132)
|
stream.write(131)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is HashResult -> {
|
is HashResult -> {
|
||||||
stream.write(133)
|
stream.write(132)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is CloudIdResult -> {
|
is CloudIdResult -> {
|
||||||
stream.write(134)
|
stream.write(133)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
else -> super.writeValue(stream, value)
|
else -> super.writeValue(stream, value)
|
||||||
|
|||||||
@@ -4,18 +4,11 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.ContentUris
|
import android.content.ContentUris
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import androidx.exifinterface.media.ExifInterface
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.ext.SdkExtensions
|
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.util.Log
|
|
||||||
import androidx.core.database.getStringOrNull
|
import androidx.core.database.getStringOrNull
|
||||||
import app.alextran.immich.core.ImmichPlugin
|
import app.alextran.immich.core.ImmichPlugin
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.load.ImageHeaderParser
|
|
||||||
import com.bumptech.glide.load.ImageHeaderParserUtils
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -35,8 +28,6 @@ sealed class AssetResult {
|
|||||||
data class InvalidAsset(val assetId: String) : AssetResult()
|
data class InvalidAsset(val assetId: String) : AssetResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val TAG = "NativeSyncApiImplBase"
|
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
||||||
private val ctx: Context = context.applicationContext
|
private val ctx: Context = context.applicationContext
|
||||||
@@ -48,13 +39,6 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
private val hashSemaphore = Semaphore(MAX_CONCURRENT_HASH_OPERATIONS)
|
private val hashSemaphore = Semaphore(MAX_CONCURRENT_HASH_OPERATIONS)
|
||||||
private const val HASHING_CANCELLED_CODE = "HASH_CANCELLED"
|
private const val HASHING_CANCELLED_CODE = "HASH_CANCELLED"
|
||||||
|
|
||||||
// MediaStore.Files.FileColumns.SPECIAL_FORMAT — S Extensions 21+
|
|
||||||
// https://developer.android.com/reference/android/provider/MediaStore.Files.FileColumns#SPECIAL_FORMAT
|
|
||||||
private const val SPECIAL_FORMAT_COLUMN = "_special_format"
|
|
||||||
private const val SPECIAL_FORMAT_GIF = 1
|
|
||||||
private const val SPECIAL_FORMAT_MOTION_PHOTO = 2
|
|
||||||
private const val SPECIAL_FORMAT_ANIMATED_WEBP = 3
|
|
||||||
|
|
||||||
const val MEDIA_SELECTION =
|
const val MEDIA_SELECTION =
|
||||||
"(${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?)"
|
"(${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?)"
|
||||||
val MEDIA_SELECTION_ARGS = arrayOf(
|
val MEDIA_SELECTION_ARGS = arrayOf(
|
||||||
@@ -76,25 +60,12 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
add(MediaStore.MediaColumns.DURATION)
|
add(MediaStore.MediaColumns.DURATION)
|
||||||
add(MediaStore.MediaColumns.ORIENTATION)
|
add(MediaStore.MediaColumns.ORIENTATION)
|
||||||
// IS_FAVORITE is only available on Android 11 and above
|
// IS_FAVORITE is only available on Android 11 and above
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
|
||||||
add(MediaStore.MediaColumns.IS_FAVORITE)
|
add(MediaStore.MediaColumns.IS_FAVORITE)
|
||||||
}
|
}
|
||||||
if (hasSpecialFormatColumn()) {
|
|
||||||
add(SPECIAL_FORMAT_COLUMN)
|
|
||||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
// Fallback: read XMP from MediaStore to detect Motion Photos
|
|
||||||
// only needed if SPECIAL_FORMAT column isn't available
|
|
||||||
add(MediaStore.MediaColumns.XMP)
|
|
||||||
}
|
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
|
|
||||||
const val HASH_BUFFER_SIZE = 2 * 1024 * 1024
|
const val HASH_BUFFER_SIZE = 2 * 1024 * 1024
|
||||||
|
|
||||||
// _special_format requires S Extensions 21+
|
|
||||||
// https://developer.android.com/reference/android/provider/MediaStore.Files.FileColumns#SPECIAL_FORMAT
|
|
||||||
private fun hasSpecialFormatColumn(): Boolean =
|
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
|
|
||||||
SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= 21
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun getCursor(
|
protected fun getCursor(
|
||||||
@@ -138,12 +109,9 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
val orientationColumn =
|
val orientationColumn =
|
||||||
c.getColumnIndexOrThrow(MediaStore.MediaColumns.ORIENTATION)
|
c.getColumnIndexOrThrow(MediaStore.MediaColumns.ORIENTATION)
|
||||||
val favoriteColumn = c.getColumnIndex(MediaStore.MediaColumns.IS_FAVORITE)
|
val favoriteColumn = c.getColumnIndex(MediaStore.MediaColumns.IS_FAVORITE)
|
||||||
val specialFormatColumn = c.getColumnIndex(SPECIAL_FORMAT_COLUMN)
|
|
||||||
val xmpColumn = c.getColumnIndex(MediaStore.MediaColumns.XMP)
|
|
||||||
|
|
||||||
while (c.moveToNext()) {
|
while (c.moveToNext()) {
|
||||||
val numericId = c.getLong(idColumn)
|
val id = c.getLong(idColumn).toString()
|
||||||
val id = numericId.toString()
|
|
||||||
val name = c.getStringOrNull(nameColumn)
|
val name = c.getStringOrNull(nameColumn)
|
||||||
val bucketId = c.getStringOrNull(bucketIdColumn)
|
val bucketId = c.getStringOrNull(bucketIdColumn)
|
||||||
val path = c.getStringOrNull(dataColumn)
|
val path = c.getStringOrNull(dataColumn)
|
||||||
@@ -157,11 +125,10 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
val rawMediaType = c.getInt(mediaTypeColumn)
|
val mediaType = when (c.getInt(mediaTypeColumn)) {
|
||||||
val assetType: Long = when (rawMediaType) {
|
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> 1
|
||||||
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> 1L
|
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> 2
|
||||||
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> 2L
|
else -> 0
|
||||||
else -> 0L
|
|
||||||
}
|
}
|
||||||
// Date taken is milliseconds since epoch, Date added is seconds since epoch
|
// Date taken is milliseconds since epoch, Date added is seconds since epoch
|
||||||
val createdAt = (c.getLong(dateTakenColumn).takeIf { it > 0 }?.div(1000))
|
val createdAt = (c.getLong(dateTakenColumn).takeIf { it > 0 }?.div(1000))
|
||||||
@@ -171,19 +138,15 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
val width = c.getInt(widthColumn).toLong()
|
val width = c.getInt(widthColumn).toLong()
|
||||||
val height = c.getInt(heightColumn).toLong()
|
val height = c.getInt(heightColumn).toLong()
|
||||||
// Duration is milliseconds
|
// Duration is milliseconds
|
||||||
val duration = if (rawMediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) 0L
|
val duration = if (mediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) 0
|
||||||
else c.getLong(durationColumn) / 1000
|
else c.getLong(durationColumn) / 1000
|
||||||
val orientation = c.getInt(orientationColumn)
|
val orientation = c.getInt(orientationColumn)
|
||||||
val isFavorite = if (favoriteColumn == -1) false else c.getInt(favoriteColumn) != 0
|
val isFavorite = if (favoriteColumn == -1) false else c.getInt(favoriteColumn) != 0
|
||||||
|
|
||||||
val playbackStyle = detectPlaybackStyle(
|
|
||||||
numericId, rawMediaType, specialFormatColumn, xmpColumn, c
|
|
||||||
)
|
|
||||||
|
|
||||||
val asset = PlatformAsset(
|
val asset = PlatformAsset(
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
assetType,
|
mediaType.toLong(),
|
||||||
createdAt,
|
createdAt,
|
||||||
modifiedAt,
|
modifiedAt,
|
||||||
width,
|
width,
|
||||||
@@ -191,7 +154,6 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
duration,
|
duration,
|
||||||
orientation.toLong(),
|
orientation.toLong(),
|
||||||
isFavorite,
|
isFavorite,
|
||||||
playbackStyle = playbackStyle,
|
|
||||||
)
|
)
|
||||||
yield(AssetResult.ValidAsset(asset, bucketId))
|
yield(AssetResult.ValidAsset(asset, bucketId))
|
||||||
}
|
}
|
||||||
@@ -199,81 +161,6 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Detects the playback style for an asset using _special_format (API 33+)
|
|
||||||
* or XMP / MIME / RIFF header fallbacks (pre-33).
|
|
||||||
*/
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
private fun detectPlaybackStyle(
|
|
||||||
assetId: Long,
|
|
||||||
rawMediaType: Int,
|
|
||||||
specialFormatColumn: Int,
|
|
||||||
xmpColumn: Int,
|
|
||||||
cursor: Cursor
|
|
||||||
): PlatformAssetPlaybackStyle {
|
|
||||||
// video currently has no special formats, so we can short circuit and avoid unnecessary work
|
|
||||||
if (rawMediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO) {
|
|
||||||
return PlatformAssetPlaybackStyle.VIDEO
|
|
||||||
}
|
|
||||||
|
|
||||||
// API 33+: use _special_format from cursor
|
|
||||||
if (specialFormatColumn != -1) {
|
|
||||||
val specialFormat = cursor.getInt(specialFormatColumn)
|
|
||||||
return when {
|
|
||||||
specialFormat == SPECIAL_FORMAT_MOTION_PHOTO -> PlatformAssetPlaybackStyle.LIVE_PHOTO
|
|
||||||
specialFormat == SPECIAL_FORMAT_GIF || specialFormat == SPECIAL_FORMAT_ANIMATED_WEBP -> PlatformAssetPlaybackStyle.IMAGE_ANIMATED
|
|
||||||
rawMediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> PlatformAssetPlaybackStyle.IMAGE
|
|
||||||
else -> PlatformAssetPlaybackStyle.UNKNOWN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawMediaType != MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) {
|
|
||||||
return PlatformAssetPlaybackStyle.UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-API 33 fallback
|
|
||||||
val uri = ContentUris.withAppendedId(
|
|
||||||
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
|
|
||||||
assetId
|
|
||||||
)
|
|
||||||
|
|
||||||
// Read XMP from cursor (API 30+) or ExifInterface stream (pre-30)
|
|
||||||
val xmp: String? = if (xmpColumn != -1) {
|
|
||||||
cursor.getBlob(xmpColumn)?.toString(Charsets.UTF_8)
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
ctx.contentResolver.openInputStream(uri)?.use { stream ->
|
|
||||||
ExifInterface(stream).getAttribute(ExifInterface.TAG_XMP)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to read XMP for asset $assetId", e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xmp != null && "Camera:MotionPhoto" in xmp) {
|
|
||||||
return PlatformAssetPlaybackStyle.LIVE_PHOTO
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
ctx.contentResolver.openInputStream(uri)?.use { stream ->
|
|
||||||
val glide = Glide.get(ctx)
|
|
||||||
val type = ImageHeaderParserUtils.getType(
|
|
||||||
glide.registry.imageHeaderParsers,
|
|
||||||
stream,
|
|
||||||
glide.arrayPool
|
|
||||||
)
|
|
||||||
if (type == ImageHeaderParser.ImageType.GIF || type == ImageHeaderParser.ImageType.ANIMATED_WEBP) {
|
|
||||||
return PlatformAssetPlaybackStyle.IMAGE_ANIMATED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to parse image header for asset $assetId", e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return PlatformAssetPlaybackStyle.IMAGE
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAlbums(): List<PlatformAlbum> {
|
fun getAlbums(): List<PlatformAlbum> {
|
||||||
val albums = mutableListOf<PlatformAlbum>()
|
val albums = mutableListOf<PlatformAlbum>()
|
||||||
val albumsCount = mutableMapOf<String, Int>()
|
val albumsCount = mutableMapOf<String, Int>()
|
||||||
|
|||||||
-1
File diff suppressed because one or more lines are too long
@@ -70,7 +70,7 @@ class LocalImagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
|
|||||||
|
|
||||||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||||
protocol LocalImageApi {
|
protocol LocalImageApi {
|
||||||
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, preferEncoded: Bool, completion: @escaping (Result<[String: Int64]?, Error>) -> Void)
|
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, completion: @escaping (Result<[String: Int64]?, Error>) -> Void)
|
||||||
func cancelRequest(requestId: Int64) throws
|
func cancelRequest(requestId: Int64) throws
|
||||||
func getThumbhash(thumbhash: String, completion: @escaping (Result<[String: Int64], Error>) -> Void)
|
func getThumbhash(thumbhash: String, completion: @escaping (Result<[String: Int64], Error>) -> Void)
|
||||||
}
|
}
|
||||||
@@ -90,8 +90,7 @@ class LocalImageApiSetup {
|
|||||||
let widthArg = args[2] as! Int64
|
let widthArg = args[2] as! Int64
|
||||||
let heightArg = args[3] as! Int64
|
let heightArg = args[3] as! Int64
|
||||||
let isVideoArg = args[4] as! Bool
|
let isVideoArg = args[4] as! Bool
|
||||||
let preferEncodedArg = args[5] as! Bool
|
api.requestImage(assetId: assetIdArg, requestId: requestIdArg, width: widthArg, height: heightArg, isVideo: isVideoArg) { result in
|
||||||
api.requestImage(assetId: assetIdArg, requestId: requestIdArg, width: widthArg, height: heightArg, isVideo: isVideoArg, preferEncoded: preferEncodedArg) { result in
|
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let res):
|
case .success(let res):
|
||||||
reply(wrapResult(res))
|
reply(wrapResult(res))
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class LocalImageApiImpl: LocalImageApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, preferEncoded: Bool, completion: @escaping (Result<[String: Int64]?, any Error>) -> Void) {
|
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, completion: @escaping (Result<[String: Int64]?, any Error>) -> Void) {
|
||||||
let request = LocalImageRequest(callback: completion)
|
let request = LocalImageRequest(callback: completion)
|
||||||
let item = DispatchWorkItem {
|
let item = DispatchWorkItem {
|
||||||
if request.isCancelled {
|
if request.isCancelled {
|
||||||
@@ -91,49 +91,6 @@ class LocalImageApiImpl: LocalImageApi {
|
|||||||
return completion(ImageProcessing.cancelledResult)
|
return completion(ImageProcessing.cancelledResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
if preferEncoded {
|
|
||||||
let dataOptions = PHImageRequestOptions()
|
|
||||||
dataOptions.isNetworkAccessAllowed = true
|
|
||||||
dataOptions.isSynchronous = true
|
|
||||||
dataOptions.version = .current
|
|
||||||
|
|
||||||
var imageData: Data?
|
|
||||||
Self.imageManager.requestImageDataAndOrientation(
|
|
||||||
for: asset,
|
|
||||||
options: dataOptions,
|
|
||||||
resultHandler: { (data, _, _, _) in
|
|
||||||
imageData = data
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if request.isCancelled {
|
|
||||||
Self.remove(requestId: requestId)
|
|
||||||
return completion(ImageProcessing.cancelledResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let data = imageData else {
|
|
||||||
Self.remove(requestId: requestId)
|
|
||||||
return completion(.failure(PigeonError(code: "", message: "Could not get image data for \(assetId)", details: nil)))
|
|
||||||
}
|
|
||||||
|
|
||||||
let length = data.count
|
|
||||||
let pointer = malloc(length)!
|
|
||||||
data.copyBytes(to: pointer.assumingMemoryBound(to: UInt8.self), count: length)
|
|
||||||
|
|
||||||
if request.isCancelled {
|
|
||||||
free(pointer)
|
|
||||||
Self.remove(requestId: requestId)
|
|
||||||
return completion(ImageProcessing.cancelledResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
request.callback(.success([
|
|
||||||
"pointer": Int64(Int(bitPattern: pointer)),
|
|
||||||
"length": Int64(length),
|
|
||||||
]))
|
|
||||||
Self.remove(requestId: requestId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var image: UIImage?
|
var image: UIImage?
|
||||||
Self.imageManager.requestImage(
|
Self.imageManager.requestImage(
|
||||||
for: asset,
|
for: asset,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class RemoteImagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable
|
|||||||
|
|
||||||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||||
protocol RemoteImageApi {
|
protocol RemoteImageApi {
|
||||||
func requestImage(url: String, headers: [String: String], requestId: Int64, preferEncoded: Bool, completion: @escaping (Result<[String: Int64]?, Error>) -> Void)
|
func requestImage(url: String, headers: [String: String], requestId: Int64, completion: @escaping (Result<[String: Int64]?, Error>) -> Void)
|
||||||
func cancelRequest(requestId: Int64) throws
|
func cancelRequest(requestId: Int64) throws
|
||||||
func clearCache(completion: @escaping (Result<Int64, Error>) -> Void)
|
func clearCache(completion: @escaping (Result<Int64, Error>) -> Void)
|
||||||
}
|
}
|
||||||
@@ -88,8 +88,7 @@ class RemoteImageApiSetup {
|
|||||||
let urlArg = args[0] as! String
|
let urlArg = args[0] as! String
|
||||||
let headersArg = args[1] as! [String: String]
|
let headersArg = args[1] as! [String: String]
|
||||||
let requestIdArg = args[2] as! Int64
|
let requestIdArg = args[2] as! Int64
|
||||||
let preferEncodedArg = args[3] as! Bool
|
api.requestImage(url: urlArg, headers: headersArg, requestId: requestIdArg) { result in
|
||||||
api.requestImage(url: urlArg, headers: headersArg, requestId: requestIdArg, preferEncoded: preferEncodedArg) { result in
|
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let res):
|
case .success(let res):
|
||||||
reply(wrapResult(res))
|
reply(wrapResult(res))
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class RemoteImageApiImpl: NSObject, RemoteImageApi {
|
|||||||
kCGImageSourceCreateThumbnailFromImageAlways: true
|
kCGImageSourceCreateThumbnailFromImageAlways: true
|
||||||
] as CFDictionary
|
] as CFDictionary
|
||||||
|
|
||||||
func requestImage(url: String, headers: [String : String], requestId: Int64, preferEncoded: Bool, completion: @escaping (Result<[String : Int64]?, any Error>) -> Void) {
|
func requestImage(url: String, headers: [String : String], requestId: Int64, completion: @escaping (Result<[String : Int64]?, any Error>) -> Void) {
|
||||||
var urlRequest = URLRequest(url: URL(string: url)!)
|
var urlRequest = URLRequest(url: URL(string: url)!)
|
||||||
urlRequest.cachePolicy = .returnCacheDataElseLoad
|
urlRequest.cachePolicy = .returnCacheDataElseLoad
|
||||||
for (key, value) in headers {
|
for (key, value) in headers {
|
||||||
@@ -41,7 +41,7 @@ class RemoteImageApiImpl: NSObject, RemoteImageApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let task = URLSessionManager.shared.session.dataTask(with: urlRequest) { data, response, error in
|
let task = URLSessionManager.shared.session.dataTask(with: urlRequest) { data, response, error in
|
||||||
Self.handleCompletion(requestId: requestId, encoded: preferEncoded, data: data, response: response, error: error)
|
Self.handleCompletion(requestId: requestId, data: data, response: response, error: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
let request = RemoteImageRequest(id: requestId, task: task, completion: completion)
|
let request = RemoteImageRequest(id: requestId, task: task, completion: completion)
|
||||||
@@ -53,7 +53,7 @@ class RemoteImageApiImpl: NSObject, RemoteImageApi {
|
|||||||
task.resume()
|
task.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func handleCompletion(requestId: Int64, encoded: Bool, data: Data?, response: URLResponse?, error: Error?) {
|
private static func handleCompletion(requestId: Int64, data: Data?, response: URLResponse?, error: Error?) {
|
||||||
os_unfair_lock_lock(&Self.lock)
|
os_unfair_lock_lock(&Self.lock)
|
||||||
guard let request = requests[requestId] else {
|
guard let request = requests[requestId] else {
|
||||||
return os_unfair_lock_unlock(&Self.lock)
|
return os_unfair_lock_unlock(&Self.lock)
|
||||||
@@ -84,24 +84,6 @@ class RemoteImageApiImpl: NSObject, RemoteImageApi {
|
|||||||
return request.completion(ImageProcessing.cancelledResult)
|
return request.completion(ImageProcessing.cancelledResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return raw encoded bytes when requested (for animated images)
|
|
||||||
if encoded {
|
|
||||||
let length = data.count
|
|
||||||
let pointer = malloc(length)!
|
|
||||||
data.copyBytes(to: pointer.assumingMemoryBound(to: UInt8.self), count: length)
|
|
||||||
|
|
||||||
if request.isCancelled {
|
|
||||||
free(pointer)
|
|
||||||
return request.completion(ImageProcessing.cancelledResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
return request.completion(
|
|
||||||
.success([
|
|
||||||
"pointer": Int64(Int(bitPattern: pointer)),
|
|
||||||
"length": Int64(length),
|
|
||||||
]))
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil),
|
guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil),
|
||||||
let cgImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, decodeOptions) else {
|
let cgImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, decodeOptions) else {
|
||||||
return request.completion(.failure(PigeonError(code: "", message: "Failed to decode image for request", details: nil)))
|
return request.completion(.failure(PigeonError(code: "", message: "Failed to decode image for request", details: nil)))
|
||||||
|
|||||||
@@ -128,15 +128,6 @@ func deepHashMessages(value: Any?, hasher: inout Hasher) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
enum PlatformAssetPlaybackStyle: Int {
|
|
||||||
case unknown = 0
|
|
||||||
case image = 1
|
|
||||||
case video = 2
|
|
||||||
case imageAnimated = 3
|
|
||||||
case livePhoto = 4
|
|
||||||
case videoLooping = 5
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generated class from Pigeon that represents data sent in messages.
|
/// Generated class from Pigeon that represents data sent in messages.
|
||||||
struct PlatformAsset: Hashable {
|
struct PlatformAsset: Hashable {
|
||||||
var id: String
|
var id: String
|
||||||
@@ -152,7 +143,6 @@ struct PlatformAsset: Hashable {
|
|||||||
var adjustmentTime: Int64? = nil
|
var adjustmentTime: Int64? = nil
|
||||||
var latitude: Double? = nil
|
var latitude: Double? = nil
|
||||||
var longitude: Double? = nil
|
var longitude: Double? = nil
|
||||||
var playbackStyle: PlatformAssetPlaybackStyle
|
|
||||||
|
|
||||||
|
|
||||||
// swift-format-ignore: AlwaysUseLowerCamelCase
|
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||||
@@ -170,7 +160,6 @@ struct PlatformAsset: Hashable {
|
|||||||
let adjustmentTime: Int64? = nilOrValue(pigeonVar_list[10])
|
let adjustmentTime: Int64? = nilOrValue(pigeonVar_list[10])
|
||||||
let latitude: Double? = nilOrValue(pigeonVar_list[11])
|
let latitude: Double? = nilOrValue(pigeonVar_list[11])
|
||||||
let longitude: Double? = nilOrValue(pigeonVar_list[12])
|
let longitude: Double? = nilOrValue(pigeonVar_list[12])
|
||||||
let playbackStyle = pigeonVar_list[13] as! PlatformAssetPlaybackStyle
|
|
||||||
|
|
||||||
return PlatformAsset(
|
return PlatformAsset(
|
||||||
id: id,
|
id: id,
|
||||||
@@ -185,8 +174,7 @@ struct PlatformAsset: Hashable {
|
|||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
adjustmentTime: adjustmentTime,
|
adjustmentTime: adjustmentTime,
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude
|
||||||
playbackStyle: playbackStyle
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
func toList() -> [Any?] {
|
func toList() -> [Any?] {
|
||||||
@@ -204,7 +192,6 @@ struct PlatformAsset: Hashable {
|
|||||||
adjustmentTime,
|
adjustmentTime,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
playbackStyle,
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool {
|
static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool {
|
||||||
@@ -362,20 +349,14 @@ private class MessagesPigeonCodecReader: FlutterStandardReader {
|
|||||||
override func readValue(ofType type: UInt8) -> Any? {
|
override func readValue(ofType type: UInt8) -> Any? {
|
||||||
switch type {
|
switch type {
|
||||||
case 129:
|
case 129:
|
||||||
let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?)
|
|
||||||
if let enumResultAsInt = enumResultAsInt {
|
|
||||||
return PlatformAssetPlaybackStyle(rawValue: enumResultAsInt)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case 130:
|
|
||||||
return PlatformAsset.fromList(self.readValue() as! [Any?])
|
return PlatformAsset.fromList(self.readValue() as! [Any?])
|
||||||
case 131:
|
case 130:
|
||||||
return PlatformAlbum.fromList(self.readValue() as! [Any?])
|
return PlatformAlbum.fromList(self.readValue() as! [Any?])
|
||||||
case 132:
|
case 131:
|
||||||
return SyncDelta.fromList(self.readValue() as! [Any?])
|
return SyncDelta.fromList(self.readValue() as! [Any?])
|
||||||
case 133:
|
case 132:
|
||||||
return HashResult.fromList(self.readValue() as! [Any?])
|
return HashResult.fromList(self.readValue() as! [Any?])
|
||||||
case 134:
|
case 133:
|
||||||
return CloudIdResult.fromList(self.readValue() as! [Any?])
|
return CloudIdResult.fromList(self.readValue() as! [Any?])
|
||||||
default:
|
default:
|
||||||
return super.readValue(ofType: type)
|
return super.readValue(ofType: type)
|
||||||
@@ -385,23 +366,20 @@ private class MessagesPigeonCodecReader: FlutterStandardReader {
|
|||||||
|
|
||||||
private class MessagesPigeonCodecWriter: FlutterStandardWriter {
|
private class MessagesPigeonCodecWriter: FlutterStandardWriter {
|
||||||
override func writeValue(_ value: Any) {
|
override func writeValue(_ value: Any) {
|
||||||
if let value = value as? PlatformAssetPlaybackStyle {
|
if let value = value as? PlatformAsset {
|
||||||
super.writeByte(129)
|
super.writeByte(129)
|
||||||
super.writeValue(value.rawValue)
|
|
||||||
} else if let value = value as? PlatformAsset {
|
|
||||||
super.writeByte(130)
|
|
||||||
super.writeValue(value.toList())
|
super.writeValue(value.toList())
|
||||||
} else if let value = value as? PlatformAlbum {
|
} else if let value = value as? PlatformAlbum {
|
||||||
super.writeByte(131)
|
super.writeByte(130)
|
||||||
super.writeValue(value.toList())
|
super.writeValue(value.toList())
|
||||||
} else if let value = value as? SyncDelta {
|
} else if let value = value as? SyncDelta {
|
||||||
super.writeByte(132)
|
super.writeByte(131)
|
||||||
super.writeValue(value.toList())
|
super.writeValue(value.toList())
|
||||||
} else if let value = value as? HashResult {
|
} else if let value = value as? HashResult {
|
||||||
super.writeByte(133)
|
super.writeByte(132)
|
||||||
super.writeValue(value.toList())
|
super.writeValue(value.toList())
|
||||||
} else if let value = value as? CloudIdResult {
|
} else if let value = value as? CloudIdResult {
|
||||||
super.writeByte(134)
|
super.writeByte(133)
|
||||||
super.writeValue(value.toList())
|
super.writeValue(value.toList())
|
||||||
} else {
|
} else {
|
||||||
super.writeValue(value)
|
super.writeValue(value)
|
||||||
|
|||||||
@@ -173,8 +173,7 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
|
|||||||
type: 0,
|
type: 0,
|
||||||
durationInSeconds: 0,
|
durationInSeconds: 0,
|
||||||
orientation: 0,
|
orientation: 0,
|
||||||
isFavorite: false,
|
isFavorite: false
|
||||||
playbackStyle: .unknown
|
|
||||||
)
|
)
|
||||||
if (updatedAssets.contains(AssetWrapper(with: predicate))) {
|
if (updatedAssets.contains(AssetWrapper(with: predicate))) {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,17 +1,6 @@
|
|||||||
import Photos
|
import Photos
|
||||||
|
|
||||||
extension PHAsset {
|
extension PHAsset {
|
||||||
var platformPlaybackStyle: PlatformAssetPlaybackStyle {
|
|
||||||
switch playbackStyle {
|
|
||||||
case .image: return .image
|
|
||||||
case .imageAnimated: return .imageAnimated
|
|
||||||
case .livePhoto: return .livePhoto
|
|
||||||
case .video: return .video
|
|
||||||
case .videoLooping: return .videoLooping
|
|
||||||
@unknown default: return .unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func toPlatformAsset() -> PlatformAsset {
|
func toPlatformAsset() -> PlatformAsset {
|
||||||
return PlatformAsset(
|
return PlatformAsset(
|
||||||
id: localIdentifier,
|
id: localIdentifier,
|
||||||
@@ -26,8 +15,7 @@ extension PHAsset {
|
|||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
adjustmentTime: adjustmentTimestamp,
|
adjustmentTime: adjustmentTimestamp,
|
||||||
latitude: location?.coordinate.latitude,
|
latitude: location?.coordinate.latitude,
|
||||||
longitude: location?.coordinate.longitude,
|
longitude: location?.coordinate.longitude
|
||||||
playbackStyle: platformPlaybackStyle
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,6 @@ enum AssetType {
|
|||||||
|
|
||||||
enum AssetState { local, remote, merged }
|
enum AssetState { local, remote, merged }
|
||||||
|
|
||||||
// do not change!
|
|
||||||
// keep in sync with PlatformAssetPlaybackStyle
|
|
||||||
enum AssetPlaybackStyle { unknown, image, video, imageAnimated, livePhoto, videoLooping }
|
|
||||||
|
|
||||||
sealed class BaseAsset {
|
sealed class BaseAsset {
|
||||||
final String name;
|
final String name;
|
||||||
final String? checksum;
|
final String? checksum;
|
||||||
@@ -47,14 +43,6 @@ sealed class BaseAsset {
|
|||||||
|
|
||||||
bool get isMotionPhoto => livePhotoVideoId != null;
|
bool get isMotionPhoto => livePhotoVideoId != null;
|
||||||
|
|
||||||
AssetPlaybackStyle get playbackStyle {
|
|
||||||
if (isVideo) return AssetPlaybackStyle.video;
|
|
||||||
if (isMotionPhoto) return AssetPlaybackStyle.livePhoto;
|
|
||||||
if (isImage && durationInSeconds != null && durationInSeconds! > 0) return AssetPlaybackStyle.imageAnimated;
|
|
||||||
if (isImage) return AssetPlaybackStyle.image;
|
|
||||||
return AssetPlaybackStyle.unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
Duration get duration {
|
Duration get duration {
|
||||||
final durationInSeconds = this.durationInSeconds;
|
final durationInSeconds = this.durationInSeconds;
|
||||||
if (durationInSeconds != null) {
|
if (durationInSeconds != null) {
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ class LocalAsset extends BaseAsset {
|
|||||||
final String? remoteAssetId;
|
final String? remoteAssetId;
|
||||||
final String? cloudId;
|
final String? cloudId;
|
||||||
final int orientation;
|
final int orientation;
|
||||||
@override
|
|
||||||
final AssetPlaybackStyle playbackStyle;
|
|
||||||
|
|
||||||
final DateTime? adjustmentTime;
|
final DateTime? adjustmentTime;
|
||||||
final double? latitude;
|
final double? latitude;
|
||||||
@@ -27,7 +25,6 @@ class LocalAsset extends BaseAsset {
|
|||||||
super.isFavorite = false,
|
super.isFavorite = false,
|
||||||
super.livePhotoVideoId,
|
super.livePhotoVideoId,
|
||||||
this.orientation = 0,
|
this.orientation = 0,
|
||||||
required this.playbackStyle,
|
|
||||||
this.adjustmentTime,
|
this.adjustmentTime,
|
||||||
this.latitude,
|
this.latitude,
|
||||||
this.longitude,
|
this.longitude,
|
||||||
@@ -59,7 +56,6 @@ class LocalAsset extends BaseAsset {
|
|||||||
width: ${width ?? "<NA>"},
|
width: ${width ?? "<NA>"},
|
||||||
height: ${height ?? "<NA>"},
|
height: ${height ?? "<NA>"},
|
||||||
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
||||||
playbackStyle: $playbackStyle,
|
|
||||||
remoteId: ${remoteId ?? "<NA>"},
|
remoteId: ${remoteId ?? "<NA>"},
|
||||||
cloudId: ${cloudId ?? "<NA>"},
|
cloudId: ${cloudId ?? "<NA>"},
|
||||||
checksum: ${checksum ?? "<NA>"},
|
checksum: ${checksum ?? "<NA>"},
|
||||||
@@ -80,7 +76,6 @@ class LocalAsset extends BaseAsset {
|
|||||||
id == other.id &&
|
id == other.id &&
|
||||||
cloudId == other.cloudId &&
|
cloudId == other.cloudId &&
|
||||||
orientation == other.orientation &&
|
orientation == other.orientation &&
|
||||||
playbackStyle == other.playbackStyle &&
|
|
||||||
adjustmentTime == other.adjustmentTime &&
|
adjustmentTime == other.adjustmentTime &&
|
||||||
latitude == other.latitude &&
|
latitude == other.latitude &&
|
||||||
longitude == other.longitude;
|
longitude == other.longitude;
|
||||||
@@ -92,7 +87,6 @@ class LocalAsset extends BaseAsset {
|
|||||||
id.hashCode ^
|
id.hashCode ^
|
||||||
remoteId.hashCode ^
|
remoteId.hashCode ^
|
||||||
orientation.hashCode ^
|
orientation.hashCode ^
|
||||||
playbackStyle.hashCode ^
|
|
||||||
adjustmentTime.hashCode ^
|
adjustmentTime.hashCode ^
|
||||||
latitude.hashCode ^
|
latitude.hashCode ^
|
||||||
longitude.hashCode;
|
longitude.hashCode;
|
||||||
@@ -111,7 +105,6 @@ class LocalAsset extends BaseAsset {
|
|||||||
int? durationInSeconds,
|
int? durationInSeconds,
|
||||||
bool? isFavorite,
|
bool? isFavorite,
|
||||||
int? orientation,
|
int? orientation,
|
||||||
AssetPlaybackStyle? playbackStyle,
|
|
||||||
DateTime? adjustmentTime,
|
DateTime? adjustmentTime,
|
||||||
double? latitude,
|
double? latitude,
|
||||||
double? longitude,
|
double? longitude,
|
||||||
@@ -131,7 +124,6 @@ class LocalAsset extends BaseAsset {
|
|||||||
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
|
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
|
||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
orientation: orientation ?? this.orientation,
|
orientation: orientation ?? this.orientation,
|
||||||
playbackStyle: playbackStyle ?? this.playbackStyle,
|
|
||||||
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
|
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
|
||||||
latitude: latitude ?? this.latitude,
|
latitude: latitude ?? this.latitude,
|
||||||
longitude: longitude ?? this.longitude,
|
longitude: longitude ?? this.longitude,
|
||||||
|
|||||||
@@ -435,19 +435,9 @@ extension PlatformToLocalAsset on PlatformAsset {
|
|||||||
durationInSeconds: durationInSeconds,
|
durationInSeconds: durationInSeconds,
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
playbackStyle: _toPlaybackStyle(playbackStyle),
|
|
||||||
adjustmentTime: tryFromSecondsSinceEpoch(adjustmentTime, isUtc: true),
|
adjustmentTime: tryFromSecondsSinceEpoch(adjustmentTime, isUtc: true),
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude,
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AssetPlaybackStyle _toPlaybackStyle(PlatformAssetPlaybackStyle style) => switch (style) {
|
|
||||||
PlatformAssetPlaybackStyle.unknown => AssetPlaybackStyle.unknown,
|
|
||||||
PlatformAssetPlaybackStyle.image => AssetPlaybackStyle.image,
|
|
||||||
PlatformAssetPlaybackStyle.video => AssetPlaybackStyle.video,
|
|
||||||
PlatformAssetPlaybackStyle.imageAnimated => AssetPlaybackStyle.imageAnimated,
|
|
||||||
PlatformAssetPlaybackStyle.livePhoto => AssetPlaybackStyle.livePhoto,
|
|
||||||
PlatformAssetPlaybackStyle.videoLooping => AssetPlaybackStyle.videoLooping,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
|
|||||||
|
|
||||||
RealColumn get longitude => real().nullable()();
|
RealColumn get longitude => real().nullable()();
|
||||||
|
|
||||||
IntColumn get playbackStyle => intEnum<AssetPlaybackStyle>().withDefault(const Constant(0))();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
}
|
}
|
||||||
@@ -45,7 +43,6 @@ extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData {
|
|||||||
width: width,
|
width: width,
|
||||||
remoteId: remoteId,
|
remoteId: remoteId,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
playbackStyle: playbackStyle,
|
|
||||||
adjustmentTime: adjustmentTime,
|
adjustmentTime: adjustmentTime,
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude,
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ typedef $$LocalAssetEntityTableCreateCompanionBuilder =
|
|||||||
i0.Value<DateTime?> adjustmentTime,
|
i0.Value<DateTime?> adjustmentTime,
|
||||||
i0.Value<double?> latitude,
|
i0.Value<double?> latitude,
|
||||||
i0.Value<double?> longitude,
|
i0.Value<double?> longitude,
|
||||||
i0.Value<i2.AssetPlaybackStyle> playbackStyle,
|
|
||||||
});
|
});
|
||||||
typedef $$LocalAssetEntityTableUpdateCompanionBuilder =
|
typedef $$LocalAssetEntityTableUpdateCompanionBuilder =
|
||||||
i1.LocalAssetEntityCompanion Function({
|
i1.LocalAssetEntityCompanion Function({
|
||||||
@@ -44,7 +43,6 @@ typedef $$LocalAssetEntityTableUpdateCompanionBuilder =
|
|||||||
i0.Value<DateTime?> adjustmentTime,
|
i0.Value<DateTime?> adjustmentTime,
|
||||||
i0.Value<double?> latitude,
|
i0.Value<double?> latitude,
|
||||||
i0.Value<double?> longitude,
|
i0.Value<double?> longitude,
|
||||||
i0.Value<i2.AssetPlaybackStyle> playbackStyle,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
class $$LocalAssetEntityTableFilterComposer
|
class $$LocalAssetEntityTableFilterComposer
|
||||||
@@ -131,16 +129,6 @@ class $$LocalAssetEntityTableFilterComposer
|
|||||||
column: $table.longitude,
|
column: $table.longitude,
|
||||||
builder: (column) => i0.ColumnFilters(column),
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
i0.ColumnWithTypeConverterFilters<
|
|
||||||
i2.AssetPlaybackStyle,
|
|
||||||
i2.AssetPlaybackStyle,
|
|
||||||
int
|
|
||||||
>
|
|
||||||
get playbackStyle => $composableBuilder(
|
|
||||||
column: $table.playbackStyle,
|
|
||||||
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$LocalAssetEntityTableOrderingComposer
|
class $$LocalAssetEntityTableOrderingComposer
|
||||||
@@ -226,11 +214,6 @@ class $$LocalAssetEntityTableOrderingComposer
|
|||||||
column: $table.longitude,
|
column: $table.longitude,
|
||||||
builder: (column) => i0.ColumnOrderings(column),
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
i0.ColumnOrderings<int> get playbackStyle => $composableBuilder(
|
|
||||||
column: $table.playbackStyle,
|
|
||||||
builder: (column) => i0.ColumnOrderings(column),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$LocalAssetEntityTableAnnotationComposer
|
class $$LocalAssetEntityTableAnnotationComposer
|
||||||
@@ -294,12 +277,6 @@ class $$LocalAssetEntityTableAnnotationComposer
|
|||||||
|
|
||||||
i0.GeneratedColumn<double> get longitude =>
|
i0.GeneratedColumn<double> get longitude =>
|
||||||
$composableBuilder(column: $table.longitude, builder: (column) => column);
|
$composableBuilder(column: $table.longitude, builder: (column) => column);
|
||||||
|
|
||||||
i0.GeneratedColumnWithTypeConverter<i2.AssetPlaybackStyle, int>
|
|
||||||
get playbackStyle => $composableBuilder(
|
|
||||||
column: $table.playbackStyle,
|
|
||||||
builder: (column) => column,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$LocalAssetEntityTableTableManager
|
class $$LocalAssetEntityTableTableManager
|
||||||
@@ -357,8 +334,6 @@ class $$LocalAssetEntityTableTableManager
|
|||||||
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
||||||
i0.Value<double?> latitude = const i0.Value.absent(),
|
i0.Value<double?> latitude = const i0.Value.absent(),
|
||||||
i0.Value<double?> longitude = const i0.Value.absent(),
|
i0.Value<double?> longitude = const i0.Value.absent(),
|
||||||
i0.Value<i2.AssetPlaybackStyle> playbackStyle =
|
|
||||||
const i0.Value.absent(),
|
|
||||||
}) => i1.LocalAssetEntityCompanion(
|
}) => i1.LocalAssetEntityCompanion(
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
@@ -375,7 +350,6 @@ class $$LocalAssetEntityTableTableManager
|
|||||||
adjustmentTime: adjustmentTime,
|
adjustmentTime: adjustmentTime,
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude,
|
||||||
playbackStyle: playbackStyle,
|
|
||||||
),
|
),
|
||||||
createCompanionCallback:
|
createCompanionCallback:
|
||||||
({
|
({
|
||||||
@@ -394,8 +368,6 @@ class $$LocalAssetEntityTableTableManager
|
|||||||
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
||||||
i0.Value<double?> latitude = const i0.Value.absent(),
|
i0.Value<double?> latitude = const i0.Value.absent(),
|
||||||
i0.Value<double?> longitude = const i0.Value.absent(),
|
i0.Value<double?> longitude = const i0.Value.absent(),
|
||||||
i0.Value<i2.AssetPlaybackStyle> playbackStyle =
|
|
||||||
const i0.Value.absent(),
|
|
||||||
}) => i1.LocalAssetEntityCompanion.insert(
|
}) => i1.LocalAssetEntityCompanion.insert(
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
@@ -412,7 +384,6 @@ class $$LocalAssetEntityTableTableManager
|
|||||||
adjustmentTime: adjustmentTime,
|
adjustmentTime: adjustmentTime,
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude,
|
||||||
playbackStyle: playbackStyle,
|
|
||||||
),
|
),
|
||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||||
@@ -625,19 +596,6 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
|||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
late final i0.GeneratedColumnWithTypeConverter<i2.AssetPlaybackStyle, int>
|
|
||||||
playbackStyle =
|
|
||||||
i0.GeneratedColumn<int>(
|
|
||||||
'playback_style',
|
|
||||||
aliasedName,
|
|
||||||
false,
|
|
||||||
type: i0.DriftSqlType.int,
|
|
||||||
requiredDuringInsert: false,
|
|
||||||
defaultValue: const i4.Constant(0),
|
|
||||||
).withConverter<i2.AssetPlaybackStyle>(
|
|
||||||
i1.$LocalAssetEntityTable.$converterplaybackStyle,
|
|
||||||
);
|
|
||||||
@override
|
|
||||||
List<i0.GeneratedColumn> get $columns => [
|
List<i0.GeneratedColumn> get $columns => [
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
@@ -654,7 +612,6 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
|||||||
adjustmentTime,
|
adjustmentTime,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
playbackStyle,
|
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? actualTableName;
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
@@ -836,12 +793,6 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
|||||||
i0.DriftSqlType.double,
|
i0.DriftSqlType.double,
|
||||||
data['${effectivePrefix}longitude'],
|
data['${effectivePrefix}longitude'],
|
||||||
),
|
),
|
||||||
playbackStyle: i1.$LocalAssetEntityTable.$converterplaybackStyle.fromSql(
|
|
||||||
attachedDatabase.typeMapping.read(
|
|
||||||
i0.DriftSqlType.int,
|
|
||||||
data['${effectivePrefix}playback_style'],
|
|
||||||
)!,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -852,10 +803,6 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
|||||||
|
|
||||||
static i0.JsonTypeConverter2<i2.AssetType, int, int> $convertertype =
|
static i0.JsonTypeConverter2<i2.AssetType, int, int> $convertertype =
|
||||||
const i0.EnumIndexConverter<i2.AssetType>(i2.AssetType.values);
|
const i0.EnumIndexConverter<i2.AssetType>(i2.AssetType.values);
|
||||||
static i0.JsonTypeConverter2<i2.AssetPlaybackStyle, int, int>
|
|
||||||
$converterplaybackStyle = const i0.EnumIndexConverter<i2.AssetPlaybackStyle>(
|
|
||||||
i2.AssetPlaybackStyle.values,
|
|
||||||
);
|
|
||||||
@override
|
@override
|
||||||
bool get withoutRowId => true;
|
bool get withoutRowId => true;
|
||||||
@override
|
@override
|
||||||
@@ -879,7 +826,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
final DateTime? adjustmentTime;
|
final DateTime? adjustmentTime;
|
||||||
final double? latitude;
|
final double? latitude;
|
||||||
final double? longitude;
|
final double? longitude;
|
||||||
final i2.AssetPlaybackStyle playbackStyle;
|
|
||||||
const LocalAssetEntityData({
|
const LocalAssetEntityData({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.type,
|
required this.type,
|
||||||
@@ -896,7 +842,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
this.adjustmentTime,
|
this.adjustmentTime,
|
||||||
this.latitude,
|
this.latitude,
|
||||||
this.longitude,
|
this.longitude,
|
||||||
required this.playbackStyle,
|
|
||||||
});
|
});
|
||||||
@override
|
@override
|
||||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
@@ -936,11 +881,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
if (!nullToAbsent || longitude != null) {
|
if (!nullToAbsent || longitude != null) {
|
||||||
map['longitude'] = i0.Variable<double>(longitude);
|
map['longitude'] = i0.Variable<double>(longitude);
|
||||||
}
|
}
|
||||||
{
|
|
||||||
map['playback_style'] = i0.Variable<int>(
|
|
||||||
i1.$LocalAssetEntityTable.$converterplaybackStyle.toSql(playbackStyle),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,9 +907,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
adjustmentTime: serializer.fromJson<DateTime?>(json['adjustmentTime']),
|
adjustmentTime: serializer.fromJson<DateTime?>(json['adjustmentTime']),
|
||||||
latitude: serializer.fromJson<double?>(json['latitude']),
|
latitude: serializer.fromJson<double?>(json['latitude']),
|
||||||
longitude: serializer.fromJson<double?>(json['longitude']),
|
longitude: serializer.fromJson<double?>(json['longitude']),
|
||||||
playbackStyle: i1.$LocalAssetEntityTable.$converterplaybackStyle.fromJson(
|
|
||||||
serializer.fromJson<int>(json['playbackStyle']),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
@@ -993,9 +930,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
'adjustmentTime': serializer.toJson<DateTime?>(adjustmentTime),
|
'adjustmentTime': serializer.toJson<DateTime?>(adjustmentTime),
|
||||||
'latitude': serializer.toJson<double?>(latitude),
|
'latitude': serializer.toJson<double?>(latitude),
|
||||||
'longitude': serializer.toJson<double?>(longitude),
|
'longitude': serializer.toJson<double?>(longitude),
|
||||||
'playbackStyle': serializer.toJson<int>(
|
|
||||||
i1.$LocalAssetEntityTable.$converterplaybackStyle.toJson(playbackStyle),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1015,7 +949,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
||||||
i0.Value<double?> latitude = const i0.Value.absent(),
|
i0.Value<double?> latitude = const i0.Value.absent(),
|
||||||
i0.Value<double?> longitude = const i0.Value.absent(),
|
i0.Value<double?> longitude = const i0.Value.absent(),
|
||||||
i2.AssetPlaybackStyle? playbackStyle,
|
|
||||||
}) => i1.LocalAssetEntityData(
|
}) => i1.LocalAssetEntityData(
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
type: type ?? this.type,
|
type: type ?? this.type,
|
||||||
@@ -1036,7 +969,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
: this.adjustmentTime,
|
: this.adjustmentTime,
|
||||||
latitude: latitude.present ? latitude.value : this.latitude,
|
latitude: latitude.present ? latitude.value : this.latitude,
|
||||||
longitude: longitude.present ? longitude.value : this.longitude,
|
longitude: longitude.present ? longitude.value : this.longitude,
|
||||||
playbackStyle: playbackStyle ?? this.playbackStyle,
|
|
||||||
);
|
);
|
||||||
LocalAssetEntityData copyWithCompanion(i1.LocalAssetEntityCompanion data) {
|
LocalAssetEntityData copyWithCompanion(i1.LocalAssetEntityCompanion data) {
|
||||||
return LocalAssetEntityData(
|
return LocalAssetEntityData(
|
||||||
@@ -1063,9 +995,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
: this.adjustmentTime,
|
: this.adjustmentTime,
|
||||||
latitude: data.latitude.present ? data.latitude.value : this.latitude,
|
latitude: data.latitude.present ? data.latitude.value : this.latitude,
|
||||||
longitude: data.longitude.present ? data.longitude.value : this.longitude,
|
longitude: data.longitude.present ? data.longitude.value : this.longitude,
|
||||||
playbackStyle: data.playbackStyle.present
|
|
||||||
? data.playbackStyle.value
|
|
||||||
: this.playbackStyle,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1086,8 +1015,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
..write('iCloudId: $iCloudId, ')
|
..write('iCloudId: $iCloudId, ')
|
||||||
..write('adjustmentTime: $adjustmentTime, ')
|
..write('adjustmentTime: $adjustmentTime, ')
|
||||||
..write('latitude: $latitude, ')
|
..write('latitude: $latitude, ')
|
||||||
..write('longitude: $longitude, ')
|
..write('longitude: $longitude')
|
||||||
..write('playbackStyle: $playbackStyle')
|
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
@@ -1109,7 +1037,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
adjustmentTime,
|
adjustmentTime,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
playbackStyle,
|
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
@@ -1129,8 +1056,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
other.iCloudId == this.iCloudId &&
|
other.iCloudId == this.iCloudId &&
|
||||||
other.adjustmentTime == this.adjustmentTime &&
|
other.adjustmentTime == this.adjustmentTime &&
|
||||||
other.latitude == this.latitude &&
|
other.latitude == this.latitude &&
|
||||||
other.longitude == this.longitude &&
|
other.longitude == this.longitude);
|
||||||
other.playbackStyle == this.playbackStyle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalAssetEntityCompanion
|
class LocalAssetEntityCompanion
|
||||||
@@ -1150,7 +1076,6 @@ class LocalAssetEntityCompanion
|
|||||||
final i0.Value<DateTime?> adjustmentTime;
|
final i0.Value<DateTime?> adjustmentTime;
|
||||||
final i0.Value<double?> latitude;
|
final i0.Value<double?> latitude;
|
||||||
final i0.Value<double?> longitude;
|
final i0.Value<double?> longitude;
|
||||||
final i0.Value<i2.AssetPlaybackStyle> playbackStyle;
|
|
||||||
const LocalAssetEntityCompanion({
|
const LocalAssetEntityCompanion({
|
||||||
this.name = const i0.Value.absent(),
|
this.name = const i0.Value.absent(),
|
||||||
this.type = const i0.Value.absent(),
|
this.type = const i0.Value.absent(),
|
||||||
@@ -1167,7 +1092,6 @@ class LocalAssetEntityCompanion
|
|||||||
this.adjustmentTime = const i0.Value.absent(),
|
this.adjustmentTime = const i0.Value.absent(),
|
||||||
this.latitude = const i0.Value.absent(),
|
this.latitude = const i0.Value.absent(),
|
||||||
this.longitude = const i0.Value.absent(),
|
this.longitude = const i0.Value.absent(),
|
||||||
this.playbackStyle = const i0.Value.absent(),
|
|
||||||
});
|
});
|
||||||
LocalAssetEntityCompanion.insert({
|
LocalAssetEntityCompanion.insert({
|
||||||
required String name,
|
required String name,
|
||||||
@@ -1185,7 +1109,6 @@ class LocalAssetEntityCompanion
|
|||||||
this.adjustmentTime = const i0.Value.absent(),
|
this.adjustmentTime = const i0.Value.absent(),
|
||||||
this.latitude = const i0.Value.absent(),
|
this.latitude = const i0.Value.absent(),
|
||||||
this.longitude = const i0.Value.absent(),
|
this.longitude = const i0.Value.absent(),
|
||||||
this.playbackStyle = const i0.Value.absent(),
|
|
||||||
}) : name = i0.Value(name),
|
}) : name = i0.Value(name),
|
||||||
type = i0.Value(type),
|
type = i0.Value(type),
|
||||||
id = i0.Value(id);
|
id = i0.Value(id);
|
||||||
@@ -1205,7 +1128,6 @@ class LocalAssetEntityCompanion
|
|||||||
i0.Expression<DateTime>? adjustmentTime,
|
i0.Expression<DateTime>? adjustmentTime,
|
||||||
i0.Expression<double>? latitude,
|
i0.Expression<double>? latitude,
|
||||||
i0.Expression<double>? longitude,
|
i0.Expression<double>? longitude,
|
||||||
i0.Expression<int>? playbackStyle,
|
|
||||||
}) {
|
}) {
|
||||||
return i0.RawValuesInsertable({
|
return i0.RawValuesInsertable({
|
||||||
if (name != null) 'name': name,
|
if (name != null) 'name': name,
|
||||||
@@ -1223,7 +1145,6 @@ class LocalAssetEntityCompanion
|
|||||||
if (adjustmentTime != null) 'adjustment_time': adjustmentTime,
|
if (adjustmentTime != null) 'adjustment_time': adjustmentTime,
|
||||||
if (latitude != null) 'latitude': latitude,
|
if (latitude != null) 'latitude': latitude,
|
||||||
if (longitude != null) 'longitude': longitude,
|
if (longitude != null) 'longitude': longitude,
|
||||||
if (playbackStyle != null) 'playback_style': playbackStyle,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1243,7 +1164,6 @@ class LocalAssetEntityCompanion
|
|||||||
i0.Value<DateTime?>? adjustmentTime,
|
i0.Value<DateTime?>? adjustmentTime,
|
||||||
i0.Value<double?>? latitude,
|
i0.Value<double?>? latitude,
|
||||||
i0.Value<double?>? longitude,
|
i0.Value<double?>? longitude,
|
||||||
i0.Value<i2.AssetPlaybackStyle>? playbackStyle,
|
|
||||||
}) {
|
}) {
|
||||||
return i1.LocalAssetEntityCompanion(
|
return i1.LocalAssetEntityCompanion(
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
@@ -1261,7 +1181,6 @@ class LocalAssetEntityCompanion
|
|||||||
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
|
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
|
||||||
latitude: latitude ?? this.latitude,
|
latitude: latitude ?? this.latitude,
|
||||||
longitude: longitude ?? this.longitude,
|
longitude: longitude ?? this.longitude,
|
||||||
playbackStyle: playbackStyle ?? this.playbackStyle,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1315,13 +1234,6 @@ class LocalAssetEntityCompanion
|
|||||||
if (longitude.present) {
|
if (longitude.present) {
|
||||||
map['longitude'] = i0.Variable<double>(longitude.value);
|
map['longitude'] = i0.Variable<double>(longitude.value);
|
||||||
}
|
}
|
||||||
if (playbackStyle.present) {
|
|
||||||
map['playback_style'] = i0.Variable<int>(
|
|
||||||
i1.$LocalAssetEntityTable.$converterplaybackStyle.toSql(
|
|
||||||
playbackStyle.value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1342,8 +1254,7 @@ class LocalAssetEntityCompanion
|
|||||||
..write('iCloudId: $iCloudId, ')
|
..write('iCloudId: $iCloudId, ')
|
||||||
..write('adjustmentTime: $adjustmentTime, ')
|
..write('adjustmentTime: $adjustmentTime, ')
|
||||||
..write('latitude: $latitude, ')
|
..write('latitude: $latitude, ')
|
||||||
..write('longitude: $longitude, ')
|
..write('longitude: $longitude')
|
||||||
..write('playbackStyle: $playbackStyle')
|
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ SELECT
|
|||||||
NULL as latitude,
|
NULL as latitude,
|
||||||
NULL as longitude,
|
NULL as longitude,
|
||||||
NULL as adjustmentTime,
|
NULL as adjustmentTime,
|
||||||
rae.is_edited,
|
rae.is_edited
|
||||||
0 as playback_style
|
|
||||||
FROM
|
FROM
|
||||||
remote_asset_entity rae
|
remote_asset_entity rae
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
@@ -64,8 +63,7 @@ SELECT
|
|||||||
lae.latitude,
|
lae.latitude,
|
||||||
lae.longitude,
|
lae.longitude,
|
||||||
lae.adjustment_time,
|
lae.adjustment_time,
|
||||||
0 as is_edited,
|
0 as is_edited
|
||||||
lae.playback_style
|
|
||||||
FROM
|
FROM
|
||||||
local_asset_entity lae
|
local_asset_entity lae
|
||||||
WHERE NOT EXISTS (
|
WHERE NOT EXISTS (
|
||||||
|
|||||||
+1
-4
@@ -29,7 +29,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
|
|||||||
);
|
);
|
||||||
$arrayStartIndex += generatedlimit.amountOfVariables;
|
$arrayStartIndex += generatedlimit.amountOfVariables;
|
||||||
return customSelect(
|
return customSelect(
|
||||||
'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited, 0 AS playback_style FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_in_seconds, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited, lae.playback_style FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}',
|
'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_in_seconds, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}',
|
||||||
variables: [
|
variables: [
|
||||||
for (var $ in userIds) i0.Variable<String>($),
|
for (var $ in userIds) i0.Variable<String>($),
|
||||||
...generatedlimit.introducedVariables,
|
...generatedlimit.introducedVariables,
|
||||||
@@ -67,7 +67,6 @@ class MergedAssetDrift extends i1.ModularAccessor {
|
|||||||
longitude: row.readNullable<double>('longitude'),
|
longitude: row.readNullable<double>('longitude'),
|
||||||
adjustmentTime: row.readNullable<DateTime>('adjustmentTime'),
|
adjustmentTime: row.readNullable<DateTime>('adjustmentTime'),
|
||||||
isEdited: row.read<bool>('is_edited'),
|
isEdited: row.read<bool>('is_edited'),
|
||||||
playbackStyle: row.read<int>('playback_style'),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -140,7 +139,6 @@ class MergedAssetResult {
|
|||||||
final double? longitude;
|
final double? longitude;
|
||||||
final DateTime? adjustmentTime;
|
final DateTime? adjustmentTime;
|
||||||
final bool isEdited;
|
final bool isEdited;
|
||||||
final int playbackStyle;
|
|
||||||
MergedAssetResult({
|
MergedAssetResult({
|
||||||
this.remoteId,
|
this.remoteId,
|
||||||
this.localId,
|
this.localId,
|
||||||
@@ -163,7 +161,6 @@ class MergedAssetResult {
|
|||||||
this.longitude,
|
this.longitude,
|
||||||
this.adjustmentTime,
|
this.adjustmentTime,
|
||||||
required this.isEdited,
|
required this.isEdited,
|
||||||
required this.playbackStyle,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ class TrashedLocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntity
|
|||||||
|
|
||||||
IntColumn get source => intEnum<TrashOrigin>()();
|
IntColumn get source => intEnum<TrashOrigin>()();
|
||||||
|
|
||||||
IntColumn get playbackStyle => intEnum<AssetPlaybackStyle>().withDefault(const Constant(0))();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id, albumId};
|
Set<Column> get primaryKey => {id, albumId};
|
||||||
}
|
}
|
||||||
@@ -47,7 +45,6 @@ extension TrashedLocalAssetEntityDataDomainExtension on TrashedLocalAssetEntityD
|
|||||||
height: height,
|
height: height,
|
||||||
width: width,
|
width: width,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
playbackStyle: playbackStyle,
|
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ typedef $$TrashedLocalAssetEntityTableCreateCompanionBuilder =
|
|||||||
i0.Value<bool> isFavorite,
|
i0.Value<bool> isFavorite,
|
||||||
i0.Value<int> orientation,
|
i0.Value<int> orientation,
|
||||||
required i3.TrashOrigin source,
|
required i3.TrashOrigin source,
|
||||||
i0.Value<i2.AssetPlaybackStyle> playbackStyle,
|
|
||||||
});
|
});
|
||||||
typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder =
|
typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder =
|
||||||
i1.TrashedLocalAssetEntityCompanion Function({
|
i1.TrashedLocalAssetEntityCompanion Function({
|
||||||
@@ -40,7 +39,6 @@ typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder =
|
|||||||
i0.Value<bool> isFavorite,
|
i0.Value<bool> isFavorite,
|
||||||
i0.Value<int> orientation,
|
i0.Value<int> orientation,
|
||||||
i0.Value<i3.TrashOrigin> source,
|
i0.Value<i3.TrashOrigin> source,
|
||||||
i0.Value<i2.AssetPlaybackStyle> playbackStyle,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
class $$TrashedLocalAssetEntityTableFilterComposer
|
class $$TrashedLocalAssetEntityTableFilterComposer
|
||||||
@@ -119,16 +117,6 @@ class $$TrashedLocalAssetEntityTableFilterComposer
|
|||||||
column: $table.source,
|
column: $table.source,
|
||||||
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
i0.ColumnWithTypeConverterFilters<
|
|
||||||
i2.AssetPlaybackStyle,
|
|
||||||
i2.AssetPlaybackStyle,
|
|
||||||
int
|
|
||||||
>
|
|
||||||
get playbackStyle => $composableBuilder(
|
|
||||||
column: $table.playbackStyle,
|
|
||||||
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$TrashedLocalAssetEntityTableOrderingComposer
|
class $$TrashedLocalAssetEntityTableOrderingComposer
|
||||||
@@ -205,11 +193,6 @@ class $$TrashedLocalAssetEntityTableOrderingComposer
|
|||||||
column: $table.source,
|
column: $table.source,
|
||||||
builder: (column) => i0.ColumnOrderings(column),
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
i0.ColumnOrderings<int> get playbackStyle => $composableBuilder(
|
|
||||||
column: $table.playbackStyle,
|
|
||||||
builder: (column) => i0.ColumnOrderings(column),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$TrashedLocalAssetEntityTableAnnotationComposer
|
class $$TrashedLocalAssetEntityTableAnnotationComposer
|
||||||
@@ -266,12 +249,6 @@ class $$TrashedLocalAssetEntityTableAnnotationComposer
|
|||||||
|
|
||||||
i0.GeneratedColumnWithTypeConverter<i3.TrashOrigin, int> get source =>
|
i0.GeneratedColumnWithTypeConverter<i3.TrashOrigin, int> get source =>
|
||||||
$composableBuilder(column: $table.source, builder: (column) => column);
|
$composableBuilder(column: $table.source, builder: (column) => column);
|
||||||
|
|
||||||
i0.GeneratedColumnWithTypeConverter<i2.AssetPlaybackStyle, int>
|
|
||||||
get playbackStyle => $composableBuilder(
|
|
||||||
column: $table.playbackStyle,
|
|
||||||
builder: (column) => column,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$TrashedLocalAssetEntityTableTableManager
|
class $$TrashedLocalAssetEntityTableTableManager
|
||||||
@@ -333,8 +310,6 @@ class $$TrashedLocalAssetEntityTableTableManager
|
|||||||
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||||
i0.Value<int> orientation = const i0.Value.absent(),
|
i0.Value<int> orientation = const i0.Value.absent(),
|
||||||
i0.Value<i3.TrashOrigin> source = const i0.Value.absent(),
|
i0.Value<i3.TrashOrigin> source = const i0.Value.absent(),
|
||||||
i0.Value<i2.AssetPlaybackStyle> playbackStyle =
|
|
||||||
const i0.Value.absent(),
|
|
||||||
}) => i1.TrashedLocalAssetEntityCompanion(
|
}) => i1.TrashedLocalAssetEntityCompanion(
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
@@ -349,7 +324,6 @@ class $$TrashedLocalAssetEntityTableTableManager
|
|||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
source: source,
|
source: source,
|
||||||
playbackStyle: playbackStyle,
|
|
||||||
),
|
),
|
||||||
createCompanionCallback:
|
createCompanionCallback:
|
||||||
({
|
({
|
||||||
@@ -366,8 +340,6 @@ class $$TrashedLocalAssetEntityTableTableManager
|
|||||||
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||||
i0.Value<int> orientation = const i0.Value.absent(),
|
i0.Value<int> orientation = const i0.Value.absent(),
|
||||||
required i3.TrashOrigin source,
|
required i3.TrashOrigin source,
|
||||||
i0.Value<i2.AssetPlaybackStyle> playbackStyle =
|
|
||||||
const i0.Value.absent(),
|
|
||||||
}) => i1.TrashedLocalAssetEntityCompanion.insert(
|
}) => i1.TrashedLocalAssetEntityCompanion.insert(
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
@@ -382,7 +354,6 @@ class $$TrashedLocalAssetEntityTableTableManager
|
|||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
source: source,
|
source: source,
|
||||||
playbackStyle: playbackStyle,
|
|
||||||
),
|
),
|
||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||||
@@ -579,19 +550,6 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
|
|||||||
i1.$TrashedLocalAssetEntityTable.$convertersource,
|
i1.$TrashedLocalAssetEntityTable.$convertersource,
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
late final i0.GeneratedColumnWithTypeConverter<i2.AssetPlaybackStyle, int>
|
|
||||||
playbackStyle =
|
|
||||||
i0.GeneratedColumn<int>(
|
|
||||||
'playback_style',
|
|
||||||
aliasedName,
|
|
||||||
false,
|
|
||||||
type: i0.DriftSqlType.int,
|
|
||||||
requiredDuringInsert: false,
|
|
||||||
defaultValue: const i4.Constant(0),
|
|
||||||
).withConverter<i2.AssetPlaybackStyle>(
|
|
||||||
i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle,
|
|
||||||
);
|
|
||||||
@override
|
|
||||||
List<i0.GeneratedColumn> get $columns => [
|
List<i0.GeneratedColumn> get $columns => [
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
@@ -606,7 +564,6 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
|
|||||||
isFavorite,
|
isFavorite,
|
||||||
orientation,
|
orientation,
|
||||||
source,
|
source,
|
||||||
playbackStyle,
|
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? actualTableName;
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
@@ -763,13 +720,6 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
|
|||||||
data['${effectivePrefix}source'],
|
data['${effectivePrefix}source'],
|
||||||
)!,
|
)!,
|
||||||
),
|
),
|
||||||
playbackStyle: i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle
|
|
||||||
.fromSql(
|
|
||||||
attachedDatabase.typeMapping.read(
|
|
||||||
i0.DriftSqlType.int,
|
|
||||||
data['${effectivePrefix}playback_style'],
|
|
||||||
)!,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -782,10 +732,6 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
|
|||||||
const i0.EnumIndexConverter<i2.AssetType>(i2.AssetType.values);
|
const i0.EnumIndexConverter<i2.AssetType>(i2.AssetType.values);
|
||||||
static i0.JsonTypeConverter2<i3.TrashOrigin, int, int> $convertersource =
|
static i0.JsonTypeConverter2<i3.TrashOrigin, int, int> $convertersource =
|
||||||
const i0.EnumIndexConverter<i3.TrashOrigin>(i3.TrashOrigin.values);
|
const i0.EnumIndexConverter<i3.TrashOrigin>(i3.TrashOrigin.values);
|
||||||
static i0.JsonTypeConverter2<i2.AssetPlaybackStyle, int, int>
|
|
||||||
$converterplaybackStyle = const i0.EnumIndexConverter<i2.AssetPlaybackStyle>(
|
|
||||||
i2.AssetPlaybackStyle.values,
|
|
||||||
);
|
|
||||||
@override
|
@override
|
||||||
bool get withoutRowId => true;
|
bool get withoutRowId => true;
|
||||||
@override
|
@override
|
||||||
@@ -807,7 +753,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
final bool isFavorite;
|
final bool isFavorite;
|
||||||
final int orientation;
|
final int orientation;
|
||||||
final i3.TrashOrigin source;
|
final i3.TrashOrigin source;
|
||||||
final i2.AssetPlaybackStyle playbackStyle;
|
|
||||||
const TrashedLocalAssetEntityData({
|
const TrashedLocalAssetEntityData({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.type,
|
required this.type,
|
||||||
@@ -822,7 +767,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
required this.isFavorite,
|
required this.isFavorite,
|
||||||
required this.orientation,
|
required this.orientation,
|
||||||
required this.source,
|
required this.source,
|
||||||
required this.playbackStyle,
|
|
||||||
});
|
});
|
||||||
@override
|
@override
|
||||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
@@ -856,13 +800,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
i1.$TrashedLocalAssetEntityTable.$convertersource.toSql(source),
|
i1.$TrashedLocalAssetEntityTable.$convertersource.toSql(source),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
{
|
|
||||||
map['playback_style'] = i0.Variable<int>(
|
|
||||||
i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle.toSql(
|
|
||||||
playbackStyle,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -889,8 +826,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
source: i1.$TrashedLocalAssetEntityTable.$convertersource.fromJson(
|
source: i1.$TrashedLocalAssetEntityTable.$convertersource.fromJson(
|
||||||
serializer.fromJson<int>(json['source']),
|
serializer.fromJson<int>(json['source']),
|
||||||
),
|
),
|
||||||
playbackStyle: i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle
|
|
||||||
.fromJson(serializer.fromJson<int>(json['playbackStyle'])),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
@@ -914,11 +849,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
'source': serializer.toJson<int>(
|
'source': serializer.toJson<int>(
|
||||||
i1.$TrashedLocalAssetEntityTable.$convertersource.toJson(source),
|
i1.$TrashedLocalAssetEntityTable.$convertersource.toJson(source),
|
||||||
),
|
),
|
||||||
'playbackStyle': serializer.toJson<int>(
|
|
||||||
i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle.toJson(
|
|
||||||
playbackStyle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -936,7 +866,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
bool? isFavorite,
|
bool? isFavorite,
|
||||||
int? orientation,
|
int? orientation,
|
||||||
i3.TrashOrigin? source,
|
i3.TrashOrigin? source,
|
||||||
i2.AssetPlaybackStyle? playbackStyle,
|
|
||||||
}) => i1.TrashedLocalAssetEntityData(
|
}) => i1.TrashedLocalAssetEntityData(
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
type: type ?? this.type,
|
type: type ?? this.type,
|
||||||
@@ -953,7 +882,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
orientation: orientation ?? this.orientation,
|
orientation: orientation ?? this.orientation,
|
||||||
source: source ?? this.source,
|
source: source ?? this.source,
|
||||||
playbackStyle: playbackStyle ?? this.playbackStyle,
|
|
||||||
);
|
);
|
||||||
TrashedLocalAssetEntityData copyWithCompanion(
|
TrashedLocalAssetEntityData copyWithCompanion(
|
||||||
i1.TrashedLocalAssetEntityCompanion data,
|
i1.TrashedLocalAssetEntityCompanion data,
|
||||||
@@ -978,9 +906,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
? data.orientation.value
|
? data.orientation.value
|
||||||
: this.orientation,
|
: this.orientation,
|
||||||
source: data.source.present ? data.source.value : this.source,
|
source: data.source.present ? data.source.value : this.source,
|
||||||
playbackStyle: data.playbackStyle.present
|
|
||||||
? data.playbackStyle.value
|
|
||||||
: this.playbackStyle,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -999,8 +924,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
..write('checksum: $checksum, ')
|
..write('checksum: $checksum, ')
|
||||||
..write('isFavorite: $isFavorite, ')
|
..write('isFavorite: $isFavorite, ')
|
||||||
..write('orientation: $orientation, ')
|
..write('orientation: $orientation, ')
|
||||||
..write('source: $source, ')
|
..write('source: $source')
|
||||||
..write('playbackStyle: $playbackStyle')
|
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
@@ -1020,7 +944,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
isFavorite,
|
isFavorite,
|
||||||
orientation,
|
orientation,
|
||||||
source,
|
source,
|
||||||
playbackStyle,
|
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
@@ -1038,8 +961,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
other.checksum == this.checksum &&
|
other.checksum == this.checksum &&
|
||||||
other.isFavorite == this.isFavorite &&
|
other.isFavorite == this.isFavorite &&
|
||||||
other.orientation == this.orientation &&
|
other.orientation == this.orientation &&
|
||||||
other.source == this.source &&
|
other.source == this.source);
|
||||||
other.playbackStyle == this.playbackStyle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TrashedLocalAssetEntityCompanion
|
class TrashedLocalAssetEntityCompanion
|
||||||
@@ -1057,7 +979,6 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
final i0.Value<bool> isFavorite;
|
final i0.Value<bool> isFavorite;
|
||||||
final i0.Value<int> orientation;
|
final i0.Value<int> orientation;
|
||||||
final i0.Value<i3.TrashOrigin> source;
|
final i0.Value<i3.TrashOrigin> source;
|
||||||
final i0.Value<i2.AssetPlaybackStyle> playbackStyle;
|
|
||||||
const TrashedLocalAssetEntityCompanion({
|
const TrashedLocalAssetEntityCompanion({
|
||||||
this.name = const i0.Value.absent(),
|
this.name = const i0.Value.absent(),
|
||||||
this.type = const i0.Value.absent(),
|
this.type = const i0.Value.absent(),
|
||||||
@@ -1072,7 +993,6 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
this.isFavorite = const i0.Value.absent(),
|
this.isFavorite = const i0.Value.absent(),
|
||||||
this.orientation = const i0.Value.absent(),
|
this.orientation = const i0.Value.absent(),
|
||||||
this.source = const i0.Value.absent(),
|
this.source = const i0.Value.absent(),
|
||||||
this.playbackStyle = const i0.Value.absent(),
|
|
||||||
});
|
});
|
||||||
TrashedLocalAssetEntityCompanion.insert({
|
TrashedLocalAssetEntityCompanion.insert({
|
||||||
required String name,
|
required String name,
|
||||||
@@ -1088,7 +1008,6 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
this.isFavorite = const i0.Value.absent(),
|
this.isFavorite = const i0.Value.absent(),
|
||||||
this.orientation = const i0.Value.absent(),
|
this.orientation = const i0.Value.absent(),
|
||||||
required i3.TrashOrigin source,
|
required i3.TrashOrigin source,
|
||||||
this.playbackStyle = const i0.Value.absent(),
|
|
||||||
}) : name = i0.Value(name),
|
}) : name = i0.Value(name),
|
||||||
type = i0.Value(type),
|
type = i0.Value(type),
|
||||||
id = i0.Value(id),
|
id = i0.Value(id),
|
||||||
@@ -1108,7 +1027,6 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
i0.Expression<bool>? isFavorite,
|
i0.Expression<bool>? isFavorite,
|
||||||
i0.Expression<int>? orientation,
|
i0.Expression<int>? orientation,
|
||||||
i0.Expression<int>? source,
|
i0.Expression<int>? source,
|
||||||
i0.Expression<int>? playbackStyle,
|
|
||||||
}) {
|
}) {
|
||||||
return i0.RawValuesInsertable({
|
return i0.RawValuesInsertable({
|
||||||
if (name != null) 'name': name,
|
if (name != null) 'name': name,
|
||||||
@@ -1124,7 +1042,6 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
if (isFavorite != null) 'is_favorite': isFavorite,
|
if (isFavorite != null) 'is_favorite': isFavorite,
|
||||||
if (orientation != null) 'orientation': orientation,
|
if (orientation != null) 'orientation': orientation,
|
||||||
if (source != null) 'source': source,
|
if (source != null) 'source': source,
|
||||||
if (playbackStyle != null) 'playback_style': playbackStyle,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1142,7 +1059,6 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
i0.Value<bool>? isFavorite,
|
i0.Value<bool>? isFavorite,
|
||||||
i0.Value<int>? orientation,
|
i0.Value<int>? orientation,
|
||||||
i0.Value<i3.TrashOrigin>? source,
|
i0.Value<i3.TrashOrigin>? source,
|
||||||
i0.Value<i2.AssetPlaybackStyle>? playbackStyle,
|
|
||||||
}) {
|
}) {
|
||||||
return i1.TrashedLocalAssetEntityCompanion(
|
return i1.TrashedLocalAssetEntityCompanion(
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
@@ -1158,7 +1074,6 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
orientation: orientation ?? this.orientation,
|
orientation: orientation ?? this.orientation,
|
||||||
source: source ?? this.source,
|
source: source ?? this.source,
|
||||||
playbackStyle: playbackStyle ?? this.playbackStyle,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1208,13 +1123,6 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
i1.$TrashedLocalAssetEntityTable.$convertersource.toSql(source.value),
|
i1.$TrashedLocalAssetEntityTable.$convertersource.toSql(source.value),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (playbackStyle.present) {
|
|
||||||
map['playback_style'] = i0.Variable<int>(
|
|
||||||
i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle.toSql(
|
|
||||||
playbackStyle.value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1233,8 +1141,7 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
..write('checksum: $checksum, ')
|
..write('checksum: $checksum, ')
|
||||||
..write('isFavorite: $isFavorite, ')
|
..write('isFavorite: $isFavorite, ')
|
||||||
..write('orientation: $orientation, ')
|
..write('orientation: $orientation, ')
|
||||||
..write('source: $source, ')
|
..write('source: $source')
|
||||||
..write('playbackStyle: $playbackStyle')
|
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ abstract class ImageRequest {
|
|||||||
|
|
||||||
Future<ImageInfo?> load(ImageDecoderCallback decode, {double scale = 1.0});
|
Future<ImageInfo?> load(ImageDecoderCallback decode, {double scale = 1.0});
|
||||||
|
|
||||||
Future<ui.Codec?> loadCodec();
|
|
||||||
|
|
||||||
void cancel() {
|
void cancel() {
|
||||||
if (_isCancelled) {
|
if (_isCancelled) {
|
||||||
return;
|
return;
|
||||||
@@ -36,7 +34,7 @@ abstract class ImageRequest {
|
|||||||
|
|
||||||
void _onCancelled();
|
void _onCancelled();
|
||||||
|
|
||||||
Future<(ui.Codec, ui.ImageDescriptor)?> _codecFromEncodedPlatformImage(int address, int length) async {
|
Future<ui.FrameInfo?> _fromEncodedPlatformImage(int address, int length) async {
|
||||||
final pointer = Pointer<Uint8>.fromAddress(address);
|
final pointer = Pointer<Uint8>.fromAddress(address);
|
||||||
if (_isCancelled) {
|
if (_isCancelled) {
|
||||||
malloc.free(pointer);
|
malloc.free(pointer);
|
||||||
@@ -69,20 +67,6 @@ abstract class ImageRequest {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (codec, descriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ui.FrameInfo?> _fromEncodedPlatformImage(int address, int length) async {
|
|
||||||
final result = await _codecFromEncodedPlatformImage(address, length);
|
|
||||||
if (result == null) return null;
|
|
||||||
|
|
||||||
final (codec, descriptor) = result;
|
|
||||||
if (_isCancelled) {
|
|
||||||
descriptor.dispose();
|
|
||||||
codec.dispose();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final frame = await codec.getNextFrame();
|
final frame = await codec.getNextFrame();
|
||||||
descriptor.dispose();
|
descriptor.dispose();
|
||||||
codec.dispose();
|
codec.dispose();
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ class LocalImageRequest extends ImageRequest {
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
isVideo: assetType == AssetType.video,
|
isVideo: assetType == AssetType.video,
|
||||||
preferEncoded: false,
|
|
||||||
);
|
);
|
||||||
if (info == null) {
|
if (info == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -32,26 +31,6 @@ class LocalImageRequest extends ImageRequest {
|
|||||||
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
|
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<ui.Codec?> loadCodec() async {
|
|
||||||
if (_isCancelled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final info = await localImageApi.requestImage(
|
|
||||||
localId,
|
|
||||||
requestId: requestId,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
isVideo: assetType == AssetType.video,
|
|
||||||
preferEncoded: true,
|
|
||||||
);
|
|
||||||
if (info == null) return null;
|
|
||||||
|
|
||||||
final (codec, _) = await _codecFromEncodedPlatformImage(info['pointer']!, info['length']!) ?? (null, null);
|
|
||||||
return codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> _onCancelled() {
|
Future<void> _onCancelled() {
|
||||||
return localImageApi.cancelRequest(requestId);
|
return localImageApi.cancelRequest(requestId);
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ class RemoteImageRequest extends ImageRequest {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final info = await remoteImageApi.requestImage(uri, headers: headers, requestId: requestId, preferEncoded: false);
|
final info = await remoteImageApi.requestImage(uri, headers: headers, requestId: requestId);
|
||||||
// Android always returns encoded data, so we need to check for both shapes of the response.
|
|
||||||
final frame = switch (info) {
|
final frame = switch (info) {
|
||||||
{'pointer': int pointer, 'length': int length} => await _fromEncodedPlatformImage(pointer, length),
|
{'pointer': int pointer, 'length': int length} => await _fromEncodedPlatformImage(pointer, length),
|
||||||
{'pointer': int pointer, 'width': int width, 'height': int height, 'rowBytes': int rowBytes} =>
|
{'pointer': int pointer, 'width': int width, 'height': int height, 'rowBytes': int rowBytes} =>
|
||||||
@@ -23,19 +22,6 @@ class RemoteImageRequest extends ImageRequest {
|
|||||||
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
|
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<ui.Codec?> loadCodec() async {
|
|
||||||
if (_isCancelled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final info = await remoteImageApi.requestImage(uri, headers: headers, requestId: requestId, preferEncoded: true);
|
|
||||||
if (info == null) return null;
|
|
||||||
|
|
||||||
final (codec, _) = await _codecFromEncodedPlatformImage(info['pointer']!, info['length']!) ?? (null, null);
|
|
||||||
return codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> _onCancelled() {
|
Future<void> _onCancelled() {
|
||||||
return remoteImageApi.cancelRequest(requestId);
|
return remoteImageApi.cancelRequest(requestId);
|
||||||
|
|||||||
@@ -16,9 +16,6 @@ class ThumbhashImageRequest extends ImageRequest {
|
|||||||
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
|
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<ui.Codec?> loadCodec() => throw UnsupportedError('Thumbhash does not support codec loading');
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void _onCancelled() {}
|
void _onCancelled() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class Drift extends $Drift implements IDatabaseRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 21;
|
int get schemaVersion => 20;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
@@ -230,10 +230,6 @@ class Drift extends $Drift implements IDatabaseRepository {
|
|||||||
await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.isVisible);
|
await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.isVisible);
|
||||||
await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.deletedAt);
|
await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.deletedAt);
|
||||||
},
|
},
|
||||||
from20To21: (m, v21) async {
|
|
||||||
await m.addColumn(v21.localAssetEntity, v21.localAssetEntity.playbackStyle);
|
|
||||||
await m.addColumn(v21.trashedLocalAssetEntity, v21.trashedLocalAssetEntity.playbackStyle);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -8904,591 +8904,6 @@ i1.GeneratedColumn<bool> _column_102(String aliasedName) =>
|
|||||||
),
|
),
|
||||||
defaultValue: const CustomExpression('1'),
|
defaultValue: const CustomExpression('1'),
|
||||||
);
|
);
|
||||||
|
|
||||||
final class Schema21 extends i0.VersionedSchema {
|
|
||||||
Schema21({required super.database}) : super(version: 21);
|
|
||||||
@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 Shape30 localAssetEntity = Shape30(
|
|
||||||
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,
|
|
||||||
_column_103,
|
|
||||||
],
|
|
||||||
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 Shape31 trashedLocalAssetEntity = Shape31(
|
|
||||||
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,
|
|
||||||
_column_103,
|
|
||||||
],
|
|
||||||
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 Shape30 extends i0.VersionedTable {
|
|
||||||
Shape30({required super.source, required super.alias}) : super.aliased();
|
|
||||||
i1.GeneratedColumn<String> get name =>
|
|
||||||
columnsByName['name']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<int> get type =>
|
|
||||||
columnsByName['type']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<DateTime> get createdAt =>
|
|
||||||
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
|
||||||
i1.GeneratedColumn<DateTime> get updatedAt =>
|
|
||||||
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
|
|
||||||
i1.GeneratedColumn<int> get width =>
|
|
||||||
columnsByName['width']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<int> get height =>
|
|
||||||
columnsByName['height']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<int> get durationInSeconds =>
|
|
||||||
columnsByName['duration_in_seconds']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<String> get id =>
|
|
||||||
columnsByName['id']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<String> get checksum =>
|
|
||||||
columnsByName['checksum']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<bool> get isFavorite =>
|
|
||||||
columnsByName['is_favorite']! as i1.GeneratedColumn<bool>;
|
|
||||||
i1.GeneratedColumn<int> get orientation =>
|
|
||||||
columnsByName['orientation']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<String> get iCloudId =>
|
|
||||||
columnsByName['i_cloud_id']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<DateTime> get adjustmentTime =>
|
|
||||||
columnsByName['adjustment_time']! as i1.GeneratedColumn<DateTime>;
|
|
||||||
i1.GeneratedColumn<double> get latitude =>
|
|
||||||
columnsByName['latitude']! as i1.GeneratedColumn<double>;
|
|
||||||
i1.GeneratedColumn<double> get longitude =>
|
|
||||||
columnsByName['longitude']! as i1.GeneratedColumn<double>;
|
|
||||||
i1.GeneratedColumn<int> get playbackStyle =>
|
|
||||||
columnsByName['playback_style']! as i1.GeneratedColumn<int>;
|
|
||||||
}
|
|
||||||
|
|
||||||
i1.GeneratedColumn<int> _column_103(String aliasedName) =>
|
|
||||||
i1.GeneratedColumn<int>(
|
|
||||||
'playback_style',
|
|
||||||
aliasedName,
|
|
||||||
false,
|
|
||||||
type: i1.DriftSqlType.int,
|
|
||||||
defaultValue: const CustomExpression('0'),
|
|
||||||
);
|
|
||||||
|
|
||||||
class Shape31 extends i0.VersionedTable {
|
|
||||||
Shape31({required super.source, required super.alias}) : super.aliased();
|
|
||||||
i1.GeneratedColumn<String> get name =>
|
|
||||||
columnsByName['name']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<int> get type =>
|
|
||||||
columnsByName['type']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<DateTime> get createdAt =>
|
|
||||||
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
|
||||||
i1.GeneratedColumn<DateTime> get updatedAt =>
|
|
||||||
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
|
|
||||||
i1.GeneratedColumn<int> get width =>
|
|
||||||
columnsByName['width']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<int> get height =>
|
|
||||||
columnsByName['height']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<int> get durationInSeconds =>
|
|
||||||
columnsByName['duration_in_seconds']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<String> get id =>
|
|
||||||
columnsByName['id']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<String> get albumId =>
|
|
||||||
columnsByName['album_id']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<String> get checksum =>
|
|
||||||
columnsByName['checksum']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<bool> get isFavorite =>
|
|
||||||
columnsByName['is_favorite']! as i1.GeneratedColumn<bool>;
|
|
||||||
i1.GeneratedColumn<int> get orientation =>
|
|
||||||
columnsByName['orientation']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<int> get source =>
|
|
||||||
columnsByName['source']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<int> get playbackStyle =>
|
|
||||||
columnsByName['playback_style']! as i1.GeneratedColumn<int>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
||||||
@@ -9509,7 +8924,6 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
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,
|
required Future<void> Function(i1.Migrator m, Schema20 schema) from19To20,
|
||||||
required Future<void> Function(i1.Migrator m, Schema21 schema) from20To21,
|
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
@@ -9608,11 +9022,6 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from19To20(migrator, schema);
|
await from19To20(migrator, schema);
|
||||||
return 20;
|
return 20;
|
||||||
case 20:
|
|
||||||
final schema = Schema21(database: database);
|
|
||||||
final migrator = i1.Migrator(database, schema);
|
|
||||||
await from20To21(migrator, schema);
|
|
||||||
return 21;
|
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
@@ -9639,7 +9048,6 @@ i1.OnUpgrade stepByStep({
|
|||||||
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,
|
required Future<void> Function(i1.Migrator m, Schema20 schema) from19To20,
|
||||||
required Future<void> Function(i1.Migrator m, Schema21 schema) from20To21,
|
|
||||||
}) => i0.VersionedSchema.stepByStepHelper(
|
}) => i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
@@ -9661,6 +9069,5 @@ i1.OnUpgrade stepByStep({
|
|||||||
from17To18: from17To18,
|
from17To18: from17To18,
|
||||||
from18To19: from18To19,
|
from18To19: from18To19,
|
||||||
from19To20: from19To20,
|
from19To20: from19To20,
|
||||||
from20To21: from20To21,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -301,7 +301,6 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
|||||||
id: asset.id,
|
id: asset.id,
|
||||||
orientation: Value(asset.orientation),
|
orientation: Value(asset.orientation),
|
||||||
isFavorite: Value(asset.isFavorite),
|
isFavorite: Value(asset.isFavorite),
|
||||||
playbackStyle: Value(asset.playbackStyle),
|
|
||||||
latitude: Value(asset.latitude),
|
latitude: Value(asset.latitude),
|
||||||
longitude: Value(asset.longitude),
|
longitude: Value(asset.longitude),
|
||||||
adjustmentTime: Value(asset.adjustmentTime),
|
adjustmentTime: Value(asset.adjustmentTime),
|
||||||
@@ -334,7 +333,6 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
|||||||
checksum: const Value(null),
|
checksum: const Value(null),
|
||||||
orientation: Value(asset.orientation),
|
orientation: Value(asset.orientation),
|
||||||
isFavorite: Value(asset.isFavorite),
|
isFavorite: Value(asset.isFavorite),
|
||||||
playbackStyle: Value(asset.playbackStyle),
|
|
||||||
);
|
);
|
||||||
batch.insert<$LocalAssetEntityTable, LocalAssetEntityData>(
|
batch.insert<$LocalAssetEntityTable, LocalAssetEntityData>(
|
||||||
_db.localAssetEntity,
|
_db.localAssetEntity,
|
||||||
|
|||||||
@@ -101,7 +101,6 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
isFavorite: row.isFavorite,
|
isFavorite: row.isFavorite,
|
||||||
durationInSeconds: row.durationInSeconds,
|
durationInSeconds: row.durationInSeconds,
|
||||||
orientation: row.orientation,
|
orientation: row.orientation,
|
||||||
playbackStyle: AssetPlaybackStyle.values[row.playbackStyle],
|
|
||||||
cloudId: row.iCloudId,
|
cloudId: row.iCloudId,
|
||||||
latitude: row.latitude,
|
latitude: row.latitude,
|
||||||
longitude: row.longitude,
|
longitude: row.longitude,
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
|
|||||||
durationInSeconds: Value(item.asset.durationInSeconds),
|
durationInSeconds: Value(item.asset.durationInSeconds),
|
||||||
isFavorite: Value(item.asset.isFavorite),
|
isFavorite: Value(item.asset.isFavorite),
|
||||||
orientation: Value(item.asset.orientation),
|
orientation: Value(item.asset.orientation),
|
||||||
playbackStyle: Value(item.asset.playbackStyle),
|
|
||||||
source: TrashOrigin.localSync,
|
source: TrashOrigin.localSync,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -148,7 +147,6 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
|
|||||||
durationInSeconds: Value(asset.durationInSeconds),
|
durationInSeconds: Value(asset.durationInSeconds),
|
||||||
isFavorite: Value(asset.isFavorite),
|
isFavorite: Value(asset.isFavorite),
|
||||||
orientation: Value(asset.orientation),
|
orientation: Value(asset.orientation),
|
||||||
playbackStyle: Value(asset.playbackStyle),
|
|
||||||
createdAt: Value(asset.createdAt),
|
createdAt: Value(asset.createdAt),
|
||||||
updatedAt: Value(asset.updatedAt),
|
updatedAt: Value(asset.updatedAt),
|
||||||
source: const Value(TrashOrigin.remoteSync),
|
source: const Value(TrashOrigin.remoteSync),
|
||||||
@@ -197,7 +195,6 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
|
|||||||
checksum: Value(e.checksum),
|
checksum: Value(e.checksum),
|
||||||
isFavorite: Value(e.isFavorite),
|
isFavorite: Value(e.isFavorite),
|
||||||
orientation: Value(e.orientation),
|
orientation: Value(e.orientation),
|
||||||
playbackStyle: Value(e.playbackStyle),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -248,7 +245,6 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
|
|||||||
checksum: Value(e.asset.checksum),
|
checksum: Value(e.asset.checksum),
|
||||||
isFavorite: Value(e.asset.isFavorite),
|
isFavorite: Value(e.asset.isFavorite),
|
||||||
orientation: Value(e.asset.orientation),
|
orientation: Value(e.asset.orientation),
|
||||||
playbackStyle: Value(e.asset.playbackStyle),
|
|
||||||
source: TrashOrigin.localUser,
|
source: TrashOrigin.localUser,
|
||||||
albumId: e.albumId,
|
albumId: e.albumId,
|
||||||
);
|
);
|
||||||
|
|||||||
+20
-25
@@ -20,7 +20,6 @@ import 'package:immich_mobile/extensions/translate_extensions.dart';
|
|||||||
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
||||||
import 'package:immich_mobile/generated/translations.g.dart';
|
import 'package:immich_mobile/generated/translations.g.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||||
import 'package:immich_mobile/pages/common/splash_screen.page.dart';
|
|
||||||
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
||||||
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
||||||
@@ -50,34 +49,30 @@ import 'package:logging/logging.dart';
|
|||||||
import 'package:timezone/data/latest.dart';
|
import 'package:timezone/data/latest.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
try {
|
ImmichWidgetsBinding();
|
||||||
ImmichWidgetsBinding();
|
unawaited(BackgroundWorkerLockService(BackgroundWorkerLockApi()).lock());
|
||||||
unawaited(BackgroundWorkerLockService(BackgroundWorkerLockApi()).lock());
|
final (isar, drift, logDb) = await Bootstrap.initDB();
|
||||||
await EasyLocalization.ensureInitialized();
|
await Bootstrap.initDomain(isar, drift, logDb);
|
||||||
final (isar, drift, logDb) = await Bootstrap.initDB();
|
await initApp();
|
||||||
await Bootstrap.initDomain(isar, drift, logDb);
|
// Warm-up isolate pool for worker manager
|
||||||
await initApp();
|
await workerManagerPatch.init(dynamicSpawning: true, isolatesCount: max(Platform.numberOfProcessors - 1, 5));
|
||||||
// Warm-up isolate pool for worker manager
|
await migrateDatabaseIfNeeded(isar, drift);
|
||||||
await workerManagerPatch.init(dynamicSpawning: true, isolatesCount: max(Platform.numberOfProcessors - 1, 5));
|
HttpSSLOptions.apply();
|
||||||
await migrateDatabaseIfNeeded(isar, drift);
|
|
||||||
HttpSSLOptions.apply();
|
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
ProviderScope(
|
ProviderScope(
|
||||||
overrides: [
|
overrides: [
|
||||||
dbProvider.overrideWithValue(isar),
|
dbProvider.overrideWithValue(isar),
|
||||||
isarProvider.overrideWithValue(isar),
|
isarProvider.overrideWithValue(isar),
|
||||||
driftProvider.overrideWith(driftOverride(drift)),
|
driftProvider.overrideWith(driftOverride(drift)),
|
||||||
],
|
],
|
||||||
child: const MainWidget(),
|
child: const MainWidget(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (error, stack) {
|
|
||||||
runApp(BootstrapErrorWidget(error: error.toString(), stack: stack.toString()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initApp() async {
|
Future<void> initApp() async {
|
||||||
|
await EasyLocalization.ensureInitialized();
|
||||||
await initializeDateFormatting();
|
await initializeDateFormatting();
|
||||||
|
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
|
|||||||
@@ -1,19 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/colors.dart';
|
|
||||||
import 'package:immich_mobile/constants/locales.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
|
||||||
import 'package:immich_mobile/generated/translations.g.dart';
|
|
||||||
import 'package:path/path.dart' as path;
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||||
@@ -22,259 +13,7 @@ import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/theme/color_scheme.dart';
|
|
||||||
import 'package:immich_mobile/theme/theme_data.dart';
|
|
||||||
import 'package:immich_mobile/widgets/common/immich_logo.dart';
|
|
||||||
import 'package:immich_mobile/widgets/common/immich_title_text.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart' show launchUrl, LaunchMode;
|
|
||||||
|
|
||||||
class BootstrapErrorWidget extends StatelessWidget {
|
|
||||||
final String error;
|
|
||||||
final String stack;
|
|
||||||
|
|
||||||
const BootstrapErrorWidget({super.key, required this.error, required this.stack});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext _) {
|
|
||||||
final immichTheme = defaultColorPreset.themeOfPreset;
|
|
||||||
|
|
||||||
return EasyLocalization(
|
|
||||||
supportedLocales: locales.values.toList(),
|
|
||||||
path: translationsPath,
|
|
||||||
useFallbackTranslations: true,
|
|
||||||
fallbackLocale: locales.values.first,
|
|
||||||
assetLoader: const CodegenLoader(),
|
|
||||||
child: Builder(
|
|
||||||
builder: (lCtx) => MaterialApp(
|
|
||||||
title: 'Immich',
|
|
||||||
debugShowCheckedModeBanner: true,
|
|
||||||
localizationsDelegates: lCtx.localizationDelegates,
|
|
||||||
supportedLocales: lCtx.supportedLocales,
|
|
||||||
locale: lCtx.locale,
|
|
||||||
themeMode: ThemeMode.system,
|
|
||||||
darkTheme: getThemeData(colorScheme: immichTheme.dark, locale: lCtx.locale),
|
|
||||||
theme: getThemeData(colorScheme: immichTheme.light, locale: lCtx.locale),
|
|
||||||
home: Builder(
|
|
||||||
builder: (ctx) => Scaffold(
|
|
||||||
body: Column(
|
|
||||||
children: [
|
|
||||||
const SafeArea(
|
|
||||||
bottom: false,
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [ImmichLogo(size: 48), SizedBox(width: 12), ImmichTitleText(fontSize: 24)],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: _ErrorCard(error: error, stack: stack),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(height: 1),
|
|
||||||
const SafeArea(
|
|
||||||
top: false,
|
|
||||||
child: Padding(padding: EdgeInsets.fromLTRB(24, 16, 24, 16), child: _BottomPanel()),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BottomPanel extends StatefulWidget {
|
|
||||||
const _BottomPanel();
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<_BottomPanel> createState() => _BottomPanelState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BottomPanelState extends State<_BottomPanel> {
|
|
||||||
bool _cleared = false;
|
|
||||||
|
|
||||||
Future<void> _clearDatabase() async {
|
|
||||||
final confirmed = await showDialog<bool>(
|
|
||||||
context: context,
|
|
||||||
builder: (dialogCtx) => AlertDialog(
|
|
||||||
title: Text(context.t.reset_sqlite_clear_app_data),
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(context.t.reset_sqlite_confirmation),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
context.t.reset_sqlite_confirmation_note,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(fontWeight: FontWeight.w600),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(onPressed: () => Navigator.of(dialogCtx).pop(false), child: Text(context.t.cancel)),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(dialogCtx).pop(true),
|
|
||||||
child: Text(context.t.confirm, style: TextStyle(color: Theme.of(context).colorScheme.error)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (confirmed != true || !mounted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final dir = await getApplicationDocumentsDirectory();
|
|
||||||
for (final suffix in ['', '-wal', '-shm']) {
|
|
||||||
final file = File(path.join(dir.path, 'immich.sqlite$suffix'));
|
|
||||||
if (await file.exists()) {
|
|
||||||
await file.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
setState(() => _cleared = true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
spacing: 8,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
_cleared ? context.t.reset_sqlite_done : context.t.scaffold_body_error_unrecoverable,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
_ActionLink(
|
|
||||||
icon: Icons.chat_bubble_outline,
|
|
||||||
label: context.t.discord,
|
|
||||||
onTap: () => launchUrl(Uri.parse('https://discord.immich.app/'), mode: LaunchMode.externalApplication),
|
|
||||||
),
|
|
||||||
_ActionLink(
|
|
||||||
icon: Icons.bug_report_outlined,
|
|
||||||
label: context.t.profile_drawer_github,
|
|
||||||
onTap: () => launchUrl(
|
|
||||||
Uri.parse('https://github.com/immich-app/immich/issues'),
|
|
||||||
mode: LaunchMode.externalApplication,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!_cleared)
|
|
||||||
_ActionLink(
|
|
||||||
icon: Icons.delete_outline,
|
|
||||||
label: context.t.reset_sqlite_clear_app_data,
|
|
||||||
onTap: _clearDatabase,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ActionLink extends StatelessWidget {
|
|
||||||
final IconData icon;
|
|
||||||
final String label;
|
|
||||||
final VoidCallback onTap;
|
|
||||||
|
|
||||||
const _ActionLink({required this.icon, required this.label, required this.onTap});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return InkWell(
|
|
||||||
onTap: onTap,
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(icon, size: 24),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(label, style: const TextStyle(fontSize: 12)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ErrorCard extends StatelessWidget {
|
|
||||||
final String error;
|
|
||||||
final String stack;
|
|
||||||
|
|
||||||
const _ErrorCard({required this.error, required this.stack});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final scheme = Theme.of(context).colorScheme;
|
|
||||||
final textTheme = Theme.of(context).textTheme;
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
clipBehavior: Clip.antiAlias,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
ColoredBox(
|
|
||||||
color: scheme.error,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(12, 8, 8, 8),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
context.t.scaffold_body_error_occurred,
|
|
||||||
style: textTheme.titleSmall?.copyWith(color: scheme.onError),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
tooltip: context.t.copy_error,
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
constraints: const BoxConstraints(),
|
|
||||||
icon: Icon(Icons.copy_outlined, size: 16, color: scheme.onError),
|
|
||||||
onPressed: () => Clipboard.setData(ClipboardData(text: '$error\n\n$stack')),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: Text(error, style: textTheme.bodyMedium),
|
|
||||||
),
|
|
||||||
const Divider(height: 1),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(context.t.stacktrace, style: textTheme.labelMedium),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
SelectableText(stack, style: textTheme.bodySmall?.copyWith(fontFamily: 'GoogleSansCode')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class SplashScreenPage extends StatefulHookConsumerWidget {
|
class SplashScreenPage extends StatefulHookConsumerWidget {
|
||||||
|
|||||||
@@ -90,14 +90,11 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadMoreSearchResult() async {
|
loadMoreSearchResult() async {
|
||||||
isSearching.value = true;
|
|
||||||
final hasResult = await ref.watch(paginatedSearchProvider.notifier).search(filter.value);
|
final hasResult = await ref.watch(paginatedSearchProvider.notifier).search(filter.value);
|
||||||
|
|
||||||
if (!hasResult) {
|
if (!hasResult) {
|
||||||
context.showSnackBar(searchInfoSnackBar('search_no_more_result'.tr()));
|
context.showSnackBar(searchInfoSnackBar('search_no_more_result'.tr()));
|
||||||
}
|
}
|
||||||
|
|
||||||
isSearching.value = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
searchPrefilter() {
|
searchPrefilter() {
|
||||||
|
|||||||
-2
@@ -55,7 +55,6 @@ class LocalImageApi {
|
|||||||
required int width,
|
required int width,
|
||||||
required int height,
|
required int height,
|
||||||
required bool isVideo,
|
required bool isVideo,
|
||||||
required bool preferEncoded,
|
|
||||||
}) async {
|
}) async {
|
||||||
final String pigeonVar_channelName =
|
final String pigeonVar_channelName =
|
||||||
'dev.flutter.pigeon.immich_mobile.LocalImageApi.requestImage$pigeonVar_messageChannelSuffix';
|
'dev.flutter.pigeon.immich_mobile.LocalImageApi.requestImage$pigeonVar_messageChannelSuffix';
|
||||||
@@ -70,7 +69,6 @@ class LocalImageApi {
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
isVideo,
|
isVideo,
|
||||||
preferEncoded,
|
|
||||||
]);
|
]);
|
||||||
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
if (pigeonVar_replyList == null) {
|
if (pigeonVar_replyList == null) {
|
||||||
|
|||||||
+9
-22
@@ -29,8 +29,6 @@ bool _deepEquals(Object? a, Object? b) {
|
|||||||
return a == b;
|
return a == b;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PlatformAssetPlaybackStyle { unknown, image, video, imageAnimated, livePhoto, videoLooping }
|
|
||||||
|
|
||||||
class PlatformAsset {
|
class PlatformAsset {
|
||||||
PlatformAsset({
|
PlatformAsset({
|
||||||
required this.id,
|
required this.id,
|
||||||
@@ -46,7 +44,6 @@ class PlatformAsset {
|
|||||||
this.adjustmentTime,
|
this.adjustmentTime,
|
||||||
this.latitude,
|
this.latitude,
|
||||||
this.longitude,
|
this.longitude,
|
||||||
required this.playbackStyle,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
String id;
|
String id;
|
||||||
@@ -75,8 +72,6 @@ class PlatformAsset {
|
|||||||
|
|
||||||
double? longitude;
|
double? longitude;
|
||||||
|
|
||||||
PlatformAssetPlaybackStyle playbackStyle;
|
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
return <Object?>[
|
return <Object?>[
|
||||||
id,
|
id,
|
||||||
@@ -92,7 +87,6 @@ class PlatformAsset {
|
|||||||
adjustmentTime,
|
adjustmentTime,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
playbackStyle,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +110,6 @@ class PlatformAsset {
|
|||||||
adjustmentTime: result[10] as int?,
|
adjustmentTime: result[10] as int?,
|
||||||
latitude: result[11] as double?,
|
latitude: result[11] as double?,
|
||||||
longitude: result[12] as double?,
|
longitude: result[12] as double?,
|
||||||
playbackStyle: result[13]! as PlatformAssetPlaybackStyle,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,23 +316,20 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||||||
if (value is int) {
|
if (value is int) {
|
||||||
buffer.putUint8(4);
|
buffer.putUint8(4);
|
||||||
buffer.putInt64(value);
|
buffer.putInt64(value);
|
||||||
} else if (value is PlatformAssetPlaybackStyle) {
|
|
||||||
buffer.putUint8(129);
|
|
||||||
writeValue(buffer, value.index);
|
|
||||||
} else if (value is PlatformAsset) {
|
} else if (value is PlatformAsset) {
|
||||||
buffer.putUint8(130);
|
buffer.putUint8(129);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is PlatformAlbum) {
|
} else if (value is PlatformAlbum) {
|
||||||
buffer.putUint8(131);
|
buffer.putUint8(130);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is SyncDelta) {
|
} else if (value is SyncDelta) {
|
||||||
buffer.putUint8(132);
|
buffer.putUint8(131);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is HashResult) {
|
} else if (value is HashResult) {
|
||||||
buffer.putUint8(133);
|
buffer.putUint8(132);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else if (value is CloudIdResult) {
|
} else if (value is CloudIdResult) {
|
||||||
buffer.putUint8(134);
|
buffer.putUint8(133);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
} else {
|
} else {
|
||||||
super.writeValue(buffer, value);
|
super.writeValue(buffer, value);
|
||||||
@@ -350,17 +340,14 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 129:
|
case 129:
|
||||||
final int? value = readValue(buffer) as int?;
|
|
||||||
return value == null ? null : PlatformAssetPlaybackStyle.values[value];
|
|
||||||
case 130:
|
|
||||||
return PlatformAsset.decode(readValue(buffer)!);
|
return PlatformAsset.decode(readValue(buffer)!);
|
||||||
case 131:
|
case 130:
|
||||||
return PlatformAlbum.decode(readValue(buffer)!);
|
return PlatformAlbum.decode(readValue(buffer)!);
|
||||||
case 132:
|
case 131:
|
||||||
return SyncDelta.decode(readValue(buffer)!);
|
return SyncDelta.decode(readValue(buffer)!);
|
||||||
case 133:
|
case 132:
|
||||||
return HashResult.decode(readValue(buffer)!);
|
return HashResult.decode(readValue(buffer)!);
|
||||||
case 134:
|
case 133:
|
||||||
return CloudIdResult.decode(readValue(buffer)!);
|
return CloudIdResult.decode(readValue(buffer)!);
|
||||||
default:
|
default:
|
||||||
return super.readValueOfType(type, buffer);
|
return super.readValueOfType(type, buffer);
|
||||||
|
|||||||
+1
-7
@@ -53,7 +53,6 @@ class RemoteImageApi {
|
|||||||
String url, {
|
String url, {
|
||||||
required Map<String, String> headers,
|
required Map<String, String> headers,
|
||||||
required int requestId,
|
required int requestId,
|
||||||
required bool preferEncoded,
|
|
||||||
}) async {
|
}) async {
|
||||||
final String pigeonVar_channelName =
|
final String pigeonVar_channelName =
|
||||||
'dev.flutter.pigeon.immich_mobile.RemoteImageApi.requestImage$pigeonVar_messageChannelSuffix';
|
'dev.flutter.pigeon.immich_mobile.RemoteImageApi.requestImage$pigeonVar_messageChannelSuffix';
|
||||||
@@ -62,12 +61,7 @@ class RemoteImageApi {
|
|||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[url, headers, requestId]);
|
||||||
url,
|
|
||||||
headers,
|
|
||||||
requestId,
|
|
||||||
preferEncoded,
|
|
||||||
]);
|
|
||||||
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
if (pigeonVar_replyList == null) {
|
if (pigeonVar_replyList == null) {
|
||||||
throw _createConnectionError(pigeonVar_channelName);
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import 'package:immich_mobile/constants/enums.dart';
|
|||||||
import 'package:immich_mobile/domain/models/person.model.dart';
|
import 'package:immich_mobile/domain/models/person.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/tag.model.dart';
|
import 'package:immich_mobile/domain/models/tag.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
@@ -116,15 +115,17 @@ class DriftSearchPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
search() => searchFilter(filter.value);
|
search() => searchFilter(filter.value);
|
||||||
|
|
||||||
|
final isLoadingMore = useState(false);
|
||||||
|
|
||||||
loadMoreSearchResult() async {
|
loadMoreSearchResult() async {
|
||||||
isSearching.value = true;
|
if (isLoadingMore.value) return;
|
||||||
|
isLoadingMore.value = true;
|
||||||
final hasResult = await ref.watch(paginatedSearchProvider.notifier).search(filter.value);
|
final hasResult = await ref.watch(paginatedSearchProvider.notifier).search(filter.value);
|
||||||
|
isLoadingMore.value = false;
|
||||||
|
|
||||||
if (!hasResult) {
|
if (!hasResult) {
|
||||||
context.showSnackBar(searchInfoSnackBar('search_no_more_result'.t(context: context)));
|
context.showSnackBar(searchInfoSnackBar('search_no_more_result'.t(context: context)));
|
||||||
}
|
}
|
||||||
|
|
||||||
isSearching.value = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
searchPreFilter() {
|
searchPreFilter() {
|
||||||
@@ -745,7 +746,7 @@ class DriftSearchPage extends HookConsumerWidget {
|
|||||||
if (isSearching.value)
|
if (isSearching.value)
|
||||||
const SliverFillRemaining(hasScrollBody: false, child: Center(child: CircularProgressIndicator()))
|
const SliverFillRemaining(hasScrollBody: false, child: Center(child: CircularProgressIndicator()))
|
||||||
else
|
else
|
||||||
_SearchResultGrid(onScrollEnd: loadMoreSearchResult),
|
_SearchResultGrid(onScrollEnd: loadMoreSearchResult, isLoadingMore: isLoadingMore.value),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -754,28 +755,30 @@ class DriftSearchPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
class _SearchResultGrid extends ConsumerWidget {
|
class _SearchResultGrid extends ConsumerWidget {
|
||||||
final VoidCallback onScrollEnd;
|
final VoidCallback onScrollEnd;
|
||||||
|
final bool isLoadingMore;
|
||||||
|
|
||||||
const _SearchResultGrid({required this.onScrollEnd});
|
const _SearchResultGrid({required this.onScrollEnd, this.isLoadingMore = false});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final assets = ref.watch(paginatedSearchProvider.select((s) => s.assets));
|
final hasAssets = ref.watch(paginatedSearchProvider.select((s) => s.assets.isNotEmpty));
|
||||||
|
|
||||||
if (assets.isEmpty) {
|
if (!hasAssets) {
|
||||||
return const _SearchEmptyContent();
|
return const _SearchEmptyContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
return NotificationListener<ScrollEndNotification>(
|
return NotificationListener<ScrollUpdateNotification>(
|
||||||
onNotification: (notification) {
|
onNotification: (notification) {
|
||||||
final isBottomSheetNotification =
|
final isBottomSheetNotification =
|
||||||
notification.context?.findAncestorWidgetOfExactType<DraggableScrollableSheet>() != null;
|
notification.context?.findAncestorWidgetOfExactType<DraggableScrollableSheet>() != null;
|
||||||
|
|
||||||
final metrics = notification.metrics;
|
final metrics = notification.metrics;
|
||||||
final isVerticalScroll = metrics.axis == Axis.vertical;
|
final isVerticalScroll = metrics.axis == Axis.vertical;
|
||||||
|
final remaining = metrics.maxScrollExtent - metrics.pixels;
|
||||||
|
|
||||||
if (metrics.pixels >= metrics.maxScrollExtent && isVerticalScroll && !isBottomSheetNotification) {
|
if (remaining < metrics.viewportDimension && isVerticalScroll && !isBottomSheetNotification) {
|
||||||
onScrollEnd();
|
onScrollEnd();
|
||||||
ref.read(paginatedSearchProvider.notifier).setScrollOffset(metrics.maxScrollExtent);
|
ref.read(paginatedSearchProvider.notifier).setScrollOffset(metrics.pixels);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -784,18 +787,29 @@ class _SearchResultGrid extends ConsumerWidget {
|
|||||||
child: ProviderScope(
|
child: ProviderScope(
|
||||||
overrides: [
|
overrides: [
|
||||||
timelineServiceProvider.overrideWith((ref) {
|
timelineServiceProvider.overrideWith((ref) {
|
||||||
final timelineService = ref.watch(timelineFactoryProvider).fromAssets(assets, TimelineOrigin.search);
|
return ref.watch(paginatedSearchTimelineProvider);
|
||||||
ref.onDispose(timelineService.dispose);
|
|
||||||
return timelineService;
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
child: Timeline(
|
child: Timeline(
|
||||||
key: ValueKey(assets.length),
|
|
||||||
groupBy: GroupAssetsBy.none,
|
groupBy: GroupAssetsBy.none,
|
||||||
appBar: null,
|
appBar: null,
|
||||||
bottomSheet: const GeneralBottomSheet(minChildSize: 0.20),
|
bottomSheet: const GeneralBottomSheet(minChildSize: 0.20),
|
||||||
snapToMonth: false,
|
snapToMonth: false,
|
||||||
initialScrollOffset: ref.read(paginatedSearchProvider.select((s) => s.scrollOffset)),
|
initialScrollOffset: ref.read(paginatedSearchProvider.select((s) => s.scrollOffset)),
|
||||||
|
bottomSliverWidget: isLoadingMore
|
||||||
|
? const SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 32),
|
||||||
|
child: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
import 'package:immich_mobile/domain/models/search_result.model.dart';
|
import 'package:immich_mobile/domain/models/search_result.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
import 'package:immich_mobile/domain/services/search.service.dart';
|
import 'package:immich_mobile/domain/services/search.service.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||||
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/search.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/search.provider.dart';
|
||||||
|
|
||||||
@@ -58,3 +63,49 @@ class PaginatedSearchNotifier extends StateNotifier<SearchResult> {
|
|||||||
state = const SearchResult(assets: [], nextPage: 1, scrollOffset: 0.0);
|
state = const SearchResult(assets: [], nextPage: 1, scrollOffset: 0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [TimelineService] for search results that stays alive across pagination.
|
||||||
|
/// Instead of recreating the service on each page load, it uses a long-lived
|
||||||
|
/// bucket stream that emits new buckets whenever the search assets change.
|
||||||
|
final paginatedSearchTimelineProvider = Provider.autoDispose<TimelineService>((ref) {
|
||||||
|
final controller = StreamController<List<Bucket>>.broadcast();
|
||||||
|
|
||||||
|
List<Bucket> generateBuckets(int count) {
|
||||||
|
if (count == 0) return [];
|
||||||
|
final buckets = List.filled(
|
||||||
|
(count / kTimelineNoneSegmentSize).ceil(),
|
||||||
|
const Bucket(assetCount: kTimelineNoneSegmentSize),
|
||||||
|
);
|
||||||
|
if (count % kTimelineNoneSegmentSize != 0) {
|
||||||
|
buckets[buckets.length - 1] = Bucket(assetCount: count % kTimelineNoneSegmentSize);
|
||||||
|
}
|
||||||
|
return buckets;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit new buckets whenever asset count changes
|
||||||
|
ref.listen(paginatedSearchProvider.select((s) => s.assets.length), (_, count) {
|
||||||
|
controller.add(generateBuckets(count));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Each subscriber gets the current bucket state then follows updates
|
||||||
|
Stream<List<Bucket>> bucketSource() async* {
|
||||||
|
yield generateBuckets(ref.read(paginatedSearchProvider).assets.length);
|
||||||
|
yield* controller.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
final service = TimelineService((
|
||||||
|
bucketSource: bucketSource,
|
||||||
|
assetSource: (offset, count) {
|
||||||
|
final assets = ref.read(paginatedSearchProvider).assets;
|
||||||
|
return Future.value(assets.skip(offset).take(count).toList(growable: false));
|
||||||
|
},
|
||||||
|
origin: TimelineOrigin.search,
|
||||||
|
));
|
||||||
|
|
||||||
|
ref.onDispose(() {
|
||||||
|
controller.close();
|
||||||
|
service.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
return service;
|
||||||
|
});
|
||||||
|
|||||||
-10
@@ -8,7 +8,6 @@ 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/presentation/widgets/action_buttons/base_action_button.widget.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/permanent_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:
|
||||||
@@ -26,15 +25,6 @@ class DeletePermanentActionButton extends ConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final count = source == ActionSource.viewer ? 1 : ref.read(multiSelectProvider).selectedAssets.length;
|
|
||||||
final confirm =
|
|
||||||
await showDialog<bool>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => PermanentDeleteDialog(count: count),
|
|
||||||
) ??
|
|
||||||
false;
|
|
||||||
if (!confirm) 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();
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -5,7 +5,7 @@ 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/permanent_delete_dialog.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:
|
||||||
@@ -28,7 +28,7 @@ class DeleteTrashActionButton extends ConsumerWidget {
|
|||||||
final confirmDelete =
|
final confirmDelete =
|
||||||
await showDialog<bool>(
|
await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => PermanentDeleteDialog(count: selectCount),
|
builder: (context) => TrashDeleteDialog(count: selectCount),
|
||||||
) ??
|
) ??
|
||||||
false;
|
false;
|
||||||
if (!confirmDelete) {
|
if (!confirmDelete) {
|
||||||
|
|||||||
@@ -14,15 +14,15 @@ import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_details.wi
|
|||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.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';
|
||||||
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/asset_viewer/video_viewer_controls.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/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/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';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
|
||||||
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
|
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
|
||||||
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
|
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
|
||||||
|
|
||||||
@@ -53,6 +53,7 @@ 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;
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ class _AssetPageState extends ConsumerState<AssetPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_proxyScrollController.addListener(_onScroll);
|
||||||
_eventSubscription = EventStream.shared.listen(_onEvent);
|
_eventSubscription = EventStream.shared.listen(_onEvent);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (!mounted || !_proxyScrollController.hasClients) return;
|
if (!mounted || !_proxyScrollController.hasClients) return;
|
||||||
@@ -78,6 +80,7 @@ class _AssetPageState extends ConsumerState<AssetPage> {
|
|||||||
_proxyScrollController.dispose();
|
_proxyScrollController.dispose();
|
||||||
_scaleBoundarySub?.cancel();
|
_scaleBoundarySub?.cancel();
|
||||||
_eventSubscription?.cancel();
|
_eventSubscription?.cancel();
|
||||||
|
_videoScaleStateNotifier.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +94,6 @@ class _AssetPageState extends ConsumerState<AssetPage> {
|
|||||||
|
|
||||||
void _showDetails() {
|
void _showDetails() {
|
||||||
if (!_proxyScrollController.hasClients || _snapOffset <= 0) return;
|
if (!_proxyScrollController.hasClients || _snapOffset <= 0) return;
|
||||||
_viewer.setShowingDetails(true);
|
|
||||||
_proxyScrollController.animateTo(_snapOffset, duration: Durations.medium2, curve: Curves.easeOutCubic);
|
_proxyScrollController.animateTo(_snapOffset, duration: Durations.medium2, curve: Curves.easeOutCubic);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +105,7 @@ class _AssetPageState extends ConsumerState<AssetPage> {
|
|||||||
SnapScrollPhysics.target(position, scrollVelocity, _snapOffset) < SnapScrollPhysics.minSnapDistance;
|
SnapScrollPhysics.target(position, scrollVelocity, _snapOffset) < SnapScrollPhysics.minSnapDistance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _syncShowingDetails() {
|
void _onScroll() {
|
||||||
final offset = _proxyScrollController.offset;
|
final offset = _proxyScrollController.offset;
|
||||||
if (offset > SnapScrollPhysics.minSnapDistance) {
|
if (offset > SnapScrollPhysics.minSnapDistance) {
|
||||||
_viewer.setShowingDetails(true);
|
_viewer.setShowingDetails(true);
|
||||||
@@ -147,8 +149,6 @@ class _AssetPageState extends ConsumerState<AssetPage> {
|
|||||||
case _DragIntent.scroll:
|
case _DragIntent.scroll:
|
||||||
if (_drag == null) _startProxyDrag();
|
if (_drag == null) _startProxyDrag();
|
||||||
_drag?.update(details);
|
_drag?.update(details);
|
||||||
|
|
||||||
_syncShowingDetails();
|
|
||||||
case _DragIntent.dismiss:
|
case _DragIntent.dismiss:
|
||||||
_handleDragDown(context, details.localPosition - _dragStart!.localPosition);
|
_handleDragDown(context, details.localPosition - _dragStart!.localPosition);
|
||||||
}
|
}
|
||||||
@@ -167,8 +167,9 @@ class _AssetPageState extends ConsumerState<AssetPage> {
|
|||||||
case _DragIntent.none:
|
case _DragIntent.none:
|
||||||
case _DragIntent.scroll:
|
case _DragIntent.scroll:
|
||||||
final scrollVelocity = -(details.primaryVelocity ?? 0.0);
|
final scrollVelocity = -(details.primaryVelocity ?? 0.0);
|
||||||
_viewer.setShowingDetails(!_willClose(scrollVelocity));
|
if (_willClose(scrollVelocity)) {
|
||||||
|
_viewer.setShowingDetails(false);
|
||||||
|
}
|
||||||
_drag?.end(details);
|
_drag?.end(details);
|
||||||
_drag = null;
|
_drag = null;
|
||||||
case _DragIntent.dismiss:
|
case _DragIntent.dismiss:
|
||||||
@@ -247,14 +248,17 @@ class _AssetPageState extends ConsumerState<AssetPage> {
|
|||||||
ref.read(isPlayingMotionVideoProvider.notifier).playing = true;
|
ref.read(isPlayingMotionVideoProvider.notifier).playing = true;
|
||||||
|
|
||||||
void _onScaleStateChanged(PhotoViewScaleState scaleState) {
|
void _onScaleStateChanged(PhotoViewScaleState scaleState) {
|
||||||
_isZoomed = switch (scaleState) {
|
_isZoomed =
|
||||||
PhotoViewScaleState.zoomedIn || PhotoViewScaleState.covering => true,
|
scaleState == PhotoViewScaleState.zoomedIn ||
|
||||||
_ => false,
|
scaleState == PhotoViewScaleState.covering ||
|
||||||
};
|
_videoScaleStateNotifier.value == PhotoViewScaleState.zoomedIn ||
|
||||||
|
_videoScaleStateNotifier.value == PhotoViewScaleState.covering;
|
||||||
_viewer.setZoomed(_isZoomed);
|
_viewer.setZoomed(_isZoomed);
|
||||||
|
|
||||||
if (scaleState != PhotoViewScaleState.initial) {
|
if (scaleState != PhotoViewScaleState.initial) {
|
||||||
if (_dragStart == null) _viewer.setControls(false);
|
if (_dragStart == null) _viewer.setControls(false);
|
||||||
|
|
||||||
|
ref.read(videoPlayerControlsProvider.notifier).pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +306,7 @@ class _AssetPageState extends ConsumerState<AssetPage> {
|
|||||||
if (displayAsset.isImage && !isPlayingMotionVideo) {
|
if (displayAsset.isImage && !isPlayingMotionVideo) {
|
||||||
final size = context.sizeData;
|
final size = context.sizeData;
|
||||||
return PhotoView(
|
return PhotoView(
|
||||||
key: Key(displayAsset.heroTag),
|
key: ValueKey(displayAsset.heroTag),
|
||||||
index: widget.index,
|
index: widget.index,
|
||||||
imageProvider: getFullImageProvider(displayAsset, size: size),
|
imageProvider: getFullImageProvider(displayAsset, size: size),
|
||||||
heroAttributes: heroAttributes,
|
heroAttributes: heroAttributes,
|
||||||
@@ -329,40 +333,36 @@ class _AssetPageState extends ConsumerState<AssetPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Stack(
|
return PhotoView.customChild(
|
||||||
children: [
|
key: ValueKey(displayAsset),
|
||||||
PhotoView.customChild(
|
onDragStart: _onDragStart,
|
||||||
key: Key(displayAsset.heroTag),
|
onDragUpdate: _onDragUpdate,
|
||||||
onDragStart: _onDragStart,
|
onDragEnd: _onDragEnd,
|
||||||
onDragUpdate: _onDragUpdate,
|
onDragCancel: _onDragCancel,
|
||||||
onDragEnd: _onDragEnd,
|
heroAttributes: heroAttributes,
|
||||||
onDragCancel: _onDragCancel,
|
filterQuality: FilterQuality.high,
|
||||||
onTapUp: _onTapUp,
|
basePosition: Alignment.center,
|
||||||
heroAttributes: heroAttributes,
|
disableScaleGestures: true,
|
||||||
basePosition: Alignment.center,
|
minScale: PhotoViewComputedScale.contained,
|
||||||
disableScaleGestures: showingDetails,
|
initialScale: PhotoViewComputedScale.contained,
|
||||||
scaleStateChangedCallback: _onScaleStateChanged,
|
tightMode: true,
|
||||||
onPageBuild: _onPageBuild,
|
onPageBuild: _onPageBuild,
|
||||||
enablePanAlways: true,
|
enablePanAlways: true,
|
||||||
backgroundDecoration: backgroundDecoration,
|
backgroundDecoration: backgroundDecoration,
|
||||||
child: SizedBox(
|
child: NativeVideoViewer(
|
||||||
width: context.width,
|
key: ValueKey(displayAsset),
|
||||||
height: context.height,
|
asset: displayAsset,
|
||||||
child: NativeVideoViewer(
|
scaleStateNotifier: _videoScaleStateNotifier,
|
||||||
key: _NativeVideoViewerKey(displayAsset.heroTag),
|
disableScaleGestures: showingDetails,
|
||||||
asset: displayAsset,
|
image: Image(
|
||||||
image: Image(
|
key: ValueKey(displayAsset.heroTag),
|
||||||
image: getFullImageProvider(displayAsset, size: context.sizeData),
|
image: getFullImageProvider(displayAsset, size: context.sizeData),
|
||||||
fit: BoxFit.contain,
|
height: context.height,
|
||||||
height: context.height,
|
width: context.width,
|
||||||
width: context.width,
|
fit: BoxFit.contain,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const Center(child: VideoViewerControls()),
|
),
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,25 +459,3 @@ class _AssetPageState extends ConsumerState<AssetPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A global key is used for video viewers to prevent them from being
|
|
||||||
// unnecessarily recreated. They're quite expensive, and maintain internal
|
|
||||||
// state. This can cause videos to restart multiple times during normal usage,
|
|
||||||
// like a hero animation.
|
|
||||||
//
|
|
||||||
// A plain ValueKey is insufficient, as it does not allow widgets to reparent. A
|
|
||||||
// GlobalObjectKey is fragile, as it checks if the given objects are identical,
|
|
||||||
// rather than equal. Hero tags are created with string interpolation, which
|
|
||||||
// prevents Dart from interning them. As such, hero tags are not identical, even
|
|
||||||
// if they are equal.
|
|
||||||
class _NativeVideoViewerKey extends GlobalKey {
|
|
||||||
final String value;
|
|
||||||
|
|
||||||
const _NativeVideoViewerKey(this.value) : super.constructor();
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) => other is _NativeVideoViewerKey && other.value == value;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => value.hashCode;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_page.widge
|
|||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_preloader.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_preloader.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.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';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_bottom_app_bar.widget.dart';
|
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart';
|
||||||
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_bottom_app_bar.widget.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/providers/asset_viewer/video_player_value_provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
|
||||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||||
@@ -117,9 +117,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
_reloadSubscription = EventStream.shared.listen(_onEvent);
|
_reloadSubscription = EventStream.shared.listen(_onEvent);
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback(_onAssetInit);
|
WidgetsBinding.instance.addPostFrameCallback(_onAssetInit);
|
||||||
|
|
||||||
final assetViewer = ref.read(assetViewerProvider);
|
|
||||||
_setSystemUIMode(assetViewer.showingControls, assetViewer.showingDetails);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -229,13 +226,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
_onAssetChanged(index);
|
_onAssetChanged(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setSystemUIMode(bool controls, bool details) {
|
|
||||||
final mode = !controls || (CurrentPlatform.isIOS && details)
|
|
||||||
? SystemUiMode.immersiveSticky
|
|
||||||
: SystemUiMode.edgeToEdge;
|
|
||||||
unawaited(SystemChrome.setEnabledSystemUIMode(mode));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final showingControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
final showingControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
||||||
@@ -255,7 +245,10 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
|
|
||||||
ref.listen(assetViewerProvider.select((value) => (value.showingControls, value.showingDetails)), (_, state) {
|
ref.listen(assetViewerProvider.select((value) => (value.showingControls, value.showingDetails)), (_, state) {
|
||||||
final (controls, details) = state;
|
final (controls, details) = state;
|
||||||
_setSystemUIMode(controls, details);
|
final mode = !controls || (CurrentPlatform.isIOS && details)
|
||||||
|
? SystemUiMode.immersiveSticky
|
||||||
|
: SystemUiMode.edgeToEdge;
|
||||||
|
unawaited(SystemChrome.setEnabledSystemUIMode(mode));
|
||||||
});
|
});
|
||||||
|
|
||||||
return PopScope(
|
return PopScope(
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ 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';
|
||||||
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer_controls.widget.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.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';
|
||||||
@@ -24,6 +26,7 @@ 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';
|
||||||
@@ -48,10 +51,21 @@ bool _isCurrentAsset(BaseAsset asset, BaseAsset? currentAsset) {
|
|||||||
class NativeVideoViewer extends HookConsumerWidget {
|
class NativeVideoViewer extends HookConsumerWidget {
|
||||||
static final log = Logger('NativeVideoViewer');
|
static final log = Logger('NativeVideoViewer');
|
||||||
final BaseAsset asset;
|
final BaseAsset asset;
|
||||||
|
final bool showControls;
|
||||||
final int playbackDelayFactor;
|
final int playbackDelayFactor;
|
||||||
final Widget image;
|
final Widget image;
|
||||||
|
final ValueNotifier<PhotoViewScaleState>? scaleStateNotifier;
|
||||||
|
final bool disableScaleGestures;
|
||||||
|
|
||||||
const NativeVideoViewer({super.key, required this.asset, required this.image, this.playbackDelayFactor = 1});
|
const NativeVideoViewer({
|
||||||
|
super.key,
|
||||||
|
required this.asset,
|
||||||
|
required this.image,
|
||||||
|
this.showControls = true,
|
||||||
|
this.playbackDelayFactor = 1,
|
||||||
|
this.scaleStateNotifier,
|
||||||
|
this.disableScaleGestures = false,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -130,6 +144,7 @@ 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;
|
||||||
@@ -305,6 +320,20 @@ 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) {
|
||||||
@@ -385,22 +414,31 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Stack(
|
return SizedBox(
|
||||||
children: [
|
width: context.width,
|
||||||
// This remains under the video to avoid flickering
|
height: context.height,
|
||||||
// For motion videos, this is the image portion of the asset
|
child: Stack(
|
||||||
Center(child: image),
|
children: [
|
||||||
if (aspectRatio.value != null && !isCasting)
|
// Hide thumbnail once video is visible to avoid it showing in background when zooming out on video.
|
||||||
Visibility.maintain(
|
if (!isVisible.value || controller.value == null) Center(key: ValueKey(asset.heroTag), child: image),
|
||||||
visible: isVisible.value,
|
if (aspectRatio.value != null && !isCasting && isCurrent)
|
||||||
child: Center(
|
Visibility.maintain(
|
||||||
child: AspectRatio(
|
key: ValueKey(asset),
|
||||||
aspectRatio: aspectRatio.value!,
|
visible: isVisible.value,
|
||||||
child: isCurrent ? NativeVideoPlayerView(onViewReady: initController) : null,
|
child: PhotoView.customChild(
|
||||||
|
key: ValueKey(asset),
|
||||||
|
enableRotation: false,
|
||||||
|
disableScaleGestures: disableScaleGestures,
|
||||||
|
// 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()),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class ArchiveBottomSheet extends ConsumerWidget {
|
|||||||
const ShareLinkActionButton(source: ActionSource.timeline),
|
const ShareLinkActionButton(source: ActionSource.timeline),
|
||||||
const UnArchiveActionButton(source: ActionSource.timeline),
|
const UnArchiveActionButton(source: ActionSource.timeline),
|
||||||
const FavoriteActionButton(source: ActionSource.timeline),
|
const FavoriteActionButton(source: ActionSource.timeline),
|
||||||
if (multiselect.onlyRemote) const DownloadActionButton(source: ActionSource.timeline),
|
const DownloadActionButton(source: ActionSource.timeline),
|
||||||
isTrashEnable
|
isTrashEnable
|
||||||
? const TrashActionButton(source: ActionSource.timeline)
|
? const TrashActionButton(source: ActionSource.timeline)
|
||||||
: const DeletePermanentActionButton(source: ActionSource.timeline),
|
: const DeletePermanentActionButton(source: ActionSource.timeline),
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class FavoriteBottomSheet extends ConsumerWidget {
|
|||||||
const ShareLinkActionButton(source: ActionSource.timeline),
|
const ShareLinkActionButton(source: ActionSource.timeline),
|
||||||
const UnFavoriteActionButton(source: ActionSource.timeline),
|
const UnFavoriteActionButton(source: ActionSource.timeline),
|
||||||
const ArchiveActionButton(source: ActionSource.timeline),
|
const ArchiveActionButton(source: ActionSource.timeline),
|
||||||
if (multiselect.onlyRemote) const DownloadActionButton(source: ActionSource.timeline),
|
const DownloadActionButton(source: ActionSource.timeline),
|
||||||
isTrashEnable
|
isTrashEnable
|
||||||
? const TrashActionButton(source: ActionSource.timeline)
|
? const TrashActionButton(source: ActionSource.timeline)
|
||||||
: const DeletePermanentActionButton(source: ActionSource.timeline),
|
: const DeletePermanentActionButton(source: ActionSource.timeline),
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class _GeneralBottomSheetState extends ConsumerState<GeneralBottomSheet> {
|
|||||||
const ShareActionButton(source: ActionSource.timeline),
|
const ShareActionButton(source: ActionSource.timeline),
|
||||||
if (multiselect.hasRemote) ...[
|
if (multiselect.hasRemote) ...[
|
||||||
const ShareLinkActionButton(source: ActionSource.timeline),
|
const ShareLinkActionButton(source: ActionSource.timeline),
|
||||||
if (multiselect.onlyRemote) const DownloadActionButton(source: ActionSource.timeline),
|
const DownloadActionButton(source: ActionSource.timeline),
|
||||||
isTrashEnable
|
isTrashEnable
|
||||||
? const TrashActionButton(source: ActionSource.timeline)
|
? const TrashActionButton(source: ActionSource.timeline)
|
||||||
: const DeletePermanentActionButton(source: ActionSource.timeline),
|
: const DeletePermanentActionButton(source: ActionSource.timeline),
|
||||||
@@ -119,11 +119,10 @@ class _GeneralBottomSheetState extends ConsumerState<GeneralBottomSheet> {
|
|||||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||||
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
|
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
|
||||||
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
|
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
|
||||||
if (multiselect.onlyLocal || multiselect.hasMerged) const DeleteActionButton(source: ActionSource.timeline),
|
if (multiselect.hasLocal || multiselect.hasMerged) const DeleteActionButton(source: ActionSource.timeline),
|
||||||
],
|
],
|
||||||
if (multiselect.onlyLocal || multiselect.hasMerged)
|
if (multiselect.hasLocal || multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||||
const DeleteLocalActionButton(source: ActionSource.timeline),
|
if (multiselect.hasLocal) const UploadActionButton(source: ActionSource.timeline),
|
||||||
if (multiselect.onlyLocal) const UploadActionButton(source: ActionSource.timeline),
|
|
||||||
],
|
],
|
||||||
slivers: multiselect.hasRemote
|
slivers: multiselect.hasRemote
|
||||||
? [
|
? [
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:ui' as ui;
|
|
||||||
|
|
||||||
import 'package:async/async.dart';
|
import 'package:async/async.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
@@ -50,7 +48,7 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<ImageInfo> loadRequest(ImageRequest request, ImageDecoderCallback decode, {bool evictOnError = true}) async* {
|
Stream<ImageInfo> loadRequest(ImageRequest request, ImageDecoderCallback decode) async* {
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
this.request = null;
|
this.request = null;
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
PaintingBinding.instance.imageCache.evict(this);
|
||||||
@@ -59,39 +57,11 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final image = await request.load(decode);
|
final image = await request.load(decode);
|
||||||
if ((image == null && evictOnError) || isCancelled) {
|
if (image == null || isCancelled) {
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
PaintingBinding.instance.imageCache.evict(this);
|
||||||
return;
|
return;
|
||||||
} else if (image == null) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
yield image;
|
yield image;
|
||||||
} catch (e, stack) {
|
|
||||||
if (evictOnError) {
|
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
_log.warning('Non-fatal image load error', e, stack);
|
|
||||||
} finally {
|
|
||||||
this.request = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ui.Codec?> loadCodecRequest(ImageRequest request) async {
|
|
||||||
if (isCancelled) {
|
|
||||||
this.request = null;
|
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final codec = await request.loadCodec();
|
|
||||||
if (codec == null || isCancelled) {
|
|
||||||
codec?.dispose();
|
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return codec;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
PaintingBinding.instance.imageCache.evict(this);
|
PaintingBinding.instance.imageCache.evict(this);
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv
|
|||||||
size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio),
|
size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio),
|
||||||
assetType: key.assetType,
|
assetType: key.assetType,
|
||||||
);
|
);
|
||||||
|
|
||||||
yield* loadRequest(request, decode);
|
yield* loadRequest(request, decode);
|
||||||
|
|
||||||
if (!Store.get(StoreKey.loadOriginal, false)) {
|
if (!Store.get(StoreKey.loadOriginal, false)) {
|
||||||
|
|||||||
@@ -93,10 +93,9 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
|||||||
uri: getThumbnailUrlForRemoteId(key.assetId, type: AssetMediaSize.preview, thumbhash: key.thumbhash),
|
uri: getThumbnailUrlForRemoteId(key.assetId, type: AssetMediaSize.preview, thumbhash: key.thumbhash),
|
||||||
headers: headers,
|
headers: headers,
|
||||||
);
|
);
|
||||||
final loadOriginal = assetType == AssetType.image && AppSetting.get(Setting.loadOriginal);
|
yield* loadRequest(previewRequest, decode);
|
||||||
yield* loadRequest(previewRequest, decode, evictOnError: !loadOriginal);
|
|
||||||
|
|
||||||
if (!loadOriginal) {
|
if (assetType != AssetType.image || !AppSetting.get(Setting.loadOriginal)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class DriftMemoryCard extends StatelessWidget {
|
|||||||
child: NativeVideoViewer(
|
child: NativeVideoViewer(
|
||||||
key: ValueKey(asset.id),
|
key: ValueKey(asset.id),
|
||||||
asset: asset,
|
asset: asset,
|
||||||
|
showControls: false,
|
||||||
playbackDelayFactor: 2,
|
playbackDelayFactor: 2,
|
||||||
image: FullImage(asset, size: Size(context.width, context.height), fit: BoxFit.contain),
|
image: FullImage(asset, size: Size(context.width, context.height), fit: BoxFit.contain),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class Timeline extends StatelessWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
this.topSliverWidget,
|
this.topSliverWidget,
|
||||||
this.topSliverWidgetHeight,
|
this.topSliverWidgetHeight,
|
||||||
|
this.bottomSliverWidget,
|
||||||
this.showStorageIndicator = false,
|
this.showStorageIndicator = false,
|
||||||
this.withStack = false,
|
this.withStack = false,
|
||||||
this.appBar = const ImmichSliverAppBar(floating: true, pinned: false, snap: false),
|
this.appBar = const ImmichSliverAppBar(floating: true, pinned: false, snap: false),
|
||||||
@@ -48,6 +49,7 @@ class Timeline extends StatelessWidget {
|
|||||||
|
|
||||||
final Widget? topSliverWidget;
|
final Widget? topSliverWidget;
|
||||||
final double? topSliverWidgetHeight;
|
final double? topSliverWidgetHeight;
|
||||||
|
final Widget? bottomSliverWidget;
|
||||||
final bool showStorageIndicator;
|
final bool showStorageIndicator;
|
||||||
final Widget? appBar;
|
final Widget? appBar;
|
||||||
final Widget? bottomSheet;
|
final Widget? bottomSheet;
|
||||||
@@ -82,6 +84,7 @@ class Timeline extends StatelessWidget {
|
|||||||
child: _SliverTimeline(
|
child: _SliverTimeline(
|
||||||
topSliverWidget: topSliverWidget,
|
topSliverWidget: topSliverWidget,
|
||||||
topSliverWidgetHeight: topSliverWidgetHeight,
|
topSliverWidgetHeight: topSliverWidgetHeight,
|
||||||
|
bottomSliverWidget: bottomSliverWidget,
|
||||||
appBar: appBar,
|
appBar: appBar,
|
||||||
bottomSheet: bottomSheet,
|
bottomSheet: bottomSheet,
|
||||||
withScrubber: withScrubber,
|
withScrubber: withScrubber,
|
||||||
@@ -111,6 +114,7 @@ class _SliverTimeline extends ConsumerStatefulWidget {
|
|||||||
const _SliverTimeline({
|
const _SliverTimeline({
|
||||||
this.topSliverWidget,
|
this.topSliverWidget,
|
||||||
this.topSliverWidgetHeight,
|
this.topSliverWidgetHeight,
|
||||||
|
this.bottomSliverWidget,
|
||||||
this.appBar,
|
this.appBar,
|
||||||
this.bottomSheet,
|
this.bottomSheet,
|
||||||
this.withScrubber = true,
|
this.withScrubber = true,
|
||||||
@@ -122,6 +126,7 @@ class _SliverTimeline extends ConsumerStatefulWidget {
|
|||||||
|
|
||||||
final Widget? topSliverWidget;
|
final Widget? topSliverWidget;
|
||||||
final double? topSliverWidgetHeight;
|
final double? topSliverWidgetHeight;
|
||||||
|
final Widget? bottomSliverWidget;
|
||||||
final Widget? appBar;
|
final Widget? appBar;
|
||||||
final Widget? bottomSheet;
|
final Widget? bottomSheet;
|
||||||
final bool withScrubber;
|
final bool withScrubber;
|
||||||
@@ -408,6 +413,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
|||||||
addRepaintBoundaries: false,
|
addRepaintBoundaries: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (widget.bottomSliverWidget != null) widget.bottomSliverWidget!,
|
||||||
SliverPadding(padding: EdgeInsets.only(bottom: bottomPadding)),
|
SliverPadding(padding: EdgeInsets.only(bottom: bottomPadding)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,12 +24,10 @@ class MultiSelectState {
|
|||||||
|
|
||||||
bool get hasStacked => selectedAssets.any((asset) => asset is RemoteAsset && asset.stackId != null);
|
bool get hasStacked => selectedAssets.any((asset) => asset is RemoteAsset && asset.stackId != null);
|
||||||
|
|
||||||
|
bool get hasLocal => selectedAssets.any((asset) => asset.storage == AssetState.local);
|
||||||
|
|
||||||
bool get hasMerged => selectedAssets.any((asset) => asset.storage == AssetState.merged);
|
bool get hasMerged => selectedAssets.any((asset) => asset.storage == AssetState.merged);
|
||||||
|
|
||||||
bool get onlyLocal => selectedAssets.any((asset) => asset.storage == AssetState.local);
|
|
||||||
|
|
||||||
bool get onlyRemote => selectedAssets.any((asset) => asset.storage == AssetState.remote);
|
|
||||||
|
|
||||||
MultiSelectState copyWith({
|
MultiSelectState copyWith({
|
||||||
Set<BaseAsset>? selectedAssets,
|
Set<BaseAsset>? selectedAssets,
|
||||||
Set<BaseAsset>? lockedSelectionAssets,
|
Set<BaseAsset>? lockedSelectionAssets,
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ class FileMediaRepository {
|
|||||||
type: AssetType.image,
|
type: AssetType.image,
|
||||||
createdAt: entity.createDateTime,
|
createdAt: entity.createDateTime,
|
||||||
updatedAt: entity.modifiedDateTime,
|
updatedAt: entity.modifiedDateTime,
|
||||||
playbackStyle: AssetPlaybackStyle.image,
|
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'dart:io';
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/album.entity.dart';
|
import 'package:immich_mobile/entities/album.entity.dart';
|
||||||
import 'package:immich_mobile/entities/android_device_asset.entity.dart';
|
import 'package:immich_mobile/entities/android_device_asset.entity.dart';
|
||||||
@@ -18,7 +17,6 @@ import 'package:immich_mobile/infrastructure/entities/device_asset.entity.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
@@ -35,7 +33,7 @@ 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 = 23;
|
const int targetVersion = 22;
|
||||||
|
|
||||||
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;
|
||||||
@@ -101,10 +99,6 @@ Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version < 23 && Store.isBetaTimelineEnabled) {
|
|
||||||
await _populateLocalAssetPlaybackStyle(drift);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (version < 22 && !Store.isBetaTimelineEnabled) {
|
if (version < 22 && !Store.isBetaTimelineEnabled) {
|
||||||
await Store.put(StoreKey.needBetaMigration, true);
|
await Store.put(StoreKey.needBetaMigration, true);
|
||||||
}
|
}
|
||||||
@@ -398,52 +392,6 @@ Future<void> migrateStoreToIsar(Isar db, Drift drift) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _populateLocalAssetPlaybackStyle(Drift db) async {
|
|
||||||
try {
|
|
||||||
final nativeApi = NativeSyncApi();
|
|
||||||
|
|
||||||
final albums = await nativeApi.getAlbums();
|
|
||||||
for (final album in albums) {
|
|
||||||
final assets = await nativeApi.getAssetsForAlbum(album.id);
|
|
||||||
await db.batch((batch) {
|
|
||||||
for (final asset in assets) {
|
|
||||||
batch.update(
|
|
||||||
db.localAssetEntity,
|
|
||||||
LocalAssetEntityCompanion(playbackStyle: Value(_toPlaybackStyle(asset.playbackStyle))),
|
|
||||||
where: (t) => t.id.equals(asset.id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
final trashedAssetMap = await nativeApi.getTrashedAssets();
|
|
||||||
for (final assets in trashedAssetMap.values) {
|
|
||||||
await db.batch((batch) {
|
|
||||||
for (final asset in assets) {
|
|
||||||
batch.update(
|
|
||||||
db.trashedLocalAssetEntity,
|
|
||||||
TrashedLocalAssetEntityCompanion(playbackStyle: Value(_toPlaybackStyle(asset.playbackStyle))),
|
|
||||||
where: (t) => t.id.equals(asset.id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
dPrint(() => "[MIGRATION] Successfully populated playbackStyle for local and trashed assets");
|
|
||||||
} catch (error) {
|
|
||||||
dPrint(() => "[MIGRATION] Error while populating playbackStyle: $error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetPlaybackStyle _toPlaybackStyle(PlatformAssetPlaybackStyle style) => switch (style) {
|
|
||||||
PlatformAssetPlaybackStyle.unknown => AssetPlaybackStyle.unknown,
|
|
||||||
PlatformAssetPlaybackStyle.image => AssetPlaybackStyle.image,
|
|
||||||
PlatformAssetPlaybackStyle.video => AssetPlaybackStyle.video,
|
|
||||||
PlatformAssetPlaybackStyle.imageAnimated => AssetPlaybackStyle.imageAnimated,
|
|
||||||
PlatformAssetPlaybackStyle.livePhoto => AssetPlaybackStyle.livePhoto,
|
|
||||||
PlatformAssetPlaybackStyle.videoLooping => AssetPlaybackStyle.videoLooping,
|
|
||||||
};
|
|
||||||
|
|
||||||
class _DeviceAsset {
|
class _DeviceAsset {
|
||||||
final String assetId;
|
final String assetId;
|
||||||
final List<int>? hash;
|
final List<int>? hash;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import 'package:immich_mobile/services/stack.service.dart';
|
|||||||
import 'package:immich_mobile/utils/immich_loading_overlay.dart';
|
import 'package:immich_mobile/utils/immich_loading_overlay.dart';
|
||||||
import 'package:immich_mobile/utils/selection_handlers.dart';
|
import 'package:immich_mobile/utils/selection_handlers.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
||||||
|
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_grid/control_bottom_app_bar.dart';
|
import 'package:immich_mobile/widgets/asset_grid/control_bottom_app_bar.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart';
|
import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
@@ -405,10 +406,10 @@ class MultiselectGrid extends HookConsumerWidget {
|
|||||||
bottom: false,
|
bottom: false,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
ref
|
ref.watch(renderListProvider).widgetWhen(
|
||||||
.watch(renderListProvider)
|
onLoading: buildLoadingIndicator ?? buildDefaultLoadingIndicator,
|
||||||
.when(
|
onError: (error, _) => Center(child: Text(error.toString())),
|
||||||
data: (data) => data.isEmpty && (buildLoadingIndicator != null || topWidget == null)
|
onData: (data) => data.isEmpty && (buildLoadingIndicator != null || topWidget == null)
|
||||||
? (buildLoadingIndicator ?? buildEmptyIndicator)()
|
? (buildLoadingIndicator ?? buildEmptyIndicator)()
|
||||||
: ImmichAssetGrid(
|
: ImmichAssetGrid(
|
||||||
renderList: data,
|
renderList: data,
|
||||||
@@ -419,8 +420,6 @@ class MultiselectGrid extends HookConsumerWidget {
|
|||||||
showStack: stackEnabled,
|
showStack: stackEnabled,
|
||||||
showDragScrollLabel: dragScrollLabelEnabled,
|
showDragScrollLabel: dragScrollLabelEnabled,
|
||||||
),
|
),
|
||||||
error: (error, _) => Center(child: Text(error.toString())),
|
|
||||||
loading: buildLoadingIndicator ?? buildDefaultLoadingIndicator,
|
|
||||||
),
|
),
|
||||||
if (selectionEnabledHook.value)
|
if (selectionEnabledHook.value)
|
||||||
ControlBottomAppBar(
|
ControlBottomAppBar(
|
||||||
|
|||||||
+2
-2
@@ -3,8 +3,8 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|||||||
import 'package:immich_mobile/generated/translations.g.dart';
|
import 'package:immich_mobile/generated/translations.g.dart';
|
||||||
import 'package:immich_ui/immich_ui.dart';
|
import 'package:immich_ui/immich_ui.dart';
|
||||||
|
|
||||||
class PermanentDeleteDialog extends StatelessWidget {
|
class TrashDeleteDialog extends StatelessWidget {
|
||||||
const PermanentDeleteDialog({super.key, required this.count});
|
const TrashDeleteDialog({super.key, required this.count});
|
||||||
|
|
||||||
final int count;
|
final int count;
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ class ImmichSliverAppBar extends ConsumerWidget {
|
|||||||
pinned: pinned,
|
pinned: pinned,
|
||||||
snap: snap,
|
snap: snap,
|
||||||
expandedHeight: expandedHeight,
|
expandedHeight: expandedHeight,
|
||||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(bottom: Radius.circular(5))),
|
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5))),
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
title: title ?? const _ImmichLogoWithText(),
|
title: title ?? const _ImmichLogoWithText(),
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -6,7 +5,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/extensions/build_context_extensions.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/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/generated/translations.g.dart';
|
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||||
@@ -89,27 +87,25 @@ class SyncStatusAndActions extends HookConsumerWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(context.t.reset_sqlite),
|
title: Text("reset_sqlite".t(context: context)),
|
||||||
content: Text(context.t.reset_sqlite_confirmation),
|
content: Text("reset_sqlite_confirmation".t(context: context)),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(onPressed: () => context.pop(), child: Text(context.t.cancel)),
|
TextButton(
|
||||||
|
onPressed: () => context.pop(),
|
||||||
|
child: Text("cancel".t(context: context)),
|
||||||
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await ref.read(driftProvider).reset();
|
await ref.read(driftProvider).reset();
|
||||||
context.pop();
|
context.pop();
|
||||||
unawaited(
|
context.scaffoldMessenger.showSnackBar(
|
||||||
showDialog<void>(
|
SnackBar(content: Text("reset_sqlite_success".t(context: context))),
|
||||||
context: context,
|
|
||||||
barrierDismissible: false,
|
|
||||||
builder: (ctx) => AlertDialog(
|
|
||||||
title: Text(context.t.reset_sqlite_success),
|
|
||||||
content: Text(context.t.reset_sqlite_done),
|
|
||||||
actions: [TextButton(onPressed: () => ctx.pop(), child: Text(context.t.ok))],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Text(context.t.confirm, style: TextStyle(color: context.colorScheme.error)),
|
child: Text(
|
||||||
|
"confirm".t(context: context),
|
||||||
|
style: TextStyle(color: context.colorScheme.error),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
Generated
-2
@@ -576,8 +576,6 @@ Class | Method | HTTP request | Description
|
|||||||
- [SyncAlbumUserV1](doc//SyncAlbumUserV1.md)
|
- [SyncAlbumUserV1](doc//SyncAlbumUserV1.md)
|
||||||
- [SyncAlbumV1](doc//SyncAlbumV1.md)
|
- [SyncAlbumV1](doc//SyncAlbumV1.md)
|
||||||
- [SyncAssetDeleteV1](doc//SyncAssetDeleteV1.md)
|
- [SyncAssetDeleteV1](doc//SyncAssetDeleteV1.md)
|
||||||
- [SyncAssetEditDeleteV1](doc//SyncAssetEditDeleteV1.md)
|
|
||||||
- [SyncAssetEditV1](doc//SyncAssetEditV1.md)
|
|
||||||
- [SyncAssetExifV1](doc//SyncAssetExifV1.md)
|
- [SyncAssetExifV1](doc//SyncAssetExifV1.md)
|
||||||
- [SyncAssetFaceDeleteV1](doc//SyncAssetFaceDeleteV1.md)
|
- [SyncAssetFaceDeleteV1](doc//SyncAssetFaceDeleteV1.md)
|
||||||
- [SyncAssetFaceV1](doc//SyncAssetFaceV1.md)
|
- [SyncAssetFaceV1](doc//SyncAssetFaceV1.md)
|
||||||
|
|||||||
Generated
-2
@@ -315,8 +315,6 @@ part 'model/sync_album_user_delete_v1.dart';
|
|||||||
part 'model/sync_album_user_v1.dart';
|
part 'model/sync_album_user_v1.dart';
|
||||||
part 'model/sync_album_v1.dart';
|
part 'model/sync_album_v1.dart';
|
||||||
part 'model/sync_asset_delete_v1.dart';
|
part 'model/sync_asset_delete_v1.dart';
|
||||||
part 'model/sync_asset_edit_delete_v1.dart';
|
|
||||||
part 'model/sync_asset_edit_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';
|
||||||
|
|||||||
Generated
+2
-2
@@ -410,7 +410,7 @@ class SearchApi {
|
|||||||
/// Filter by person IDs
|
/// Filter by person IDs
|
||||||
///
|
///
|
||||||
/// * [num] rating:
|
/// * [num] rating:
|
||||||
/// Filter by rating [1-5], or null for unrated
|
/// Filter by rating
|
||||||
///
|
///
|
||||||
/// * [num] size:
|
/// * [num] size:
|
||||||
/// Number of results to return
|
/// Number of results to return
|
||||||
@@ -633,7 +633,7 @@ class SearchApi {
|
|||||||
/// Filter by person IDs
|
/// Filter by person IDs
|
||||||
///
|
///
|
||||||
/// * [num] rating:
|
/// * [num] rating:
|
||||||
/// Filter by rating [1-5], or null for unrated
|
/// Filter by rating
|
||||||
///
|
///
|
||||||
/// * [num] size:
|
/// * [num] size:
|
||||||
/// Number of results to return
|
/// Number of results to return
|
||||||
|
|||||||
Generated
+6
-24
@@ -30,9 +30,6 @@ class TimelineApi {
|
|||||||
/// * [String] albumId:
|
/// * [String] albumId:
|
||||||
/// Filter assets belonging to a specific album
|
/// Filter assets belonging to a specific album
|
||||||
///
|
///
|
||||||
/// * [String] bbox:
|
|
||||||
/// Bounding box coordinates as west,south,east,north (WGS84)
|
|
||||||
///
|
|
||||||
/// * [bool] isFavorite:
|
/// * [bool] isFavorite:
|
||||||
/// Filter by favorite status (true for favorites only, false for non-favorites only)
|
/// Filter by favorite status (true for favorites only, false for non-favorites only)
|
||||||
///
|
///
|
||||||
@@ -66,7 +63,7 @@ class TimelineApi {
|
|||||||
///
|
///
|
||||||
/// * [bool] withStacked:
|
/// * [bool] withStacked:
|
||||||
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
||||||
Future<Response> getTimeBucketWithHttpInfo(String timeBucket, { String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
Future<Response> getTimeBucketWithHttpInfo(String timeBucket, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/timeline/bucket';
|
final apiPath = r'/timeline/bucket';
|
||||||
|
|
||||||
@@ -80,9 +77,6 @@ class TimelineApi {
|
|||||||
if (albumId != null) {
|
if (albumId != null) {
|
||||||
queryParams.addAll(_queryParams('', 'albumId', albumId));
|
queryParams.addAll(_queryParams('', 'albumId', albumId));
|
||||||
}
|
}
|
||||||
if (bbox != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'bbox', bbox));
|
|
||||||
}
|
|
||||||
if (isFavorite != null) {
|
if (isFavorite != null) {
|
||||||
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
|
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
|
||||||
}
|
}
|
||||||
@@ -147,9 +141,6 @@ class TimelineApi {
|
|||||||
/// * [String] albumId:
|
/// * [String] albumId:
|
||||||
/// Filter assets belonging to a specific album
|
/// Filter assets belonging to a specific album
|
||||||
///
|
///
|
||||||
/// * [String] bbox:
|
|
||||||
/// Bounding box coordinates as west,south,east,north (WGS84)
|
|
||||||
///
|
|
||||||
/// * [bool] isFavorite:
|
/// * [bool] isFavorite:
|
||||||
/// Filter by favorite status (true for favorites only, false for non-favorites only)
|
/// Filter by favorite status (true for favorites only, false for non-favorites only)
|
||||||
///
|
///
|
||||||
@@ -183,8 +174,8 @@ class TimelineApi {
|
|||||||
///
|
///
|
||||||
/// * [bool] withStacked:
|
/// * [bool] withStacked:
|
||||||
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
||||||
Future<TimeBucketAssetResponseDto?> getTimeBucket(String timeBucket, { String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
Future<TimeBucketAssetResponseDto?> getTimeBucket(String timeBucket, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||||
final response = await getTimeBucketWithHttpInfo(timeBucket, albumId: albumId, bbox: bbox, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, );
|
final response = await getTimeBucketWithHttpInfo(timeBucket, albumId: albumId, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, );
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
}
|
}
|
||||||
@@ -209,9 +200,6 @@ class TimelineApi {
|
|||||||
/// * [String] albumId:
|
/// * [String] albumId:
|
||||||
/// Filter assets belonging to a specific album
|
/// Filter assets belonging to a specific album
|
||||||
///
|
///
|
||||||
/// * [String] bbox:
|
|
||||||
/// Bounding box coordinates as west,south,east,north (WGS84)
|
|
||||||
///
|
|
||||||
/// * [bool] isFavorite:
|
/// * [bool] isFavorite:
|
||||||
/// Filter by favorite status (true for favorites only, false for non-favorites only)
|
/// Filter by favorite status (true for favorites only, false for non-favorites only)
|
||||||
///
|
///
|
||||||
@@ -245,7 +233,7 @@ class TimelineApi {
|
|||||||
///
|
///
|
||||||
/// * [bool] withStacked:
|
/// * [bool] withStacked:
|
||||||
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
||||||
Future<Response> getTimeBucketsWithHttpInfo({ String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
Future<Response> getTimeBucketsWithHttpInfo({ String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/timeline/buckets';
|
final apiPath = r'/timeline/buckets';
|
||||||
|
|
||||||
@@ -259,9 +247,6 @@ class TimelineApi {
|
|||||||
if (albumId != null) {
|
if (albumId != null) {
|
||||||
queryParams.addAll(_queryParams('', 'albumId', albumId));
|
queryParams.addAll(_queryParams('', 'albumId', albumId));
|
||||||
}
|
}
|
||||||
if (bbox != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'bbox', bbox));
|
|
||||||
}
|
|
||||||
if (isFavorite != null) {
|
if (isFavorite != null) {
|
||||||
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
|
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
|
||||||
}
|
}
|
||||||
@@ -322,9 +307,6 @@ class TimelineApi {
|
|||||||
/// * [String] albumId:
|
/// * [String] albumId:
|
||||||
/// Filter assets belonging to a specific album
|
/// Filter assets belonging to a specific album
|
||||||
///
|
///
|
||||||
/// * [String] bbox:
|
|
||||||
/// Bounding box coordinates as west,south,east,north (WGS84)
|
|
||||||
///
|
|
||||||
/// * [bool] isFavorite:
|
/// * [bool] isFavorite:
|
||||||
/// Filter by favorite status (true for favorites only, false for non-favorites only)
|
/// Filter by favorite status (true for favorites only, false for non-favorites only)
|
||||||
///
|
///
|
||||||
@@ -358,8 +340,8 @@ class TimelineApi {
|
|||||||
///
|
///
|
||||||
/// * [bool] withStacked:
|
/// * [bool] withStacked:
|
||||||
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
||||||
Future<List<TimeBucketsResponseDto>?> getTimeBuckets({ String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
Future<List<TimeBucketsResponseDto>?> getTimeBuckets({ String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||||
final response = await getTimeBucketsWithHttpInfo( albumId: albumId, bbox: bbox, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, );
|
final response = await getTimeBucketsWithHttpInfo( albumId: albumId, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, );
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
-4
@@ -676,10 +676,6 @@ class ApiClient {
|
|||||||
return SyncAlbumV1.fromJson(value);
|
return SyncAlbumV1.fromJson(value);
|
||||||
case 'SyncAssetDeleteV1':
|
case 'SyncAssetDeleteV1':
|
||||||
return SyncAssetDeleteV1.fromJson(value);
|
return SyncAssetDeleteV1.fromJson(value);
|
||||||
case 'SyncAssetEditDeleteV1':
|
|
||||||
return SyncAssetEditDeleteV1.fromJson(value);
|
|
||||||
case 'SyncAssetEditV1':
|
|
||||||
return SyncAssetEditV1.fromJson(value);
|
|
||||||
case 'SyncAssetExifV1':
|
case 'SyncAssetExifV1':
|
||||||
return SyncAssetExifV1.fromJson(value);
|
return SyncAssetExifV1.fromJson(value);
|
||||||
case 'SyncAssetFaceDeleteV1':
|
case 'SyncAssetFaceDeleteV1':
|
||||||
|
|||||||
+8
-4
@@ -86,10 +86,16 @@ class AssetBulkUpdateDto {
|
|||||||
///
|
///
|
||||||
num? longitude;
|
num? longitude;
|
||||||
|
|
||||||
/// Rating in range [1-5], or null for unrated
|
/// Rating
|
||||||
///
|
///
|
||||||
/// Minimum value: -1
|
/// Minimum value: -1
|
||||||
/// Maximum value: 5
|
/// Maximum value: 5
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
num? rating;
|
num? rating;
|
||||||
|
|
||||||
/// Time zone (IANA timezone)
|
/// Time zone (IANA timezone)
|
||||||
@@ -217,9 +223,7 @@ class AssetBulkUpdateDto {
|
|||||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
||||||
latitude: num.parse('${json[r'latitude']}'),
|
latitude: num.parse('${json[r'latitude']}'),
|
||||||
longitude: num.parse('${json[r'longitude']}'),
|
longitude: num.parse('${json[r'longitude']}'),
|
||||||
rating: json[r'rating'] == null
|
rating: num.parse('${json[r'rating']}'),
|
||||||
? null
|
|
||||||
: num.parse('${json[r'rating']}'),
|
|
||||||
timeZone: mapValueOfType<String>(json, r'timeZone'),
|
timeZone: mapValueOfType<String>(json, r'timeZone'),
|
||||||
visibility: AssetVisibility.fromJson(json[r'visibility']),
|
visibility: AssetVisibility.fromJson(json[r'visibility']),
|
||||||
);
|
);
|
||||||
|
|||||||
+8
-4
@@ -256,10 +256,16 @@ class MetadataSearchDto {
|
|||||||
///
|
///
|
||||||
String? previewPath;
|
String? previewPath;
|
||||||
|
|
||||||
/// Filter by rating [1-5], or null for unrated
|
/// Filter by rating
|
||||||
///
|
///
|
||||||
/// Minimum value: -1
|
/// Minimum value: -1
|
||||||
/// Maximum value: 5
|
/// Maximum value: 5
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
num? rating;
|
num? rating;
|
||||||
|
|
||||||
/// Number of results to return
|
/// Number of results to return
|
||||||
@@ -748,9 +754,7 @@ class MetadataSearchDto {
|
|||||||
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
||||||
: const [],
|
: const [],
|
||||||
previewPath: mapValueOfType<String>(json, r'previewPath'),
|
previewPath: mapValueOfType<String>(json, r'previewPath'),
|
||||||
rating: json[r'rating'] == null
|
rating: num.parse('${json[r'rating']}'),
|
||||||
? null
|
|
||||||
: num.parse('${json[r'rating']}'),
|
|
||||||
size: num.parse('${json[r'size']}'),
|
size: num.parse('${json[r'size']}'),
|
||||||
state: mapValueOfType<String>(json, r'state'),
|
state: mapValueOfType<String>(json, r'state'),
|
||||||
tagIds: json[r'tagIds'] is Iterable
|
tagIds: json[r'tagIds'] is Iterable
|
||||||
|
|||||||
+8
-4
@@ -159,10 +159,16 @@ class RandomSearchDto {
|
|||||||
/// Filter by person IDs
|
/// Filter by person IDs
|
||||||
List<String> personIds;
|
List<String> personIds;
|
||||||
|
|
||||||
/// Filter by rating [1-5], or null for unrated
|
/// Filter by rating
|
||||||
///
|
///
|
||||||
/// Minimum value: -1
|
/// Minimum value: -1
|
||||||
/// Maximum value: 5
|
/// Maximum value: 5
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
num? rating;
|
num? rating;
|
||||||
|
|
||||||
/// Number of results to return
|
/// Number of results to return
|
||||||
@@ -559,9 +565,7 @@ class RandomSearchDto {
|
|||||||
personIds: json[r'personIds'] is Iterable
|
personIds: json[r'personIds'] is Iterable
|
||||||
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
||||||
: const [],
|
: const [],
|
||||||
rating: json[r'rating'] == null
|
rating: num.parse('${json[r'rating']}'),
|
||||||
? null
|
|
||||||
: num.parse('${json[r'rating']}'),
|
|
||||||
size: num.parse('${json[r'size']}'),
|
size: num.parse('${json[r'size']}'),
|
||||||
state: mapValueOfType<String>(json, r'state'),
|
state: mapValueOfType<String>(json, r'state'),
|
||||||
tagIds: json[r'tagIds'] is Iterable
|
tagIds: json[r'tagIds'] is Iterable
|
||||||
|
|||||||
+8
-4
@@ -199,10 +199,16 @@ class SmartSearchDto {
|
|||||||
///
|
///
|
||||||
String? queryAssetId;
|
String? queryAssetId;
|
||||||
|
|
||||||
/// Filter by rating [1-5], or null for unrated
|
/// Filter by rating
|
||||||
///
|
///
|
||||||
/// Minimum value: -1
|
/// Minimum value: -1
|
||||||
/// Maximum value: 5
|
/// Maximum value: 5
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
num? rating;
|
num? rating;
|
||||||
|
|
||||||
/// Number of results to return
|
/// Number of results to return
|
||||||
@@ -599,9 +605,7 @@ class SmartSearchDto {
|
|||||||
: const [],
|
: const [],
|
||||||
query: mapValueOfType<String>(json, r'query'),
|
query: mapValueOfType<String>(json, r'query'),
|
||||||
queryAssetId: mapValueOfType<String>(json, r'queryAssetId'),
|
queryAssetId: mapValueOfType<String>(json, r'queryAssetId'),
|
||||||
rating: json[r'rating'] == null
|
rating: num.parse('${json[r'rating']}'),
|
||||||
? null
|
|
||||||
: num.parse('${json[r'rating']}'),
|
|
||||||
size: num.parse('${json[r'size']}'),
|
size: num.parse('${json[r'size']}'),
|
||||||
state: mapValueOfType<String>(json, r'state'),
|
state: mapValueOfType<String>(json, r'state'),
|
||||||
tagIds: json[r'tagIds'] is Iterable
|
tagIds: json[r'tagIds'] is Iterable
|
||||||
|
|||||||
+8
-4
@@ -164,10 +164,16 @@ class StatisticsSearchDto {
|
|||||||
/// Filter by person IDs
|
/// Filter by person IDs
|
||||||
List<String> personIds;
|
List<String> personIds;
|
||||||
|
|
||||||
/// Filter by rating [1-5], or null for unrated
|
/// Filter by rating
|
||||||
///
|
///
|
||||||
/// Minimum value: -1
|
/// Minimum value: -1
|
||||||
/// Maximum value: 5
|
/// Maximum value: 5
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
num? rating;
|
num? rating;
|
||||||
|
|
||||||
/// Filter by state/province name
|
/// Filter by state/province name
|
||||||
@@ -489,9 +495,7 @@ class StatisticsSearchDto {
|
|||||||
personIds: json[r'personIds'] is Iterable
|
personIds: json[r'personIds'] is Iterable
|
||||||
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
||||||
: const [],
|
: const [],
|
||||||
rating: json[r'rating'] == null
|
rating: num.parse('${json[r'rating']}'),
|
||||||
? null
|
|
||||||
: num.parse('${json[r'rating']}'),
|
|
||||||
state: mapValueOfType<String>(json, r'state'),
|
state: mapValueOfType<String>(json, r'state'),
|
||||||
tagIds: json[r'tagIds'] is Iterable
|
tagIds: json[r'tagIds'] is Iterable
|
||||||
? (json[r'tagIds'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'tagIds'] as Iterable).cast<String>().toList(growable: false)
|
||||||
|
|||||||
@@ -1,99 +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 SyncAssetEditDeleteV1 {
|
|
||||||
/// Returns a new [SyncAssetEditDeleteV1] instance.
|
|
||||||
SyncAssetEditDeleteV1({
|
|
||||||
required this.editId,
|
|
||||||
});
|
|
||||||
|
|
||||||
String editId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) => identical(this, other) || other is SyncAssetEditDeleteV1 &&
|
|
||||||
other.editId == editId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode =>
|
|
||||||
// ignore: unnecessary_parenthesis
|
|
||||||
(editId.hashCode);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'SyncAssetEditDeleteV1[editId=$editId]';
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final json = <String, dynamic>{};
|
|
||||||
json[r'editId'] = this.editId;
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a new [SyncAssetEditDeleteV1] instance and imports its values from
|
|
||||||
/// [value] if it's a [Map], null otherwise.
|
|
||||||
// ignore: prefer_constructors_over_static_methods
|
|
||||||
static SyncAssetEditDeleteV1? fromJson(dynamic value) {
|
|
||||||
upgradeDto(value, "SyncAssetEditDeleteV1");
|
|
||||||
if (value is Map) {
|
|
||||||
final json = value.cast<String, dynamic>();
|
|
||||||
|
|
||||||
return SyncAssetEditDeleteV1(
|
|
||||||
editId: mapValueOfType<String>(json, r'editId')!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<SyncAssetEditDeleteV1> listFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final result = <SyncAssetEditDeleteV1>[];
|
|
||||||
if (json is List && json.isNotEmpty) {
|
|
||||||
for (final row in json) {
|
|
||||||
final value = SyncAssetEditDeleteV1.fromJson(row);
|
|
||||||
if (value != null) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toList(growable: growable);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, SyncAssetEditDeleteV1> mapFromJson(dynamic json) {
|
|
||||||
final map = <String, SyncAssetEditDeleteV1>{};
|
|
||||||
if (json is Map && json.isNotEmpty) {
|
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
final value = SyncAssetEditDeleteV1.fromJson(entry.value);
|
|
||||||
if (value != null) {
|
|
||||||
map[entry.key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
// maps a json object with a list of SyncAssetEditDeleteV1-objects as value to a dart map
|
|
||||||
static Map<String, List<SyncAssetEditDeleteV1>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final map = <String, List<SyncAssetEditDeleteV1>>{};
|
|
||||||
if (json is Map && json.isNotEmpty) {
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
json = json.cast<String, dynamic>();
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
map[entry.key] = SyncAssetEditDeleteV1.listFromJson(entry.value, growable: growable,);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The list of required keys that must be present in a JSON.
|
|
||||||
static const requiredKeys = <String>{
|
|
||||||
'editId',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
-131
@@ -1,131 +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 SyncAssetEditV1 {
|
|
||||||
/// Returns a new [SyncAssetEditV1] instance.
|
|
||||||
SyncAssetEditV1({
|
|
||||||
required this.action,
|
|
||||||
required this.assetId,
|
|
||||||
required this.id,
|
|
||||||
required this.parameters,
|
|
||||||
required this.sequence,
|
|
||||||
});
|
|
||||||
|
|
||||||
AssetEditAction action;
|
|
||||||
|
|
||||||
String assetId;
|
|
||||||
|
|
||||||
String id;
|
|
||||||
|
|
||||||
Object parameters;
|
|
||||||
|
|
||||||
int sequence;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) => identical(this, other) || other is SyncAssetEditV1 &&
|
|
||||||
other.action == action &&
|
|
||||||
other.assetId == assetId &&
|
|
||||||
other.id == id &&
|
|
||||||
other.parameters == parameters &&
|
|
||||||
other.sequence == sequence;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode =>
|
|
||||||
// ignore: unnecessary_parenthesis
|
|
||||||
(action.hashCode) +
|
|
||||||
(assetId.hashCode) +
|
|
||||||
(id.hashCode) +
|
|
||||||
(parameters.hashCode) +
|
|
||||||
(sequence.hashCode);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'SyncAssetEditV1[action=$action, assetId=$assetId, id=$id, parameters=$parameters, sequence=$sequence]';
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final json = <String, dynamic>{};
|
|
||||||
json[r'action'] = this.action;
|
|
||||||
json[r'assetId'] = this.assetId;
|
|
||||||
json[r'id'] = this.id;
|
|
||||||
json[r'parameters'] = this.parameters;
|
|
||||||
json[r'sequence'] = this.sequence;
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a new [SyncAssetEditV1] instance and imports its values from
|
|
||||||
/// [value] if it's a [Map], null otherwise.
|
|
||||||
// ignore: prefer_constructors_over_static_methods
|
|
||||||
static SyncAssetEditV1? fromJson(dynamic value) {
|
|
||||||
upgradeDto(value, "SyncAssetEditV1");
|
|
||||||
if (value is Map) {
|
|
||||||
final json = value.cast<String, dynamic>();
|
|
||||||
|
|
||||||
return SyncAssetEditV1(
|
|
||||||
action: AssetEditAction.fromJson(json[r'action'])!,
|
|
||||||
assetId: mapValueOfType<String>(json, r'assetId')!,
|
|
||||||
id: mapValueOfType<String>(json, r'id')!,
|
|
||||||
parameters: mapValueOfType<Object>(json, r'parameters')!,
|
|
||||||
sequence: mapValueOfType<int>(json, r'sequence')!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<SyncAssetEditV1> listFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final result = <SyncAssetEditV1>[];
|
|
||||||
if (json is List && json.isNotEmpty) {
|
|
||||||
for (final row in json) {
|
|
||||||
final value = SyncAssetEditV1.fromJson(row);
|
|
||||||
if (value != null) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toList(growable: growable);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, SyncAssetEditV1> mapFromJson(dynamic json) {
|
|
||||||
final map = <String, SyncAssetEditV1>{};
|
|
||||||
if (json is Map && json.isNotEmpty) {
|
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
final value = SyncAssetEditV1.fromJson(entry.value);
|
|
||||||
if (value != null) {
|
|
||||||
map[entry.key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
// maps a json object with a list of SyncAssetEditV1-objects as value to a dart map
|
|
||||||
static Map<String, List<SyncAssetEditV1>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final map = <String, List<SyncAssetEditV1>>{};
|
|
||||||
if (json is Map && json.isNotEmpty) {
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
json = json.cast<String, dynamic>();
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
map[entry.key] = SyncAssetEditV1.listFromJson(entry.value, growable: growable,);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The list of required keys that must be present in a JSON.
|
|
||||||
static const requiredKeys = <String>{
|
|
||||||
'action',
|
|
||||||
'assetId',
|
|
||||||
'id',
|
|
||||||
'parameters',
|
|
||||||
'sequence',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
-6
@@ -29,8 +29,6 @@ class SyncEntityType {
|
|||||||
static const assetV1 = SyncEntityType._(r'AssetV1');
|
static const assetV1 = SyncEntityType._(r'AssetV1');
|
||||||
static const assetDeleteV1 = SyncEntityType._(r'AssetDeleteV1');
|
static const assetDeleteV1 = SyncEntityType._(r'AssetDeleteV1');
|
||||||
static const assetExifV1 = SyncEntityType._(r'AssetExifV1');
|
static const assetExifV1 = SyncEntityType._(r'AssetExifV1');
|
||||||
static const assetEditV1 = SyncEntityType._(r'AssetEditV1');
|
|
||||||
static const assetEditDeleteV1 = SyncEntityType._(r'AssetEditDeleteV1');
|
|
||||||
static const assetMetadataV1 = SyncEntityType._(r'AssetMetadataV1');
|
static const assetMetadataV1 = SyncEntityType._(r'AssetMetadataV1');
|
||||||
static const assetMetadataDeleteV1 = SyncEntityType._(r'AssetMetadataDeleteV1');
|
static const assetMetadataDeleteV1 = SyncEntityType._(r'AssetMetadataDeleteV1');
|
||||||
static const partnerV1 = SyncEntityType._(r'PartnerV1');
|
static const partnerV1 = SyncEntityType._(r'PartnerV1');
|
||||||
@@ -82,8 +80,6 @@ class SyncEntityType {
|
|||||||
assetV1,
|
assetV1,
|
||||||
assetDeleteV1,
|
assetDeleteV1,
|
||||||
assetExifV1,
|
assetExifV1,
|
||||||
assetEditV1,
|
|
||||||
assetEditDeleteV1,
|
|
||||||
assetMetadataV1,
|
assetMetadataV1,
|
||||||
assetMetadataDeleteV1,
|
assetMetadataDeleteV1,
|
||||||
partnerV1,
|
partnerV1,
|
||||||
@@ -170,8 +166,6 @@ class SyncEntityTypeTypeTransformer {
|
|||||||
case r'AssetV1': return SyncEntityType.assetV1;
|
case r'AssetV1': return SyncEntityType.assetV1;
|
||||||
case r'AssetDeleteV1': return SyncEntityType.assetDeleteV1;
|
case r'AssetDeleteV1': return SyncEntityType.assetDeleteV1;
|
||||||
case r'AssetExifV1': return SyncEntityType.assetExifV1;
|
case r'AssetExifV1': return SyncEntityType.assetExifV1;
|
||||||
case r'AssetEditV1': return SyncEntityType.assetEditV1;
|
|
||||||
case r'AssetEditDeleteV1': return SyncEntityType.assetEditDeleteV1;
|
|
||||||
case r'AssetMetadataV1': return SyncEntityType.assetMetadataV1;
|
case r'AssetMetadataV1': return SyncEntityType.assetMetadataV1;
|
||||||
case r'AssetMetadataDeleteV1': return SyncEntityType.assetMetadataDeleteV1;
|
case r'AssetMetadataDeleteV1': return SyncEntityType.assetMetadataDeleteV1;
|
||||||
case r'PartnerV1': return SyncEntityType.partnerV1;
|
case r'PartnerV1': return SyncEntityType.partnerV1;
|
||||||
|
|||||||
-3
@@ -30,7 +30,6 @@ class SyncRequestType {
|
|||||||
static const albumAssetExifsV1 = SyncRequestType._(r'AlbumAssetExifsV1');
|
static const albumAssetExifsV1 = SyncRequestType._(r'AlbumAssetExifsV1');
|
||||||
static const assetsV1 = SyncRequestType._(r'AssetsV1');
|
static const assetsV1 = SyncRequestType._(r'AssetsV1');
|
||||||
static const assetExifsV1 = SyncRequestType._(r'AssetExifsV1');
|
static const assetExifsV1 = SyncRequestType._(r'AssetExifsV1');
|
||||||
static const assetEditsV1 = SyncRequestType._(r'AssetEditsV1');
|
|
||||||
static const assetMetadataV1 = SyncRequestType._(r'AssetMetadataV1');
|
static const assetMetadataV1 = SyncRequestType._(r'AssetMetadataV1');
|
||||||
static const authUsersV1 = SyncRequestType._(r'AuthUsersV1');
|
static const authUsersV1 = SyncRequestType._(r'AuthUsersV1');
|
||||||
static const memoriesV1 = SyncRequestType._(r'MemoriesV1');
|
static const memoriesV1 = SyncRequestType._(r'MemoriesV1');
|
||||||
@@ -55,7 +54,6 @@ class SyncRequestType {
|
|||||||
albumAssetExifsV1,
|
albumAssetExifsV1,
|
||||||
assetsV1,
|
assetsV1,
|
||||||
assetExifsV1,
|
assetExifsV1,
|
||||||
assetEditsV1,
|
|
||||||
assetMetadataV1,
|
assetMetadataV1,
|
||||||
authUsersV1,
|
authUsersV1,
|
||||||
memoriesV1,
|
memoriesV1,
|
||||||
@@ -115,7 +113,6 @@ class SyncRequestTypeTypeTransformer {
|
|||||||
case r'AlbumAssetExifsV1': return SyncRequestType.albumAssetExifsV1;
|
case r'AlbumAssetExifsV1': return SyncRequestType.albumAssetExifsV1;
|
||||||
case r'AssetsV1': return SyncRequestType.assetsV1;
|
case r'AssetsV1': return SyncRequestType.assetsV1;
|
||||||
case r'AssetExifsV1': return SyncRequestType.assetExifsV1;
|
case r'AssetExifsV1': return SyncRequestType.assetExifsV1;
|
||||||
case r'AssetEditsV1': return SyncRequestType.assetEditsV1;
|
|
||||||
case r'AssetMetadataV1': return SyncRequestType.assetMetadataV1;
|
case r'AssetMetadataV1': return SyncRequestType.assetMetadataV1;
|
||||||
case r'AuthUsersV1': return SyncRequestType.authUsersV1;
|
case r'AuthUsersV1': return SyncRequestType.authUsersV1;
|
||||||
case r'MemoriesV1': return SyncRequestType.memoriesV1;
|
case r'MemoriesV1': return SyncRequestType.memoriesV1;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user