mirror of
https://github.com/immich-app/immich.git
synced 2026-06-04 13:15:22 -04:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1fb1eccc46 | |||
| 1481e416f8 | |||
| b41e860b43 | |||
| fbdf2a8aab | |||
| 15c9ee2dd9 | |||
| 44284fd7d9 | |||
| 53c5ff1cd7 | |||
| 59af9e087b | |||
| 6e91e2e202 | |||
| bf6ed541dd | |||
| 855817514c | |||
| d5ad35ea52 | |||
| e63213d774 | |||
| 0be1ffade6 | |||
| 1a04caee29 | |||
| 3ace578fc0 | |||
| 25c573bc7a | |||
| 10bb83cf75 | |||
| 10b53b525d | |||
| 8db61d341f | |||
| eadb2f89af | |||
| f2f11b1924 | |||
| 141be5cbc9 | |||
| e81faa1dbf | |||
| 0beb1f9e7a | |||
| e07a91f9c2 | |||
| c6defd453b | |||
| 4e0e1b2c5c | |||
| 84c3980844 | |||
| e50579eefc | |||
| 0cb153a971 | |||
| 12d23e987b | |||
| 9486eed97e | |||
| 913e939606 | |||
| 9be01e79f7 | |||
| 2d09853c3d | |||
| 91831f68e2 |
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "2.5.1",
|
||||
"version": "2.5.2",
|
||||
"description": "Command Line Interface (CLI) for Immich",
|
||||
"type": "module",
|
||||
"exports": "./dist/index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tools]
|
||||
terragrunt = "0.93.10"
|
||||
opentofu = "1.10.7"
|
||||
terragrunt = "0.98.0"
|
||||
opentofu = "1.11.4"
|
||||
|
||||
[tasks."tg:fmt"]
|
||||
run = "terragrunt hclfmt"
|
||||
|
||||
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
||||
[
|
||||
{
|
||||
"label": "v2.5.1",
|
||||
"url": "https://docs.v2.5.1.archive.immich.app"
|
||||
"label": "v2.5.2",
|
||||
"url": "https://docs.v2.5.2.archive.immich.app"
|
||||
},
|
||||
{
|
||||
"label": "v2.4.1",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "2.5.1",
|
||||
"version": "2.5.2",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { expect, test } from '@playwright/test';
|
||||
import {
|
||||
Changes,
|
||||
createDefaultTimelineConfig,
|
||||
generateTimelineData,
|
||||
TimelineAssetConfig,
|
||||
TimelineData,
|
||||
} from 'src/generators/timeline';
|
||||
import { setupBaseMockApiRoutes } from 'src/mock-network/base-network';
|
||||
import { setupTimelineMockApiRoutes, TimelineTestContext } from 'src/mock-network/timeline-network';
|
||||
import { assetViewerUtils } from 'src/web/specs/timeline/utils';
|
||||
|
||||
const buildSearchUrl = (assetId: string) => {
|
||||
const searchQuery = encodeURIComponent(JSON.stringify({ originalFileName: 'test' }));
|
||||
return `/search/photos/${assetId}?query=${searchQuery}`;
|
||||
};
|
||||
|
||||
test.describe.configure({ mode: 'parallel' });
|
||||
test.describe('search gallery-viewer', () => {
|
||||
let adminUserId: string;
|
||||
let timelineRestData: TimelineData;
|
||||
const assets: TimelineAssetConfig[] = [];
|
||||
const testContext = new TimelineTestContext();
|
||||
const changes: Changes = {
|
||||
albumAdditions: [],
|
||||
assetDeletions: [],
|
||||
assetArchivals: [],
|
||||
assetFavorites: [],
|
||||
};
|
||||
|
||||
test.beforeAll(async () => {
|
||||
adminUserId = faker.string.uuid();
|
||||
testContext.adminId = adminUserId;
|
||||
timelineRestData = generateTimelineData({ ...createDefaultTimelineConfig(), ownerId: adminUserId });
|
||||
for (const timeBucket of timelineRestData.buckets.values()) {
|
||||
assets.push(...timeBucket);
|
||||
}
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ context }) => {
|
||||
await setupBaseMockApiRoutes(context, adminUserId);
|
||||
await setupTimelineMockApiRoutes(context, timelineRestData, changes, testContext);
|
||||
|
||||
await context.route('**/api/search/metadata', async (route, request) => {
|
||||
if (request.method() === 'POST') {
|
||||
const searchAssets = assets.slice(0, 5).filter((asset) => !changes.assetDeletions.includes(asset.id));
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
json: {
|
||||
albums: { total: 0, count: 0, items: [], facets: [] },
|
||||
assets: {
|
||||
total: searchAssets.length,
|
||||
count: searchAssets.length,
|
||||
items: searchAssets,
|
||||
facets: [],
|
||||
nextPage: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
await route.fallback();
|
||||
});
|
||||
});
|
||||
|
||||
test.afterEach(() => {
|
||||
testContext.slowBucket = false;
|
||||
changes.albumAdditions = [];
|
||||
changes.assetDeletions = [];
|
||||
changes.assetArchivals = [];
|
||||
changes.assetFavorites = [];
|
||||
});
|
||||
|
||||
test.describe('/search/photos/:id', () => {
|
||||
test('Deleting a photo advances to the next photo', async ({ page }) => {
|
||||
const asset = assets[0];
|
||||
await page.goto(buildSearchUrl(asset.id));
|
||||
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||
await page.getByLabel('Delete').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[1]);
|
||||
});
|
||||
|
||||
test('Deleting two photos in a row advances to the next photo each time', async ({ page }) => {
|
||||
const asset = assets[0];
|
||||
await page.goto(buildSearchUrl(asset.id));
|
||||
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||
await page.getByLabel('Delete').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[1]);
|
||||
await page.getByLabel('Delete').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[2]);
|
||||
});
|
||||
|
||||
test('Navigating backward then deleting advances to the next photo', async ({ page }) => {
|
||||
const asset = assets[1];
|
||||
await page.goto(buildSearchUrl(asset.id));
|
||||
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||
await page.getByLabel('View previous asset').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[0]);
|
||||
await page.getByLabel('View next asset').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||
await page.getByLabel('Delete').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[2]);
|
||||
});
|
||||
|
||||
test('Deleting the last photo advances to the previous photo', async ({ page }) => {
|
||||
const lastAsset = assets[4];
|
||||
await page.goto(buildSearchUrl(lastAsset.id));
|
||||
await assetViewerUtils.waitForViewerLoad(page, lastAsset);
|
||||
await expect(page.getByLabel('View next asset')).toHaveCount(0);
|
||||
await page.getByLabel('Delete').click();
|
||||
await assetViewerUtils.waitForViewerLoad(page, assets[3]);
|
||||
await expect(page.getByLabel('View previous asset')).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-i18n",
|
||||
"version": "2.5.1",
|
||||
"version": "2.5.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"format": "prettier --check .",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "immich-ml"
|
||||
version = "2.5.1"
|
||||
version = "2.5.2"
|
||||
description = ""
|
||||
authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }]
|
||||
requires-python = ">=3.11,<4.0"
|
||||
|
||||
Generated
+1
-1
@@ -919,7 +919,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "immich-ml"
|
||||
version = "2.4.1"
|
||||
version = "2.5.2"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiocache" },
|
||||
|
||||
@@ -75,7 +75,7 @@ if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then
|
||||
pnpm --prefix server run build
|
||||
( cd ./open-api && bash ./bin/generate-open-api.sh )
|
||||
|
||||
uvx --from=toml-cli toml set --toml-path=machine-learning/pyproject.toml project.version "$NEXT_SERVER"
|
||||
uv version --directory machine-learning "$NEXT_SERVER"
|
||||
|
||||
./misc/release/archive-version.js "$NEXT_SERVER"
|
||||
fi
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
experimental_monorepo_root = true
|
||||
|
||||
[monorepo]
|
||||
config_roots = [
|
||||
"plugins",
|
||||
"server",
|
||||
"cli",
|
||||
"deployment",
|
||||
"mobile",
|
||||
"e2e",
|
||||
"web",
|
||||
"docs",
|
||||
".github",
|
||||
]
|
||||
|
||||
[tools]
|
||||
node = "24.13.0"
|
||||
flutter = "3.35.7"
|
||||
pnpm = "10.28.0"
|
||||
terragrunt = "0.93.10"
|
||||
opentofu = "1.10.7"
|
||||
java = "25.0.1"
|
||||
terragrunt = "0.98.0"
|
||||
opentofu = "1.11.4"
|
||||
java = "21.0.2"
|
||||
|
||||
[tools."github:CQLabs/homebrew-dcm"]
|
||||
version = "1.30.0"
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"flutter": "3.35.7"
|
||||
}
|
||||
+1
-4
@@ -55,8 +55,5 @@ default.isar
|
||||
default.isar.lock
|
||||
libisar.so
|
||||
|
||||
# FVM Version
|
||||
.fvm/
|
||||
|
||||
# Translation file
|
||||
lib/generated/
|
||||
lib/generated/
|
||||
|
||||
Vendored
+3
-1
@@ -2,7 +2,9 @@
|
||||
"dart.flutterSdkPath": ".fvm/versions/3.35.7",
|
||||
"dart.lineLength": 120,
|
||||
"[dart]": {
|
||||
"editor.rulers": [120]
|
||||
"editor.rulers": [
|
||||
120
|
||||
]
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/.fvm": true
|
||||
|
||||
+7
-5
@@ -4,10 +4,12 @@ The Immich mobile app is a Flutter-based solution leveraging the Isar Database f
|
||||
|
||||
## Setup
|
||||
|
||||
1. Setup Flutter toolchain using FVM.
|
||||
2. Run `flutter pub get` to install the dependencies.
|
||||
3. Run `make translation` to generate the translation file.
|
||||
4. Run `fvm flutter run` to start the app.
|
||||
1. [Install mise](https://mise.jdx.dev/installing-mise.html).
|
||||
2. Change to the immich directory and trust the mise config with `mise trust`.
|
||||
3. Install tools with mise: `mise install`.
|
||||
4. Run `flutter pub get` to install the dependencies.
|
||||
5. Run `make translation` to generate the translation file.
|
||||
6. Run `flutter run` to start the app.
|
||||
|
||||
## Translation
|
||||
|
||||
@@ -29,7 +31,7 @@ dcm analyze lib
|
||||
```
|
||||
|
||||
[DCM](https://dcm.dev/) is a vendor tool that needs to be downloaded manually to run locally.
|
||||
Immich was provided an open source license.
|
||||
Immich was provided an open source license.
|
||||
To use it, it is important that you do not have an active free tier license (can be verified with `dcm license`).
|
||||
If you have write-access to the Immich repository directly, running dcm in your clone should just work.
|
||||
If you are working on a clone of a fork, you need to connect to the main Immich repository as remote first:
|
||||
|
||||
@@ -35,8 +35,8 @@ platform :android do
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 3032,
|
||||
"android.injected.version.name" => "2.5.1",
|
||||
"android.injected.version.code" => 3033,
|
||||
"android.injected.version.name" => "2.5.2",
|
||||
}
|
||||
)
|
||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||
|
||||
@@ -741,7 +741,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 233;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -885,7 +885,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 233;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -915,7 +915,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 233;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -949,7 +949,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 233;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -992,7 +992,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 233;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -1032,7 +1032,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 233;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -1071,7 +1071,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 233;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@@ -1115,7 +1115,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 233;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@@ -1156,7 +1156,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 233;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
|
||||
@@ -79,6 +79,7 @@ class RemoteImageApiDelegate: NSObject, URLSessionDataDelegate {
|
||||
kCGImageSourceShouldCache: false,
|
||||
kCGImageSourceShouldCacheImmediately: true,
|
||||
kCGImageSourceCreateThumbnailWithTransform: true,
|
||||
kCGImageSourceCreateThumbnailFromImageAlways: true
|
||||
] as CFDictionary
|
||||
|
||||
func urlSession(
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.5.1</string>
|
||||
<string>2.5.2</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@@ -107,7 +107,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>233</string>
|
||||
<string>240</string>
|
||||
<key>FLTEnableImpeller</key>
|
||||
<true/>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
|
||||
@@ -39,6 +39,14 @@ iOS Release to TestFlight
|
||||
|
||||
iOS Manual Release
|
||||
|
||||
### ios gha_build_only
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios gha_build_only
|
||||
```
|
||||
|
||||
iOS Build Only (no TestFlight upload)
|
||||
|
||||
----
|
||||
|
||||
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
||||
|
||||
@@ -16,9 +16,9 @@ class ScrollToDateEvent extends Event {
|
||||
}
|
||||
|
||||
// Asset Viewer Events
|
||||
class ViewerOpenBottomSheetEvent extends Event {
|
||||
class ViewerShowDetailsEvent extends Event {
|
||||
final bool activitiesMode;
|
||||
const ViewerOpenBottomSheetEvent({this.activitiesMode = false});
|
||||
const ViewerShowDetailsEvent({this.activitiesMode = false});
|
||||
}
|
||||
|
||||
class ViewerReloadAssetEvent extends Event {
|
||||
|
||||
@@ -89,7 +89,9 @@ enum StoreKey<T> {
|
||||
cleanupKeepMediaType<int>._(1009),
|
||||
cleanupKeepAlbumIds<String>._(1010),
|
||||
cleanupCutoffDaysAgo<int>._(1011),
|
||||
cleanupDefaultsInitialized<bool>._(1012);
|
||||
cleanupDefaultsInitialized<bool>._(1012),
|
||||
|
||||
syncMigrationStatus<String>._(1013);
|
||||
|
||||
const StoreKey._(this.id);
|
||||
final int id;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/models/sync_event.model.dart';
|
||||
@@ -7,12 +10,21 @@ import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/sync_migration.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
||||
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/semver.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
enum SyncMigrationTask {
|
||||
v20260128_ResetExifV1, // EXIF table has incorrect width and height information.
|
||||
v20260128_CopyExifWidthHeightToAsset, // Asset table has incorrect width and height for video ratio calculations.
|
||||
v20260128_ResetAssetV1, // Asset v2.5.0 has width and height information that were edited assets.
|
||||
}
|
||||
|
||||
class SyncStreamService {
|
||||
final Logger _logger = Logger('SyncStreamService');
|
||||
|
||||
@@ -22,6 +34,8 @@ class SyncStreamService {
|
||||
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
|
||||
final LocalFilesManagerRepository _localFilesManager;
|
||||
final StorageRepository _storageRepository;
|
||||
final SyncMigrationRepository _syncMigrationRepository;
|
||||
final ApiService _api;
|
||||
final bool Function()? _cancelChecker;
|
||||
|
||||
SyncStreamService({
|
||||
@@ -31,6 +45,8 @@ class SyncStreamService {
|
||||
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
|
||||
required LocalFilesManagerRepository localFilesManager,
|
||||
required StorageRepository storageRepository,
|
||||
required SyncMigrationRepository syncMigrationRepository,
|
||||
required ApiService api,
|
||||
bool Function()? cancelChecker,
|
||||
}) : _syncApiRepository = syncApiRepository,
|
||||
_syncStreamRepository = syncStreamRepository,
|
||||
@@ -38,12 +54,32 @@ class SyncStreamService {
|
||||
_trashedLocalAssetRepository = trashedLocalAssetRepository,
|
||||
_localFilesManager = localFilesManager,
|
||||
_storageRepository = storageRepository,
|
||||
_syncMigrationRepository = syncMigrationRepository,
|
||||
_api = api,
|
||||
_cancelChecker = cancelChecker;
|
||||
|
||||
bool get isCancelled => _cancelChecker?.call() ?? false;
|
||||
|
||||
Future<bool> sync() async {
|
||||
_logger.info("Remote sync request for user");
|
||||
final serverVersion = await _api.serverInfoApi.getServerVersion();
|
||||
if (serverVersion == null) {
|
||||
_logger.severe("Cannot perform sync: unable to determine server version");
|
||||
return false;
|
||||
}
|
||||
|
||||
final semVer = SemVer(major: serverVersion.major, minor: serverVersion.minor, patch: serverVersion.patch_);
|
||||
|
||||
final value = Store.get(StoreKey.syncMigrationStatus, "[]");
|
||||
final migrations = (jsonDecode(value) as List).cast<String>();
|
||||
int previousLength = migrations.length;
|
||||
await _runPreSyncTasks(migrations, semVer);
|
||||
|
||||
if (migrations.length != previousLength) {
|
||||
_logger.info("Updated pre-sync migration status: $migrations");
|
||||
await Store.put(StoreKey.syncMigrationStatus, jsonEncode(migrations));
|
||||
}
|
||||
|
||||
// Start the sync stream and handle events
|
||||
bool shouldReset = false;
|
||||
await _syncApiRepository.streamChanges(_handleEvents, onReset: () => shouldReset = true);
|
||||
@@ -51,9 +87,56 @@ class SyncStreamService {
|
||||
_logger.info("Resetting sync state as requested by server");
|
||||
await _syncApiRepository.streamChanges(_handleEvents);
|
||||
}
|
||||
|
||||
previousLength = migrations.length;
|
||||
await _runPostSyncTasks(migrations);
|
||||
|
||||
if (migrations.length != previousLength) {
|
||||
_logger.info("Updated pre-sync migration status: $migrations");
|
||||
await Store.put(StoreKey.syncMigrationStatus, jsonEncode(migrations));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> _runPreSyncTasks(List<String> migrations, SemVer semVer) async {
|
||||
if (!migrations.contains(SyncMigrationTask.v20260128_ResetExifV1.name)) {
|
||||
_logger.info("Running pre-sync task: v20260128_ResetExifV1");
|
||||
await _syncApiRepository.deleteSyncAck([
|
||||
SyncEntityType.assetExifV1,
|
||||
SyncEntityType.partnerAssetExifV1,
|
||||
SyncEntityType.albumAssetExifCreateV1,
|
||||
SyncEntityType.albumAssetExifUpdateV1,
|
||||
]);
|
||||
migrations.add(SyncMigrationTask.v20260128_ResetExifV1.name);
|
||||
}
|
||||
|
||||
if (!migrations.contains(SyncMigrationTask.v20260128_ResetAssetV1.name) &&
|
||||
semVer >= const SemVer(major: 2, minor: 5, patch: 0)) {
|
||||
_logger.info("Running pre-sync task: v20260128_ResetAssetV1");
|
||||
await _syncApiRepository.deleteSyncAck([
|
||||
SyncEntityType.assetV1,
|
||||
SyncEntityType.partnerAssetV1,
|
||||
SyncEntityType.albumAssetCreateV1,
|
||||
SyncEntityType.albumAssetUpdateV1,
|
||||
]);
|
||||
|
||||
migrations.add(SyncMigrationTask.v20260128_ResetAssetV1.name);
|
||||
|
||||
if (!migrations.contains(SyncMigrationTask.v20260128_CopyExifWidthHeightToAsset.name)) {
|
||||
migrations.add(SyncMigrationTask.v20260128_CopyExifWidthHeightToAsset.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _runPostSyncTasks(List<String> migrations) async {
|
||||
if (!migrations.contains(SyncMigrationTask.v20260128_CopyExifWidthHeightToAsset.name)) {
|
||||
_logger.info("Running post-sync task: v20260128_CopyExifWidthHeightToAsset");
|
||||
await _syncMigrationRepository.v20260128CopyExifWidthHeightToAsset();
|
||||
migrations.add(SyncMigrationTask.v20260128_CopyExifWidthHeightToAsset.name);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleEvents(List<SyncEvent> events, Function() abort, Function() reset) async {
|
||||
List<SyncEvent> items = [];
|
||||
for (final event in events) {
|
||||
|
||||
@@ -19,6 +19,10 @@ class SyncApiRepository {
|
||||
return _api.syncApi.sendSyncAck(SyncAckSetDto(acks: data));
|
||||
}
|
||||
|
||||
Future<void> deleteSyncAck(List<SyncEntityType> types) {
|
||||
return _api.syncApi.deleteSyncAck(SyncAckDeleteDto(types: types));
|
||||
}
|
||||
|
||||
Future<void> streamChanges(
|
||||
Future<void> Function(List<SyncEvent>, Function() abort, Function() reset) onData, {
|
||||
Function()? onReset,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
|
||||
class SyncMigrationRepository extends DriftDatabaseRepository {
|
||||
final Drift _db;
|
||||
|
||||
const SyncMigrationRepository(super.db) : _db = db;
|
||||
|
||||
Future<void> v20260128CopyExifWidthHeightToAsset() async {
|
||||
await _db.customStatement('''
|
||||
UPDATE remote_asset_entity
|
||||
SET width = CASE
|
||||
WHEN exif.orientation IN ('5', '6', '7', '8', '-90', '90') THEN exif.height
|
||||
ELSE exif.width
|
||||
END,
|
||||
height = CASE
|
||||
WHEN exif.orientation IN ('5', '6', '7', '8', '-90', '90') THEN exif.width
|
||||
ELSE exif.height
|
||||
END
|
||||
FROM remote_exif_entity exif
|
||||
WHERE exif.asset_id = remote_asset_entity.id
|
||||
AND (exif.width IS NOT NULL OR exif.height IS NOT NULL);
|
||||
''');
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ enum VersionStatus {
|
||||
|
||||
class ServerInfo {
|
||||
final ServerVersion serverVersion;
|
||||
final ServerVersion latestVersion;
|
||||
final ServerVersion? latestVersion;
|
||||
final ServerFeatures serverFeatures;
|
||||
final ServerConfig serverConfig;
|
||||
final ServerDiskInfo serverDiskInfo;
|
||||
|
||||
+95
-103
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -18,7 +17,6 @@ import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet/she
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet/sheet_people_details.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/rating_bar.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/sheet_tile.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||
@@ -32,36 +30,10 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
|
||||
const _kSeparator = ' • ';
|
||||
|
||||
class AssetDetailBottomSheet extends ConsumerWidget {
|
||||
final DraggableScrollableController? controller;
|
||||
final double initialChildSize;
|
||||
class AssetDetails extends ConsumerWidget {
|
||||
final double minHeight;
|
||||
|
||||
const AssetDetailBottomSheet({this.controller, this.initialChildSize = 0.35, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final asset = ref.watch(currentAssetNotifier);
|
||||
if (asset == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return BaseBottomSheet(
|
||||
actions: [],
|
||||
slivers: const [_AssetDetailBottomSheet()],
|
||||
controller: controller,
|
||||
initialChildSize: initialChildSize,
|
||||
minChildSize: 0.1,
|
||||
maxChildSize: 0.88,
|
||||
expand: false,
|
||||
shouldCloseOnMinExtent: false,
|
||||
resizeOnScroll: false,
|
||||
backgroundColor: context.isDarkTheme ? context.colorScheme.surface : Colors.white,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AssetDetailBottomSheet extends ConsumerWidget {
|
||||
const _AssetDetailBottomSheet();
|
||||
const AssetDetails({required this.minHeight, super.key});
|
||||
|
||||
String _getDateTime(BuildContext ctx, BaseAsset asset, ExifInfo? exifInfo) {
|
||||
DateTime dateTime = asset.createdAt.toLocal();
|
||||
@@ -199,7 +171,7 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final asset = ref.watch(currentAssetNotifier);
|
||||
if (asset == null) {
|
||||
return const SliverToBoxAdapter(child: SizedBox.shrink());
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final exifInfo = ref.watch(currentAssetExifProvider).valueOrNull;
|
||||
@@ -247,81 +219,100 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
return SliverList.list(
|
||||
children: [
|
||||
// Asset Date and Time
|
||||
SheetTile(
|
||||
title: _getDateTime(context, asset, exifInfo),
|
||||
titleStyle: context.textTheme.labelLarge,
|
||||
trailing: asset.hasRemote && isOwner ? const Icon(Icons.edit, size: 18) : null,
|
||||
onTap: asset.hasRemote && isOwner ? () async => await _editDateTime(context, ref) : null,
|
||||
),
|
||||
if (exifInfo != null) _SheetAssetDescription(exif: exifInfo, isEditable: isOwner),
|
||||
const SheetPeopleDetails(),
|
||||
const SheetLocationDetails(),
|
||||
// Details header
|
||||
SheetTile(
|
||||
title: 'details'.t(context: context),
|
||||
titleStyle: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
),
|
||||
// File info
|
||||
buildFileInfoTile(),
|
||||
// Camera info
|
||||
if (cameraTitle != null) ...[
|
||||
return Container(
|
||||
constraints: BoxConstraints(minHeight: minHeight),
|
||||
decoration: BoxDecoration(
|
||||
color: context.isDarkTheme ? context.colorScheme.surface : Colors.white,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
SheetTile(
|
||||
title: cameraTitle,
|
||||
titleStyle: context.textTheme.labelLarge,
|
||||
leading: Icon(Icons.camera_alt_outlined, size: 24, color: context.textTheme.labelLarge?.color),
|
||||
subtitle: _getCameraInfoSubtitle(exifInfo),
|
||||
subtitleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
),
|
||||
],
|
||||
// Lens info
|
||||
if (lensTitle != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
SheetTile(
|
||||
title: lensTitle,
|
||||
titleStyle: context.textTheme.labelLarge,
|
||||
leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color),
|
||||
subtitle: _getLensInfoSubtitle(exifInfo),
|
||||
subtitleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
),
|
||||
],
|
||||
// Rating bar
|
||||
if (isRatingEnabled) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 16.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Text(
|
||||
'rating'.t(context: context),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
),
|
||||
RatingBar(
|
||||
initialRating: exifInfo?.rating?.toDouble() ?? 0,
|
||||
filledColor: context.themeData.colorScheme.primary,
|
||||
unfilledColor: context.themeData.colorScheme.onSurface.withAlpha(100),
|
||||
itemSize: 40,
|
||||
onRatingUpdate: (rating) async {
|
||||
await ref.read(actionProvider.notifier).updateRating(ActionSource.viewer, rating.round());
|
||||
},
|
||||
onClearRating: () async {
|
||||
await ref.read(actionProvider.notifier).updateRating(ActionSource.viewer, 0);
|
||||
},
|
||||
),
|
||||
],
|
||||
Center(
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
color: context.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Asset Date and Time
|
||||
SheetTile(
|
||||
title: _getDateTime(context, asset, exifInfo),
|
||||
titleStyle: context.textTheme.labelLarge,
|
||||
trailing: asset.hasRemote && isOwner ? const Icon(Icons.edit, size: 18) : null,
|
||||
onTap: asset.hasRemote && isOwner ? () async => await _editDateTime(context, ref) : null,
|
||||
),
|
||||
if (exifInfo != null) _SheetAssetDescription(exif: exifInfo, isEditable: isOwner),
|
||||
const SheetPeopleDetails(),
|
||||
const SheetLocationDetails(),
|
||||
// Details header
|
||||
SheetTile(
|
||||
title: 'details'.t(context: context),
|
||||
titleStyle: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
),
|
||||
// File info
|
||||
buildFileInfoTile(),
|
||||
// Camera info
|
||||
if (cameraTitle != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
SheetTile(
|
||||
title: cameraTitle,
|
||||
titleStyle: context.textTheme.labelLarge,
|
||||
leading: Icon(Icons.camera_alt_outlined, size: 24, color: context.textTheme.labelLarge?.color),
|
||||
subtitle: _getCameraInfoSubtitle(exifInfo),
|
||||
subtitleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
),
|
||||
],
|
||||
// Lens info
|
||||
if (lensTitle != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
SheetTile(
|
||||
title: lensTitle,
|
||||
titleStyle: context.textTheme.labelLarge,
|
||||
leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color),
|
||||
subtitle: _getLensInfoSubtitle(exifInfo),
|
||||
subtitleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
),
|
||||
],
|
||||
// Rating bar
|
||||
if (isRatingEnabled) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 16.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Text(
|
||||
'rating'.t(context: context),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
),
|
||||
RatingBar(
|
||||
initialRating: exifInfo?.rating?.toDouble() ?? 0,
|
||||
filledColor: context.themeData.colorScheme.primary,
|
||||
unfilledColor: context.themeData.colorScheme.onSurface.withAlpha(100),
|
||||
itemSize: 40,
|
||||
onRatingUpdate: (rating) async {
|
||||
await ref.read(actionProvider.notifier).updateRating(ActionSource.viewer, rating.round());
|
||||
},
|
||||
onClearRating: () async {
|
||||
await ref.read(actionProvider.notifier).updateRating(ActionSource.viewer, 0);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
// Appears in (Albums)
|
||||
Padding(padding: const EdgeInsets.only(top: 16.0), child: _buildAppearsInList(ref, context)),
|
||||
// padding at the bottom to avoid cut-off
|
||||
const SizedBox(height: 60),
|
||||
],
|
||||
// Appears in (Albums)
|
||||
Padding(padding: const EdgeInsets.only(top: 16.0), child: _buildAppearsInList(ref, context)),
|
||||
// padding at the bottom to avoid cut-off
|
||||
const SizedBox(height: 60),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -407,3 +398,4 @@ class _SheetAssetDescriptionState extends ConsumerState<_SheetAssetDescription>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -14,12 +15,11 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/scroll_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/download_status_floating_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/activities_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_details.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_bar.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/top_app_bar.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||
@@ -30,7 +30,6 @@ import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider
|
||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
|
||||
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
|
||||
@@ -92,33 +91,32 @@ class AssetViewer extends ConsumerStatefulWidget {
|
||||
if (asset.isVideo || asset.isMotionPhoto) {
|
||||
ref.read(videoPlaybackValueProvider.notifier).reset();
|
||||
ref.read(videoPlayerControlsProvider.notifier).pause();
|
||||
// Hide controls by default for videos and motion photos
|
||||
}
|
||||
// Hide controls by default for videos
|
||||
if (asset.isVideo) {
|
||||
ref.read(assetViewerProvider.notifier).setControls(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const double _kBottomSheetMinimumExtent = 0.4;
|
||||
const double _kBottomSheetSnapExtent = 0.67;
|
||||
enum _DragMode { undecided, dismiss, scroll }
|
||||
|
||||
class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
class _AssetViewerState extends ConsumerState<AssetViewer> with TickerProviderStateMixin {
|
||||
static final _dummyListener = ImageStreamListener((image, _) => image.dispose());
|
||||
late PageController pageController;
|
||||
late DraggableScrollableController bottomSheetController;
|
||||
PersistentBottomSheetController? sheetCloseController;
|
||||
// PhotoViewGallery takes care of disposing it's controllers
|
||||
PhotoViewControllerBase? viewController;
|
||||
StreamSubscription? reloadSubscription;
|
||||
StreamSubscription? _scaleBoundarySub;
|
||||
|
||||
late final int heroOffset;
|
||||
late PhotoViewControllerValue initialPhotoViewState;
|
||||
bool? hasDraggedDown;
|
||||
bool isSnapping = false;
|
||||
bool blockGestures = false;
|
||||
bool dragInProgress = false;
|
||||
bool shouldPopOnDrag = false;
|
||||
_DragMode _dragMode = _DragMode.undecided;
|
||||
Offset _dragStartGlobalPosition = Offset.zero;
|
||||
bool assetReloadRequested = false;
|
||||
double previousExtent = _kBottomSheetMinimumExtent;
|
||||
Offset dragDownPosition = Offset.zero;
|
||||
int totalAssets = 0;
|
||||
int stackIndex = 0;
|
||||
@@ -133,13 +131,19 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
|
||||
KeepAliveLink? _stackChildrenKeepAlive;
|
||||
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
late final AnimationController _ballisticAnimController;
|
||||
double _assetDetailsOpacity = 0.0;
|
||||
double _currentSnapOffset = 0.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
assert(ref.read(currentAssetNotifier) != null, "Current asset should not be null when opening the AssetViewer");
|
||||
pageController = PageController(initialPage: widget.initialIndex);
|
||||
_scrollController.addListener(_onScroll);
|
||||
_ballisticAnimController = AnimationController.unbounded(vsync: this)..addListener(_onBallisticTick);
|
||||
totalAssets = ref.read(timelineServiceProvider).totalAssets;
|
||||
bottomSheetController = DraggableScrollableController();
|
||||
WidgetsBinding.instance.addPostFrameCallback(_onAssetInit);
|
||||
reloadSubscription = EventStream.shared.listen(_onEvent);
|
||||
heroOffset = widget.heroOffset ?? TabsRouterScope.of(context)?.controller.activeIndex ?? 0;
|
||||
@@ -147,14 +151,136 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
if (asset != null) {
|
||||
_stackChildrenKeepAlive = ref.read(stackChildrenNotifier(asset).notifier).ref.keepAlive();
|
||||
}
|
||||
if (ref.read(assetViewerProvider).showingControls) {
|
||||
unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge));
|
||||
} else {
|
||||
unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky));
|
||||
}
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
final newOpacity = _scrollController.offset > 5 ? 1.0 : 0.0;
|
||||
if (newOpacity != _assetDetailsOpacity) {
|
||||
setState(() {
|
||||
_assetDetailsOpacity = newOpacity;
|
||||
});
|
||||
if (_assetDetailsOpacity == 0) {
|
||||
ref.read(assetViewerProvider.notifier).setControls(true);
|
||||
} else {
|
||||
ref.read(assetViewerProvider.notifier).setControls(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Match Flutter's ScrollPhysics defaults
|
||||
static final _snapSpring = SpringDescription.withDampingRatio(mass: 0.5, stiffness: 100.0, ratio: 1.1);
|
||||
static const _minFlingVelocity = 50.0; // px/s, matches ScrollPhysics.minFlingVelocity
|
||||
|
||||
Tolerance get _scrollTolerance {
|
||||
final dpr = MediaQuery.devicePixelRatioOf(context);
|
||||
return Tolerance(velocity: 1.0 / (0.050 * dpr), distance: 1.0 / dpr);
|
||||
}
|
||||
|
||||
/// Drive the scroll controller by [dy] pixels (positive = scroll down).
|
||||
void _scrollBy(double dy) {
|
||||
if (!_scrollController.hasClients) return;
|
||||
final newOffset = (_scrollController.offset - dy).clamp(0.0, _scrollController.position.maxScrollExtent);
|
||||
_scrollController.jumpTo(newOffset);
|
||||
}
|
||||
|
||||
/// Animate the scroll position to [target] using a spring simulation.
|
||||
void _animateScrollTo(double target, double velocity) {
|
||||
final offset = _scrollController.offset;
|
||||
final tolerance = _scrollTolerance;
|
||||
if ((offset - target).abs() < tolerance.distance) {
|
||||
_scrollController.jumpTo(target);
|
||||
return;
|
||||
}
|
||||
_ballisticAnimController.value = offset;
|
||||
_ballisticAnimController.animateWith(
|
||||
ScrollSpringSimulation(_snapSpring, offset, target, velocity, tolerance: tolerance),
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a platform-appropriate fling simulation (clamping on Android, bouncing on iOS).
|
||||
Simulation _createFlingSimulation(double offset, double velocity) {
|
||||
final tolerance = _scrollTolerance;
|
||||
if (CurrentPlatform.isIOS) {
|
||||
return BouncingScrollSimulation(
|
||||
position: offset,
|
||||
velocity: velocity,
|
||||
leadingExtent: _currentSnapOffset,
|
||||
trailingExtent: _scrollController.position.maxScrollExtent,
|
||||
spring: _snapSpring,
|
||||
tolerance: tolerance,
|
||||
);
|
||||
}
|
||||
return ClampingScrollSimulation(position: offset, velocity: velocity, tolerance: tolerance);
|
||||
}
|
||||
|
||||
void _onBallisticTick() {
|
||||
if (!_scrollController.hasClients) return;
|
||||
final raw = _ballisticAnimController.value;
|
||||
final max = _scrollController.position.maxScrollExtent;
|
||||
final offset = raw.clamp(0.0, max);
|
||||
final prevOffset = _scrollController.offset;
|
||||
|
||||
// Stop at bounds
|
||||
if (raw != offset) {
|
||||
_ballisticAnimController.stop();
|
||||
_scrollController.jumpTo(offset);
|
||||
return;
|
||||
}
|
||||
|
||||
// During free-scroll deceleration, don't cross into the snap zone.
|
||||
// Stop at snapOffset so the user can release there cleanly.
|
||||
final snap = _currentSnapOffset;
|
||||
if (prevOffset >= snap && offset < snap) {
|
||||
_ballisticAnimController.stop();
|
||||
_scrollController.jumpTo(snap);
|
||||
return;
|
||||
}
|
||||
|
||||
_scrollController.jumpTo(offset);
|
||||
}
|
||||
|
||||
void _snapScroll(double velocity) {
|
||||
if (!_scrollController.hasClients) return;
|
||||
final offset = _scrollController.offset;
|
||||
final snap = _currentSnapOffset;
|
||||
if (snap <= 0) return;
|
||||
|
||||
// Above snap offset: free scroll or spring back to snap
|
||||
if (offset >= snap) {
|
||||
if (velocity.abs() < _minFlingVelocity) return;
|
||||
if (velocity < -_minFlingVelocity) {
|
||||
_animateScrollTo(snap, velocity);
|
||||
return;
|
||||
}
|
||||
// Scrolling up: decelerate with platform-native physics
|
||||
_ballisticAnimController.value = offset;
|
||||
_ballisticAnimController.animateWith(_createFlingSimulation(offset, velocity));
|
||||
return;
|
||||
}
|
||||
|
||||
// In snap zone (0 → snapOffset): snap to nearest target
|
||||
final double target;
|
||||
if (velocity.abs() > _minFlingVelocity) {
|
||||
target = velocity > 0 ? snap : 0;
|
||||
} else {
|
||||
target = (offset < snap / 2) ? 0 : snap;
|
||||
}
|
||||
_animateScrollTo(target, velocity);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ballisticAnimController.dispose();
|
||||
_scrollController.dispose();
|
||||
pageController.dispose();
|
||||
bottomSheetController.dispose();
|
||||
_cancelTimers();
|
||||
reloadSubscription?.cancel();
|
||||
_scaleBoundarySub?.cancel();
|
||||
_prevPreCacheStream?.removeListener(_dummyListener);
|
||||
_nextPreCacheStream?.removeListener(_dummyListener);
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
@@ -162,7 +288,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool get showingBottomSheet => ref.read(assetViewerProvider.select((s) => s.showingBottomSheet));
|
||||
bool get showingDetails => _scrollController.offset > 0;
|
||||
|
||||
Color get backgroundColor {
|
||||
final opacity = ref.read(assetViewerProvider.select((s) => s.backgroundOpacity));
|
||||
@@ -176,9 +302,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
_delayedOperations.clear();
|
||||
}
|
||||
|
||||
double _getVerticalOffsetForBottomSheet(double extent) =>
|
||||
(context.height * extent) - (context.height * _kBottomSheetMinimumExtent);
|
||||
|
||||
ImageStream _precacheImage(BaseAsset asset) {
|
||||
final provider = getFullImageProvider(asset, size: context.sizeData);
|
||||
return provider.resolve(ImageConfiguration.empty)..addListener(_dummyListener);
|
||||
@@ -213,6 +336,8 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
}
|
||||
|
||||
void _onAssetChanged(int index) async {
|
||||
_ballisticAnimController.stop();
|
||||
if (_scrollController.hasClients) _scrollController.jumpTo(0);
|
||||
final timelineService = ref.read(timelineServiceProvider);
|
||||
final asset = await timelineService.getAssetAsync(index);
|
||||
if (asset == null) {
|
||||
@@ -256,20 +381,23 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
}
|
||||
}
|
||||
|
||||
void _onPageBuild(PhotoViewControllerBase controller) {
|
||||
viewController ??= controller;
|
||||
if (showingBottomSheet && bottomSheetController.isAttached) {
|
||||
final verticalOffset =
|
||||
(context.height * bottomSheetController.size) - (context.height * _kBottomSheetMinimumExtent);
|
||||
controller.position = Offset(0, -verticalOffset);
|
||||
// Apply the zoom effect when the bottom sheet is showing
|
||||
controller.scale = (controller.scale ?? 1.0) + 0.01;
|
||||
}
|
||||
}
|
||||
|
||||
void _onPageChanged(int index, PhotoViewControllerBase? controller) {
|
||||
_onAssetChanged(index);
|
||||
viewController = controller;
|
||||
_listenForScaleBoundaries(controller);
|
||||
}
|
||||
|
||||
void _listenForScaleBoundaries(PhotoViewControllerBase? controller) {
|
||||
_scaleBoundarySub?.cancel();
|
||||
_scaleBoundarySub = null;
|
||||
if (controller == null || controller.scaleBoundaries != null) return;
|
||||
_scaleBoundarySub = controller.outputStateStream.listen((_) {
|
||||
if (controller.scaleBoundaries != null) {
|
||||
_scaleBoundarySub?.cancel();
|
||||
_scaleBoundarySub = null;
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onDragStart(
|
||||
@@ -278,40 +406,43 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
PhotoViewControllerBase controller,
|
||||
PhotoViewScaleStateController scaleStateController,
|
||||
) {
|
||||
_ballisticAnimController.stop();
|
||||
viewController = controller;
|
||||
dragDownPosition = details.localPosition;
|
||||
_dragStartGlobalPosition = details.globalPosition;
|
||||
initialPhotoViewState = controller.value;
|
||||
_dragMode = showingDetails ? _DragMode.scroll : _DragMode.undecided;
|
||||
final isZoomed =
|
||||
scaleStateController.scaleState == PhotoViewScaleState.zoomedIn ||
|
||||
scaleStateController.scaleState == PhotoViewScaleState.covering;
|
||||
if (!showingBottomSheet && isZoomed) {
|
||||
if (!showingDetails && isZoomed) {
|
||||
blockGestures = true;
|
||||
}
|
||||
}
|
||||
|
||||
void _onDragEnd(BuildContext ctx, _, __) {
|
||||
void _onDragEnd(BuildContext ctx, DragEndDetails details, _) {
|
||||
dragInProgress = false;
|
||||
final mode = _dragMode;
|
||||
_dragMode = _DragMode.undecided;
|
||||
|
||||
if (mode == _DragMode.scroll) {
|
||||
// Convert finger velocity to scroll velocity (inverted)
|
||||
final scrollVelocity = -details.velocity.pixelsPerSecond.dy;
|
||||
_snapScroll(scrollVelocity);
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldPopOnDrag) {
|
||||
// Dismiss immediately without state updates to avoid rebuilds
|
||||
ctx.maybePop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not reset the state if the bottom sheet is showing
|
||||
if (showingBottomSheet) {
|
||||
_snapBottomSheet();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the gestures are blocked, do not reset the state
|
||||
if (blockGestures) {
|
||||
blockGestures = false;
|
||||
return;
|
||||
}
|
||||
|
||||
shouldPopOnDrag = false;
|
||||
hasDraggedDown = null;
|
||||
viewController?.animateMultiple(
|
||||
position: initialPhotoViewState.position,
|
||||
scale: viewController?.initialScale ?? initialPhotoViewState.scale,
|
||||
@@ -321,37 +452,28 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
}
|
||||
|
||||
void _onDragUpdate(BuildContext ctx, DragUpdateDetails details, _) {
|
||||
if (blockGestures) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (blockGestures) return;
|
||||
dragInProgress = true;
|
||||
final delta = details.localPosition - dragDownPosition;
|
||||
hasDraggedDown ??= delta.dy > 0;
|
||||
if (!hasDraggedDown! || showingBottomSheet) {
|
||||
_handleDragUp(ctx, delta);
|
||||
|
||||
if (_dragMode == _DragMode.undecided) {
|
||||
final globalDelta = details.globalPosition - _dragStartGlobalPosition;
|
||||
if (globalDelta.dy > 1) {
|
||||
_dragMode = _DragMode.dismiss;
|
||||
} else if (globalDelta.dy < -1) {
|
||||
_dragMode = _DragMode.scroll;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
// Fall through to process this update immediately
|
||||
}
|
||||
|
||||
if (_dragMode == _DragMode.dismiss) {
|
||||
final delta = details.localPosition - dragDownPosition;
|
||||
_handleDragDown(ctx, delta);
|
||||
return;
|
||||
}
|
||||
|
||||
_handleDragDown(ctx, delta);
|
||||
}
|
||||
|
||||
void _handleDragUp(BuildContext ctx, Offset delta) {
|
||||
const double openThreshold = 50;
|
||||
|
||||
final position = initialPhotoViewState.position + Offset(0, delta.dy);
|
||||
final distanceToOrigin = position.distance;
|
||||
|
||||
viewController?.updateMultiple(position: position);
|
||||
// Moves the bottom sheet when the asset is being dragged up
|
||||
if (showingBottomSheet && bottomSheetController.isAttached) {
|
||||
final centre = (ctx.height * _kBottomSheetMinimumExtent);
|
||||
bottomSheetController.jumpTo((centre + distanceToOrigin) / ctx.height);
|
||||
}
|
||||
|
||||
if (distanceToOrigin > openThreshold && !showingBottomSheet && !ref.read(readonlyModeProvider)) {
|
||||
_openBottomSheet(ctx);
|
||||
}
|
||||
_scrollBy(details.delta.dy);
|
||||
}
|
||||
|
||||
void _handleDragDown(BuildContext ctx, Offset delta) {
|
||||
@@ -375,54 +497,12 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
ref.read(assetViewerProvider.notifier).setOpacity(backgroundOpacity);
|
||||
}
|
||||
|
||||
void _onTapDown(_, __, ___) {
|
||||
if (!showingBottomSheet) {
|
||||
void _onTapUp(_, __, ___) {
|
||||
if (!showingDetails) {
|
||||
ref.read(assetViewerProvider.notifier).toggleControls();
|
||||
}
|
||||
}
|
||||
|
||||
bool _onNotification(Notification delta) {
|
||||
if (delta is DraggableScrollableNotification) {
|
||||
_handleDraggableNotification(delta);
|
||||
}
|
||||
|
||||
// Handle sheet snap manually so that the it snaps only at _kBottomSheetSnapExtent but not after
|
||||
// the isSnapping guard is to prevent the notification from recursively handling the
|
||||
// notification, eventually resulting in a heap overflow
|
||||
if (!isSnapping && delta is ScrollEndNotification) {
|
||||
_snapBottomSheet();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void _handleDraggableNotification(DraggableScrollableNotification delta) {
|
||||
final currentExtent = delta.extent;
|
||||
final isDraggingDown = currentExtent < previousExtent;
|
||||
previousExtent = currentExtent;
|
||||
// Closes the bottom sheet if the user is dragging down
|
||||
if (isDraggingDown && delta.extent < 0.67) {
|
||||
if (dragInProgress) {
|
||||
blockGestures = true;
|
||||
}
|
||||
// Jump to a lower position before starting close animation to prevent glitch
|
||||
if (bottomSheetController.isAttached) {
|
||||
bottomSheetController.jumpTo(0.67);
|
||||
}
|
||||
sheetCloseController?.close();
|
||||
}
|
||||
|
||||
// If the asset is being dragged down, we do not want to update the asset position again
|
||||
if (dragInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
final verticalOffset = _getVerticalOffsetForBottomSheet(delta.extent);
|
||||
// Moves the asset when the bottom sheet is being dragged
|
||||
if (verticalOffset > 0) {
|
||||
viewController?.position = Offset(0, -verticalOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void _onEvent(Event event) {
|
||||
if (event is TimelineReloadEvent) {
|
||||
_onTimelineReloadEvent();
|
||||
@@ -434,15 +514,18 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event is ViewerOpenBottomSheetEvent) {
|
||||
final extent = _kBottomSheetMinimumExtent + 0.3;
|
||||
_openBottomSheet(scaffoldContext!, extent: extent, activitiesMode: event.activitiesMode);
|
||||
final offset = _getVerticalOffsetForBottomSheet(extent);
|
||||
viewController?.position = Offset(0, -offset);
|
||||
if (event is ViewerShowDetailsEvent) {
|
||||
_showDetails();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _showDetails() {
|
||||
if (!_scrollController.hasClients || _currentSnapOffset <= 0) return;
|
||||
_ballisticAnimController.stop();
|
||||
_animateScrollTo(_currentSnapOffset, 0);
|
||||
}
|
||||
|
||||
void _onTimelineReloadEvent() {
|
||||
totalAssets = ref.read(timelineServiceProvider).totalAssets;
|
||||
if (totalAssets == 0) {
|
||||
@@ -467,57 +550,16 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
}
|
||||
|
||||
final currentAsset = ref.read(currentAssetNotifier);
|
||||
// Do not reload / close the bottom sheet if the asset has not changed
|
||||
// Do not reload if the asset has not changed
|
||||
if (newAsset.heroTag == currentAsset?.heroTag) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_onAssetChanged(pageController.page!.round());
|
||||
sheetCloseController?.close();
|
||||
});
|
||||
}
|
||||
|
||||
void _openBottomSheet(BuildContext ctx, {double extent = _kBottomSheetMinimumExtent, bool activitiesMode = false}) {
|
||||
ref.read(assetViewerProvider.notifier).setBottomSheet(true);
|
||||
previousExtent = _kBottomSheetMinimumExtent;
|
||||
sheetCloseController = showBottomSheet(
|
||||
context: ctx,
|
||||
sheetAnimationStyle: const AnimationStyle(duration: Durations.medium2, reverseDuration: Durations.medium2),
|
||||
constraints: const BoxConstraints(maxWidth: double.infinity),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20.0))),
|
||||
backgroundColor: ctx.colorScheme.surfaceContainerLowest,
|
||||
builder: (_) {
|
||||
return NotificationListener<Notification>(
|
||||
onNotification: _onNotification,
|
||||
child: activitiesMode
|
||||
? ActivitiesBottomSheet(controller: bottomSheetController, initialChildSize: extent)
|
||||
: AssetDetailBottomSheet(controller: bottomSheetController, initialChildSize: extent),
|
||||
);
|
||||
},
|
||||
);
|
||||
sheetCloseController?.closed.then((_) => _handleSheetClose());
|
||||
}
|
||||
|
||||
void _handleSheetClose() {
|
||||
viewController?.animateMultiple(position: Offset.zero);
|
||||
viewController?.updateMultiple(scale: viewController?.initialScale);
|
||||
ref.read(assetViewerProvider.notifier).setBottomSheet(false);
|
||||
sheetCloseController = null;
|
||||
shouldPopOnDrag = false;
|
||||
hasDraggedDown = null;
|
||||
}
|
||||
|
||||
void _snapBottomSheet() {
|
||||
if (!bottomSheetController.isAttached ||
|
||||
bottomSheetController.size > _kBottomSheetSnapExtent ||
|
||||
bottomSheetController.size < 0.4) {
|
||||
return;
|
||||
}
|
||||
isSnapping = true;
|
||||
bottomSheetController.animateTo(_kBottomSheetSnapExtent, duration: Durations.short3, curve: Curves.easeOut);
|
||||
}
|
||||
|
||||
Widget _placeholderBuilder(BuildContext ctx, ImageChunkEvent? progress, int index) {
|
||||
return const Center(child: ImmichLoadingIndicator());
|
||||
}
|
||||
@@ -531,7 +573,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!showingBottomSheet) {
|
||||
if (!showingDetails) {
|
||||
ref.read(assetViewerProvider.notifier).setControls(true);
|
||||
}
|
||||
}
|
||||
@@ -580,11 +622,11 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
heroAttributes: PhotoViewHeroAttributes(tag: '${asset.heroTag}_$heroOffset'),
|
||||
filterQuality: FilterQuality.high,
|
||||
tightMode: true,
|
||||
disableScaleGestures: showingBottomSheet,
|
||||
disableScaleGestures: showingDetails,
|
||||
onDragStart: _onDragStart,
|
||||
onDragUpdate: _onDragUpdate,
|
||||
onDragEnd: _onDragEnd,
|
||||
onTapDown: _onTapDown,
|
||||
onTapUp: _onTapUp,
|
||||
onLongPressStart: asset.isMotionPhoto ? _onLongPress : null,
|
||||
errorBuilder: (_, __, ___) => Container(
|
||||
width: size.width,
|
||||
@@ -605,7 +647,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
onDragStart: _onDragStart,
|
||||
onDragUpdate: _onDragUpdate,
|
||||
onDragEnd: _onDragEnd,
|
||||
onTapDown: _onTapDown,
|
||||
onTapUp: _onTapUp,
|
||||
heroAttributes: PhotoViewHeroAttributes(tag: '${asset.heroTag}_$heroOffset'),
|
||||
filterQuality: FilterQuality.high,
|
||||
maxScale: 1.0,
|
||||
@@ -638,7 +680,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
Widget build(BuildContext context) {
|
||||
// Rebuild the widget when the asset viewer state changes
|
||||
// Using multiple selectors to avoid unnecessary rebuilds for other state changes
|
||||
ref.watch(assetViewerProvider.select((s) => s.showingBottomSheet));
|
||||
ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity));
|
||||
ref.watch(assetViewerProvider.select((s) => s.stackIndex));
|
||||
ref.watch(isPlayingMotionVideoProvider);
|
||||
@@ -665,9 +706,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
}
|
||||
});
|
||||
|
||||
// Currently it is not possible to scroll the asset when the bottom sheet is open all the way.
|
||||
// Issue: https://github.com/flutter/flutter/issues/109037
|
||||
// TODO: Add a custom scrum builder once the fix lands on stable
|
||||
return PopScope(
|
||||
onPopInvokedWithResult: _onPop,
|
||||
child: Scaffold(
|
||||
@@ -683,36 +721,92 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
child: const DownloadStatusFloatingButton(),
|
||||
),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
PhotoViewGallery.builder(
|
||||
gaplessPlayback: true,
|
||||
loadingBuilder: _placeholderBuilder,
|
||||
pageController: pageController,
|
||||
scrollPhysics: CurrentPlatform.isIOS
|
||||
? const FastScrollPhysics() // Use bouncing physics for iOS
|
||||
: const FastClampingScrollPhysics(), // Use heavy physics for Android
|
||||
itemCount: totalAssets,
|
||||
onPageChanged: _onPageChanged,
|
||||
onPageBuild: _onPageBuild,
|
||||
scaleStateChangedCallback: _onScaleStateChanged,
|
||||
builder: _assetBuilder,
|
||||
backgroundDecoration: BoxDecoration(color: backgroundColor),
|
||||
enablePanAlways: true,
|
||||
),
|
||||
if (!showingBottomSheet)
|
||||
const Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [AssetStackRow(), ViewerBottomBar()],
|
||||
body: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final viewportWidth = constraints.maxWidth;
|
||||
final viewportHeight = constraints.maxHeight;
|
||||
|
||||
// Use the actual rendered image size from PhotoView when available,
|
||||
// falling back to a calculation from asset metadata.
|
||||
final sb = viewController?.scaleBoundaries;
|
||||
double imageHeight;
|
||||
if (sb != null) {
|
||||
imageHeight = sb.childSize.height * sb.initialScale;
|
||||
} else {
|
||||
final asset = ref.read(currentAssetNotifier);
|
||||
final assetWidth = asset?.width;
|
||||
final assetHeight = asset?.height;
|
||||
imageHeight = viewportHeight;
|
||||
if (assetWidth != null && assetHeight != null && assetWidth > 0 && assetHeight > 0) {
|
||||
final aspectRatio = assetWidth / assetHeight;
|
||||
imageHeight = math.min(viewportWidth / aspectRatio, viewportHeight);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate padding to center the image in the viewport
|
||||
final topPadding = math.max((viewportHeight - imageHeight) / 2, 0.0);
|
||||
final snapOffset = math.max(topPadding + (imageHeight / 2), viewportHeight / 4 * 3);
|
||||
_currentSnapOffset = snapOffset;
|
||||
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedOverflowBox(
|
||||
size: Size(double.infinity, topPadding + imageHeight - (kMinInteractiveDimension / 2)),
|
||||
alignment: Alignment.topCenter,
|
||||
child: SizedBox(
|
||||
height: viewportHeight,
|
||||
child: PhotoViewGallery.builder(
|
||||
gaplessPlayback: true,
|
||||
loadingBuilder: _placeholderBuilder,
|
||||
pageController: pageController,
|
||||
scrollPhysics: CurrentPlatform.isIOS
|
||||
? const FastScrollPhysics() // Use bouncing physics for iOS
|
||||
: const FastClampingScrollPhysics(), // Use heavy physics for Android
|
||||
itemCount: totalAssets,
|
||||
onPageChanged: _onPageChanged,
|
||||
scaleStateChangedCallback: _onScaleStateChanged,
|
||||
builder: _assetBuilder,
|
||||
backgroundDecoration: BoxDecoration(color: backgroundColor),
|
||||
enablePanAlways: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
GestureDetector(
|
||||
onVerticalDragStart: (_) => _ballisticAnimController.stop(),
|
||||
onVerticalDragUpdate: (details) => _scrollBy(details.delta.dy),
|
||||
onVerticalDragEnd: (details) => _snapScroll(-details.velocity.pixelsPerSecond.dy),
|
||||
child: AnimatedOpacity(
|
||||
opacity: _assetDetailsOpacity,
|
||||
duration: kThemeAnimationDuration,
|
||||
child: AssetDetails(minHeight: viewportHeight / 4 * 3),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Positioned(
|
||||
height: viewportHeight,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [AssetStackRow(), ViewerBottomBar()],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -55,7 +55,7 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
IconButton(
|
||||
icon: const Icon(Icons.chat_outlined),
|
||||
onPressed: () {
|
||||
EventStream.shared.emit(const ViewerOpenBottomSheetEvent(activitiesMode: true));
|
||||
EventStream.shared.emit(const ViewerShowDetailsEvent(activitiesMode: true));
|
||||
},
|
||||
),
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:immich_mobile/domain/services/hash.service.dart';
|
||||
import 'package:immich_mobile/domain/services/local_sync.service.dart';
|
||||
import 'package:immich_mobile/domain/services/sync_stream.service.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/sync_migration.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
@@ -13,6 +14,8 @@ import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
||||
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
|
||||
|
||||
final syncMigrationRepositoryProvider = Provider((ref) => SyncMigrationRepository(ref.watch(driftProvider)));
|
||||
|
||||
final syncStreamServiceProvider = Provider(
|
||||
(ref) => SyncStreamService(
|
||||
syncApiRepository: ref.watch(syncApiRepositoryProvider),
|
||||
@@ -21,6 +24,8 @@ final syncStreamServiceProvider = Provider(
|
||||
trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository),
|
||||
localFilesManager: ref.watch(localFilesManagerRepositoryProvider),
|
||||
storageRepository: ref.watch(storageRepositoryProvider),
|
||||
syncMigrationRepository: ref.watch(syncMigrationRepositoryProvider),
|
||||
api: ref.watch(apiServiceProvider),
|
||||
cancelChecker: ref.watch(cancellationProvider),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
|
||||
: super(
|
||||
const ServerInfo(
|
||||
serverVersion: ServerVersion(major: 0, minor: 0, patch: 0),
|
||||
latestVersion: ServerVersion(major: 0, minor: 0, patch: 0),
|
||||
latestVersion: null,
|
||||
serverFeatures: ServerFeatures(map: true, trash: true, oauthEnabled: false, passwordLogin: true),
|
||||
serverConfig: ServerConfig(
|
||||
trashDays: 30,
|
||||
@@ -43,7 +43,7 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
|
||||
try {
|
||||
final serverVersion = await _serverInfoService.getServerVersion();
|
||||
|
||||
// using isClientOutOfDate since that will show to users reguardless of if they are an admin
|
||||
// using isClientOutOfDate since that will show to users regardless of if they are an admin
|
||||
if (serverVersion == null) {
|
||||
state = state.copyWith(versionStatus: VersionStatus.error);
|
||||
return;
|
||||
@@ -76,7 +76,7 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
|
||||
state = state.copyWith(versionStatus: VersionStatus.upToDate);
|
||||
}
|
||||
|
||||
handleReleaseInfo(ServerVersion serverVersion, ServerVersion latestVersion) {
|
||||
handleReleaseInfo(ServerVersion serverVersion, ServerVersion? latestVersion) {
|
||||
// Update local server version
|
||||
_checkServerVersionMismatch(serverVersion, latestVersion: latestVersion);
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ enum ActionButtonType {
|
||||
iconData: Icons.info_outline,
|
||||
iconColor: context.originalTheme?.iconTheme.color,
|
||||
menuItem: true,
|
||||
onPressed: () => EventStream.shared.emit(const ViewerOpenBottomSheetEvent()),
|
||||
onPressed: () => EventStream.shared.emit(const ViewerShowDetailsEvent()),
|
||||
),
|
||||
ActionButtonType.viewInTimeline => BaseActionButton(
|
||||
label: 'view_in_timeline'.tr(),
|
||||
|
||||
@@ -28,6 +28,7 @@ import 'package:immich_mobile/utils/datetime_helpers.dart';
|
||||
import 'package:immich_mobile/utils/debug_print.dart';
|
||||
import 'package:immich_mobile/utils/diff.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
// ignore: import_rule_photo_manager
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
@@ -88,7 +89,6 @@ Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
|
||||
|
||||
if (version < 20 && Store.isBetaTimelineEnabled) {
|
||||
await _syncLocalAlbumIsIosSharedAlbum(drift);
|
||||
await _backfillAssetExifWidthHeight(drift);
|
||||
}
|
||||
|
||||
if (targetVersion >= 12) {
|
||||
@@ -282,22 +282,6 @@ Future<void> _syncLocalAlbumIsIosSharedAlbum(Drift db) async {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _backfillAssetExifWidthHeight(Drift db) async {
|
||||
try {
|
||||
await db.customStatement('''
|
||||
UPDATE remote_exif_entity AS remote_exif
|
||||
SET width = asset.width,
|
||||
height = asset.height
|
||||
FROM remote_asset_entity AS asset
|
||||
WHERE remote_exif.asset_id = asset.id;
|
||||
''');
|
||||
|
||||
dPrint(() => "[MIGRATION] Successfully backfilled asset exif width and height");
|
||||
} catch (error) {
|
||||
dPrint(() => "[MIGRATION] Error while backfilling asset exif width and height: $error");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> migrateDeviceAssetToSqlite(Isar db, Drift drift) async {
|
||||
try {
|
||||
final isarDeviceAssets = await db.deviceAssetEntitys.where().findAll();
|
||||
|
||||
@@ -170,50 +170,52 @@ class AppBarServerInfo extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
const Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: Divider(thickness: 1)),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 10.0),
|
||||
child: Row(
|
||||
children: [
|
||||
if (serverInfoState.versionStatus == VersionStatus.serverOutOfDate)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 5.0),
|
||||
child: Icon(Icons.info, color: Color.fromARGB(255, 243, 188, 106), size: 12),
|
||||
if (serverInfoState.latestVersion != null) ...[
|
||||
const Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: Divider(thickness: 1)),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 10.0),
|
||||
child: Row(
|
||||
children: [
|
||||
if (serverInfoState.versionStatus == VersionStatus.serverOutOfDate)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 5.0),
|
||||
child: Icon(Icons.info, color: Color.fromARGB(255, 243, 188, 106), size: 12),
|
||||
),
|
||||
Text(
|
||||
"latest_version".tr(),
|
||||
style: TextStyle(
|
||||
fontSize: titleFontSize,
|
||||
color: context.textTheme.labelSmall?.color,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"latest_version".tr(),
|
||||
style: TextStyle(
|
||||
fontSize: titleFontSize,
|
||||
color: context.textTheme.labelSmall?.color,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 10.0),
|
||||
child: Text(
|
||||
serverInfoState.latestVersion.major > 0
|
||||
? "${serverInfoState.latestVersion.major}.${serverInfoState.latestVersion.minor}.${serverInfoState.latestVersion.patch}"
|
||||
: "--",
|
||||
style: TextStyle(
|
||||
fontSize: contentFontSize,
|
||||
color: context.colorScheme.onSurfaceSecondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 10.0),
|
||||
child: Text(
|
||||
serverInfoState.latestVersion!.major > 0
|
||||
? "${serverInfoState.latestVersion!.major}.${serverInfoState.latestVersion!.minor}.${serverInfoState.latestVersion!.patch}"
|
||||
: "--",
|
||||
style: TextStyle(
|
||||
fontSize: contentFontSize,
|
||||
color: context.colorScheme.onSurfaceSecondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -414,6 +414,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
keyboardAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.url,
|
||||
autofillHints: const [AutofillHints.url],
|
||||
autoCorrect: false,
|
||||
onSubmit: (ctx, _) => ImmichForm.of(ctx).submit(),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -203,9 +203,13 @@ class PhotoViewGestureRecognizer extends ScaleGestureRecognizer {
|
||||
|
||||
void _decideIfWeAcceptEvent(PointerEvent event) {
|
||||
final move = _initialFocalPoint! - _currentFocalPoint!;
|
||||
final bool shouldMove = validateAxis == Axis.vertical
|
||||
? hitDetector!.shouldMove(move, Axis.vertical)
|
||||
: hitDetector!.shouldMove(move, Axis.horizontal);
|
||||
|
||||
// Accept gesture if movement is possible in the direction the user is swiping
|
||||
final bool isHorizontalGesture = move.dx.abs() > move.dy.abs();
|
||||
final bool shouldMove = isHorizontalGesture
|
||||
? hitDetector!.shouldMove(move, Axis.horizontal)
|
||||
: hitDetector!.shouldMove(move, Axis.vertical);
|
||||
|
||||
if (shouldMove || _pointerLocations.keys.length > 1) {
|
||||
final double spanDelta = (_currentSpan! - _initialSpan!).abs();
|
||||
final double focalPointDelta = (_currentFocalPoint! - _initialFocalPoint!).distance;
|
||||
|
||||
Generated
+1
-1
@@ -3,7 +3,7 @@ Immich API
|
||||
|
||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 2.5.1
|
||||
- API version: 2.5.2
|
||||
- Generator version: 7.8.0
|
||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||
|
||||
|
||||
+14
@@ -130,14 +130,19 @@ class ActivitiesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// Album ID
|
||||
///
|
||||
/// * [String] assetId:
|
||||
/// Asset ID (if activity is for an asset)
|
||||
///
|
||||
/// * [ReactionLevel] level:
|
||||
/// Filter by activity level
|
||||
///
|
||||
/// * [ReactionType] type:
|
||||
/// Filter by activity type
|
||||
///
|
||||
/// * [String] userId:
|
||||
/// Filter by user ID
|
||||
Future<Response> getActivitiesWithHttpInfo(String albumId, { String? assetId, ReactionLevel? level, ReactionType? type, String? userId, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/activities';
|
||||
@@ -184,14 +189,19 @@ class ActivitiesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// Album ID
|
||||
///
|
||||
/// * [String] assetId:
|
||||
/// Asset ID (if activity is for an asset)
|
||||
///
|
||||
/// * [ReactionLevel] level:
|
||||
/// Filter by activity level
|
||||
///
|
||||
/// * [ReactionType] type:
|
||||
/// Filter by activity type
|
||||
///
|
||||
/// * [String] userId:
|
||||
/// Filter by user ID
|
||||
Future<List<ActivityResponseDto>?> getActivities(String albumId, { String? assetId, ReactionLevel? level, ReactionType? type, String? userId, }) async {
|
||||
final response = await getActivitiesWithHttpInfo(albumId, assetId: assetId, level: level, type: type, userId: userId, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -219,8 +229,10 @@ class ActivitiesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// Album ID
|
||||
///
|
||||
/// * [String] assetId:
|
||||
/// Asset ID (if activity is for an asset)
|
||||
Future<Response> getActivityStatisticsWithHttpInfo(String albumId, { String? assetId, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/activities/statistics';
|
||||
@@ -258,8 +270,10 @@ class ActivitiesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId (required):
|
||||
/// Album ID
|
||||
///
|
||||
/// * [String] assetId:
|
||||
/// Asset ID (if activity is for an asset)
|
||||
Future<ActivityStatisticsResponseDto?> getActivityStatistics(String albumId, { String? assetId, }) async {
|
||||
final response = await getActivityStatisticsWithHttpInfo(albumId, assetId: assetId, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
Generated
+6
-2
@@ -347,6 +347,7 @@ class AlbumsApi {
|
||||
/// * [String] slug:
|
||||
///
|
||||
/// * [bool] withoutAssets:
|
||||
/// Exclude assets from response
|
||||
Future<Response> getAlbumInfoWithHttpInfo(String id, { String? key, String? slug, bool? withoutAssets, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/albums/{id}'
|
||||
@@ -396,6 +397,7 @@ class AlbumsApi {
|
||||
/// * [String] slug:
|
||||
///
|
||||
/// * [bool] withoutAssets:
|
||||
/// Exclude assets from response
|
||||
Future<AlbumResponseDto?> getAlbumInfo(String id, { String? key, String? slug, bool? withoutAssets, }) async {
|
||||
final response = await getAlbumInfoWithHttpInfo(id, key: key, slug: slug, withoutAssets: withoutAssets, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -468,9 +470,10 @@ class AlbumsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] assetId:
|
||||
/// Only returns albums that contain the asset Ignores the shared parameter undefined: get all albums
|
||||
/// Filter albums containing this asset ID (ignores shared parameter)
|
||||
///
|
||||
/// * [bool] shared:
|
||||
/// Filter by shared status: true = only shared, false = only own, undefined = all
|
||||
Future<Response> getAllAlbumsWithHttpInfo({ String? assetId, bool? shared, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/albums';
|
||||
@@ -510,9 +513,10 @@ class AlbumsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] assetId:
|
||||
/// Only returns albums that contain the asset Ignores the shared parameter undefined: get all albums
|
||||
/// Filter albums containing this asset ID (ignores shared parameter)
|
||||
///
|
||||
/// * [bool] shared:
|
||||
/// Filter by shared status: true = only shared, false = only own, undefined = all
|
||||
Future<List<AlbumResponseDto>?> getAllAlbums({ String? assetId, bool? shared, }) async {
|
||||
final response = await getAllAlbumsWithHttpInfo( assetId: assetId, shared: shared, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
Generated
+62
@@ -185,8 +185,10 @@ class AssetsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
/// Asset ID
|
||||
///
|
||||
/// * [String] key (required):
|
||||
/// Metadata key
|
||||
Future<Response> deleteAssetMetadataWithHttpInfo(String id, String key,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/{id}/metadata/{key}'
|
||||
@@ -221,8 +223,10 @@ class AssetsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
/// Asset ID
|
||||
///
|
||||
/// * [String] key (required):
|
||||
/// Metadata key
|
||||
Future<void> deleteAssetMetadata(String id, String key,) async {
|
||||
final response = await deleteAssetMetadataWithHttpInfo(id, key,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -337,6 +341,7 @@ class AssetsApi {
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [bool] edited:
|
||||
/// Return edited asset if available
|
||||
///
|
||||
/// * [String] key:
|
||||
///
|
||||
@@ -386,6 +391,7 @@ class AssetsApi {
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [bool] edited:
|
||||
/// Return edited asset if available
|
||||
///
|
||||
/// * [String] key:
|
||||
///
|
||||
@@ -475,6 +481,7 @@ class AssetsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] deviceId (required):
|
||||
/// Device ID
|
||||
Future<Response> getAllUserAssetsByDeviceIdWithHttpInfo(String deviceId,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/device/{deviceId}'
|
||||
@@ -508,6 +515,7 @@ class AssetsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] deviceId (required):
|
||||
/// Device ID
|
||||
Future<List<String>?> getAllUserAssetsByDeviceId(String deviceId,) async {
|
||||
final response = await getAllUserAssetsByDeviceIdWithHttpInfo(deviceId,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -724,8 +732,10 @@ class AssetsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
/// Asset ID
|
||||
///
|
||||
/// * [String] key (required):
|
||||
/// Metadata key
|
||||
Future<Response> getAssetMetadataByKeyWithHttpInfo(String id, String key,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/{id}/metadata/{key}'
|
||||
@@ -760,8 +770,10 @@ class AssetsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
/// Asset ID
|
||||
///
|
||||
/// * [String] key (required):
|
||||
/// Metadata key
|
||||
Future<AssetMetadataResponseDto?> getAssetMetadataByKey(String id, String key,) async {
|
||||
final response = await getAssetMetadataByKeyWithHttpInfo(id, key,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -846,10 +858,13 @@ class AssetsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
/// Filter by favorite status
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
/// Filter by trash status
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Filter by visibility
|
||||
Future<Response> getAssetStatisticsWithHttpInfo({ bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/statistics';
|
||||
@@ -892,10 +907,13 @@ class AssetsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
/// Filter by favorite status
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
/// Filter by trash status
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Filter by visibility
|
||||
Future<AssetStatsResponseDto?> getAssetStatistics({ bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async {
|
||||
final response = await getAssetStatisticsWithHttpInfo( isFavorite: isFavorite, isTrashed: isTrashed, visibility: visibility, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -920,6 +938,7 @@ class AssetsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [num] count:
|
||||
/// Number of random assets to return
|
||||
Future<Response> getRandomWithHttpInfo({ num? count, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/random';
|
||||
@@ -956,6 +975,7 @@ class AssetsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [num] count:
|
||||
/// Number of random assets to return
|
||||
Future<List<AssetResponseDto>?> getRandom({ num? count, }) async {
|
||||
final response = await getRandomWithHttpInfo( count: count, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -1106,22 +1126,29 @@ class AssetsApi {
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [MultipartFile] assetData (required):
|
||||
/// Asset file data
|
||||
///
|
||||
/// * [String] deviceAssetId (required):
|
||||
/// Device asset ID
|
||||
///
|
||||
/// * [String] deviceId (required):
|
||||
/// Device ID
|
||||
///
|
||||
/// * [DateTime] fileCreatedAt (required):
|
||||
/// File creation date
|
||||
///
|
||||
/// * [DateTime] fileModifiedAt (required):
|
||||
/// File modification date
|
||||
///
|
||||
/// * [String] key:
|
||||
///
|
||||
/// * [String] slug:
|
||||
///
|
||||
/// * [String] duration:
|
||||
/// Duration (for videos)
|
||||
///
|
||||
/// * [String] filename:
|
||||
/// Filename
|
||||
Future<Response> replaceAssetWithHttpInfo(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? duration, String? filename, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/{id}/original'
|
||||
@@ -1198,22 +1225,29 @@ class AssetsApi {
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [MultipartFile] assetData (required):
|
||||
/// Asset file data
|
||||
///
|
||||
/// * [String] deviceAssetId (required):
|
||||
/// Device asset ID
|
||||
///
|
||||
/// * [String] deviceId (required):
|
||||
/// Device ID
|
||||
///
|
||||
/// * [DateTime] fileCreatedAt (required):
|
||||
/// File creation date
|
||||
///
|
||||
/// * [DateTime] fileModifiedAt (required):
|
||||
/// File modification date
|
||||
///
|
||||
/// * [String] key:
|
||||
///
|
||||
/// * [String] slug:
|
||||
///
|
||||
/// * [String] duration:
|
||||
/// Duration (for videos)
|
||||
///
|
||||
/// * [String] filename:
|
||||
/// Filename
|
||||
Future<AssetMediaResponseDto?> replaceAsset(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? duration, String? filename, }) async {
|
||||
final response = await replaceAssetWithHttpInfo(id, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, slug: slug, duration: duration, filename: filename, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -1518,14 +1552,19 @@ class AssetsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [MultipartFile] assetData (required):
|
||||
/// Asset file data
|
||||
///
|
||||
/// * [String] deviceAssetId (required):
|
||||
/// Device asset ID
|
||||
///
|
||||
/// * [String] deviceId (required):
|
||||
/// Device ID
|
||||
///
|
||||
/// * [DateTime] fileCreatedAt (required):
|
||||
/// File creation date
|
||||
///
|
||||
/// * [DateTime] fileModifiedAt (required):
|
||||
/// File modification date
|
||||
///
|
||||
/// * [String] key:
|
||||
///
|
||||
@@ -1535,18 +1574,25 @@ class AssetsApi {
|
||||
/// sha1 checksum that can be used for duplicate detection before the file is uploaded
|
||||
///
|
||||
/// * [String] duration:
|
||||
/// Duration (for videos)
|
||||
///
|
||||
/// * [String] filename:
|
||||
/// Filename
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
/// Mark as favorite
|
||||
///
|
||||
/// * [String] livePhotoVideoId:
|
||||
/// Live photo video ID
|
||||
///
|
||||
/// * [List<AssetMetadataUpsertItemDto>] metadata:
|
||||
/// Asset metadata items
|
||||
///
|
||||
/// * [MultipartFile] sidecarData:
|
||||
/// Sidecar file data
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Asset visibility
|
||||
Future<Response> uploadAssetWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, String? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List<AssetMetadataUpsertItemDto>? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets';
|
||||
@@ -1645,14 +1691,19 @@ class AssetsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [MultipartFile] assetData (required):
|
||||
/// Asset file data
|
||||
///
|
||||
/// * [String] deviceAssetId (required):
|
||||
/// Device asset ID
|
||||
///
|
||||
/// * [String] deviceId (required):
|
||||
/// Device ID
|
||||
///
|
||||
/// * [DateTime] fileCreatedAt (required):
|
||||
/// File creation date
|
||||
///
|
||||
/// * [DateTime] fileModifiedAt (required):
|
||||
/// File modification date
|
||||
///
|
||||
/// * [String] key:
|
||||
///
|
||||
@@ -1662,18 +1713,25 @@ class AssetsApi {
|
||||
/// sha1 checksum that can be used for duplicate detection before the file is uploaded
|
||||
///
|
||||
/// * [String] duration:
|
||||
/// Duration (for videos)
|
||||
///
|
||||
/// * [String] filename:
|
||||
/// Filename
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
/// Mark as favorite
|
||||
///
|
||||
/// * [String] livePhotoVideoId:
|
||||
/// Live photo video ID
|
||||
///
|
||||
/// * [List<AssetMetadataUpsertItemDto>] metadata:
|
||||
/// Asset metadata items
|
||||
///
|
||||
/// * [MultipartFile] sidecarData:
|
||||
/// Sidecar file data
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Asset visibility
|
||||
Future<AssetMediaResponseDto?> uploadAsset(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, String? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List<AssetMetadataUpsertItemDto>? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
|
||||
final response = await uploadAssetWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, slug: slug, xImmichChecksum: xImmichChecksum, duration: duration, filename: filename, isFavorite: isFavorite, livePhotoVideoId: livePhotoVideoId, metadata: metadata, sidecarData: sidecarData, visibility: visibility, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -1700,10 +1758,12 @@ class AssetsApi {
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [bool] edited:
|
||||
/// Return edited asset if available
|
||||
///
|
||||
/// * [String] key:
|
||||
///
|
||||
/// * [AssetMediaSize] size:
|
||||
/// Asset media size
|
||||
///
|
||||
/// * [String] slug:
|
||||
Future<Response> viewAssetWithHttpInfo(String id, { bool? edited, String? key, AssetMediaSize? size, String? slug, }) async {
|
||||
@@ -1754,10 +1814,12 @@ class AssetsApi {
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [bool] edited:
|
||||
/// Return edited asset if available
|
||||
///
|
||||
/// * [String] key:
|
||||
///
|
||||
/// * [AssetMediaSize] size:
|
||||
/// Asset media size
|
||||
///
|
||||
/// * [String] slug:
|
||||
Future<MultipartFile?> viewAsset(String id, { bool? edited, String? key, AssetMediaSize? size, String? slug, }) async {
|
||||
|
||||
+20
@@ -82,6 +82,7 @@ class DeprecatedApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] deviceId (required):
|
||||
/// Device ID
|
||||
Future<Response> getAllUserAssetsByDeviceIdWithHttpInfo(String deviceId,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/device/{deviceId}'
|
||||
@@ -115,6 +116,7 @@ class DeprecatedApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] deviceId (required):
|
||||
/// Device ID
|
||||
Future<List<String>?> getAllUserAssetsByDeviceId(String deviceId,) async {
|
||||
final response = await getAllUserAssetsByDeviceIdWithHttpInfo(deviceId,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -305,6 +307,7 @@ class DeprecatedApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [num] count:
|
||||
/// Number of random assets to return
|
||||
Future<Response> getRandomWithHttpInfo({ num? count, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/random';
|
||||
@@ -341,6 +344,7 @@ class DeprecatedApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [num] count:
|
||||
/// Number of random assets to return
|
||||
Future<List<AssetResponseDto>?> getRandom({ num? count, }) async {
|
||||
final response = await getRandomWithHttpInfo( count: count, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -370,22 +374,29 @@ class DeprecatedApi {
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [MultipartFile] assetData (required):
|
||||
/// Asset file data
|
||||
///
|
||||
/// * [String] deviceAssetId (required):
|
||||
/// Device asset ID
|
||||
///
|
||||
/// * [String] deviceId (required):
|
||||
/// Device ID
|
||||
///
|
||||
/// * [DateTime] fileCreatedAt (required):
|
||||
/// File creation date
|
||||
///
|
||||
/// * [DateTime] fileModifiedAt (required):
|
||||
/// File modification date
|
||||
///
|
||||
/// * [String] key:
|
||||
///
|
||||
/// * [String] slug:
|
||||
///
|
||||
/// * [String] duration:
|
||||
/// Duration (for videos)
|
||||
///
|
||||
/// * [String] filename:
|
||||
/// Filename
|
||||
Future<Response> replaceAssetWithHttpInfo(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? duration, String? filename, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/assets/{id}/original'
|
||||
@@ -462,22 +473,29 @@ class DeprecatedApi {
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [MultipartFile] assetData (required):
|
||||
/// Asset file data
|
||||
///
|
||||
/// * [String] deviceAssetId (required):
|
||||
/// Device asset ID
|
||||
///
|
||||
/// * [String] deviceId (required):
|
||||
/// Device ID
|
||||
///
|
||||
/// * [DateTime] fileCreatedAt (required):
|
||||
/// File creation date
|
||||
///
|
||||
/// * [DateTime] fileModifiedAt (required):
|
||||
/// File modification date
|
||||
///
|
||||
/// * [String] key:
|
||||
///
|
||||
/// * [String] slug:
|
||||
///
|
||||
/// * [String] duration:
|
||||
/// Duration (for videos)
|
||||
///
|
||||
/// * [String] filename:
|
||||
/// Filename
|
||||
Future<AssetMediaResponseDto?> replaceAsset(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? duration, String? filename, }) async {
|
||||
final response = await replaceAssetWithHttpInfo(id, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, slug: slug, duration: duration, filename: filename, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -502,6 +520,7 @@ class DeprecatedApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [QueueCommandDto] queueCommandDto (required):
|
||||
Future<Response> runQueueCommandLegacyWithHttpInfo(QueueName name, QueueCommandDto queueCommandDto,) async {
|
||||
@@ -537,6 +556,7 @@ class DeprecatedApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [QueueCommandDto] queueCommandDto (required):
|
||||
Future<QueueResponseLegacyDto?> runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async {
|
||||
|
||||
Generated
+2
@@ -126,6 +126,7 @@ class FacesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
/// Face ID
|
||||
Future<Response> getFacesWithHttpInfo(String id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/faces';
|
||||
@@ -160,6 +161,7 @@ class FacesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
/// Face ID
|
||||
Future<List<AssetFaceResponseDto>?> getFaces(String id,) async {
|
||||
final response = await getFacesWithHttpInfo(id,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
Generated
+2
@@ -121,6 +121,7 @@ class JobsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [QueueCommandDto] queueCommandDto (required):
|
||||
Future<Response> runQueueCommandLegacyWithHttpInfo(QueueName name, QueueCommandDto queueCommandDto,) async {
|
||||
@@ -156,6 +157,7 @@ class JobsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [QueueCommandDto] queueCommandDto (required):
|
||||
Future<QueueResponseLegacyDto?> runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async {
|
||||
|
||||
Generated
+16
@@ -25,16 +25,22 @@ class MapApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [DateTime] fileCreatedAfter:
|
||||
/// Filter assets created after this date
|
||||
///
|
||||
/// * [DateTime] fileCreatedBefore:
|
||||
/// Filter assets created before this date
|
||||
///
|
||||
/// * [bool] isArchived:
|
||||
/// Filter by archived status
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
/// Filter by favorite status
|
||||
///
|
||||
/// * [bool] withPartners:
|
||||
/// Include partner assets
|
||||
///
|
||||
/// * [bool] withSharedAlbums:
|
||||
/// Include shared album assets
|
||||
Future<Response> getMapMarkersWithHttpInfo({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, bool? withPartners, bool? withSharedAlbums, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/map/markers';
|
||||
@@ -86,16 +92,22 @@ class MapApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [DateTime] fileCreatedAfter:
|
||||
/// Filter assets created after this date
|
||||
///
|
||||
/// * [DateTime] fileCreatedBefore:
|
||||
/// Filter assets created before this date
|
||||
///
|
||||
/// * [bool] isArchived:
|
||||
/// Filter by archived status
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
/// Filter by favorite status
|
||||
///
|
||||
/// * [bool] withPartners:
|
||||
/// Include partner assets
|
||||
///
|
||||
/// * [bool] withSharedAlbums:
|
||||
/// Include shared album assets
|
||||
Future<List<MapMarkerResponseDto>?> getMapMarkers({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, bool? withPartners, bool? withSharedAlbums, }) async {
|
||||
final response = await getMapMarkersWithHttpInfo( fileCreatedAfter: fileCreatedAfter, fileCreatedBefore: fileCreatedBefore, isArchived: isArchived, isFavorite: isFavorite, withPartners: withPartners, withSharedAlbums: withSharedAlbums, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -123,8 +135,10 @@ class MapApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [double] lat (required):
|
||||
/// Latitude (-90 to 90)
|
||||
///
|
||||
/// * [double] lon (required):
|
||||
/// Longitude (-180 to 180)
|
||||
Future<Response> reverseGeocodeWithHttpInfo(double lat, double lon,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/map/reverse-geocode';
|
||||
@@ -160,8 +174,10 @@ class MapApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [double] lat (required):
|
||||
/// Latitude (-90 to 90)
|
||||
///
|
||||
/// * [double] lon (required):
|
||||
/// Longitude (-180 to 180)
|
||||
Future<List<MapReverseGeocodeResponseDto>?> reverseGeocode(double lat, double lon,) async {
|
||||
final response = await reverseGeocodeWithHttpInfo(lat, lon,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
Generated
+20
@@ -251,17 +251,22 @@ class MemoriesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [DateTime] for_:
|
||||
/// Filter by date
|
||||
///
|
||||
/// * [bool] isSaved:
|
||||
/// Filter by saved status
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
/// Include trashed memories
|
||||
///
|
||||
/// * [MemorySearchOrder] order:
|
||||
/// Sort order
|
||||
///
|
||||
/// * [int] size:
|
||||
/// Number of memories to return
|
||||
///
|
||||
/// * [MemoryType] type:
|
||||
/// Memory type
|
||||
Future<Response> memoriesStatisticsWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/memories/statistics';
|
||||
@@ -313,17 +318,22 @@ class MemoriesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [DateTime] for_:
|
||||
/// Filter by date
|
||||
///
|
||||
/// * [bool] isSaved:
|
||||
/// Filter by saved status
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
/// Include trashed memories
|
||||
///
|
||||
/// * [MemorySearchOrder] order:
|
||||
/// Sort order
|
||||
///
|
||||
/// * [int] size:
|
||||
/// Number of memories to return
|
||||
///
|
||||
/// * [MemoryType] type:
|
||||
/// Memory type
|
||||
Future<MemoryStatisticsResponseDto?> memoriesStatistics({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async {
|
||||
final response = await memoriesStatisticsWithHttpInfo( for_: for_, isSaved: isSaved, isTrashed: isTrashed, order: order, size: size, type: type, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -412,17 +422,22 @@ class MemoriesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [DateTime] for_:
|
||||
/// Filter by date
|
||||
///
|
||||
/// * [bool] isSaved:
|
||||
/// Filter by saved status
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
/// Include trashed memories
|
||||
///
|
||||
/// * [MemorySearchOrder] order:
|
||||
/// Sort order
|
||||
///
|
||||
/// * [int] size:
|
||||
/// Number of memories to return
|
||||
///
|
||||
/// * [MemoryType] type:
|
||||
/// Memory type
|
||||
Future<Response> searchMemoriesWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/memories';
|
||||
@@ -474,17 +489,22 @@ class MemoriesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [DateTime] for_:
|
||||
/// Filter by date
|
||||
///
|
||||
/// * [bool] isSaved:
|
||||
/// Filter by saved status
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
/// Include trashed memories
|
||||
///
|
||||
/// * [MemorySearchOrder] order:
|
||||
/// Sort order
|
||||
///
|
||||
/// * [int] size:
|
||||
/// Number of memories to return
|
||||
///
|
||||
/// * [MemoryType] type:
|
||||
/// Memory type
|
||||
Future<List<MemoryResponseDto>?> searchMemories({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async {
|
||||
final response = await searchMemoriesWithHttpInfo( for_: for_, isSaved: isSaved, isTrashed: isTrashed, order: order, size: size, type: type, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
+8
@@ -179,12 +179,16 @@ class NotificationsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id:
|
||||
/// Filter by notification ID
|
||||
///
|
||||
/// * [NotificationLevel] level:
|
||||
/// Filter by notification level
|
||||
///
|
||||
/// * [NotificationType] type:
|
||||
/// Filter by notification type
|
||||
///
|
||||
/// * [bool] unread:
|
||||
/// Filter by unread status
|
||||
Future<Response> getNotificationsWithHttpInfo({ String? id, NotificationLevel? level, NotificationType? type, bool? unread, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/notifications';
|
||||
@@ -230,12 +234,16 @@ class NotificationsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id:
|
||||
/// Filter by notification ID
|
||||
///
|
||||
/// * [NotificationLevel] level:
|
||||
/// Filter by notification level
|
||||
///
|
||||
/// * [NotificationType] type:
|
||||
/// Filter by notification type
|
||||
///
|
||||
/// * [bool] unread:
|
||||
/// Filter by unread status
|
||||
Future<List<NotificationDto>?> getNotifications({ String? id, NotificationLevel? level, NotificationType? type, bool? unread, }) async {
|
||||
final response = await getNotificationsWithHttpInfo( id: id, level: level, type: type, unread: unread, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
Generated
+2
@@ -138,6 +138,7 @@ class PartnersApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [PartnerDirection] direction (required):
|
||||
/// Partner direction
|
||||
Future<Response> getPartnersWithHttpInfo(PartnerDirection direction,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/partners';
|
||||
@@ -172,6 +173,7 @@ class PartnersApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [PartnerDirection] direction (required):
|
||||
/// Partner direction
|
||||
Future<List<PartnerResponseDto>?> getPartners(PartnerDirection direction,) async {
|
||||
final response = await getPartnersWithHttpInfo(direction,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
Generated
+6
@@ -178,8 +178,10 @@ class PeopleApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] closestAssetId:
|
||||
/// Closest asset ID for similarity search
|
||||
///
|
||||
/// * [String] closestPersonId:
|
||||
/// Closest person ID for similarity search
|
||||
///
|
||||
/// * [num] page:
|
||||
/// Page number for pagination
|
||||
@@ -188,6 +190,7 @@ class PeopleApi {
|
||||
/// Number of items per page
|
||||
///
|
||||
/// * [bool] withHidden:
|
||||
/// Include hidden people
|
||||
Future<Response> getAllPeopleWithHttpInfo({ String? closestAssetId, String? closestPersonId, num? page, num? size, bool? withHidden, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/people';
|
||||
@@ -236,8 +239,10 @@ class PeopleApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] closestAssetId:
|
||||
/// Closest asset ID for similarity search
|
||||
///
|
||||
/// * [String] closestPersonId:
|
||||
/// Closest person ID for similarity search
|
||||
///
|
||||
/// * [num] page:
|
||||
/// Page number for pagination
|
||||
@@ -246,6 +251,7 @@ class PeopleApi {
|
||||
/// Number of items per page
|
||||
///
|
||||
/// * [bool] withHidden:
|
||||
/// Include hidden people
|
||||
Future<PeopleResponseDto?> getAllPeople({ String? closestAssetId, String? closestPersonId, num? page, num? size, bool? withHidden, }) async {
|
||||
final response = await getAllPeopleWithHttpInfo( closestAssetId: closestAssetId, closestPersonId: closestPersonId, page: page, size: size, withHidden: withHidden, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
Generated
+10
@@ -25,6 +25,7 @@ class QueuesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [QueueDeleteDto] queueDeleteDto (required):
|
||||
Future<Response> emptyQueueWithHttpInfo(QueueName name, QueueDeleteDto queueDeleteDto,) async {
|
||||
@@ -60,6 +61,7 @@ class QueuesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [QueueDeleteDto] queueDeleteDto (required):
|
||||
Future<void> emptyQueue(QueueName name, QueueDeleteDto queueDeleteDto,) async {
|
||||
@@ -78,6 +80,7 @@ class QueuesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
Future<Response> getQueueWithHttpInfo(QueueName name,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/queues/{name}'
|
||||
@@ -111,6 +114,7 @@ class QueuesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
Future<QueueResponseDto?> getQueue(QueueName name,) async {
|
||||
final response = await getQueueWithHttpInfo(name,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -135,8 +139,10 @@ class QueuesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [List<QueueJobStatus>] status:
|
||||
/// Filter jobs by status
|
||||
Future<Response> getQueueJobsWithHttpInfo(QueueName name, { List<QueueJobStatus>? status, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/queues/{name}/jobs'
|
||||
@@ -174,8 +180,10 @@ class QueuesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [List<QueueJobStatus>] status:
|
||||
/// Filter jobs by status
|
||||
Future<List<QueueJobResponseDto>?> getQueueJobs(QueueName name, { List<QueueJobStatus>? status, }) async {
|
||||
final response = await getQueueJobsWithHttpInfo(name, status: status, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -254,6 +262,7 @@ class QueuesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [QueueUpdateDto] queueUpdateDto (required):
|
||||
Future<Response> updateQueueWithHttpInfo(QueueName name, QueueUpdateDto queueUpdateDto,) async {
|
||||
@@ -289,6 +298,7 @@ class QueuesApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [QueueName] name (required):
|
||||
/// Queue name
|
||||
///
|
||||
/// * [QueueUpdateDto] queueUpdateDto (required):
|
||||
Future<QueueResponseDto?> updateQueue(QueueName name, QueueUpdateDto queueUpdateDto,) async {
|
||||
|
||||
Generated
+84
@@ -127,18 +127,25 @@ class SearchApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [SearchSuggestionType] type (required):
|
||||
/// Suggestion type
|
||||
///
|
||||
/// * [String] country:
|
||||
/// Filter by country
|
||||
///
|
||||
/// * [bool] includeNull:
|
||||
/// Include null values in suggestions
|
||||
///
|
||||
/// * [String] lensModel:
|
||||
/// Filter by lens model
|
||||
///
|
||||
/// * [String] make:
|
||||
/// Filter by camera make
|
||||
///
|
||||
/// * [String] model:
|
||||
/// Filter by camera model
|
||||
///
|
||||
/// * [String] state:
|
||||
/// Filter by state/province
|
||||
Future<Response> getSearchSuggestionsWithHttpInfo(SearchSuggestionType type, { String? country, bool? includeNull, String? lensModel, String? make, String? model, String? state, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/search/suggestions';
|
||||
@@ -191,18 +198,25 @@ class SearchApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [SearchSuggestionType] type (required):
|
||||
/// Suggestion type
|
||||
///
|
||||
/// * [String] country:
|
||||
/// Filter by country
|
||||
///
|
||||
/// * [bool] includeNull:
|
||||
/// Include null values in suggestions
|
||||
///
|
||||
/// * [String] lensModel:
|
||||
/// Filter by lens model
|
||||
///
|
||||
/// * [String] make:
|
||||
/// Filter by camera make
|
||||
///
|
||||
/// * [String] model:
|
||||
/// Filter by camera model
|
||||
///
|
||||
/// * [String] state:
|
||||
/// Filter by state/province
|
||||
Future<List<String>?> getSearchSuggestions(SearchSuggestionType type, { String? country, bool? includeNull, String? lensModel, String? make, String? model, String? state, }) async {
|
||||
final response = await getSearchSuggestionsWithHttpInfo(type, country: country, includeNull: includeNull, lensModel: lensModel, make: make, model: model, state: state, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -342,68 +356,100 @@ class SearchApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [List<String>] albumIds:
|
||||
/// Filter by album IDs
|
||||
///
|
||||
/// * [String] city:
|
||||
/// Filter by city name
|
||||
///
|
||||
/// * [String] country:
|
||||
/// Filter by country name
|
||||
///
|
||||
/// * [DateTime] createdAfter:
|
||||
/// Filter by creation date (after)
|
||||
///
|
||||
/// * [DateTime] createdBefore:
|
||||
/// Filter by creation date (before)
|
||||
///
|
||||
/// * [String] deviceId:
|
||||
/// Device ID to filter by
|
||||
///
|
||||
/// * [bool] isEncoded:
|
||||
/// Filter by encoded status
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
/// Filter by favorite status
|
||||
///
|
||||
/// * [bool] isMotion:
|
||||
/// Filter by motion photo status
|
||||
///
|
||||
/// * [bool] isNotInAlbum:
|
||||
/// Filter assets not in any album
|
||||
///
|
||||
/// * [bool] isOffline:
|
||||
/// Filter by offline status
|
||||
///
|
||||
/// * [String] lensModel:
|
||||
/// Filter by lens model
|
||||
///
|
||||
/// * [String] libraryId:
|
||||
/// Library ID to filter by
|
||||
///
|
||||
/// * [String] make:
|
||||
/// Filter by camera make
|
||||
///
|
||||
/// * [int] minFileSize:
|
||||
/// Minimum file size in bytes
|
||||
///
|
||||
/// * [String] model:
|
||||
/// Filter by camera model
|
||||
///
|
||||
/// * [String] ocr:
|
||||
/// Filter by OCR text content
|
||||
///
|
||||
/// * [List<String>] personIds:
|
||||
/// Filter by person IDs
|
||||
///
|
||||
/// * [num] rating:
|
||||
/// Filter by rating
|
||||
///
|
||||
/// * [num] size:
|
||||
/// Number of results to return
|
||||
///
|
||||
/// * [String] state:
|
||||
/// Filter by state/province name
|
||||
///
|
||||
/// * [List<String>] tagIds:
|
||||
/// Filter by tag IDs
|
||||
///
|
||||
/// * [DateTime] takenAfter:
|
||||
/// Filter by taken date (after)
|
||||
///
|
||||
/// * [DateTime] takenBefore:
|
||||
/// Filter by taken date (before)
|
||||
///
|
||||
/// * [DateTime] trashedAfter:
|
||||
/// Filter by trash date (after)
|
||||
///
|
||||
/// * [DateTime] trashedBefore:
|
||||
/// Filter by trash date (before)
|
||||
///
|
||||
/// * [AssetTypeEnum] type:
|
||||
/// Asset type filter
|
||||
///
|
||||
/// * [DateTime] updatedAfter:
|
||||
/// Filter by update date (after)
|
||||
///
|
||||
/// * [DateTime] updatedBefore:
|
||||
/// Filter by update date (before)
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Filter by visibility
|
||||
///
|
||||
/// * [bool] withDeleted:
|
||||
/// Include deleted assets
|
||||
///
|
||||
/// * [bool] withExif:
|
||||
/// Include EXIF data in response
|
||||
Future<Response> searchLargeAssetsWithHttpInfo({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List<String>? personIds, num? rating, num? size, String? state, List<String>? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/search/large-assets';
|
||||
@@ -533,68 +579,100 @@ class SearchApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [List<String>] albumIds:
|
||||
/// Filter by album IDs
|
||||
///
|
||||
/// * [String] city:
|
||||
/// Filter by city name
|
||||
///
|
||||
/// * [String] country:
|
||||
/// Filter by country name
|
||||
///
|
||||
/// * [DateTime] createdAfter:
|
||||
/// Filter by creation date (after)
|
||||
///
|
||||
/// * [DateTime] createdBefore:
|
||||
/// Filter by creation date (before)
|
||||
///
|
||||
/// * [String] deviceId:
|
||||
/// Device ID to filter by
|
||||
///
|
||||
/// * [bool] isEncoded:
|
||||
/// Filter by encoded status
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
/// Filter by favorite status
|
||||
///
|
||||
/// * [bool] isMotion:
|
||||
/// Filter by motion photo status
|
||||
///
|
||||
/// * [bool] isNotInAlbum:
|
||||
/// Filter assets not in any album
|
||||
///
|
||||
/// * [bool] isOffline:
|
||||
/// Filter by offline status
|
||||
///
|
||||
/// * [String] lensModel:
|
||||
/// Filter by lens model
|
||||
///
|
||||
/// * [String] libraryId:
|
||||
/// Library ID to filter by
|
||||
///
|
||||
/// * [String] make:
|
||||
/// Filter by camera make
|
||||
///
|
||||
/// * [int] minFileSize:
|
||||
/// Minimum file size in bytes
|
||||
///
|
||||
/// * [String] model:
|
||||
/// Filter by camera model
|
||||
///
|
||||
/// * [String] ocr:
|
||||
/// Filter by OCR text content
|
||||
///
|
||||
/// * [List<String>] personIds:
|
||||
/// Filter by person IDs
|
||||
///
|
||||
/// * [num] rating:
|
||||
/// Filter by rating
|
||||
///
|
||||
/// * [num] size:
|
||||
/// Number of results to return
|
||||
///
|
||||
/// * [String] state:
|
||||
/// Filter by state/province name
|
||||
///
|
||||
/// * [List<String>] tagIds:
|
||||
/// Filter by tag IDs
|
||||
///
|
||||
/// * [DateTime] takenAfter:
|
||||
/// Filter by taken date (after)
|
||||
///
|
||||
/// * [DateTime] takenBefore:
|
||||
/// Filter by taken date (before)
|
||||
///
|
||||
/// * [DateTime] trashedAfter:
|
||||
/// Filter by trash date (after)
|
||||
///
|
||||
/// * [DateTime] trashedBefore:
|
||||
/// Filter by trash date (before)
|
||||
///
|
||||
/// * [AssetTypeEnum] type:
|
||||
/// Asset type filter
|
||||
///
|
||||
/// * [DateTime] updatedAfter:
|
||||
/// Filter by update date (after)
|
||||
///
|
||||
/// * [DateTime] updatedBefore:
|
||||
/// Filter by update date (before)
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Filter by visibility
|
||||
///
|
||||
/// * [bool] withDeleted:
|
||||
/// Include deleted assets
|
||||
///
|
||||
/// * [bool] withExif:
|
||||
/// Include EXIF data in response
|
||||
Future<List<AssetResponseDto>?> searchLargeAssets({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List<String>? personIds, num? rating, num? size, String? state, List<String>? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async {
|
||||
final response = await searchLargeAssetsWithHttpInfo( albumIds: albumIds, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceId: deviceId, isEncoded: isEncoded, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, lensModel: lensModel, libraryId: libraryId, make: make, minFileSize: minFileSize, model: model, ocr: ocr, personIds: personIds, rating: rating, size: size, state: state, tagIds: tagIds, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, visibility: visibility, withDeleted: withDeleted, withExif: withExif, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -622,8 +700,10 @@ class SearchApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] name (required):
|
||||
/// Person name to search for
|
||||
///
|
||||
/// * [bool] withHidden:
|
||||
/// Include hidden people
|
||||
Future<Response> searchPersonWithHttpInfo(String name, { bool? withHidden, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/search/person';
|
||||
@@ -661,8 +741,10 @@ class SearchApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] name (required):
|
||||
/// Person name to search for
|
||||
///
|
||||
/// * [bool] withHidden:
|
||||
/// Include hidden people
|
||||
Future<List<PersonResponseDto>?> searchPerson(String name, { bool? withHidden, }) async {
|
||||
final response = await searchPersonWithHttpInfo(name, withHidden: withHidden, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -690,6 +772,7 @@ class SearchApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] name (required):
|
||||
/// Place name to search for
|
||||
Future<Response> searchPlacesWithHttpInfo(String name,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/search/places';
|
||||
@@ -724,6 +807,7 @@ class SearchApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] name (required):
|
||||
/// Place name to search for
|
||||
Future<List<PlacesResponseDto>?> searchPlaces(String name,) async {
|
||||
final response = await searchPlacesWithHttpInfo(name,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
+8
@@ -160,8 +160,10 @@ class SharedLinksApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId:
|
||||
/// Filter by album ID
|
||||
///
|
||||
/// * [String] id:
|
||||
/// Filter by shared link ID
|
||||
Future<Response> getAllSharedLinksWithHttpInfo({ String? albumId, String? id, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/shared-links';
|
||||
@@ -201,8 +203,10 @@ class SharedLinksApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId:
|
||||
/// Filter by album ID
|
||||
///
|
||||
/// * [String] id:
|
||||
/// Filter by shared link ID
|
||||
Future<List<SharedLinkResponseDto>?> getAllSharedLinks({ String? albumId, String? id, }) async {
|
||||
final response = await getAllSharedLinksWithHttpInfo( albumId: albumId, id: id, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -232,10 +236,12 @@ class SharedLinksApi {
|
||||
/// * [String] key:
|
||||
///
|
||||
/// * [String] password:
|
||||
/// Link password
|
||||
///
|
||||
/// * [String] slug:
|
||||
///
|
||||
/// * [String] token:
|
||||
/// Access token
|
||||
Future<Response> getMySharedLinkWithHttpInfo({ String? key, String? password, String? slug, String? token, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/shared-links/me';
|
||||
@@ -283,10 +289,12 @@ class SharedLinksApi {
|
||||
/// * [String] key:
|
||||
///
|
||||
/// * [String] password:
|
||||
/// Link password
|
||||
///
|
||||
/// * [String] slug:
|
||||
///
|
||||
/// * [String] token:
|
||||
/// Access token
|
||||
Future<SharedLinkResponseDto?> getMySharedLink({ String? key, String? password, String? slug, String? token, }) async {
|
||||
final response = await getMySharedLinkWithHttpInfo( key: key, password: password, slug: slug, token: token, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
Generated
+2
@@ -289,6 +289,7 @@ class StacksApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] primaryAssetId:
|
||||
/// Filter by primary asset ID
|
||||
Future<Response> searchStacksWithHttpInfo({ String? primaryAssetId, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/stacks';
|
||||
@@ -325,6 +326,7 @@ class StacksApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] primaryAssetId:
|
||||
/// Filter by primary asset ID
|
||||
Future<List<StackResponseDto>?> searchStacks({ String? primaryAssetId, }) async {
|
||||
final response = await searchStacksWithHttpInfo( primaryAssetId: primaryAssetId, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
+10
@@ -318,10 +318,13 @@ class UsersAdminApi {
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
/// Filter by favorite status
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
/// Filter by trash status
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Filter by visibility
|
||||
Future<Response> getUserStatisticsAdminWithHttpInfo(String id, { bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/admin/users/{id}/statistics'
|
||||
@@ -367,10 +370,13 @@ class UsersAdminApi {
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
/// Filter by favorite status
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
/// Filter by trash status
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
/// Filter by visibility
|
||||
Future<AssetStatsResponseDto?> getUserStatisticsAdmin(String id, { bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async {
|
||||
final response = await getUserStatisticsAdminWithHttpInfo(id, isFavorite: isFavorite, isTrashed: isTrashed, visibility: visibility, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -452,8 +458,10 @@ class UsersAdminApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id:
|
||||
/// User ID filter
|
||||
///
|
||||
/// * [bool] withDeleted:
|
||||
/// Include deleted users
|
||||
Future<Response> searchUsersAdminWithHttpInfo({ String? id, bool? withDeleted, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/admin/users';
|
||||
@@ -493,8 +501,10 @@ class UsersAdminApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id:
|
||||
/// User ID filter
|
||||
///
|
||||
/// * [bool] withDeleted:
|
||||
/// Include deleted users
|
||||
Future<List<UserAdminResponseDto>?> searchUsersAdmin({ String? id, bool? withDeleted, }) async {
|
||||
final response = await searchUsersAdminWithHttpInfo( id: id, withDeleted: withDeleted, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
Generated
+2
@@ -25,6 +25,7 @@ class UsersApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [MultipartFile] file (required):
|
||||
/// Profile image file
|
||||
Future<Response> createProfileImageWithHttpInfo(MultipartFile file,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/users/profile-image';
|
||||
@@ -67,6 +68,7 @@ class UsersApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [MultipartFile] file (required):
|
||||
/// Profile image file
|
||||
Future<CreateProfileImageResponseDto?> createProfileImage(MultipartFile file,) async {
|
||||
final response = await createProfileImageWithHttpInfo(file,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
|
||||
@@ -19,8 +19,10 @@ class ActivityCreateDto {
|
||||
required this.type,
|
||||
});
|
||||
|
||||
/// Album ID
|
||||
String albumId;
|
||||
|
||||
/// Asset ID (if activity is for an asset)
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -29,6 +31,7 @@ class ActivityCreateDto {
|
||||
///
|
||||
String? assetId;
|
||||
|
||||
/// Comment text (required if type is comment)
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -37,6 +40,7 @@ class ActivityCreateDto {
|
||||
///
|
||||
String? comment;
|
||||
|
||||
/// Activity type (like or comment)
|
||||
ReactionType type;
|
||||
|
||||
@override
|
||||
|
||||
@@ -21,14 +21,19 @@ class ActivityResponseDto {
|
||||
required this.user,
|
||||
});
|
||||
|
||||
/// Asset ID (if activity is for an asset)
|
||||
String? assetId;
|
||||
|
||||
/// Comment text (for comment activities)
|
||||
String? comment;
|
||||
|
||||
/// Creation date
|
||||
DateTime createdAt;
|
||||
|
||||
/// Activity ID
|
||||
String id;
|
||||
|
||||
/// Activity type
|
||||
ReactionType type;
|
||||
|
||||
UserResponseDto user;
|
||||
|
||||
@@ -17,8 +17,10 @@ class ActivityStatisticsResponseDto {
|
||||
required this.likes,
|
||||
});
|
||||
|
||||
/// Number of comments
|
||||
int comments;
|
||||
|
||||
/// Number of likes
|
||||
int likes;
|
||||
|
||||
@override
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ class AddUsersDto {
|
||||
this.albumUsers = const [],
|
||||
});
|
||||
|
||||
/// Album users to add
|
||||
List<AlbumUserAddDto> albumUsers;
|
||||
|
||||
@override
|
||||
|
||||
@@ -16,6 +16,7 @@ class AdminOnboardingUpdateDto {
|
||||
required this.isOnboarded,
|
||||
});
|
||||
|
||||
/// Is admin onboarded
|
||||
bool isOnboarded;
|
||||
|
||||
@override
|
||||
|
||||
+15
@@ -34,22 +34,28 @@ class AlbumResponseDto {
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
/// Album name
|
||||
String albumName;
|
||||
|
||||
/// Thumbnail asset ID
|
||||
String? albumThumbnailAssetId;
|
||||
|
||||
List<AlbumUserResponseDto> albumUsers;
|
||||
|
||||
/// Number of assets
|
||||
int assetCount;
|
||||
|
||||
List<AssetResponseDto> assets;
|
||||
|
||||
List<ContributorCountResponseDto> contributorCounts;
|
||||
|
||||
/// Creation date
|
||||
DateTime createdAt;
|
||||
|
||||
/// Album description
|
||||
String description;
|
||||
|
||||
/// End date (latest asset)
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -58,12 +64,16 @@ class AlbumResponseDto {
|
||||
///
|
||||
DateTime? endDate;
|
||||
|
||||
/// Has shared link
|
||||
bool hasSharedLink;
|
||||
|
||||
/// Album ID
|
||||
String id;
|
||||
|
||||
/// Activity feed enabled
|
||||
bool isActivityEnabled;
|
||||
|
||||
/// Last modified asset timestamp
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -72,6 +82,7 @@ class AlbumResponseDto {
|
||||
///
|
||||
DateTime? lastModifiedAssetTimestamp;
|
||||
|
||||
/// Asset sort order
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -82,10 +93,13 @@ class AlbumResponseDto {
|
||||
|
||||
UserResponseDto owner;
|
||||
|
||||
/// Owner user ID
|
||||
String ownerId;
|
||||
|
||||
/// Is shared album
|
||||
bool shared;
|
||||
|
||||
/// Start date (earliest asset)
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -94,6 +108,7 @@ class AlbumResponseDto {
|
||||
///
|
||||
DateTime? startDate;
|
||||
|
||||
/// Last update date
|
||||
DateTime updatedAt;
|
||||
|
||||
@override
|
||||
|
||||
@@ -18,10 +18,13 @@ class AlbumStatisticsResponseDto {
|
||||
required this.shared,
|
||||
});
|
||||
|
||||
/// Number of non-shared albums
|
||||
int notShared;
|
||||
|
||||
/// Number of owned albums
|
||||
int owned;
|
||||
|
||||
/// Number of shared albums
|
||||
int shared;
|
||||
|
||||
@override
|
||||
|
||||
@@ -17,8 +17,10 @@ class AlbumUserAddDto {
|
||||
required this.userId,
|
||||
});
|
||||
|
||||
/// Album user role
|
||||
AlbumUserRole role;
|
||||
|
||||
/// User ID
|
||||
String userId;
|
||||
|
||||
@override
|
||||
|
||||
@@ -17,8 +17,10 @@ class AlbumUserCreateDto {
|
||||
required this.userId,
|
||||
});
|
||||
|
||||
/// Album user role
|
||||
AlbumUserRole role;
|
||||
|
||||
/// User ID
|
||||
String userId;
|
||||
|
||||
@override
|
||||
|
||||
@@ -17,6 +17,7 @@ class AlbumUserResponseDto {
|
||||
required this.user,
|
||||
});
|
||||
|
||||
/// Album user role
|
||||
AlbumUserRole role;
|
||||
|
||||
UserResponseDto user;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
|
||||
/// Album user role
|
||||
class AlbumUserRole {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AlbumUserRole._(this.value);
|
||||
|
||||
@@ -17,8 +17,10 @@ class AlbumsAddAssetsDto {
|
||||
this.assetIds = const [],
|
||||
});
|
||||
|
||||
/// Album IDs
|
||||
List<String> albumIds;
|
||||
|
||||
/// Asset IDs
|
||||
List<String> assetIds;
|
||||
|
||||
@override
|
||||
|
||||
@@ -17,6 +17,7 @@ class AlbumsAddAssetsResponseDto {
|
||||
required this.success,
|
||||
});
|
||||
|
||||
/// Error reason
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -25,6 +26,7 @@ class AlbumsAddAssetsResponseDto {
|
||||
///
|
||||
BulkIdErrorReason? error;
|
||||
|
||||
/// Operation success
|
||||
bool success;
|
||||
|
||||
@override
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ class AlbumsResponse {
|
||||
this.defaultAssetOrder = AssetOrder.desc,
|
||||
});
|
||||
|
||||
/// Default asset order for albums
|
||||
AssetOrder defaultAssetOrder;
|
||||
|
||||
@override
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ class AlbumsUpdate {
|
||||
this.defaultAssetOrder,
|
||||
});
|
||||
|
||||
/// Default asset order for albums
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
||||
@@ -17,6 +17,7 @@ class APIKeyCreateDto {
|
||||
this.permissions = const [],
|
||||
});
|
||||
|
||||
/// API key name
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -25,6 +26,7 @@ class APIKeyCreateDto {
|
||||
///
|
||||
String? name;
|
||||
|
||||
/// List of permissions
|
||||
List<Permission> permissions;
|
||||
|
||||
@override
|
||||
|
||||
@@ -19,6 +19,7 @@ class APIKeyCreateResponseDto {
|
||||
|
||||
APIKeyResponseDto apiKey;
|
||||
|
||||
/// API key secret (only shown once)
|
||||
String secret;
|
||||
|
||||
@override
|
||||
|
||||
@@ -20,14 +20,19 @@ class APIKeyResponseDto {
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
/// Creation date
|
||||
DateTime createdAt;
|
||||
|
||||
/// API key ID
|
||||
String id;
|
||||
|
||||
/// API key name
|
||||
String name;
|
||||
|
||||
/// List of permissions
|
||||
List<Permission> permissions;
|
||||
|
||||
/// Last update date
|
||||
DateTime updatedAt;
|
||||
|
||||
@override
|
||||
|
||||
@@ -17,6 +17,7 @@ class APIKeyUpdateDto {
|
||||
this.permissions = const [],
|
||||
});
|
||||
|
||||
/// API key name
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -25,6 +26,7 @@ class APIKeyUpdateDto {
|
||||
///
|
||||
String? name;
|
||||
|
||||
/// List of permissions
|
||||
List<Permission> permissions;
|
||||
|
||||
@override
|
||||
|
||||
@@ -17,6 +17,7 @@ class AssetBulkDeleteDto {
|
||||
this.ids = const [],
|
||||
});
|
||||
|
||||
/// Force delete even if in use
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -25,6 +26,7 @@ class AssetBulkDeleteDto {
|
||||
///
|
||||
bool? force;
|
||||
|
||||
/// IDs to process
|
||||
List<String> ids;
|
||||
|
||||
@override
|
||||
|
||||
+12
@@ -26,6 +26,7 @@ class AssetBulkUpdateDto {
|
||||
this.visibility,
|
||||
});
|
||||
|
||||
/// Original date and time
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -34,6 +35,7 @@ class AssetBulkUpdateDto {
|
||||
///
|
||||
String? dateTimeOriginal;
|
||||
|
||||
/// Relative time offset in seconds
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -42,6 +44,7 @@ class AssetBulkUpdateDto {
|
||||
///
|
||||
num? dateTimeRelative;
|
||||
|
||||
/// Asset description
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -50,10 +53,13 @@ class AssetBulkUpdateDto {
|
||||
///
|
||||
String? description;
|
||||
|
||||
/// Duplicate asset ID
|
||||
String? duplicateId;
|
||||
|
||||
/// Asset IDs to update
|
||||
List<String> ids;
|
||||
|
||||
/// Mark as favorite
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -62,6 +68,7 @@ class AssetBulkUpdateDto {
|
||||
///
|
||||
bool? isFavorite;
|
||||
|
||||
/// Latitude coordinate
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -70,6 +77,7 @@ class AssetBulkUpdateDto {
|
||||
///
|
||||
num? latitude;
|
||||
|
||||
/// Longitude coordinate
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -78,6 +86,8 @@ class AssetBulkUpdateDto {
|
||||
///
|
||||
num? longitude;
|
||||
|
||||
/// Rating
|
||||
///
|
||||
/// Minimum value: -1
|
||||
/// Maximum value: 5
|
||||
///
|
||||
@@ -88,6 +98,7 @@ class AssetBulkUpdateDto {
|
||||
///
|
||||
num? rating;
|
||||
|
||||
/// Time zone (IANA timezone)
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -96,6 +107,7 @@ class AssetBulkUpdateDto {
|
||||
///
|
||||
String? timeZone;
|
||||
|
||||
/// Asset visibility
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
||||
@@ -16,6 +16,7 @@ class AssetBulkUploadCheckDto {
|
||||
this.assets = const [],
|
||||
});
|
||||
|
||||
/// Assets to check
|
||||
List<AssetBulkUploadCheckItem> assets;
|
||||
|
||||
@override
|
||||
|
||||
+2
-1
@@ -17,9 +17,10 @@ class AssetBulkUploadCheckItem {
|
||||
required this.id,
|
||||
});
|
||||
|
||||
/// base64 or hex encoded sha1 hash
|
||||
/// Base64 or hex encoded SHA1 hash
|
||||
String checksum;
|
||||
|
||||
/// Asset ID
|
||||
String id;
|
||||
|
||||
@override
|
||||
|
||||
@@ -16,6 +16,7 @@ class AssetBulkUploadCheckResponseDto {
|
||||
this.results = const [],
|
||||
});
|
||||
|
||||
/// Upload check results
|
||||
List<AssetBulkUploadCheckResult> results;
|
||||
|
||||
@override
|
||||
|
||||
@@ -20,8 +20,10 @@ class AssetBulkUploadCheckResult {
|
||||
this.reason,
|
||||
});
|
||||
|
||||
/// Upload action
|
||||
AssetBulkUploadCheckResultActionEnum action;
|
||||
|
||||
/// Existing asset ID if duplicate
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -30,8 +32,10 @@ class AssetBulkUploadCheckResult {
|
||||
///
|
||||
String? assetId;
|
||||
|
||||
/// Asset ID
|
||||
String id;
|
||||
|
||||
/// Whether existing asset is trashed
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -40,6 +44,7 @@ class AssetBulkUploadCheckResult {
|
||||
///
|
||||
bool? isTrashed;
|
||||
|
||||
/// Rejection reason if rejected
|
||||
AssetBulkUploadCheckResultReasonEnum? reason;
|
||||
|
||||
@override
|
||||
@@ -150,7 +155,7 @@ class AssetBulkUploadCheckResult {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// Upload action
|
||||
class AssetBulkUploadCheckResultActionEnum {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetBulkUploadCheckResultActionEnum._(this.value);
|
||||
@@ -224,7 +229,7 @@ class AssetBulkUploadCheckResultActionEnumTypeTransformer {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Rejection reason if rejected
|
||||
class AssetBulkUploadCheckResultReasonEnum {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetBulkUploadCheckResultReasonEnum._(this.value);
|
||||
|
||||
+7
@@ -22,18 +22,25 @@ class AssetCopyDto {
|
||||
required this.targetId,
|
||||
});
|
||||
|
||||
/// Copy album associations
|
||||
bool albums;
|
||||
|
||||
/// Copy favorite status
|
||||
bool favorite;
|
||||
|
||||
/// Copy shared links
|
||||
bool sharedLinks;
|
||||
|
||||
/// Copy sidecar file
|
||||
bool sidecar;
|
||||
|
||||
/// Source asset ID
|
||||
String sourceId;
|
||||
|
||||
/// Copy stack association
|
||||
bool stack;
|
||||
|
||||
/// Target asset ID
|
||||
String targetId;
|
||||
|
||||
@override
|
||||
|
||||
@@ -17,8 +17,10 @@ class AssetDeltaSyncDto {
|
||||
this.userIds = const [],
|
||||
});
|
||||
|
||||
/// Sync assets updated after this date
|
||||
DateTime updatedAfter;
|
||||
|
||||
/// User IDs to sync
|
||||
List<String> userIds;
|
||||
|
||||
@override
|
||||
|
||||
@@ -18,10 +18,13 @@ class AssetDeltaSyncResponseDto {
|
||||
this.upserted = const [],
|
||||
});
|
||||
|
||||
/// Deleted asset IDs
|
||||
List<String> deleted;
|
||||
|
||||
/// Whether full sync is needed
|
||||
bool needsFullSync;
|
||||
|
||||
/// Upserted assets
|
||||
List<AssetResponseDto> upserted;
|
||||
|
||||
@override
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
|
||||
/// Type of edit action to perform
|
||||
class AssetEditAction {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetEditAction._(this.value);
|
||||
|
||||
@@ -17,6 +17,7 @@ class AssetEditActionCrop {
|
||||
required this.parameters,
|
||||
});
|
||||
|
||||
/// Type of edit action to perform
|
||||
AssetEditAction action;
|
||||
|
||||
CropParameters parameters;
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ class AssetEditActionListDto {
|
||||
this.edits = const [],
|
||||
});
|
||||
|
||||
/// list of edits
|
||||
/// List of edit actions to apply (crop, rotate, or mirror)
|
||||
List<AssetEditActionListDtoEditsInner> edits;
|
||||
|
||||
@override
|
||||
|
||||
@@ -17,6 +17,7 @@ class AssetEditActionListDtoEditsInner {
|
||||
required this.parameters,
|
||||
});
|
||||
|
||||
/// Type of edit action to perform
|
||||
AssetEditAction action;
|
||||
|
||||
MirrorParameters parameters;
|
||||
|
||||
@@ -17,6 +17,7 @@ class AssetEditActionMirror {
|
||||
required this.parameters,
|
||||
});
|
||||
|
||||
/// Type of edit action to perform
|
||||
AssetEditAction action;
|
||||
|
||||
MirrorParameters parameters;
|
||||
|
||||
@@ -17,6 +17,7 @@ class AssetEditActionRotate {
|
||||
required this.parameters,
|
||||
});
|
||||
|
||||
/// Type of edit action to perform
|
||||
AssetEditAction action;
|
||||
|
||||
RotateParameters parameters;
|
||||
|
||||
+2
-1
@@ -17,9 +17,10 @@ class AssetEditsDto {
|
||||
this.edits = const [],
|
||||
});
|
||||
|
||||
/// Asset ID to apply edits to
|
||||
String assetId;
|
||||
|
||||
/// list of edits
|
||||
/// List of edit actions to apply (crop, rotate, or mirror)
|
||||
List<AssetEditActionListDtoEditsInner> edits;
|
||||
|
||||
@override
|
||||
|
||||
@@ -23,20 +23,28 @@ class AssetFaceCreateDto {
|
||||
required this.y,
|
||||
});
|
||||
|
||||
/// Asset ID
|
||||
String assetId;
|
||||
|
||||
/// Face bounding box height
|
||||
int height;
|
||||
|
||||
/// Image height in pixels
|
||||
int imageHeight;
|
||||
|
||||
/// Image width in pixels
|
||||
int imageWidth;
|
||||
|
||||
/// Person ID
|
||||
String personId;
|
||||
|
||||
/// Face bounding box width
|
||||
int width;
|
||||
|
||||
/// Face bounding box X coordinate
|
||||
int x;
|
||||
|
||||
/// Face bounding box Y coordinate
|
||||
int y;
|
||||
|
||||
@override
|
||||
|
||||
@@ -16,6 +16,7 @@ class AssetFaceDeleteDto {
|
||||
required this.force,
|
||||
});
|
||||
|
||||
/// Force delete even if person has other faces
|
||||
bool force;
|
||||
|
||||
@override
|
||||
|
||||
@@ -24,22 +24,31 @@ class AssetFaceResponseDto {
|
||||
this.sourceType,
|
||||
});
|
||||
|
||||
/// Bounding box X1 coordinate
|
||||
int boundingBoxX1;
|
||||
|
||||
/// Bounding box X2 coordinate
|
||||
int boundingBoxX2;
|
||||
|
||||
/// Bounding box Y1 coordinate
|
||||
int boundingBoxY1;
|
||||
|
||||
/// Bounding box Y2 coordinate
|
||||
int boundingBoxY2;
|
||||
|
||||
/// Face ID
|
||||
String id;
|
||||
|
||||
/// Image height in pixels
|
||||
int imageHeight;
|
||||
|
||||
/// Image width in pixels
|
||||
int imageWidth;
|
||||
|
||||
/// Person associated with face
|
||||
PersonResponseDto? person;
|
||||
|
||||
/// Face detection source type
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
||||
@@ -16,6 +16,7 @@ class AssetFaceUpdateDto {
|
||||
this.data = const [],
|
||||
});
|
||||
|
||||
/// Face update items
|
||||
List<AssetFaceUpdateItem> data;
|
||||
|
||||
@override
|
||||
|
||||
@@ -17,8 +17,10 @@ class AssetFaceUpdateItem {
|
||||
required this.personId,
|
||||
});
|
||||
|
||||
/// Asset ID
|
||||
String assetId;
|
||||
|
||||
/// Person ID
|
||||
String personId;
|
||||
|
||||
@override
|
||||
|
||||
@@ -23,20 +23,28 @@ class AssetFaceWithoutPersonResponseDto {
|
||||
this.sourceType,
|
||||
});
|
||||
|
||||
/// Bounding box X1 coordinate
|
||||
int boundingBoxX1;
|
||||
|
||||
/// Bounding box X2 coordinate
|
||||
int boundingBoxX2;
|
||||
|
||||
/// Bounding box Y1 coordinate
|
||||
int boundingBoxY1;
|
||||
|
||||
/// Bounding box Y2 coordinate
|
||||
int boundingBoxY2;
|
||||
|
||||
/// Face ID
|
||||
String id;
|
||||
|
||||
/// Image height in pixels
|
||||
int imageHeight;
|
||||
|
||||
/// Image width in pixels
|
||||
int imageWidth;
|
||||
|
||||
/// Face detection source type
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
||||
@@ -19,6 +19,7 @@ class AssetFullSyncDto {
|
||||
this.userId,
|
||||
});
|
||||
|
||||
/// Last asset ID (pagination)
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -27,11 +28,15 @@ class AssetFullSyncDto {
|
||||
///
|
||||
String? lastId;
|
||||
|
||||
/// Maximum number of assets to return
|
||||
///
|
||||
/// Minimum value: 1
|
||||
int limit;
|
||||
|
||||
/// Sync assets updated until this date
|
||||
DateTime updatedUntil;
|
||||
|
||||
/// Filter by user ID
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ class AssetIdsDto {
|
||||
this.assetIds = const [],
|
||||
});
|
||||
|
||||
/// Asset IDs
|
||||
List<String> assetIds;
|
||||
|
||||
@override
|
||||
|
||||
+4
-1
@@ -18,10 +18,13 @@ class AssetIdsResponseDto {
|
||||
required this.success,
|
||||
});
|
||||
|
||||
/// Asset ID
|
||||
String assetId;
|
||||
|
||||
/// Error reason if failed
|
||||
AssetIdsResponseDtoErrorEnum? error;
|
||||
|
||||
/// Whether operation succeeded
|
||||
bool success;
|
||||
|
||||
@override
|
||||
@@ -116,7 +119,7 @@ class AssetIdsResponseDto {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// Error reason if failed
|
||||
class AssetIdsResponseDtoErrorEnum {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetIdsResponseDtoErrorEnum._(this.value);
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
|
||||
/// Job name
|
||||
class AssetJobName {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetJobName._(this.value);
|
||||
|
||||
+2
@@ -17,8 +17,10 @@ class AssetJobsDto {
|
||||
required this.name,
|
||||
});
|
||||
|
||||
/// Asset IDs
|
||||
List<String> assetIds;
|
||||
|
||||
/// Job name
|
||||
AssetJobName name;
|
||||
|
||||
@override
|
||||
|
||||
@@ -17,8 +17,10 @@ class AssetMediaResponseDto {
|
||||
required this.status,
|
||||
});
|
||||
|
||||
/// Asset media ID
|
||||
String id;
|
||||
|
||||
/// Upload status
|
||||
AssetMediaStatus status;
|
||||
|
||||
@override
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user