Compare commits

..

28 Commits

Author SHA1 Message Date
Daniel Dietzler
d1f92e1c38 chore: downgrade zod and kysely 2026-07-21 12:48:57 +02:00
renovate[bot]
1cda6d96cd fix(deps): update typescript-projects 2026-07-21 10:34:59 +00:00
renovate[bot]
e918658cc1 chore(deps): update ghcr.io/jdx/mise docker tag to v2026.7.11 (#30086) 2026-07-21 12:24:32 +02:00
Nikola Nedevski
2e587fc7e8 feat(docs): add bulgarian readme (#29427)
* Added Bulgarian readme.md

* Updated collaborators link for BG readme collaborators section.
2026-07-21 00:10:08 -04:00
renovate[bot]
df970da59e chore(deps): update dependency eslint-plugin-unicorn to v70 - abandoned (#29684)
* chore(deps): update dependency eslint-plugin-unicorn to v70

* fix: linting

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-07-20 23:47:14 -04:00
Rust
8061a2e5ff fix(web): fix Country/State filters when set to Unknown (#30026)
* fix(web): create emptyStringToNull helper func and apply it to country/state search payload generation

* chore: feedback

* fix: other empty string fields

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2026-07-20 21:15:00 -05:00
Daniel Dietzler
77091b0107 fix: search statistics locked folder permissions (#30063)
fix; search statistics locked folder permissions
2026-07-20 20:08:42 -04:00
Daniel Dietzler
4c754f2999 fix: timebuckets locked permissions (#30066) 2026-07-20 20:07:56 -04:00
Timon
6fa3f2feac chore: bump openapi-generator version to v7.24.0 (#30067)
* remove patch in favor of [OpenAPITools/openapi-generator#23930](https://github.com/OpenAPITools/openapi-generator/issues/23930)

partially revert "chore(mobile): make openapi requests abortable (#28692)"

* gen client

* bump openapi-generator version to v7.24.0

* gen client

* enum migration changes

* drop enum exhaustion switch check
2026-07-20 20:06:23 -04:00
Adam Gastineau
3f6897ef80 chore(mobile): enable Xcode displaying issues from Flutter inline (#30080) 2026-07-20 14:16:12 -05:00
Matthias Keim
89d0a9d59f fix(web): attach file picker input to DOM so iOS Safari fires change … (#29660)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-07-20 19:03:42 +00:00
Adam Gastineau
45a6ea84af fix(mobile): prevent crash on video widget dispose (#30078) 2026-07-20 19:00:37 +00:00
shenlong
bf64f3867b fix: do not show the whats new page on fresh login (#30072)
* fix: do not show the whats new page on fresh login

* review suggestion

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-20 12:24:01 -05:00
Priyanshu
4a4d468aa2 fix(web): refresh folder view after asset deletion (#29899) 2026-07-20 18:20:52 +02:00
Priyanshu
522def1ed6 fix(web): align ContextMenu z-index with design-system token (#30015)
Co-authored-by: priyanshuANDcoad <priyanshu23154050@akgec.ac.in>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-07-20 13:54:59 +00:00
Timon
3adc3920fb chore: bump openapi-generator version to v7.23.0 (#28916) 2026-07-20 15:06:51 +02:00
Matthew Momjian
00cb50cc67 fix(docs): remove ref to synology channel (#30051)
synology channel
2026-07-19 21:22:17 -05:00
Alex
f2b0b696f6 fix: long press share quality override preference settings (#30030) 2026-07-18 12:07:18 -05:00
Luis Nachtigall
b5401eb120 fix(mobile): update album creation to use user-defined name from dialog (#30002)
fix: update album creation to use user-defined name from dialog
2026-07-17 23:45:20 +05:30
Ben Beckford
12fc8bac18 feat: workflow filter assets by upload path (#30000)
Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2026-07-17 09:46:48 +00:00
Ben Beckford
f2c00c107d fix(server): return workflow steps in ascending order (#29999) 2026-07-17 11:30:40 +02:00
shenlong
f19f30ec66 fix: reset override for kebab buttons (#29380)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-17 01:19:55 +05:30
shenlong
297316c7b8 refactor: owned action assets filter (#29382)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-16 19:49:30 +00:00
renovate[bot]
f18065b3c2 chore(deps): update dependency @types/node to ^24.13.3 (#29892) 2026-07-16 20:31:45 +02:00
renovate[bot]
a3dd19cd2f fix(deps): update typescript-projects (#29633)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-07-16 20:14:05 +02:00
shenlong
9cea3f2375 chore: pump drift (#29113)
* chore: pump drift

* run codegen

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-16 17:32:30 +00:00
shenlong
507448d797 chore: pump home_widget (#29981)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-16 21:58:11 +05:30
Yaros
fe3eb7d865 feat(web): undo archive from toast (#27061)
Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2026-07-16 17:53:02 +02:00
434 changed files with 6788 additions and 7755 deletions

38
.vscode/settings.json vendored
View File

@@ -29,9 +29,6 @@
"editor.formatOnSave": true,
"tailwindCSS.lint.suggestCanonicalClasses": "ignore"
},
"svelte.plugin.svelte.compilerWarnings": {
"state_referenced_locally": "ignore"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
@@ -43,37 +40,40 @@
"eslint.useFlatConfig": true,
"eslint.validate": ["javascript", "typescript", "svelte"],
"eslint.workingDirectories": [
{ "directory": "cli", "changeProcessCWD": true },
{ "directory": "e2e", "changeProcessCWD": true },
{ "directory": "server", "changeProcessCWD": true },
{ "directory": "web", "changeProcessCWD": true }
{ "changeProcessCWD": true, "directory": "cli" },
{ "changeProcessCWD": true, "directory": "e2e" },
{ "changeProcessCWD": true, "directory": "server" },
{ "changeProcessCWD": true, "directory": "web" }
],
"files.watcherExclude": {
"**/.jj/**": true,
"**/.git/**": true,
"**/node_modules/**": true,
"**/build/**": true,
"**/dist/**": true,
"**/.svelte-kit/**": true
},
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.patterns": {
"*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart",
"*.ts": "${capture}.spec.ts,${capture}.mock.ts",
"*.js": "${capture}.spec.js,${capture}.mock.js",
"*.ts": "${capture}.spec.ts,${capture}.mock.ts",
"package.json": "package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb, bun.lock, pnpm-workspace.yaml, .pnpmfile.cjs"
},
"files.watcherExclude": {
"**/.git/**": true,
"**/.jj/**": true,
"**/.svelte-kit/**": true,
"**/build/**": true,
"**/dist/**": true,
"**/node_modules/**": true
},
"js/ts.preferences.importModuleSpecifier": "non-relative",
"search.exclude": {
"**/node_modules": true,
"**/.svelte-kit": true,
"**/build": true,
"**/dist": true,
"**/.svelte-kit": true,
"**/node_modules": true,
"**/open-api/typescript-sdk/src": true
},
"svelte.enable-ts-plugin": true,
"svelte.plugin.svelte.compilerWarnings": {
"state_referenced_locally": "ignore"
},
"tailwindCSS.experimental.configFile": {
"web/src/app.css": "web/src/**"
},
"js/ts.preferences.importModuleSpecifier": "non-relative",
"vitest.maximumConfigs": 10
}

View File

@@ -32,6 +32,7 @@
<a href="readme_i18n/README_zh_TW.md">正體中文</a>
<a href="readme_i18n/README_uk_UA.md">Українська</a>
<a href="readme_i18n/README_ru_RU.md">Русский</a>
<a href="readme_i18n/README_bg_BG.md">Български</a>
<a href="readme_i18n/README_pt_BR.md">Português Brasileiro</a>
<a href="readme_i18n/README_sv_SE.md">Svenska</a>
<a href="readme_i18n/README_ar_JO.md">العربية</a>

View File

@@ -7,7 +7,7 @@ sidebar_position: 85
:::note
This is a community contribution and not officially supported by the Immich team, but included here for convenience.
Community support can be found in the dedicated channel on the [Discord Server](https://discord.immich.app/).
Community support should be directed to Synology-specific support platforms.
:::
Immich can easily be installed on a Synology NAS using Container Manager within DSM. If you have not installed Container Manager already, you can install it in the Packages Center. Refer to the [Container Manager docs](https://kb.synology.com/en-us/DSM/help/ContainerManager/docker_desc?version=7) for more information on using Container Manager.

View File

@@ -121,7 +121,7 @@ alt="Go to Docker Tab and visit the address listed next to immich-web"
width="90%"
alt="Go to Docker Tab and visit the address listed next to immich-web"
/>
</details>
:::tip

View File

@@ -1,7 +1,7 @@
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
[[tools.wrangler]]
version = "4.100.0"
version = "4.110.0"
backend = "npm:wrangler"
[tools.wrangler.options]

View File

@@ -28,4 +28,4 @@ run = "prettier --write ."
run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}"
[tools]
wrangler = "4.100.0"
wrangler = "4.111.0"

View File

@@ -41,9 +41,17 @@ export default typescriptEslint.config([
'@typescript-eslint/no-floating-promises': 'error',
'unicorn/prefer-module': 'off',
'unicorn/import-style': 'off',
'unicorn/consistent-boolean-name': 'off',
'unicorn/no-non-function-verb-prefix': 'off',
'unicorn/no-unreadable-for-of-expression': 'off',
'unicorn/max-nested-calls': 'off',
'unicorn/prefer-uint8array-base64': 'off',
'unicorn/isolated-functions': 'off',
'unicorn/prefer-promise-with-resolvers': 'off',
'unicorn/no-declarations-before-early-exit': 'off',
curly: 2,
'prettier/prettier': 0,
'unicorn/prevent-abbreviations': 'off',
'unicorn/name-replacements': 'off',
'unicorn/filename-case': 'off',
'unicorn/no-null': 'off',
'unicorn/prefer-top-level-await': 'off',

View File

@@ -32,7 +32,7 @@
"@playwright/test": "^1.44.1",
"@socket.io/component-emitter": "^3.1.2",
"@types/luxon": "^3.4.2",
"@types/node": "^24.13.2",
"@types/node": "^24.13.3",
"@types/pg": "^8.15.1",
"@types/pngjs": "^6.0.4",
"@types/supertest": "^7.0.0",
@@ -40,7 +40,7 @@
"eslint": "^10.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^64.0.0",
"eslint-plugin-unicorn": "^70.0.0",
"exiftool-vendored": "^35.0.0",
"globals": "^17.0.0",
"luxon": "^3.4.4",

View File

@@ -118,7 +118,7 @@ describe('/admin/database-backups', () => {
expect(status).toBe(201);
cookie = headers['set-cookie'][0].split(';')[0];
cookie = headers['set-cookie'][0].split(';', 1)[0];
await expect
.poll(
@@ -224,7 +224,7 @@ describe('/admin/database-backups', () => {
});
expect(status).toBe(201);
cookie = headers['set-cookie'][0].split(';')[0];
cookie = headers['set-cookie'][0].split(';', 1)[0];
await expect
.poll(
@@ -295,7 +295,7 @@ describe('/admin/database-backups', () => {
});
expect(status).toBe(201);
cookie = headers['set-cookie'][0].split(';')[0];
cookie = headers['set-cookie'][0].split(';', 1)[0];
await expect
.poll(

View File

@@ -85,7 +85,7 @@ describe('/admin/maintenance', () => {
expect(status).toBe(201);
cookie = headers['set-cookie'][0].split(';')[0];
cookie = headers['set-cookie'][0].split(';', 1)[0];
expect(cookie).toEqual(
expect.stringMatching(/^immich_maintenance_token=[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*$/),
);
@@ -149,7 +149,7 @@ describe('/admin/maintenance', () => {
const { status, body } = await request(app)
.post('/admin/maintenance/login')
.send({
token: cookie!.split('=')[1].trim(),
token: cookie!.split('=', 2)[1].trim(),
});
expect(status).toBe(201);
expect(body).toEqual(

View File

@@ -27,7 +27,7 @@ test.describe('Maintenance', () => {
test('maintenance shows no options to users until they authenticate', async ({ page }) => {
const setCookie = await utils.enterMaintenance(admin.accessToken);
const cookie = setCookie
?.map((cookie) => cookie.split(';')[0].split('='))
?.map((cookie) => cookie.split(';', 1)[0].split('='))
?.find(([name]) => name === 'immich_maintenance_token');
expect(cookie).toBeTruthy();

View File

@@ -120,6 +120,7 @@ describe('/albums', () => {
}),
]);
// eslint-disable-next-line unicorn/no-unreadable-array-destructuring
[user2Albums[0]] = await Promise.all([
getAlbumInfo({ id: user2Albums[0].id }, { headers: asBearerAuth(user2.accessToken) }),
deleteUserAdmin({ id: user3.userId, userAdminDeleteDto: {} }, { headers: asBearerAuth(admin.accessToken) }),

View File

@@ -781,7 +781,7 @@ describe('/asset', () => {
exifImageWidth: 4032,
exifImageHeight: 3024,
latitude: 41.2203,
longitude: -96.071_625,
longitude: -96.071625,
make: 'Apple',
model: 'iPhone 7',
lensModel: 'iPhone 7 back camera 3.99mm f/1.8',
@@ -973,9 +973,9 @@ describe('/asset', () => {
fileSizeInByte: 31_175_472,
focalLength: 18.3,
iso: 100,
latitude: 36.613_24,
latitude: 36.61324,
lensModel: '18.3mm F2.8',
longitude: -121.897_85,
longitude: -121.89785,
make: 'RICOH IMAGING COMPANY, LTD.',
model: 'RICOH GR III',
orientation: '1',

View File

@@ -75,7 +75,7 @@ describe('/map', () => {
country: 'United States of America',
id: expect.any(String),
lat: expect.closeTo(39.115),
lon: expect.closeTo(-108.400_968),
lon: expect.closeTo(-108.400968),
state: 'Colorado',
},
{
@@ -83,7 +83,7 @@ describe('/map', () => {
country: 'United States of America',
id: expect.any(String),
lat: expect.closeTo(41.2203),
lon: expect.closeTo(-96.071_625),
lon: expect.closeTo(-96.071625),
state: 'Nebraska',
},
]);
@@ -123,7 +123,7 @@ describe('/map', () => {
country: 'United States of America',
id: expect.any(String),
lat: expect.closeTo(39.115),
lon: expect.closeTo(-108.400_968),
lon: expect.closeTo(-108.400968),
state: 'Colorado',
},
{
@@ -131,7 +131,7 @@ describe('/map', () => {
country: 'United States of America',
id: expect.any(String),
lat: expect.closeTo(41.2203),
lon: expect.closeTo(-96.071_625),
lon: expect.closeTo(-96.071625),
state: 'Nebraska',
},
]);
@@ -188,20 +188,20 @@ describe('/map', () => {
const reverseGeocodeTestCases = [
{
name: 'Vaucluse',
lat: -33.858_977_058_663_13,
lon: 151.278_490_730_270_48,
lat: -33.85897705866313,
lon: 151.27849073027048,
results: [{ city: 'Vaucluse', state: 'New South Wales', country: 'Australia' }],
},
{
name: 'Ravenhall',
lat: -37.765_732_399_174_75,
lon: 144.752_453_164_883_3,
lat: -37.76573239917475,
lon: 144.7524531648833,
results: [{ city: 'Ravenhall', state: 'Victoria', country: 'Australia' }],
},
{
name: 'Scarborough',
lat: -31.894_346_156_789_997,
lon: 115.757_617_103_904_64,
lat: -31.894346156789997,
lon: 115.75761710390464,
results: [{ city: 'Scarborough', state: 'Western Australia', country: 'Australia' }],
},
];

View File

@@ -44,7 +44,7 @@ const loginWithOAuth = async (sub: OAuthUser | string, redirectUri?: string) =>
});
// login
const response1 = await redirect(url.replace(authServer.internal, authServer.external));
const response1 = await redirect(url.replace(authServer.internal, () => authServer.external));
const response2 = await request(authServer.external + response1.location)
.post('')
.set('Cookie', response1.cookies)

View File

@@ -87,23 +87,23 @@ describe('/search', () => {
// note: the coordinates here are not the actual coordinates of the images and are random for most of them
const coordinates = [
{ latitude: 48.853_41, longitude: 2.3488 }, // paris
{ latitude: 35.6895, longitude: 139.691_71 }, // tokyo
{ latitude: 52.524_37, longitude: 13.410_53 }, // berlin
{ latitude: 1.314_663_1, longitude: 103.845_409_3 }, // singapore
{ latitude: 41.013_84, longitude: 28.949_66 }, // istanbul
{ latitude: 5.556_02, longitude: -0.1969 }, // accra
{ latitude: 37.544_270_6, longitude: -4.727_752_8 }, // andalusia
{ latitude: 23.133_02, longitude: -82.383_04 }, // havana
{ latitude: 41.694_11, longitude: 44.833_68 }, // tbilisi
{ latitude: 31.222_22, longitude: 121.458_06 }, // shanghai
{ latitude: 48.85341, longitude: 2.3488 }, // paris
{ latitude: 35.6895, longitude: 139.69171 }, // tokyo
{ latitude: 52.52437, longitude: 13.41053 }, // berlin
{ latitude: 1.3146631, longitude: 103.8454093 }, // singapore
{ latitude: 41.01384, longitude: 28.94966 }, // istanbul
{ latitude: 5.55602, longitude: -0.1969 }, // accra
{ latitude: 37.5442706, longitude: -4.7277528 }, // andalusia
{ latitude: 23.13302, longitude: -82.38304 }, // havana
{ latitude: 41.69411, longitude: 44.83368 }, // tbilisi
{ latitude: 31.22222, longitude: 121.45806 }, // shanghai
{ latitude: 38.9711, longitude: -109.7137 }, // thompson springs
{ latitude: 40.714_27, longitude: -74.005_97 }, // new york
{ latitude: 47.040_57, longitude: 9.068_04 }, // glarus
{ latitude: 32.771_52, longitude: -89.116_73 }, // philadelphia
{ latitude: 31.634_16, longitude: -7.999_94 }, // marrakesh
{ latitude: 38.523_735_4, longitude: -78.488_619_4 }, // tanners ridge
{ latitude: 59.938_63, longitude: 30.314_13 }, // st. petersburg
{ latitude: 40.71427, longitude: -74.00597 }, // new york
{ latitude: 47.04057, longitude: 9.06804 }, // glarus
{ latitude: 32.77152, longitude: -89.11673 }, // philadelphia
{ latitude: 31.63416, longitude: -7.99994 }, // marrakesh
{ latitude: 38.5237354, longitude: -78.4886194 }, // tanners ridge
{ latitude: 59.93863, longitude: 30.31413 }, // st. petersburg
{ latitude: 0, longitude: 0 }, // null island
];
@@ -112,7 +112,7 @@ describe('/search', () => {
);
await Promise.all(updates);
for (const [i] of coordinates.entries()) {
for (const i of coordinates.keys()) {
await utils.waitForWebsocketEvent({ event: 'assetUpdate', id: assets[i].id });
}

View File

@@ -31,7 +31,7 @@ describe(`immich login`, () => {
it('should login and save auth.yml with 600', async () => {
const admin = await utils.adminSetup();
const key = await utils.createApiKey(admin.accessToken, [Permission.All]);
const { stdout, stderr, exitCode } = await immichCli(['login', app, `${key.secret}`]);
const { stdout, stderr, exitCode } = await immichCli(['login', app, key.secret]);
expect(stdout.split('\n')).toEqual([
'Logging in to http://127.0.0.1:2285/api',
'Logged in as admin@immich.cloud',
@@ -48,7 +48,7 @@ describe(`immich login`, () => {
it('should login without /api in the url', async () => {
const admin = await utils.adminSetup();
const key = await utils.createApiKey(admin.accessToken, [Permission.All]);
const { stdout, stderr, exitCode } = await immichCli(['login', app.replaceAll('/api', ''), `${key.secret}`]);
const { stdout, stderr, exitCode } = await immichCli(['login', app.replaceAll('/api', ''), key.secret]);
expect(stdout.split('\n')).toEqual([
'Logging in to http://127.0.0.1:2285',
'Discovered API at http://127.0.0.1:2285/api',

View File

@@ -119,7 +119,9 @@ describe(`immich upload`, () => {
const baseDir = `/tmp/upload/`;
const testPaths = Object.keys(files).map((filePath) => `${baseDir}/${filePath}`);
testPaths.map((filePath) => utils.createImageFile(filePath));
for (const filePath of testPaths) {
utils.createImageFile(filePath);
}
const commandLine = paths.map((argument) => `${baseDir}/${argument}`);
@@ -135,7 +137,9 @@ describe(`immich upload`, () => {
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(expectedCount);
testPaths.map((filePath) => utils.removeImageFile(filePath));
for (const filePath of testPaths) {
utils.removeImageFile(filePath);
}
});
}
});

View File

@@ -21,7 +21,7 @@ export const randomImageFromString = async (
let seedNumber = 0;
for (let i = 0; i < seed.length; i++) {
seedNumber = (seedNumber << 5) - seedNumber + (seed.codePointAt(i) ?? 0);
seedNumber = seedNumber & seedNumber; // Convert to 32bit integer
seedNumber &= seedNumber; // Convert to 32bit integer
}
return randomImage(new SeededRandom(Math.abs(seedNumber)), { width, height });
};

View File

@@ -64,7 +64,7 @@ export function generateAsset(
const asset: MockTimelineAsset = {
id: assetId,
ownerId,
ratio: Number.parseFloat(ratio.split(':')[0]) / Number.parseFloat(ratio.split(':')[1]),
ratio: Number(ratio.split(':', 1)[0]) / Number(ratio.split(':', 2)[1]),
thumbhash: generateThumbhash(rng),
localDateTime: date.toISOString(),
fileCreatedAt: date.toISOString(),
@@ -214,7 +214,7 @@ export function generateTimelineData(config: TimelineConfig): MockTimelineData {
}
// Create a mock album from random assets
const allAssets = [...buckets.values()].flat();
const allAssets = buckets.values().toArray().flat();
// Select 10-30 random assets for the album (or all assets if less than 10)
const albumSize = Math.min(allAssets.length, globalRng.nextInt(10, 31));

View File

@@ -172,11 +172,7 @@ function shouldIncludeAsset(
if (isArchived !== undefined && actuallyArchived !== isArchived) {
return false;
}
if (isFavorite !== undefined && actuallyFavorited !== isFavorite) {
return false;
}
return true;
return isFavorite === undefined || actuallyFavorited === isFavorite;
}
/**
* Get summary for all buckets (mimics getTimeBuckets API)
@@ -361,7 +357,7 @@ export function getAsset(
owner?: UserResponseDto,
): AssetResponseDto | undefined {
// Search through all buckets for the asset
const buckets = [...timelineData.buckets.values()];
const buckets = timelineData.buckets.values().toArray();
for (const assets of buckets) {
const asset = assets.find((a) => a.id === assetId);
if (asset) {
@@ -395,7 +391,7 @@ export function getAlbum(
// Get the actual asset objects from the timeline data
const albumAssets: AssetResponseDto[] = [];
const allAssets = [...timelineData.buckets.values()].flat();
const allAssets = timelineData.buckets.values().toArray().flat();
for (const assetId of album.assetIds) {
const assetConfig = allAssets.find((a) => a.id === assetId);

View File

@@ -143,7 +143,7 @@ export function validateTimelineConfig(config: TimelineConfig): void {
}
// Validate seed if provided
if (config.seed !== undefined && (config.seed < 0 || !Number.isInteger(config.seed))) {
if (config.seed !== undefined && (config.seed < 0 || !Number.isSafeInteger(config.seed))) {
throw new Error('Seed must be a non-negative integer');
}

View File

@@ -153,11 +153,8 @@ export function getMockAsset(
const isInDifferentPeriod = (date1: DateTime, date2: DateTime): boolean => {
if (unit === 'day') {
return !date1.startOf('day').equals(date2.startOf('day'));
} else if (unit === 'month') {
return date1.year !== date2.year || date1.month !== date2.month;
} else {
return date1.year !== date2.year;
}
return unit === 'month' ? date1.year !== date2.year || date1.month !== date2.month : date1.year !== date2.year;
};
if (direction === 'next') {

View File

@@ -40,7 +40,8 @@ export const setupTimelineMockApiRoutes = async (
contentType: 'application/json',
json: getTimeBuckets(timelineRestData, isTrashed, isArchived, isFavorite, albumId, changes),
});
} else if (pathname === '/api/timeline/bucket') {
}
if (pathname === '/api/timeline/bucket') {
const timeBucket = url.searchParams.get('timeBucket');
if (!timeBucket) {
return route.continue();

View File

@@ -1,3 +1,4 @@
/* eslint-disable unicorn/no-this-outside-of-class */
import type { AssetResponseDto } from '@immich/sdk';
import { expect, Page } from '@playwright/test';

View File

@@ -72,6 +72,7 @@ export const thumbnailUtils = {
},
async queryThumbnailInViewport(page: Page, collector: (assetId: string) => boolean) {
const assetIds: string[] = [];
// eslint-disable-next-line unicorn/no-this-outside-of-class
for (const thumb of await this.locator(page).all()) {
const box = await thumb.boundingBox();
if (box) {
@@ -151,6 +152,7 @@ export const timelineUtils = {
page.evaluate(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return document.querySelector('#asset-grid').scrollTop;
});
await expect.poll(queryTop).toBeGreaterThan(0);
@@ -177,6 +179,7 @@ export const assetViewerUtils = {
page.evaluate(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line unicorn/no-optional-chaining-on-undeclared-variable
return document.activeElement?.dataset?.asset;
});
await expect(poll(page, activeElement, (result) => result === assetId)).resolves.toBe(assetId);

View File

@@ -1,3 +1,4 @@
/* eslint-disable unicorn/no-top-level-assignment-in-function */
import {
AssetMediaCreateDto,
AssetMediaResponseDto,
@@ -177,7 +178,7 @@ export const utils = {
resetDatabase: async (tables?: string[]) => {
client = await utils.connectDatabase();
tables = tables || [
tables ||= [
// TODO e2e test for deleting a stack, since it is quite complex
'stack',
'library',
@@ -304,7 +305,7 @@ export const utils = {
},
adminSetup: async (options?: AdminSetupOptions) => {
options = options || { onboarding: true };
options ||= { onboarding: true };
await signUpAdmin({ signUpDto: signupDto.admin });
const response = await login({ loginCredentialDto: loginDto.admin });
@@ -545,6 +546,7 @@ export const utils = {
{
headers: asBearerAuth(accessToken),
fetch: (...args: Parameters<typeof fetch>) =>
// eslint-disable-next-line unicorn/no-invalid-argument-count, unicorn/prefer-await
fetch(...args).then((response) => {
setCookie = response.headers.getSetCookie();
return response;
@@ -674,7 +676,7 @@ export const utils = {
cliLogin: async (accessToken: string) => {
const key = await utils.createApiKey(accessToken, [Permission.All]);
await immichCli(['login', app, `${key.secret}`]);
await immichCli(['login', app, key.secret]);
return key.secret;
},
@@ -706,6 +708,7 @@ export const utils = {
},
};
// eslint-disable-next-line unicorn/no-top-level-side-effects
utils.initSdk();
if (!existsSync(`${testAssetDir}/albums`)) {

View File

@@ -10,6 +10,7 @@
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"target": "es2023",
"lib": ["dom", "ESNext"],
"sourceMap": true,
"outDir": "./dist",
"incremental": true,

View File

@@ -287,43 +287,43 @@ url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536406"
[[tools.pnpm]]
version = "11.6.0"
version = "11.13.1"
backend = "aqua:pnpm/pnpm"
[tools.pnpm."platforms.linux-arm64"]
checksum = "sha256:2fec653ff6dadab340d1c3d2214688a7451cc471f39710839440b293ca7c53b0"
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-arm64.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120174"
checksum = "sha256:b52db99d215ed7dc9563aed815953c62a6c1ffd7cd75803d3a07ad7e4f246aed"
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-linux-arm64.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/478563983"
provenance = "github-attestations"
[tools.pnpm."platforms.linux-arm64-musl"]
checksum = "sha256:56a78c08cf22adf29e7dacb6f7f100139731693863d20fb94a7883463a62169e"
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-arm64-musl.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120177"
checksum = "sha256:2cadd4fc815c591f498a0a84c9e74a836e3e8c1236275f6e1cfd355bae6ae957"
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-linux-arm64-musl.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/478563985"
provenance = "github-attestations"
[tools.pnpm."platforms.linux-x64"]
checksum = "sha256:74d64c1646385fb21691f32f0ab6aca1a9f5c829ba54d3cda3a24838a228e68c"
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-x64.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120176"
checksum = "sha256:bd6d4b0e14536207ad76bc838f5980cecd968da15f69aae0b207380cca3f2e98"
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-linux-x64.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/478563986"
provenance = "github-attestations"
[tools.pnpm."platforms.linux-x64-musl"]
checksum = "sha256:7a0c463a09d912fba6b7d9eca0a946bc228ea50f3015a05c09a29e7e85a932d7"
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-x64-musl.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120175"
checksum = "sha256:ba19690f4ed1b64f1203ade14e9216352b46232d5582468b26a0160e0c9618c5"
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-linux-x64-musl.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/478563980"
provenance = "github-attestations"
[tools.pnpm."platforms.macos-arm64"]
checksum = "sha256:87c901635a14481fb30566a3749041134ffd4317bc6fe866c345b69fdf9b6b85"
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-darwin-arm64.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120180"
checksum = "sha256:765c2bf04e8129cb58c0f946e324262e418370b35a203b50b1f06a0567ef8bc1"
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-darwin-arm64.tar.gz"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/478563984"
provenance = "github-attestations"
[tools.pnpm."platforms.windows-x64"]
checksum = "sha256:91c753435542b04859c689304fae0dd64eba6b40937cfa426a48485b712e4e9c"
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-win32-x64.zip"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120173"
checksum = "sha256:d8bebbc71df2702961c1d34a5e61196bc0aa3bbde33c33253f6afa3dd4546a6d"
url = "https://github.com/pnpm/pnpm/releases/download/v11.13.1/pnpm-win32-x64.zip"
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/478563981"
provenance = "github-attestations"
[[tools.terragrunt]]

View File

@@ -16,7 +16,7 @@ config_roots = [
[tools]
node = "24.15.0"
pnpm = "11.6.0"
pnpm = "11.13.1"
terragrunt = "1.0.3"
opentofu = "1.11.6"
"npm:oazapfts" = "7.5.0"

View File

@@ -89,13 +89,6 @@ flutter {
}
dependencies {
constraints {
implementation("androidx.glance:glance-appwidget") {
version { strictly libs.versions.glance.get() }
because 'home_widget requests 1.+ which can resolve to pre-releases incompatible with our compileSdk/AGP'
}
}
implementation libs.okhttp
implementation libs.cronet.embedded
implementation libs.media3.datasource.okhttp

View File

@@ -46,7 +46,7 @@ void main() {
void sendUser(SyncStream stream, String id, String name) {
stream.send(
type: SyncEntityType.userV1.value,
type: SyncEntityType.userV1.toString(),
data: SyncUserV1(
id: id,
name: name,

View File

@@ -12,12 +12,8 @@ PODS:
- Flutter
- fluttertoast (0.0.2):
- Flutter
- home_widget (0.0.1):
- Flutter
- native_video_player (1.0.0):
- Flutter
- permission_handler_apple (9.3.0):
- Flutter
- share_handler_ios (0.0.14):
- Flutter
- share_handler_ios/share_handler_ios_models (= 0.0.14)
@@ -34,9 +30,7 @@ DEPENDENCIES:
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- home_widget (from `.symlinks/plugins/home_widget/ios`)
- native_video_player (from `.symlinks/plugins/native_video_player/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- share_handler_ios (from `.symlinks/plugins/share_handler_ios/ios`)
- share_handler_ios_models (from `.symlinks/plugins/share_handler_ios/ios/Models`)
@@ -53,12 +47,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
home_widget:
:path: ".symlinks/plugins/home_widget/ios"
native_video_player:
:path: ".symlinks/plugins/native_video_player/ios"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
share_handler_ios:
:path: ".symlinks/plugins/share_handler_ios/ios"
share_handler_ios_models:
@@ -71,12 +61,10 @@ SPEC CHECKSUMS:
flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f
native_video_player: b65c58951ede2f93d103a25366bdebca95081265
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb
share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871
PODFILE CHECKSUM: 938abbae4114b9c2140c550a2a0d8f7c674f5dfe
PODFILE CHECKSUM: 3c43a700a4bffb4120bf696cad263aefd4bb3c8c
COCOAPODS: 1.16.2

View File

@@ -154,11 +154,15 @@
/* Begin PBXFileSystemSynchronizedRootGroup section */
B231F52D2E93A44A00BC45D1 /* Core */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = Core;
sourceTree = "<group>";
};
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = Sync;
sourceTree = "<group>";
};
@@ -180,6 +184,8 @@
};
FEE084F22EC172080045228E /* Schemas */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = Schemas;
sourceTree = "<group>";
};
@@ -372,7 +378,6 @@
FAC6F89A2D287C890078CB2F /* Embed Foundation Extensions */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
513DA7292DED6106813332F4 /* [CP] Embed Pods Frameworks */,
2FA39DEC809D6D7C4A01EFCB /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -466,7 +471,7 @@
);
mainGroup = 97C146E51CF9000F007C117D;
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */,
FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */,
);
@@ -513,27 +518,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
2FA39DEC809D6D7C4A01EFCB /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -558,14 +542,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
@@ -606,7 +586,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
shellScript = "/bin/bash \"$SRCROOT/scripts/xcode_flutter_build.sh\"\n";
};
BAEA01ACA3F5C9CD3D732370 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
@@ -1266,7 +1246,7 @@
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# Makes Flutter's builds through the Xcode GUI properly display errors and warnings
# in the Issue navigator
#
# Flutter's `xcode_backend.dart` runs `flutter assemble` with `allowFail: true`,
# which intentionally does not prefix output with `error:`. Unsure why they do this,
# but this script rebuilds the expected output so Xcode can parse and display the errors
set -o pipefail
# The Immich mobile root (containing the Dart `lib` directory). This is used to make
# absolute paths for Xcode linking
app_root="${FLUTTER_APPLICATION_PATH:-$SRCROOT/..}"
/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build 2>&1 \
| awk -v app_root="$app_root" '
# Match Dart CFE diagnostics: <path>.dart:<line>:<col>: <Kind>: <message>
# Written for macOS/POSIX/BSD awk
{
# Always pass the original line through to preserve the original build log
print
if ($0 ~ /^.*\.dart:[0-9]+:[0-9]+: (Error|Warning|Context|Info):/) {
# Locate the ": Kind:" separator to split location from message.
rest = $0
if (match(rest, /: Error:/)) { kind = "Error"; keyword = "error" }
else if (match(rest, /: Warning:/)) { kind = "Warning"; keyword = "warning" }
else if (match(rest, /: Context:/)) { kind = "Context"; keyword = "note" }
else if (match(rest, /: Info:/)) { kind = "Info"; keyword = "note" }
# location = everything before ": Kind:" (e.g. "lib/foo.dart:12:5")
location = substr(rest, 1, RSTART - 1)
# message = everything after ": Kind:" (leading space preserved)
message = substr(rest, RSTART + length(": " kind ":"))
# Make the path absolute so Xcode links to it
if (location !~ /^\//)
location = app_root "/" location
printf "%s: %s:%s\n", location, keyword, message
}
}
'
exit "${PIPESTATUS[0]}"

View File

@@ -70,6 +70,10 @@ class RemoteAsset extends BaseAsset {
bool get isTrashed => deletedAt != null;
bool get isStacked => stackId != null;
bool get isArchived => visibility == .archive;
@override
String toString() {
return '''Asset {

View File

@@ -328,8 +328,6 @@ class SyncStreamService {
return _syncStreamRepository.updateAssetOcrV1(data.cast());
case SyncEntityType.assetOcrDeleteV1:
return _syncStreamRepository.deleteAssetOcrV1(data.cast());
default:
_logger.warning("Unknown sync data type: $type");
}
}

View File

@@ -58,7 +58,6 @@ extension on api.AssetVisibility {
api.AssetVisibility.hidden => AssetVisibility.hidden,
api.AssetVisibility.archive => AssetVisibility.archive,
api.AssetVisibility.locked => AssetVisibility.locked,
_ => AssetVisibility.timeline,
};
}
@@ -68,6 +67,5 @@ extension on api.AssetTypeEnum {
api.AssetTypeEnum.VIDEO => AssetType.video,
api.AssetTypeEnum.AUDIO => AssetType.audio,
api.AssetTypeEnum.OTHER => AssetType.other,
_ => throw Exception('Unknown AssetType value: $this'),
};
}

View File

@@ -44,16 +44,7 @@ final class $$AssetEditEntityTableReferences
static i5.$RemoteAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) =>
i6.ReadDatabaseContainer(db)
.resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity')
.createAlias(
i0.$_aliasNameGenerator(
i6.ReadDatabaseContainer(db)
.resultSet<i1.$AssetEditEntityTable>('asset_edit_entity')
.assetId,
i6.ReadDatabaseContainer(
db,
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity').id,
),
);
.createAlias('asset_edit_entity__asset_id__remote_asset_entity__id');
i5.$$RemoteAssetEntityTableProcessedTableManager get assetId {
final $_column = $_itemColumn<String>('asset_id')!;

View File

@@ -59,16 +59,7 @@ final class $$AssetFaceEntityTableReferences
static i4.$RemoteAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) =>
i5.ReadDatabaseContainer(db)
.resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity')
.createAlias(
i0.$_aliasNameGenerator(
i5.ReadDatabaseContainer(db)
.resultSet<i1.$AssetFaceEntityTable>('asset_face_entity')
.assetId,
i5.ReadDatabaseContainer(
db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity').id,
),
);
.createAlias('asset_face_entity__asset_id__remote_asset_entity__id');
i4.$$RemoteAssetEntityTableProcessedTableManager get assetId {
final $_column = $_itemColumn<String>('asset_id')!;
@@ -91,16 +82,7 @@ final class $$AssetFaceEntityTableReferences
static i6.$PersonEntityTable _personIdTable(i0.GeneratedDatabase db) =>
i5.ReadDatabaseContainer(db)
.resultSet<i6.$PersonEntityTable>('person_entity')
.createAlias(
i0.$_aliasNameGenerator(
i5.ReadDatabaseContainer(db)
.resultSet<i1.$AssetFaceEntityTable>('asset_face_entity')
.personId,
i5.ReadDatabaseContainer(
db,
).resultSet<i6.$PersonEntityTable>('person_entity').id,
),
);
.createAlias('asset_face_entity__person_id__person_entity__id');
i6.$$PersonEntityTableProcessedTableManager? get personId {
final $_column = $_itemColumn<String>('person_id');

View File

@@ -61,16 +61,7 @@ final class $$AssetOcrEntityTableReferences
static i4.$RemoteAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) =>
i5.ReadDatabaseContainer(db)
.resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity')
.createAlias(
i0.$_aliasNameGenerator(
i5.ReadDatabaseContainer(
db,
).resultSet<i1.$AssetOcrEntityTable>('asset_ocr_entity').assetId,
i5.ReadDatabaseContainer(
db,
).resultSet<i4.$RemoteAssetEntityTable>('remote_asset_entity').id,
),
);
.createAlias('asset_ocr_entity__asset_id__remote_asset_entity__id');
i4.$$RemoteAssetEntityTableProcessedTableManager get assetId {
final $_column = $_itemColumn<String>('asset_id')!;

View File

@@ -75,16 +75,7 @@ final class $$RemoteExifEntityTableReferences
static i3.$RemoteAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) =>
i4.ReadDatabaseContainer(db)
.resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity')
.createAlias(
i0.$_aliasNameGenerator(
i4.ReadDatabaseContainer(db)
.resultSet<i1.$RemoteExifEntityTable>('remote_exif_entity')
.assetId,
i4.ReadDatabaseContainer(
db,
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity').id,
),
);
.createAlias('remote_exif_entity__asset_id__remote_asset_entity__id');
i3.$$RemoteAssetEntityTableProcessedTableManager get assetId {
final $_column = $_itemColumn<String>('asset_id')!;

View File

@@ -50,14 +50,7 @@ final class $$LocalAlbumEntityTableReferences
) => i6.ReadDatabaseContainer(db)
.resultSet<i5.$RemoteAlbumEntityTable>('remote_album_entity')
.createAlias(
i0.$_aliasNameGenerator(
i6.ReadDatabaseContainer(db)
.resultSet<i1.$LocalAlbumEntityTable>('local_album_entity')
.linkedRemoteAlbumId,
i6.ReadDatabaseContainer(
db,
).resultSet<i5.$RemoteAlbumEntityTable>('remote_album_entity').id,
),
'local_album_entity__linked_remote_album_id__remote_album_entity__id',
);
i5.$$RemoteAlbumEntityTableProcessedTableManager? get linkedRemoteAlbumId {

View File

@@ -41,16 +41,7 @@ final class $$LocalAlbumAssetEntityTableReferences
i4.ReadDatabaseContainer(db)
.resultSet<i3.$LocalAssetEntityTable>('local_asset_entity')
.createAlias(
i0.$_aliasNameGenerator(
i4.ReadDatabaseContainer(db)
.resultSet<i1.$LocalAlbumAssetEntityTable>(
'local_album_asset_entity',
)
.assetId,
i4.ReadDatabaseContainer(
db,
).resultSet<i3.$LocalAssetEntityTable>('local_asset_entity').id,
),
'local_album_asset_entity__asset_id__local_asset_entity__id',
);
i3.$$LocalAssetEntityTableProcessedTableManager get assetId {
@@ -75,16 +66,7 @@ final class $$LocalAlbumAssetEntityTableReferences
i4.ReadDatabaseContainer(db)
.resultSet<i5.$LocalAlbumEntityTable>('local_album_entity')
.createAlias(
i0.$_aliasNameGenerator(
i4.ReadDatabaseContainer(db)
.resultSet<i1.$LocalAlbumAssetEntityTable>(
'local_album_asset_entity',
)
.albumId,
i4.ReadDatabaseContainer(
db,
).resultSet<i5.$LocalAlbumEntityTable>('local_album_entity').id,
),
'local_album_asset_entity__album_id__local_album_entity__id',
);
i5.$$LocalAlbumEntityTableProcessedTableManager get albumId {

View File

@@ -53,16 +53,7 @@ final class $$MemoryEntityTableReferences
static i5.$UserEntityTable _ownerIdTable(i0.GeneratedDatabase db) =>
i6.ReadDatabaseContainer(db)
.resultSet<i5.$UserEntityTable>('user_entity')
.createAlias(
i0.$_aliasNameGenerator(
i6.ReadDatabaseContainer(
db,
).resultSet<i1.$MemoryEntityTable>('memory_entity').ownerId,
i6.ReadDatabaseContainer(
db,
).resultSet<i5.$UserEntityTable>('user_entity').id,
),
);
.createAlias('memory_entity__owner_id__user_entity__id');
i5.$$UserEntityTableProcessedTableManager get ownerId {
final $_column = $_itemColumn<String>('owner_id')!;

View File

@@ -39,14 +39,7 @@ final class $$MemoryAssetEntityTableReferences
i4.ReadDatabaseContainer(db)
.resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity')
.createAlias(
i0.$_aliasNameGenerator(
i4.ReadDatabaseContainer(db)
.resultSet<i1.$MemoryAssetEntityTable>('memory_asset_entity')
.assetId,
i4.ReadDatabaseContainer(
db,
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity').id,
),
'memory_asset_entity__asset_id__remote_asset_entity__id',
);
i3.$$RemoteAssetEntityTableProcessedTableManager get assetId {
@@ -70,16 +63,7 @@ final class $$MemoryAssetEntityTableReferences
static i5.$MemoryEntityTable _memoryIdTable(i0.GeneratedDatabase db) =>
i4.ReadDatabaseContainer(db)
.resultSet<i5.$MemoryEntityTable>('memory_entity')
.createAlias(
i0.$_aliasNameGenerator(
i4.ReadDatabaseContainer(db)
.resultSet<i1.$MemoryAssetEntityTable>('memory_asset_entity')
.memoryId,
i4.ReadDatabaseContainer(
db,
).resultSet<i5.$MemoryEntityTable>('memory_entity').id,
),
);
.createAlias('memory_asset_entity__memory_id__memory_entity__id');
i5.$$MemoryEntityTableProcessedTableManager get memoryId {
final $_column = $_itemColumn<String>('memory_id')!;

View File

@@ -39,16 +39,7 @@ final class $$PartnerEntityTableReferences
static i4.$UserEntityTable _sharedByIdTable(i0.GeneratedDatabase db) =>
i5.ReadDatabaseContainer(db)
.resultSet<i4.$UserEntityTable>('user_entity')
.createAlias(
i0.$_aliasNameGenerator(
i5.ReadDatabaseContainer(
db,
).resultSet<i1.$PartnerEntityTable>('partner_entity').sharedById,
i5.ReadDatabaseContainer(
db,
).resultSet<i4.$UserEntityTable>('user_entity').id,
),
);
.createAlias('partner_entity__shared_by_id__user_entity__id');
i4.$$UserEntityTableProcessedTableManager get sharedById {
final $_column = $_itemColumn<String>('shared_by_id')!;
@@ -71,16 +62,7 @@ final class $$PartnerEntityTableReferences
static i4.$UserEntityTable _sharedWithIdTable(i0.GeneratedDatabase db) =>
i5.ReadDatabaseContainer(db)
.resultSet<i4.$UserEntityTable>('user_entity')
.createAlias(
i0.$_aliasNameGenerator(
i5.ReadDatabaseContainer(db)
.resultSet<i1.$PartnerEntityTable>('partner_entity')
.sharedWithId,
i5.ReadDatabaseContainer(
db,
).resultSet<i4.$UserEntityTable>('user_entity').id,
),
);
.createAlias('partner_entity__shared_with_id__user_entity__id');
i4.$$UserEntityTableProcessedTableManager get sharedWithId {
final $_column = $_itemColumn<String>('shared_with_id')!;

View File

@@ -48,16 +48,7 @@ final class $$PersonEntityTableReferences
static i4.$UserEntityTable _ownerIdTable(i0.GeneratedDatabase db) =>
i5.ReadDatabaseContainer(db)
.resultSet<i4.$UserEntityTable>('user_entity')
.createAlias(
i0.$_aliasNameGenerator(
i5.ReadDatabaseContainer(
db,
).resultSet<i1.$PersonEntityTable>('person_entity').ownerId,
i5.ReadDatabaseContainer(
db,
).resultSet<i4.$UserEntityTable>('user_entity').id,
),
);
.createAlias('person_entity__owner_id__user_entity__id');
i4.$$UserEntityTableProcessedTableManager get ownerId {
final $_column = $_itemColumn<String>('owner_id')!;

View File

@@ -52,14 +52,7 @@ final class $$RemoteAlbumEntityTableReferences
) => i6.ReadDatabaseContainer(db)
.resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity')
.createAlias(
i0.$_aliasNameGenerator(
i6.ReadDatabaseContainer(db)
.resultSet<i1.$RemoteAlbumEntityTable>('remote_album_entity')
.thumbnailAssetId,
i6.ReadDatabaseContainer(
db,
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity').id,
),
'remote_album_entity__thumbnail_asset_id__remote_asset_entity__id',
);
i5.$$RemoteAssetEntityTableProcessedTableManager? get thumbnailAssetId {

View File

@@ -39,16 +39,7 @@ final class $$RemoteAlbumAssetEntityTableReferences
i4.ReadDatabaseContainer(db)
.resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity')
.createAlias(
i0.$_aliasNameGenerator(
i4.ReadDatabaseContainer(db)
.resultSet<i1.$RemoteAlbumAssetEntityTable>(
'remote_album_asset_entity',
)
.assetId,
i4.ReadDatabaseContainer(
db,
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity').id,
),
'remote_album_asset_entity__asset_id__remote_asset_entity__id',
);
i3.$$RemoteAssetEntityTableProcessedTableManager get assetId {
@@ -73,16 +64,7 @@ final class $$RemoteAlbumAssetEntityTableReferences
i4.ReadDatabaseContainer(db)
.resultSet<i5.$RemoteAlbumEntityTable>('remote_album_entity')
.createAlias(
i0.$_aliasNameGenerator(
i4.ReadDatabaseContainer(db)
.resultSet<i1.$RemoteAlbumAssetEntityTable>(
'remote_album_asset_entity',
)
.albumId,
i4.ReadDatabaseContainer(
db,
).resultSet<i5.$RemoteAlbumEntityTable>('remote_album_entity').id,
),
'remote_album_asset_entity__album_id__remote_album_entity__id',
);
i5.$$RemoteAlbumEntityTableProcessedTableManager get albumId {

View File

@@ -42,16 +42,7 @@ final class $$RemoteAlbumUserEntityTableReferences
i5.ReadDatabaseContainer(db)
.resultSet<i4.$RemoteAlbumEntityTable>('remote_album_entity')
.createAlias(
i0.$_aliasNameGenerator(
i5.ReadDatabaseContainer(db)
.resultSet<i1.$RemoteAlbumUserEntityTable>(
'remote_album_user_entity',
)
.albumId,
i5.ReadDatabaseContainer(
db,
).resultSet<i4.$RemoteAlbumEntityTable>('remote_album_entity').id,
),
'remote_album_user_entity__album_id__remote_album_entity__id',
);
i4.$$RemoteAlbumEntityTableProcessedTableManager get albumId {
@@ -75,18 +66,7 @@ final class $$RemoteAlbumUserEntityTableReferences
static i6.$UserEntityTable _userIdTable(i0.GeneratedDatabase db) =>
i5.ReadDatabaseContainer(db)
.resultSet<i6.$UserEntityTable>('user_entity')
.createAlias(
i0.$_aliasNameGenerator(
i5.ReadDatabaseContainer(db)
.resultSet<i1.$RemoteAlbumUserEntityTable>(
'remote_album_user_entity',
)
.userId,
i5.ReadDatabaseContainer(
db,
).resultSet<i6.$UserEntityTable>('user_entity').id,
),
);
.createAlias('remote_album_user_entity__user_id__user_entity__id');
i6.$$UserEntityTableProcessedTableManager get userId {
final $_column = $_itemColumn<String>('user_id')!;

View File

@@ -74,16 +74,7 @@ final class $$RemoteAssetEntityTableReferences
static i5.$UserEntityTable _ownerIdTable(i0.GeneratedDatabase db) =>
i6.ReadDatabaseContainer(db)
.resultSet<i5.$UserEntityTable>('user_entity')
.createAlias(
i0.$_aliasNameGenerator(
i6.ReadDatabaseContainer(db)
.resultSet<i1.$RemoteAssetEntityTable>('remote_asset_entity')
.ownerId,
i6.ReadDatabaseContainer(
db,
).resultSet<i5.$UserEntityTable>('user_entity').id,
),
);
.createAlias('remote_asset_entity__owner_id__user_entity__id');
i5.$$UserEntityTableProcessedTableManager get ownerId {
final $_column = $_itemColumn<String>('owner_id')!;

View File

@@ -45,16 +45,7 @@ final class $$RemoteAssetCloudIdEntityTableReferences
i4.ReadDatabaseContainer(db)
.resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity')
.createAlias(
i0.$_aliasNameGenerator(
i4.ReadDatabaseContainer(db)
.resultSet<i1.$RemoteAssetCloudIdEntityTable>(
'remote_asset_cloud_id_entity',
)
.assetId,
i4.ReadDatabaseContainer(
db,
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity').id,
),
'remote_asset_cloud_id_entity__asset_id__remote_asset_entity__id',
);
i3.$$RemoteAssetEntityTableProcessedTableManager get assetId {

View File

@@ -38,16 +38,7 @@ final class $$StackEntityTableReferences
static i4.$UserEntityTable _ownerIdTable(i0.GeneratedDatabase db) =>
i5.ReadDatabaseContainer(db)
.resultSet<i4.$UserEntityTable>('user_entity')
.createAlias(
i0.$_aliasNameGenerator(
i5.ReadDatabaseContainer(
db,
).resultSet<i1.$StackEntityTable>('stack_entity').ownerId,
i5.ReadDatabaseContainer(
db,
).resultSet<i4.$UserEntityTable>('user_entity').id,
),
);
.createAlias('stack_entity__owner_id__user_entity__id');
i4.$$UserEntityTableProcessedTableManager get ownerId {
final $_column = $_itemColumn<String>('owner_id')!;

View File

@@ -40,18 +40,7 @@ final class $$UserMetadataEntityTableReferences
static i5.$UserEntityTable _userIdTable(i0.GeneratedDatabase db) =>
i6.ReadDatabaseContainer(db)
.resultSet<i5.$UserEntityTable>('user_entity')
.createAlias(
i0.$_aliasNameGenerator(
i6.ReadDatabaseContainer(db)
.resultSet<i1.$UserMetadataEntityTable>(
'user_metadata_entity',
)
.userId,
i6.ReadDatabaseContainer(
db,
).resultSet<i5.$UserEntityTable>('user_entity').id,
),
);
.createAlias('user_metadata_entity__user_id__user_entity__id');
i5.$$UserEntityTableProcessedTableManager get userId {
final $_column = $_itemColumn<String>('user_id')!;

View File

@@ -937,7 +937,6 @@ extension on AssetTypeEnum {
AssetTypeEnum.VIDEO => AssetType.video,
AssetTypeEnum.AUDIO => AssetType.audio,
AssetTypeEnum.OTHER => AssetType.other,
_ => throw Exception('Unknown AssetType value: $this'),
};
}
@@ -945,14 +944,12 @@ extension on AssetOrder {
AlbumAssetOrder toAlbumAssetOrder() => switch (this) {
AssetOrder.asc => AlbumAssetOrder.asc,
AssetOrder.desc => AlbumAssetOrder.desc,
_ => throw Exception('Unknown AssetOrder value: $this'),
};
}
extension on MemoryType {
MemoryTypeEnum toMemoryType() => switch (this) {
MemoryType.onThisDay => MemoryTypeEnum.onThisDay,
_ => throw Exception('Unknown MemoryType value: $this'),
};
}
@@ -961,7 +958,6 @@ extension on api.AlbumUserRole {
api.AlbumUserRole.editor => AlbumUserRole.editor,
api.AlbumUserRole.viewer => AlbumUserRole.viewer,
api.AlbumUserRole.owner => AlbumUserRole.owner,
_ => throw Exception('Unknown AlbumUserRole value: $this'),
};
}
@@ -971,7 +967,6 @@ extension on api.AssetVisibility {
api.AssetVisibility.hidden => AssetVisibility.hidden,
api.AssetVisibility.archive => AssetVisibility.archive,
api.AssetVisibility.locked => AssetVisibility.locked,
_ => throw Exception('Unknown AssetVisibility value: $this'),
};
}
@@ -980,12 +975,11 @@ extension on api.UserMetadataKey {
api.UserMetadataKey.onboarding => UserMetadataKey.onboarding,
api.UserMetadataKey.preferences => UserMetadataKey.preferences,
api.UserMetadataKey.license => UserMetadataKey.license,
_ => throw Exception('Unknown UserMetadataKey value: $this'),
};
}
extension on UserAvatarColor {
AvatarColor? toAvatarColor() => AvatarColor.values.firstWhereOrNull((c) => c.name == value);
AvatarColor? toAvatarColor() => AvatarColor.values.firstWhereOrNull((c) => c.name == toString());
}
extension on api.AssetEditAction {
@@ -993,6 +987,5 @@ extension on api.AssetEditAction {
api.AssetEditAction.crop => AssetEditAction.crop,
api.AssetEditAction.rotate => AssetEditAction.rotate,
api.AssetEditAction.mirror => AssetEditAction.mirror,
_ => AssetEditAction.other,
};
}

View File

@@ -3,25 +3,23 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/presentation/actions/action.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/utils/asset_filter.dart';
import 'package:immich_ui/immich_ui.dart';
class FavoriteAction extends AssetAction<RemoteAsset> {
final bool shouldFavorite;
final bool favorite;
FavoriteAction({required super.assets}) : shouldFavorite = assets.any((asset) => !asset.isFavorite);
FavoriteAction({required super.assets}) : favorite = assets.any((asset) => !asset.isFavorite);
@override
IconData get icon => shouldFavorite ? Icons.favorite_border_rounded : Icons.favorite_rounded;
IconData get icon => favorite ? Icons.favorite_border_rounded : Icons.favorite_rounded;
@override
String label(ActionScope scope) => shouldFavorite ? scope.context.t.favorite : scope.context.t.unfavorite;
String label(ActionScope scope) => favorite ? scope.context.t.favorite : scope.context.t.unfavorite;
@override
Iterable<RemoteAsset> filter(ActionScope scope) => assets
.where(
(asset) => asset is RemoteAsset && asset.ownerId == scope.authUser.id && asset.isFavorite == !shouldFavorite,
)
.cast<RemoteAsset>();
Iterable<RemoteAsset> filter(ActionScope scope) =>
AssetFilter(assets).owned(scope.authUser.id).favorite(isFavorite: !favorite);
@override
bool isVisible(ActionScope scope) => filter(scope).isNotEmpty;
@@ -31,8 +29,8 @@ class FavoriteAction extends AssetAction<RemoteAsset> {
final ActionScope(:ref) = scope;
final assets = filter(scope).map((asset) => asset.id).toList(growable: false);
await ref.read(assetServiceProvider).updateFavorite(assets, shouldFavorite);
final message = shouldFavorite
await ref.read(assetServiceProvider).updateFavorite(assets, favorite);
final message = favorite
? StaticTranslations.instance.favorite_action_prompt(count: assets.length)
: StaticTranslations.instance.unfavorite_action_prompt(count: assets.length);
snackbar.success(message);

View File

@@ -1,17 +1,49 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/presentation/widgets/memory/memory_lane.widget.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/widgets/common/page_chrome/tab_page.dart';
import 'package:immich_mobile/widgets/common/page_chrome/types.dart';
import 'package:immich_mobile/presentation/widgets/feature_message/feature_message_dialog.widget.dart';
import 'package:immich_mobile/providers/feature_message.provider.dart';
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
@RoutePage()
class MainTimelinePage extends TabPage {
class MainTimelinePage extends ConsumerStatefulWidget {
const MainTimelinePage({super.key});
@override
Widget scopeBuilder(BuildContext context, Widget child) => timelineChromeScope(context, child);
ConsumerState<MainTimelinePage> createState() => _MainTimelinePageState();
}
class _MainTimelinePageState extends ConsumerState<MainTimelinePage> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!mounted) {
return;
}
final service = ref.read(featureMessageServiceProvider);
if (!service.shouldShow()) {
return;
}
await service.markSeen();
if (!mounted) {
return;
}
await showFeatureMessageDialog(context);
});
}
@override
ProviderListenable<PageChromeContent> get content => timelineChromeProvider;
Widget build(BuildContext context) {
final hasMemories = ref.watch(driftMemoryFutureProvider.select((state) => state.value?.isNotEmpty ?? false));
return Timeline(
topSliverWidget: const SliverToBoxAdapter(child: DriftMemoryLane()),
topSliverWidgetHeight: hasMemories ? 200 : 0,
showStorageIndicator: true,
);
}
}

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -5,70 +7,64 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/common/page_chrome/tab_page.dart';
import 'package:immich_mobile/widgets/common/page_chrome/types.dart';
import 'package:immich_mobile/widgets/common/primary_app_bar.dart';
import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
@RoutePage()
class DriftAlbumsPage extends TabPage {
class DriftAlbumsPage extends ConsumerStatefulWidget {
const DriftAlbumsPage({super.key});
@override
ProviderListenable<PageChromeContent> get content => Provider<PageChromeContent>((ref) {
ConsumerState<DriftAlbumsPage> createState() => _DriftAlbumsPageState();
}
class _DriftAlbumsPageState extends ConsumerState<DriftAlbumsPage> {
final ScrollController _scrollController = ScrollController();
Future<void> onRefresh() async {
await ref.read(remoteAlbumProvider.notifier).refresh();
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final albumCount = ref.watch(remoteAlbumProvider.select((state) => state.albums.length));
final showScrollbar = albumCount > 20;
return PageChromeContent(
final scrollView = CustomScrollView(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
appBar: const PrimaryAppBar(
snap: false,
floating: false,
pinned: true,
showUploadButton: false,
actions: [_CreateAlbumAction()],
),
viewportBuilder: (scrollView) => _AlbumRefreshWrapper(showScrollbar: showScrollbar, scrollView: scrollView),
slivers: [const _AlbumSelectorSliver()],
slivers: [
ImmichSliverAppBar(
snap: false,
floating: false,
pinned: true,
actions: [
IconButton(
onPressed: () => context.pushRoute(const DriftCreateAlbumRoute()),
icon: const Icon(Icons.add_rounded),
),
],
showUploadButton: false,
),
AlbumSelector(
onAlbumSelected: (album) {
context.router.push(RemoteAlbumRoute(album: album));
},
),
],
);
});
}
class _CreateAlbumAction extends StatelessWidget {
const _CreateAlbumAction();
@override
Widget build(BuildContext context) {
return IconButton(
onPressed: () => context.pushRoute(const DriftCreateAlbumRoute()),
icon: const Icon(Icons.add_rounded),
);
}
}
class _AlbumSelectorSliver extends StatelessWidget {
const _AlbumSelectorSliver();
@override
Widget build(BuildContext context) {
return AlbumSelector(onAlbumSelected: (album) => context.router.push(RemoteAlbumRoute(album: album)));
}
}
class _AlbumRefreshWrapper extends ConsumerWidget {
final bool showScrollbar;
final CustomScrollView scrollView;
const _AlbumRefreshWrapper({required this.showScrollbar, required this.scrollView});
@override
Widget build(BuildContext context, WidgetRef ref) {
final controller = PrimaryScrollController.of(context);
return RefreshIndicator(
onRefresh: () => ref.read(remoteAlbumProvider.notifier).refresh(),
onRefresh: onRefresh,
edgeOffset: 100,
child: showScrollbar
? RawScrollbar(
controller: controller,
controller: _scrollController,
interactive: true,
thickness: 8,
radius: const Radius.circular(4),

View File

@@ -16,23 +16,27 @@ import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:immich_mobile/widgets/common/page_chrome/tab_page.dart';
import 'package:immich_mobile/widgets/common/page_chrome/types.dart';
import 'package:immich_mobile/widgets/common/primary_app_bar.dart';
import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
import 'package:immich_mobile/widgets/map/map_thumbnail.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
@RoutePage()
class DriftLibraryPage extends TabPage {
class DriftLibraryPage extends ConsumerWidget {
const DriftLibraryPage({super.key});
@override
ProviderListenable<PageChromeContent> get content => Provider<PageChromeContent>((ref) {
return const PageChromeContent(
appBar: PrimaryAppBar(snap: false, floating: false, pinned: true, showUploadButton: false),
slivers: [_ActionButtonGrid(), _CollectionCards(), _QuickAccessButtonList()],
Widget build(BuildContext context, WidgetRef ref) {
return const Scaffold(
body: CustomScrollView(
slivers: [
ImmichSliverAppBar(snap: false, floating: false, pinned: true, showUploadButton: false),
_ActionButtonGrid(),
_CollectionCards(),
_QuickAccessButtonList(),
],
),
);
});
}
}
class _ActionButtonGrid extends ConsumerWidget {

View File

@@ -7,7 +7,6 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/settings_key.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/generated/translations.g.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
@@ -129,12 +128,6 @@ class ShareActionButton extends ConsumerWidget {
return;
}
await ref.read(settingsProvider).write(SettingsKey.shareFileType, fileType);
if (!context.mounted) {
return;
}
await _share(context, ref, fileType);
}

View File

@@ -745,10 +745,15 @@ class AddToAlbumHeader extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
Future<void> onCreateAlbum() async {
final albumName = await showDialog<String?>(context: context, builder: (context) => const NewAlbumNameModal());
if (albumName == null) {
return;
}
final selectedAssets = ref.read(multiSelectProvider).selectedAssets;
final newAlbum = await ref
.read(remoteAlbumProvider.notifier)
.createAlbumWithAssets(title: "Untitled Album", assets: selectedAssets);
.createAlbumWithAssets(title: albumName, assets: selectedAssets);
if (newAlbum == null) {
ImmichToast.show(context: context, toastType: ToastType.error, msg: 'errors.failed_to_create_album'.tr());

View File

@@ -91,7 +91,7 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
}
case AppLifecycleState.paused:
_shouldPlayOnForeground = await _controller?.isPlaying() ?? true;
if (_shouldPlayOnForeground) {
if (_shouldPlayOnForeground && mounted) {
await _notifier.pause();
}
default:
@@ -268,10 +268,13 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
return;
}
await _notifier.load(source);
// Grab refs to prevent reading after dispose
final loopVideo = ref.read(appConfigProvider).viewer.loopVideo;
await _notifier.setLoop(!widget.asset.isMotionPhoto && loopVideo);
await _notifier.setVolume(1);
final localNotifier = _notifier;
await localNotifier.load(source);
await localNotifier.setLoop(!widget.asset.isMotionPhoto && loopVideo);
await localNotifier.setVolume(1);
}
void _initController(NativeVideoPlayerController nc) {

View File

@@ -63,7 +63,7 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
ActionIconButtonWidget(action: FavoriteAction(assets: assetForAction)),
ViewerKebabMenu(originalTheme: originalTheme),
ImmichColorOverride(color: null, child: ViewerKebabMenu(originalTheme: originalTheme)),
];
final lockedViewActions = <Widget>[ViewerKebabMenu(originalTheme: originalTheme)];

View File

@@ -7,7 +7,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
@@ -17,23 +16,17 @@ import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/download_status_floating_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/feature_message/feature_message_dialog.widget.dart';
import 'package:immich_mobile/presentation/widgets/memory/memory_lane.widget.dart';
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
import 'package:immich_mobile/presentation/widgets/timeline/scrubber.widget.dart';
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline_scroll.provider.dart';
import 'package:immich_mobile/providers/feature_message.provider.dart';
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
import 'package:immich_mobile/widgets/common/page_chrome/types.dart';
import 'package:immich_mobile/widgets/common/primary_app_bar.dart';
import 'package:immich_mobile/widgets/common/selection_sliver_app_bar.dart';
class Timeline extends ConsumerWidget {
@@ -44,7 +37,7 @@ class Timeline extends ConsumerWidget {
this.bottomSliverWidget,
this.showStorageIndicator = false,
this.withStack = false,
this.appBar = const PrimaryAppBar(floating: true, pinned: false, snap: false),
this.appBar = const ImmichSliverAppBar(floating: true, pinned: false, snap: false),
this.bottomSheet = const GeneralBottomSheet(minChildSize: 0.23),
this.groupBy,
this.withScrubber = true,
@@ -755,225 +748,3 @@ class CustomScaleGestureRecognizer extends ScaleGestureRecognizer {
acceptGesture(pointer);
}
}
// ---------------------------------------------------------------------------
// PageChrome (Photos) path
//
// The shared `PageChrome` owns the Scaffold + scroll view + app bar. The
// timeline contributes a [PageChromeContent] via [timelineChromeProvider],
// read inside [timelineChromeScope] (which overrides `timelineArgsProvider`
// from the real layout constraints). The controller/observer/scroll logic
// lives in [timelineScrollProvider] so the chrome can read the controller
// before it builds the scroll view (same frame).
// ---------------------------------------------------------------------------
/// Wraps the chrome body in the timeline's `ProviderScope`, overriding
/// `timelineArgsProvider` from the actual scroll-view constraints via a
/// `LayoutBuilder` (built during layout, so the value is real, not a guess).
Widget timelineChromeScope(BuildContext context, Widget child) {
return LayoutBuilder(
builder: (context, constraints) {
return Consumer(
builder: (context, ref, _) {
final columnCount = ref.watch(appConfigProvider.select((config) => config.timeline.tilesPerRow));
return ProviderScope(
overrides: [
timelineArgsProvider.overrideWithValue(
TimelineArgs(
maxWidth: constraints.maxWidth,
maxHeight: constraints.maxHeight,
columnCount: columnCount,
showStorageIndicator: true,
),
),
],
child: _TimelineChromePopScope(child: child),
);
},
);
},
);
}
/// Pops out of multiselect (instead of the route) while a selection is active.
class _TimelineChromePopScope extends ConsumerWidget {
final Widget child;
const _TimelineChromePopScope({required this.child});
@override
Widget build(BuildContext context, WidgetRef ref) {
final isMultiSelectEnabled = ref.watch(multiSelectProvider.select((s) => s.isEnabled));
return PopScope(
canPop: !isMultiSelectEnabled,
onPopInvokedWithResult: (didPop, _) {
if (!didPop && isMultiSelectEnabled) {
ref.read(multiSelectProvider.notifier).reset();
}
},
child: child,
);
}
}
final timelineChromeProvider = Provider<PageChromeContent>((ref) {
final scroll = ref.watch(timelineScrollProvider.notifier);
final physics = ref.watch(timelineScrollProvider.select((s) => s.physics));
final maxHeight = ref.watch(timelineArgsProvider.select((args) => args.maxHeight));
final isSelectionMode = ref.watch(multiSelectProvider.select((s) => s.forceEnable));
return PageChromeContent(
controller: scroll.controller,
physics: physics,
scrollCacheExtent: ScrollCacheExtent.pixels(maxHeight * 2),
appBar: isSelectionMode
? const SelectionSliverAppBar()
: const PrimaryAppBar(floating: true, pinned: false, snap: false),
slivers: const [_TimelineChromeContent()],
viewportBuilder: (scrollView) => _TimelineChromeViewport(scrollView: scrollView),
floatingActionButton: const DownloadStatusFloatingButton(),
overlays: const [_TimelineChromeOverlays()],
);
}, dependencies: [timelineServiceProvider, timelineArgsProvider, timelineScrollProvider, multiSelectProvider]);
/// The timeline's content slivers (memory lane, the segmented grid, bottom
/// padding). Mounted by the chrome inside its scroll view, so the ambient
/// `PrimaryScrollController` is the chrome's controller.
class _TimelineChromeContent extends ConsumerWidget {
const _TimelineChromeContent();
@override
Widget build(BuildContext context, WidgetRef ref) {
// A reload event forces the grid to rebuild.
ref.listen(timelineSegmentProvider, (_, __) {});
final asyncSegments = ref.watch(timelineSegmentProvider);
final isMultiSelectEnabled = ref.watch(multiSelectProvider.select((s) => s.isEnabled));
final hasMemories = ref.watch(driftMemoryFutureProvider.select((state) => state.value?.isNotEmpty ?? false));
return asyncSegments.when(
loading: () => const SliverToBoxAdapter(child: SizedBox.shrink()),
error: (_, __) => const SliverToBoxAdapter(child: SizedBox.shrink()),
data: (segments) {
final childCount = (segments.lastOrNull?.lastIndex ?? -1) + 1;
const bottomSheetOpenModifier = 120.0;
final contentBottomPadding = context.padding.bottom + (isMultiSelectEnabled ? bottomSheetOpenModifier : 0);
return SliverMainAxisGroup(
slivers: [
if (hasMemories) const SliverToBoxAdapter(child: DriftMemoryLane()),
_SliverSegmentedList(
segments: segments,
delegate: SliverChildBuilderDelegate((ctx, index) {
if (index >= childCount) {
return null;
}
final segment = segments.findByIndex(index);
return segment?.builder(ctx, index) ?? const SizedBox.shrink();
}, childCount: childCount, addAutomaticKeepAlives: false, addRepaintBoundaries: false),
),
SliverPadding(padding: EdgeInsets.only(bottom: contentBottomPadding)),
],
);
},
);
}
}
/// Wraps the shared scroll view in the pinch-to-scale gesture, drag selection
/// region and the scrubber.
class _TimelineChromeViewport extends ConsumerWidget {
final CustomScrollView scrollView;
const _TimelineChromeViewport({required this.scrollView});
@override
Widget build(BuildContext context, WidgetRef ref) {
final scroll = ref.read(timelineScrollProvider.notifier);
final isReadonlyModeEnabled = ref.watch(readonlyModeProvider);
final maxHeight = ref.watch(timelineArgsProvider.select((args) => args.maxHeight));
final isMultiSelectEnabled = ref.watch(multiSelectProvider.select((s) => s.isEnabled));
final hasMemories = ref.watch(driftMemoryFutureProvider.select((state) => state.value?.isNotEmpty ?? false));
final segments = ref.watch(timelineSegmentProvider).valueOrNull ?? const [];
final topPadding = context.padding.top + kToolbarHeight + 10;
const bottomSheetOpenModifier = 120.0;
final contentBottomPadding = context.padding.bottom + (isMultiSelectEnabled ? bottomSheetOpenModifier : 0);
final scrubberBottomPadding = contentBottomPadding + kScrubberThumbHeight;
final timeline = Scrubber(
layoutSegments: segments,
timelineHeight: maxHeight,
topPadding: topPadding,
bottomPadding: scrubberBottomPadding,
monthSegmentSnappingOffset: hasMemories ? 200 : 0,
hasAppBar: true,
child: scrollView,
);
return RawGestureDetector(
gestures: {
CustomScaleGestureRecognizer: GestureRecognizerFactoryWithHandlers<CustomScaleGestureRecognizer>(
() => CustomScaleGestureRecognizer(),
(CustomScaleGestureRecognizer recognizer) {
recognizer.onStart = (_) => scroll.onScaleStart();
recognizer.onUpdate = (details) => scroll.onScaleUpdate(details.scale, segments);
},
),
},
child: TimelineDragRegion(
onStart: !isReadonlyModeEnabled ? scroll.setDragStartIndex : null,
onAssetEnter: scroll.handleDragAssetEnter,
onEnd: !isReadonlyModeEnabled ? scroll.stopDrag : null,
onScroll: scroll.dragScroll,
onScrollStart: scroll.onScrollStart,
child: timeline,
),
);
}
}
/// The multiselect status button + bottom sheet stacked on top of the timeline.
class _TimelineChromeOverlays extends HookConsumerWidget {
const _TimelineChromeOverlays();
@override
Widget build(BuildContext context, WidgetRef ref) {
// Show the "what's new" feature message once, when the timeline mounts.
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!context.mounted) {
return;
}
final service = ref.read(featureMessageServiceProvider);
if (!service.shouldShow()) {
return;
}
await service.markSeen();
if (!context.mounted) {
return;
}
await showFeatureMessageDialog(context);
});
return null;
}, const []);
final isSelectionMode = ref.watch(multiSelectProvider.select((s) => s.forceEnable));
final isMultiSelectEnabled = ref.watch(multiSelectProvider.select((s) => s.isEnabled));
final isVisible = !isSelectionMode && isMultiSelectEnabled;
if (!isVisible) {
return const SizedBox.shrink();
}
return Stack(
clipBehavior: Clip.none,
children: [
Positioned(
top: MediaQuery.paddingOf(context).top,
left: 25,
child: const SizedBox(height: kToolbarHeight, child: Center(child: _MultiSelectStatusButton())),
),
const GeneralBottomSheet(minChildSize: 0.23),
],
);
}
}

View File

@@ -1,282 +0,0 @@
import 'dart:async';
import 'dart:collection';
import 'dart:math' as math;
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/models/settings_key.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
/// Transient interaction state of the chrome timeline that drives rebuilds of
/// the content slivers + viewport (scale/drag/physics).
class TimelineScrollState {
final int perRow;
final double scaleFactor;
final bool dragging;
final ScrollPhysics? physics;
const TimelineScrollState({this.perRow = 4, this.scaleFactor = 3.0, this.dragging = false, this.physics});
TimelineScrollState copyWith({int? perRow, double? scaleFactor, bool? dragging, ScrollPhysics? physics}) {
return TimelineScrollState(
perRow: perRow ?? this.perRow,
scaleFactor: scaleFactor ?? this.scaleFactor,
dragging: dragging ?? this.dragging,
// physics is nullable-by-design; pass explicitly to clear.
physics: physics,
);
}
}
/// Owns the chrome timeline's [ScrollController] and everything bound to it:
/// the iOS status-bar-tap observer, the scroll event subscription, position
/// restore, pinch-to-scale and drag-selection logic.
///
/// This lives in a provider (not a widget [State]) so the shared `PageChrome`
/// can read the controller *before* it builds the scroll view, keeping the
/// timeline's chrome same-frame. Scoped under the timeline's `ProviderScope`
/// (via [timelineServiceProvider]/[timelineArgsProvider]) so each timeline gets
/// its own instance.
class TimelineScrollController extends Notifier<TimelineScrollState> with WidgetsBindingObserver {
late final ScrollController controller;
StreamSubscription? _eventSubscription;
// Drag selection
TimelineAssetIndex? _dragAnchorIndex;
final Set<BaseAsset> _draggedAssets = HashSet();
double _baseScaleFactor = 3.0;
int? _restoreAssetIndex;
@override
TimelineScrollState build() {
WidgetsBinding.instance.addObserver(this);
controller = ScrollController(onAttach: _restoreAssetPosition);
_eventSubscription = EventStream.shared.listen(_onEvent);
final perRow = ref.read(appConfigProvider.select((config) => config.timeline.tilesPerRow));
_baseScaleFactor = 7.0 - perRow;
ref.listen(multiSelectProvider.select((s) => s.isEnabled), _onMultiSelectionToggled);
// Remember scroll position across width changes (orientation / rail toggle)
// so it can be restored after the segments regenerate.
ref.listen(timelineArgsProvider.select((a) => a.maxWidth), (previous, next) {
if (previous != null && previous != next) {
final segments = ref.read(timelineSegmentProvider).valueOrNull;
if (segments != null && controller.hasClients) {
_restoreAssetIndex = _getCurrentAssetIndex(segments);
}
}
});
ref.onDispose(() {
WidgetsBinding.instance.removeObserver(this);
controller.dispose();
_eventSubscription?.cancel();
});
return TimelineScrollState(perRow: perRow, scaleFactor: _baseScaleFactor);
}
// Capture iOS status bar tap
@override
void handleStatusBarTap() => scrollToTop();
void _onEvent(Event event) {
switch (event) {
case ScrollToTopEvent():
scrollToTop();
case ScrollToDateEvent scrollToDateEvent:
scrollToDate(scrollToDateEvent.date);
default:
break;
}
}
void _restoreAssetPosition(_) {
if (_restoreAssetIndex == null) {
return;
}
final asyncSegments = ref.read(timelineSegmentProvider);
asyncSegments.whenData((segments) {
final targetSegment = segments.lastWhereOrNull((segment) => segment.firstAssetIndex <= _restoreAssetIndex!);
if (targetSegment != null) {
final assetIndexInSegment = _restoreAssetIndex! - targetSegment.firstAssetIndex;
final newColumnCount = ref.read(timelineArgsProvider).columnCount;
final rowIndexInSegment = (assetIndexInSegment / newColumnCount).floor();
final targetRowIndex = targetSegment.firstIndex + 1 + rowIndexInSegment;
final targetOffset = targetSegment.indexToLayoutOffset(targetRowIndex);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (controller.hasClients) {
controller.jumpTo(targetOffset.clamp(0.0, controller.position.maxScrollExtent));
}
});
}
});
_restoreAssetIndex = null;
}
void _onMultiSelectionToggled(_, bool isEnabled) {
EventStream.shared.emit(MultiSelectToggleEvent(isEnabled));
}
int? getCurrentAssetIndex(List<Segment> segments) => _getCurrentAssetIndex(segments);
int? _getCurrentAssetIndex(List<Segment> segments) {
final currentOffset = controller.offset.clamp(0.0, controller.position.maxScrollExtent);
final segment = segments.findByOffset(currentOffset) ?? segments.lastOrNull;
int? targetAssetIndex;
if (segment != null) {
final rowIndex = segment.getMinChildIndexForScrollOffset(currentOffset);
if (rowIndex > segment.firstIndex) {
final rowIndexInSegment = rowIndex - (segment.firstIndex + 1);
final assetsPerRow = ref.read(timelineArgsProvider).columnCount;
final assetIndexInSegment = rowIndexInSegment * assetsPerRow;
targetAssetIndex = segment.firstAssetIndex + assetIndexInSegment;
} else {
targetAssetIndex = segment.firstAssetIndex;
}
}
return targetAssetIndex;
}
void scrollToTop() {
if (!controller.hasClients) {
return;
}
final timelineState = ref.read(timelineStateProvider.notifier);
timelineState.setScrubbing(true);
controller
.animateTo(0, duration: const Duration(milliseconds: 250), curve: Curves.easeInOut)
.whenComplete(() => timelineState.setScrubbing(false));
}
void scrollToDate(DateTime date) {
final timelineState = ref.read(timelineStateProvider.notifier);
final asyncSegments = ref.read(timelineSegmentProvider);
asyncSegments.whenData((segments) {
final targetSegment = segments.firstWhereOrNull((segment) {
if (segment.bucket is TimeBucket) {
final segmentDate = (segment.bucket as TimeBucket).date;
return segmentDate.year == date.year && segmentDate.month == date.month && segmentDate.day == date.day;
}
return false;
});
final fallbackSegment =
targetSegment ??
segments.firstWhereOrNull((segment) {
if (segment.bucket is TimeBucket) {
final segmentDate = (segment.bucket as TimeBucket).date;
return segmentDate.year == date.year && segmentDate.month == date.month;
}
return false;
});
if (fallbackSegment != null) {
final targetOffset = fallbackSegment.startOffset - 50;
timelineState.setScrubbing(true);
controller
.animateTo(
targetOffset.clamp(0.0, controller.position.maxScrollExtent),
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
)
.whenComplete(() => timelineState.setScrubbing(false));
} else {
timelineState.setScrubbing(false);
}
});
}
// Pinch to scale
void onScaleStart() {
_baseScaleFactor = state.scaleFactor;
}
void onScaleUpdate(double scale, List<Segment> segments) {
final newScaleFactor = math.max(math.min(5.0, _baseScaleFactor * scale), 1.0);
final newPerRow = 7 - newScaleFactor.toInt();
if (newPerRow != state.perRow) {
_restoreAssetIndex = _getCurrentAssetIndex(segments);
state = state.copyWith(scaleFactor: newScaleFactor, perRow: newPerRow, physics: state.physics);
ref.read(settingsProvider).write(SettingsKey.timelineTilesPerRow, newPerRow);
}
}
// Drag selection
void setDragStartIndex(TimelineAssetIndex index) {
_dragAnchorIndex = index;
state = state.copyWith(dragging: true, physics: const ClampingScrollPhysics());
}
void stopDrag() {
WidgetsBinding.instance.addPostFrameCallback((_) {
// Reset physics post frame to prevent a sudden change on iOS.
state = state.copyWith(dragging: state.dragging, physics: null);
});
_draggedAssets.clear();
state = state.copyWith(dragging: false, physics: state.physics);
final timelineState = ref.read(timelineStateProvider.notifier);
Future.delayed(const Duration(milliseconds: 300), () {
timelineState.setScrolling(false);
});
}
void dragScroll(ScrollDirection direction) {
controller.animateTo(
controller.offset + (direction == ScrollDirection.forward ? 175 : -175),
duration: const Duration(milliseconds: 125),
curve: Curves.easeOut,
);
}
void onScrollStart() {
ref.read(timelineStateProvider.notifier).setScrolling(true);
}
void handleDragAssetEnter(TimelineAssetIndex index) {
if (_dragAnchorIndex == null || !state.dragging) {
return;
}
final timelineService = ref.read(timelineServiceProvider);
final dragAnchorIndex = _dragAnchorIndex!;
final startIndex = math.min(dragAnchorIndex.assetIndex, index.assetIndex);
final endIndex = math.max(dragAnchorIndex.assetIndex, index.assetIndex);
final count = endIndex - startIndex + 1;
if (timelineService.hasRange(startIndex, count)) {
final selectedAssets = timelineService.getAssets(startIndex, count);
final multiSelectNotifier = ref.read(multiSelectProvider.notifier);
for (final asset in _draggedAssets) {
multiSelectNotifier.deselectAsset(asset);
}
_draggedAssets.clear();
for (final asset in selectedAssets) {
multiSelectNotifier.selectAsset(asset);
_draggedAssets.add(asset);
}
}
}
}
final timelineScrollProvider = NotifierProvider<TimelineScrollController, TimelineScrollState>(
TimelineScrollController.new,
dependencies: [timelineServiceProvider, timelineArgsProvider, multiSelectProvider, timelineSegmentProvider],
);

View File

@@ -318,7 +318,7 @@ class BackgroundUploadService {
isFavorite: asset.isFavorite,
requiresWiFi: requiresWiFi,
// Visibility hidden on upload to prevent the server from running regular jobs on the live photo asset
fields: entity.isLivePhoto ? {'visibility': api.AssetVisibility.hidden.value} : null,
fields: entity.isLivePhoto ? {'visibility': api.AssetVisibility.hidden.toString()} : null,
cloudId: entity.isLivePhoto ? null : asset.cloudId,
adjustmentTime: entity.isLivePhoto ? null : asset.adjustmentTime?.toIso8601String(),
latitude: entity.isLivePhoto ? null : asset.latitude?.toString(),

View File

@@ -342,7 +342,7 @@ class ForegroundUploadService {
file: livePhotoFile,
originalFileName: livePhotoTitle,
// Visibility hidden on upload to prevent the server from running regular jobs on the live photo asset
fields: {...fields, 'visibility': AssetVisibility.hidden.value},
fields: {...fields, 'visibility': AssetVisibility.hidden.toString()},
cancelToken: cancelToken,
onProgress: onProgress != null
? (bytes, totalBytes) => onProgress(asset.localId!, livePhotoTitle, bytes, totalBytes)

View File

@@ -0,0 +1,22 @@
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
extension type const AssetFilter<T extends BaseAsset>(Iterable<T> assets) implements Iterable<T> {
AssetFilter<T> where(bool Function(T asset) test) => AssetFilter(assets.where(test));
AssetFilter<T> whereNot(bool Function(T asset) test) => AssetFilter(assets.where((asset) => !test(asset)));
AssetFilter<T> type(AssetType type) => where((asset) => asset.type == type);
AssetFilter<T> favorite({bool isFavorite = true}) => where((asset) => asset.isFavorite == isFavorite);
AssetFilter<RemoteAsset> remote() => AssetFilter(assets.whereType<RemoteAsset>());
AssetFilter<RemoteAsset> owned(String ownerId) => remote().where((asset) => asset.ownerId == ownerId);
AssetFilter<RemoteAsset> visibility(AssetVisibility visibility) =>
remote().where((asset) => asset.visibility == visibility);
AssetFilter<RemoteAsset> notVisibility(AssetVisibility visibility) =>
remote().where((asset) => asset.visibility != visibility);
AssetFilter<RemoteAsset> archived({bool isArchived = true}) =>
remote().where((asset) => asset.isArchived == isArchived);
AssetFilter<RemoteAsset> stacked({bool isStacked = true}) => remote().where((asset) => asset.isStacked == isStacked);
AssetFilter<LocalAsset> local() => AssetFilter(assets.whereType<LocalAsset>());
AssetFilter<LocalAsset> backedUp() => local().where((asset) => asset.remoteAssetId != null);
}

View File

@@ -12,7 +12,7 @@ String getThumbnailUrlForRemoteId(
bool edited = true,
String? thumbhash,
}) {
final url = '${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail?size=${type.value}&edited=$edited';
final url = '${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail?size=${type.toString()}&edited=$edited';
return thumbhash != null ? '$url&c=${Uri.encodeComponent(thumbhash)}' : url;
}

View File

@@ -11,17 +11,20 @@ import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/domain/models/settings_key.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/services/feature_message.service.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
const int targetVersion = 26;
Future<void> migrateDatabaseIfNeeded(Drift drift) async {
final int version = Store.get(StoreKey.version, targetVersion);
final int? storedVersion = Store.tryGet(StoreKey.version);
final version = storedVersion ?? targetVersion;
if (version < 25) {
await _migrateTo25();
@@ -31,6 +34,10 @@ Future<void> migrateDatabaseIfNeeded(Drift drift) async {
await _migrateTo26(drift);
}
if (storedVersion == null) {
await FeatureMessageService(SettingsRepository.instance).markSeen();
}
await Store.put(StoreKey.version, targetVersion);
return;
}

View File

@@ -20,23 +20,17 @@ import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/asset_viewer/cast_dialog.dart';
import 'package:immich_mobile/widgets/common/app_bar_dialog/app_bar_dialog.dart';
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
import 'package:immich_ui/immich_ui.dart';
/// The app's standard app bar. Composes Immich-specific indicators (sync,
/// backup, profile, cast) and the multiselect dim behaviour onto the generic
/// [ImmichSliverAppBar] primitive from the ui package.
class PrimaryAppBar extends ConsumerWidget {
class ImmichSliverAppBar extends ConsumerWidget {
final List<Widget>? actions;
final bool showUploadButton;
final bool floating;
final bool pinned;
final bool snap;
final Widget? title;
final Widget? leading;
final bool automaticallyImplyLeading;
final double? expandedHeight;
const PrimaryAppBar({
const ImmichSliverAppBar({
super.key,
this.actions,
this.showUploadButton = true,
@@ -44,8 +38,6 @@ class PrimaryAppBar extends ConsumerWidget {
this.pinned = false,
this.snap = true,
this.title,
this.leading,
this.automaticallyImplyLeading = false,
this.expandedHeight,
});
@@ -55,27 +47,38 @@ class PrimaryAppBar extends ConsumerWidget {
final isReadonlyModeEnabled = ref.watch(readonlyModeProvider);
final isMultiSelectEnabled = ref.watch(multiSelectProvider.select((s) => s.isEnabled));
return ImmichSliverAppBar(
floating: floating,
pinned: pinned,
snap: snap,
expandedHeight: expandedHeight,
dimmed: isMultiSelectEnabled,
automaticallyImplyLeading: automaticallyImplyLeading,
leading: leading,
title: title ?? const _ImmichLogoWithText(),
actions: [
const _SyncStatusIndicator(),
if (isCasting && !isReadonlyModeEnabled)
IconButton(
onPressed: () => showDialog(context: context, builder: (context) => const CastDialog()),
icon: Icon(isCasting ? Icons.cast_connected_rounded : Icons.cast_rounded),
),
if (actions != null) ...actions!,
if (showUploadButton && !isReadonlyModeEnabled) const _BackupIndicator(),
const _ProfileIndicator(),
const SizedBox(width: 8),
],
return SliverIgnorePointer(
ignoring: isMultiSelectEnabled,
sliver: SliverAnimatedOpacity(
duration: Durations.medium1,
opacity: isMultiSelectEnabled ? 0 : 1,
sliver: SliverAppBar(
backgroundColor: context.colorScheme.surface,
surfaceTintColor: context.colorScheme.surfaceTint,
elevation: 0,
scrolledUnderElevation: 1.0,
floating: floating,
pinned: pinned,
snap: snap,
expandedHeight: expandedHeight,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(bottom: Radius.circular(5))),
automaticallyImplyLeading: false,
centerTitle: false,
title: title ?? const _ImmichLogoWithText(),
actions: [
const _SyncStatusIndicator(),
if (isCasting && !isReadonlyModeEnabled)
IconButton(
onPressed: () => showDialog(context: context, builder: (context) => const CastDialog()),
icon: Icon(isCasting ? Icons.cast_connected_rounded : Icons.cast_rounded),
),
if (actions != null) ...actions!,
if (showUploadButton && !isReadonlyModeEnabled) const _BackupIndicator(),
const _ProfileIndicator(),
const SizedBox(width: 8),
],
),
),
);
}
}

View File

@@ -1,64 +0,0 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/widgets/common/page_chrome/types.dart';
/// The shared page chrome. Authored once; tabs never instantiate it directly
/// (they extend `TabPage`, whose `build` renders this beneath the tab's scope).
///
/// Owns the [Scaffold], the single [CustomScrollView] and its
/// [PrimaryScrollController]. Reads the active tab's [PageChromeContent] from
/// [content] (available in the same frame, since this is a descendant of the
/// tab's scope) and renders its app bar + content slivers.
class PageChrome extends ConsumerStatefulWidget {
final ProviderListenable<PageChromeContent> content;
const PageChrome({super.key, required this.content});
@override
ConsumerState<PageChrome> createState() => _PageChromeState();
}
class _PageChromeState extends ConsumerState<PageChrome> {
ScrollController? _ownController;
@override
void dispose() {
_ownController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final content = ref.watch(widget.content);
// The tab's controller, or a chrome-owned default created lazily.
final controller = content.controller ?? (_ownController ??= ScrollController());
final scrollView = CustomScrollView(
controller: controller,
physics: content.physics,
scrollCacheExtent: content.scrollCacheExtent,
slivers: [content.appBar, ...content.slivers],
);
Widget body = content.viewportBuilder != null ? content.viewportBuilder!(scrollView) : scrollView;
if (content.overlays.isNotEmpty) {
body = Stack(clipBehavior: Clip.none, children: [body, ...content.overlays]);
}
body = Scaffold(
// Removes the built-in Scaffold `handleStatusBarTap`, avoiding duplicate
// events when a tab (e.g. the timeline) provides its own.
primary: false,
resizeToAvoidBottomInset: false,
floatingActionButton: content.floatingActionButton,
body: body,
);
// The chrome owns the single scroll view; mark its controller primary so
// the scrubber's `PrimaryScrollController.of` and iOS status-bar-tap both
// resolve it. Wrapping the Scaffold preserves status-bar-tap resolution.
return PrimaryScrollController(controller: controller, child: body);
}
}

View File

@@ -1,30 +0,0 @@
import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/widgets/common/page_chrome/page_chrome.dart';
import 'package:immich_mobile/widgets/common/page_chrome/types.dart';
/// The base type for a page rendered as a primary tab.
///
/// Extending [TabPage] is the *only* place that knows the page is a tab: a
/// subclass declares its chrome [content] provider and, if needed, the
/// [scopeBuilder] the provider must be read within. The [PageChrome] is
/// rendered automatically beneath that scope, so it reads the tab's content
/// provider as a descendant (same frame). Nothing beneath the chrome has any
/// knowledge of tabs.
abstract class TabPage extends ConsumerWidget {
const TabPage({super.key});
/// The provider that yields this tab's chrome content (app bar + slivers +
/// controller + decorators). Read beneath [scopeBuilder].
ProviderListenable<PageChromeContent> get content;
/// Produces the scope the [content] provider must be read within (e.g. the
/// timeline's `LayoutBuilder` + `timelineArgsProvider` override). Defaults to
/// the identity scope.
Widget scopeBuilder(BuildContext context, Widget child) => child;
@override
Widget build(BuildContext context, WidgetRef ref) {
return scopeBuilder(context, PageChrome(content: content));
}
}

View File

@@ -1,51 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
/// Everything a tab contributes to the single shared `PageChrome`.
///
/// Published per-tab via a `Provider<PageChromeContent>`; the chrome reads the
/// active tab's value (within that tab's scope) and renders it. Data flows
/// downward, so the chrome renders with the active tab's content in the same
/// frame.
class PageChromeContent {
/// The app bar sliver placed first in the shared scroll view. The tab decides
/// which bar it wants (e.g. a primary bar, or a selection bar during
/// multiselect).
final Widget appBar;
/// The tab's content slivers, rendered after [appBar].
final List<Widget> slivers;
/// Scroll controller for the shared scroll view. Provided by the tab when it
/// needs custom behaviour (e.g. the timeline's position restore). When `null`,
/// the chrome creates and owns a default controller.
final ScrollController? controller;
/// Optionally decorates the shared scroll view, e.g. wrapping it in the
/// timeline's `Scrubber`. Receives the built scroll view and returns the
/// widget to place in the body.
final Widget Function(CustomScrollView scrollView)? viewportBuilder;
final Widget? floatingActionButton;
/// Widgets stacked on top of the scroll view (e.g. bottom sheet, multiselect
/// status button).
final List<Widget> overlays;
final ScrollPhysics? physics;
/// Forwarded to the shared [CustomScrollView] (e.g. the timeline's larger
/// cache extent).
final ScrollCacheExtent? scrollCacheExtent;
const PageChromeContent({
required this.appBar,
this.slivers = const [],
this.controller,
this.viewportBuilder,
this.floatingActionButton,
this.overlays = const [],
this.physics,
this.scrollCacheExtent,
});
}

View File

@@ -1 +1 @@
7.22.0
7.24.0

View File

@@ -4,12 +4,12 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 3.0.3
- Generator version: 7.22.0
- Generator version: 7.24.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen
## Requirements
Dart 2.12 or later
Dart 2.17 or later
## Installation & Usage

View File

@@ -97,9 +97,9 @@ class ApiClient {
if (nullableHeaderParams != null) {
request.headers.addAll(nullableHeaderParams);
}
if (msgBody is String) {
if (msgBody is String && msgBody.isNotEmpty) {
request.body = msgBody;
} else if (msgBody is List<int>) {
} else if (msgBody is List<int> && msgBody.isNotEmpty) {
request.bodyBytes = msgBody;
} else if (msgBody is Map<String, String>) {
request.bodyFields = msgBody;

View File

@@ -11,31 +11,30 @@
part of openapi.api;
/// Album user role
class AlbumUserRole {
/// Instantiate a new enum with the provided [value].
const AlbumUserRole._(this.value);
enum AlbumUserRole {
editor._(r'editor'),
owner._(r'owner'),
viewer._(r'viewer'),
;
/// Instantiate a new enum with the provided value.
const AlbumUserRole._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const editor = AlbumUserRole._(r'editor');
static const owner = AlbumUserRole._(r'owner');
static const viewer = AlbumUserRole._(r'viewer');
/// List of all possible values in this [enum][AlbumUserRole].
static const values = <AlbumUserRole>[
editor,
owner,
viewer,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [AlbumUserRole] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static AlbumUserRole? fromJson(dynamic value) => AlbumUserRoleTypeTransformer().decode(value);
/// Returns a [List] containing instances of [AlbumUserRole]
/// that were successfully decoded from the passed [JSON][json].
static List<AlbumUserRole> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AlbumUserRole>[];
if (json is List && json.isNotEmpty) {
@@ -57,9 +56,11 @@ class AlbumUserRoleTypeTransformer {
const AlbumUserRoleTypeTransformer._();
String encode(AlbumUserRole data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(AlbumUserRole data) => data._value;
/// Decodes a [dynamic value][data] to a AlbumUserRole.
/// Returns the instance of [AlbumUserRole] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -68,6 +69,9 @@ class AlbumUserRoleTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AlbumUserRole? decode(dynamic data, {bool allowNull = true}) {
if (data is AlbumUserRole) {
return data;
}
if (data != null) {
switch (data) {
case r'editor': return AlbumUserRole.editor;
@@ -82,7 +86,7 @@ class AlbumUserRoleTypeTransformer {
return null;
}
/// Singleton [AlbumUserRoleTypeTransformer] instance.
/// The singleton instance of this transformer.
static AlbumUserRoleTypeTransformer? _instance;
}

View File

@@ -11,31 +11,30 @@
part of openapi.api;
/// Type of edit action to perform
class AssetEditAction {
/// Instantiate a new enum with the provided [value].
const AssetEditAction._(this.value);
enum AssetEditAction {
crop._(r'crop'),
rotate._(r'rotate'),
mirror._(r'mirror'),
;
/// Instantiate a new enum with the provided value.
const AssetEditAction._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const crop = AssetEditAction._(r'crop');
static const rotate = AssetEditAction._(r'rotate');
static const mirror = AssetEditAction._(r'mirror');
/// List of all possible values in this [enum][AssetEditAction].
static const values = <AssetEditAction>[
crop,
rotate,
mirror,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [AssetEditAction] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static AssetEditAction? fromJson(dynamic value) => AssetEditActionTypeTransformer().decode(value);
/// Returns a [List] containing instances of [AssetEditAction]
/// that were successfully decoded from the passed [JSON][json].
static List<AssetEditAction> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetEditAction>[];
if (json is List && json.isNotEmpty) {
@@ -57,9 +56,11 @@ class AssetEditActionTypeTransformer {
const AssetEditActionTypeTransformer._();
String encode(AssetEditAction data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(AssetEditAction data) => data._value;
/// Decodes a [dynamic value][data] to a AssetEditAction.
/// Returns the instance of [AssetEditAction] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -68,6 +69,9 @@ class AssetEditActionTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AssetEditAction? decode(dynamic data, {bool allowNull = true}) {
if (data is AssetEditAction) {
return data;
}
if (data != null) {
switch (data) {
case r'crop': return AssetEditAction.crop;
@@ -82,7 +86,7 @@ class AssetEditActionTypeTransformer {
return null;
}
/// Singleton [AssetEditActionTypeTransformer] instance.
/// The singleton instance of this transformer.
static AssetEditActionTypeTransformer? _instance;
}

View File

@@ -11,31 +11,30 @@
part of openapi.api;
/// Error reason if failed
class AssetIdErrorReason {
/// Instantiate a new enum with the provided [value].
const AssetIdErrorReason._(this.value);
enum AssetIdErrorReason {
duplicate._(r'duplicate'),
noPermission._(r'no_permission'),
notFound._(r'not_found'),
;
/// Instantiate a new enum with the provided value.
const AssetIdErrorReason._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const duplicate = AssetIdErrorReason._(r'duplicate');
static const noPermission = AssetIdErrorReason._(r'no_permission');
static const notFound = AssetIdErrorReason._(r'not_found');
/// List of all possible values in this [enum][AssetIdErrorReason].
static const values = <AssetIdErrorReason>[
duplicate,
noPermission,
notFound,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [AssetIdErrorReason] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static AssetIdErrorReason? fromJson(dynamic value) => AssetIdErrorReasonTypeTransformer().decode(value);
/// Returns a [List] containing instances of [AssetIdErrorReason]
/// that were successfully decoded from the passed [JSON][json].
static List<AssetIdErrorReason> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetIdErrorReason>[];
if (json is List && json.isNotEmpty) {
@@ -57,9 +56,11 @@ class AssetIdErrorReasonTypeTransformer {
const AssetIdErrorReasonTypeTransformer._();
String encode(AssetIdErrorReason data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(AssetIdErrorReason data) => data._value;
/// Decodes a [dynamic value][data] to a AssetIdErrorReason.
/// Returns the instance of [AssetIdErrorReason] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -68,6 +69,9 @@ class AssetIdErrorReasonTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AssetIdErrorReason? decode(dynamic data, {bool allowNull = true}) {
if (data is AssetIdErrorReason) {
return data;
}
if (data != null) {
switch (data) {
case r'duplicate': return AssetIdErrorReason.duplicate;
@@ -82,7 +86,7 @@ class AssetIdErrorReasonTypeTransformer {
return null;
}
/// Singleton [AssetIdErrorReasonTypeTransformer] instance.
/// The singleton instance of this transformer.
static AssetIdErrorReasonTypeTransformer? _instance;
}

View File

@@ -11,33 +11,31 @@
part of openapi.api;
/// Job name
class AssetJobName {
/// Instantiate a new enum with the provided [value].
const AssetJobName._(this.value);
enum AssetJobName {
refreshFaces._(r'refresh-faces'),
refreshMetadata._(r'refresh-metadata'),
regenerateThumbnail._(r'regenerate-thumbnail'),
transcodeVideo._(r'transcode-video'),
;
/// Instantiate a new enum with the provided value.
const AssetJobName._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const refreshFaces = AssetJobName._(r'refresh-faces');
static const refreshMetadata = AssetJobName._(r'refresh-metadata');
static const regenerateThumbnail = AssetJobName._(r'regenerate-thumbnail');
static const transcodeVideo = AssetJobName._(r'transcode-video');
/// List of all possible values in this [enum][AssetJobName].
static const values = <AssetJobName>[
refreshFaces,
refreshMetadata,
regenerateThumbnail,
transcodeVideo,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [AssetJobName] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static AssetJobName? fromJson(dynamic value) => AssetJobNameTypeTransformer().decode(value);
/// Returns a [List] containing instances of [AssetJobName]
/// that were successfully decoded from the passed [JSON][json].
static List<AssetJobName> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetJobName>[];
if (json is List && json.isNotEmpty) {
@@ -59,9 +57,11 @@ class AssetJobNameTypeTransformer {
const AssetJobNameTypeTransformer._();
String encode(AssetJobName data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(AssetJobName data) => data._value;
/// Decodes a [dynamic value][data] to a AssetJobName.
/// Returns the instance of [AssetJobName] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -70,6 +70,9 @@ class AssetJobNameTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AssetJobName? decode(dynamic data, {bool allowNull = true}) {
if (data is AssetJobName) {
return data;
}
if (data != null) {
switch (data) {
case r'refresh-faces': return AssetJobName.refreshFaces;
@@ -85,7 +88,7 @@ class AssetJobNameTypeTransformer {
return null;
}
/// Singleton [AssetJobNameTypeTransformer] instance.
/// The singleton instance of this transformer.
static AssetJobNameTypeTransformer? _instance;
}

View File

@@ -11,33 +11,31 @@
part of openapi.api;
/// Asset media size
class AssetMediaSize {
/// Instantiate a new enum with the provided [value].
const AssetMediaSize._(this.value);
enum AssetMediaSize {
original._(r'original'),
fullsize._(r'fullsize'),
preview._(r'preview'),
thumbnail._(r'thumbnail'),
;
/// Instantiate a new enum with the provided value.
const AssetMediaSize._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const original = AssetMediaSize._(r'original');
static const fullsize = AssetMediaSize._(r'fullsize');
static const preview = AssetMediaSize._(r'preview');
static const thumbnail = AssetMediaSize._(r'thumbnail');
/// List of all possible values in this [enum][AssetMediaSize].
static const values = <AssetMediaSize>[
original,
fullsize,
preview,
thumbnail,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [AssetMediaSize] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static AssetMediaSize? fromJson(dynamic value) => AssetMediaSizeTypeTransformer().decode(value);
/// Returns a [List] containing instances of [AssetMediaSize]
/// that were successfully decoded from the passed [JSON][json].
static List<AssetMediaSize> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetMediaSize>[];
if (json is List && json.isNotEmpty) {
@@ -59,9 +57,11 @@ class AssetMediaSizeTypeTransformer {
const AssetMediaSizeTypeTransformer._();
String encode(AssetMediaSize data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(AssetMediaSize data) => data._value;
/// Decodes a [dynamic value][data] to a AssetMediaSize.
/// Returns the instance of [AssetMediaSize] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -70,6 +70,9 @@ class AssetMediaSizeTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AssetMediaSize? decode(dynamic data, {bool allowNull = true}) {
if (data is AssetMediaSize) {
return data;
}
if (data != null) {
switch (data) {
case r'original': return AssetMediaSize.original;
@@ -85,7 +88,7 @@ class AssetMediaSizeTypeTransformer {
return null;
}
/// Singleton [AssetMediaSizeTypeTransformer] instance.
/// The singleton instance of this transformer.
static AssetMediaSizeTypeTransformer? _instance;
}

View File

@@ -11,29 +11,29 @@
part of openapi.api;
/// Upload status
class AssetMediaStatus {
/// Instantiate a new enum with the provided [value].
const AssetMediaStatus._(this.value);
enum AssetMediaStatus {
created._(r'created'),
duplicate._(r'duplicate'),
;
/// Instantiate a new enum with the provided value.
const AssetMediaStatus._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const created = AssetMediaStatus._(r'created');
static const duplicate = AssetMediaStatus._(r'duplicate');
/// List of all possible values in this [enum][AssetMediaStatus].
static const values = <AssetMediaStatus>[
created,
duplicate,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [AssetMediaStatus] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static AssetMediaStatus? fromJson(dynamic value) => AssetMediaStatusTypeTransformer().decode(value);
/// Returns a [List] containing instances of [AssetMediaStatus]
/// that were successfully decoded from the passed [JSON][json].
static List<AssetMediaStatus> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetMediaStatus>[];
if (json is List && json.isNotEmpty) {
@@ -55,9 +55,11 @@ class AssetMediaStatusTypeTransformer {
const AssetMediaStatusTypeTransformer._();
String encode(AssetMediaStatus data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(AssetMediaStatus data) => data._value;
/// Decodes a [dynamic value][data] to a AssetMediaStatus.
/// Returns the instance of [AssetMediaStatus] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -66,6 +68,9 @@ class AssetMediaStatusTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AssetMediaStatus? decode(dynamic data, {bool allowNull = true}) {
if (data is AssetMediaStatus) {
return data;
}
if (data != null) {
switch (data) {
case r'created': return AssetMediaStatus.created;
@@ -79,7 +84,7 @@ class AssetMediaStatusTypeTransformer {
return null;
}
/// Singleton [AssetMediaStatusTypeTransformer] instance.
/// The singleton instance of this transformer.
static AssetMediaStatusTypeTransformer? _instance;
}

View File

@@ -11,29 +11,29 @@
part of openapi.api;
/// Asset sort order
class AssetOrder {
/// Instantiate a new enum with the provided [value].
const AssetOrder._(this.value);
enum AssetOrder {
asc._(r'asc'),
desc._(r'desc'),
;
/// Instantiate a new enum with the provided value.
const AssetOrder._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const asc = AssetOrder._(r'asc');
static const desc = AssetOrder._(r'desc');
/// List of all possible values in this [enum][AssetOrder].
static const values = <AssetOrder>[
asc,
desc,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [AssetOrder] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static AssetOrder? fromJson(dynamic value) => AssetOrderTypeTransformer().decode(value);
/// Returns a [List] containing instances of [AssetOrder]
/// that were successfully decoded from the passed [JSON][json].
static List<AssetOrder> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetOrder>[];
if (json is List && json.isNotEmpty) {
@@ -55,9 +55,11 @@ class AssetOrderTypeTransformer {
const AssetOrderTypeTransformer._();
String encode(AssetOrder data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(AssetOrder data) => data._value;
/// Decodes a [dynamic value][data] to a AssetOrder.
/// Returns the instance of [AssetOrder] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -66,6 +68,9 @@ class AssetOrderTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AssetOrder? decode(dynamic data, {bool allowNull = true}) {
if (data is AssetOrder) {
return data;
}
if (data != null) {
switch (data) {
case r'asc': return AssetOrder.asc;
@@ -79,7 +84,7 @@ class AssetOrderTypeTransformer {
return null;
}
/// Singleton [AssetOrderTypeTransformer] instance.
/// The singleton instance of this transformer.
static AssetOrderTypeTransformer? _instance;
}

View File

@@ -11,29 +11,29 @@
part of openapi.api;
/// Asset sorting property
class AssetOrderBy {
/// Instantiate a new enum with the provided [value].
const AssetOrderBy._(this.value);
enum AssetOrderBy {
takenAt._(r'takenAt'),
createdAt._(r'createdAt'),
;
/// Instantiate a new enum with the provided value.
const AssetOrderBy._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const takenAt = AssetOrderBy._(r'takenAt');
static const createdAt = AssetOrderBy._(r'createdAt');
/// List of all possible values in this [enum][AssetOrderBy].
static const values = <AssetOrderBy>[
takenAt,
createdAt,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [AssetOrderBy] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static AssetOrderBy? fromJson(dynamic value) => AssetOrderByTypeTransformer().decode(value);
/// Returns a [List] containing instances of [AssetOrderBy]
/// that were successfully decoded from the passed [JSON][json].
static List<AssetOrderBy> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetOrderBy>[];
if (json is List && json.isNotEmpty) {
@@ -55,9 +55,11 @@ class AssetOrderByTypeTransformer {
const AssetOrderByTypeTransformer._();
String encode(AssetOrderBy data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(AssetOrderBy data) => data._value;
/// Decodes a [dynamic value][data] to a AssetOrderBy.
/// Returns the instance of [AssetOrderBy] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -66,6 +68,9 @@ class AssetOrderByTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AssetOrderBy? decode(dynamic data, {bool allowNull = true}) {
if (data is AssetOrderBy) {
return data;
}
if (data != null) {
switch (data) {
case r'takenAt': return AssetOrderBy.takenAt;
@@ -79,7 +84,7 @@ class AssetOrderByTypeTransformer {
return null;
}
/// Singleton [AssetOrderByTypeTransformer] instance.
/// The singleton instance of this transformer.
static AssetOrderByTypeTransformer? _instance;
}

View File

@@ -11,29 +11,29 @@
part of openapi.api;
/// Rejection reason if rejected
class AssetRejectReason {
/// Instantiate a new enum with the provided [value].
const AssetRejectReason._(this.value);
enum AssetRejectReason {
duplicate._(r'duplicate'),
unsupportedFormat._(r'unsupported-format'),
;
/// Instantiate a new enum with the provided value.
const AssetRejectReason._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const duplicate = AssetRejectReason._(r'duplicate');
static const unsupportedFormat = AssetRejectReason._(r'unsupported-format');
/// List of all possible values in this [enum][AssetRejectReason].
static const values = <AssetRejectReason>[
duplicate,
unsupportedFormat,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [AssetRejectReason] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static AssetRejectReason? fromJson(dynamic value) => AssetRejectReasonTypeTransformer().decode(value);
/// Returns a [List] containing instances of [AssetRejectReason]
/// that were successfully decoded from the passed [JSON][json].
static List<AssetRejectReason> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetRejectReason>[];
if (json is List && json.isNotEmpty) {
@@ -55,9 +55,11 @@ class AssetRejectReasonTypeTransformer {
const AssetRejectReasonTypeTransformer._();
String encode(AssetRejectReason data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(AssetRejectReason data) => data._value;
/// Decodes a [dynamic value][data] to a AssetRejectReason.
/// Returns the instance of [AssetRejectReason] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -66,6 +68,9 @@ class AssetRejectReasonTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AssetRejectReason? decode(dynamic data, {bool allowNull = true}) {
if (data is AssetRejectReason) {
return data;
}
if (data != null) {
switch (data) {
case r'duplicate': return AssetRejectReason.duplicate;
@@ -79,7 +84,7 @@ class AssetRejectReasonTypeTransformer {
return null;
}
/// Singleton [AssetRejectReasonTypeTransformer] instance.
/// The singleton instance of this transformer.
static AssetRejectReasonTypeTransformer? _instance;
}

View File

@@ -11,33 +11,31 @@
part of openapi.api;
/// Asset type
class AssetTypeEnum {
/// Instantiate a new enum with the provided [value].
const AssetTypeEnum._(this.value);
enum AssetTypeEnum {
IMAGE._(r'IMAGE'),
VIDEO._(r'VIDEO'),
AUDIO._(r'AUDIO'),
OTHER._(r'OTHER'),
;
/// Instantiate a new enum with the provided value.
const AssetTypeEnum._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const IMAGE = AssetTypeEnum._(r'IMAGE');
static const VIDEO = AssetTypeEnum._(r'VIDEO');
static const AUDIO = AssetTypeEnum._(r'AUDIO');
static const OTHER = AssetTypeEnum._(r'OTHER');
/// List of all possible values in this [enum][AssetTypeEnum].
static const values = <AssetTypeEnum>[
IMAGE,
VIDEO,
AUDIO,
OTHER,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [AssetTypeEnum] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static AssetTypeEnum? fromJson(dynamic value) => AssetTypeEnumTypeTransformer().decode(value);
/// Returns a [List] containing instances of [AssetTypeEnum]
/// that were successfully decoded from the passed [JSON][json].
static List<AssetTypeEnum> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetTypeEnum>[];
if (json is List && json.isNotEmpty) {
@@ -59,9 +57,11 @@ class AssetTypeEnumTypeTransformer {
const AssetTypeEnumTypeTransformer._();
String encode(AssetTypeEnum data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(AssetTypeEnum data) => data._value;
/// Decodes a [dynamic value][data] to a AssetTypeEnum.
/// Returns the instance of [AssetTypeEnum] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -70,6 +70,9 @@ class AssetTypeEnumTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AssetTypeEnum? decode(dynamic data, {bool allowNull = true}) {
if (data is AssetTypeEnum) {
return data;
}
if (data != null) {
switch (data) {
case r'IMAGE': return AssetTypeEnum.IMAGE;
@@ -85,7 +88,7 @@ class AssetTypeEnumTypeTransformer {
return null;
}
/// Singleton [AssetTypeEnumTypeTransformer] instance.
/// The singleton instance of this transformer.
static AssetTypeEnumTypeTransformer? _instance;
}

View File

@@ -11,29 +11,29 @@
part of openapi.api;
/// Upload action
class AssetUploadAction {
/// Instantiate a new enum with the provided [value].
const AssetUploadAction._(this.value);
enum AssetUploadAction {
accept._(r'accept'),
reject._(r'reject'),
;
/// Instantiate a new enum with the provided value.
const AssetUploadAction._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const accept = AssetUploadAction._(r'accept');
static const reject = AssetUploadAction._(r'reject');
/// List of all possible values in this [enum][AssetUploadAction].
static const values = <AssetUploadAction>[
accept,
reject,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [AssetUploadAction] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static AssetUploadAction? fromJson(dynamic value) => AssetUploadActionTypeTransformer().decode(value);
/// Returns a [List] containing instances of [AssetUploadAction]
/// that were successfully decoded from the passed [JSON][json].
static List<AssetUploadAction> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetUploadAction>[];
if (json is List && json.isNotEmpty) {
@@ -55,9 +55,11 @@ class AssetUploadActionTypeTransformer {
const AssetUploadActionTypeTransformer._();
String encode(AssetUploadAction data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(AssetUploadAction data) => data._value;
/// Decodes a [dynamic value][data] to a AssetUploadAction.
/// Returns the instance of [AssetUploadAction] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -66,6 +68,9 @@ class AssetUploadActionTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AssetUploadAction? decode(dynamic data, {bool allowNull = true}) {
if (data is AssetUploadAction) {
return data;
}
if (data != null) {
switch (data) {
case r'accept': return AssetUploadAction.accept;
@@ -79,7 +84,7 @@ class AssetUploadActionTypeTransformer {
return null;
}
/// Singleton [AssetUploadActionTypeTransformer] instance.
/// The singleton instance of this transformer.
static AssetUploadActionTypeTransformer? _instance;
}

View File

@@ -11,33 +11,31 @@
part of openapi.api;
/// Asset visibility
class AssetVisibility {
/// Instantiate a new enum with the provided [value].
const AssetVisibility._(this.value);
enum AssetVisibility {
archive._(r'archive'),
timeline._(r'timeline'),
hidden._(r'hidden'),
locked._(r'locked'),
;
/// Instantiate a new enum with the provided value.
const AssetVisibility._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const archive = AssetVisibility._(r'archive');
static const timeline = AssetVisibility._(r'timeline');
static const hidden = AssetVisibility._(r'hidden');
static const locked = AssetVisibility._(r'locked');
/// List of all possible values in this [enum][AssetVisibility].
static const values = <AssetVisibility>[
archive,
timeline,
hidden,
locked,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [AssetVisibility] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static AssetVisibility? fromJson(dynamic value) => AssetVisibilityTypeTransformer().decode(value);
/// Returns a [List] containing instances of [AssetVisibility]
/// that were successfully decoded from the passed [JSON][json].
static List<AssetVisibility> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetVisibility>[];
if (json is List && json.isNotEmpty) {
@@ -59,9 +57,11 @@ class AssetVisibilityTypeTransformer {
const AssetVisibilityTypeTransformer._();
String encode(AssetVisibility data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(AssetVisibility data) => data._value;
/// Decodes a [dynamic value][data] to a AssetVisibility.
/// Returns the instance of [AssetVisibility] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -70,6 +70,9 @@ class AssetVisibilityTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AssetVisibility? decode(dynamic data, {bool allowNull = true}) {
if (data is AssetVisibility) {
return data;
}
if (data != null) {
switch (data) {
case r'archive': return AssetVisibility.archive;
@@ -85,7 +88,7 @@ class AssetVisibilityTypeTransformer {
return null;
}
/// Singleton [AssetVisibilityTypeTransformer] instance.
/// The singleton instance of this transformer.
static AssetVisibilityTypeTransformer? _instance;
}

View File

@@ -11,33 +11,31 @@
part of openapi.api;
/// Target audio codec
class AudioCodec {
/// Instantiate a new enum with the provided [value].
const AudioCodec._(this.value);
enum AudioCodec {
mp3._(r'mp3'),
aac._(r'aac'),
opus._(r'opus'),
pcmS16le._(r'pcm_s16le'),
;
/// Instantiate a new enum with the provided value.
const AudioCodec._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const mp3 = AudioCodec._(r'mp3');
static const aac = AudioCodec._(r'aac');
static const opus = AudioCodec._(r'opus');
static const pcmS16le = AudioCodec._(r'pcm_s16le');
/// List of all possible values in this [enum][AudioCodec].
static const values = <AudioCodec>[
mp3,
aac,
opus,
pcmS16le,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [AudioCodec] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static AudioCodec? fromJson(dynamic value) => AudioCodecTypeTransformer().decode(value);
/// Returns a [List] containing instances of [AudioCodec]
/// that were successfully decoded from the passed [JSON][json].
static List<AudioCodec> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AudioCodec>[];
if (json is List && json.isNotEmpty) {
@@ -59,9 +57,11 @@ class AudioCodecTypeTransformer {
const AudioCodecTypeTransformer._();
String encode(AudioCodec data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(AudioCodec data) => data._value;
/// Decodes a [dynamic value][data] to a AudioCodec.
/// Returns the instance of [AudioCodec] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -70,6 +70,9 @@ class AudioCodecTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
AudioCodec? decode(dynamic data, {bool allowNull = true}) {
if (data is AudioCodec) {
return data;
}
if (data != null) {
switch (data) {
case r'mp3': return AudioCodec.mp3;
@@ -85,7 +88,7 @@ class AudioCodecTypeTransformer {
return null;
}
/// Singleton [AudioCodecTypeTransformer] instance.
/// The singleton instance of this transformer.
static AudioCodecTypeTransformer? _instance;
}

View File

@@ -11,35 +11,32 @@
part of openapi.api;
/// Error reason
class BulkIdErrorReason {
/// Instantiate a new enum with the provided [value].
const BulkIdErrorReason._(this.value);
enum BulkIdErrorReason {
duplicate._(r'duplicate'),
noPermission._(r'no_permission'),
notFound._(r'not_found'),
unknown._(r'unknown'),
validation._(r'validation'),
;
/// Instantiate a new enum with the provided value.
const BulkIdErrorReason._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const duplicate = BulkIdErrorReason._(r'duplicate');
static const noPermission = BulkIdErrorReason._(r'no_permission');
static const notFound = BulkIdErrorReason._(r'not_found');
static const unknown = BulkIdErrorReason._(r'unknown');
static const validation = BulkIdErrorReason._(r'validation');
/// List of all possible values in this [enum][BulkIdErrorReason].
static const values = <BulkIdErrorReason>[
duplicate,
noPermission,
notFound,
unknown,
validation,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [BulkIdErrorReason] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static BulkIdErrorReason? fromJson(dynamic value) => BulkIdErrorReasonTypeTransformer().decode(value);
/// Returns a [List] containing instances of [BulkIdErrorReason]
/// that were successfully decoded from the passed [JSON][json].
static List<BulkIdErrorReason> listFromJson(dynamic json, {bool growable = false,}) {
final result = <BulkIdErrorReason>[];
if (json is List && json.isNotEmpty) {
@@ -61,9 +58,11 @@ class BulkIdErrorReasonTypeTransformer {
const BulkIdErrorReasonTypeTransformer._();
String encode(BulkIdErrorReason data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(BulkIdErrorReason data) => data._value;
/// Decodes a [dynamic value][data] to a BulkIdErrorReason.
/// Returns the instance of [BulkIdErrorReason] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -72,6 +71,9 @@ class BulkIdErrorReasonTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
BulkIdErrorReason? decode(dynamic data, {bool allowNull = true}) {
if (data is BulkIdErrorReason) {
return data;
}
if (data != null) {
switch (data) {
case r'duplicate': return BulkIdErrorReason.duplicate;
@@ -88,7 +90,7 @@ class BulkIdErrorReasonTypeTransformer {
return null;
}
/// Singleton [BulkIdErrorReasonTypeTransformer] instance.
/// The singleton instance of this transformer.
static BulkIdErrorReasonTypeTransformer? _instance;
}

View File

@@ -11,29 +11,29 @@
part of openapi.api;
/// Type of calendar heatmap
class CalendarHeatmapType {
/// Instantiate a new enum with the provided [value].
const CalendarHeatmapType._(this.value);
enum CalendarHeatmapType {
upload._(r'Upload'),
taken._(r'Taken'),
;
/// Instantiate a new enum with the provided value.
const CalendarHeatmapType._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const upload = CalendarHeatmapType._(r'Upload');
static const taken = CalendarHeatmapType._(r'Taken');
/// List of all possible values in this [enum][CalendarHeatmapType].
static const values = <CalendarHeatmapType>[
upload,
taken,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [CalendarHeatmapType] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static CalendarHeatmapType? fromJson(dynamic value) => CalendarHeatmapTypeTypeTransformer().decode(value);
/// Returns a [List] containing instances of [CalendarHeatmapType]
/// that were successfully decoded from the passed [JSON][json].
static List<CalendarHeatmapType> listFromJson(dynamic json, {bool growable = false,}) {
final result = <CalendarHeatmapType>[];
if (json is List && json.isNotEmpty) {
@@ -55,9 +55,11 @@ class CalendarHeatmapTypeTypeTransformer {
const CalendarHeatmapTypeTypeTransformer._();
String encode(CalendarHeatmapType data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(CalendarHeatmapType data) => data._value;
/// Decodes a [dynamic value][data] to a CalendarHeatmapType.
/// Returns the instance of [CalendarHeatmapType] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -66,6 +68,9 @@ class CalendarHeatmapTypeTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
CalendarHeatmapType? decode(dynamic data, {bool allowNull = true}) {
if (data is CalendarHeatmapType) {
return data;
}
if (data != null) {
switch (data) {
case r'Upload': return CalendarHeatmapType.upload;
@@ -79,7 +84,7 @@ class CalendarHeatmapTypeTypeTransformer {
return null;
}
/// Singleton [CalendarHeatmapTypeTypeTransformer] instance.
/// The singleton instance of this transformer.
static CalendarHeatmapTypeTypeTransformer? _instance;
}

View File

@@ -11,29 +11,29 @@
part of openapi.api;
/// Colorspace
class Colorspace {
/// Instantiate a new enum with the provided [value].
const Colorspace._(this.value);
enum Colorspace {
srgb._(r'srgb'),
p3._(r'p3'),
;
/// Instantiate a new enum with the provided value.
const Colorspace._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const srgb = Colorspace._(r'srgb');
static const p3 = Colorspace._(r'p3');
/// List of all possible values in this [enum][Colorspace].
static const values = <Colorspace>[
srgb,
p3,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [Colorspace] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static Colorspace? fromJson(dynamic value) => ColorspaceTypeTransformer().decode(value);
/// Returns a [List] containing instances of [Colorspace]
/// that were successfully decoded from the passed [JSON][json].
static List<Colorspace> listFromJson(dynamic json, {bool growable = false,}) {
final result = <Colorspace>[];
if (json is List && json.isNotEmpty) {
@@ -55,9 +55,11 @@ class ColorspaceTypeTransformer {
const ColorspaceTypeTransformer._();
String encode(Colorspace data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(Colorspace data) => data._value;
/// Decodes a [dynamic value][data] to a Colorspace.
/// Returns the instance of [Colorspace] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -66,6 +68,9 @@ class ColorspaceTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
Colorspace? decode(dynamic data, {bool allowNull = true}) {
if (data is Colorspace) {
return data;
}
if (data != null) {
switch (data) {
case r'srgb': return Colorspace.srgb;
@@ -79,7 +84,7 @@ class ColorspaceTypeTransformer {
return null;
}
/// Singleton [ColorspaceTypeTransformer] instance.
/// The singleton instance of this transformer.
static ColorspaceTypeTransformer? _instance;
}

View File

@@ -11,31 +11,30 @@
part of openapi.api;
/// CQ mode
class CQMode {
/// Instantiate a new enum with the provided [value].
const CQMode._(this.value);
enum CQMode {
auto._(r'auto'),
cqp._(r'cqp'),
icq._(r'icq'),
;
/// Instantiate a new enum with the provided value.
const CQMode._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const auto = CQMode._(r'auto');
static const cqp = CQMode._(r'cqp');
static const icq = CQMode._(r'icq');
/// List of all possible values in this [enum][CQMode].
static const values = <CQMode>[
auto,
cqp,
icq,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [CQMode] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static CQMode? fromJson(dynamic value) => CQModeTypeTransformer().decode(value);
/// Returns a [List] containing instances of [CQMode]
/// that were successfully decoded from the passed [JSON][json].
static List<CQMode> listFromJson(dynamic json, {bool growable = false,}) {
final result = <CQMode>[];
if (json is List && json.isNotEmpty) {
@@ -57,9 +56,11 @@ class CQModeTypeTransformer {
const CQModeTypeTransformer._();
String encode(CQMode data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(CQMode data) => data._value;
/// Decodes a [dynamic value][data] to a CQMode.
/// Returns the instance of [CQMode] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -68,6 +69,9 @@ class CQModeTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
CQMode? decode(dynamic data, {bool allowNull = true}) {
if (data is CQMode) {
return data;
}
if (data != null) {
switch (data) {
case r'auto': return CQMode.auto;
@@ -82,7 +86,7 @@ class CQModeTypeTransformer {
return null;
}
/// Singleton [CQModeTypeTransformer] instance.
/// The singleton instance of this transformer.
static CQModeTypeTransformer? _instance;
}

View File

@@ -11,35 +11,32 @@
part of openapi.api;
/// HLS video resolution
class HlsVideoResolution {
/// Instantiate a new enum with the provided [value].
const HlsVideoResolution._(this.value);
enum HlsVideoResolution {
number480._(480),
number720._(720),
number1080._(1080),
number1440._(1440),
number2160._(2160),
;
/// Instantiate a new enum with the provided value.
const HlsVideoResolution._(this._value);
/// The underlying value of this enum member.
final int value;
final int _value;
@override
String toString() => value.toString();
String toString() => _value.toString();
int toJson() => value;
static const number480 = HlsVideoResolution._(480);
static const number720 = HlsVideoResolution._(720);
static const number1080 = HlsVideoResolution._(1080);
static const number1440 = HlsVideoResolution._(1440);
static const number2160 = HlsVideoResolution._(2160);
/// List of all possible values in this [enum][HlsVideoResolution].
static const values = <HlsVideoResolution>[
number480,
number720,
number1080,
number1440,
number2160,
];
/// Encodes this enum as a value suitable for JSON.
int toJson() => _value;
/// Returns the instance of [HlsVideoResolution] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static HlsVideoResolution? fromJson(dynamic value) => HlsVideoResolutionTypeTransformer().decode(value);
/// Returns a [List] containing instances of [HlsVideoResolution]
/// that were successfully decoded from the passed [JSON][json].
static List<HlsVideoResolution> listFromJson(dynamic json, {bool growable = false,}) {
final result = <HlsVideoResolution>[];
if (json is List && json.isNotEmpty) {
@@ -61,9 +58,11 @@ class HlsVideoResolutionTypeTransformer {
const HlsVideoResolutionTypeTransformer._();
int encode(HlsVideoResolution data) => data.value;
/// Encodes this enum as a value suitable for JSON.
int encode(HlsVideoResolution data) => data._value;
/// Decodes a [dynamic value][data] to a HlsVideoResolution.
/// Returns the instance of [HlsVideoResolution] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -72,6 +71,9 @@ class HlsVideoResolutionTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
HlsVideoResolution? decode(dynamic data, {bool allowNull = true}) {
if (data is HlsVideoResolution) {
return data;
}
if (data != null) {
switch (data) {
case 480: return HlsVideoResolution.number480;
@@ -88,7 +90,7 @@ class HlsVideoResolutionTypeTransformer {
return null;
}
/// Singleton [HlsVideoResolutionTypeTransformer] instance.
/// The singleton instance of this transformer.
static HlsVideoResolutionTypeTransformer? _instance;
}

View File

@@ -11,29 +11,29 @@
part of openapi.api;
/// Image format
class ImageFormat {
/// Instantiate a new enum with the provided [value].
const ImageFormat._(this.value);
enum ImageFormat {
jpeg._(r'jpeg'),
webp._(r'webp'),
;
/// Instantiate a new enum with the provided value.
const ImageFormat._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const jpeg = ImageFormat._(r'jpeg');
static const webp = ImageFormat._(r'webp');
/// List of all possible values in this [enum][ImageFormat].
static const values = <ImageFormat>[
jpeg,
webp,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [ImageFormat] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static ImageFormat? fromJson(dynamic value) => ImageFormatTypeTransformer().decode(value);
/// Returns a [List] containing instances of [ImageFormat]
/// that were successfully decoded from the passed [JSON][json].
static List<ImageFormat> listFromJson(dynamic json, {bool growable = false,}) {
final result = <ImageFormat>[];
if (json is List && json.isNotEmpty) {
@@ -55,9 +55,11 @@ class ImageFormatTypeTransformer {
const ImageFormatTypeTransformer._();
String encode(ImageFormat data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(ImageFormat data) => data._value;
/// Decodes a [dynamic value][data] to a ImageFormat.
/// Returns the instance of [ImageFormat] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -66,6 +68,9 @@ class ImageFormatTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
ImageFormat? decode(dynamic data, {bool allowNull = true}) {
if (data is ImageFormat) {
return data;
}
if (data != null) {
switch (data) {
case r'jpeg': return ImageFormat.jpeg;
@@ -79,7 +84,7 @@ class ImageFormatTypeTransformer {
return null;
}
/// Singleton [ImageFormatTypeTransformer] instance.
/// The singleton instance of this transformer.
static ImageFormatTypeTransformer? _instance;
}

View File

@@ -11,31 +11,30 @@
part of openapi.api;
/// Integrity report type
class IntegrityReport {
/// Instantiate a new enum with the provided [value].
const IntegrityReport._(this.value);
enum IntegrityReport {
untrackedFile._(r'untracked_file'),
missingFile._(r'missing_file'),
checksumMismatch._(r'checksum_mismatch'),
;
/// Instantiate a new enum with the provided value.
const IntegrityReport._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const untrackedFile = IntegrityReport._(r'untracked_file');
static const missingFile = IntegrityReport._(r'missing_file');
static const checksumMismatch = IntegrityReport._(r'checksum_mismatch');
/// List of all possible values in this [enum][IntegrityReport].
static const values = <IntegrityReport>[
untrackedFile,
missingFile,
checksumMismatch,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [IntegrityReport] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static IntegrityReport? fromJson(dynamic value) => IntegrityReportTypeTransformer().decode(value);
/// Returns a [List] containing instances of [IntegrityReport]
/// that were successfully decoded from the passed [JSON][json].
static List<IntegrityReport> listFromJson(dynamic json, {bool growable = false,}) {
final result = <IntegrityReport>[];
if (json is List && json.isNotEmpty) {
@@ -57,9 +56,11 @@ class IntegrityReportTypeTransformer {
const IntegrityReportTypeTransformer._();
String encode(IntegrityReport data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(IntegrityReport data) => data._value;
/// Decodes a [dynamic value][data] to a IntegrityReport.
/// Returns the instance of [IntegrityReport] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -68,6 +69,9 @@ class IntegrityReportTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
IntegrityReport? decode(dynamic data, {bool allowNull = true}) {
if (data is IntegrityReport) {
return data;
}
if (data != null) {
switch (data) {
case r'untracked_file': return IntegrityReport.untrackedFile;
@@ -82,7 +86,7 @@ class IntegrityReportTypeTransformer {
return null;
}
/// Singleton [IntegrityReportTypeTransformer] instance.
/// The singleton instance of this transformer.
static IntegrityReportTypeTransformer? _instance;
}

View File

@@ -11,157 +11,93 @@
part of openapi.api;
/// Job name
class JobName {
/// Instantiate a new enum with the provided [value].
const JobName._(this.value);
enum JobName {
assetDelete._(r'AssetDelete'),
assetDeleteCheck._(r'AssetDeleteCheck'),
assetDetectFacesQueueAll._(r'AssetDetectFacesQueueAll'),
assetDetectFaces._(r'AssetDetectFaces'),
assetDetectDuplicatesQueueAll._(r'AssetDetectDuplicatesQueueAll'),
assetDetectDuplicates._(r'AssetDetectDuplicates'),
assetEditThumbnailGeneration._(r'AssetEditThumbnailGeneration'),
assetEncodeVideoQueueAll._(r'AssetEncodeVideoQueueAll'),
assetEncodeVideo._(r'AssetEncodeVideo'),
assetEmptyTrash._(r'AssetEmptyTrash'),
assetExtractMetadataQueueAll._(r'AssetExtractMetadataQueueAll'),
assetExtractMetadata._(r'AssetExtractMetadata'),
assetFileMigration._(r'AssetFileMigration'),
assetGenerateThumbnailsQueueAll._(r'AssetGenerateThumbnailsQueueAll'),
assetGenerateThumbnails._(r'AssetGenerateThumbnails'),
auditTableCleanup._(r'AuditTableCleanup'),
databaseBackup._(r'DatabaseBackup'),
facialRecognitionQueueAll._(r'FacialRecognitionQueueAll'),
facialRecognition._(r'FacialRecognition'),
fileDelete._(r'FileDelete'),
fileMigrationQueueAll._(r'FileMigrationQueueAll'),
libraryDeleteCheck._(r'LibraryDeleteCheck'),
libraryDelete._(r'LibraryDelete'),
libraryRemoveAsset._(r'LibraryRemoveAsset'),
libraryScanAssetsQueueAll._(r'LibraryScanAssetsQueueAll'),
librarySyncAssets._(r'LibrarySyncAssets'),
librarySyncFilesQueueAll._(r'LibrarySyncFilesQueueAll'),
librarySyncFiles._(r'LibrarySyncFiles'),
libraryScanQueueAll._(r'LibraryScanQueueAll'),
hlsSessionCleanup._(r'HlsSessionCleanup'),
memoryCleanup._(r'MemoryCleanup'),
memoryGenerate._(r'MemoryGenerate'),
notificationsCleanup._(r'NotificationsCleanup'),
notifyUserSignup._(r'NotifyUserSignup'),
notifyAlbumInvite._(r'NotifyAlbumInvite'),
notifyAlbumUpdate._(r'NotifyAlbumUpdate'),
userDelete._(r'UserDelete'),
userDeleteCheck._(r'UserDeleteCheck'),
userSyncUsage._(r'UserSyncUsage'),
personCleanup._(r'PersonCleanup'),
personFileMigration._(r'PersonFileMigration'),
personGenerateThumbnail._(r'PersonGenerateThumbnail'),
sessionCleanup._(r'SessionCleanup'),
sendMail._(r'SendMail'),
sidecarQueueAll._(r'SidecarQueueAll'),
sidecarCheck._(r'SidecarCheck'),
sidecarWrite._(r'SidecarWrite'),
smartSearchQueueAll._(r'SmartSearchQueueAll'),
smartSearch._(r'SmartSearch'),
storageTemplateMigration._(r'StorageTemplateMigration'),
storageTemplateMigrationSingle._(r'StorageTemplateMigrationSingle'),
tagCleanup._(r'TagCleanup'),
versionCheck._(r'VersionCheck'),
ocrQueueAll._(r'OcrQueueAll'),
ocr._(r'Ocr'),
workflowAssetTrigger._(r'WorkflowAssetTrigger'),
integrityUntrackedFilesQueueAll._(r'IntegrityUntrackedFilesQueueAll'),
integrityUntrackedFiles._(r'IntegrityUntrackedFiles'),
integrityUntrackedRefresh._(r'IntegrityUntrackedRefresh'),
integrityMissingFilesQueueAll._(r'IntegrityMissingFilesQueueAll'),
integrityMissingFiles._(r'IntegrityMissingFiles'),
integrityMissingFilesRefresh._(r'IntegrityMissingFilesRefresh'),
integrityChecksumFiles._(r'IntegrityChecksumFiles'),
integrityChecksumFilesRefresh._(r'IntegrityChecksumFilesRefresh'),
integrityDeleteReportType._(r'IntegrityDeleteReportType'),
integrityDeleteReports._(r'IntegrityDeleteReports'),
;
/// Instantiate a new enum with the provided value.
const JobName._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const assetDelete = JobName._(r'AssetDelete');
static const assetDeleteCheck = JobName._(r'AssetDeleteCheck');
static const assetDetectFacesQueueAll = JobName._(r'AssetDetectFacesQueueAll');
static const assetDetectFaces = JobName._(r'AssetDetectFaces');
static const assetDetectDuplicatesQueueAll = JobName._(r'AssetDetectDuplicatesQueueAll');
static const assetDetectDuplicates = JobName._(r'AssetDetectDuplicates');
static const assetEditThumbnailGeneration = JobName._(r'AssetEditThumbnailGeneration');
static const assetEncodeVideoQueueAll = JobName._(r'AssetEncodeVideoQueueAll');
static const assetEncodeVideo = JobName._(r'AssetEncodeVideo');
static const assetEmptyTrash = JobName._(r'AssetEmptyTrash');
static const assetExtractMetadataQueueAll = JobName._(r'AssetExtractMetadataQueueAll');
static const assetExtractMetadata = JobName._(r'AssetExtractMetadata');
static const assetFileMigration = JobName._(r'AssetFileMigration');
static const assetGenerateThumbnailsQueueAll = JobName._(r'AssetGenerateThumbnailsQueueAll');
static const assetGenerateThumbnails = JobName._(r'AssetGenerateThumbnails');
static const auditTableCleanup = JobName._(r'AuditTableCleanup');
static const databaseBackup = JobName._(r'DatabaseBackup');
static const facialRecognitionQueueAll = JobName._(r'FacialRecognitionQueueAll');
static const facialRecognition = JobName._(r'FacialRecognition');
static const fileDelete = JobName._(r'FileDelete');
static const fileMigrationQueueAll = JobName._(r'FileMigrationQueueAll');
static const libraryDeleteCheck = JobName._(r'LibraryDeleteCheck');
static const libraryDelete = JobName._(r'LibraryDelete');
static const libraryRemoveAsset = JobName._(r'LibraryRemoveAsset');
static const libraryScanAssetsQueueAll = JobName._(r'LibraryScanAssetsQueueAll');
static const librarySyncAssets = JobName._(r'LibrarySyncAssets');
static const librarySyncFilesQueueAll = JobName._(r'LibrarySyncFilesQueueAll');
static const librarySyncFiles = JobName._(r'LibrarySyncFiles');
static const libraryScanQueueAll = JobName._(r'LibraryScanQueueAll');
static const hlsSessionCleanup = JobName._(r'HlsSessionCleanup');
static const memoryCleanup = JobName._(r'MemoryCleanup');
static const memoryGenerate = JobName._(r'MemoryGenerate');
static const notificationsCleanup = JobName._(r'NotificationsCleanup');
static const notifyUserSignup = JobName._(r'NotifyUserSignup');
static const notifyAlbumInvite = JobName._(r'NotifyAlbumInvite');
static const notifyAlbumUpdate = JobName._(r'NotifyAlbumUpdate');
static const userDelete = JobName._(r'UserDelete');
static const userDeleteCheck = JobName._(r'UserDeleteCheck');
static const userSyncUsage = JobName._(r'UserSyncUsage');
static const personCleanup = JobName._(r'PersonCleanup');
static const personFileMigration = JobName._(r'PersonFileMigration');
static const personGenerateThumbnail = JobName._(r'PersonGenerateThumbnail');
static const sessionCleanup = JobName._(r'SessionCleanup');
static const sendMail = JobName._(r'SendMail');
static const sidecarQueueAll = JobName._(r'SidecarQueueAll');
static const sidecarCheck = JobName._(r'SidecarCheck');
static const sidecarWrite = JobName._(r'SidecarWrite');
static const smartSearchQueueAll = JobName._(r'SmartSearchQueueAll');
static const smartSearch = JobName._(r'SmartSearch');
static const storageTemplateMigration = JobName._(r'StorageTemplateMigration');
static const storageTemplateMigrationSingle = JobName._(r'StorageTemplateMigrationSingle');
static const tagCleanup = JobName._(r'TagCleanup');
static const versionCheck = JobName._(r'VersionCheck');
static const ocrQueueAll = JobName._(r'OcrQueueAll');
static const ocr = JobName._(r'Ocr');
static const workflowAssetTrigger = JobName._(r'WorkflowAssetTrigger');
static const integrityUntrackedFilesQueueAll = JobName._(r'IntegrityUntrackedFilesQueueAll');
static const integrityUntrackedFiles = JobName._(r'IntegrityUntrackedFiles');
static const integrityUntrackedRefresh = JobName._(r'IntegrityUntrackedRefresh');
static const integrityMissingFilesQueueAll = JobName._(r'IntegrityMissingFilesQueueAll');
static const integrityMissingFiles = JobName._(r'IntegrityMissingFiles');
static const integrityMissingFilesRefresh = JobName._(r'IntegrityMissingFilesRefresh');
static const integrityChecksumFiles = JobName._(r'IntegrityChecksumFiles');
static const integrityChecksumFilesRefresh = JobName._(r'IntegrityChecksumFilesRefresh');
static const integrityDeleteReportType = JobName._(r'IntegrityDeleteReportType');
static const integrityDeleteReports = JobName._(r'IntegrityDeleteReports');
/// List of all possible values in this [enum][JobName].
static const values = <JobName>[
assetDelete,
assetDeleteCheck,
assetDetectFacesQueueAll,
assetDetectFaces,
assetDetectDuplicatesQueueAll,
assetDetectDuplicates,
assetEditThumbnailGeneration,
assetEncodeVideoQueueAll,
assetEncodeVideo,
assetEmptyTrash,
assetExtractMetadataQueueAll,
assetExtractMetadata,
assetFileMigration,
assetGenerateThumbnailsQueueAll,
assetGenerateThumbnails,
auditTableCleanup,
databaseBackup,
facialRecognitionQueueAll,
facialRecognition,
fileDelete,
fileMigrationQueueAll,
libraryDeleteCheck,
libraryDelete,
libraryRemoveAsset,
libraryScanAssetsQueueAll,
librarySyncAssets,
librarySyncFilesQueueAll,
librarySyncFiles,
libraryScanQueueAll,
hlsSessionCleanup,
memoryCleanup,
memoryGenerate,
notificationsCleanup,
notifyUserSignup,
notifyAlbumInvite,
notifyAlbumUpdate,
userDelete,
userDeleteCheck,
userSyncUsage,
personCleanup,
personFileMigration,
personGenerateThumbnail,
sessionCleanup,
sendMail,
sidecarQueueAll,
sidecarCheck,
sidecarWrite,
smartSearchQueueAll,
smartSearch,
storageTemplateMigration,
storageTemplateMigrationSingle,
tagCleanup,
versionCheck,
ocrQueueAll,
ocr,
workflowAssetTrigger,
integrityUntrackedFilesQueueAll,
integrityUntrackedFiles,
integrityUntrackedRefresh,
integrityMissingFilesQueueAll,
integrityMissingFiles,
integrityMissingFilesRefresh,
integrityChecksumFiles,
integrityChecksumFilesRefresh,
integrityDeleteReportType,
integrityDeleteReports,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [JobName] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static JobName? fromJson(dynamic value) => JobNameTypeTransformer().decode(value);
/// Returns a [List] containing instances of [JobName]
/// that were successfully decoded from the passed [JSON][json].
static List<JobName> listFromJson(dynamic json, {bool growable = false,}) {
final result = <JobName>[];
if (json is List && json.isNotEmpty) {
@@ -183,9 +119,11 @@ class JobNameTypeTransformer {
const JobNameTypeTransformer._();
String encode(JobName data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(JobName data) => data._value;
/// Decodes a [dynamic value][data] to a JobName.
/// Returns the instance of [JobName] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -194,6 +132,9 @@ class JobNameTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
JobName? decode(dynamic data, {bool allowNull = true}) {
if (data is JobName) {
return data;
}
if (data != null) {
switch (data) {
case r'AssetDelete': return JobName.assetDelete;
@@ -271,7 +212,7 @@ class JobNameTypeTransformer {
return null;
}
/// Singleton [JobNameTypeTransformer] instance.
/// The singleton instance of this transformer.
static JobNameTypeTransformer? _instance;
}

View File

@@ -11,37 +11,33 @@
part of openapi.api;
/// Log level
class LogLevel {
/// Instantiate a new enum with the provided [value].
const LogLevel._(this.value);
enum LogLevel {
verbose._(r'verbose'),
debug._(r'debug'),
log._(r'log'),
warn._(r'warn'),
error._(r'error'),
fatal._(r'fatal'),
;
/// Instantiate a new enum with the provided value.
const LogLevel._(this._value);
/// The underlying value of this enum member.
final String value;
final String _value;
@override
String toString() => value;
String toString() => _value;
String toJson() => value;
static const verbose = LogLevel._(r'verbose');
static const debug = LogLevel._(r'debug');
static const log = LogLevel._(r'log');
static const warn = LogLevel._(r'warn');
static const error = LogLevel._(r'error');
static const fatal = LogLevel._(r'fatal');
/// List of all possible values in this [enum][LogLevel].
static const values = <LogLevel>[
verbose,
debug,
log,
warn,
error,
fatal,
];
/// Encodes this enum as a value suitable for JSON.
String toJson() => _value;
/// Returns the instance of [LogLevel] that was successfully decoded
/// from the passed [value] on success, null otherwise.
static LogLevel? fromJson(dynamic value) => LogLevelTypeTransformer().decode(value);
/// Returns a [List] containing instances of [LogLevel]
/// that were successfully decoded from the passed [JSON][json].
static List<LogLevel> listFromJson(dynamic json, {bool growable = false,}) {
final result = <LogLevel>[];
if (json is List && json.isNotEmpty) {
@@ -63,9 +59,11 @@ class LogLevelTypeTransformer {
const LogLevelTypeTransformer._();
String encode(LogLevel data) => data.value;
/// Encodes this enum as a value suitable for JSON.
String encode(LogLevel data) => data._value;
/// Decodes a [dynamic value][data] to a LogLevel.
/// Returns the instance of [LogLevel] that was successfully decoded
/// from the passed [data] value on success, null otherwise.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
@@ -74,6 +72,9 @@ class LogLevelTypeTransformer {
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
LogLevel? decode(dynamic data, {bool allowNull = true}) {
if (data is LogLevel) {
return data;
}
if (data != null) {
switch (data) {
case r'verbose': return LogLevel.verbose;
@@ -91,7 +92,7 @@ class LogLevelTypeTransformer {
return null;
}
/// Singleton [LogLevelTypeTransformer] instance.
/// The singleton instance of this transformer.
static LogLevelTypeTransformer? _instance;
}

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