Compare commits

...

31 Commits

Author SHA1 Message Date
bo0tzz 66bde40ef8 chore: use non-default database name for medium tests 2025-06-20 21:17:51 +02:00
Alex c707f9cef4 refactor(mobile): partner.interface.dart (#19338) 2025-06-20 18:37:59 +00:00
dotlambda 6fda863c08 fix(server): don't hardcode database name in migration (#19376) 2025-06-20 21:33:34 +03:00
Daniel Dietzler 373b654156 chore: migrate profile picture cropper modal (#19378) 2025-06-20 18:16:10 +00:00
Daniel Dietzler a5d84ba552 chore: consistent modal footer spacing (#19377) 2025-06-20 18:05:39 +00:00
Daniel Dietzler 1dc8fa2979 chore: rename edit album form modal (#19375) 2025-06-20 13:51:14 -04:00
Alex 0426699f13 refactor(mobile): partner_api.interface.dart (#19337)
* refactor(mobile): partner_api.interface.dart

* merge main
2025-06-20 17:04:15 +00:00
Alex 8154ec29df refactor(mobile): person_api.interface.dart (#19336) 2025-06-20 11:45:31 -05:00
Alex 3024cd343b refactor(mobile): timeline.interface.dart (#19331)
refactor(mobile): timeline.repository.dart
2025-06-20 11:44:02 -05:00
Zack Pollard 0b44d4b6f2 fix: partner and album backfill acks (#19371)
fix: partner sync being entirely broken
2025-06-20 16:14:36 +00:00
github-actions a04c6ed80d chore: version v1.135.2 2025-06-20 14:52:47 +00:00
Brandon Wees 1c50e19894 fix: use icons instead of toggles for admin user features view (#19369)
* fix: use icons instead of toggles for admin user features view

* fix: use red for X icon
2025-06-20 14:48:18 +00:00
Alex e61d7f2616 refactor(mobile): album_media.interface.dart (#19355)
* refactor(mobile): album_media.interface.dart

* refactor: album_media repo

* make dcm happy

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-06-20 14:23:43 +00:00
Alex a6b0869714 refactor(mobile): secure_storage.interface.dart (#19335)
* refactor(mobile): secure_storage.interface.dart

* fix: lint
2025-06-20 09:12:07 -05:00
Alex 9c25b8ba7d refactor(mobile): download.interface.dart (#19345) 2025-06-20 09:11:52 -05:00
Alex 3c72f489d8 refactor(mobile): sessions_api.interface.dart (#19334)
refactor(mobile): sessions_api.repository.dart
2025-06-20 09:11:33 -05:00
Alex 1f2c779b36 refactor(mobile): shared_handler.interface.dart (#19333)
refactor(mobile): shared_handler.repository.dart
2025-06-20 09:11:21 -05:00
Alex 5c74f634b7 refactor(mobile): network.interface.dart (#19341)
* refactor(mobile): network.interface.dart

* refactor(mobile): network.interface.dart
2025-06-20 09:11:03 -05:00
Alex ecc99bfd16 refactor(mobile): biometric.interface.dart (#19347) 2025-06-20 09:10:53 -05:00
Alex ff4d70e351 refactor(mobile): album_api.interface.dart (#19356) 2025-06-20 09:08:23 -05:00
Alex 42c2389eb5 refactor(mobile): activity_api.interface.dart (#19357) 2025-06-20 09:08:12 -05:00
Jason Rasmussen 33c9f88ba4 fix: time bucket grouping (#19329) 2025-06-20 09:46:30 -04:00
Mert 11c469907f fix(server): migration failing on pg15+ (#19363)
* reset params

* unused parameter
2025-06-20 08:36:07 -05:00
Mert 7c43e6c3c8 fix: bump vchord default to 0.4.3 (#19365)
bump default to 0.4.3
2025-06-20 08:35:32 -05:00
Zack Pollard 00aa385972 fix: people ordering by asset count (#19366) 2025-06-20 07:34:04 -05:00
Min Idzelis a5ed453929 chore: unused deps (#19256)
remove joi
2025-06-20 00:30:23 -04:00
Jason Rasmussen dd8969cb7d fix: container padding (#19316) 2025-06-19 21:33:12 -05:00
Alex bce4f93c90 refactor(mobile): (1) user.interface.dart (#19322)
* refactor(mobile): user.interface.dart

* generate files

* refactor(mobile): (2) user_api.interface.dart (#19323)

* refactor(mobile): (2) user_api.interface.dart

* generate files

* refactor(mobile): (3) sync_stream.interface.dart (#19325)
2025-06-19 23:25:18 +00:00
Alex a4c0dc5007 chore: post release tasks (#19311) 2025-06-19 15:35:25 -04:00
Matthew Momjian d233a7d97a fix(server): remove excessive inactivity log (#19306) 2025-06-19 19:13:13 +00:00
Jason Rasmussen 5cdbb65d28 feat: better contrast for checkmark indicator (#19312)
feat: better constrast
2025-06-19 13:20:57 -05:00
133 changed files with 469 additions and 816 deletions
+1 -1
View File
@@ -644,7 +644,7 @@ jobs:
contents: read contents: read
services: services:
postgres: postgres:
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3
env: env:
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres POSTGRES_USER: postgres
+3 -3
View File
@@ -1,12 +1,12 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.70", "version": "2.2.71",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.70", "version": "2.2.71",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
@@ -54,7 +54,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.135.1", "version": "1.135.2",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.70", "version": "2.2.71",
"description": "Command Line Interface (CLI) for Immich", "description": "Command Line Interface (CLI) for Immich",
"type": "module", "type": "module",
"exports": "./dist/index.js", "exports": "./dist/index.js",
+1 -1
View File
@@ -122,7 +122,7 @@ services:
database: database:
container_name: immich_postgres container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.2-pgvectors0.2.0 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
env_file: env_file:
- .env - .env
environment: environment:
+1 -1
View File
@@ -63,7 +63,7 @@ services:
database: database:
container_name: immich_postgres container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.2-pgvectors0.2.0 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
env_file: env_file:
- .env - .env
environment: environment:
+1 -1
View File
@@ -56,7 +56,7 @@ services:
database: database:
container_name: immich_postgres container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.2-pgvectors0.2.0 image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
environment: environment:
POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME} POSTGRES_USER: ${DB_USERNAME}
+4
View File
@@ -1,4 +1,8 @@
[ [
{
"label": "v1.135.2",
"url": "https://v1.135.2.archive.immich.app"
},
{ {
"label": "v1.135.1", "label": "v1.135.1",
"url": "https://v1.135.1.archive.immich.app" "url": "https://v1.135.1.archive.immich.app"
+4 -4
View File
@@ -1,12 +1,12 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.135.1", "version": "1.135.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.135.1", "version": "1.135.2",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.1.0", "@eslint/eslintrc": "^3.1.0",
@@ -44,7 +44,7 @@
}, },
"../cli": { "../cli": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.70", "version": "2.2.71",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
@@ -93,7 +93,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.135.1", "version": "1.135.2",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.135.1", "version": "1.135.2",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
+13 -13
View File
@@ -125,12 +125,12 @@ describe('/people', () => {
total: 11, total: 11,
hidden: 1, hidden: 1,
people: [ people: [
expect.objectContaining({ name: 'Bill' }),
expect.objectContaining({ name: 'Freddy' }), expect.objectContaining({ name: 'Freddy' }),
expect.objectContaining({ name: 'Alice' }), expect.objectContaining({ name: 'Bill' }),
expect.objectContaining({ name: 'Bob' }),
expect.objectContaining({ name: 'Charlie' }),
expect.objectContaining({ name: 'multiple_assets_person' }), expect.objectContaining({ name: 'multiple_assets_person' }),
expect.objectContaining({ name: 'Bob' }),
expect.objectContaining({ name: 'Alice' }),
expect.objectContaining({ name: 'Charlie' }),
expect.objectContaining({ name: 'visible_person' }), expect.objectContaining({ name: 'visible_person' }),
expect.objectContaining({ id: nameNullPerson4Assets.id, name: '' }), expect.objectContaining({ id: nameNullPerson4Assets.id, name: '' }),
expect.objectContaining({ id: nameNullPerson3Assets.id, name: '' }), expect.objectContaining({ id: nameNullPerson3Assets.id, name: '' }),
@@ -150,12 +150,12 @@ describe('/people', () => {
const people = body.people as PersonResponseDto[]; const people = body.people as PersonResponseDto[];
expect(people.map((p) => p.id)).toEqual([ expect(people.map((p) => p.id)).toEqual([
nameBillPersonFavourite.id, // name: 'Bill', count: 2
nameFreddyPersonFavourite.id, // name: 'Freddy', count: 2 nameFreddyPersonFavourite.id, // name: 'Freddy', count: 2
nameAlicePerson.id, // name: 'Alice', count: 1 nameBillPersonFavourite.id, // name: 'Bill', count: 1
nameBobPerson.id, // name: 'Bob', count: 2
nameCharliePerson.id, // name: 'Charlie', count: 1
multipleAssetsPerson.id, // name: 'multiple_assets_person', count: 3 multipleAssetsPerson.id, // name: 'multiple_assets_person', count: 3
nameBobPerson.id, // name: 'Bob', count: 2
nameAlicePerson.id, // name: 'Alice', count: 1
nameCharliePerson.id, // name: 'Charlie', count: 1
visiblePerson.id, // name: 'visible_person', count: 1 visiblePerson.id, // name: 'visible_person', count: 1
nameNullPerson4Assets.id, // name: '', count: 4 nameNullPerson4Assets.id, // name: '', count: 4
nameNullPerson3Assets.id, // name: '', count: 3 nameNullPerson3Assets.id, // name: '', count: 3
@@ -173,12 +173,12 @@ describe('/people', () => {
total: 11, total: 11,
hidden: 1, hidden: 1,
people: [ people: [
expect.objectContaining({ name: 'Bill' }),
expect.objectContaining({ name: 'Freddy' }), expect.objectContaining({ name: 'Freddy' }),
expect.objectContaining({ name: 'Alice' }), expect.objectContaining({ name: 'Bill' }),
expect.objectContaining({ name: 'Bob' }),
expect.objectContaining({ name: 'Charlie' }),
expect.objectContaining({ name: 'multiple_assets_person' }), expect.objectContaining({ name: 'multiple_assets_person' }),
expect.objectContaining({ name: 'Bob' }),
expect.objectContaining({ name: 'Alice' }),
expect.objectContaining({ name: 'Charlie' }),
expect.objectContaining({ name: 'visible_person' }), expect.objectContaining({ name: 'visible_person' }),
expect.objectContaining({ id: nameNullPerson4Assets.id, name: '' }), expect.objectContaining({ id: nameNullPerson4Assets.id, name: '' }),
expect.objectContaining({ id: nameNullPerson3Assets.id, name: '' }), expect.objectContaining({ id: nameNullPerson3Assets.id, name: '' }),
@@ -197,7 +197,7 @@ describe('/people', () => {
hasNextPage: true, hasNextPage: true,
total: 11, total: 11,
hidden: 1, hidden: 1,
people: [expect.objectContaining({ name: 'Charlie' })], people: [expect.objectContaining({ name: 'Alice' })],
}); });
}); });
}); });
-1
View File
@@ -244,7 +244,6 @@ async def load(model: InferenceModel) -> InferenceModel:
async def idle_shutdown_task() -> None: async def idle_shutdown_task() -> None:
while True: while True:
log.debug("Checking for inactivity...")
if ( if (
last_called is not None last_called is not None
and not active_requests and not active_requests
+2 -2
View File
@@ -35,8 +35,8 @@ platform :android do
task: 'bundle', task: 'bundle',
build_type: 'Release', build_type: 'Release',
properties: { properties: {
"android.injected.version.code" => 202, "android.injected.version.code" => 203,
"android.injected.version.name" => "1.135.1", "android.injected.version.name" => "1.135.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') 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')
+12 -18
View File
@@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 77; objectVersion = 54;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@@ -117,6 +117,8 @@
/* Begin PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFileSystemSynchronizedRootGroup section */
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = { B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
isa = PBXFileSystemSynchronizedRootGroup; isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = Sync; path = Sync;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@@ -471,14 +473,10 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Copy Pods Resources"; name = "[CP] Copy Pods Resources";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
@@ -507,14 +505,10 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
@@ -655,7 +649,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 209; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -799,7 +793,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 209; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -829,7 +823,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 209; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -863,7 +857,7 @@
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 209; CURRENT_PROJECT_VERSION = 210;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -906,7 +900,7 @@
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 209; CURRENT_PROJECT_VERSION = 210;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -946,7 +940,7 @@
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 209; CURRENT_PROJECT_VERSION = 210;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -985,7 +979,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 209; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -1029,7 +1023,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 209; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -1070,7 +1064,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 209; CURRENT_PROJECT_VERSION = 210;
CUSTOM_GROUP_ID = group.app.immich.share; CUSTOM_GROUP_ID = group.app.immich.share;
DEVELOPMENT_TEAM = 2F67MQ8R79; DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
+2 -2
View File
@@ -78,7 +78,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.135.0</string> <string>1.135.1</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
@@ -93,7 +93,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>209</string> <string>210</string>
<key>FLTEnableImpeller</key> <key>FLTEnableImpeller</key>
<true /> <true />
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
+1 -1
View File
@@ -22,7 +22,7 @@ platform :ios do
path: "./Runner.xcodeproj", path: "./Runner.xcodeproj",
) )
increment_version_number( increment_version_number(
version_number: "1.135.1" version_number: "1.135.2"
) )
increment_build_number( increment_build_number(
build_number: latest_testflight_build_number + 1, build_number: latest_testflight_build_number + 1,
+2
View File
@@ -10,3 +10,5 @@ enum TextSearchType {
} }
enum AssetVisibilityEnum { timeline, hidden, archive, locked } enum AssetVisibilityEnum { timeline, hidden, archive, locked }
enum SortUserBy { id }
@@ -1,18 +0,0 @@
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
import 'package:openapi/api.dart';
abstract interface class ISyncStreamRepository implements IDatabaseRepository {
Future<void> updateUsersV1(Iterable<SyncUserV1> data);
Future<void> deleteUsersV1(Iterable<SyncUserDeleteV1> data);
Future<void> updatePartnerV1(Iterable<SyncPartnerV1> data);
Future<void> deletePartnerV1(Iterable<SyncPartnerDeleteV1> data);
Future<void> updateAssetsV1(Iterable<SyncAssetV1> data);
Future<void> deleteAssetsV1(Iterable<SyncAssetDeleteV1> data);
Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data);
Future<void> updatePartnerAssetsV1(Iterable<SyncAssetV1> data);
Future<void> deletePartnerAssetsV1(Iterable<SyncAssetDeleteV1> data);
Future<void> updatePartnerAssetsExifV1(Iterable<SyncAssetExifV1> data);
}
@@ -1,22 +0,0 @@
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
abstract interface class IUserRepository implements IDatabaseRepository {
Future<bool> insert(UserDto user);
Future<UserDto?> getByUserId(String id);
Future<List<UserDto?>> getByUserIds(List<String> ids);
Future<List<UserDto>> getAll({SortUserBy? sortBy});
Future<bool> updateAll(List<UserDto> users);
Future<UserDto> update(UserDto user);
Future<void> delete(List<String> ids);
Future<void> deleteAll();
}
enum SortUserBy { id }
@@ -1,15 +0,0 @@
import 'dart:typed_data';
import 'package:immich_mobile/domain/models/user.model.dart';
abstract interface class IUserApiRepository {
Future<UserDto?> getMyUser();
Future<List<UserDto>> getAll();
/// Saves the [data] in the server and uses it as the current users profile image
Future<String> createProfileImage({
required String name,
required Uint8List data,
});
}
@@ -1,11 +1,5 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert'; import 'dart:convert';
abstract interface class IPersonApiRepository {
Future<List<Person>> getAll();
Future<Person> update(String id, {String? name});
}
class Person { class Person {
Person({ Person({
required this.id, required this.id,
@@ -1,8 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart'; import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart';
import 'package:immich_mobile/domain/interfaces/sync_stream.interface.dart';
import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
@@ -10,12 +10,12 @@ class SyncStreamService {
final Logger _logger = Logger('SyncStreamService'); final Logger _logger = Logger('SyncStreamService');
final ISyncApiRepository _syncApiRepository; final ISyncApiRepository _syncApiRepository;
final ISyncStreamRepository _syncStreamRepository; final SyncStreamRepository _syncStreamRepository;
final bool Function()? _cancelChecker; final bool Function()? _cancelChecker;
SyncStreamService({ SyncStreamService({
required ISyncApiRepository syncApiRepository, required ISyncApiRepository syncApiRepository,
required ISyncStreamRepository syncStreamRepository, required SyncStreamRepository syncStreamRepository,
bool Function()? cancelChecker, bool Function()? cancelChecker,
}) : _syncApiRepository = syncApiRepository, }) : _syncApiRepository = syncApiRepository,
_syncStreamRepository = syncStreamRepository, _syncStreamRepository = syncStreamRepository,
+11 -11
View File
@@ -1,24 +1,24 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/domain/interfaces/user_api.interface.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/domain/services/store.service.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
class UserService { class UserService {
final Logger _log = Logger("UserService"); final Logger _log = Logger("UserService");
final IUserRepository _userRepository; final IsarUserRepository _isarUserRepository;
final IUserApiRepository _userApiRepository; final UserApiRepository _userApiRepository;
final StoreService _storeService; final StoreService _storeService;
UserService({ UserService({
required IUserRepository userRepository, required IsarUserRepository isarUserRepository,
required IUserApiRepository userApiRepository, required UserApiRepository userApiRepository,
required StoreService storeService, required StoreService storeService,
}) : _userRepository = userRepository, }) : _isarUserRepository = isarUserRepository,
_userApiRepository = userApiRepository, _userApiRepository = userApiRepository,
_storeService = storeService; _storeService = storeService;
@@ -38,7 +38,7 @@ class UserService {
final user = await _userApiRepository.getMyUser(); final user = await _userApiRepository.getMyUser();
if (user == null) return null; if (user == null) return null;
await _storeService.put(StoreKey.currentUser, user); await _storeService.put(StoreKey.currentUser, user);
await _userRepository.update(user); await _isarUserRepository.update(user);
return user; return user;
} }
@@ -50,7 +50,7 @@ class UserService {
); );
final updatedUser = getMyUser().copyWith(profileImagePath: path); final updatedUser = getMyUser().copyWith(profileImagePath: path);
await _storeService.put(StoreKey.currentUser, updatedUser); await _storeService.put(StoreKey.currentUser, updatedUser);
await _userRepository.update(updatedUser); await _isarUserRepository.update(updatedUser);
return path; return path;
} catch (e) { } catch (e) {
_log.warning("Failed to upload profile image", e); _log.warning("Failed to upload profile image", e);
@@ -59,10 +59,10 @@ class UserService {
} }
Future<List<UserDto>> getAll() async { Future<List<UserDto>> getAll() async {
return await _userRepository.getAll(); return await _isarUserRepository.getAll();
} }
Future<void> deleteAll() { Future<void> deleteAll() {
return _userRepository.deleteAll(); return _isarUserRepository.deleteAll();
} }
} }
@@ -1,5 +1,4 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/interfaces/sync_stream.interface.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart';
@@ -10,14 +9,12 @@ import 'package:logging/logging.dart';
import 'package:openapi/api.dart' as api show AssetVisibility; import 'package:openapi/api.dart' as api show AssetVisibility;
import 'package:openapi/api.dart' hide AssetVisibility; import 'package:openapi/api.dart' hide AssetVisibility;
class DriftSyncStreamRepository extends DriftDatabaseRepository class SyncStreamRepository extends DriftDatabaseRepository {
implements ISyncStreamRepository {
final Logger _logger = Logger('DriftSyncStreamRepository'); final Logger _logger = Logger('DriftSyncStreamRepository');
final Drift _db; final Drift _db;
DriftSyncStreamRepository(super.db) : _db = db; SyncStreamRepository(super.db) : _db = db;
@override
Future<void> deleteUsersV1(Iterable<SyncUserDeleteV1> data) async { Future<void> deleteUsersV1(Iterable<SyncUserDeleteV1> data) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
@@ -34,7 +31,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updateUsersV1(Iterable<SyncUserV1> data) async { Future<void> updateUsersV1(Iterable<SyncUserV1> data) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
@@ -57,7 +53,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> deletePartnerV1(Iterable<SyncPartnerDeleteV1> data) async { Future<void> deletePartnerV1(Iterable<SyncPartnerDeleteV1> data) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
@@ -77,7 +72,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updatePartnerV1(Iterable<SyncPartnerV1> data) async { Future<void> updatePartnerV1(Iterable<SyncPartnerV1> data) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
@@ -101,7 +95,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> deleteAssetsV1(Iterable<SyncAssetDeleteV1> data) async { Future<void> deleteAssetsV1(Iterable<SyncAssetDeleteV1> data) async {
try { try {
await _deleteAssetsV1(data); await _deleteAssetsV1(data);
@@ -111,7 +104,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updateAssetsV1(Iterable<SyncAssetV1> data) async { Future<void> updateAssetsV1(Iterable<SyncAssetV1> data) async {
try { try {
await _updateAssetsV1(data); await _updateAssetsV1(data);
@@ -121,7 +113,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> deletePartnerAssetsV1(Iterable<SyncAssetDeleteV1> data) async { Future<void> deletePartnerAssetsV1(Iterable<SyncAssetDeleteV1> data) async {
try { try {
await _deleteAssetsV1(data); await _deleteAssetsV1(data);
@@ -131,7 +122,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updatePartnerAssetsV1(Iterable<SyncAssetV1> data) async { Future<void> updatePartnerAssetsV1(Iterable<SyncAssetV1> data) async {
try { try {
await _updateAssetsV1(data); await _updateAssetsV1(data);
@@ -141,7 +131,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data) async { Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data) async {
try { try {
await _updateAssetExifV1(data); await _updateAssetExifV1(data);
@@ -151,7 +140,6 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
} }
} }
@override
Future<void> updatePartnerAssetsExifV1(Iterable<SyncAssetExifV1> data) async { Future<void> updatePartnerAssetsExifV1(Iterable<SyncAssetExifV1> data) async {
try { try {
await _updateAssetExifV1(data); await _updateAssetExifV1(data);
@@ -1,30 +1,26 @@
import 'package:immich_mobile/domain/interfaces/user.interface.dart'; import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
as entity; as entity;
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
class IsarUserRepository extends IsarDatabaseRepository class IsarUserRepository extends IsarDatabaseRepository {
implements IUserRepository {
final Isar _db; final Isar _db;
const IsarUserRepository(super.db) : _db = db; const IsarUserRepository(super.db) : _db = db;
@override
Future<void> delete(List<String> ids) async { Future<void> delete(List<String> ids) async {
await transaction(() async { await transaction(() async {
await _db.users.deleteAllById(ids); await _db.users.deleteAllById(ids);
}); });
} }
@override
Future<void> deleteAll() async { Future<void> deleteAll() async {
await transaction(() async { await transaction(() async {
await _db.users.clear(); await _db.users.clear();
}); });
} }
@override
Future<List<UserDto>> getAll({SortUserBy? sortBy}) async { Future<List<UserDto>> getAll({SortUserBy? sortBy}) async {
return (await _db.users return (await _db.users
.where() .where()
@@ -39,17 +35,14 @@ class IsarUserRepository extends IsarDatabaseRepository
.toList(); .toList();
} }
@override
Future<UserDto?> getByUserId(String id) async { Future<UserDto?> getByUserId(String id) async {
return (await _db.users.getById(id))?.toDto(); return (await _db.users.getById(id))?.toDto();
} }
@override
Future<List<UserDto?>> getByUserIds(List<String> ids) async { Future<List<UserDto?>> getByUserIds(List<String> ids) async {
return (await _db.users.getAllById(ids)).map((u) => u?.toDto()).toList(); return (await _db.users.getAllById(ids)).map((u) => u?.toDto()).toList();
} }
@override
Future<bool> insert(UserDto user) async { Future<bool> insert(UserDto user) async {
await transaction(() async { await transaction(() async {
await _db.users.put(entity.User.fromDto(user)); await _db.users.put(entity.User.fromDto(user));
@@ -57,7 +50,6 @@ class IsarUserRepository extends IsarDatabaseRepository
return true; return true;
} }
@override
Future<UserDto> update(UserDto user) async { Future<UserDto> update(UserDto user) async {
await transaction(() async { await transaction(() async {
await _db.users.put(entity.User.fromDto(user)); await _db.users.put(entity.User.fromDto(user));
@@ -65,7 +57,6 @@ class IsarUserRepository extends IsarDatabaseRepository
return user; return user;
} }
@override
Future<bool> updateAll(List<UserDto> users) async { Future<bool> updateAll(List<UserDto> users) async {
await transaction(() async { await transaction(() async {
await _db.users.putAll(users.map(entity.User.fromDto).toList()); await _db.users.putAll(users.map(entity.User.fromDto).toList());
@@ -1,17 +1,15 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:immich_mobile/domain/interfaces/user_api.interface.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/repositories/api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/api.repository.dart';
import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
class UserApiRepository extends ApiRepository implements IUserApiRepository { class UserApiRepository extends ApiRepository {
final UsersApi _api; final UsersApi _api;
const UserApiRepository(this._api); const UserApiRepository(this._api);
@override
Future<UserDto?> getMyUser() async { Future<UserDto?> getMyUser() async {
final (adminDto, preferenceDto) = final (adminDto, preferenceDto) =
await (_api.getMyUser(), _api.getMyPreferences()).wait; await (_api.getMyUser(), _api.getMyPreferences()).wait;
@@ -20,7 +18,6 @@ class UserApiRepository extends ApiRepository implements IUserApiRepository {
return UserConverter.fromAdminDto(adminDto, preferenceDto); return UserConverter.fromAdminDto(adminDto, preferenceDto);
} }
@override
Future<String> createProfileImage({ Future<String> createProfileImage({
required String name, required String name,
required Uint8List data, required Uint8List data,
@@ -33,7 +30,6 @@ class UserApiRepository extends ApiRepository implements IUserApiRepository {
return res.profileImagePath; return res.profileImagePath;
} }
@override
Future<List<UserDto>> getAll() async { Future<List<UserDto>> getAll() async {
final dto = await checkNull(_api.searchUsers()); final dto = await checkNull(_api.searchUsers());
return dto.map(UserConverter.fromSimpleUserDto).toList(); return dto.map(UserConverter.fromSimpleUserDto).toList();
@@ -1,16 +0,0 @@
import 'package:immich_mobile/models/activities/activity.model.dart';
abstract interface class IActivityApiRepository {
Future<List<Activity>> getAll(
String albumId, {
String? assetId,
});
Future<Activity> create(
String albumId,
ActivityType type, {
String? assetId,
String? comment,
});
Future<void> delete(String id);
Future<ActivityStats> getStats(String albumId, {String? assetId});
}
@@ -1,42 +0,0 @@
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/entities/album.entity.dart';
abstract interface class IAlbumApiRepository {
Future<Album> get(String id);
Future<List<Album>> getAll({bool? shared});
Future<Album> create(
String name, {
required Iterable<String> assetIds,
Iterable<String> sharedUserIds = const [],
});
Future<Album> update(
String albumId, {
String? name,
String? thumbnailAssetId,
String? description,
bool? activityEnabled,
SortOrder? sortOrder,
});
Future<void> delete(String albumId);
Future<({List<String> added, List<String> duplicates})> addAssets(
String albumId,
Iterable<String> assetIds,
);
Future<({List<String> removed, List<String> failed})> removeAssets(
String albumId,
Iterable<String> assetIds,
);
Future<Album> addUsers(
String albumId,
Iterable<String> userIds,
);
Future<void> removeUser(String albumId, {required String userId});
}
@@ -1,21 +0,0 @@
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
abstract interface class IAlbumMediaRepository {
Future<List<Album>> getAll();
Future<List<String>> getAssetIds(String albumId);
Future<int> getAssetCount(String albumId);
Future<List<Asset>> getAssets(
String albumId, {
int start = 0,
int end = 0x7fffffffffffffff,
DateTime? modifiedFrom,
DateTime? modifiedUntil,
bool orderByModificationDate = false,
});
Future<Album> get(String id);
}
@@ -1,6 +0,0 @@
import 'package:immich_mobile/models/auth/biometric_status.model.dart';
abstract interface class IBiometricRepository {
Future<BiometricStatus> getStatus();
Future<bool> authenticate(String? message);
}
@@ -1,15 +0,0 @@
import 'package:background_downloader/background_downloader.dart';
abstract interface class IDownloadRepository {
void Function(TaskStatusUpdate)? onImageDownloadStatus;
void Function(TaskStatusUpdate)? onVideoDownloadStatus;
void Function(TaskStatusUpdate)? onLivePhotoDownloadStatus;
void Function(TaskProgressUpdate)? onTaskProgress;
Future<List<TaskRecord>> getLiveVideoTasks();
Future<List<bool>> downloadAll(List<DownloadTask> tasks);
Future<bool> cancel(String id);
Future<void> deleteAllTrackingRecords();
Future<void> deleteRecordsWithIds(List<String> id);
}
@@ -1,4 +0,0 @@
abstract interface class INetworkRepository {
Future<String?> getWifiName();
Future<String?> getWifiIp();
}
@@ -1,8 +0,0 @@
import 'package:immich_mobile/domain/models/user.model.dart';
abstract class IPartnerRepository {
Future<List<UserDto>> getSharedWith();
Future<List<UserDto>> getSharedBy();
Stream<List<UserDto>> watchSharedWith();
Stream<List<UserDto>> watchSharedBy();
}
@@ -1,13 +0,0 @@
import 'package:immich_mobile/domain/models/user.model.dart';
abstract interface class IPartnerApiRepository {
Future<List<UserDto>> getAll(Direction direction);
Future<UserDto> create(String id);
Future<UserDto> update(String id, {required bool inTimeline});
Future<void> delete(String id);
}
enum Direction {
sharedWithMe,
sharedByMe,
}
@@ -1,5 +0,0 @@
abstract interface class ISecureStorageRepository {
Future<String?> read(String key);
Future<void> write(String key, String value);
Future<void> delete(String key);
}
@@ -1,9 +0,0 @@
import 'package:immich_mobile/models/sessions/session_create_response.model.dart';
abstract interface class ISessionAPIRepository {
Future<SessionCreateResponse> createSession(
String deviceName,
String deviceOS, {
int? duration,
});
}
@@ -1,7 +0,0 @@
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
abstract interface class IShareHandlerRepository {
void Function(List<ShareIntentAttachment>)? onSharedMedia;
Future<void> init();
}
@@ -1,39 +0,0 @@
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
abstract class ITimelineRepository {
Future<List<String>> getTimelineUserIds(String id);
Stream<List<String>> watchTimelineUsers(String id);
Stream<RenderList> watchArchiveTimeline(String userId);
Stream<RenderList> watchFavoriteTimeline(String userId);
Stream<RenderList> watchTrashTimeline(String userId);
Stream<RenderList> watchAlbumTimeline(
Album album,
GroupAssetsBy groupAssetsBy,
);
Stream<RenderList> watchAllVideosTimeline(String userId);
Stream<RenderList> watchHomeTimeline(
String userId,
GroupAssetsBy groupAssetsBy,
);
Stream<RenderList> watchMultiUsersTimeline(
List<String> userIds,
GroupAssetsBy groupAssetsBy,
);
Future<RenderList> getTimelineFromAssets(
List<Asset> assets,
GroupAssetsBy getGroupByOption,
);
Stream<RenderList> watchAssetSelectionTimeline(String userId);
Stream<RenderList> watchLockedTimeline(
String userId,
GroupAssetsBy groupAssetsBy,
);
}
@@ -1,8 +1,8 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first // ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert'; import 'dart:convert';
import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
class SearchLocationFilter { class SearchLocationFilter {
String? country; String? country;
+1 -1
View File
@@ -6,9 +6,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/providers/search/paginated_search.provider.dart'; import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
import 'package:immich_mobile/providers/search/search_input_focus.provider.dart'; import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';
@@ -9,7 +9,6 @@ import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/backup_album.interface.dart'; import 'package:immich_mobile/interfaces/backup_album.interface.dart';
import 'package:immich_mobile/interfaces/file_media.interface.dart'; import 'package:immich_mobile/interfaces/file_media.interface.dart';
import 'package:immich_mobile/models/auth/auth_state.model.dart'; import 'package:immich_mobile/models/auth/auth_state.model.dart';
@@ -108,7 +107,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
final AuthState _authState; final AuthState _authState;
final BackgroundService _backgroundService; final BackgroundService _backgroundService;
final GalleryPermissionNotifier _galleryPermissionNotifier; final GalleryPermissionNotifier _galleryPermissionNotifier;
final IAlbumMediaRepository _albumMediaRepository; final AlbumMediaRepository _albumMediaRepository;
final IFileMediaRepository _fileMediaRepository; final IFileMediaRepository _fileMediaRepository;
final BackupAlbumService _backupAlbumService; final BackupAlbumService _backupAlbumService;
final Ref ref; final Ref ref;
@@ -26,7 +26,7 @@ final syncApiRepositoryProvider = Provider(
); );
final syncStreamRepositoryProvider = Provider( final syncStreamRepositoryProvider = Provider(
(ref) => DriftSyncStreamRepository(ref.watch(driftProvider)), (ref) => SyncStreamRepository(ref.watch(driftProvider)),
); );
final localSyncServiceProvider = Provider( final localSyncServiceProvider = Provider(
@@ -1,6 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/domain/interfaces/user_api.interface.dart';
import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
@@ -12,16 +10,16 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'user.provider.g.dart'; part 'user.provider.g.dart';
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
IUserRepository userRepository(Ref ref) => IsarUserRepository userRepository(Ref ref) =>
IsarUserRepository(ref.watch(isarProvider)); IsarUserRepository(ref.watch(isarProvider));
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
IUserApiRepository userApiRepository(Ref ref) => UserApiRepository userApiRepository(Ref ref) =>
UserApiRepository(ref.watch(apiServiceProvider).usersApi); UserApiRepository(ref.watch(apiServiceProvider).usersApi);
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
UserService userService(Ref ref) => UserService( UserService userService(Ref ref) => UserService(
userRepository: ref.watch(userRepositoryProvider), isarUserRepository: ref.watch(userRepositoryProvider),
userApiRepository: ref.watch(userApiRepositoryProvider), userApiRepository: ref.watch(userApiRepositoryProvider),
storeService: ref.watch(storeServiceProvider), storeService: ref.watch(storeServiceProvider),
); );
+7 -7
View File
@@ -6,11 +6,11 @@ part of 'user.provider.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$userRepositoryHash() => r'1a2ac726bcc44397dcaecf449084fefd336696d4'; String _$userRepositoryHash() => r'538791a4ad126ed086c9db682c67fc5c654d54f3';
/// See also [userRepository]. /// See also [userRepository].
@ProviderFor(userRepository) @ProviderFor(userRepository)
final userRepositoryProvider = Provider<IUserRepository>.internal( final userRepositoryProvider = Provider<IsarUserRepository>.internal(
userRepository, userRepository,
name: r'userRepositoryProvider', name: r'userRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
@@ -22,12 +22,12 @@ final userRepositoryProvider = Provider<IUserRepository>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef UserRepositoryRef = ProviderRef<IUserRepository>; typedef UserRepositoryRef = ProviderRef<IsarUserRepository>;
String _$userApiRepositoryHash() => r'6b19f2c99fb83162a5ceb91adb8589eaae01bc92'; String _$userApiRepositoryHash() => r'8a7340ca4544c8c6b20225c65bff2abb9e96baa2';
/// See also [userApiRepository]. /// See also [userApiRepository].
@ProviderFor(userApiRepository) @ProviderFor(userApiRepository)
final userApiRepositoryProvider = Provider<IUserApiRepository>.internal( final userApiRepositoryProvider = Provider<UserApiRepository>.internal(
userApiRepository, userApiRepository,
name: r'userApiRepositoryProvider', name: r'userApiRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
@@ -39,8 +39,8 @@ final userApiRepositoryProvider = Provider<IUserApiRepository>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef UserApiRepositoryRef = ProviderRef<IUserApiRepository>; typedef UserApiRepositoryRef = ProviderRef<UserApiRepository>;
String _$userServiceHash() => r'4a0873357b7115b4d6bfa8e89b847c0b74ce0d93'; String _$userServiceHash() => r'181414dddc7891be6237e13d568c287a804228d1';
/// See also [userService]. /// See also [userService].
@ProviderFor(userService) @ProviderFor(userService)
@@ -1,5 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart'; import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/services/person.service.dart'; import 'package:immich_mobile/services/person.service.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
@@ -1,6 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
import 'package:immich_mobile/interfaces/activity_api.interface.dart';
import 'package:immich_mobile/models/activities/activity.model.dart'; import 'package:immich_mobile/models/activities/activity.model.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/api.repository.dart'; import 'package:immich_mobile/repositories/api.repository.dart';
@@ -10,20 +9,17 @@ final activityApiRepositoryProvider = Provider(
(ref) => ActivityApiRepository(ref.watch(apiServiceProvider).activitiesApi), (ref) => ActivityApiRepository(ref.watch(apiServiceProvider).activitiesApi),
); );
class ActivityApiRepository extends ApiRepository class ActivityApiRepository extends ApiRepository {
implements IActivityApiRepository {
final ActivitiesApi _api; final ActivitiesApi _api;
ActivityApiRepository(this._api); ActivityApiRepository(this._api);
@override
Future<List<Activity>> getAll(String albumId, {String? assetId}) async { Future<List<Activity>> getAll(String albumId, {String? assetId}) async {
final response = final response =
await checkNull(_api.getActivities(albumId, assetId: assetId)); await checkNull(_api.getActivities(albumId, assetId: assetId));
return response.map(_toActivity).toList(); return response.map(_toActivity).toList();
} }
@override
Future<Activity> create( Future<Activity> create(
String albumId, String albumId,
ActivityType type, { ActivityType type, {
@@ -42,12 +38,10 @@ class ActivityApiRepository extends ApiRepository
return _toActivity(response); return _toActivity(response);
} }
@override
Future<void> delete(String id) { Future<void> delete(String id) {
return checkNull(_api.deleteActivity(id)); return checkNull(_api.deleteActivity(id));
} }
@override
Future<ActivityStats> getStats(String albumId, {String? assetId}) async { Future<ActivityStats> getStats(String albumId, {String? assetId}) async {
final response = final response =
await checkNull(_api.getActivityStatistics(albumId, assetId: assetId)); await checkNull(_api.getActivityStatistics(albumId, assetId: assetId));
@@ -5,7 +5,6 @@ import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
as entity; as entity;
import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
import 'package:immich_mobile/interfaces/album_api.interface.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/api.repository.dart'; import 'package:immich_mobile/repositories/api.repository.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
@@ -14,24 +13,21 @@ final albumApiRepositoryProvider = Provider(
(ref) => AlbumApiRepository(ref.watch(apiServiceProvider).albumsApi), (ref) => AlbumApiRepository(ref.watch(apiServiceProvider).albumsApi),
); );
class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository { class AlbumApiRepository extends ApiRepository {
final AlbumsApi _api; final AlbumsApi _api;
AlbumApiRepository(this._api); AlbumApiRepository(this._api);
@override
Future<Album> get(String id) async { Future<Album> get(String id) async {
final dto = await checkNull(_api.getAlbumInfo(id)); final dto = await checkNull(_api.getAlbumInfo(id));
return _toAlbum(dto); return _toAlbum(dto);
} }
@override
Future<List<Album>> getAll({bool? shared}) async { Future<List<Album>> getAll({bool? shared}) async {
final dtos = await checkNull(_api.getAllAlbums(shared: shared)); final dtos = await checkNull(_api.getAllAlbums(shared: shared));
return dtos.map(_toAlbum).toList(); return dtos.map(_toAlbum).toList();
} }
@override
Future<Album> create( Future<Album> create(
String name, { String name, {
required Iterable<String> assetIds, required Iterable<String> assetIds,
@@ -54,7 +50,6 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
return _toAlbum(responseDto); return _toAlbum(responseDto);
} }
@override
Future<Album> update( Future<Album> update(
String albumId, { String albumId, {
String? name, String? name,
@@ -84,12 +79,10 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
return _toAlbum(response); return _toAlbum(response);
} }
@override
Future<void> delete(String albumId) { Future<void> delete(String albumId) {
return _api.deleteAlbum(albumId); return _api.deleteAlbum(albumId);
} }
@override
Future<({List<String> added, List<String> duplicates})> addAssets( Future<({List<String> added, List<String> duplicates})> addAssets(
String albumId, String albumId,
Iterable<String> assetIds, Iterable<String> assetIds,
@@ -114,7 +107,6 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
return (added: added, duplicates: duplicates); return (added: added, duplicates: duplicates);
} }
@override
Future<({List<String> removed, List<String> failed})> removeAssets( Future<({List<String> removed, List<String> failed})> removeAssets(
String albumId, String albumId,
Iterable<String> assetIds, Iterable<String> assetIds,
@@ -136,7 +128,6 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
return (removed: removed, failed: failed); return (removed: removed, failed: failed);
} }
@override
Future<Album> addUsers(String albumId, Iterable<String> userIds) async { Future<Album> addUsers(String albumId, Iterable<String> userIds) async {
final albumUsers = final albumUsers =
userIds.map((userId) => AlbumUserAddDto(userId: userId)).toList(); userIds.map((userId) => AlbumUserAddDto(userId: userId)).toList();
@@ -149,7 +140,6 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
return _toAlbum(response); return _toAlbum(response);
} }
@override
Future<void> removeUser(String albumId, {required String userId}) { Future<void> removeUser(String albumId, {required String userId}) {
return _api.removeUserFromAlbum(albumId, userId); return _api.removeUserFromAlbum(albumId, userId);
} }
@@ -4,14 +4,13 @@ import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart';
import 'package:photo_manager/photo_manager.dart' hide AssetType; import 'package:photo_manager/photo_manager.dart' hide AssetType;
final albumMediaRepositoryProvider = final albumMediaRepositoryProvider =
Provider((ref) => const AlbumMediaRepository()); Provider((ref) => const AlbumMediaRepository());
class AlbumMediaRepository implements IAlbumMediaRepository { class AlbumMediaRepository {
const AlbumMediaRepository(); const AlbumMediaRepository();
bool get useCustomFilter => bool get useCustomFilter =>
@@ -41,7 +40,6 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
) )
: null; : null;
@override
Future<List<Album>> getAll() async { Future<List<Album>> getAll() async {
final filter = useCustomFilter final filter = useCustomFilter
? CustomFilter.sql(where: '${CustomColumns.base.width} > 0') ? CustomFilter.sql(where: '${CustomColumns.base.width} > 0')
@@ -52,7 +50,6 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
return assetPathEntities.map(_toAlbum).toList(); return assetPathEntities.map(_toAlbum).toList();
} }
@override
Future<List<String>> getAssetIds(String albumId) async { Future<List<String>> getAssetIds(String albumId) async {
final album = final album =
await AssetPathEntity.fromId(albumId, filterOption: _getAlbumFilter()); await AssetPathEntity.fromId(albumId, filterOption: _getAlbumFilter());
@@ -61,14 +58,12 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
return assets.map((e) => e.id).toList(); return assets.map((e) => e.id).toList();
} }
@override
Future<int> getAssetCount(String albumId) async { Future<int> getAssetCount(String albumId) async {
final album = final album =
await AssetPathEntity.fromId(albumId, filterOption: _getAlbumFilter()); await AssetPathEntity.fromId(albumId, filterOption: _getAlbumFilter());
return album.assetCountAsync; return album.assetCountAsync;
} }
@override
Future<List<Asset>> getAssets( Future<List<Asset>> getAssets(
String albumId, { String albumId, {
int start = 0, int start = 0,
@@ -97,12 +92,7 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
return assets.map(AssetMediaRepository.toAsset).toList().cast(); return assets.map(AssetMediaRepository.toAsset).toList().cast();
} }
@override Future<Album> get(String id) async {
Future<Album> get(
String id, {
DateTime? modifiedFrom,
DateTime? modifiedUntil,
}) async {
final assetPathEntity = await AssetPathEntity.fromId( final assetPathEntity = await AssetPathEntity.fromId(
id, id,
filterOption: _getAlbumFilter(containsPathModified: true), filterOption: _getAlbumFilter(containsPathModified: true),
@@ -1,18 +1,16 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/biometric.interface.dart';
import 'package:immich_mobile/models/auth/biometric_status.model.dart'; import 'package:immich_mobile/models/auth/biometric_status.model.dart';
import 'package:local_auth/local_auth.dart'; import 'package:local_auth/local_auth.dart';
final biometricRepositoryProvider = final biometricRepositoryProvider =
Provider((ref) => BiometricRepository(LocalAuthentication())); Provider((ref) => BiometricRepository(LocalAuthentication()));
class BiometricRepository implements IBiometricRepository { class BiometricRepository {
final LocalAuthentication _localAuth; final LocalAuthentication _localAuth;
BiometricRepository(this._localAuth); BiometricRepository(this._localAuth);
@override
Future<BiometricStatus> getStatus() async { Future<BiometricStatus> getStatus() async {
final bool canAuthenticateWithBiometrics = final bool canAuthenticateWithBiometrics =
await _localAuth.canCheckBiometrics; await _localAuth.canCheckBiometrics;
@@ -26,7 +24,6 @@ class BiometricRepository implements IBiometricRepository {
); );
} }
@override
Future<bool> authenticate(String? message) async { Future<bool> authenticate(String? message) async {
return _localAuth.authenticate( return _localAuth.authenticate(
localizedReason: message ?? 'please_auth_to_access'.tr(), localizedReason: message ?? 'please_auth_to_access'.tr(),
@@ -1,21 +1,16 @@
import 'package:background_downloader/background_downloader.dart'; import 'package:background_downloader/background_downloader.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/download.interface.dart';
import 'package:immich_mobile/utils/download.dart'; import 'package:immich_mobile/utils/download.dart';
final downloadRepositoryProvider = Provider((ref) => DownloadRepository()); final downloadRepositoryProvider = Provider((ref) => DownloadRepository());
class DownloadRepository implements IDownloadRepository { class DownloadRepository {
@override
void Function(TaskStatusUpdate)? onImageDownloadStatus; void Function(TaskStatusUpdate)? onImageDownloadStatus;
@override
void Function(TaskStatusUpdate)? onVideoDownloadStatus; void Function(TaskStatusUpdate)? onVideoDownloadStatus;
@override
void Function(TaskStatusUpdate)? onLivePhotoDownloadStatus; void Function(TaskStatusUpdate)? onLivePhotoDownloadStatus;
@override
void Function(TaskProgressUpdate)? onTaskProgress; void Function(TaskProgressUpdate)? onTaskProgress;
DownloadRepository() { DownloadRepository() {
@@ -38,22 +33,18 @@ class DownloadRepository implements IDownloadRepository {
); );
} }
@override
Future<List<bool>> downloadAll(List<DownloadTask> tasks) { Future<List<bool>> downloadAll(List<DownloadTask> tasks) {
return FileDownloader().enqueueAll(tasks); return FileDownloader().enqueueAll(tasks);
} }
@override
Future<void> deleteAllTrackingRecords() { Future<void> deleteAllTrackingRecords() {
return FileDownloader().database.deleteAllRecords(); return FileDownloader().database.deleteAllRecords();
} }
@override
Future<bool> cancel(String id) { Future<bool> cancel(String id) {
return FileDownloader().cancelTaskWithId(id); return FileDownloader().cancelTaskWithId(id);
} }
@override
Future<List<TaskRecord>> getLiveVideoTasks() { Future<List<TaskRecord>> getLiveVideoTasks() {
return FileDownloader().database.allRecordsWithStatus( return FileDownloader().database.allRecordsWithStatus(
TaskStatus.complete, TaskStatus.complete,
@@ -61,7 +52,6 @@ class DownloadRepository implements IDownloadRepository {
); );
} }
@override
Future<void> deleteRecordsWithIds(List<String> ids) { Future<void> deleteRecordsWithIds(List<String> ids) {
return FileDownloader().database.deleteRecordsWithIds(ids); return FileDownloader().database.deleteRecordsWithIds(ids);
} }
@@ -1,7 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/network.interface.dart';
import 'package:network_info_plus/network_info_plus.dart'; import 'package:network_info_plus/network_info_plus.dart';
final networkRepositoryProvider = Provider((_) { final networkRepositoryProvider = Provider((_) {
@@ -10,12 +9,11 @@ final networkRepositoryProvider = Provider((_) {
return NetworkRepository(networkInfo); return NetworkRepository(networkInfo);
}); });
class NetworkRepository implements INetworkRepository { class NetworkRepository {
final NetworkInfo _networkInfo; final NetworkInfo _networkInfo;
NetworkRepository(this._networkInfo); NetworkRepository(this._networkInfo);
@override
Future<String?> getWifiName() { Future<String?> getWifiName() {
if (Platform.isAndroid) { if (Platform.isAndroid) {
// remove quote around the return value on Android // remove quote around the return value on Android
@@ -30,7 +28,6 @@ class NetworkRepository implements INetworkRepository {
return _networkInfo.getWifiName(); return _networkInfo.getWifiName();
} }
@override
Future<String?> getWifiIp() { Future<String?> getWifiIp() {
return _networkInfo.getWifiIP(); return _networkInfo.getWifiIP();
} }
@@ -2,7 +2,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
as entity; as entity;
import 'package:immich_mobile/interfaces/partner.interface.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/repositories/database.repository.dart'; import 'package:immich_mobile/repositories/database.repository.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
@@ -11,11 +10,9 @@ final partnerRepositoryProvider = Provider(
(ref) => PartnerRepository(ref.watch(dbProvider)), (ref) => PartnerRepository(ref.watch(dbProvider)),
); );
class PartnerRepository extends DatabaseRepository class PartnerRepository extends DatabaseRepository {
implements IPartnerRepository {
PartnerRepository(super.db); PartnerRepository(super.db);
@override
Future<List<UserDto>> getSharedBy() async { Future<List<UserDto>> getSharedBy() async {
return (await db.users return (await db.users
.filter() .filter()
@@ -26,7 +23,6 @@ class PartnerRepository extends DatabaseRepository
.toList(); .toList();
} }
@override
Future<List<UserDto>> getSharedWith() async { Future<List<UserDto>> getSharedWith() async {
return (await db.users return (await db.users
.filter() .filter()
@@ -37,13 +33,11 @@ class PartnerRepository extends DatabaseRepository
.toList(); .toList();
} }
@override
Stream<List<UserDto>> watchSharedBy() { Stream<List<UserDto>> watchSharedBy() {
return (db.users.filter().isPartnerSharedByEqualTo(true).sortById().watch()) return (db.users.filter().isPartnerSharedByEqualTo(true).sortById().watch())
.map((users) => users.map((u) => u.toDto()).toList()); .map((users) => users.map((u) => u.toDto()).toList());
} }
@override
Stream<List<UserDto>> watchSharedWith() { Stream<List<UserDto>> watchSharedWith() {
return (db.users return (db.users
.filter() .filter()
@@ -1,24 +1,26 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/api.repository.dart'; import 'package:immich_mobile/repositories/api.repository.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
enum Direction {
sharedWithMe,
sharedByMe,
}
final partnerApiRepositoryProvider = Provider( final partnerApiRepositoryProvider = Provider(
(ref) => PartnerApiRepository( (ref) => PartnerApiRepository(
ref.watch(apiServiceProvider).partnersApi, ref.watch(apiServiceProvider).partnersApi,
), ),
); );
class PartnerApiRepository extends ApiRepository class PartnerApiRepository extends ApiRepository {
implements IPartnerApiRepository {
final PartnersApi _api; final PartnersApi _api;
PartnerApiRepository(this._api); PartnerApiRepository(this._api);
@override
Future<List<UserDto>> getAll(Direction direction) async { Future<List<UserDto>> getAll(Direction direction) async {
final response = await checkNull( final response = await checkNull(
_api.getPartners( _api.getPartners(
@@ -30,16 +32,13 @@ class PartnerApiRepository extends ApiRepository
return response.map(UserConverter.fromPartnerDto).toList(); return response.map(UserConverter.fromPartnerDto).toList();
} }
@override
Future<UserDto> create(String id) async { Future<UserDto> create(String id) async {
final dto = await checkNull(_api.createPartner(id)); final dto = await checkNull(_api.createPartner(id));
return UserConverter.fromPartnerDto(dto); return UserConverter.fromPartnerDto(dto);
} }
@override
Future<void> delete(String id) => _api.removePartner(id); Future<void> delete(String id) => _api.removePartner(id);
@override
Future<UserDto> update(String id, {required bool inTimeline}) async { Future<UserDto> update(String id, {required bool inTimeline}) async {
final dto = await checkNull( final dto = await checkNull(
_api.updatePartner( _api.updatePartner(
@@ -1,5 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart'; import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/api.repository.dart'; import 'package:immich_mobile/repositories/api.repository.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
@@ -8,19 +8,16 @@ final personApiRepositoryProvider = Provider(
(ref) => PersonApiRepository(ref.watch(apiServiceProvider).peopleApi), (ref) => PersonApiRepository(ref.watch(apiServiceProvider).peopleApi),
); );
class PersonApiRepository extends ApiRepository class PersonApiRepository extends ApiRepository {
implements IPersonApiRepository {
final PeopleApi _api; final PeopleApi _api;
PersonApiRepository(this._api); PersonApiRepository(this._api);
@override
Future<List<Person>> getAll() async { Future<List<Person>> getAll() async {
final dto = await checkNull(_api.getAllPeople()); final dto = await checkNull(_api.getAllPeople());
return dto.people.map(_toPerson).toList(); return dto.people.map(_toPerson).toList();
} }
@override
Future<Person> update(String id, {String? name}) async { Future<Person> update(String id, {String? name}) async {
final dto = await checkNull( final dto = await checkNull(
_api.updatePerson(id, PersonUpdateDto(name: name)), _api.updatePerson(id, PersonUpdateDto(name: name)),
@@ -1,26 +1,22 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/secure_storage.interface.dart';
final secureStorageRepositoryProvider = final secureStorageRepositoryProvider =
Provider((ref) => SecureStorageRepository(const FlutterSecureStorage())); Provider((ref) => SecureStorageRepository(const FlutterSecureStorage()));
class SecureStorageRepository implements ISecureStorageRepository { class SecureStorageRepository {
final FlutterSecureStorage _secureStorage; final FlutterSecureStorage _secureStorage;
SecureStorageRepository(this._secureStorage); SecureStorageRepository(this._secureStorage);
@override
Future<String?> read(String key) { Future<String?> read(String key) {
return _secureStorage.read(key: key); return _secureStorage.read(key: key);
} }
@override
Future<void> write(String key, String value) { Future<void> write(String key, String value) {
return _secureStorage.write(key: key, value: value); return _secureStorage.write(key: key, value: value);
} }
@override
Future<void> delete(String key) { Future<void> delete(String key) {
return _secureStorage.delete(key: key); return _secureStorage.delete(key: key);
} }
@@ -1,5 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/sessions_api.interface.dart';
import 'package:immich_mobile/models/sessions/session_create_response.model.dart'; import 'package:immich_mobile/models/sessions/session_create_response.model.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/api.repository.dart'; import 'package:immich_mobile/repositories/api.repository.dart';
@@ -11,13 +10,11 @@ final sessionsAPIRepositoryProvider = Provider(
), ),
); );
class SessionsAPIRepository extends ApiRepository class SessionsAPIRepository extends ApiRepository {
implements ISessionAPIRepository {
final SessionsApi _api; final SessionsApi _api;
SessionsAPIRepository(this._api); SessionsAPIRepository(this._api);
@override
Future<SessionCreateResponse> createSession( Future<SessionCreateResponse> createSession(
String deviceType, String deviceType,
String deviceOS, { String deviceOS, {
@@ -1,7 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/share_handler.interface.dart';
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart'; import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
import 'package:share_handler/share_handler.dart'; import 'package:share_handler/share_handler.dart';
@@ -9,13 +8,11 @@ final shareHandlerRepositoryProvider = Provider(
(ref) => ShareHandlerRepository(), (ref) => ShareHandlerRepository(),
); );
class ShareHandlerRepository implements IShareHandlerRepository { class ShareHandlerRepository {
ShareHandlerRepository(); ShareHandlerRepository();
@override
void Function(List<ShareIntentAttachment> attachments)? onSharedMedia; void Function(List<ShareIntentAttachment> attachments)? onSharedMedia;
@override
Future<void> init() async { Future<void> init() async {
final handler = ShareHandlerPlatform.instance; final handler = ShareHandlerPlatform.instance;
final media = await handler.getInitialSharedMedia(); final media = await handler.getInitialSharedMedia();
@@ -3,7 +3,6 @@ import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/interfaces/timeline.interface.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/repositories/database.repository.dart'; import 'package:immich_mobile/repositories/database.repository.dart';
import 'package:immich_mobile/utils/hash.dart'; import 'package:immich_mobile/utils/hash.dart';
@@ -13,11 +12,9 @@ import 'package:isar/isar.dart';
final timelineRepositoryProvider = final timelineRepositoryProvider =
Provider((ref) => TimelineRepository(ref.watch(dbProvider))); Provider((ref) => TimelineRepository(ref.watch(dbProvider)));
class TimelineRepository extends DatabaseRepository class TimelineRepository extends DatabaseRepository {
implements ITimelineRepository {
TimelineRepository(super.db); TimelineRepository(super.db);
@override
Future<List<String>> getTimelineUserIds(String id) { Future<List<String>> getTimelineUserIds(String id) {
return db.users return db.users
.filter() .filter()
@@ -28,7 +25,6 @@ class TimelineRepository extends DatabaseRepository
.findAll(); .findAll();
} }
@override
Stream<List<String>> watchTimelineUsers(String id) { Stream<List<String>> watchTimelineUsers(String id) {
return db.users return db.users
.filter() .filter()
@@ -39,7 +35,6 @@ class TimelineRepository extends DatabaseRepository
.watch(); .watch();
} }
@override
Stream<RenderList> watchArchiveTimeline(String userId) { Stream<RenderList> watchArchiveTimeline(String userId) {
final query = db.assets final query = db.assets
.where() .where()
@@ -52,7 +47,6 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(query, GroupAssetsBy.none); return _watchRenderList(query, GroupAssetsBy.none);
} }
@override
Stream<RenderList> watchFavoriteTimeline(String userId) { Stream<RenderList> watchFavoriteTimeline(String userId) {
final query = db.assets final query = db.assets
.where() .where()
@@ -67,7 +61,6 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(query, GroupAssetsBy.none); return _watchRenderList(query, GroupAssetsBy.none);
} }
@override
Stream<RenderList> watchAlbumTimeline( Stream<RenderList> watchAlbumTimeline(
Album album, Album album,
GroupAssetsBy groupAssetByOption, GroupAssetsBy groupAssetByOption,
@@ -86,7 +79,6 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(withSortedOption, groupAssetByOption); return _watchRenderList(withSortedOption, groupAssetByOption);
} }
@override
Stream<RenderList> watchTrashTimeline(String userId) { Stream<RenderList> watchTrashTimeline(String userId) {
final query = db.assets final query = db.assets
.filter() .filter()
@@ -97,7 +89,6 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(query, GroupAssetsBy.none); return _watchRenderList(query, GroupAssetsBy.none);
} }
@override
Stream<RenderList> watchAllVideosTimeline(String userId) { Stream<RenderList> watchAllVideosTimeline(String userId) {
final query = db.assets final query = db.assets
.where() .where()
@@ -111,7 +102,6 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(query, GroupAssetsBy.none); return _watchRenderList(query, GroupAssetsBy.none);
} }
@override
Stream<RenderList> watchHomeTimeline( Stream<RenderList> watchHomeTimeline(
String userId, String userId,
GroupAssetsBy groupAssetByOption, GroupAssetsBy groupAssetByOption,
@@ -128,7 +118,6 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(query, groupAssetByOption); return _watchRenderList(query, groupAssetByOption);
} }
@override
Stream<RenderList> watchMultiUsersTimeline( Stream<RenderList> watchMultiUsersTimeline(
List<String> userIds, List<String> userIds,
GroupAssetsBy groupAssetByOption, GroupAssetsBy groupAssetByOption,
@@ -145,7 +134,6 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(query, groupAssetByOption); return _watchRenderList(query, groupAssetByOption);
} }
@override
Future<RenderList> getTimelineFromAssets( Future<RenderList> getTimelineFromAssets(
List<Asset> assets, List<Asset> assets,
GroupAssetsBy getGroupByOption, GroupAssetsBy getGroupByOption,
@@ -153,7 +141,6 @@ class TimelineRepository extends DatabaseRepository
return RenderList.fromAssets(assets, getGroupByOption); return RenderList.fromAssets(assets, getGroupByOption);
} }
@override
Stream<RenderList> watchAssetSelectionTimeline(String userId) { Stream<RenderList> watchAssetSelectionTimeline(String userId) {
final query = db.assets final query = db.assets
.where() .where()
@@ -168,7 +155,6 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(query, GroupAssetsBy.none); return _watchRenderList(query, GroupAssetsBy.none);
} }
@override
Stream<RenderList> watchLockedTimeline( Stream<RenderList> watchLockedTimeline(
String userId, String userId,
GroupAssetsBy getGroupByOption, GroupAssetsBy getGroupByOption,
+2 -2
View File
@@ -1,10 +1,10 @@
import 'package:immich_mobile/interfaces/activity_api.interface.dart';
import 'package:immich_mobile/mixins/error_logger.mixin.dart'; import 'package:immich_mobile/mixins/error_logger.mixin.dart';
import 'package:immich_mobile/models/activities/activity.model.dart'; import 'package:immich_mobile/models/activities/activity.model.dart';
import 'package:immich_mobile/repositories/activity_api.repository.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
class ActivityService with ErrorLoggerMixin { class ActivityService with ErrorLoggerMixin {
final IActivityApiRepository _activityApiRepository; final ActivityApiRepository _activityApiRepository;
@override @override
final Logger logger = Logger("ActivityService"); final Logger logger = Logger("ActivityService");
+2 -4
View File
@@ -14,8 +14,6 @@ import 'package:immich_mobile/entities/backup_album.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
as entity; as entity;
import 'package:immich_mobile/interfaces/album.interface.dart'; import 'package:immich_mobile/interfaces/album.interface.dart';
import 'package:immich_mobile/interfaces/album_api.interface.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/backup_album.interface.dart'; import 'package:immich_mobile/interfaces/backup_album.interface.dart';
import 'package:immich_mobile/models/albums/album_add_asset_response.model.dart'; import 'package:immich_mobile/models/albums/album_add_asset_response.model.dart';
@@ -51,8 +49,8 @@ class AlbumService {
final IAlbumRepository _albumRepository; final IAlbumRepository _albumRepository;
final IAssetRepository _assetRepository; final IAssetRepository _assetRepository;
final IBackupAlbumRepository _backupAlbumRepository; final IBackupAlbumRepository _backupAlbumRepository;
final IAlbumMediaRepository _albumMediaRepository; final AlbumMediaRepository _albumMediaRepository;
final IAlbumApiRepository _albumApiRepository; final AlbumApiRepository _albumApiRepository;
final Logger _log = Logger('AlbumService'); final Logger _log = Logger('AlbumService');
Completer<bool> _localCompleter = Completer()..complete(false); Completer<bool> _localCompleter = Completer()..complete(false);
Completer<bool> _remoteCompleter = Completer()..complete(false); Completer<bool> _remoteCompleter = Completer()..complete(false);
+6 -4
View File
@@ -5,11 +5,11 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/asset_api.interface.dart'; import 'package:immich_mobile/interfaces/asset_api.interface.dart';
import 'package:immich_mobile/interfaces/asset_media.interface.dart'; import 'package:immich_mobile/interfaces/asset_media.interface.dart';
@@ -53,7 +53,7 @@ class AssetService {
final IAssetApiRepository _assetApiRepository; final IAssetApiRepository _assetApiRepository;
final IAssetRepository _assetRepository; final IAssetRepository _assetRepository;
final IExifInfoRepository _exifInfoRepository; final IExifInfoRepository _exifInfoRepository;
final IUserRepository _userRepository; final IsarUserRepository _isarUserRepository;
final IETagRepository _etagRepository; final IETagRepository _etagRepository;
final IBackupAlbumRepository _backupRepository; final IBackupAlbumRepository _backupRepository;
final ApiService _apiService; final ApiService _apiService;
@@ -68,7 +68,7 @@ class AssetService {
this._assetApiRepository, this._assetApiRepository,
this._assetRepository, this._assetRepository,
this._exifInfoRepository, this._exifInfoRepository,
this._userRepository, this._isarUserRepository,
this._etagRepository, this._etagRepository,
this._backupRepository, this._backupRepository,
this._apiService, this._apiService,
@@ -85,7 +85,9 @@ class AssetService {
final syncedUserIds = await _etagRepository.getAllIds(); final syncedUserIds = await _etagRepository.getAllIds();
final List<UserDto> syncedUsers = syncedUserIds.isEmpty final List<UserDto> syncedUsers = syncedUserIds.isEmpty
? [] ? []
: (await _userRepository.getByUserIds(syncedUserIds)).nonNulls.toList(); : (await _isarUserRepository.getByUserIds(syncedUserIds))
.nonNulls
.toList();
final Stopwatch sw = Stopwatch()..start(); final Stopwatch sw = Stopwatch()..start();
final bool changes = await _syncService.syncRemoteAssetsToDb( final bool changes = await _syncService.syncRemoteAssetsToDb(
users: syncedUsers, users: syncedUsers,
+1 -2
View File
@@ -11,7 +11,6 @@ import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/asset_media.interface.dart'; import 'package:immich_mobile/interfaces/asset_media.interface.dart';
import 'package:immich_mobile/interfaces/file_media.interface.dart'; import 'package:immich_mobile/interfaces/file_media.interface.dart';
@@ -52,7 +51,7 @@ class BackupService {
final Logger _log = Logger("BackupService"); final Logger _log = Logger("BackupService");
final AppSettingsService _appSetting; final AppSettingsService _appSetting;
final AlbumService _albumService; final AlbumService _albumService;
final IAlbumMediaRepository _albumMediaRepository; final AlbumMediaRepository _albumMediaRepository;
final IFileMediaRepository _fileMediaRepository; final IFileMediaRepository _fileMediaRepository;
final IAssetRepository _assetRepository; final IAssetRepository _assetRepository;
final IAssetMediaRepository _assetMediaRepository; final IAssetMediaRepository _assetMediaRepository;
+1 -2
View File
@@ -6,7 +6,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/interfaces/download.interface.dart';
import 'package:immich_mobile/interfaces/file_media.interface.dart'; import 'package:immich_mobile/interfaces/file_media.interface.dart';
import 'package:immich_mobile/models/download/livephotos_medatada.model.dart'; import 'package:immich_mobile/models/download/livephotos_medatada.model.dart';
import 'package:immich_mobile/repositories/download.repository.dart'; import 'package:immich_mobile/repositories/download.repository.dart';
@@ -23,7 +22,7 @@ final downloadServiceProvider = Provider(
); );
class DownloadService { class DownloadService {
final IDownloadRepository _downloadRepository; final DownloadRepository _downloadRepository;
final IFileMediaRepository _fileMediaRepository; final IFileMediaRepository _fileMediaRepository;
final Logger _log = Logger("DownloadService"); final Logger _log = Logger("DownloadService");
void Function(TaskStatusUpdate)? onImageDownloadStatus; void Function(TaskStatusUpdate)? onImageDownloadStatus;
+5 -5
View File
@@ -1,24 +1,24 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/repositories/asset.repository.dart'; import 'package:immich_mobile/repositories/asset.repository.dart';
class EntityService { class EntityService {
final IAssetRepository _assetRepository; final IAssetRepository _assetRepository;
final IUserRepository _userRepository; final IsarUserRepository _isarUserRepository;
EntityService( EntityService(
this._assetRepository, this._assetRepository,
this._userRepository, this._isarUserRepository,
); );
Future<Album> fillAlbumWithDatabaseEntities(Album album) async { Future<Album> fillAlbumWithDatabaseEntities(Album album) async {
final ownerId = album.ownerId; final ownerId = album.ownerId;
if (ownerId != null) { if (ownerId != null) {
// replace owner with user from database // replace owner with user from database
final user = await _userRepository.getByUserId(ownerId); final user = await _isarUserRepository.getByUserId(ownerId);
album.owner.value = user == null ? null : User.fromDto(user); album.owner.value = user == null ? null : User.fromDto(user);
} }
final thumbnailAssetId = final thumbnailAssetId =
@@ -30,7 +30,7 @@ class EntityService {
} }
if (album.remoteUsers.isNotEmpty) { if (album.remoteUsers.isNotEmpty) {
// replace all users with users from database // replace all users with users from database
final users = await _userRepository final users = await _isarUserRepository
.getByUserIds(album.remoteUsers.map((user) => user.id).toList()); .getByUserIds(album.remoteUsers.map((user) => user.id).toList());
album.sharedUsers.clear(); album.sharedUsers.clear();
album.sharedUsers.addAll(users.nonNulls.map(User.fromDto)); album.sharedUsers.addAll(users.nonNulls.map(User.fromDto));
+1 -4
View File
@@ -1,5 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/biometric.interface.dart';
import 'package:immich_mobile/models/auth/biometric_status.model.dart'; import 'package:immich_mobile/models/auth/biometric_status.model.dart';
import 'package:immich_mobile/repositories/biometric.repository.dart'; import 'package:immich_mobile/repositories/biometric.repository.dart';
@@ -10,9 +9,7 @@ final localAuthServiceProvider = Provider(
); );
class LocalAuthService { class LocalAuthService {
// final _log = Logger("LocalAuthService"); final BiometricRepository _biometricRepository;
final IBiometricRepository _biometricRepository;
LocalAuthService(this._biometricRepository); LocalAuthService(this._biometricRepository);
+1 -2
View File
@@ -1,5 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/network.interface.dart';
import 'package:immich_mobile/repositories/network.repository.dart'; import 'package:immich_mobile/repositories/network.repository.dart';
import 'package:immich_mobile/repositories/permission.repository.dart'; import 'package:immich_mobile/repositories/permission.repository.dart';
@@ -11,7 +10,7 @@ final networkServiceProvider = Provider((ref) {
}); });
class NetworkService { class NetworkService {
final INetworkRepository _repository; final NetworkRepository _repository;
final IPermissionRepository _permissionRepository; final IPermissionRepository _permissionRepository;
NetworkService(this._repository, this._permissionRepository); NetworkService(this._repository, this._permissionRepository);
+10 -10
View File
@@ -1,8 +1,6 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/interfaces/partner.interface.dart'; import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/repositories/partner.repository.dart'; import 'package:immich_mobile/repositories/partner.repository.dart';
import 'package:immich_mobile/repositories/partner_api.repository.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart';
@@ -17,14 +15,14 @@ final partnerServiceProvider = Provider(
); );
class PartnerService { class PartnerService {
final IPartnerApiRepository _partnerApiRepository; final PartnerApiRepository _partnerApiRepository;
final IPartnerRepository _partnerRepository; final PartnerRepository _partnerRepository;
final IUserRepository _userRepository; final IsarUserRepository _isarUserRepository;
final Logger _log = Logger("PartnerService"); final Logger _log = Logger("PartnerService");
PartnerService( PartnerService(
this._partnerApiRepository, this._partnerApiRepository,
this._userRepository, this._isarUserRepository,
this._partnerRepository, this._partnerRepository,
); );
@@ -47,7 +45,8 @@ class PartnerService {
Future<bool> removePartner(UserDto partner) async { Future<bool> removePartner(UserDto partner) async {
try { try {
await _partnerApiRepository.delete(partner.id); await _partnerApiRepository.delete(partner.id);
await _userRepository.update(partner.copyWith(isPartnerSharedBy: false)); await _isarUserRepository
.update(partner.copyWith(isPartnerSharedBy: false));
} catch (e) { } catch (e) {
_log.warning("Failed to remove partner ${partner.id}", e); _log.warning("Failed to remove partner ${partner.id}", e);
return false; return false;
@@ -58,7 +57,8 @@ class PartnerService {
Future<bool> addPartner(UserDto partner) async { Future<bool> addPartner(UserDto partner) async {
try { try {
await _partnerApiRepository.create(partner.id); await _partnerApiRepository.create(partner.id);
await _userRepository.update(partner.copyWith(isPartnerSharedBy: true)); await _isarUserRepository
.update(partner.copyWith(isPartnerSharedBy: true));
return true; return true;
} catch (e) { } catch (e) {
_log.warning("Failed to add partner ${partner.id}", e); _log.warning("Failed to add partner ${partner.id}", e);
@@ -75,7 +75,7 @@ class PartnerService {
partner.id, partner.id,
inTimeline: inTimeline, inTimeline: inTimeline,
); );
await _userRepository await _isarUserRepository
.update(partner.copyWith(inTimeline: dto.inTimeline)); .update(partner.copyWith(inTimeline: dto.inTimeline));
return true; return true;
} catch (e) { } catch (e) {
+2 -2
View File
@@ -1,8 +1,8 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/asset_api.interface.dart'; import 'package:immich_mobile/interfaces/asset_api.interface.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/repositories/asset.repository.dart'; import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart';
import 'package:immich_mobile/repositories/person_api.repository.dart'; import 'package:immich_mobile/repositories/person_api.repository.dart';
@@ -20,7 +20,7 @@ PersonService personService(Ref ref) => PersonService(
class PersonService { class PersonService {
final Logger _log = Logger("PersonService"); final Logger _log = Logger("PersonService");
final IPersonApiRepository _personApiRepository; final PersonApiRepository _personApiRepository;
final IAssetApiRepository _assetApiRepository; final IAssetApiRepository _assetApiRepository;
final IAssetRepository _assetRepository; final IAssetRepository _assetRepository;
@@ -1,5 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/secure_storage.interface.dart';
import 'package:immich_mobile/repositories/secure_storage.repository.dart'; import 'package:immich_mobile/repositories/secure_storage.repository.dart';
final secureStorageServiceProvider = Provider( final secureStorageServiceProvider = Provider(
@@ -9,9 +8,7 @@ final secureStorageServiceProvider = Provider(
); );
class SecureStorageService { class SecureStorageService {
// final _log = Logger("LocalAuthService"); final SecureStorageRepository _secureStorageRepository;
final ISecureStorageRepository _secureStorageRepository;
SecureStorageService(this._secureStorageRepository); SecureStorageService(this._secureStorageRepository);
+15 -18
View File
@@ -3,23 +3,20 @@ import 'dart:io';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/domain/interfaces/user_api.interface.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/etag.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart';
import 'package:immich_mobile/extensions/collection_extensions.dart'; import 'package:immich_mobile/extensions/collection_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
import 'package:immich_mobile/interfaces/album.interface.dart'; import 'package:immich_mobile/interfaces/album.interface.dart';
import 'package:immich_mobile/interfaces/album_api.interface.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/etag.interface.dart'; import 'package:immich_mobile/interfaces/etag.interface.dart';
import 'package:immich_mobile/interfaces/local_files_manager.interface.dart'; import 'package:immich_mobile/interfaces/local_files_manager.interface.dart';
import 'package:immich_mobile/interfaces/partner.interface.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; import 'package:immich_mobile/providers/infrastructure/exif.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
@@ -63,17 +60,17 @@ final syncServiceProvider = Provider(
class SyncService { class SyncService {
final HashService _hashService; final HashService _hashService;
final EntityService _entityService; final EntityService _entityService;
final IAlbumMediaRepository _albumMediaRepository; final AlbumMediaRepository _albumMediaRepository;
final IAlbumApiRepository _albumApiRepository; final AlbumApiRepository _albumApiRepository;
final IAlbumRepository _albumRepository; final IAlbumRepository _albumRepository;
final IAssetRepository _assetRepository; final IAssetRepository _assetRepository;
final IExifInfoRepository _exifInfoRepository; final IExifInfoRepository _exifInfoRepository;
final IUserRepository _userRepository; final IsarUserRepository _isarUserRepository;
final UserService _userService; final UserService _userService;
final IPartnerRepository _partnerRepository; final PartnerRepository _partnerRepository;
final IETagRepository _eTagRepository; final IETagRepository _eTagRepository;
final IPartnerApiRepository _partnerApiRepository; final PartnerApiRepository _partnerApiRepository;
final IUserApiRepository _userApiRepository; final UserApiRepository _userApiRepository;
final AsyncMutex _lock = AsyncMutex(); final AsyncMutex _lock = AsyncMutex();
final Logger _log = Logger('SyncService'); final Logger _log = Logger('SyncService');
final AppSettingsService _appSettingsService; final AppSettingsService _appSettingsService;
@@ -88,7 +85,7 @@ class SyncService {
this._assetRepository, this._assetRepository,
this._exifInfoRepository, this._exifInfoRepository,
this._partnerRepository, this._partnerRepository,
this._userRepository, this._isarUserRepository,
this._userService, this._userService,
this._eTagRepository, this._eTagRepository,
this._appSettingsService, this._appSettingsService,
@@ -165,7 +162,7 @@ class SyncService {
/// Returns `true`if there were any changes /// Returns `true`if there were any changes
Future<bool> _syncUsersFromServer(List<UserDto> users) async { Future<bool> _syncUsersFromServer(List<UserDto> users) async {
users.sortBy((u) => u.id); users.sortBy((u) => u.id);
final dbUsers = await _userRepository.getAll(sortBy: SortUserBy.id); final dbUsers = await _isarUserRepository.getAll(sortBy: SortUserBy.id);
final List<String> toDelete = []; final List<String> toDelete = [];
final List<UserDto> toUpsert = []; final List<UserDto> toUpsert = [];
final changes = diffSortedListsSync( final changes = diffSortedListsSync(
@@ -186,9 +183,9 @@ class SyncService {
onlySecond: (UserDto b) => toDelete.add(b.id), onlySecond: (UserDto b) => toDelete.add(b.id),
); );
if (changes) { if (changes) {
await _userRepository.transaction(() async { await _isarUserRepository.transaction(() async {
await _userRepository.delete(toDelete); await _isarUserRepository.delete(toDelete);
await _userRepository.updateAll(toUpsert); await _isarUserRepository.updateAll(toUpsert);
}); });
} }
return changes; return changes;
@@ -448,7 +445,7 @@ class SyncService {
final (existingInDb, updated) = await _linkWithExistingFromDb(toAdd); final (existingInDb, updated) = await _linkWithExistingFromDb(toAdd);
await upsertAssetsWithExif(updated); await upsertAssetsWithExif(updated);
final assetsToLink = existingInDb + updated; final assetsToLink = existingInDb + updated;
final usersToLink = await _userRepository.getByUserIds(userIdsToAdd); final usersToLink = await _isarUserRepository.getByUserIds(userIdsToAdd);
album.name = dto.name; album.name = dto.name;
album.description = dto.description; album.description = dto.description;
+1 -2
View File
@@ -2,7 +2,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/interfaces/timeline.interface.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/repositories/timeline.repository.dart'; import 'package:immich_mobile/repositories/timeline.repository.dart';
@@ -18,7 +17,7 @@ final timelineServiceProvider = Provider<TimelineService>((ref) {
}); });
class TimelineService { class TimelineService {
final ITimelineRepository _timelineRepository; final TimelineRepository _timelineRepository;
final AppSettingsService _appSettingsService; final AppSettingsService _appSettingsService;
final UserService _userService; final UserService _userService;
@@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/pages/common/large_leading_tile.dart'; import 'package:immich_mobile/pages/common/large_leading_tile.dart';
import 'package:immich_mobile/providers/search/people.provider.dart'; import 'package:immich_mobile/providers/search/people.provider.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
+1 -1
View File
@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.135.1 - API version: 1.135.2
- Generator version: 7.8.0 - Generator version: 7.8.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen - Build package: org.openapitools.codegen.languages.DartClientCodegen
+1 -1
View File
@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone description: Immich - selfhosted backup media file on mobile phone
publish_to: 'none' publish_to: 'none'
version: 1.135.1+202 version: 1.135.2+203
environment: environment:
sdk: '>=3.3.0 <4.0.0' sdk: '>=3.3.0 <4.0.0'
@@ -4,9 +4,9 @@ import 'dart:async';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart'; import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart';
import 'package:immich_mobile/domain/interfaces/sync_stream.interface.dart';
import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart';
import 'package:immich_mobile/domain/services/sync_stream.service.dart'; import 'package:immich_mobile/domain/services/sync_stream.service.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import '../../fixtures/sync_stream.stub.dart'; import '../../fixtures/sync_stream.stub.dart';
@@ -30,7 +30,7 @@ class _MockCancellationWrapper extends Mock implements _CancellationWrapper {}
void main() { void main() {
late SyncStreamService sut; late SyncStreamService sut;
late ISyncStreamRepository mockSyncStreamRepo; late SyncStreamRepository mockSyncStreamRepo;
late ISyncApiRepository mockSyncApiRepo; late ISyncApiRepository mockSyncApiRepo;
late Function(List<SyncEvent>, Function()) handleEventsCallback; late Function(List<SyncEvent>, Function()) handleEventsCallback;
late _MockAbortCallbackWrapper mockAbortCallbackWrapper; late _MockAbortCallbackWrapper mockAbortCallbackWrapper;
@@ -1,11 +1,11 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/domain/interfaces/user_api.interface.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/domain/services/store.service.dart';
import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import '../../fixtures/user.stub.dart'; import '../../fixtures/user.stub.dart';
@@ -14,16 +14,16 @@ import '../service.mock.dart';
void main() { void main() {
late UserService sut; late UserService sut;
late IUserRepository mockUserRepo; late IsarUserRepository mockUserRepo;
late IUserApiRepository mockUserApiRepo; late UserApiRepository mockUserApiRepo;
late StoreService mockStoreService; late StoreService mockStoreService;
setUp(() { setUp(() {
mockUserRepo = MockUserRepository(); mockUserRepo = MockIsarUserRepository();
mockUserApiRepo = MockUserApiRepository(); mockUserApiRepo = MockUserApiRepository();
mockStoreService = MockStoreService(); mockStoreService = MockStoreService();
sut = UserService( sut = UserService(
userRepository: mockUserRepo, isarUserRepository: mockUserRepo,
userApiRepository: mockUserApiRepo, userApiRepository: mockUserApiRepo,
storeService: mockStoreService, storeService: mockStoreService,
); );
@@ -5,21 +5,21 @@ import 'package:immich_mobile/domain/interfaces/log.interface.dart';
import 'package:immich_mobile/domain/interfaces/storage.interface.dart'; import 'package:immich_mobile/domain/interfaces/storage.interface.dart';
import 'package:immich_mobile/domain/interfaces/store.interface.dart'; import 'package:immich_mobile/domain/interfaces/store.interface.dart';
import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart'; import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart';
import 'package:immich_mobile/domain/interfaces/sync_stream.interface.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart'; import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/domain/interfaces/user_api.interface.dart'; import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
class MockStoreRepository extends Mock implements IStoreRepository {} class MockStoreRepository extends Mock implements IStoreRepository {}
class MockLogRepository extends Mock implements ILogRepository {} class MockLogRepository extends Mock implements ILogRepository {}
class MockUserRepository extends Mock implements IUserRepository {} class MockIsarUserRepository extends Mock implements IsarUserRepository {}
class MockDeviceAssetRepository extends Mock class MockDeviceAssetRepository extends Mock
implements IDeviceAssetRepository {} implements IDeviceAssetRepository {}
class MockSyncStreamRepository extends Mock implements ISyncStreamRepository {} class MockSyncStreamRepository extends Mock implements SyncStreamRepository {}
class MockLocalAlbumRepository extends Mock implements ILocalAlbumRepository {} class MockLocalAlbumRepository extends Mock implements ILocalAlbumRepository {}
@@ -28,6 +28,6 @@ class MockLocalAssetRepository extends Mock implements ILocalAssetRepository {}
class MockStorageRepository extends Mock implements IStorageRepository {} class MockStorageRepository extends Mock implements IStorageRepository {}
// API Repos // API Repos
class MockUserApiRepository extends Mock implements IUserApiRepository {} class MockUserApiRepository extends Mock implements UserApiRepository {}
class MockSyncApiRepository extends Mock implements ISyncApiRepository {} class MockSyncApiRepository extends Mock implements ISyncApiRepository {}
@@ -1,6 +1,6 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart'; import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/domain/services/log.service.dart';
@@ -11,7 +11,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart';
import 'package:immich_mobile/services/sync.service.dart'; import 'package:immich_mobile/services/sync.service.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
@@ -55,7 +55,7 @@ void main() {
final MockAlbumRepository albumRepository = MockAlbumRepository(); final MockAlbumRepository albumRepository = MockAlbumRepository();
final MockAssetRepository assetRepository = MockAssetRepository(); final MockAssetRepository assetRepository = MockAssetRepository();
final MockExifInfoRepository exifInfoRepository = MockExifInfoRepository(); final MockExifInfoRepository exifInfoRepository = MockExifInfoRepository();
final MockUserRepository userRepository = MockUserRepository(); final MockIsarUserRepository userRepository = MockIsarUserRepository();
final MockETagRepository eTagRepository = MockETagRepository(); final MockETagRepository eTagRepository = MockETagRepository();
final MockAlbumMediaRepository albumMediaRepository = final MockAlbumMediaRepository albumMediaRepository =
MockAlbumMediaRepository(); MockAlbumMediaRepository();
+8 -8
View File
@@ -1,7 +1,5 @@
import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
import 'package:immich_mobile/interfaces/album.interface.dart'; import 'package:immich_mobile/interfaces/album.interface.dart';
import 'package:immich_mobile/interfaces/album_api.interface.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/asset_api.interface.dart'; import 'package:immich_mobile/interfaces/asset_api.interface.dart';
import 'package:immich_mobile/interfaces/asset_media.interface.dart'; import 'package:immich_mobile/interfaces/asset_media.interface.dart';
@@ -11,8 +9,10 @@ import 'package:immich_mobile/interfaces/backup_album.interface.dart';
import 'package:immich_mobile/interfaces/etag.interface.dart'; import 'package:immich_mobile/interfaces/etag.interface.dart';
import 'package:immich_mobile/interfaces/file_media.interface.dart'; import 'package:immich_mobile/interfaces/file_media.interface.dart';
import 'package:immich_mobile/interfaces/local_files_manager.interface.dart'; import 'package:immich_mobile/interfaces/local_files_manager.interface.dart';
import 'package:immich_mobile/interfaces/partner.interface.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart'; import 'package:immich_mobile/repositories/album_media.repository.dart';
import 'package:immich_mobile/repositories/album_api.repository.dart';
import 'package:immich_mobile/repositories/partner.repository.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
class MockAlbumRepository extends Mock implements IAlbumRepository {} class MockAlbumRepository extends Mock implements IAlbumRepository {}
@@ -25,7 +25,7 @@ class MockExifInfoRepository extends Mock implements IExifInfoRepository {}
class MockETagRepository extends Mock implements IETagRepository {} class MockETagRepository extends Mock implements IETagRepository {}
class MockAlbumMediaRepository extends Mock implements IAlbumMediaRepository {} class MockAlbumMediaRepository extends Mock implements AlbumMediaRepository {}
class MockBackupAlbumRepository extends Mock class MockBackupAlbumRepository extends Mock
implements IBackupAlbumRepository {} implements IBackupAlbumRepository {}
@@ -36,15 +36,15 @@ class MockAssetMediaRepository extends Mock implements IAssetMediaRepository {}
class MockFileMediaRepository extends Mock implements IFileMediaRepository {} class MockFileMediaRepository extends Mock implements IFileMediaRepository {}
class MockAlbumApiRepository extends Mock implements IAlbumApiRepository {} class MockAlbumApiRepository extends Mock implements AlbumApiRepository {}
class MockAuthApiRepository extends Mock implements IAuthApiRepository {} class MockAuthApiRepository extends Mock implements IAuthApiRepository {}
class MockAuthRepository extends Mock implements IAuthRepository {} class MockAuthRepository extends Mock implements IAuthRepository {}
class MockPartnerRepository extends Mock implements IPartnerRepository {} class MockPartnerRepository extends Mock implements PartnerRepository {}
class MockPartnerApiRepository extends Mock implements IPartnerApiRepository {} class MockPartnerApiRepository extends Mock implements PartnerApiRepository {}
class MockLocalFilesManagerRepository extends Mock class MockLocalFilesManagerRepository extends Mock
implements ILocalFilesManager {} implements ILocalFilesManager {}
+2 -2
View File
@@ -22,7 +22,7 @@ void main() {
late MockExifInfoRepository exifInfoRepository; late MockExifInfoRepository exifInfoRepository;
late MockETagRepository eTagRepository; late MockETagRepository eTagRepository;
late MockBackupAlbumRepository backupAlbumRepository; late MockBackupAlbumRepository backupAlbumRepository;
late MockUserRepository userRepository; late MockIsarUserRepository userRepository;
late MockAssetMediaRepository assetMediaRepository; late MockAssetMediaRepository assetMediaRepository;
late MockApiService apiService; late MockApiService apiService;
@@ -35,7 +35,7 @@ void main() {
assetRepository = MockAssetRepository(); assetRepository = MockAssetRepository();
assetApiRepository = MockAssetApiRepository(); assetApiRepository = MockAssetApiRepository();
exifInfoRepository = MockExifInfoRepository(); exifInfoRepository = MockExifInfoRepository();
userRepository = MockUserRepository(); userRepository = MockIsarUserRepository();
eTagRepository = MockETagRepository(); eTagRepository = MockETagRepository();
backupAlbumRepository = MockBackupAlbumRepository(); backupAlbumRepository = MockBackupAlbumRepository();
apiService = MockApiService(); apiService = MockApiService();
@@ -12,11 +12,11 @@ import '../repository.mocks.dart';
void main() { void main() {
late EntityService sut; late EntityService sut;
late MockAssetRepository assetRepository; late MockAssetRepository assetRepository;
late MockUserRepository userRepository; late MockIsarUserRepository userRepository;
setUp(() { setUp(() {
assetRepository = MockAssetRepository(); assetRepository = MockAssetRepository();
userRepository = MockUserRepository(); userRepository = MockIsarUserRepository();
sut = EntityService(assetRepository, userRepository); sut = EntityService(assetRepository, userRepository);
}); });
+1 -1
View File
@@ -8503,7 +8503,7 @@
"info": { "info": {
"title": "Immich", "title": "Immich",
"description": "Immich API", "description": "Immich API",
"version": "1.135.1", "version": "1.135.2",
"contact": {} "contact": {}
}, },
"tags": [], "tags": [],
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.135.1", "version": "1.135.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.135.1", "version": "1.135.2",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.135.1", "version": "1.135.2",
"description": "Auto-generated TypeScript SDK for the Immich API", "description": "Auto-generated TypeScript SDK for the Immich API",
"type": "module", "type": "module",
"main": "./build/index.js", "main": "./build/index.js",
+1 -1
View File
@@ -1,6 +1,6 @@
/** /**
* Immich * Immich
* 1.135.1 * 1.135.2
* DO NOT MODIFY - This file has been generated using oazapfts. * DO NOT MODIFY - This file has been generated using oazapfts.
* See https://www.npmjs.com/package/oazapfts * See https://www.npmjs.com/package/oazapfts
*/ */
+2 -52
View File
@@ -1,12 +1,12 @@
{ {
"name": "immich", "name": "immich",
"version": "1.135.1", "version": "1.135.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich", "name": "immich",
"version": "1.135.1", "version": "1.135.2",
"hasInstallScript": true, "hasInstallScript": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
@@ -42,7 +42,6 @@
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"i18n-iso-countries": "^7.6.0", "i18n-iso-countries": "^7.6.0",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"joi": "^17.10.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"kysely": "^0.28.0", "kysely": "^0.28.0",
"kysely-postgres-js": "^2.0.0", "kysely-postgres-js": "^2.0.0",
@@ -1349,21 +1348,6 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
"license": "BSD-3-Clause"
},
"node_modules/@hapi/topo": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
"license": "BSD-3-Clause",
"dependencies": {
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -5052,27 +5036,6 @@
"url": "https://ko-fi.com/killymxi" "url": "https://ko-fi.com/killymxi"
} }
}, },
"node_modules/@sideway/address": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
"integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==",
"license": "BSD-3-Clause",
"dependencies": {
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@sideway/formula": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
"integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==",
"license": "BSD-3-Clause"
},
"node_modules/@sideway/pinpoint": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
"license": "BSD-3-Clause"
},
"node_modules/@socket.io/component-emitter": { "node_modules/@socket.io/component-emitter": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
@@ -11333,19 +11296,6 @@
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
}, },
"node_modules/joi": {
"version": "17.13.3",
"resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz",
"integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==",
"license": "BSD-3-Clause",
"dependencies": {
"@hapi/hoek": "^9.3.0",
"@hapi/topo": "^5.1.0",
"@sideway/address": "^4.1.5",
"@sideway/formula": "^3.0.1",
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/jose": { "node_modules/jose": {
"version": "6.0.10", "version": "6.0.10",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.0.10.tgz", "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.10.tgz",
+1 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich", "name": "immich",
"version": "1.135.1", "version": "1.135.2",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
@@ -68,7 +68,6 @@
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"i18n-iso-countries": "^7.6.0", "i18n-iso-countries": "^7.6.0",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"joi": "^17.10.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"kysely": "^0.28.0", "kysely": "^0.28.0",
"kysely-postgres-js": "^2.0.0", "kysely-postgres-js": "^2.0.0",
+3 -3
View File
@@ -224,7 +224,7 @@ limit
with with
"assets" as ( "assets" as (
select select
date_trunc('MONTH', "localDateTime" at time zone 'UTC') at time zone 'UTC' as "timeBucket" date_trunc('MONTH', "localDateTime" AT TIME ZONE 'UTC') AT TIME ZONE 'UTC' as "timeBucket"
from from
"assets" "assets"
where where
@@ -232,7 +232,7 @@ with
and "assets"."visibility" in ('archive', 'timeline') and "assets"."visibility" in ('archive', 'timeline')
) )
select select
"timeBucket"::date::text as "timeBucket", ("timeBucket" AT TIME ZONE 'UTC')::date::text as "timeBucket",
count(*) as "count" count(*) as "count"
from from
"assets" "assets"
@@ -300,7 +300,7 @@ with
where where
"assets"."deletedAt" is null "assets"."deletedAt" is null
and "assets"."visibility" in ('archive', 'timeline') and "assets"."visibility" in ('archive', 'timeline')
and date_trunc('MONTH', "localDateTime" at time zone 'UTC') at time zone 'UTC' = $2 and date_trunc('MONTH', "localDateTime" AT TIME ZONE 'UTC') AT TIME ZONE 'UTC' = $2
and not exists ( and not exists (
select select
from from
+2 -1
View File
@@ -35,8 +35,9 @@ having
order by order by
"person"."isHidden" asc, "person"."isHidden" asc,
"person"."isFavorite" desc, "person"."isFavorite" desc,
NULLIF(person.name, '') asc nulls last, NULLIF(person.name, '') is null asc,
count("asset_faces"."assetId") desc, count("asset_faces"."assetId") desc,
NULLIF(person.name, '') asc nulls last,
"person"."createdAt" "person"."createdAt"
limit limit
$5 $5
+3 -3
View File
@@ -130,7 +130,7 @@ from
where where
"ownerId" = $1 "ownerId" = $1
and "updatedAt" < now() - interval '1 millisecond' and "updatedAt" < now() - interval '1 millisecond'
and "updateId" < $2 and "updateId" <= $2
and "updateId" >= $3 and "updateId" >= $3
order by order by
"updateId" asc "updateId" asc
@@ -274,7 +274,7 @@ from
where where
"assets"."ownerId" = $1 "assets"."ownerId" = $1
and "exif"."updatedAt" < now() - interval '1 millisecond' and "exif"."updatedAt" < now() - interval '1 millisecond'
and "exif"."updateId" < $2 and "exif"."updateId" <= $2
and "exif"."updateId" >= $3 and "exif"."updateId" >= $3
order by order by
"exif"."updateId" asc "exif"."updateId" asc
@@ -418,7 +418,7 @@ from
where where
"albumsId" = $1 "albumsId" = $1
and "updatedAt" < now() - interval '1 millisecond' and "updatedAt" < now() - interval '1 millisecond'
and "updateId" < $2 and "updateId" <= $2
and "updateId" >= $3 and "updateId" >= $3
order by order by
"updateId" asc "updateId" asc
+4 -9
View File
@@ -42,11 +42,6 @@ interface LivePhotoSearchOptions {
type: AssetType; type: AssetType;
} }
export enum TimeBucketSize {
DAY = 'DAY',
MONTH = 'MONTH',
}
interface AssetBuilderOptions { interface AssetBuilderOptions {
isFavorite?: boolean; isFavorite?: boolean;
isTrashed?: boolean; isTrashed?: boolean;
@@ -490,13 +485,13 @@ export class AssetRepository {
.execute(); .execute();
} }
@GenerateSql({ params: [{ size: TimeBucketSize.MONTH }] }) @GenerateSql({ params: [{}] })
async getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]> { async getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]> {
return this.db return this.db
.with('assets', (qb) => .with('assets', (qb) =>
qb qb
.selectFrom('assets') .selectFrom('assets')
.select(truncatedDate<Date>(TimeBucketSize.MONTH).as('timeBucket')) .select(truncatedDate<Date>().as('timeBucket'))
.$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED)) .$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED))
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null) .where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
.$if(options.visibility === undefined, withDefaultVisibility) .$if(options.visibility === undefined, withDefaultVisibility)
@@ -525,7 +520,7 @@ export class AssetRepository {
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)), .$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)),
) )
.selectFrom('assets') .selectFrom('assets')
.select(sql<string>`"timeBucket"::date::text`.as('timeBucket')) .select(sql<string>`("timeBucket" AT TIME ZONE 'UTC')::date::text`.as('timeBucket'))
.select((eb) => eb.fn.countAll<number>().as('count')) .select((eb) => eb.fn.countAll<number>().as('count'))
.groupBy('timeBucket') .groupBy('timeBucket')
.orderBy('timeBucket', options.order ?? 'desc') .orderBy('timeBucket', options.order ?? 'desc')
@@ -576,7 +571,7 @@ export class AssetRepository {
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null) .where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
.$if(options.visibility == undefined, withDefaultVisibility) .$if(options.visibility == undefined, withDefaultVisibility)
.$if(!!options.visibility, (qb) => qb.where('assets.visibility', '=', options.visibility!)) .$if(!!options.visibility, (qb) => qb.where('assets.visibility', '=', options.visibility!))
.where(truncatedDate(TimeBucketSize.MONTH), '=', timeBucket.replace(/^[+-]/, '')) .where(truncatedDate(), '=', timeBucket.replace(/^[+-]/, ''))
.$if(!!options.albumId, (qb) => .$if(!!options.albumId, (qb) =>
qb.where((eb) => qb.where((eb) =>
eb.exists( eb.exists(
+2 -1
View File
@@ -180,8 +180,9 @@ export class PersonRepository {
) )
.$if(!options?.closestFaceAssetId, (qb) => .$if(!options?.closestFaceAssetId, (qb) =>
qb qb
.orderBy(sql`NULLIF(person.name, '')`, (om) => om.asc().nullsLast()) .orderBy(sql`NULLIF(person.name, '') is null`, 'asc')
.orderBy((eb) => eb.fn.count('asset_faces.assetId'), 'desc') .orderBy((eb) => eb.fn.count('asset_faces.assetId'), 'desc')
.orderBy(sql`NULLIF(person.name, '')`, (om) => om.asc().nullsLast())
.orderBy('person.createdAt'), .orderBy('person.createdAt'),
) )
.$if(!options?.withHidden, (qb) => qb.where('person.isHidden', '=', false)) .$if(!options?.withHidden, (qb) => qb.where('person.isHidden', '=', false))
+3 -3
View File
@@ -111,7 +111,7 @@ export class SyncRepository {
.select(columns.syncAsset) .select(columns.syncAsset)
.where('ownerId', '=', partnerId) .where('ownerId', '=', partnerId)
.where('updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'")) .where('updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.where('updateId', '<', beforeUpdateId) .where('updateId', '<=', beforeUpdateId)
.$if(!!afterUpdateId, (eb) => eb.where('updateId', '>=', afterUpdateId!)) .$if(!!afterUpdateId, (eb) => eb.where('updateId', '>=', afterUpdateId!))
.orderBy('updateId', 'asc') .orderBy('updateId', 'asc')
.stream(); .stream();
@@ -169,7 +169,7 @@ export class SyncRepository {
.innerJoin('assets', 'assets.id', 'exif.assetId') .innerJoin('assets', 'assets.id', 'exif.assetId')
.where('assets.ownerId', '=', partnerId) .where('assets.ownerId', '=', partnerId)
.where('exif.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'")) .where('exif.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.where('exif.updateId', '<', beforeUpdateId) .where('exif.updateId', '<=', beforeUpdateId)
.$if(!!afterUpdateId, (eb) => eb.where('exif.updateId', '>=', afterUpdateId!)) .$if(!!afterUpdateId, (eb) => eb.where('exif.updateId', '>=', afterUpdateId!))
.orderBy('exif.updateId', 'asc') .orderBy('exif.updateId', 'asc')
.stream(); .stream();
@@ -273,7 +273,7 @@ export class SyncRepository {
.select(columns.syncAlbumUser) .select(columns.syncAlbumUser)
.where('albumsId', '=', albumId) .where('albumsId', '=', albumId)
.where('updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'")) .where('updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.where('updateId', '<', beforeUpdateId) .where('updateId', '<=', beforeUpdateId)
.$if(!!afterUpdateId, (eb) => eb.where('updateId', '>=', afterUpdateId!)) .$if(!!afterUpdateId, (eb) => eb.where('updateId', '>=', afterUpdateId!))
.orderBy('updateId', 'asc') .orderBy('updateId', 'asc')
.stream(); .stream();
@@ -1,15 +1,21 @@
import { Kysely, sql } from 'kysely'; import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> { export async function up(qb: Kysely<any>): Promise<void> {
const { rows } = await sql<{ db: string }>`SELECT current_database() as db;`.execute(db); type Conf = { db: string; guc: string[] };
const databaseName = rows[0].db; const res = await sql<Conf>`select current_database() db, to_json(setconfig) guc from pg_db_role_setting`.execute(qb);
await sql.raw(`ALTER DATABASE "${databaseName}" RESET vchordrq.prewarm_dim;`).execute(db); if (res.rows.length === 0) {
return;
} }
export async function down(db: Kysely<any>): Promise<void> { const { db, guc } = res.rows[0];
const { rows } = await sql<{ db: string }>`SELECT current_database() as db;`.execute(db); await sql.raw(`alter database "${db}" reset all;`).execute(qb);
const databaseName = rows[0].db; for (const parameter of guc) {
await sql const [key, value] = parameter.split('=');
.raw(`ALTER DATABASE "${databaseName}" SET vchordrq.prewarm_dim = '512,640,768,1024,1152,1536';`) if (key === 'vchordrq.prewarm_dim') {
.execute(db); continue;
} }
await sql.raw(`alter database "${db}" set ${key} to ${value};`).execute(qb);
}
}
export async function down(): Promise<void> {}
+19 -16
View File
@@ -38,11 +38,11 @@ const mapSyncAssetV1 = ({ checksum, thumbhash, ...data }: AssetLike): SyncAssetV
thumbhash: thumbhash ? hexOrBufferToBase64(thumbhash) : null, thumbhash: thumbhash ? hexOrBufferToBase64(thumbhash) : null,
}); });
const isEntityBackfillComplete = (entity: { createId: string }, checkpoint: SyncAck | undefined): boolean => const isEntityBackfillComplete = (createId: string, checkpoint: SyncAck | undefined): boolean =>
entity.createId === checkpoint?.updateId && checkpoint.extraId === COMPLETE_ID; createId === checkpoint?.updateId && checkpoint.extraId === COMPLETE_ID;
const getStartId = (entity: { createId: string }, checkpoint: SyncAck | undefined): string | undefined => const getStartId = (createId: string, checkpoint: SyncAck | undefined): string | undefined =>
checkpoint?.updateId === entity.createId ? checkpoint?.extraId : undefined; createId === checkpoint?.updateId ? checkpoint?.extraId : undefined;
const send = <T extends keyof SyncItem, D extends SyncItem[T]>(response: Writable, item: SerializeOptions<T, D>) => { const send = <T extends keyof SyncItem, D extends SyncItem[T]>(response: Writable, item: SerializeOptions<T, D>) => {
response.write(serialize(item)); response.write(serialize(item));
@@ -235,22 +235,23 @@ export class SyncService extends BaseService {
const endId = upsertCheckpoint.updateId; const endId = upsertCheckpoint.updateId;
for (const partner of partners) { for (const partner of partners) {
if (isEntityBackfillComplete(partner, backfillCheckpoint)) { const createId = partner.createId;
if (isEntityBackfillComplete(createId, backfillCheckpoint)) {
continue; continue;
} }
const startId = getStartId(partner, backfillCheckpoint); const startId = getStartId(createId, backfillCheckpoint);
const backfill = this.syncRepository.getPartnerAssetsBackfill(partner.sharedById, startId, endId); const backfill = this.syncRepository.getPartnerAssetsBackfill(partner.sharedById, startId, endId);
for await (const { updateId, ...data } of backfill) { for await (const { updateId, ...data } of backfill) {
send(response, { send(response, {
type: backfillType, type: backfillType,
ids: [updateId], ids: [createId, updateId],
data: mapSyncAssetV1(data), data: mapSyncAssetV1(data),
}); });
} }
sendEntityBackfillCompleteAck(response, backfillType, partner.sharedById); sendEntityBackfillCompleteAck(response, backfillType, createId);
} }
} else if (partners.length > 0) { } else if (partners.length > 0) {
await this.upsertBackfillCheckpoint({ await this.upsertBackfillCheckpoint({
@@ -291,18 +292,19 @@ export class SyncService extends BaseService {
const endId = upsertCheckpoint.updateId; const endId = upsertCheckpoint.updateId;
for (const partner of partners) { for (const partner of partners) {
if (isEntityBackfillComplete(partner, backfillCheckpoint)) { const createId = partner.createId;
if (isEntityBackfillComplete(createId, backfillCheckpoint)) {
continue; continue;
} }
const startId = getStartId(partner, backfillCheckpoint); const startId = getStartId(createId, backfillCheckpoint);
const backfill = this.syncRepository.getPartnerAssetExifsBackfill(partner.sharedById, startId, endId); const backfill = this.syncRepository.getPartnerAssetExifsBackfill(partner.sharedById, startId, endId);
for await (const { updateId, ...data } of backfill) { for await (const { updateId, ...data } of backfill) {
send(response, { type: backfillType, ids: [updateId], data }); send(response, { type: backfillType, ids: [partner.createId, updateId], data });
} }
sendEntityBackfillCompleteAck(response, backfillType, partner.sharedById); sendEntityBackfillCompleteAck(response, backfillType, partner.createId);
} }
} else if (partners.length > 0) { } else if (partners.length > 0) {
await this.upsertBackfillCheckpoint({ await this.upsertBackfillCheckpoint({
@@ -350,18 +352,19 @@ export class SyncService extends BaseService {
const endId = upsertCheckpoint.updateId; const endId = upsertCheckpoint.updateId;
for (const album of albums) { for (const album of albums) {
if (isEntityBackfillComplete(album, backfillCheckpoint)) { const createId = album.createId;
if (isEntityBackfillComplete(createId, backfillCheckpoint)) {
continue; continue;
} }
const startId = getStartId(album, backfillCheckpoint); const startId = getStartId(createId, backfillCheckpoint);
const backfill = this.syncRepository.getAlbumUsersBackfill(album.id, startId, endId); const backfill = this.syncRepository.getAlbumUsersBackfill(album.id, startId, endId);
for await (const { updateId, ...data } of backfill) { for await (const { updateId, ...data } of backfill) {
send(response, { type: backfillType, ids: [updateId], data }); send(response, { type: backfillType, ids: [createId, updateId], data });
} }
sendEntityBackfillCompleteAck(response, backfillType, album.id); sendEntityBackfillCompleteAck(response, backfillType, createId);
} }
} else if (albums.length > 0) { } else if (albums.length > 0) {
await this.upsertBackfillCheckpoint({ await this.upsertBackfillCheckpoint({
+2 -3
View File
@@ -18,7 +18,6 @@ import postgres, { Notice } from 'postgres';
import { columns, Exif, Person } from 'src/database'; import { columns, Exif, Person } from 'src/database';
import { DB } from 'src/db'; import { DB } from 'src/db';
import { AssetFileType, AssetVisibility, DatabaseExtension, DatabaseSslMode } from 'src/enum'; import { AssetFileType, AssetVisibility, DatabaseExtension, DatabaseSslMode } from 'src/enum';
import { TimeBucketSize } from 'src/repositories/asset.repository';
import { AssetSearchBuilderOptions } from 'src/repositories/search.repository'; import { AssetSearchBuilderOptions } from 'src/repositories/search.repository';
import { DatabaseConnectionParams, VectorExtension } from 'src/types'; import { DatabaseConnectionParams, VectorExtension } from 'src/types';
@@ -279,8 +278,8 @@ export function withTags(eb: ExpressionBuilder<DB, 'assets'>) {
).as('tags'); ).as('tags');
} }
export function truncatedDate<O>(size: TimeBucketSize) { export function truncatedDate<O>() {
return sql<O>`date_trunc(${sql.lit(size)}, "localDateTime" at time zone 'UTC') at time zone 'UTC'`; return sql<O>`date_trunc(${sql.lit('MONTH')}, "localDateTime" AT TIME ZONE 'UTC') AT TIME ZONE 'UTC'`;
} }
export function withTagId<O>(qb: SelectQueryBuilder<DB, 'assets', O>, tagId: string) { export function withTagId<O>(qb: SelectQueryBuilder<DB, 'assets', O>, tagId: string) {
+3 -3
View File
@@ -7,12 +7,12 @@ import { getKyselyConfig } from 'src/utils/database';
import { GenericContainer, Wait } from 'testcontainers'; import { GenericContainer, Wait } from 'testcontainers';
const globalSetup = async () => { const globalSetup = async () => {
const postgresContainer = await new GenericContainer('ghcr.io/immich-app/postgres:14-vectorchord0.4.1') const postgresContainer = await new GenericContainer('ghcr.io/immich-app/postgres:14-vectorchord0.4.3')
.withExposedPorts(5432) .withExposedPorts(5432)
.withEnvironment({ .withEnvironment({
POSTGRES_PASSWORD: 'postgres', POSTGRES_PASSWORD: 'postgres',
POSTGRES_USER: 'postgres', POSTGRES_USER: 'postgres',
POSTGRES_DB: 'immich', POSTGRES_DB: 'dbname',
}) })
.withCommand([ .withCommand([
'postgres', 'postgres',
@@ -35,7 +35,7 @@ const globalSetup = async () => {
.start(); .start();
const postgresPort = postgresContainer.getMappedPort(5432); const postgresPort = postgresContainer.getMappedPort(5432);
const postgresUrl = `postgres://postgres:postgres@localhost:${postgresPort}/immich`; const postgresUrl = `postgres://postgres:postgres@localhost:${postgresPort}/dbname`;
process.env.IMMICH_TEST_POSTGRES_URL = postgresUrl; process.env.IMMICH_TEST_POSTGRES_URL = postgresUrl;
@@ -19,7 +19,7 @@ beforeAll(async () => {
defaultDatabase = await getKyselyDB(); defaultDatabase = await getKyselyDB();
}); });
describe.concurrent(SyncRequestType.PartnerAssetExifsV1, () => { describe(SyncRequestType.PartnerAssetExifsV1, () => {
it('should detect and sync the first partner asset exif', async () => { it('should detect and sync the first partner asset exif', async () => {
const { auth, sut, getRepository, testSync } = await setup(); const { auth, sut, getRepository, testSync } = await setup();
@@ -78,7 +78,6 @@ describe.concurrent(SyncRequestType.PartnerAssetExifsV1, () => {
await sut.setAcks(auth, { acks }); await sut.setAcks(auth, { acks });
const ackSyncResponse = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]); const ackSyncResponse = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]);
expect(ackSyncResponse).toHaveLength(0); expect(ackSyncResponse).toHaveLength(0);
}); });
@@ -196,6 +195,79 @@ describe.concurrent(SyncRequestType.PartnerAssetExifsV1, () => {
expect(finalAcks).toEqual([]); expect(finalAcks).toEqual([]);
}); });
it('should handle partners with users ids lower than a uuidv7', async () => {
const { auth, sut, getRepository, testSync } = await setup();
const userRepo = getRepository('user');
const user2 = mediumFactory.userInsert({ id: '00d4c0af-7695-4cf2-85e6-415eeaf449cb' });
const user3 = mediumFactory.userInsert({ id: '00e4c0af-7695-4cf2-85e6-415eeaf449cb' });
await userRepo.create(user2);
await userRepo.create(user3);
const assetRepo = getRepository('asset');
const assetUser3 = mediumFactory.assetInsert({ ownerId: user3.id });
await assetRepo.create(assetUser3);
await assetRepo.upsertExif({ assetId: assetUser3.id, make: 'assetUser3' });
await wait(2);
const assetUser2 = mediumFactory.assetInsert({ ownerId: user2.id });
await assetRepo.create(assetUser2);
await assetRepo.upsertExif({ assetId: assetUser2.id, make: 'assetUser2' });
const partnerRepo = getRepository('partner');
await partnerRepo.create({ sharedById: user2.id, sharedWithId: auth.user.id });
const response = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]);
expect(response).toHaveLength(1);
expect(response).toEqual(
expect.arrayContaining([
{
ack: expect.any(String),
data: expect.objectContaining({
assetId: assetUser2.id,
}),
type: SyncEntityType.PartnerAssetExifV1,
},
]),
);
const acks = response.map(({ ack }) => ack);
await sut.setAcks(auth, { acks });
// This checks that our ack upsert is correct
const ackUpsertResponse = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]);
expect(ackUpsertResponse).toEqual([]);
await partnerRepo.create({ sharedById: user3.id, sharedWithId: auth.user.id });
const syncAckResponse = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]);
expect(syncAckResponse).toHaveLength(2);
expect(syncAckResponse).toEqual(
expect.arrayContaining([
{
ack: expect.stringMatching(new RegExp(`${SyncEntityType.PartnerAssetExifBackfillV1}\\|.+?\\|.+`)),
data: expect.objectContaining({
assetId: assetUser3.id,
}),
type: SyncEntityType.PartnerAssetExifBackfillV1,
},
{
ack: expect.stringContaining(SyncEntityType.PartnerAssetExifBackfillV1),
data: {},
type: SyncEntityType.SyncAckV1,
},
]),
);
const syncAckResponseAcks = syncAckResponse.map(({ ack }) => ack);
await sut.setAcks(auth, { acks: [syncAckResponseAcks[1]] });
const finalResponse = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]);
expect(finalResponse).toEqual([]);
});
it('should only backfill partner assets created prior to the current partner asset checkpoint', async () => { it('should only backfill partner assets created prior to the current partner asset checkpoint', async () => {
const { auth, sut, getRepository, testSync } = await setup(); const { auth, sut, getRepository, testSync } = await setup();
@@ -210,13 +282,13 @@ describe.concurrent(SyncRequestType.PartnerAssetExifsV1, () => {
const assetUser2 = mediumFactory.assetInsert({ ownerId: user2.id }); const assetUser2 = mediumFactory.assetInsert({ ownerId: user2.id });
const asset2User3 = mediumFactory.assetInsert({ ownerId: user3.id }); const asset2User3 = mediumFactory.assetInsert({ ownerId: user3.id });
await assetRepo.create(assetUser3); await assetRepo.create(assetUser3);
await assetRepo.upsertExif({ assetId: assetUser3.id, make: 'Canon' }); await assetRepo.upsertExif({ assetId: assetUser3.id, make: 'assetUser3' });
await wait(2); await wait(2);
await assetRepo.create(assetUser2); await assetRepo.create(assetUser2);
await assetRepo.upsertExif({ assetId: assetUser2.id, make: 'Canon' }); await assetRepo.upsertExif({ assetId: assetUser2.id, make: 'assetUser2' });
await wait(2); await wait(2);
await assetRepo.create(asset2User3); await assetRepo.create(asset2User3);
await assetRepo.upsertExif({ assetId: asset2User3.id, make: 'Canon' }); await assetRepo.upsertExif({ assetId: asset2User3.id, make: 'asset2User3' });
const partnerRepo = getRepository('partner'); const partnerRepo = getRepository('partner');
await partnerRepo.create({ sharedById: user2.id, sharedWithId: auth.user.id }); await partnerRepo.create({ sharedById: user2.id, sharedWithId: auth.user.id });
@@ -246,7 +318,7 @@ describe.concurrent(SyncRequestType.PartnerAssetExifsV1, () => {
expect(backfillResponse).toEqual( expect(backfillResponse).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ {
ack: expect.any(String), ack: expect.stringMatching(new RegExp(`${SyncEntityType.PartnerAssetExifBackfillV1}\\|.+?\\|.+`)),
data: expect.objectContaining({ data: expect.objectContaining({
assetId: assetUser3.id, assetId: assetUser3.id,
}), }),
@@ -270,7 +342,6 @@ describe.concurrent(SyncRequestType.PartnerAssetExifsV1, () => {
const backfillAck = backfillResponse[1].ack; const backfillAck = backfillResponse[1].ack;
const partnerAssetAck = backfillResponse[2].ack; const partnerAssetAck = backfillResponse[2].ack;
await sut.setAcks(auth, { acks: [backfillAck, partnerAssetAck] }); await sut.setAcks(auth, { acks: [backfillAck, partnerAssetAck] });
const finalResponse = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]); const finalResponse = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]);
const finalAcks = finalResponse.map(({ ack }) => ack); const finalAcks = finalResponse.map(({ ack }) => ack);
+7 -7
View File
@@ -1,17 +1,17 @@
{ {
"name": "immich-web", "name": "immich-web",
"version": "1.135.1", "version": "1.135.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich-web", "name": "immich-web",
"version": "1.135.1", "version": "1.135.2",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@formatjs/icu-messageformat-parser": "^2.9.8", "@formatjs/icu-messageformat-parser": "^2.9.8",
"@immich/sdk": "file:../open-api/typescript-sdk", "@immich/sdk": "file:../open-api/typescript-sdk",
"@immich/ui": "^0.22.7", "@immich/ui": "^0.22.8",
"@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mapbox/mapbox-gl-rtl-text": "0.2.3",
"@mdi/js": "^7.4.47", "@mdi/js": "^7.4.47",
"@photo-sphere-viewer/core": "^5.11.5", "@photo-sphere-viewer/core": "^5.11.5",
@@ -87,7 +87,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.135.1", "version": "1.135.2",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"
@@ -1333,9 +1333,9 @@
"link": true "link": true
}, },
"node_modules/@immich/ui": { "node_modules/@immich/ui": {
"version": "0.22.7", "version": "0.22.8",
"resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.22.7.tgz", "resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.22.8.tgz",
"integrity": "sha512-FdA0RDSOO1IDSTQmCbW9u5yXFl59EHu++tYonDR/FEZUKrMwfmQEanePSW5g5KofdumKEuxBN1fWFym3NbB0jQ==", "integrity": "sha512-DVhDgz6drx7vfNhAssX4yYgOC3JpLm8uovLvz3n36skCNU6pm8GoSgH6gMGTM36sx5go3fvhHw5N3KR+A/7bjg==",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@mdi/js": "^7.4.47", "@mdi/js": "^7.4.47",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-web", "name": "immich-web",
"version": "1.135.1", "version": "1.135.2",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -28,7 +28,7 @@
"dependencies": { "dependencies": {
"@formatjs/icu-messageformat-parser": "^2.9.8", "@formatjs/icu-messageformat-parser": "^2.9.8",
"@immich/sdk": "file:../open-api/typescript-sdk", "@immich/sdk": "file:../open-api/typescript-sdk",
"@immich/ui": "^0.22.7", "@immich/ui": "^0.22.8",
"@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mapbox/mapbox-gl-rtl-text": "0.2.3",
"@mdi/js": "^7.4.47", "@mdi/js": "^7.4.47",
"@photo-sphere-viewer/core": "^5.11.5", "@photo-sphere-viewer/core": "^5.11.5",
@@ -0,0 +1,16 @@
<script lang="ts">
import { Icon, Text } from '@immich/ui';
import { mdiCheck, mdiClose } from '@mdi/js';
interface Props {
title: string;
state: boolean;
}
let { title, state }: Props = $props();
</script>
<div class="flex justify-between items-center">
<Text class="text-sm font-medium">{title}</Text>
<Icon icon={state ? mdiCheck : mdiClose} class={state ? 'text-primary' : 'text-danger'} size="24" />
</div>

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