mirror of
https://github.com/immich-app/immich.git
synced 2026-01-31 09:14:47 -08:00
Compare commits
33 Commits
fix-migrat
...
refactor/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a04159687b | ||
|
|
75be0acb08 | ||
|
|
7c9e9b5205 | ||
|
|
9d7a23ce13 | ||
|
|
425b4bc63e | ||
|
|
679db1dfb2 | ||
|
|
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,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"
|
||||
|
||||
4
docs/static/archived-versions.json
vendored
4
docs/static/archived-versions.json
vendored
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "2.5.1",
|
||||
"version": "2.5.2",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
||||
116
e2e/src/web/specs/search/search-gallery.ui-spec.ts
Normal file
116
e2e/src/web/specs/search/search-gallery.ui-spec.ts
Normal file
@@ -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,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"
|
||||
|
||||
2
machine-learning/uv.lock
generated
2
machine-learning/uv.lock
generated
@@ -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
|
||||
|
||||
19
mise.toml
19
mise.toml
@@ -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"
|
||||
}
|
||||
5
mobile/.gitignore
vendored
5
mobile/.gitignore
vendored
@@ -55,8 +55,5 @@ default.isar
|
||||
default.isar.lock
|
||||
libisar.so
|
||||
|
||||
# FVM Version
|
||||
.fvm/
|
||||
|
||||
# Translation file
|
||||
lib/generated/
|
||||
lib/generated/
|
||||
|
||||
4
mobile/.vscode/settings.json
vendored
4
mobile/.vscode/settings.json
vendored
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2,9 +2,10 @@ import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart';
|
||||
import 'package:immich_mobile/providers/image/immich_local_thumbnail_provider.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as base_asset;
|
||||
|
||||
@RoutePage()
|
||||
class FailedBackupStatusPage extends HookConsumerWidget {
|
||||
@@ -58,7 +59,7 @@ class FailedBackupStatusPage extends HookConsumerWidget {
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: Image(
|
||||
fit: BoxFit.cover,
|
||||
image: ImmichLocalThumbnailProvider(asset: errorAsset.asset, height: 512, width: 512),
|
||||
image: LocalThumbProvider(id: errorAsset.asset.localId!, assetType: base_asset.AssetType.video),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
||||
import 'package:immich_mobile/providers/image/immich_remote_image_provider.dart';
|
||||
|
||||
class GalleryStackedChildren extends HookConsumerWidget {
|
||||
final ValueNotifier<int> stackIndex;
|
||||
@@ -70,7 +70,7 @@ class GalleryStackedChildren extends HookConsumerWidget {
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
child: Image(
|
||||
fit: BoxFit.cover,
|
||||
image: ImmichRemoteImageProvider(assetId: assetId),
|
||||
image: RemoteImageProvider.thumbnail(assetId: assetId, thumbhash: asset.thumbhash ?? ""),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'package:immich_mobile/providers/partner.provider.dart';
|
||||
import 'package:immich_mobile/providers/search/people.provider.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_app_bar.dart';
|
||||
@@ -221,12 +221,7 @@ class PeopleCollectionCard extends ConsumerWidget {
|
||||
mainAxisSpacing: 8,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: people.take(4).map((person) {
|
||||
return CircleAvatar(
|
||||
backgroundImage: NetworkImage(
|
||||
getFaceThumbnailUrl(person.id),
|
||||
headers: ApiService.getRequestHeaders(),
|
||||
),
|
||||
);
|
||||
return CircleAvatar(backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id)));
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -5,8 +5,8 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/providers/search/people.provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
import 'package:immich_mobile/widgets/common/search_field.dart';
|
||||
import 'package:immich_mobile/widgets/search/person_name_edit_form.dart';
|
||||
@@ -17,7 +17,6 @@ class PeopleCollectionPage extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final people = ref.watch(getAllPeopleProvider);
|
||||
final headers = ApiService.getRequestHeaders();
|
||||
final formFocus = useFocusNode();
|
||||
final ValueNotifier<String?> search = useState(null);
|
||||
|
||||
@@ -88,7 +87,7 @@ class PeopleCollectionPage extends HookConsumerWidget {
|
||||
elevation: 3,
|
||||
child: CircleAvatar(
|
||||
maxRadius: isTablet ? 120 / 2 : 96 / 2,
|
||||
backgroundImage: NetworkImage(getFaceThumbnailUrl(person.id), headers: headers),
|
||||
backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||
@@ -10,9 +9,10 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
||||
import 'package:immich_mobile/pages/common/large_leading_tile.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||
import 'package:immich_mobile/providers/search/search_page_state.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/widgets/common/search_field.dart';
|
||||
import 'package:immich_mobile/widgets/map/map_thumbnail.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
@@ -125,13 +125,10 @@ class PlaceTile extends StatelessWidget {
|
||||
title: Text(name, style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500)),
|
||||
leading: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
child: CachedNetworkImage(
|
||||
child: SizedBox(
|
||||
width: 80,
|
||||
height: 80,
|
||||
fit: BoxFit.cover,
|
||||
imageUrl: thumbnailUrl,
|
||||
httpHeaders: ApiService.getRequestHeaders(),
|
||||
errorWidget: (context, url, error) => const Icon(Icons.image_not_supported_outlined),
|
||||
child: Thumbnail(imageProvider: RemoteImageProvider(url: thumbnailUrl)),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -4,8 +4,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/providers/search/people.provider.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/widgets/search/person_name_edit_form.dart';
|
||||
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
@@ -88,10 +88,7 @@ class PersonResultPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.only(left: 8.0, top: 24),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 36,
|
||||
backgroundImage: NetworkImage(getFaceThumbnailUrl(personId), headers: ApiService.getRequestHeaders()),
|
||||
),
|
||||
CircleAvatar(radius: 36, backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(personId))),
|
||||
Expanded(
|
||||
child: Padding(padding: const EdgeInsets.only(left: 16.0, right: 16.0), child: buildTitleBlock()),
|
||||
),
|
||||
|
||||
@@ -12,8 +12,8 @@ import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/partner.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/people.provider.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
|
||||
import 'package:immich_mobile/widgets/map/map_thumbnail.dart';
|
||||
@@ -179,12 +179,7 @@ class _PeopleCollectionCard extends ConsumerWidget {
|
||||
mainAxisSpacing: 8,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: people.take(4).map((person) {
|
||||
return CircleAvatar(
|
||||
backgroundImage: NetworkImage(
|
||||
getFaceThumbnailUrl(person.id),
|
||||
headers: ApiService.getRequestHeaders(),
|
||||
),
|
||||
);
|
||||
return CircleAvatar(backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id)));
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -4,8 +4,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/people.provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
import 'package:immich_mobile/utils/people.utils.dart';
|
||||
import 'package:immich_mobile/widgets/common/search_field.dart';
|
||||
@@ -31,7 +31,6 @@ class _DriftPeopleCollectionPageState extends ConsumerState<DriftPeopleCollectio
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final people = ref.watch(driftGetAllPeopleProvider);
|
||||
final headers = ApiService.getRequestHeaders();
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
@@ -90,7 +89,7 @@ class _DriftPeopleCollectionPageState extends ConsumerState<DriftPeopleCollectio
|
||||
elevation: 3,
|
||||
child: CircleAvatar(
|
||||
maxRadius: isTablet ? 100 / 2 : 96 / 2,
|
||||
backgroundImage: NetworkImage(getFaceThumbnailUrl(person.id), headers: headers),
|
||||
backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -92,7 +92,9 @@ 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);
|
||||
}
|
||||
}
|
||||
@@ -147,6 +149,11 @@ 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));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -10,8 +10,8 @@ import 'package:immich_mobile/presentation/widgets/people/person_edit_name_modal
|
||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/people.provider.dart';
|
||||
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
import 'package:immich_mobile/utils/people.utils.dart';
|
||||
|
||||
@@ -108,8 +108,6 @@ class _PeopleAvatar extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final headers = ApiService.getRequestHeaders();
|
||||
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 96),
|
||||
child: Padding(
|
||||
@@ -127,7 +125,7 @@ class _PeopleAvatar extends StatelessWidget {
|
||||
elevation: 3,
|
||||
child: CircleAvatar(
|
||||
maxRadius: imageSize / 2,
|
||||
backgroundImage: NetworkImage(getFaceThumbnailUrl(person.id), headers: headers),
|
||||
backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -134,7 +134,7 @@ ImageProvider? getThumbnailImageProvider(BaseAsset asset, {Size size = kThumbnai
|
||||
|
||||
final assetId = asset is RemoteAsset ? asset.id : (asset as LocalAsset).remoteId;
|
||||
final thumbhash = asset is RemoteAsset ? asset.thumbHash ?? "" : "";
|
||||
return assetId != null ? RemoteThumbProvider(assetId: assetId, thumbhash: thumbhash) : null;
|
||||
return assetId != null ? RemoteImageProvider.thumbnail(assetId: assetId, thumbhash: thumbhash) : null;
|
||||
}
|
||||
|
||||
bool _shouldUseLocalAsset(BaseAsset asset) =>
|
||||
|
||||
@@ -10,50 +10,48 @@ import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
class RemoteThumbProvider extends CancellableImageProvider<RemoteThumbProvider>
|
||||
with CancellableImageProviderMixin<RemoteThumbProvider> {
|
||||
final String assetId;
|
||||
final String thumbhash;
|
||||
class RemoteImageProvider extends CancellableImageProvider<RemoteImageProvider>
|
||||
with CancellableImageProviderMixin<RemoteImageProvider> {
|
||||
final String url;
|
||||
|
||||
RemoteThumbProvider({required this.assetId, required this.thumbhash});
|
||||
RemoteImageProvider({required this.url});
|
||||
|
||||
RemoteImageProvider.thumbnail({required String assetId, required String thumbhash})
|
||||
: url = getThumbnailUrlForRemoteId(assetId, thumbhash: thumbhash);
|
||||
|
||||
@override
|
||||
Future<RemoteThumbProvider> obtainKey(ImageConfiguration configuration) {
|
||||
Future<RemoteImageProvider> obtainKey(ImageConfiguration configuration) {
|
||||
return SynchronousFuture(this);
|
||||
}
|
||||
|
||||
@override
|
||||
ImageStreamCompleter loadImage(RemoteThumbProvider key, ImageDecoderCallback decode) {
|
||||
ImageStreamCompleter loadImage(RemoteImageProvider key, ImageDecoderCallback decode) {
|
||||
return OneFramePlaceholderImageStreamCompleter(
|
||||
_codec(key, decode),
|
||||
informationCollector: () => <DiagnosticsNode>[
|
||||
DiagnosticsProperty<ImageProvider>('Image provider', this),
|
||||
DiagnosticsProperty<String>('Asset Id', key.assetId),
|
||||
DiagnosticsProperty<String>('URL', key.url),
|
||||
],
|
||||
onDispose: cancel,
|
||||
);
|
||||
}
|
||||
|
||||
Stream<ImageInfo> _codec(RemoteThumbProvider key, ImageDecoderCallback decode) {
|
||||
final request = this.request = RemoteImageRequest(
|
||||
uri: getThumbnailUrlForRemoteId(key.assetId, thumbhash: key.thumbhash),
|
||||
headers: ApiService.getRequestHeaders(),
|
||||
);
|
||||
Stream<ImageInfo> _codec(RemoteImageProvider key, ImageDecoderCallback decode) {
|
||||
final request = this.request = RemoteImageRequest(uri: key.url, headers: ApiService.getRequestHeaders());
|
||||
return loadRequest(request, decode);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other is RemoteThumbProvider) {
|
||||
return assetId == other.assetId && thumbhash == other.thumbhash;
|
||||
if (other is RemoteImageProvider) {
|
||||
return url == other.url;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => assetId.hashCode ^ thumbhash.hashCode;
|
||||
int get hashCode => url.hashCode;
|
||||
}
|
||||
|
||||
class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImageProvider>
|
||||
@@ -73,7 +71,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
||||
ImageStreamCompleter loadImage(RemoteFullImageProvider key, ImageDecoderCallback decode) {
|
||||
return OneFramePlaceholderImageStreamCompleter(
|
||||
_codec(key, decode),
|
||||
initialImage: getInitialImage(RemoteThumbProvider(assetId: key.assetId, thumbhash: key.thumbhash)),
|
||||
initialImage: getInitialImage(RemoteImageProvider.thumbnail(assetId: key.assetId, thumbhash: key.thumbhash)),
|
||||
informationCollector: () => <DiagnosticsNode>[
|
||||
DiagnosticsProperty<ImageProvider>('Image provider', this),
|
||||
DiagnosticsProperty<String>('Asset Id', key.assetId),
|
||||
|
||||
@@ -27,7 +27,7 @@ class Thumbnail extends StatefulWidget {
|
||||
this.fit = BoxFit.cover,
|
||||
Size size = kThumbnailResolution,
|
||||
super.key,
|
||||
}) : imageProvider = RemoteThumbProvider(assetId: remoteId, thumbhash: thumbhash),
|
||||
}) : imageProvider = RemoteImageProvider.thumbnail(assetId: remoteId, thumbhash: thumbhash),
|
||||
thumbhashProvider = null;
|
||||
|
||||
Thumbnail.fromAsset({
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
|
||||
class PartnerUserAvatar extends StatelessWidget {
|
||||
const PartnerUserAvatar({super.key, required this.partner});
|
||||
@@ -18,11 +17,7 @@ class PartnerUserAvatar extends StatelessWidget {
|
||||
return CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: context.primaryColor.withAlpha(50),
|
||||
foregroundImage: CachedNetworkImageProvider(
|
||||
url,
|
||||
headers: ApiService.getRequestHeaders(),
|
||||
cacheKey: "user-${partner.id}-profile",
|
||||
),
|
||||
foregroundImage: RemoteImageProvider(url: url),
|
||||
// silence errors if user has no profile image, use initials as fallback
|
||||
onForegroundImageError: (exception, stackTrace) {},
|
||||
child: Text(nameFirstLetter.toUpperCase()),
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:photo_manager/photo_manager.dart' show ThumbnailSize;
|
||||
|
||||
/// The local image provider for an asset
|
||||
class ImmichLocalImageProvider extends ImageProvider<ImmichLocalImageProvider> {
|
||||
final Asset asset;
|
||||
// only used for videos
|
||||
final double width;
|
||||
final double height;
|
||||
final Logger log = Logger('ImmichLocalImageProvider');
|
||||
|
||||
ImmichLocalImageProvider({required this.asset, required this.width, required this.height})
|
||||
: assert(asset.local != null, 'Only usable when asset.local is set');
|
||||
|
||||
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
|
||||
/// that describes the precise image to load.
|
||||
@override
|
||||
Future<ImmichLocalImageProvider> obtainKey(ImageConfiguration configuration) {
|
||||
return SynchronousFuture(this);
|
||||
}
|
||||
|
||||
@override
|
||||
ImageStreamCompleter loadImage(ImmichLocalImageProvider key, ImageDecoderCallback decode) {
|
||||
final chunkEvents = StreamController<ImageChunkEvent>();
|
||||
return MultiImageStreamCompleter(
|
||||
codec: _codec(key.asset, decode, chunkEvents),
|
||||
scale: 1.0,
|
||||
chunkEvents: chunkEvents.stream,
|
||||
informationCollector: () sync* {
|
||||
yield ErrorDescription(asset.fileName);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Streams in each stage of the image as we ask for it
|
||||
Stream<ui.Codec> _codec(
|
||||
Asset asset,
|
||||
ImageDecoderCallback decode,
|
||||
StreamController<ImageChunkEvent> chunkEvents,
|
||||
) async* {
|
||||
try {
|
||||
final local = asset.local;
|
||||
if (local == null) {
|
||||
throw StateError('Asset ${asset.fileName} has no local data');
|
||||
}
|
||||
|
||||
switch (asset.type) {
|
||||
case AssetType.image:
|
||||
final File? file = await local.originFile;
|
||||
if (file == null) {
|
||||
throw StateError("Opening file for asset ${asset.fileName} failed");
|
||||
}
|
||||
final buffer = await ui.ImmutableBuffer.fromFilePath(file.path);
|
||||
yield await decode(buffer);
|
||||
break;
|
||||
case AssetType.video:
|
||||
final size = ThumbnailSize(width.ceil(), height.ceil());
|
||||
final thumbBytes = await local.thumbnailDataWithSize(size);
|
||||
if (thumbBytes == null) {
|
||||
throw StateError("Failed to load preview for ${asset.fileName}");
|
||||
}
|
||||
final buffer = await ui.ImmutableBuffer.fromUint8List(thumbBytes);
|
||||
yield await decode(buffer);
|
||||
break;
|
||||
default:
|
||||
throw StateError('Unsupported asset type ${asset.type}');
|
||||
}
|
||||
} catch (error, stack) {
|
||||
log.severe('Error loading local image ${asset.fileName}', error, stack);
|
||||
} finally {
|
||||
unawaited(chunkEvents.close());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other is ImmichLocalImageProvider) {
|
||||
return asset.id == other.asset.id && asset.localId == other.asset.localId;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(asset.id, asset.localId);
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:immich_mobile/providers/image/cache/thumbnail_image_cache_manager.dart';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:photo_manager/photo_manager.dart' show ThumbnailSize;
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
/// The local image provider for an asset
|
||||
/// Only viable
|
||||
class ImmichLocalThumbnailProvider extends ImageProvider<ImmichLocalThumbnailProvider> {
|
||||
final Asset asset;
|
||||
final int height;
|
||||
final int width;
|
||||
final CacheManager? cacheManager;
|
||||
final Logger log = Logger("ImmichLocalThumbnailProvider");
|
||||
final String? userId;
|
||||
|
||||
ImmichLocalThumbnailProvider({
|
||||
required this.asset,
|
||||
this.height = 256,
|
||||
this.width = 256,
|
||||
this.cacheManager,
|
||||
this.userId,
|
||||
}) : assert(asset.local != null, 'Only usable when asset.local is set');
|
||||
|
||||
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
|
||||
/// that describes the precise image to load.
|
||||
@override
|
||||
Future<ImmichLocalThumbnailProvider> obtainKey(ImageConfiguration configuration) {
|
||||
return SynchronousFuture(this);
|
||||
}
|
||||
|
||||
@override
|
||||
ImageStreamCompleter loadImage(ImmichLocalThumbnailProvider key, ImageDecoderCallback decode) {
|
||||
final cache = cacheManager ?? ThumbnailImageCacheManager();
|
||||
return MultiImageStreamCompleter(
|
||||
codec: _codec(key.asset, cache, decode),
|
||||
scale: 1.0,
|
||||
informationCollector: () sync* {
|
||||
yield ErrorDescription(key.asset.fileName);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Streams in each stage of the image as we ask for it
|
||||
Stream<ui.Codec> _codec(Asset assetData, CacheManager cache, ImageDecoderCallback decode) async* {
|
||||
final cacheKey = '$userId${assetData.localId}${assetData.checksum}$width$height';
|
||||
final fileFromCache = await cache.getFileFromCache(cacheKey);
|
||||
if (fileFromCache != null) {
|
||||
try {
|
||||
final buffer = await ui.ImmutableBuffer.fromFilePath(fileFromCache.file.path);
|
||||
final codec = await decode(buffer);
|
||||
yield codec;
|
||||
return;
|
||||
} catch (error) {
|
||||
log.severe('Found thumbnail in cache, but loading it failed', error);
|
||||
}
|
||||
}
|
||||
|
||||
final thumbnailBytes = await assetData.local?.thumbnailDataWithSize(ThumbnailSize(width, height), quality: 80);
|
||||
if (thumbnailBytes == null) {
|
||||
throw StateError("Loading thumb for local photo ${assetData.fileName} failed");
|
||||
}
|
||||
|
||||
final buffer = await ui.ImmutableBuffer.fromUint8List(thumbnailBytes);
|
||||
final codec = await decode(buffer);
|
||||
yield codec;
|
||||
await cache.putFile(cacheKey, thumbnailBytes);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other is ImmichLocalThumbnailProvider) {
|
||||
return asset.id == other.asset.id && asset.localId == other.asset.localId;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(asset.id, asset.localId);
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:immich_mobile/providers/image/cache/image_loader.dart';
|
||||
import 'package:immich_mobile/providers/image/cache/remote_image_cache_manager.dart';
|
||||
import 'package:openapi/api.dart' as api;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
|
||||
/// The remote image provider for full size remote images
|
||||
class ImmichRemoteImageProvider extends ImageProvider<ImmichRemoteImageProvider> {
|
||||
/// The [Asset.remoteId] of the asset to fetch
|
||||
final String assetId;
|
||||
|
||||
/// The image cache manager
|
||||
final CacheManager? cacheManager;
|
||||
|
||||
const ImmichRemoteImageProvider({required this.assetId, this.cacheManager});
|
||||
|
||||
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
|
||||
/// that describes the precise image to load.
|
||||
@override
|
||||
Future<ImmichRemoteImageProvider> obtainKey(ImageConfiguration configuration) {
|
||||
return SynchronousFuture(this);
|
||||
}
|
||||
|
||||
@override
|
||||
ImageStreamCompleter loadImage(ImmichRemoteImageProvider key, ImageDecoderCallback decode) {
|
||||
final cache = cacheManager ?? RemoteImageCacheManager();
|
||||
final chunkEvents = StreamController<ImageChunkEvent>();
|
||||
return MultiImageStreamCompleter(
|
||||
codec: _codec(key, cache, decode, chunkEvents),
|
||||
scale: 1.0,
|
||||
chunkEvents: chunkEvents.stream,
|
||||
);
|
||||
}
|
||||
|
||||
/// Whether to show the original file or load a compressed version
|
||||
bool get _useOriginal => Store.get(AppSettingsEnum.loadOriginal.storeKey, AppSettingsEnum.loadOriginal.defaultValue);
|
||||
|
||||
// Streams in each stage of the image as we ask for it
|
||||
Stream<ui.Codec> _codec(
|
||||
ImmichRemoteImageProvider key,
|
||||
CacheManager cache,
|
||||
ImageDecoderCallback decode,
|
||||
StreamController<ImageChunkEvent> chunkEvents,
|
||||
) async* {
|
||||
// Load the higher resolution version of the image
|
||||
final url = getThumbnailUrlForRemoteId(key.assetId, type: api.AssetMediaSize.preview);
|
||||
final codec = await ImageLoader.loadImageFromCache(url, cache: cache, decode: decode, chunkEvents: chunkEvents);
|
||||
yield codec;
|
||||
|
||||
// Load the final remote image
|
||||
if (_useOriginal) {
|
||||
// Load the original image
|
||||
final url = getOriginalUrlForRemoteId(key.assetId);
|
||||
final codec = await ImageLoader.loadImageFromCache(url, cache: cache, decode: decode, chunkEvents: chunkEvents);
|
||||
yield codec;
|
||||
}
|
||||
await chunkEvents.close();
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other is ImmichRemoteImageProvider) {
|
||||
return assetId == other.assetId;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => assetId.hashCode;
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:immich_mobile/providers/image/cache/image_loader.dart';
|
||||
import 'package:immich_mobile/providers/image/cache/thumbnail_image_cache_manager.dart';
|
||||
import 'package:openapi/api.dart' as api;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
|
||||
/// The remote image provider
|
||||
class ImmichRemoteThumbnailProvider extends ImageProvider<ImmichRemoteThumbnailProvider> {
|
||||
/// The [Asset.remoteId] of the asset to fetch
|
||||
final String assetId;
|
||||
|
||||
final int? height;
|
||||
final int? width;
|
||||
|
||||
/// The image cache manager
|
||||
final CacheManager? cacheManager;
|
||||
|
||||
const ImmichRemoteThumbnailProvider({required this.assetId, this.height, this.width, this.cacheManager});
|
||||
|
||||
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
|
||||
/// that describes the precise image to load.
|
||||
@override
|
||||
Future<ImmichRemoteThumbnailProvider> obtainKey(ImageConfiguration configuration) {
|
||||
return SynchronousFuture(this);
|
||||
}
|
||||
|
||||
@override
|
||||
ImageStreamCompleter loadImage(ImmichRemoteThumbnailProvider key, ImageDecoderCallback decode) {
|
||||
final cache = cacheManager ?? ThumbnailImageCacheManager();
|
||||
return MultiImageStreamCompleter(codec: _codec(key, cache, decode), scale: 1.0);
|
||||
}
|
||||
|
||||
// Streams in each stage of the image as we ask for it
|
||||
Stream<ui.Codec> _codec(ImmichRemoteThumbnailProvider key, CacheManager cache, ImageDecoderCallback decode) async* {
|
||||
// Load a preview to the chunk events
|
||||
final preview = getThumbnailUrlForRemoteId(key.assetId, type: api.AssetMediaSize.thumbnail);
|
||||
|
||||
yield await ImageLoader.loadImageFromCache(preview, cache: cache, decode: decode);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other is ImmichRemoteThumbnailProvider) {
|
||||
return assetId == other.assetId;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => assetId.hashCode;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
11
mobile/lib/utils/cache/custom_image_cache.dart
vendored
11
mobile/lib/utils/cache/custom_image_cache.dart
vendored
@@ -2,10 +2,6 @@ import 'package:flutter/painting.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/thumb_hash_provider.dart';
|
||||
import 'package:immich_mobile/providers/image/immich_local_image_provider.dart';
|
||||
import 'package:immich_mobile/providers/image/immich_local_thumbnail_provider.dart';
|
||||
import 'package:immich_mobile/providers/image/immich_remote_image_provider.dart';
|
||||
import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart';
|
||||
|
||||
/// [ImageCache] that uses two caches for small and large images
|
||||
/// so that a single large image does not evict all small images
|
||||
@@ -39,14 +35,9 @@ final class CustomImageCache implements ImageCache {
|
||||
}
|
||||
|
||||
/// Gets the cache for the given key
|
||||
/// [_large] is used for [ImmichLocalImageProvider] and [ImmichRemoteImageProvider]
|
||||
/// [_small] is used for [ImmichLocalThumbnailProvider] and [ImmichRemoteThumbnailProvider]
|
||||
ImageCache _cacheForKey(Object key) {
|
||||
return switch (key) {
|
||||
ImmichLocalImageProvider() ||
|
||||
ImmichRemoteImageProvider() ||
|
||||
LocalFullImageProvider() ||
|
||||
RemoteFullImageProvider() => _large,
|
||||
LocalFullImageProvider() || RemoteFullImageProvider() => _large,
|
||||
ThumbHashProvider() => _thumbhash,
|
||||
_ => _small,
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -4,8 +4,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/datetime_extensions.dart';
|
||||
import 'package:immich_mobile/models/activities/activity.model.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/providers/activity_service.provider.dart';
|
||||
import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
||||
|
||||
@@ -102,7 +102,7 @@ class _ActivityAssetThumbnail extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
image: DecorationImage(
|
||||
image: ImmichRemoteThumbnailProvider(assetId: assetId),
|
||||
image: RemoteImageProvider.thumbnail(assetId: assetId, thumbhash: ""),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -4,9 +4,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/datetime_extensions.dart';
|
||||
import 'package:immich_mobile/models/activities/activity.model.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/providers/activity.provider.dart';
|
||||
import 'package:immich_mobile/providers/activity_service.provider.dart';
|
||||
import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/widgets/activities/dismissible_activity.dart';
|
||||
@@ -56,7 +56,7 @@ class CommentBubble extends ConsumerWidget {
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
child: Image(
|
||||
image: ImmichRemoteThumbnailProvider(assetId: activity.assetId!),
|
||||
image: RemoteImageProvider.thumbnail(assetId: activity.assetId!, thumbhash: ""),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/entities/album.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
@@ -32,15 +32,12 @@ class AlbumThumbnailListTile extends StatelessWidget {
|
||||
}
|
||||
|
||||
buildAlbumThumbnail() {
|
||||
return CachedNetworkImage(
|
||||
return SizedBox(
|
||||
width: cardSize,
|
||||
height: cardSize,
|
||||
fit: BoxFit.cover,
|
||||
fadeInDuration: const Duration(milliseconds: 200),
|
||||
imageUrl: getAlbumThumbnailUrl(album, type: AssetMediaSize.thumbnail),
|
||||
httpHeaders: ApiService.getRequestHeaders(),
|
||||
cacheKey: getAlbumThumbNailCacheKey(album, type: AssetMediaSize.thumbnail),
|
||||
errorWidget: (context, url, error) => const Icon(Icons.image_not_supported_outlined),
|
||||
child: Thumbnail(
|
||||
imageProvider: RemoteImageProvider(url: getAlbumThumbnailUrl(album, type: AssetMediaSize.thumbnail)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as base_asset;
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/providers/image/immich_local_image_provider.dart';
|
||||
import 'package:immich_mobile/providers/image/immich_remote_image_provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/widgets/asset_grid/thumbnail_placeholder.dart';
|
||||
import 'package:octo_image/octo_image.dart';
|
||||
|
||||
@@ -34,13 +35,21 @@ class ImmichImage extends StatelessWidget {
|
||||
}
|
||||
|
||||
if (asset == null) {
|
||||
return ImmichRemoteImageProvider(assetId: assetId!);
|
||||
return RemoteFullImageProvider(assetId: assetId!, thumbhash: '', assetType: base_asset.AssetType.video);
|
||||
}
|
||||
|
||||
if (useLocal(asset)) {
|
||||
return ImmichLocalImageProvider(asset: asset, width: width, height: height);
|
||||
return LocalFullImageProvider(
|
||||
id: asset.localId!,
|
||||
assetType: base_asset.AssetType.video,
|
||||
size: Size(width, height),
|
||||
);
|
||||
} else {
|
||||
return ImmichRemoteImageProvider(assetId: asset.remoteId!);
|
||||
return RemoteFullImageProvider(
|
||||
assetId: asset.remoteId!,
|
||||
thumbhash: asset.thumbhash ?? '',
|
||||
assetType: base_asset.AssetType.video,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,15 @@ import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/providers/image/immich_local_thumbnail_provider.dart';
|
||||
import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/utils/hooks/blurhash_hook.dart';
|
||||
import 'package:immich_mobile/utils/thumbnail_utils.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_image.dart';
|
||||
import 'package:immich_mobile/widgets/common/thumbhash_placeholder.dart';
|
||||
import 'package:octo_image/octo_image.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as base_asset;
|
||||
|
||||
class ImmichThumbnail extends HookConsumerWidget {
|
||||
const ImmichThumbnail({this.asset, this.width = 250, this.height = 250, this.fit = BoxFit.cover, super.key});
|
||||
@@ -24,26 +24,29 @@ class ImmichThumbnail extends HookConsumerWidget {
|
||||
/// either by using the asset ID or the asset itself
|
||||
/// [asset] is the Asset to request, or else use [assetId] to get a remote
|
||||
/// image provider
|
||||
static ImageProvider imageProvider({Asset? asset, String? assetId, String? userId, int thumbnailSize = 256}) {
|
||||
static ImageProvider imageProvider({Asset? asset, String? assetId, int thumbnailSize = 256}) {
|
||||
if (asset == null && assetId == null) {
|
||||
throw Exception('Must supply either asset or assetId');
|
||||
}
|
||||
|
||||
if (asset == null) {
|
||||
return ImmichRemoteThumbnailProvider(assetId: assetId!);
|
||||
return RemoteImageProvider.thumbnail(assetId: assetId!, thumbhash: "");
|
||||
}
|
||||
|
||||
if (ImmichImage.useLocal(asset)) {
|
||||
return ImmichLocalThumbnailProvider(asset: asset, height: thumbnailSize, width: thumbnailSize, userId: userId);
|
||||
return LocalThumbProvider(
|
||||
id: asset.localId!,
|
||||
assetType: base_asset.AssetType.video,
|
||||
size: Size(thumbnailSize.toDouble(), thumbnailSize.toDouble()),
|
||||
);
|
||||
} else {
|
||||
return ImmichRemoteThumbnailProvider(assetId: asset.remoteId!, height: thumbnailSize, width: thumbnailSize);
|
||||
return RemoteImageProvider.thumbnail(assetId: asset.remoteId!, thumbhash: asset.thumbhash ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Uint8List? blurhash = useBlurHashRef(asset).value;
|
||||
final userId = ref.watch(currentUserProvider)?.id;
|
||||
|
||||
if (asset == null) {
|
||||
return Container(
|
||||
@@ -56,7 +59,7 @@ class ImmichThumbnail extends HookConsumerWidget {
|
||||
|
||||
final assetAltText = getAltText(asset!.exifInfo, asset!.fileCreatedAt, asset!.type, []);
|
||||
|
||||
final thumbnailProviderInstance = ImmichThumbnail.imageProvider(asset: asset, userId: userId);
|
||||
final thumbnailProviderInstance = ImmichThumbnail.imageProvider(asset: asset);
|
||||
|
||||
customErrorBuilder(BuildContext ctx, Object error, StackTrace? stackTrace) {
|
||||
thumbnailProviderInstance.evict();
|
||||
|
||||
@@ -14,8 +14,8 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/people.utils.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
|
||||
@@ -230,10 +230,7 @@ class _ExpandedBackgroundState extends ConsumerState<_ExpandedBackground> with S
|
||||
elevation: 3,
|
||||
child: CircleAvatar(
|
||||
maxRadius: 84 / 2,
|
||||
backgroundImage: NetworkImage(
|
||||
getFaceThumbnailUrl(widget.person.id),
|
||||
headers: ApiService.getRequestHeaders(),
|
||||
),
|
||||
backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(widget.person.id)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
|
||||
Widget userAvatar(BuildContext context, UserDto u, {double? radius}) {
|
||||
final url = "${Store.get(StoreKey.serverEndpoint)}/users/${u.id}/profile-image";
|
||||
@@ -12,11 +11,7 @@ Widget userAvatar(BuildContext context, UserDto u, {double? radius}) {
|
||||
return CircleAvatar(
|
||||
radius: radius,
|
||||
backgroundColor: context.primaryColor.withAlpha(50),
|
||||
foregroundImage: CachedNetworkImageProvider(
|
||||
url,
|
||||
headers: ApiService.getRequestHeaders(),
|
||||
cacheKey: "user-${u.id}-profile",
|
||||
),
|
||||
foregroundImage: RemoteImageProvider(url: url),
|
||||
// silence errors if user has no profile image, use initials as fallback
|
||||
onForegroundImageError: (exception, stackTrace) {},
|
||||
child: Text(nameFirstLetter.toUpperCase()),
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/widgets/common/transparent_image.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class UserCircleAvatar extends ConsumerWidget {
|
||||
@@ -46,16 +44,12 @@ class UserCircleAvatar extends ConsumerWidget {
|
||||
child: user.hasProfileImage
|
||||
? ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
child: CachedNetworkImage(
|
||||
child: Image(
|
||||
fit: BoxFit.cover,
|
||||
cacheKey: '${user.id}-${user.profileChangedAt.toIso8601String()}',
|
||||
width: size,
|
||||
height: size,
|
||||
placeholder: (_, __) => Image.memory(kTransparentImage),
|
||||
imageUrl: profileImageUrl,
|
||||
httpHeaders: ApiService.getRequestHeaders(),
|
||||
fadeInDuration: const Duration(milliseconds: 300),
|
||||
errorWidget: (context, error, stackTrace) => textIcon,
|
||||
image: RemoteImageProvider(url: profileImageUrl),
|
||||
errorBuilder: (context, error, stackTrace) => textIcon,
|
||||
),
|
||||
)
|
||||
: textIcon,
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
|
||||
class PositionedAssetMarkerIcon extends StatelessWidget {
|
||||
@@ -53,7 +52,6 @@ class _AssetMarkerIcon extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final imageUrl = getThumbnailUrlForRemoteId(id);
|
||||
final cacheKey = getThumbnailCacheKeyForRemoteId(id, thumbhash);
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Stack(
|
||||
@@ -79,12 +77,7 @@ class _AssetMarkerIcon extends StatelessWidget {
|
||||
backgroundColor: context.colorScheme.onSurface,
|
||||
child: CircleAvatar(
|
||||
radius: constraints.maxHeight * 0.37,
|
||||
backgroundImage: CachedNetworkImageProvider(
|
||||
imageUrl,
|
||||
cacheKey: cacheKey,
|
||||
headers: ApiService.getRequestHeaders(),
|
||||
errorListener: (_) => const Icon(Icons.image_not_supported_outlined),
|
||||
),
|
||||
backgroundImage: RemoteImageProvider(url: imageUrl),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/models/search/search_curated_content.model.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
|
||||
class CuratedPeopleRow extends StatelessWidget {
|
||||
@@ -29,7 +29,6 @@ class CuratedPeopleRow extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: List.generate(content.length, (index) {
|
||||
final person = content[index];
|
||||
final headers = ApiService.getRequestHeaders();
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: Column(
|
||||
@@ -44,7 +43,7 @@ class CuratedPeopleRow extends StatelessWidget {
|
||||
elevation: 3,
|
||||
child: CircleAvatar(
|
||||
maxRadius: imageSize / 2,
|
||||
backgroundImage: NetworkImage(getFaceThumbnailUrl(person.id), headers: headers),
|
||||
backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -6,8 +6,8 @@ import 'package:immich_mobile/domain/models/person.model.dart';
|
||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/pages/common/large_leading_tile.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/providers/search/people.provider.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
import 'package:immich_mobile/widgets/common/search_field.dart';
|
||||
|
||||
@@ -23,7 +23,6 @@ class PeoplePicker extends HookConsumerWidget {
|
||||
final imageSize = 60.0;
|
||||
final searchQuery = useState('');
|
||||
final people = ref.watch(getAllPeopleProvider);
|
||||
final headers = ApiService.getRequestHeaders();
|
||||
final selectedPeople = useState<Set<PersonDto>>(filter ?? {});
|
||||
|
||||
return Column(
|
||||
@@ -75,7 +74,7 @@ class PeoplePicker extends HookConsumerWidget {
|
||||
elevation: 3,
|
||||
child: CircleAvatar(
|
||||
maxRadius: imageSize / 2,
|
||||
backgroundImage: NetworkImage(getFaceThumbnailUrl(person.id), headers: headers),
|
||||
backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||
import 'package:immich_mobile/widgets/search/thumbnail_with_info_container.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
|
||||
class ThumbnailWithInfo extends StatelessWidget {
|
||||
const ThumbnailWithInfo({
|
||||
@@ -30,14 +30,7 @@ class ThumbnailWithInfo extends StatelessWidget {
|
||||
child: imageUrl != null
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
child: CachedNetworkImage(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
imageUrl: imageUrl!,
|
||||
httpHeaders: ApiService.getRequestHeaders(),
|
||||
errorWidget: (context, url, error) => const Icon(Icons.image_not_supported_outlined),
|
||||
),
|
||||
child: Thumbnail(imageProvider: RemoteImageProvider(url: imageUrl!)),
|
||||
)
|
||||
: Center(child: Icon(noImageIcon ?? Icons.not_listed_location, color: textAndIconColor)),
|
||||
);
|
||||
|
||||
2
mobile/openapi/README.md
generated
2
mobile/openapi/README.md
generated
@@ -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
mobile/openapi/lib/api/activities_api.dart
generated
14
mobile/openapi/lib/api/activities_api.dart
generated
@@ -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) {
|
||||
|
||||
8
mobile/openapi/lib/api/albums_api.dart
generated
8
mobile/openapi/lib/api/albums_api.dart
generated
@@ -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) {
|
||||
|
||||
62
mobile/openapi/lib/api/assets_api.dart
generated
62
mobile/openapi/lib/api/assets_api.dart
generated
@@ -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
mobile/openapi/lib/api/deprecated_api.dart
generated
20
mobile/openapi/lib/api/deprecated_api.dart
generated
@@ -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 {
|
||||
|
||||
2
mobile/openapi/lib/api/faces_api.dart
generated
2
mobile/openapi/lib/api/faces_api.dart
generated
@@ -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) {
|
||||
|
||||
2
mobile/openapi/lib/api/jobs_api.dart
generated
2
mobile/openapi/lib/api/jobs_api.dart
generated
@@ -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 {
|
||||
|
||||
16
mobile/openapi/lib/api/map_api.dart
generated
16
mobile/openapi/lib/api/map_api.dart
generated
@@ -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) {
|
||||
|
||||
20
mobile/openapi/lib/api/memories_api.dart
generated
20
mobile/openapi/lib/api/memories_api.dart
generated
@@ -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
mobile/openapi/lib/api/notifications_api.dart
generated
8
mobile/openapi/lib/api/notifications_api.dart
generated
@@ -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) {
|
||||
|
||||
2
mobile/openapi/lib/api/partners_api.dart
generated
2
mobile/openapi/lib/api/partners_api.dart
generated
@@ -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) {
|
||||
|
||||
6
mobile/openapi/lib/api/people_api.dart
generated
6
mobile/openapi/lib/api/people_api.dart
generated
@@ -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) {
|
||||
|
||||
10
mobile/openapi/lib/api/queues_api.dart
generated
10
mobile/openapi/lib/api/queues_api.dart
generated
@@ -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 {
|
||||
|
||||
84
mobile/openapi/lib/api/search_api.dart
generated
84
mobile/openapi/lib/api/search_api.dart
generated
@@ -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
mobile/openapi/lib/api/shared_links_api.dart
generated
8
mobile/openapi/lib/api/shared_links_api.dart
generated
@@ -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) {
|
||||
|
||||
2
mobile/openapi/lib/api/stacks_api.dart
generated
2
mobile/openapi/lib/api/stacks_api.dart
generated
@@ -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
mobile/openapi/lib/api/users_admin_api.dart
generated
10
mobile/openapi/lib/api/users_admin_api.dart
generated
@@ -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) {
|
||||
|
||||
2
mobile/openapi/lib/api/users_api.dart
generated
2
mobile/openapi/lib/api/users_api.dart
generated
@@ -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
mobile/openapi/lib/model/add_users_dto.dart
generated
1
mobile/openapi/lib/model/add_users_dto.dart
generated
@@ -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
mobile/openapi/lib/model/album_response_dto.dart
generated
15
mobile/openapi/lib/model/album_response_dto.dart
generated
@@ -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
|
||||
|
||||
2
mobile/openapi/lib/model/album_user_add_dto.dart
generated
2
mobile/openapi/lib/model/album_user_add_dto.dart
generated
@@ -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;
|
||||
|
||||
2
mobile/openapi/lib/model/album_user_role.dart
generated
2
mobile/openapi/lib/model/album_user_role.dart
generated
@@ -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
mobile/openapi/lib/model/albums_response.dart
generated
1
mobile/openapi/lib/model/albums_response.dart
generated
@@ -16,6 +16,7 @@ class AlbumsResponse {
|
||||
this.defaultAssetOrder = AssetOrder.desc,
|
||||
});
|
||||
|
||||
/// Default asset order for albums
|
||||
AssetOrder defaultAssetOrder;
|
||||
|
||||
@override
|
||||
|
||||
1
mobile/openapi/lib/model/albums_update.dart
generated
1
mobile/openapi/lib/model/albums_update.dart
generated
@@ -16,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
|
||||
|
||||
2
mobile/openapi/lib/model/api_key_create_dto.dart
generated
2
mobile/openapi/lib/model/api_key_create_dto.dart
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
|
||||
|
||||
2
mobile/openapi/lib/model/api_key_update_dto.dart
generated
2
mobile/openapi/lib/model/api_key_update_dto.dart
generated
@@ -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
mobile/openapi/lib/model/asset_bulk_update_dto.dart
generated
12
mobile/openapi/lib/model/asset_bulk_update_dto.dart
generated
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user