diff --git a/.gitignore b/.gitignore
index 25731cc2aa..3220701cc6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,7 @@ mobile/libisar.dylib
mobile/openapi/test
mobile/openapi/doc
mobile/openapi/.openapi-generator/FILES
+mobile/ios/build
open-api/typescript-sdk/build
mobile/android/fastlane/report.xml
diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md
index 928e0b26e5..4e081c8966 100644
--- a/docs/docs/install/environment-variables.md
+++ b/docs/docs/install/environment-variables.md
@@ -169,8 +169,6 @@ Redis (Sentinel) URL example JSON before encoding:
| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning |
| `MACHINE_LEARNING_DEVICE_IDS`\*4 | Device IDs to use in multi-GPU environments | `0` | machine learning |
| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning |
-| `MACHINE_LEARNING_PING_TIMEOUT` | How long (ms) to wait for a PING response when checking if an ML server is available | `2000` | server |
-| `MACHINE_LEARNING_AVAILABILITY_BACKOFF_TIME` | How long to ignore ML servers that are offline before trying again | `30000` | server |
| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning |
| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spinned up while inferencing. | `1` | machine learning |
diff --git a/i18n/en.json b/i18n/en.json
index aa1999adcb..c7145df889 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -123,6 +123,13 @@
"logging_enable_description": "Enable logging",
"logging_level_description": "When enabled, what log level to use.",
"logging_settings": "Logging",
+ "machine_learning_availability_checks": "Availability checks",
+ "machine_learning_availability_checks_description": "Automatically detect and prefer available machine learning servers",
+ "machine_learning_availability_checks_enabled": "Enable availability checks",
+ "machine_learning_availability_checks_interval": "Check interval",
+ "machine_learning_availability_checks_interval_description": "Interval in milliseconds between availability checks",
+ "machine_learning_availability_checks_timeout": "Request timeout",
+ "machine_learning_availability_checks_timeout_description": "Timeout in milliseconds for availability checks",
"machine_learning_clip_model": "CLIP model",
"machine_learning_clip_model_description": "The name of a CLIP model listed here. Note that you must re-run the 'Smart Search' job for all images upon changing a model.",
"machine_learning_duplicate_detection": "Duplicate Detection",
@@ -913,6 +920,7 @@
"cant_get_number_of_comments": "Can't get number of comments",
"cant_search_people": "Can't search people",
"cant_search_places": "Can't search places",
+ "clipboard_unsupported_mime_type": "The system clipboard does not support copying this type of content: {mimeType}",
"error_adding_assets_to_album": "Error adding assets to album",
"error_adding_users_to_album": "Error adding users to album",
"error_deleting_shared_user": "Error deleting shared user",
@@ -1916,6 +1924,7 @@
"stacktrace": "Stacktrace",
"start": "Start",
"start_date": "Start date",
+ "start_date_before_end_date": "Start date must be before end date",
"state": "State",
"status": "Status",
"stop_casting": "Stop casting",
diff --git a/mise.toml b/mise.toml
index 7d4824de57..c741b0c71a 100644
--- a/mise.toml
+++ b/mise.toml
@@ -1,7 +1,7 @@
[tools]
node = "22.19.0"
flutter = "3.35.4"
-pnpm = "10.14.0"
+pnpm = "10.15.1"
dart = "3.8.2"
[tools."github:CQLabs/homebrew-dcm"]
diff --git a/mobile/drift_schemas/main/drift_schema_v11.json b/mobile/drift_schemas/main/drift_schema_v11.json
new file mode 100644
index 0000000000..1c100ab37f
--- /dev/null
+++ b/mobile/drift_schemas/main/drift_schema_v11.json
@@ -0,0 +1 @@
+{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":true},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"user_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"has_profile_image","getter_name":"hasProfileImage","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"has_profile_image\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"has_profile_image\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"profile_changed_at","getter_name":"profileChangedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"avatar_color","getter_name":"avatarColor","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AvatarColor.values)","dart_type_name":"AvatarColor"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":1,"references":[0],"type":"table","data":{"name":"remote_asset_entity","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetType.values)","dart_type_name":"AssetType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"checksum","getter_name":"checksum","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"local_date_time","getter_name":"localDateTime","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"thumb_hash","getter_name":"thumbHash","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"deleted_at","getter_name":"deletedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"live_photo_video_id","getter_name":"livePhotoVideoId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"visibility","getter_name":"visibility","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetVisibility.values)","dart_type_name":"AssetVisibility"}},{"name":"stack_id","getter_name":"stackId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"library_id","getter_name":"libraryId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":2,"references":[0],"type":"table","data":{"name":"stack_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"primary_asset_id","getter_name":"primaryAssetId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":3,"references":[],"type":"table","data":{"name":"local_asset_entity","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetType.values)","dart_type_name":"AssetType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"checksum","getter_name":"checksum","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"orientation","getter_name":"orientation","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":4,"references":[0,1],"type":"table","data":{"name":"remote_album_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"description","getter_name":"description","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('\\'\\'')","default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"thumbnail_asset_id","getter_name":"thumbnailAssetId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"is_activity_enabled","getter_name":"isActivityEnabled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_activity_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_activity_enabled\" IN (0, 1))"},"default_dart":"const CustomExpression('1')","default_client_dart":null,"dsl_features":[]},{"name":"order","getter_name":"order","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AlbumAssetOrder.values)","dart_type_name":"AlbumAssetOrder"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":5,"references":[4],"type":"table","data":{"name":"local_album_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"backup_selection","getter_name":"backupSelection","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(BackupSelection.values)","dart_type_name":"BackupSelection"}},{"name":"is_ios_shared_album","getter_name":"isIosSharedAlbum","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_ios_shared_album\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_ios_shared_album\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"linked_remote_album_id","getter_name":"linkedRemoteAlbumId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"marker","getter_name":"marker_","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"marker\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"marker\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":6,"references":[3,5],"type":"table","data":{"name":"local_album_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES local_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES local_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES local_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES local_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"marker","getter_name":"marker_","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"marker\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"marker\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","album_id"]}},{"id":7,"references":[3],"type":"index","data":{"on":3,"name":"idx_local_asset_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)","unique":false,"columns":[]}},{"id":8,"references":[1],"type":"index","data":{"on":1,"name":"idx_remote_asset_owner_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)","unique":false,"columns":[]}},{"id":9,"references":[1],"type":"index","data":{"on":1,"name":"UQ_remote_assets_owner_checksum","sql":"CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n","unique":true,"columns":[]}},{"id":10,"references":[1],"type":"index","data":{"on":1,"name":"UQ_remote_assets_owner_library_checksum","sql":"CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n","unique":true,"columns":[]}},{"id":11,"references":[1],"type":"index","data":{"on":1,"name":"idx_remote_asset_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)","unique":false,"columns":[]}},{"id":12,"references":[],"type":"table","data":{"name":"auth_user_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_admin","getter_name":"isAdmin","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_admin\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_admin\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"has_profile_image","getter_name":"hasProfileImage","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"has_profile_image\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"has_profile_image\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"profile_changed_at","getter_name":"profileChangedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"avatar_color","getter_name":"avatarColor","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AvatarColor.values)","dart_type_name":"AvatarColor"}},{"name":"quota_size_in_bytes","getter_name":"quotaSizeInBytes","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"quota_usage_in_bytes","getter_name":"quotaUsageInBytes","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"pin_code","getter_name":"pinCode","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":13,"references":[0],"type":"table","data":{"name":"user_metadata_entity","was_declared_in_moor":false,"columns":[{"name":"user_id","getter_name":"userId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"key","getter_name":"key","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(UserMetadataKey.values)","dart_type_name":"UserMetadataKey"}},{"name":"value","getter_name":"value","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"userMetadataConverter","dart_type_name":"Map"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["user_id","key"]}},{"id":14,"references":[0],"type":"table","data":{"name":"partner_entity","was_declared_in_moor":false,"columns":[{"name":"shared_by_id","getter_name":"sharedById","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"shared_with_id","getter_name":"sharedWithId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"in_timeline","getter_name":"inTimeline","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"in_timeline\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"in_timeline\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["shared_by_id","shared_with_id"]}},{"id":15,"references":[1],"type":"table","data":{"name":"remote_exif_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"city","getter_name":"city","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"state","getter_name":"state","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"country","getter_name":"country","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"date_time_original","getter_name":"dateTimeOriginal","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"description","getter_name":"description","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"exposure_time","getter_name":"exposureTime","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"f_number","getter_name":"fNumber","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"file_size","getter_name":"fileSize","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"focal_length","getter_name":"focalLength","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"latitude","getter_name":"latitude","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"longitude","getter_name":"longitude","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"iso","getter_name":"iso","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"make","getter_name":"make","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"model","getter_name":"model","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"lens","getter_name":"lens","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"orientation","getter_name":"orientation","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"time_zone","getter_name":"timeZone","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"rating","getter_name":"rating","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"projection_type","getter_name":"projectionType","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id"]}},{"id":16,"references":[1,4],"type":"table","data":{"name":"remote_album_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","album_id"]}},{"id":17,"references":[4,0],"type":"table","data":{"name":"remote_album_user_entity","was_declared_in_moor":false,"columns":[{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"user_id","getter_name":"userId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"role","getter_name":"role","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AlbumUserRole.values)","dart_type_name":"AlbumUserRole"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["album_id","user_id"]}},{"id":18,"references":[0],"type":"table","data":{"name":"memory_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"deleted_at","getter_name":"deletedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(MemoryTypeEnum.values)","dart_type_name":"MemoryTypeEnum"}},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_saved","getter_name":"isSaved","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_saved\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_saved\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"memory_at","getter_name":"memoryAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"seen_at","getter_name":"seenAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"show_at","getter_name":"showAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"hide_at","getter_name":"hideAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":19,"references":[1,18],"type":"table","data":{"name":"memory_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"memory_id","getter_name":"memoryId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES memory_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES memory_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","memory_id"]}},{"id":20,"references":[0],"type":"table","data":{"name":"person_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"face_asset_id","getter_name":"faceAssetId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_hidden","getter_name":"isHidden","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_hidden\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_hidden\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"color","getter_name":"color","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"birth_date","getter_name":"birthDate","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":21,"references":[1,20],"type":"table","data":{"name":"asset_face_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"person_id","getter_name":"personId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES person_entity (id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES person_entity (id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"image_width","getter_name":"imageWidth","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"image_height","getter_name":"imageHeight","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_x1","getter_name":"boundingBoxX1","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_y1","getter_name":"boundingBoxY1","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_x2","getter_name":"boundingBoxX2","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_y2","getter_name":"boundingBoxY2","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_type","getter_name":"sourceType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":22,"references":[],"type":"table","data":{"name":"store_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"string_value","getter_name":"stringValue","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"int_value","getter_name":"intValue","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":23,"references":[15],"type":"index","data":{"on":15,"name":"idx_lat_lng","sql":"CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)","unique":false,"columns":[]}}]}
\ No newline at end of file
diff --git a/mobile/lib/infrastructure/entities/local_album_asset.entity.dart b/mobile/lib/infrastructure/entities/local_album_asset.entity.dart
index 8de879a09d..53f1a10662 100644
--- a/mobile/lib/infrastructure/entities/local_album_asset.entity.dart
+++ b/mobile/lib/infrastructure/entities/local_album_asset.entity.dart
@@ -10,6 +10,9 @@ class LocalAlbumAssetEntity extends Table with DriftDefaultsMixin {
TextColumn get albumId => text().references(LocalAlbumEntity, #id, onDelete: KeyAction.cascade)();
+ // Used for mark & sweep
+ BoolColumn get marker_ => boolean().nullable()();
+
@override
Set get primaryKey => {assetId, albumId};
}
diff --git a/mobile/lib/infrastructure/entities/local_album_asset.entity.drift.dart b/mobile/lib/infrastructure/entities/local_album_asset.entity.drift.dart
index 78da361f62..70c298332b 100644
--- a/mobile/lib/infrastructure/entities/local_album_asset.entity.drift.dart
+++ b/mobile/lib/infrastructure/entities/local_album_asset.entity.drift.dart
@@ -15,11 +15,13 @@ typedef $$LocalAlbumAssetEntityTableCreateCompanionBuilder =
i1.LocalAlbumAssetEntityCompanion Function({
required String assetId,
required String albumId,
+ i0.Value marker_,
});
typedef $$LocalAlbumAssetEntityTableUpdateCompanionBuilder =
i1.LocalAlbumAssetEntityCompanion Function({
i0.Value assetId,
i0.Value albumId,
+ i0.Value marker_,
});
final class $$LocalAlbumAssetEntityTableReferences
@@ -113,6 +115,11 @@ class $$LocalAlbumAssetEntityTableFilterComposer
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
+ i0.ColumnFilters get marker_ => $composableBuilder(
+ column: $table.marker_,
+ builder: (column) => i0.ColumnFilters(column),
+ );
+
i3.$$LocalAssetEntityTableFilterComposer get assetId {
final i3.$$LocalAssetEntityTableFilterComposer composer = $composerBuilder(
composer: this,
@@ -177,6 +184,11 @@ class $$LocalAlbumAssetEntityTableOrderingComposer
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
+ i0.ColumnOrderings get marker_ => $composableBuilder(
+ column: $table.marker_,
+ builder: (column) => i0.ColumnOrderings(column),
+ );
+
i3.$$LocalAssetEntityTableOrderingComposer get assetId {
final i3.$$LocalAssetEntityTableOrderingComposer composer =
$composerBuilder(
@@ -243,6 +255,9 @@ class $$LocalAlbumAssetEntityTableAnnotationComposer
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
+ i0.GeneratedColumn get marker_ =>
+ $composableBuilder(column: $table.marker_, builder: (column) => column);
+
i3.$$LocalAssetEntityTableAnnotationComposer get assetId {
final i3.$$LocalAssetEntityTableAnnotationComposer composer =
$composerBuilder(
@@ -344,16 +359,22 @@ class $$LocalAlbumAssetEntityTableTableManager
({
i0.Value assetId = const i0.Value.absent(),
i0.Value albumId = const i0.Value.absent(),
+ i0.Value marker_ = const i0.Value.absent(),
}) => i1.LocalAlbumAssetEntityCompanion(
assetId: assetId,
albumId: albumId,
+ marker_: marker_,
),
createCompanionCallback:
- ({required String assetId, required String albumId}) =>
- i1.LocalAlbumAssetEntityCompanion.insert(
- assetId: assetId,
- albumId: albumId,
- ),
+ ({
+ required String assetId,
+ required String albumId,
+ i0.Value marker_ = const i0.Value.absent(),
+ }) => i1.LocalAlbumAssetEntityCompanion.insert(
+ assetId: assetId,
+ albumId: albumId,
+ marker_: marker_,
+ ),
withReferenceMapper: (p0) => p0
.map(
(e) => (
@@ -477,8 +498,22 @@ class $LocalAlbumAssetEntityTable extends i2.LocalAlbumAssetEntity
'REFERENCES local_album_entity (id) ON DELETE CASCADE',
),
);
+ static const i0.VerificationMeta _marker_Meta = const i0.VerificationMeta(
+ 'marker_',
+ );
@override
- List get $columns => [assetId, albumId];
+ late final i0.GeneratedColumn marker_ = i0.GeneratedColumn(
+ 'marker',
+ aliasedName,
+ true,
+ type: i0.DriftSqlType.bool,
+ requiredDuringInsert: false,
+ defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
+ 'CHECK ("marker" IN (0, 1))',
+ ),
+ );
+ @override
+ List get $columns => [assetId, albumId, marker_];
@override
String get aliasedName => _alias ?? actualTableName;
@override
@@ -507,6 +542,12 @@ class $LocalAlbumAssetEntityTable extends i2.LocalAlbumAssetEntity
} else if (isInserting) {
context.missing(_albumIdMeta);
}
+ if (data.containsKey('marker')) {
+ context.handle(
+ _marker_Meta,
+ marker_.isAcceptableOrUnknown(data['marker']!, _marker_Meta),
+ );
+ }
return context;
}
@@ -527,6 +568,10 @@ class $LocalAlbumAssetEntityTable extends i2.LocalAlbumAssetEntity
i0.DriftSqlType.string,
data['${effectivePrefix}album_id'],
)!,
+ marker_: attachedDatabase.typeMapping.read(
+ i0.DriftSqlType.bool,
+ data['${effectivePrefix}marker'],
+ ),
);
}
@@ -545,15 +590,20 @@ class LocalAlbumAssetEntityData extends i0.DataClass
implements i0.Insertable {
final String assetId;
final String albumId;
+ final bool? marker_;
const LocalAlbumAssetEntityData({
required this.assetId,
required this.albumId,
+ this.marker_,
});
@override
Map toColumns(bool nullToAbsent) {
final map = {};
map['asset_id'] = i0.Variable(assetId);
map['album_id'] = i0.Variable(albumId);
+ if (!nullToAbsent || marker_ != null) {
+ map['marker'] = i0.Variable(marker_);
+ }
return map;
}
@@ -565,6 +615,7 @@ class LocalAlbumAssetEntityData extends i0.DataClass
return LocalAlbumAssetEntityData(
assetId: serializer.fromJson(json['assetId']),
albumId: serializer.fromJson(json['albumId']),
+ marker_: serializer.fromJson(json['marker_']),
);
}
@override
@@ -573,20 +624,26 @@ class LocalAlbumAssetEntityData extends i0.DataClass
return {
'assetId': serializer.toJson(assetId),
'albumId': serializer.toJson(albumId),
+ 'marker_': serializer.toJson(marker_),
};
}
- i1.LocalAlbumAssetEntityData copyWith({String? assetId, String? albumId}) =>
- i1.LocalAlbumAssetEntityData(
- assetId: assetId ?? this.assetId,
- albumId: albumId ?? this.albumId,
- );
+ i1.LocalAlbumAssetEntityData copyWith({
+ String? assetId,
+ String? albumId,
+ i0.Value marker_ = const i0.Value.absent(),
+ }) => i1.LocalAlbumAssetEntityData(
+ assetId: assetId ?? this.assetId,
+ albumId: albumId ?? this.albumId,
+ marker_: marker_.present ? marker_.value : this.marker_,
+ );
LocalAlbumAssetEntityData copyWithCompanion(
i1.LocalAlbumAssetEntityCompanion data,
) {
return LocalAlbumAssetEntityData(
assetId: data.assetId.present ? data.assetId.value : this.assetId,
albumId: data.albumId.present ? data.albumId.value : this.albumId,
+ marker_: data.marker_.present ? data.marker_.value : this.marker_,
);
}
@@ -594,51 +651,60 @@ class LocalAlbumAssetEntityData extends i0.DataClass
String toString() {
return (StringBuffer('LocalAlbumAssetEntityData(')
..write('assetId: $assetId, ')
- ..write('albumId: $albumId')
+ ..write('albumId: $albumId, ')
+ ..write('marker_: $marker_')
..write(')'))
.toString();
}
@override
- int get hashCode => Object.hash(assetId, albumId);
+ int get hashCode => Object.hash(assetId, albumId, marker_);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is i1.LocalAlbumAssetEntityData &&
other.assetId == this.assetId &&
- other.albumId == this.albumId);
+ other.albumId == this.albumId &&
+ other.marker_ == this.marker_);
}
class LocalAlbumAssetEntityCompanion
extends i0.UpdateCompanion {
final i0.Value assetId;
final i0.Value albumId;
+ final i0.Value marker_;
const LocalAlbumAssetEntityCompanion({
this.assetId = const i0.Value.absent(),
this.albumId = const i0.Value.absent(),
+ this.marker_ = const i0.Value.absent(),
});
LocalAlbumAssetEntityCompanion.insert({
required String assetId,
required String albumId,
+ this.marker_ = const i0.Value.absent(),
}) : assetId = i0.Value(assetId),
albumId = i0.Value(albumId);
static i0.Insertable custom({
i0.Expression? assetId,
i0.Expression? albumId,
+ i0.Expression? marker_,
}) {
return i0.RawValuesInsertable({
if (assetId != null) 'asset_id': assetId,
if (albumId != null) 'album_id': albumId,
+ if (marker_ != null) 'marker': marker_,
});
}
i1.LocalAlbumAssetEntityCompanion copyWith({
i0.Value? assetId,
i0.Value? albumId,
+ i0.Value? marker_,
}) {
return i1.LocalAlbumAssetEntityCompanion(
assetId: assetId ?? this.assetId,
albumId: albumId ?? this.albumId,
+ marker_: marker_ ?? this.marker_,
);
}
@@ -651,6 +717,9 @@ class LocalAlbumAssetEntityCompanion
if (albumId.present) {
map['album_id'] = i0.Variable(albumId.value);
}
+ if (marker_.present) {
+ map['marker'] = i0.Variable(marker_.value);
+ }
return map;
}
@@ -658,7 +727,8 @@ class LocalAlbumAssetEntityCompanion
String toString() {
return (StringBuffer('LocalAlbumAssetEntityCompanion(')
..write('assetId: $assetId, ')
- ..write('albumId: $albumId')
+ ..write('albumId: $albumId, ')
+ ..write('marker_: $marker_')
..write(')'))
.toString();
}
diff --git a/mobile/lib/infrastructure/repositories/db.repository.dart b/mobile/lib/infrastructure/repositories/db.repository.dart
index f04ed27779..65d26d9747 100644
--- a/mobile/lib/infrastructure/repositories/db.repository.dart
+++ b/mobile/lib/infrastructure/repositories/db.repository.dart
@@ -93,7 +93,7 @@ class Drift extends $Drift implements IDatabaseRepository {
}
@override
- int get schemaVersion => 10;
+ int get schemaVersion => 11;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -156,6 +156,9 @@ class Drift extends $Drift implements IDatabaseRepository {
await m.addColumn(v10.userEntity, v10.userEntity.avatarColor);
await m.alterTable(TableMigration(v10.userEntity));
},
+ from10To11: (m, v11) async {
+ await m.addColumn(v11.localAlbumAssetEntity, v11.localAlbumAssetEntity.marker_);
+ },
),
);
diff --git a/mobile/lib/infrastructure/repositories/db.repository.steps.dart b/mobile/lib/infrastructure/repositories/db.repository.steps.dart
index be6d53d5a8..7910d9fcee 100644
--- a/mobile/lib/infrastructure/repositories/db.repository.steps.dart
+++ b/mobile/lib/infrastructure/repositories/db.repository.steps.dart
@@ -4270,6 +4270,395 @@ i1.GeneratedColumn _column_94(String aliasedName) =>
true,
type: i1.DriftSqlType.string,
);
+
+final class Schema11 extends i0.VersionedSchema {
+ Schema11({required super.database}) : super(version: 11);
+ @override
+ late final List entities = [
+ userEntity,
+ remoteAssetEntity,
+ stackEntity,
+ localAssetEntity,
+ remoteAlbumEntity,
+ localAlbumEntity,
+ localAlbumAssetEntity,
+ idxLocalAssetChecksum,
+ idxRemoteAssetOwnerChecksum,
+ uQRemoteAssetsOwnerChecksum,
+ uQRemoteAssetsOwnerLibraryChecksum,
+ idxRemoteAssetChecksum,
+ authUserEntity,
+ userMetadataEntity,
+ partnerEntity,
+ remoteExifEntity,
+ remoteAlbumAssetEntity,
+ remoteAlbumUserEntity,
+ memoryEntity,
+ memoryAssetEntity,
+ personEntity,
+ assetFaceEntity,
+ storeEntity,
+ idxLatLng,
+ ];
+ late final Shape20 userEntity = Shape20(
+ source: i0.VersionedTable(
+ entityName: 'user_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(id)'],
+ columns: [
+ _column_0,
+ _column_1,
+ _column_3,
+ _column_84,
+ _column_85,
+ _column_91,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ late final Shape17 remoteAssetEntity = Shape17(
+ source: i0.VersionedTable(
+ entityName: 'remote_asset_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(id)'],
+ columns: [
+ _column_1,
+ _column_8,
+ _column_9,
+ _column_5,
+ _column_10,
+ _column_11,
+ _column_12,
+ _column_0,
+ _column_13,
+ _column_14,
+ _column_15,
+ _column_16,
+ _column_17,
+ _column_18,
+ _column_19,
+ _column_20,
+ _column_21,
+ _column_86,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ late final Shape3 stackEntity = Shape3(
+ source: i0.VersionedTable(
+ entityName: 'stack_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(id)'],
+ columns: [_column_0, _column_9, _column_5, _column_15, _column_75],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ late final Shape2 localAssetEntity = Shape2(
+ source: i0.VersionedTable(
+ entityName: 'local_asset_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(id)'],
+ columns: [
+ _column_1,
+ _column_8,
+ _column_9,
+ _column_5,
+ _column_10,
+ _column_11,
+ _column_12,
+ _column_0,
+ _column_22,
+ _column_14,
+ _column_23,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ late final Shape9 remoteAlbumEntity = Shape9(
+ source: i0.VersionedTable(
+ entityName: 'remote_album_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(id)'],
+ columns: [
+ _column_0,
+ _column_1,
+ _column_56,
+ _column_9,
+ _column_5,
+ _column_15,
+ _column_57,
+ _column_58,
+ _column_59,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ late final Shape19 localAlbumEntity = Shape19(
+ source: i0.VersionedTable(
+ entityName: 'local_album_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(id)'],
+ columns: [
+ _column_0,
+ _column_1,
+ _column_5,
+ _column_31,
+ _column_32,
+ _column_90,
+ _column_33,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ late final Shape22 localAlbumAssetEntity = Shape22(
+ source: i0.VersionedTable(
+ entityName: 'local_album_asset_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
+ columns: [_column_34, _column_35, _column_33],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ final i1.Index idxLocalAssetChecksum = i1.Index(
+ 'idx_local_asset_checksum',
+ 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
+ );
+ final i1.Index idxRemoteAssetOwnerChecksum = i1.Index(
+ 'idx_remote_asset_owner_checksum',
+ 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)',
+ );
+ final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
+ 'UQ_remote_assets_owner_checksum',
+ 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
+ );
+ final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
+ 'UQ_remote_assets_owner_library_checksum',
+ 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
+ );
+ final i1.Index idxRemoteAssetChecksum = i1.Index(
+ 'idx_remote_asset_checksum',
+ 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
+ );
+ late final Shape21 authUserEntity = Shape21(
+ source: i0.VersionedTable(
+ entityName: 'auth_user_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(id)'],
+ columns: [
+ _column_0,
+ _column_1,
+ _column_3,
+ _column_2,
+ _column_84,
+ _column_85,
+ _column_92,
+ _column_93,
+ _column_7,
+ _column_94,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ late final Shape4 userMetadataEntity = Shape4(
+ source: i0.VersionedTable(
+ entityName: 'user_metadata_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(user_id, "key")'],
+ columns: [_column_25, _column_26, _column_27],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ late final Shape5 partnerEntity = Shape5(
+ source: i0.VersionedTable(
+ entityName: 'partner_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
+ columns: [_column_28, _column_29, _column_30],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ late final Shape8 remoteExifEntity = Shape8(
+ source: i0.VersionedTable(
+ entityName: 'remote_exif_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(asset_id)'],
+ columns: [
+ _column_36,
+ _column_37,
+ _column_38,
+ _column_39,
+ _column_40,
+ _column_41,
+ _column_11,
+ _column_10,
+ _column_42,
+ _column_43,
+ _column_44,
+ _column_45,
+ _column_46,
+ _column_47,
+ _column_48,
+ _column_49,
+ _column_50,
+ _column_51,
+ _column_52,
+ _column_53,
+ _column_54,
+ _column_55,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ late final Shape7 remoteAlbumAssetEntity = Shape7(
+ source: i0.VersionedTable(
+ entityName: 'remote_album_asset_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
+ columns: [_column_36, _column_60],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ late final Shape10 remoteAlbumUserEntity = Shape10(
+ source: i0.VersionedTable(
+ entityName: 'remote_album_user_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
+ columns: [_column_60, _column_25, _column_61],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ late final Shape11 memoryEntity = Shape11(
+ source: i0.VersionedTable(
+ entityName: 'memory_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(id)'],
+ columns: [
+ _column_0,
+ _column_9,
+ _column_5,
+ _column_18,
+ _column_15,
+ _column_8,
+ _column_62,
+ _column_63,
+ _column_64,
+ _column_65,
+ _column_66,
+ _column_67,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ late final Shape12 memoryAssetEntity = Shape12(
+ source: i0.VersionedTable(
+ entityName: 'memory_asset_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
+ columns: [_column_36, _column_68],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ late final Shape14 personEntity = Shape14(
+ source: i0.VersionedTable(
+ entityName: 'person_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(id)'],
+ columns: [
+ _column_0,
+ _column_9,
+ _column_5,
+ _column_15,
+ _column_1,
+ _column_69,
+ _column_71,
+ _column_72,
+ _column_73,
+ _column_74,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ late final Shape15 assetFaceEntity = Shape15(
+ source: i0.VersionedTable(
+ entityName: 'asset_face_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(id)'],
+ columns: [
+ _column_0,
+ _column_36,
+ _column_76,
+ _column_77,
+ _column_78,
+ _column_79,
+ _column_80,
+ _column_81,
+ _column_82,
+ _column_83,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ late final Shape18 storeEntity = Shape18(
+ source: i0.VersionedTable(
+ entityName: 'store_entity',
+ withoutRowId: true,
+ isStrict: true,
+ tableConstraints: ['PRIMARY KEY(id)'],
+ columns: [_column_87, _column_88, _column_89],
+ attachedDatabase: database,
+ ),
+ alias: null,
+ );
+ final i1.Index idxLatLng = i1.Index(
+ 'idx_lat_lng',
+ 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
+ );
+}
+
+class Shape22 extends i0.VersionedTable {
+ Shape22({required super.source, required super.alias}) : super.aliased();
+ i1.GeneratedColumn get assetId =>
+ columnsByName['asset_id']! as i1.GeneratedColumn;
+ i1.GeneratedColumn get albumId =>
+ columnsByName['album_id']! as i1.GeneratedColumn;
+ i1.GeneratedColumn get marker_ =>
+ columnsByName['marker']! as i1.GeneratedColumn;
+}
+
i0.MigrationStepWithVersion migrationSteps({
required Future Function(i1.Migrator m, Schema2 schema) from1To2,
required Future Function(i1.Migrator m, Schema3 schema) from2To3,
@@ -4280,6 +4669,7 @@ i0.MigrationStepWithVersion migrationSteps({
required Future Function(i1.Migrator m, Schema8 schema) from7To8,
required Future Function(i1.Migrator m, Schema9 schema) from8To9,
required Future Function(i1.Migrator m, Schema10 schema) from9To10,
+ required Future Function(i1.Migrator m, Schema11 schema) from10To11,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
@@ -4328,6 +4718,11 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema);
await from9To10(migrator, schema);
return 10;
+ case 10:
+ final schema = Schema11(database: database);
+ final migrator = i1.Migrator(database, schema);
+ await from10To11(migrator, schema);
+ return 11;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
@@ -4344,6 +4739,7 @@ i1.OnUpgrade stepByStep({
required Future Function(i1.Migrator m, Schema8 schema) from7To8,
required Future Function(i1.Migrator m, Schema9 schema) from8To9,
required Future Function(i1.Migrator m, Schema10 schema) from9To10,
+ required Future Function(i1.Migrator m, Schema11 schema) from10To11,
}) => i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(
from1To2: from1To2,
@@ -4355,5 +4751,6 @@ i1.OnUpgrade stepByStep({
from7To8: from7To8,
from8To9: from8To9,
from9To10: from9To10,
+ from10To11: from10To11,
),
);
diff --git a/mobile/lib/infrastructure/repositories/local_album.repository.dart b/mobile/lib/infrastructure/repositories/local_album.repository.dart
index b97884c27f..e4bff24879 100644
--- a/mobile/lib/infrastructure/repositories/local_album.repository.dart
+++ b/mobile/lib/infrastructure/repositories/local_album.repository.dart
@@ -72,17 +72,33 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
return Future.value();
}
- final deleteSmt = _db.localAssetEntity.delete();
- deleteSmt.where((localAsset) {
- final subQuery = _db.localAlbumAssetEntity.selectOnly()
- ..addColumns([_db.localAlbumAssetEntity.assetId])
- ..join([innerJoin(_db.localAlbumEntity, _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id))]);
- subQuery.where(
- _db.localAlbumEntity.id.equals(albumId) & _db.localAlbumAssetEntity.assetId.isNotIn(assetIdsToKeep),
- );
- return localAsset.id.isInQuery(subQuery);
+ return _db.transaction(() async {
+ await _db.managers.localAlbumAssetEntity
+ .filter((row) => row.albumId.id.equals(albumId))
+ .update((album) => album(marker_: const Value(true)));
+
+ await _db.batch((batch) {
+ for (final assetId in assetIdsToKeep) {
+ batch.update(
+ _db.localAlbumAssetEntity,
+ const LocalAlbumAssetEntityCompanion(marker_: Value(null)),
+ where: (row) => row.assetId.equals(assetId) & row.albumId.equals(albumId),
+ );
+ }
+ });
+
+ final query = _db.localAssetEntity.delete()
+ ..where(
+ (row) => row.id.isInQuery(
+ _db.localAlbumAssetEntity.selectOnly()
+ ..addColumns([_db.localAlbumAssetEntity.assetId])
+ ..where(
+ _db.localAlbumAssetEntity.albumId.equals(albumId) & _db.localAlbumAssetEntity.marker_.isNotNull(),
+ ),
+ ),
+ );
+ await query.go();
});
- await deleteSmt.go();
}
Future upsert(
@@ -198,10 +214,9 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
// List
await _db.batch((batch) async {
assetAlbums.cast>().forEach((assetId, albumIds) {
- batch.deleteWhere(
- _db.localAlbumAssetEntity,
- (f) => f.albumId.isNotIn(albumIds.cast().nonNulls) & f.assetId.equals(assetId),
- );
+ for (final albumId in albumIds.cast().nonNulls) {
+ batch.deleteWhere(_db.localAlbumAssetEntity, (f) => f.albumId.equals(albumId) & f.assetId.equals(assetId));
+ }
});
});
await _db.batch((batch) async {
@@ -288,12 +303,14 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
return transaction(() async {
if (assetsToUnLink.isNotEmpty) {
- await _db.batch(
- (batch) => batch.deleteWhere(
- _db.localAlbumAssetEntity,
- (f) => f.assetId.isIn(assetsToUnLink) & f.albumId.equals(albumId),
- ),
- );
+ await _db.batch((batch) {
+ for (final assetId in assetsToUnLink) {
+ batch.deleteWhere(
+ _db.localAlbumAssetEntity,
+ (row) => row.assetId.equals(assetId) & row.albumId.equals(albumId),
+ );
+ }
+ });
}
await _deleteAssets(assetsToDelete);
@@ -320,7 +337,9 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
}
return _db.batch((batch) {
- batch.deleteWhere(_db.localAssetEntity, (f) => f.id.isIn(ids));
+ for (final id in ids) {
+ batch.deleteWhere(_db.localAssetEntity, (row) => row.id.equals(id));
+ }
});
}
diff --git a/mobile/lib/infrastructure/repositories/local_asset.repository.dart b/mobile/lib/infrastructure/repositories/local_asset.repository.dart
index c4f5221966..2b76472c9e 100644
--- a/mobile/lib/infrastructure/repositories/local_asset.repository.dart
+++ b/mobile/lib/infrastructure/repositories/local_asset.repository.dart
@@ -1,4 +1,3 @@
-import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
@@ -58,8 +57,8 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
}
return _db.batch((batch) {
- for (final slice in ids.slices(32000)) {
- batch.deleteWhere(_db.localAssetEntity, (e) => e.id.isIn(slice));
+ for (final id in ids) {
+ batch.deleteWhere(_db.localAssetEntity, (e) => e.id.equals(id));
}
});
}
diff --git a/mobile/lib/infrastructure/repositories/remote_album.repository.dart b/mobile/lib/infrastructure/repositories/remote_album.repository.dart
index 78b56e7436..5dfe4ac9b3 100644
--- a/mobile/lib/infrastructure/repositories/remote_album.repository.dart
+++ b/mobile/lib/infrastructure/repositories/remote_album.repository.dart
@@ -166,8 +166,15 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
);
}
- Future removeAssets(String albumId, List assetIds) {
- return _db.remoteAlbumAssetEntity.deleteWhere((tbl) => tbl.albumId.equals(albumId) & tbl.assetId.isIn(assetIds));
+ Future removeAssets(String albumId, List assetIds) {
+ return _db.batch((batch) {
+ for (final assetId in assetIds) {
+ batch.deleteWhere(
+ _db.remoteAlbumAssetEntity,
+ (row) => row.albumId.equals(albumId) & row.assetId.equals(assetId),
+ );
+ }
+ });
}
FutureOr<(DateTime, DateTime)> getDateRange(String albumId) {
diff --git a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart
index 40f397f0ab..092bb728d9 100644
--- a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart
+++ b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart
@@ -160,7 +160,11 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
}
Future delete(List ids) {
- return _db.remoteAssetEntity.deleteWhere((row) => row.id.isIn(ids));
+ return _db.batch((batch) {
+ for (final id in ids) {
+ batch.deleteWhere(_db.remoteAssetEntity, (row) => row.id.equals(id));
+ }
+ });
}
Future updateLocation(List ids, LatLng location) {
@@ -199,7 +203,11 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
.map((row) => row.id)
.get();
- await _db.stackEntity.deleteWhere((row) => row.id.isIn(stackIds));
+ await _db.batch((batch) {
+ for (final stackId in stackIds) {
+ batch.deleteWhere(_db.stackEntity, (row) => row.id.equals(stackId));
+ }
+ });
await _db.batch((batch) {
final companion = StackEntityCompanion(ownerId: Value(userId), primaryAssetId: Value(stack.primaryAssetId));
@@ -219,15 +227,21 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
Future unStack(List stackIds) {
return _db.transaction(() async {
- await _db.stackEntity.deleteWhere((row) => row.id.isIn(stackIds));
+ await _db.batch((batch) {
+ for (final stackId in stackIds) {
+ batch.deleteWhere(_db.stackEntity, (row) => row.id.equals(stackId));
+ }
+ });
// TODO: delete this after adding foreign key on stackId
await _db.batch((batch) {
- batch.update(
- _db.remoteAssetEntity,
- const RemoteAssetEntityCompanion(stackId: Value(null)),
- where: (e) => e.stackId.isIn(stackIds),
- );
+ for (final stackId in stackIds) {
+ batch.update(
+ _db.remoteAssetEntity,
+ const RemoteAssetEntityCompanion(stackId: Value(null)),
+ where: (e) => e.stackId.equals(stackId),
+ );
+ }
});
});
}
diff --git a/mobile/lib/infrastructure/repositories/search_api.repository.dart b/mobile/lib/infrastructure/repositories/search_api.repository.dart
index 129746120b..dd72333398 100644
--- a/mobile/lib/infrastructure/repositories/search_api.repository.dart
+++ b/mobile/lib/infrastructure/repositories/search_api.repository.dart
@@ -33,7 +33,7 @@ class SearchApiRepository extends ApiRepository {
personIds: filter.people.map((e) => e.id).toList(),
type: type,
page: page,
- size: 1000,
+ size: 100,
),
);
}
diff --git a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart
index 960a84435f..f4720fb110 100644
--- a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart
+++ b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart
@@ -93,7 +93,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
Future deleteUsersV1(Iterable data) async {
try {
- await _db.userEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.userId)));
+ await _db.batch((batch) {
+ for (final user in data) {
+ batch.deleteWhere(_db.userEntity, (row) => row.id.equals(user.userId));
+ }
+ });
} catch (error, stack) {
_logger.severe('Error: SyncUserDeleteV1', error, stack);
rethrow;
@@ -158,7 +162,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
Future deleteAssetsV1(Iterable data, {String debugLabel = 'user'}) async {
try {
- await _db.remoteAssetEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.assetId)));
+ await _db.batch((batch) {
+ for (final asset in data) {
+ batch.deleteWhere(_db.remoteAssetEntity, (row) => row.id.equals(asset.assetId));
+ }
+ });
} catch (error, stack) {
_logger.severe('Error: deleteAssetsV1 - $debugLabel', error, stack);
rethrow;
@@ -243,7 +251,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
Future deleteAlbumsV1(Iterable data) async {
try {
- await _db.remoteAlbumEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.albumId)));
+ await _db.batch((batch) {
+ for (final album in data) {
+ batch.deleteWhere(_db.remoteAlbumEntity, (row) => row.id.equals(album.albumId));
+ }
+ });
} catch (error, stack) {
_logger.severe('Error: deleteAlbumsV1', error, stack);
rethrow;
@@ -379,7 +391,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
Future deleteMemoriesV1(Iterable data) async {
try {
- await _db.memoryEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.memoryId)));
+ await _db.batch((batch) {
+ for (final memory in data) {
+ batch.deleteWhere(_db.memoryEntity, (row) => row.id.equals(memory.memoryId));
+ }
+ });
} catch (error, stack) {
_logger.severe('Error: deleteMemoriesV1', error, stack);
rethrow;
@@ -443,7 +459,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
Future deleteStacksV1(Iterable data, {String debugLabel = 'user'}) async {
try {
- await _db.stackEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.stackId)));
+ await _db.batch((batch) {
+ for (final stack in data) {
+ batch.deleteWhere(_db.stackEntity, (row) => row.id.equals(stack.stackId));
+ }
+ });
} catch (error, stack) {
_logger.severe('Error: deleteStacksV1 - $debugLabel', error, stack);
rethrow;
diff --git a/mobile/lib/presentation/pages/download_info.page.dart b/mobile/lib/presentation/pages/download_info.page.dart
new file mode 100644
index 0000000000..e805458e76
--- /dev/null
+++ b/mobile/lib/presentation/pages/download_info.page.dart
@@ -0,0 +1,57 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/extensions/translate_extensions.dart';
+import 'package:immich_mobile/pages/common/download_panel.dart';
+import 'package:immich_mobile/providers/asset_viewer/download.provider.dart';
+
+@RoutePage()
+class DownloadInfoPage extends ConsumerWidget {
+ const DownloadInfoPage({super.key});
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final tasks = ref.watch(downloadStateProvider.select((state) => state.taskProgress)).entries.toList();
+
+ onCancelDownload(String id) {
+ ref.watch(downloadStateProvider.notifier).cancelDownload(id);
+ }
+
+ return Scaffold(
+ appBar: AppBar(
+ title: Text("download".t(context: context)),
+ actions: [],
+ ),
+ body: ListView.builder(
+ physics: const ClampingScrollPhysics(),
+ shrinkWrap: true,
+ itemCount: tasks.length,
+ itemBuilder: (context, index) {
+ final task = tasks[index];
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
+ child: DownloadTaskTile(
+ progress: task.value.progress,
+ fileName: task.value.fileName,
+ status: task.value.status,
+ onCancelDownload: () => onCancelDownload(task.key),
+ ),
+ );
+ },
+ ),
+ persistentFooterButtons: [
+ OutlinedButton(
+ onPressed: () {
+ tasks.map((e) => e.key).forEach(onCancelDownload);
+ },
+ style: OutlinedButton.styleFrom(side: BorderSide(color: context.colorScheme.primary)),
+ child: Text(
+ 'clear_all'.t(context: context),
+ style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.primary),
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/mobile/lib/presentation/pages/search/drift_search.page.dart b/mobile/lib/presentation/pages/search/drift_search.page.dart
index c899dad119..7e70ebf8ff 100644
--- a/mobile/lib/presentation/pages/search/drift_search.page.dart
+++ b/mobile/lib/presentation/pages/search/drift_search.page.dart
@@ -633,7 +633,7 @@ class _SearchResultGrid extends ConsumerWidget {
groupBy: GroupAssetsBy.none,
appBar: null,
bottomSheet: const GeneralBottomSheet(minChildSize: 0.20),
- withScrubber: false,
+ snapToMonth: false,
),
),
),
diff --git a/mobile/lib/presentation/widgets/action_buttons/download_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/download_action_button.widget.dart
index 7c0db5ed9a..cb898f069a 100644
--- a/mobile/lib/presentation/widgets/action_buttons/download_action_button.widget.dart
+++ b/mobile/lib/presentation/widgets/action_buttons/download_action_button.widget.dart
@@ -1,54 +1,45 @@
-import 'package:fluttertoast/fluttertoast.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/domain/utils/background_sync.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
+import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
-import 'package:immich_mobile/widgets/common/immich_toast.dart';
class DownloadActionButton extends ConsumerWidget {
final ActionSource source;
+ final bool menuItem;
+ const DownloadActionButton({super.key, required this.source, this.menuItem = false});
- const DownloadActionButton({super.key, required this.source});
-
- void _onTap(BuildContext context, WidgetRef ref) async {
+ void _onTap(BuildContext context, WidgetRef ref, BackgroundSyncManager backgroundSyncManager) async {
if (!context.mounted) {
return;
}
- final result = await ref.read(actionProvider.notifier).downloadAll(source);
- ref.read(multiSelectProvider.notifier).reset();
+ try {
+ await ref.read(actionProvider.notifier).downloadAll(source);
- if (!context.mounted) {
- return;
- }
-
- if (!result.success) {
- ImmichToast.show(
- context: context,
- msg: 'scaffold_body_error_occurred'.t(context: context),
- gravity: ToastGravity.BOTTOM,
- toastType: ToastType.error,
- );
- } else if (result.count > 0) {
- ImmichToast.show(
- context: context,
- msg: 'download_action_prompt'.t(context: context, args: {'count': result.count.toString()}),
- gravity: ToastGravity.BOTTOM,
- toastType: ToastType.success,
- );
+ Future.delayed(const Duration(seconds: 1), () async {
+ await backgroundSyncManager.syncLocal();
+ await backgroundSyncManager.hashAssets();
+ });
+ } finally {
+ ref.read(multiSelectProvider.notifier).reset();
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
+ final backgroundManager = ref.watch(backgroundSyncProvider);
+
return BaseActionButton(
iconData: Icons.download,
maxWidth: 95,
label: "download".t(context: context),
- onPressed: () => _onTap(context, ref),
+ menuItem: menuItem,
+ onPressed: () => _onTap(context, ref, backgroundManager),
);
}
}
diff --git a/mobile/lib/presentation/widgets/action_buttons/download_status_floating_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/download_status_floating_button.widget.dart
new file mode 100644
index 0000000000..efa7f5c6d0
--- /dev/null
+++ b/mobile/lib/presentation/widgets/action_buttons/download_status_floating_button.widget.dart
@@ -0,0 +1,64 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/providers/asset_viewer/download.provider.dart';
+import 'package:immich_mobile/routing/router.dart';
+
+class DownloadStatusFloatingButton extends ConsumerWidget {
+ const DownloadStatusFloatingButton({super.key});
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final shouldShow = ref.watch(downloadStateProvider.select((state) => state.showProgress));
+ final itemCount = ref.watch(downloadStateProvider.select((state) => state.taskProgress.length));
+ final isDownloading = ref
+ .watch(downloadStateProvider.select((state) => state.taskProgress))
+ .values
+ .where((element) => element.progress != 1)
+ .isNotEmpty;
+
+ return shouldShow
+ ? Badge.count(
+ count: itemCount,
+ textColor: context.colorScheme.onPrimary,
+ backgroundColor: context.colorScheme.primary,
+ child: FloatingActionButton(
+ shape: RoundedRectangleBorder(
+ borderRadius: const BorderRadius.all(Radius.circular(20)),
+ side: BorderSide(color: context.colorScheme.outlineVariant, width: 1),
+ ),
+ backgroundColor: context.isDarkTheme
+ ? context.colorScheme.surfaceContainer
+ : context.colorScheme.surfaceBright,
+ elevation: 2,
+ onPressed: () {
+ context.pushRoute(const DownloadInfoRoute());
+ },
+ child: Stack(
+ alignment: AlignmentDirectional.center,
+ children: [
+ isDownloading
+ ? Icon(Icons.downloading_rounded, color: context.colorScheme.primary, size: 28)
+ : Icon(
+ Icons.download_done,
+ color: context.isDarkTheme ? Colors.green[200] : Colors.green[400],
+ size: 28,
+ ),
+ if (isDownloading)
+ const SizedBox(
+ height: 31,
+ width: 31,
+ child: CircularProgressIndicator(
+ strokeWidth: 2,
+ backgroundColor: Colors.transparent,
+ value: null, // Indeterminate progress
+ ),
+ ),
+ ],
+ ),
+ ),
+ )
+ : const SizedBox.shrink();
+ }
+}
diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart
index 170cb3fdd9..7ac8cf34d5 100644
--- a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart
+++ b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart
@@ -12,6 +12,7 @@ import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/platform_extensions.dart';
import 'package:immich_mobile/extensions/scroll_extensions.dart';
+import 'package:immich_mobile/presentation/widgets/action_buttons/download_status_floating_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.widget.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
@@ -649,20 +650,25 @@ class _AssetViewerState extends ConsumerState {
appBar: const ViewerTopAppBar(),
extendBody: true,
extendBodyBehindAppBar: true,
- body: PhotoViewGallery.builder(
- gaplessPlayback: true,
- loadingBuilder: _placeholderBuilder,
- pageController: pageController,
- scrollPhysics: CurrentPlatform.isIOS
- ? const FastScrollPhysics() // Use bouncing physics for iOS
- : const FastClampingScrollPhysics(), // Use heavy physics for Android
- itemCount: totalAssets,
- onPageChanged: _onPageChanged,
- onPageBuild: _onPageBuild,
- scaleStateChangedCallback: _onScaleStateChanged,
- builder: _assetBuilder,
- backgroundDecoration: BoxDecoration(color: backgroundColor),
- enablePanAlways: true,
+ floatingActionButton: const DownloadStatusFloatingButton(),
+ body: Stack(
+ children: [
+ PhotoViewGallery.builder(
+ gaplessPlayback: true,
+ loadingBuilder: _placeholderBuilder,
+ pageController: pageController,
+ scrollPhysics: CurrentPlatform.isIOS
+ ? const FastScrollPhysics() // Use bouncing physics for iOS
+ : const FastClampingScrollPhysics(), // Use heavy physics for Android
+ itemCount: totalAssets,
+ onPageChanged: _onPageChanged,
+ onPageBuild: _onPageBuild,
+ scaleStateChangedCallback: _onScaleStateChanged,
+ builder: _assetBuilder,
+ backgroundDecoration: BoxDecoration(color: backgroundColor),
+ enablePanAlways: true,
+ ),
+ ],
),
bottomNavigationBar: showingBottomSheet
? const SizedBox.shrink()
diff --git a/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart
index 05492f17e4..0159e04c4e 100644
--- a/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart
+++ b/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart
@@ -8,6 +8,7 @@ import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/cast_action_button.widget.dart';
+import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart';
@@ -56,6 +57,7 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
final isCasting = ref.watch(castProvider.select((c) => c.isCasting));
final actions = [
+ if (asset.hasRemote) const DownloadActionButton(source: ActionSource.viewer, menuItem: true),
if (isCasting || (asset.hasRemote)) const CastActionButton(menuItem: true),
if (album != null && album.isActivityEnabled && album.isShared)
IconButton(
diff --git a/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart b/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart
index 19f393d9fc..58d7f933e9 100644
--- a/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart
+++ b/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart
@@ -31,6 +31,11 @@ class Scrubber extends ConsumerStatefulWidget {
final double? monthSegmentSnappingOffset;
+ final bool snapToMonth;
+
+ /// Whether an app bar is present, affects coordinate calculations
+ final bool hasAppBar;
+
Scrubber({
super.key,
Key? scrollThumbKey,
@@ -39,6 +44,8 @@ class Scrubber extends ConsumerStatefulWidget {
this.topPadding = 0,
this.bottomPadding = 0,
this.monthSegmentSnappingOffset,
+ this.snapToMonth = true,
+ this.hasAppBar = true,
required this.child,
}) : assert(child.scrollDirection == Axis.vertical);
@@ -232,7 +239,7 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi
}
}
- if (_monthCount < kMinMonthsToEnableScrubberSnap) {
+ if (_monthCount < kMinMonthsToEnableScrubberSnap || !widget.snapToMonth) {
// If there are less than kMinMonthsToEnableScrubberSnap months, we don't need to snap to segments
setState(() {
_thumbTopOffset = dragPosition;
@@ -259,14 +266,28 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi
/// - If user drags to global Y position that's 100 pixels from the top
/// - The relative position would be 100 - 50 = 50 (50 pixels into the scrubber area)
double _calculateDragPosition(DragUpdateDetails details) {
+ if (widget.hasAppBar) {
+ final dragAreaTop = widget.topPadding;
+ final dragAreaBottom = widget.timelineHeight - widget.bottomPadding;
+ final dragAreaHeight = dragAreaBottom - dragAreaTop;
+
+ final relativePosition = details.globalPosition.dy - dragAreaTop;
+
+ // Make sure the position stays within the scrubber's bounds
+ return relativePosition.clamp(0.0, dragAreaHeight);
+ }
+
+ // Get the local position relative to the gesture detector
+ final RenderBox? renderBox = context.findRenderObject() as RenderBox?;
+ if (renderBox != null) {
+ final localPosition = renderBox.globalToLocal(details.globalPosition);
+ return localPosition.dy.clamp(0.0, _scrubberHeight);
+ }
+
+ // Fallback to current logic if render box is not available
final dragAreaTop = widget.topPadding;
- final dragAreaBottom = widget.timelineHeight - widget.bottomPadding;
- final dragAreaHeight = dragAreaBottom - dragAreaTop;
-
final relativePosition = details.globalPosition.dy - dragAreaTop;
-
- // Make sure the position stays within the scrubber's bounds
- return relativePosition.clamp(0.0, dragAreaHeight);
+ return relativePosition.clamp(0.0, _scrubberHeight);
}
/// Find the segment closest to the given position
diff --git a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart
index 19c3551e4a..98831403f6 100644
--- a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart
+++ b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart
@@ -14,6 +14,7 @@ import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/presentation/widgets/action_buttons/download_status_floating_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/timeline/scrubber.widget.dart';
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
@@ -38,6 +39,7 @@ class Timeline extends StatelessWidget {
this.bottomSheet = const GeneralBottomSheet(minChildSize: 0.18),
this.groupBy,
this.withScrubber = true,
+ this.snapToMonth = true,
});
final Widget? topSliverWidget;
@@ -48,11 +50,13 @@ class Timeline extends StatelessWidget {
final bool withStack;
final GroupAssetsBy? groupBy;
final bool withScrubber;
+ final bool snapToMonth;
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
+ floatingActionButton: const DownloadStatusFloatingButton(),
body: LayoutBuilder(
builder: (_, constraints) => ProviderScope(
overrides: [
@@ -73,6 +77,7 @@ class Timeline extends StatelessWidget {
appBar: appBar,
bottomSheet: bottomSheet,
withScrubber: withScrubber,
+ snapToMonth: snapToMonth,
),
),
),
@@ -87,6 +92,7 @@ class _SliverTimeline extends ConsumerStatefulWidget {
this.appBar,
this.bottomSheet,
this.withScrubber = true,
+ this.snapToMonth = true,
});
final Widget? topSliverWidget;
@@ -94,6 +100,7 @@ class _SliverTimeline extends ConsumerStatefulWidget {
final Widget? appBar;
final Widget? bottomSheet;
final bool withScrubber;
+ final bool snapToMonth;
@override
ConsumerState createState() => _SliverTimelineState();
@@ -309,11 +316,13 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
final Widget timeline;
if (widget.withScrubber) {
timeline = Scrubber(
+ snapToMonth: widget.snapToMonth,
layoutSegments: segments,
timelineHeight: maxHeight,
topPadding: topPadding,
bottomPadding: bottomPadding,
monthSegmentSnappingOffset: widget.topSliverWidgetHeight ?? 0 + appBarExpandedHeight,
+ hasAppBar: widget.appBar != null,
child: grid,
);
} else {
diff --git a/mobile/lib/providers/infrastructure/action.provider.dart b/mobile/lib/providers/infrastructure/action.provider.dart
index 03e2dfc6d5..f62791fb57 100644
--- a/mobile/lib/providers/infrastructure/action.provider.dart
+++ b/mobile/lib/providers/infrastructure/action.provider.dart
@@ -356,7 +356,6 @@ class ActionNotifier extends Notifier {
Future downloadAll(ActionSource source) async {
final assets = _getAssets(source).whereType().toList(growable: false);
-
try {
final didEnqueue = await _service.downloadAll(assets);
final enqueueCount = didEnqueue.where((e) => e).length;
diff --git a/mobile/lib/repositories/download.repository.dart b/mobile/lib/repositories/download.repository.dart
index c4b31e9d93..1ac9410fc6 100644
--- a/mobile/lib/repositories/download.repository.dart
+++ b/mobile/lib/repositories/download.repository.dart
@@ -90,7 +90,11 @@ class DownloadRepository {
final isVideo = asset.isVideo;
final url = getOriginalUrlForRemoteId(id);
- if (Platform.isAndroid || livePhotoVideoId == null || isVideo) {
+ // on iOS it cannot link the image, check if the filename has .MP extension
+ // to avoid downloading the video part
+ final isAndroidMotionPhoto = asset.name.contains(".MP");
+
+ if (Platform.isAndroid || livePhotoVideoId == null || isVideo || isAndroidMotionPhoto) {
tasks[taskIndex++] = DownloadTask(
taskId: id,
url: url,
diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart
index cdf384fcf8..7554c7b1cf 100644
--- a/mobile/lib/routing/router.dart
+++ b/mobile/lib/routing/router.dart
@@ -81,6 +81,7 @@ import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
import 'package:immich_mobile/presentation/pages/dev/feat_in_development.page.dart';
import 'package:immich_mobile/presentation/pages/dev/main_timeline.page.dart';
import 'package:immich_mobile/presentation/pages/dev/media_stat.page.dart';
+import 'package:immich_mobile/presentation/pages/download_info.page.dart';
import 'package:immich_mobile/presentation/pages/drift_activities.page.dart';
import 'package:immich_mobile/presentation/pages/drift_album.page.dart';
import 'package:immich_mobile/presentation/pages/drift_album_options.page.dart';
@@ -345,6 +346,7 @@ class AppRouter extends RootStackRouter {
AutoRoute(page: DriftActivitiesRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: DriftBackupAssetDetailRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: AssetTroubleshootRoute.page, guards: [_authGuard, _duplicateGuard]),
+ AutoRoute(page: DownloadInfoRoute.page, guards: [_authGuard, _duplicateGuard]),
// required to handle all deeplinks in deep_link.service.dart
// auto_route_library#1722
RedirectRoute(path: '*', redirectTo: '/'),
diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart
index 981828acf1..4e488a30c7 100644
--- a/mobile/lib/routing/router.gr.dart
+++ b/mobile/lib/routing/router.gr.dart
@@ -688,6 +688,22 @@ class CropImageRouteArgs {
}
}
+/// generated route for
+/// [DownloadInfoPage]
+class DownloadInfoRoute extends PageRouteInfo {
+ const DownloadInfoRoute({List? children})
+ : super(DownloadInfoRoute.name, initialChildren: children);
+
+ static const String name = 'DownloadInfoRoute';
+
+ static PageInfo page = PageInfo(
+ name,
+ builder: (data) {
+ return const DownloadInfoPage();
+ },
+ );
+}
+
/// generated route for
/// [DriftActivitiesPage]
class DriftActivitiesRoute extends PageRouteInfo {
diff --git a/mobile/lib/services/action.service.dart b/mobile/lib/services/action.service.dart
index 9a12745acd..4edd49fec2 100644
--- a/mobile/lib/services/action.service.dart
+++ b/mobile/lib/services/action.service.dart
@@ -1,7 +1,6 @@
-import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/repositories/download.repository.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
@@ -11,6 +10,7 @@ import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/repositories/asset_api.repository.dart';
import 'package:immich_mobile/repositories/asset_media.repository.dart';
+import 'package:immich_mobile/repositories/download.repository.dart';
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/common/date_time_picker.dart';
@@ -199,14 +199,11 @@ class ActionService {
}
Future removeFromAlbum(List remoteIds, String albumId) async {
- int removedCount = 0;
final result = await _albumApiRepository.removeAssets(albumId, remoteIds);
-
if (result.removed.isNotEmpty) {
- removedCount = await _remoteAlbumRepository.removeAssets(albumId, result.removed);
+ await _remoteAlbumRepository.removeAssets(albumId, result.removed);
}
-
- return removedCount;
+ return result.removed.length;
}
Future updateDescription(String assetId, String description) async {
diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md
index 239c62449f..7a426b74fc 100644
--- a/mobile/openapi/README.md
+++ b/mobile/openapi/README.md
@@ -393,6 +393,7 @@ Class | Method | HTTP request | Description
- [LoginCredentialDto](doc//LoginCredentialDto.md)
- [LoginResponseDto](doc//LoginResponseDto.md)
- [LogoutResponseDto](doc//LogoutResponseDto.md)
+ - [MachineLearningAvailabilityChecksDto](doc//MachineLearningAvailabilityChecksDto.md)
- [ManualJobName](doc//ManualJobName.md)
- [MapMarkerResponseDto](doc//MapMarkerResponseDto.md)
- [MapReverseGeocodeResponseDto](doc//MapReverseGeocodeResponseDto.md)
diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart
index e87c160d96..df2c2226b1 100644
--- a/mobile/openapi/lib/api.dart
+++ b/mobile/openapi/lib/api.dart
@@ -164,6 +164,7 @@ part 'model/log_level.dart';
part 'model/login_credential_dto.dart';
part 'model/login_response_dto.dart';
part 'model/logout_response_dto.dart';
+part 'model/machine_learning_availability_checks_dto.dart';
part 'model/manual_job_name.dart';
part 'model/map_marker_response_dto.dart';
part 'model/map_reverse_geocode_response_dto.dart';
diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart
index ae5fd9227b..06d27593c9 100644
--- a/mobile/openapi/lib/api_client.dart
+++ b/mobile/openapi/lib/api_client.dart
@@ -382,6 +382,8 @@ class ApiClient {
return LoginResponseDto.fromJson(value);
case 'LogoutResponseDto':
return LogoutResponseDto.fromJson(value);
+ case 'MachineLearningAvailabilityChecksDto':
+ return MachineLearningAvailabilityChecksDto.fromJson(value);
case 'ManualJobName':
return ManualJobNameTypeTransformer().decode(value);
case 'MapMarkerResponseDto':
diff --git a/mobile/openapi/lib/model/machine_learning_availability_checks_dto.dart b/mobile/openapi/lib/model/machine_learning_availability_checks_dto.dart
new file mode 100644
index 0000000000..84b3181426
--- /dev/null
+++ b/mobile/openapi/lib/model/machine_learning_availability_checks_dto.dart
@@ -0,0 +1,115 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.18
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class MachineLearningAvailabilityChecksDto {
+ /// Returns a new [MachineLearningAvailabilityChecksDto] instance.
+ MachineLearningAvailabilityChecksDto({
+ required this.enabled,
+ required this.interval,
+ required this.timeout,
+ });
+
+ bool enabled;
+
+ num interval;
+
+ num timeout;
+
+ @override
+ bool operator ==(Object other) => identical(this, other) || other is MachineLearningAvailabilityChecksDto &&
+ other.enabled == enabled &&
+ other.interval == interval &&
+ other.timeout == timeout;
+
+ @override
+ int get hashCode =>
+ // ignore: unnecessary_parenthesis
+ (enabled.hashCode) +
+ (interval.hashCode) +
+ (timeout.hashCode);
+
+ @override
+ String toString() => 'MachineLearningAvailabilityChecksDto[enabled=$enabled, interval=$interval, timeout=$timeout]';
+
+ Map toJson() {
+ final json = {};
+ json[r'enabled'] = this.enabled;
+ json[r'interval'] = this.interval;
+ json[r'timeout'] = this.timeout;
+ return json;
+ }
+
+ /// Returns a new [MachineLearningAvailabilityChecksDto] instance and imports its values from
+ /// [value] if it's a [Map], null otherwise.
+ // ignore: prefer_constructors_over_static_methods
+ static MachineLearningAvailabilityChecksDto? fromJson(dynamic value) {
+ upgradeDto(value, "MachineLearningAvailabilityChecksDto");
+ if (value is Map) {
+ final json = value.cast();
+
+ return MachineLearningAvailabilityChecksDto(
+ enabled: mapValueOfType(json, r'enabled')!,
+ interval: num.parse('${json[r'interval']}'),
+ timeout: num.parse('${json[r'timeout']}'),
+ );
+ }
+ return null;
+ }
+
+ static List listFromJson(dynamic json, {bool growable = false,}) {
+ final result = [];
+ if (json is List && json.isNotEmpty) {
+ for (final row in json) {
+ final value = MachineLearningAvailabilityChecksDto.fromJson(row);
+ if (value != null) {
+ result.add(value);
+ }
+ }
+ }
+ return result.toList(growable: growable);
+ }
+
+ static Map mapFromJson(dynamic json) {
+ final map = {};
+ if (json is Map && json.isNotEmpty) {
+ json = json.cast(); // ignore: parameter_assignments
+ for (final entry in json.entries) {
+ final value = MachineLearningAvailabilityChecksDto.fromJson(entry.value);
+ if (value != null) {
+ map[entry.key] = value;
+ }
+ }
+ }
+ return map;
+ }
+
+ // maps a json object with a list of MachineLearningAvailabilityChecksDto-objects as value to a dart map
+ static Map> mapListFromJson(dynamic json, {bool growable = false,}) {
+ final map = >{};
+ if (json is Map && json.isNotEmpty) {
+ // ignore: parameter_assignments
+ json = json.cast();
+ for (final entry in json.entries) {
+ map[entry.key] = MachineLearningAvailabilityChecksDto.listFromJson(entry.value, growable: growable,);
+ }
+ }
+ return map;
+ }
+
+ /// The list of required keys that must be present in a JSON.
+ static const requiredKeys = {
+ 'enabled',
+ 'interval',
+ 'timeout',
+ };
+}
+
diff --git a/mobile/openapi/lib/model/system_config_machine_learning_dto.dart b/mobile/openapi/lib/model/system_config_machine_learning_dto.dart
index a4a9ca7d82..d7b2566d59 100644
--- a/mobile/openapi/lib/model/system_config_machine_learning_dto.dart
+++ b/mobile/openapi/lib/model/system_config_machine_learning_dto.dart
@@ -13,14 +13,16 @@ part of openapi.api;
class SystemConfigMachineLearningDto {
/// Returns a new [SystemConfigMachineLearningDto] instance.
SystemConfigMachineLearningDto({
+ required this.availabilityChecks,
required this.clip,
required this.duplicateDetection,
required this.enabled,
required this.facialRecognition,
- this.url,
this.urls = const [],
});
+ MachineLearningAvailabilityChecksDto availabilityChecks;
+
CLIPConfig clip;
DuplicateDetectionConfig duplicateDetection;
@@ -29,50 +31,37 @@ class SystemConfigMachineLearningDto {
FacialRecognitionConfig facialRecognition;
- /// This property was deprecated in v1.122.0
- ///
- /// Please note: This property should have been non-nullable! Since the specification file
- /// does not include a default value (using the "default:" property), however, the generated
- /// source code must fall back to having a nullable type.
- /// Consider adding a "default:" property in the specification file to hide this note.
- ///
- String? url;
-
List urls;
@override
bool operator ==(Object other) => identical(this, other) || other is SystemConfigMachineLearningDto &&
+ other.availabilityChecks == availabilityChecks &&
other.clip == clip &&
other.duplicateDetection == duplicateDetection &&
other.enabled == enabled &&
other.facialRecognition == facialRecognition &&
- other.url == url &&
_deepEquality.equals(other.urls, urls);
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
+ (availabilityChecks.hashCode) +
(clip.hashCode) +
(duplicateDetection.hashCode) +
(enabled.hashCode) +
(facialRecognition.hashCode) +
- (url == null ? 0 : url!.hashCode) +
(urls.hashCode);
@override
- String toString() => 'SystemConfigMachineLearningDto[clip=$clip, duplicateDetection=$duplicateDetection, enabled=$enabled, facialRecognition=$facialRecognition, url=$url, urls=$urls]';
+ String toString() => 'SystemConfigMachineLearningDto[availabilityChecks=$availabilityChecks, clip=$clip, duplicateDetection=$duplicateDetection, enabled=$enabled, facialRecognition=$facialRecognition, urls=$urls]';
Map toJson() {
final json = {};
+ json[r'availabilityChecks'] = this.availabilityChecks;
json[r'clip'] = this.clip;
json[r'duplicateDetection'] = this.duplicateDetection;
json[r'enabled'] = this.enabled;
json[r'facialRecognition'] = this.facialRecognition;
- if (this.url != null) {
- json[r'url'] = this.url;
- } else {
- // json[r'url'] = null;
- }
json[r'urls'] = this.urls;
return json;
}
@@ -86,11 +75,11 @@ class SystemConfigMachineLearningDto {
final json = value.cast();
return SystemConfigMachineLearningDto(
+ availabilityChecks: MachineLearningAvailabilityChecksDto.fromJson(json[r'availabilityChecks'])!,
clip: CLIPConfig.fromJson(json[r'clip'])!,
duplicateDetection: DuplicateDetectionConfig.fromJson(json[r'duplicateDetection'])!,
enabled: mapValueOfType(json, r'enabled')!,
facialRecognition: FacialRecognitionConfig.fromJson(json[r'facialRecognition'])!,
- url: mapValueOfType(json, r'url'),
urls: json[r'urls'] is Iterable
? (json[r'urls'] as Iterable).cast().toList(growable: false)
: const [],
@@ -141,6 +130,7 @@ class SystemConfigMachineLearningDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = {
+ 'availabilityChecks',
'clip',
'duplicateDetection',
'enabled',
diff --git a/mobile/test/drift/main/generated/schema.dart b/mobile/test/drift/main/generated/schema.dart
index 76573e4997..1d78a44317 100644
--- a/mobile/test/drift/main/generated/schema.dart
+++ b/mobile/test/drift/main/generated/schema.dart
@@ -13,6 +13,7 @@ import 'schema_v7.dart' as v7;
import 'schema_v8.dart' as v8;
import 'schema_v9.dart' as v9;
import 'schema_v10.dart' as v10;
+import 'schema_v11.dart' as v11;
class GeneratedHelper implements SchemaInstantiationHelper {
@override
@@ -38,10 +39,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v9.DatabaseAtV9(db);
case 10:
return v10.DatabaseAtV10(db);
+ case 11:
+ return v11.DatabaseAtV11(db);
default:
throw MissingSchemaException(version, versions);
}
}
- static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+ static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
}
diff --git a/mobile/test/drift/main/generated/schema_v11.dart b/mobile/test/drift/main/generated/schema_v11.dart
new file mode 100644
index 0000000000..fe27f1bb3d
--- /dev/null
+++ b/mobile/test/drift/main/generated/schema_v11.dart
@@ -0,0 +1,7198 @@
+// dart format width=80
+// GENERATED CODE, DO NOT EDIT BY HAND.
+// ignore_for_file: type=lint
+import 'package:drift/drift.dart';
+
+class UserEntity extends Table with TableInfo {
+ @override
+ final GeneratedDatabase attachedDatabase;
+ final String? _alias;
+ UserEntity(this.attachedDatabase, [this._alias]);
+ late final GeneratedColumn id = GeneratedColumn(
+ 'id',
+ aliasedName,
+ false,
+ type: DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
+ late final GeneratedColumn name = GeneratedColumn(
+ 'name',
+ aliasedName,
+ false,
+ type: DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
+ late final GeneratedColumn email = GeneratedColumn(
+ 'email',
+ aliasedName,
+ false,
+ type: DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
+ late final GeneratedColumn hasProfileImage = GeneratedColumn(
+ 'has_profile_image',
+ aliasedName,
+ false,
+ type: DriftSqlType.bool,
+ requiredDuringInsert: false,
+ defaultConstraints: GeneratedColumn.constraintIsAlways(
+ 'CHECK ("has_profile_image" IN (0, 1))',
+ ),
+ defaultValue: const CustomExpression('0'),
+ );
+ late final GeneratedColumn profileChangedAt =
+ GeneratedColumn(
+ 'profile_changed_at',
+ aliasedName,
+ false,
+ type: DriftSqlType.dateTime,
+ requiredDuringInsert: false,
+ defaultValue: const CustomExpression('CURRENT_TIMESTAMP'),
+ );
+ late final GeneratedColumn avatarColor = GeneratedColumn(
+ 'avatar_color',
+ aliasedName,
+ false,
+ type: DriftSqlType.int,
+ requiredDuringInsert: false,
+ defaultValue: const CustomExpression('0'),
+ );
+ @override
+ List get $columns => [
+ id,
+ name,
+ email,
+ hasProfileImage,
+ profileChangedAt,
+ avatarColor,
+ ];
+ @override
+ String get aliasedName => _alias ?? actualTableName;
+ @override
+ String get actualTableName => $name;
+ static const String $name = 'user_entity';
+ @override
+ Set get $primaryKey => {id};
+ @override
+ UserEntityData map(Map data, {String? tablePrefix}) {
+ final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
+ return UserEntityData(
+ id: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}id'],
+ )!,
+ name: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}name'],
+ )!,
+ email: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}email'],
+ )!,
+ hasProfileImage: attachedDatabase.typeMapping.read(
+ DriftSqlType.bool,
+ data['${effectivePrefix}has_profile_image'],
+ )!,
+ profileChangedAt: attachedDatabase.typeMapping.read(
+ DriftSqlType.dateTime,
+ data['${effectivePrefix}profile_changed_at'],
+ )!,
+ avatarColor: attachedDatabase.typeMapping.read(
+ DriftSqlType.int,
+ data['${effectivePrefix}avatar_color'],
+ )!,
+ );
+ }
+
+ @override
+ UserEntity createAlias(String alias) {
+ return UserEntity(attachedDatabase, alias);
+ }
+
+ @override
+ bool get withoutRowId => true;
+ @override
+ bool get isStrict => true;
+}
+
+class UserEntityData extends DataClass implements Insertable {
+ final String id;
+ final String name;
+ final String email;
+ final bool hasProfileImage;
+ final DateTime profileChangedAt;
+ final int avatarColor;
+ const UserEntityData({
+ required this.id,
+ required this.name,
+ required this.email,
+ required this.hasProfileImage,
+ required this.profileChangedAt,
+ required this.avatarColor,
+ });
+ @override
+ Map toColumns(bool nullToAbsent) {
+ final map = {};
+ map['id'] = Variable(id);
+ map['name'] = Variable(name);
+ map['email'] = Variable(email);
+ map['has_profile_image'] = Variable(hasProfileImage);
+ map['profile_changed_at'] = Variable(profileChangedAt);
+ map['avatar_color'] = Variable(avatarColor);
+ return map;
+ }
+
+ factory UserEntityData.fromJson(
+ Map json, {
+ ValueSerializer? serializer,
+ }) {
+ serializer ??= driftRuntimeOptions.defaultSerializer;
+ return UserEntityData(
+ id: serializer.fromJson(json['id']),
+ name: serializer.fromJson(json['name']),
+ email: serializer.fromJson(json['email']),
+ hasProfileImage: serializer.fromJson(json['hasProfileImage']),
+ profileChangedAt: serializer.fromJson(json['profileChangedAt']),
+ avatarColor: serializer.fromJson(json['avatarColor']),
+ );
+ }
+ @override
+ Map toJson({ValueSerializer? serializer}) {
+ serializer ??= driftRuntimeOptions.defaultSerializer;
+ return {
+ 'id': serializer.toJson(id),
+ 'name': serializer.toJson(name),
+ 'email': serializer.toJson(email),
+ 'hasProfileImage': serializer.toJson(hasProfileImage),
+ 'profileChangedAt': serializer.toJson(profileChangedAt),
+ 'avatarColor': serializer.toJson(avatarColor),
+ };
+ }
+
+ UserEntityData copyWith({
+ String? id,
+ String? name,
+ String? email,
+ bool? hasProfileImage,
+ DateTime? profileChangedAt,
+ int? avatarColor,
+ }) => UserEntityData(
+ id: id ?? this.id,
+ name: name ?? this.name,
+ email: email ?? this.email,
+ hasProfileImage: hasProfileImage ?? this.hasProfileImage,
+ profileChangedAt: profileChangedAt ?? this.profileChangedAt,
+ avatarColor: avatarColor ?? this.avatarColor,
+ );
+ UserEntityData copyWithCompanion(UserEntityCompanion data) {
+ return UserEntityData(
+ id: data.id.present ? data.id.value : this.id,
+ name: data.name.present ? data.name.value : this.name,
+ email: data.email.present ? data.email.value : this.email,
+ hasProfileImage: data.hasProfileImage.present
+ ? data.hasProfileImage.value
+ : this.hasProfileImage,
+ profileChangedAt: data.profileChangedAt.present
+ ? data.profileChangedAt.value
+ : this.profileChangedAt,
+ avatarColor: data.avatarColor.present
+ ? data.avatarColor.value
+ : this.avatarColor,
+ );
+ }
+
+ @override
+ String toString() {
+ return (StringBuffer('UserEntityData(')
+ ..write('id: $id, ')
+ ..write('name: $name, ')
+ ..write('email: $email, ')
+ ..write('hasProfileImage: $hasProfileImage, ')
+ ..write('profileChangedAt: $profileChangedAt, ')
+ ..write('avatarColor: $avatarColor')
+ ..write(')'))
+ .toString();
+ }
+
+ @override
+ int get hashCode => Object.hash(
+ id,
+ name,
+ email,
+ hasProfileImage,
+ profileChangedAt,
+ avatarColor,
+ );
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ (other is UserEntityData &&
+ other.id == this.id &&
+ other.name == this.name &&
+ other.email == this.email &&
+ other.hasProfileImage == this.hasProfileImage &&
+ other.profileChangedAt == this.profileChangedAt &&
+ other.avatarColor == this.avatarColor);
+}
+
+class UserEntityCompanion extends UpdateCompanion {
+ final Value id;
+ final Value name;
+ final Value email;
+ final Value hasProfileImage;
+ final Value profileChangedAt;
+ final Value avatarColor;
+ const UserEntityCompanion({
+ this.id = const Value.absent(),
+ this.name = const Value.absent(),
+ this.email = const Value.absent(),
+ this.hasProfileImage = const Value.absent(),
+ this.profileChangedAt = const Value.absent(),
+ this.avatarColor = const Value.absent(),
+ });
+ UserEntityCompanion.insert({
+ required String id,
+ required String name,
+ required String email,
+ this.hasProfileImage = const Value.absent(),
+ this.profileChangedAt = const Value.absent(),
+ this.avatarColor = const Value.absent(),
+ }) : id = Value(id),
+ name = Value(name),
+ email = Value(email);
+ static Insertable custom({
+ Expression? id,
+ Expression? name,
+ Expression? email,
+ Expression? hasProfileImage,
+ Expression? profileChangedAt,
+ Expression? avatarColor,
+ }) {
+ return RawValuesInsertable({
+ if (id != null) 'id': id,
+ if (name != null) 'name': name,
+ if (email != null) 'email': email,
+ if (hasProfileImage != null) 'has_profile_image': hasProfileImage,
+ if (profileChangedAt != null) 'profile_changed_at': profileChangedAt,
+ if (avatarColor != null) 'avatar_color': avatarColor,
+ });
+ }
+
+ UserEntityCompanion copyWith({
+ Value? id,
+ Value? name,
+ Value? email,
+ Value? hasProfileImage,
+ Value? profileChangedAt,
+ Value? avatarColor,
+ }) {
+ return UserEntityCompanion(
+ id: id ?? this.id,
+ name: name ?? this.name,
+ email: email ?? this.email,
+ hasProfileImage: hasProfileImage ?? this.hasProfileImage,
+ profileChangedAt: profileChangedAt ?? this.profileChangedAt,
+ avatarColor: avatarColor ?? this.avatarColor,
+ );
+ }
+
+ @override
+ Map toColumns(bool nullToAbsent) {
+ final map = {};
+ if (id.present) {
+ map['id'] = Variable(id.value);
+ }
+ if (name.present) {
+ map['name'] = Variable(name.value);
+ }
+ if (email.present) {
+ map['email'] = Variable(email.value);
+ }
+ if (hasProfileImage.present) {
+ map['has_profile_image'] = Variable(hasProfileImage.value);
+ }
+ if (profileChangedAt.present) {
+ map['profile_changed_at'] = Variable(profileChangedAt.value);
+ }
+ if (avatarColor.present) {
+ map['avatar_color'] = Variable(avatarColor.value);
+ }
+ return map;
+ }
+
+ @override
+ String toString() {
+ return (StringBuffer('UserEntityCompanion(')
+ ..write('id: $id, ')
+ ..write('name: $name, ')
+ ..write('email: $email, ')
+ ..write('hasProfileImage: $hasProfileImage, ')
+ ..write('profileChangedAt: $profileChangedAt, ')
+ ..write('avatarColor: $avatarColor')
+ ..write(')'))
+ .toString();
+ }
+}
+
+class RemoteAssetEntity extends Table
+ with TableInfo {
+ @override
+ final GeneratedDatabase attachedDatabase;
+ final String? _alias;
+ RemoteAssetEntity(this.attachedDatabase, [this._alias]);
+ late final GeneratedColumn name = GeneratedColumn(
+ 'name',
+ aliasedName,
+ false,
+ type: DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
+ late final GeneratedColumn type = GeneratedColumn(
+ 'type',
+ aliasedName,
+ false,
+ type: DriftSqlType.int,
+ requiredDuringInsert: true,
+ );
+ late final GeneratedColumn createdAt = GeneratedColumn(
+ 'created_at',
+ aliasedName,
+ false,
+ type: DriftSqlType.dateTime,
+ requiredDuringInsert: false,
+ defaultValue: const CustomExpression('CURRENT_TIMESTAMP'),
+ );
+ late final GeneratedColumn updatedAt = GeneratedColumn(
+ 'updated_at',
+ aliasedName,
+ false,
+ type: DriftSqlType.dateTime,
+ requiredDuringInsert: false,
+ defaultValue: const CustomExpression('CURRENT_TIMESTAMP'),
+ );
+ late final GeneratedColumn width = GeneratedColumn(
+ 'width',
+ aliasedName,
+ true,
+ type: DriftSqlType.int,
+ requiredDuringInsert: false,
+ );
+ late final GeneratedColumn height = GeneratedColumn(
+ 'height',
+ aliasedName,
+ true,
+ type: DriftSqlType.int,
+ requiredDuringInsert: false,
+ );
+ late final GeneratedColumn durationInSeconds = GeneratedColumn(
+ 'duration_in_seconds',
+ aliasedName,
+ true,
+ type: DriftSqlType.int,
+ requiredDuringInsert: false,
+ );
+ late final GeneratedColumn id = GeneratedColumn(
+ 'id',
+ aliasedName,
+ false,
+ type: DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
+ late final GeneratedColumn checksum = GeneratedColumn(
+ 'checksum',
+ aliasedName,
+ false,
+ type: DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
+ late final GeneratedColumn isFavorite = GeneratedColumn(
+ 'is_favorite',
+ aliasedName,
+ false,
+ type: DriftSqlType.bool,
+ requiredDuringInsert: false,
+ defaultConstraints: GeneratedColumn.constraintIsAlways(
+ 'CHECK ("is_favorite" IN (0, 1))',
+ ),
+ defaultValue: const CustomExpression('0'),
+ );
+ late final GeneratedColumn ownerId = GeneratedColumn(
+ 'owner_id',
+ aliasedName,
+ false,
+ type: DriftSqlType.string,
+ requiredDuringInsert: true,
+ defaultConstraints: GeneratedColumn.constraintIsAlways(
+ 'REFERENCES user_entity (id) ON DELETE CASCADE',
+ ),
+ );
+ late final GeneratedColumn localDateTime =
+ GeneratedColumn(
+ 'local_date_time',
+ aliasedName,
+ true,
+ type: DriftSqlType.dateTime,
+ requiredDuringInsert: false,
+ );
+ late final GeneratedColumn thumbHash = GeneratedColumn(
+ 'thumb_hash',
+ aliasedName,
+ true,
+ type: DriftSqlType.string,
+ requiredDuringInsert: false,
+ );
+ late final GeneratedColumn deletedAt = GeneratedColumn(
+ 'deleted_at',
+ aliasedName,
+ true,
+ type: DriftSqlType.dateTime,
+ requiredDuringInsert: false,
+ );
+ late final GeneratedColumn livePhotoVideoId = GeneratedColumn(
+ 'live_photo_video_id',
+ aliasedName,
+ true,
+ type: DriftSqlType.string,
+ requiredDuringInsert: false,
+ );
+ late final GeneratedColumn visibility = GeneratedColumn(
+ 'visibility',
+ aliasedName,
+ false,
+ type: DriftSqlType.int,
+ requiredDuringInsert: true,
+ );
+ late final GeneratedColumn stackId = GeneratedColumn(
+ 'stack_id',
+ aliasedName,
+ true,
+ type: DriftSqlType.string,
+ requiredDuringInsert: false,
+ );
+ late final GeneratedColumn libraryId = GeneratedColumn(
+ 'library_id',
+ aliasedName,
+ true,
+ type: DriftSqlType.string,
+ requiredDuringInsert: false,
+ );
+ @override
+ List get $columns => [
+ name,
+ type,
+ createdAt,
+ updatedAt,
+ width,
+ height,
+ durationInSeconds,
+ id,
+ checksum,
+ isFavorite,
+ ownerId,
+ localDateTime,
+ thumbHash,
+ deletedAt,
+ livePhotoVideoId,
+ visibility,
+ stackId,
+ libraryId,
+ ];
+ @override
+ String get aliasedName => _alias ?? actualTableName;
+ @override
+ String get actualTableName => $name;
+ static const String $name = 'remote_asset_entity';
+ @override
+ Set get $primaryKey => {id};
+ @override
+ RemoteAssetEntityData map(Map data, {String? tablePrefix}) {
+ final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
+ return RemoteAssetEntityData(
+ name: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}name'],
+ )!,
+ type: attachedDatabase.typeMapping.read(
+ DriftSqlType.int,
+ data['${effectivePrefix}type'],
+ )!,
+ createdAt: attachedDatabase.typeMapping.read(
+ DriftSqlType.dateTime,
+ data['${effectivePrefix}created_at'],
+ )!,
+ updatedAt: attachedDatabase.typeMapping.read(
+ DriftSqlType.dateTime,
+ data['${effectivePrefix}updated_at'],
+ )!,
+ width: attachedDatabase.typeMapping.read(
+ DriftSqlType.int,
+ data['${effectivePrefix}width'],
+ ),
+ height: attachedDatabase.typeMapping.read(
+ DriftSqlType.int,
+ data['${effectivePrefix}height'],
+ ),
+ durationInSeconds: attachedDatabase.typeMapping.read(
+ DriftSqlType.int,
+ data['${effectivePrefix}duration_in_seconds'],
+ ),
+ id: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}id'],
+ )!,
+ checksum: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}checksum'],
+ )!,
+ isFavorite: attachedDatabase.typeMapping.read(
+ DriftSqlType.bool,
+ data['${effectivePrefix}is_favorite'],
+ )!,
+ ownerId: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}owner_id'],
+ )!,
+ localDateTime: attachedDatabase.typeMapping.read(
+ DriftSqlType.dateTime,
+ data['${effectivePrefix}local_date_time'],
+ ),
+ thumbHash: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}thumb_hash'],
+ ),
+ deletedAt: attachedDatabase.typeMapping.read(
+ DriftSqlType.dateTime,
+ data['${effectivePrefix}deleted_at'],
+ ),
+ livePhotoVideoId: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}live_photo_video_id'],
+ ),
+ visibility: attachedDatabase.typeMapping.read(
+ DriftSqlType.int,
+ data['${effectivePrefix}visibility'],
+ )!,
+ stackId: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}stack_id'],
+ ),
+ libraryId: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}library_id'],
+ ),
+ );
+ }
+
+ @override
+ RemoteAssetEntity createAlias(String alias) {
+ return RemoteAssetEntity(attachedDatabase, alias);
+ }
+
+ @override
+ bool get withoutRowId => true;
+ @override
+ bool get isStrict => true;
+}
+
+class RemoteAssetEntityData extends DataClass
+ implements Insertable {
+ final String name;
+ final int type;
+ final DateTime createdAt;
+ final DateTime updatedAt;
+ final int? width;
+ final int? height;
+ final int? durationInSeconds;
+ final String id;
+ final String checksum;
+ final bool isFavorite;
+ final String ownerId;
+ final DateTime? localDateTime;
+ final String? thumbHash;
+ final DateTime? deletedAt;
+ final String? livePhotoVideoId;
+ final int visibility;
+ final String? stackId;
+ final String? libraryId;
+ const RemoteAssetEntityData({
+ required this.name,
+ required this.type,
+ required this.createdAt,
+ required this.updatedAt,
+ this.width,
+ this.height,
+ this.durationInSeconds,
+ required this.id,
+ required this.checksum,
+ required this.isFavorite,
+ required this.ownerId,
+ this.localDateTime,
+ this.thumbHash,
+ this.deletedAt,
+ this.livePhotoVideoId,
+ required this.visibility,
+ this.stackId,
+ this.libraryId,
+ });
+ @override
+ Map toColumns(bool nullToAbsent) {
+ final map = {};
+ map['name'] = Variable(name);
+ map['type'] = Variable(type);
+ map['created_at'] = Variable(createdAt);
+ map['updated_at'] = Variable(updatedAt);
+ if (!nullToAbsent || width != null) {
+ map['width'] = Variable(width);
+ }
+ if (!nullToAbsent || height != null) {
+ map['height'] = Variable(height);
+ }
+ if (!nullToAbsent || durationInSeconds != null) {
+ map['duration_in_seconds'] = Variable(durationInSeconds);
+ }
+ map['id'] = Variable(id);
+ map['checksum'] = Variable(checksum);
+ map['is_favorite'] = Variable(isFavorite);
+ map['owner_id'] = Variable(ownerId);
+ if (!nullToAbsent || localDateTime != null) {
+ map['local_date_time'] = Variable(localDateTime);
+ }
+ if (!nullToAbsent || thumbHash != null) {
+ map['thumb_hash'] = Variable