diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/platform/messages.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/platform/messages.g.kt new file mode 100644 index 0000000000..49b165de70 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/platform/messages.g.kt @@ -0,0 +1,278 @@ +// Autogenerated from Pigeon (v25.3.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer +private object MessagesPigeonUtils { + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf( + exception.code, + exception.message, + exception.details + ) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) + } + } + fun deepEquals(a: Any?, b: Any?): Boolean { + if (a is ByteArray && b is ByteArray) { + return a.contentEquals(b) + } + if (a is IntArray && b is IntArray) { + return a.contentEquals(b) + } + if (a is LongArray && b is LongArray) { + return a.contentEquals(b) + } + if (a is DoubleArray && b is DoubleArray) { + return a.contentEquals(b) + } + if (a is Array<*> && b is Array<*>) { + return a.size == b.size && + a.indices.all{ deepEquals(a[it], b[it]) } + } + if (a is List<*> && b is List<*>) { + return a.size == b.size && + a.indices.all{ deepEquals(a[it], b[it]) } + } + if (a is Map<*, *> && b is Map<*, *>) { + return a.size == b.size && a.all { + (b as Map).containsKey(it.key) && + deepEquals(it.value, b[it.key]) + } + } + return a == b + } + +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null +) : Throwable() + +/** Generated class from Pigeon that represents data sent in messages. */ +data class Asset ( + val id: String, + val name: String, + val type: Long, + val createdAt: String? = null, + val updatedAt: String? = null, + val durationInSeconds: Long, + val albumIds: List +) + { + companion object { + fun fromList(pigeonVar_list: List): Asset { + val id = pigeonVar_list[0] as String + val name = pigeonVar_list[1] as String + val type = pigeonVar_list[2] as Long + val createdAt = pigeonVar_list[3] as String? + val updatedAt = pigeonVar_list[4] as String? + val durationInSeconds = pigeonVar_list[5] as Long + val albumIds = pigeonVar_list[6] as List + return Asset(id, name, type, createdAt, updatedAt, durationInSeconds, albumIds) + } + } + fun toList(): List { + return listOf( + id, + name, + type, + createdAt, + updatedAt, + durationInSeconds, + albumIds, + ) + } + override fun equals(other: Any?): Boolean { + if (other !is Asset) { + return false + } + if (this === other) { + return true + } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } + + override fun hashCode(): Int = toList().hashCode() +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class SyncDelta ( + val updates: List, + val deletes: List +) + { + companion object { + fun fromList(pigeonVar_list: List): SyncDelta { + val updates = pigeonVar_list[0] as List + val deletes = pigeonVar_list[1] as List + return SyncDelta(updates, deletes) + } + } + fun toList(): List { + return listOf( + updates, + deletes, + ) + } + override fun equals(other: Any?): Boolean { + if (other !is SyncDelta) { + return false + } + if (this === other) { + return true + } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } + + override fun hashCode(): Int = toList().hashCode() +} +private open class messagesPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as? List)?.let { + Asset.fromList(it) + } + } + 130.toByte() -> { + return (readValue(buffer) as? List)?.let { + SyncDelta.fromList(it) + } + } + else -> super.readValueOfType(type, buffer) + } + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is Asset -> { + stream.write(129) + writeValue(stream, value.toList()) + } + is SyncDelta -> { + stream.write(130) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface ImHostService { + fun shouldFullSync(callback: (Result) -> Unit) + fun hasMediaChanges(callback: (Result) -> Unit) + fun getMediaChanges(callback: (Result) -> Unit) + fun checkpointSync(callback: (Result) -> Unit) + + companion object { + /** The codec used by ImHostService. */ + val codec: MessageCodec by lazy { + messagesPigeonCodec() + } + /** Sets up an instance of `ImHostService` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: ImHostService?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val taskQueue = binaryMessenger.makeBackgroundTaskQueue() + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ImHostService.shouldFullSync$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.shouldFullSync{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ImHostService.hasMediaChanges$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.hasMediaChanges{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ImHostService.getMediaChanges$separatedMessageChannelSuffix", codec, taskQueue) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.getMediaChanges{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ImHostService.checkpointSync$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.checkpointSync{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + reply.reply(MessagesPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/mobile/drift_schemas/main/drift_schema_v1.json b/mobile/drift_schemas/main/drift_schema_v1.json index 836a926b03..b9fc57de4d 100644 --- a/mobile/drift_schemas/main/drift_schema_v1.json +++ b/mobile/drift_schemas/main/drift_schema_v1.json @@ -1 +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":"blob","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":"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":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"profile_image_path","getter_name":"profileImagePath","moor_type":"string","nullable":true,"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":"quota_size_in_bytes","getter_name":"quotaSizeInBytes","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"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":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":1,"references":[0],"type":"table","data":{"name":"user_metadata_entity","was_declared_in_moor":false,"columns":[{"name":"user_id","getter_name":"userId","moor_type":"blob","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":"preferences","getter_name":"preferences","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"userPreferenceConverter","dart_type_name":"UserPreferences"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["user_id"]}},{"id":2,"references":[0],"type":"table","data":{"name":"partner_entity","was_declared_in_moor":false,"columns":[{"name":"shared_by_id","getter_name":"sharedById","moor_type":"blob","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":"blob","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":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":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"local_id","getter_name":"localId","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":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["local_id"]}},{"id":4,"references":[3],"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":"thumbnail_id","getter_name":"thumbnailId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES local_asset_entity (local_id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES local_asset_entity (local_id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"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"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":5,"references":[3,4],"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 (local_id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES local_asset_entity (local_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"]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","album_id"]}},{"id":6,"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":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"remote_id","getter_name":"remoteId","moor_type":"blob","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,"defaultConstraints":"UNIQUE","dialectAwareDefaultConstraints":{"sqlite":"UNIQUE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"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":"blob","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":"thumbhash","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":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["remote_id"]}},{"id":7,"references":[6],"type":"table","data":{"name":"exif_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"blob","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (remote_id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (remote_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":"int","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":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"latitude","getter_name":"latitude","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"longitude","getter_name":"longitude","moor_type":"int","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":"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":8,"references":[3],"type":"index","data":{"on":3,"name":"local_asset_checksum","sql":null,"unique":false,"columns":["checksum"]}},{"id":9,"references":[6],"type":"index","data":{"on":6,"name":"remote_asset_checksum","sql":null,"unique":false,"columns":["checksum"]}}]} \ No newline at end of file +{"_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":"blob","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":"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":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"profile_image_path","getter_name":"profileImagePath","moor_type":"string","nullable":true,"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":"quota_size_in_bytes","getter_name":"quotaSizeInBytes","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"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":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":1,"references":[0],"type":"table","data":{"name":"user_metadata_entity","was_declared_in_moor":false,"columns":[{"name":"user_id","getter_name":"userId","moor_type":"blob","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":"preferences","getter_name":"preferences","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"userPreferenceConverter","dart_type_name":"UserPreferences"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["user_id"]}},{"id":2,"references":[0],"type":"table","data":{"name":"partner_entity","was_declared_in_moor":false,"columns":[{"name":"shared_by_id","getter_name":"sharedById","moor_type":"blob","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":"blob","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":3,"references":[],"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":"marker","getter_name":"marker_","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"marker\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"marker\" 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":["id"]}},{"id":4,"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":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"local_id","getter_name":"localId","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":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["local_id"]}},{"id":5,"references":[4,3],"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 (local_id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES local_asset_entity (local_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"]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","album_id"]}},{"id":6,"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":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"remote_id","getter_name":"remoteId","moor_type":"blob","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,"defaultConstraints":"UNIQUE","dialectAwareDefaultConstraints":{"sqlite":"UNIQUE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"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":"blob","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":"thumbhash","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":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["remote_id"]}},{"id":7,"references":[6],"type":"table","data":{"name":"exif_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"blob","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (remote_id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (remote_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":"int","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":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"latitude","getter_name":"latitude","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"longitude","getter_name":"longitude","moor_type":"int","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":"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":8,"references":[4],"type":"index","data":{"on":4,"name":"local_asset_checksum","sql":null,"unique":false,"columns":["checksum"]}},{"id":9,"references":[6],"type":"index","data":{"on":6,"name":"remote_asset_checksum","sql":null,"unique":false,"columns":["checksum"]}}]} \ No newline at end of file diff --git a/mobile/immich_lint/pubspec.lock b/mobile/immich_lint/pubspec.lock index 6d4630f1fb..a7cd0ef71d 100644 --- a/mobile/immich_lint/pubspec.lock +++ b/mobile/immich_lint/pubspec.lock @@ -5,31 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 url: "https://pub.dev" source: hosted - version: "76.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.3" + version: "80.0.0" analyzer: dependency: "direct main" description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "7.3.0" analyzer_plugin: dependency: "direct main" description: name: analyzer_plugin - sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161" + sha256: b3075265c5ab222f8b3188342dcb50b476286394a40323e85d1fa725035d40a4 url: "https://pub.dev" source: hosted - version: "0.11.3" + version: "0.13.0" args: dependency: transitive description: @@ -106,34 +101,42 @@ packages: dependency: transitive description: name: custom_lint - sha256: "4500e88854e7581ee43586abeaf4443cb22375d6d289241a87b1aadf678d5545" + sha256: "409c485fd14f544af1da965d5a0d160ee57cd58b63eeaa7280a4f28cf5bda7f1" url: "https://pub.dev" source: hosted - version: "0.6.10" + version: "0.7.5" custom_lint_builder: dependency: "direct main" description: name: custom_lint_builder - sha256: "5a95eff100da256fbf086b329c17c8b49058c261cdf56d3a4157d3c31c511d78" + sha256: "107e0a43606138015777590ee8ce32f26ba7415c25b722ff0908a6f5d7a4c228" url: "https://pub.dev" source: hosted - version: "0.6.10" + version: "0.7.5" custom_lint_core: dependency: transitive description: name: custom_lint_core - sha256: "76a4046cc71d976222a078a8fd4a65e198b70545a8d690a75196dd14f08510f6" + sha256: "31110af3dde9d29fb10828ca33f1dce24d2798477b167675543ce3d208dee8be" url: "https://pub.dev" source: hosted - version: "0.6.10" + version: "0.7.5" + custom_lint_visitor: + dependency: transitive + description: + name: custom_lint_visitor + sha256: "36282d85714af494ee2d7da8c8913630aa6694da99f104fb2ed4afcf8fc857d8" + url: "https://pub.dev" + source: hosted + version: "1.0.0+7.3.0" dart_style: dependency: transitive description: name: dart_style - sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" + sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" url: "https://pub.dev" source: hosted - version: "2.3.8" + version: "3.0.1" file: dependency: transitive description: @@ -154,10 +157,10 @@ packages: dependency: transitive description: name: freezed_annotation - sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "3.0.0" glob: dependency: "direct main" description: @@ -198,14 +201,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.dev" - source: hosted - version: "0.1.3-main.0" matcher: dependency: transitive description: diff --git a/mobile/immich_lint/pubspec.yaml b/mobile/immich_lint/pubspec.yaml index 4cfd8abe81..2890a4a595 100644 --- a/mobile/immich_lint/pubspec.yaml +++ b/mobile/immich_lint/pubspec.yaml @@ -5,9 +5,9 @@ environment: sdk: '>=3.0.0 <4.0.0' dependencies: - analyzer: ^6.0.0 - analyzer_plugin: ^0.11.3 - custom_lint_builder: ^0.6.4 + analyzer: ^7.0.0 + analyzer_plugin: ^0.13.0 + custom_lint_builder: ^0.7.5 glob: ^2.1.2 dev_dependencies: diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index 744ddc053b..e258bfe239 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -89,6 +89,20 @@ FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerProfile.entitlements; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedRootGroup section */ + B28F36282DC3150F00B18015 /* Platform */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); + explicitFileTypes = { + }; + explicitFolders = ( + ); + path = Platform; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -175,6 +189,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + B28F36282DC3150F00B18015 /* Platform */, FA9973382CF6DF4B000EF859 /* Runner.entitlements */, 65DD438629917FAD0047FFA8 /* BackgroundSync */, FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */, @@ -224,6 +239,9 @@ dependencies = ( FAC6F8992D287C890078CB2F /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + B28F36282DC3150F00B18015 /* Platform */, + ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Immich-Debug.app */; diff --git a/mobile/ios/Runner/AppDelegate.swift b/mobile/ios/Runner/AppDelegate.swift index fd62618205..e13e7b59c9 100644 --- a/mobile/ios/Runner/AppDelegate.swift +++ b/mobile/ios/Runner/AppDelegate.swift @@ -22,6 +22,10 @@ import UIKit BackgroundServicePlugin.registerBackgroundProcessing() BackgroundServicePlugin.register(with: self.registrar(forPlugin: "BackgroundServicePlugin")!) + + // Register pigeon handler + let controller: FlutterViewController = window?.rootViewController as! FlutterViewController + ImHostServiceSetup.setUp(binaryMessenger: controller.binaryMessenger, api: ImHostServiceImpl()) BackgroundServicePlugin.setPluginRegistrantCallback { registry in if !registry.hasPlugin("org.cocoapods.path-provider-foundation") { diff --git a/mobile/ios/Runner/Platform/MediaManager.swift b/mobile/ios/Runner/Platform/MediaManager.swift new file mode 100644 index 0000000000..862f9cff26 --- /dev/null +++ b/mobile/ios/Runner/Platform/MediaManager.swift @@ -0,0 +1,152 @@ +import Photos + +class MediaManager { + let _defaults: UserDefaults + + let _changeTokenKey = "immich:changeToken"; + + init(with defaults: UserDefaults = .standard) { + _defaults = defaults + } + + @available(iOS 16, *) + func _getChangeToken() -> PHPersistentChangeToken? { + guard let encodedToken = _defaults.data(forKey: _changeTokenKey) else { + print("_getChangeToken: Change token not available in UserDefaults") + return nil + } + + do { + let changeToken = try NSKeyedUnarchiver.unarchivedObject(ofClass: PHPersistentChangeToken.self, from: encodedToken) + return changeToken + } catch { + print("_getChangeToken: Cannot decode the token from UserDefaults") + return nil + } + } + + @available(iOS 16, *) + func _saveChangeToken(token: PHPersistentChangeToken) -> Void { + do { + let encodedToken = try NSKeyedArchiver.archivedData(withRootObject: token, requiringSecureCoding: true) + _defaults.set(encodedToken, forKey: _changeTokenKey) + print("_setChangeToken: Change token saved to UserDefaults") + } catch { + print("_setChangeToken: Failed to persist the token to UserDefaults: \(error)") + } + } + + @available(iOS 16, *) + func checkpointSync(completion: @escaping (Result) -> Void) { + _saveChangeToken(token: PHPhotoLibrary.shared().currentChangeToken) + completion(.success(())) + } + + @available(iOS 16, *) + func shouldFullSync(completion: @escaping (Result) -> Void) { + guard PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized else { + // When we do not have access to photo library, return true to fallback to old sync + completion(.success(true)) + return + } + + guard let storedToken = _getChangeToken() else { + // No token exists, perform the initial full sync + print("shouldUseOldSync: No token found") + completion(.success(true)) + return + } + + do { + _ = try PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken) + completion(.success(false)) + } catch { + // fallback to using old sync when we cannot detect changes using the available token + print("shouldUseOldSync: fetchPersistentChanges failed with error (\(error))") + completion(.success(true)) + } + + } + + @available(iOS 16, *) + func hasMediaChanges(completion: @escaping (Result) -> Void) { + guard PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized else { + completion(.failure(PigeonError(code: "1", message: "No photo library access", details: nil))) + return + } + + let storedToken = _getChangeToken() + let currentToken = PHPhotoLibrary.shared().currentChangeToken + completion(.success(storedToken != currentToken)) + } + + @available(iOS 16, *) + func getMediaChanges(completion: @escaping (Result) -> Void) { + guard PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized else { + completion(.failure(PigeonError(code: "1", message: "No photo library access", details: nil))) + return + } + + guard let storedToken = _getChangeToken() else { + // No token exists, definitely need a full sync + print("getMediaChanges: No token found") + completion(.failure(PigeonError(code: "2", message: "No stored change token", details: nil))) + return + } + + do { + let result = try PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken) + + let dateFormatter = ISO8601DateFormatter() + dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + var delta = SyncDelta(updates: [], deletes: []) + for changes in result { + let details = try changes.changeDetails(for: PHObjectType.asset) + let updated = details.updatedLocalIdentifiers.union(details.insertedLocalIdentifiers) + let deleted = details.deletedLocalIdentifiers + + let options = PHFetchOptions() + options.includeHiddenAssets = true + let updatedAssets = PHAsset.fetchAssets(withLocalIdentifiers: Array(updated), options: options) + + var updates: [Asset] = [] + updatedAssets.enumerateObjects { (asset, _, _) in + let id = asset.localIdentifier + let name = PHAssetResource.assetResources(for: asset).first?.originalFilename ?? asset.title() + let type: Int64 = Int64(asset.mediaType.rawValue) + let createdAt = asset.creationDate.map { dateFormatter.string(from: $0) } + let updatedAt = asset.modificationDate.map { dateFormatter.string(from: $0) } + let durationInSeconds: Int64 = Int64(asset.duration) + + let dAsset = Asset(id: id, name: name, type: type, createdAt: createdAt, updatedAt: updatedAt, durationInSeconds: durationInSeconds, albumIds: self._getAlbumIdsForAsset(asset: asset)) + updates.append(dAsset) + } + + delta.updates.append(contentsOf: updates) + delta.deletes.append(contentsOf: deleted) + } + + completion(.success(delta)) + return + } catch { + print("getMediaChanges: Error fetching persistent changes: \(error)") + completion(.failure(PigeonError(code: "3", message: error.localizedDescription, details: nil))) + return + } + } + + @available(iOS 16, *) + func _getAlbumIdsForAsset(asset: PHAsset) -> [String] { + var albumIds: [String] = [] + var albums = PHAssetCollection.fetchAssetCollectionsContaining(asset, with: .album, options: nil) + albums.enumerateObjects { (album, _, _) in + albumIds.append(album.localIdentifier) + } + albums = PHAssetCollection.fetchAssetCollectionsContaining(asset, with: .smartAlbum, options: nil) + albums.enumerateObjects { (album, _, _) in + albumIds.append(album.localIdentifier) + } + return albumIds + } + +} diff --git a/mobile/ios/Runner/Platform/Messages.g.swift b/mobile/ios/Runner/Platform/Messages.g.swift new file mode 100644 index 0000000000..0534f9a4ce --- /dev/null +++ b/mobile/ios/Runner/Platform/Messages.g.swift @@ -0,0 +1,333 @@ +// Autogenerated from Pigeon (v25.3.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +/// Error class for passing custom error details to Dart side. +final class PigeonError: Error { + let code: String + let message: String? + let details: Sendable? + + init(code: String, message: String?, details: Sendable?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + return + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +func deepEqualsMessages(_ lhs: Any?, _ rhs: Any?) -> Bool { + let cleanLhs = nilOrValue(lhs) as Any? + let cleanRhs = nilOrValue(rhs) as Any? + switch (cleanLhs, cleanRhs) { + case (nil, nil): + return true + + case (nil, _), (_, nil): + return false + + case is (Void, Void): + return true + + case let (cleanLhsHashable, cleanRhsHashable) as (AnyHashable, AnyHashable): + return cleanLhsHashable == cleanRhsHashable + + case let (cleanLhsArray, cleanRhsArray) as ([Any?], [Any?]): + guard cleanLhsArray.count == cleanRhsArray.count else { return false } + for (index, element) in cleanLhsArray.enumerated() { + if !deepEqualsMessages(element, cleanRhsArray[index]) { + return false + } + } + return true + + case let (cleanLhsDictionary, cleanRhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]): + guard cleanLhsDictionary.count == cleanRhsDictionary.count else { return false } + for (key, cleanLhsValue) in cleanLhsDictionary { + guard cleanRhsDictionary.index(forKey: key) != nil else { return false } + if !deepEqualsMessages(cleanLhsValue, cleanRhsDictionary[key]!) { + return false + } + } + return true + + default: + // Any other type shouldn't be able to be used with pigeon. File an issue if you find this to be untrue. + return false + } +} + +func deepHashMessages(value: Any?, hasher: inout Hasher) { + if let valueList = value as? [AnyHashable] { + for item in valueList { deepHashMessages(value: item, hasher: &hasher) } + return + } + + if let valueDict = value as? [AnyHashable: AnyHashable] { + for key in valueDict.keys { + hasher.combine(key) + deepHashMessages(value: valueDict[key]!, hasher: &hasher) + } + return + } + + if let hashableValue = value as? AnyHashable { + hasher.combine(hashableValue.hashValue) + } + + return hasher.combine(String(describing: value)) +} + + + +/// Generated class from Pigeon that represents data sent in messages. +struct Asset: Hashable { + var id: String + var name: String + var type: Int64 + var createdAt: String? = nil + var updatedAt: String? = nil + var durationInSeconds: Int64 + var albumIds: [String] + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> Asset? { + let id = pigeonVar_list[0] as! String + let name = pigeonVar_list[1] as! String + let type = pigeonVar_list[2] as! Int64 + let createdAt: String? = nilOrValue(pigeonVar_list[3]) + let updatedAt: String? = nilOrValue(pigeonVar_list[4]) + let durationInSeconds = pigeonVar_list[5] as! Int64 + let albumIds = pigeonVar_list[6] as! [String] + + return Asset( + id: id, + name: name, + type: type, + createdAt: createdAt, + updatedAt: updatedAt, + durationInSeconds: durationInSeconds, + albumIds: albumIds + ) + } + func toList() -> [Any?] { + return [ + id, + name, + type, + createdAt, + updatedAt, + durationInSeconds, + albumIds, + ] + } + static func == (lhs: Asset, rhs: Asset) -> Bool { + return deepEqualsMessages(lhs.toList(), rhs.toList()) } + func hash(into hasher: inout Hasher) { + deepHashMessages(value: toList(), hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct SyncDelta: Hashable { + var updates: [Asset] + var deletes: [String] + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> SyncDelta? { + let updates = pigeonVar_list[0] as! [Asset] + let deletes = pigeonVar_list[1] as! [String] + + return SyncDelta( + updates: updates, + deletes: deletes + ) + } + func toList() -> [Any?] { + return [ + updates, + deletes, + ] + } + static func == (lhs: SyncDelta, rhs: SyncDelta) -> Bool { + return deepEqualsMessages(lhs.toList(), rhs.toList()) } + func hash(into hasher: inout Hasher) { + deepHashMessages(value: toList(), hasher: &hasher) + } +} + +private class MessagesPigeonCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 129: + return Asset.fromList(self.readValue() as! [Any?]) + case 130: + return SyncDelta.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class MessagesPigeonCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? Asset { + super.writeByte(129) + super.writeValue(value.toList()) + } else if let value = value as? SyncDelta { + super.writeByte(130) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class MessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return MessagesPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return MessagesPigeonCodecWriter(data: data) + } +} + +class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = MessagesPigeonCodec(readerWriter: MessagesPigeonCodecReaderWriter()) +} + + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol ImHostService { + func shouldFullSync(completion: @escaping (Result) -> Void) + func hasMediaChanges(completion: @escaping (Result) -> Void) + func getMediaChanges(completion: @escaping (Result) -> Void) + func checkpointSync(completion: @escaping (Result) -> Void) +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class ImHostServiceSetup { + static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared } + /// Sets up an instance of `ImHostService` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: ImHostService?, messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + #if os(iOS) + let taskQueue = binaryMessenger.makeBackgroundTaskQueue?() + #else + let taskQueue: FlutterTaskQueue? = nil + #endif + let shouldFullSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ImHostService.shouldFullSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + shouldFullSyncChannel.setMessageHandler { _, reply in + api.shouldFullSync { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + shouldFullSyncChannel.setMessageHandler(nil) + } + let hasMediaChangesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ImHostService.hasMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + hasMediaChangesChannel.setMessageHandler { _, reply in + api.hasMediaChanges { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + hasMediaChangesChannel.setMessageHandler(nil) + } + let getMediaChangesChannel = taskQueue == nil + ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ImHostService.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ImHostService.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + if let api = api { + getMediaChangesChannel.setMessageHandler { _, reply in + api.getMediaChanges { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + getMediaChangesChannel.setMessageHandler(nil) + } + let checkpointSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ImHostService.checkpointSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + checkpointSyncChannel.setMessageHandler { _, reply in + api.checkpointSync { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + checkpointSyncChannel.setMessageHandler(nil) + } + } +} diff --git a/mobile/ios/Runner/Platform/MessagesImpl.swift b/mobile/ios/Runner/Platform/MessagesImpl.swift new file mode 100644 index 0000000000..461df1217f --- /dev/null +++ b/mobile/ios/Runner/Platform/MessagesImpl.swift @@ -0,0 +1,44 @@ +import Photos + +class ImHostServiceImpl: ImHostService { + let _mediaManager: MediaManager + + init() { + _mediaManager = MediaManager() + } + + func shouldFullSync(completion: @escaping (Result) -> Void) { + if #available(iOS 16, *) { + _mediaManager.shouldFullSync(completion: completion) + return; + } else { + // Always fall back to full sync on older iOS versions + completion(.success(true)) + } + } + + func hasMediaChanges(completion: @escaping (Result) -> Void) { + if #available(iOS 16, *) { + _mediaManager.hasMediaChanges(completion: completion) + } else { + completion(.failure(PigeonError(code: "-1", message: "Not supported", details: nil))) + } + } + + func getMediaChanges(completion: @escaping (Result) -> Void) { + if #available(iOS 16, *) { + _mediaManager.getMediaChanges(completion: completion) + } else { + completion(.failure(PigeonError(code: "-1", message: "Not supported", details: nil))) + } + } + + func checkpointSync(completion: @escaping (Result) -> Void) { + if #available(iOS 16, *) { + _mediaManager.checkpointSync(completion: completion) + } else { + completion(.success(())) + } + } + +} diff --git a/mobile/lib/domain/interfaces/local_album.interface.dart b/mobile/lib/domain/interfaces/local_album.interface.dart index 611527d08a..b5a6924fa9 100644 --- a/mobile/lib/domain/interfaces/local_album.interface.dart +++ b/mobile/lib/domain/interfaces/local_album.interface.dart @@ -1,6 +1,7 @@ import 'package:immich_mobile/domain/interfaces/db.interface.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/local_album.model.dart'; +import 'package:immich_mobile/platform/messages.g.dart'; abstract interface class ILocalAlbumRepository implements IDatabaseRepository { Future insert(LocalAlbum album, Iterable assets); @@ -13,6 +14,10 @@ abstract interface class ILocalAlbumRepository implements IDatabaseRepository { Future update(LocalAlbum album); + Future updateAll(Iterable albums); + + Future handleSyncDelta(SyncDelta delta); + Future delete(String albumId); Future removeAssets(String albumId, Iterable assetIds); diff --git a/mobile/lib/domain/interfaces/local_asset.interface.dart b/mobile/lib/domain/interfaces/local_asset.interface.dart index 2f9fbd143f..b61ec9a746 100644 --- a/mobile/lib/domain/interfaces/local_asset.interface.dart +++ b/mobile/lib/domain/interfaces/local_asset.interface.dart @@ -2,5 +2,5 @@ import 'package:immich_mobile/domain/interfaces/db.interface.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; abstract interface class ILocalAssetRepository implements IDatabaseRepository { - Future get(String assetId); + Future get(String id); } diff --git a/mobile/lib/domain/services/device_sync.service.dart b/mobile/lib/domain/services/device_sync.service.dart index 4a7b1c7c07..f6160df335 100644 --- a/mobile/lib/domain/services/device_sync.service.dart +++ b/mobile/lib/domain/services/device_sync.service.dart @@ -4,28 +4,50 @@ import 'package:collection/collection.dart'; import 'package:flutter/widgets.dart'; import 'package:immich_mobile/domain/interfaces/album_media.interface.dart'; import 'package:immich_mobile/domain/interfaces/local_album.interface.dart'; -import 'package:immich_mobile/domain/interfaces/local_asset.interface.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/local_album.model.dart'; +import 'package:immich_mobile/platform/messages.g.dart' as platform; import 'package:immich_mobile/utils/diff.dart'; -import 'package:immich_mobile/utils/nullable_value.dart'; import 'package:logging/logging.dart'; class DeviceSyncService { final IAlbumMediaRepository _albumMediaRepository; final ILocalAlbumRepository _localAlbumRepository; - final ILocalAssetRepository _localAssetRepository; + final platform.ImHostService _hostService; final Logger _log = Logger("SyncService"); DeviceSyncService({ required IAlbumMediaRepository albumMediaRepository, required ILocalAlbumRepository localAlbumRepository, - required ILocalAssetRepository localAssetRepository, + required platform.ImHostService hostService, }) : _albumMediaRepository = albumMediaRepository, _localAlbumRepository = localAlbumRepository, - _localAssetRepository = localAssetRepository; + _hostService = hostService; Future sync() async { + try { + if (await _hostService.shouldFullSync()) { + _log.fine("Cannot use partial sync. Performing full sync"); + return await fullSync(); + } + + if (!await _hostService.hasMediaChanges()) { + _log.fine("No media changes detected. Skipping sync"); + return; + } + + final deviceAlbums = await _albumMediaRepository.getAll(); + await _localAlbumRepository.updateAll(deviceAlbums); + + final delta = await _hostService.getMediaChanges(); + await _localAlbumRepository.handleSyncDelta(delta); + await _hostService.checkpointSync(); + } catch (e, s) { + _log.severe("Error performing device sync", e, s); + } + } + + Future fullSync() async { try { final Stopwatch stopwatch = Stopwatch()..start(); // The deviceAlbums will not have the updatedAt field @@ -47,6 +69,7 @@ class DeviceSyncService { onlySecond: addAlbum, ); + _hostService.checkpointSync(); stopwatch.stop(); _log.info("Full device sync took - ${stopwatch.elapsedMilliseconds}ms"); } catch (e, s) { @@ -57,17 +80,12 @@ class DeviceSyncService { Future addAlbum(LocalAlbum newAlbum) async { try { _log.info("Adding device album ${newAlbum.name}"); - final deviceAlbum = await _albumMediaRepository.refresh(newAlbum.id); + final album = await _albumMediaRepository.refresh(newAlbum.id); - final assets = deviceAlbum.assetCount > 0 - ? await _albumMediaRepository.getAssetsForAlbum(deviceAlbum.id) + final assets = album.assetCount > 0 + ? await _albumMediaRepository.getAssetsForAlbum(album.id) : []; - final album = deviceAlbum.copyWith( - // The below assumes the list is already sorted by createdDate from the filter - thumbnailId: NullableValue.valueOrEmpty(assets.firstOrNull?.id), - ); - await _localAlbumRepository.insert(album, assets); _log.fine("Successfully added device album ${album.name}"); } catch (e, s) { @@ -110,7 +128,7 @@ class DeviceSyncService { } // Slower path - full sync - return await fullSync(dbAlbum, deviceAlbum); + return await fullDiff(dbAlbum, deviceAlbum); } catch (e, s) { _log.warning("Error while diff device album", e, s); } @@ -155,25 +173,8 @@ class DeviceSyncService { return false; } - String? thumbnailId = dbAlbum.thumbnailId; - if (thumbnailId == null || newAssets.isNotEmpty) { - if (thumbnailId == null) { - thumbnailId = newAssets.firstOrNull?.id; - } else if (newAssets.isNotEmpty) { - // The below assumes the list is already sorted by createdDate from the filter - final oldThumbAsset = await _localAssetRepository.get(thumbnailId); - if (oldThumbAsset.createdAt - .isBefore(newAssets.firstOrNull!.createdAt)) { - thumbnailId = newAssets.firstOrNull?.id; - } - } - } - await _updateAlbum( - deviceAlbum.copyWith( - thumbnailId: NullableValue.valueOrEmpty(thumbnailId), - backupSelection: dbAlbum.backupSelection, - ), + deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection), assetsToUpsert: newAssets, ); @@ -187,7 +188,7 @@ class DeviceSyncService { @visibleForTesting // The [deviceAlbum] is expected to be refreshed before calling this method // with modified time and asset count - Future fullSync(LocalAlbum dbAlbum, LocalAlbum deviceAlbum) async { + Future fullDiff(LocalAlbum dbAlbum, LocalAlbum deviceAlbum) async { try { final assetsInDevice = deviceAlbum.assetCount > 0 ? await _albumMediaRepository.getAssetsForAlbum(deviceAlbum.id) @@ -201,23 +202,13 @@ class DeviceSyncService { "Device album ${deviceAlbum.name} is empty. Removing assets from DB.", ); await _updateAlbum( - deviceAlbum.copyWith( - // Clear thumbnail for empty album - thumbnailId: const NullableValue.empty(), - backupSelection: dbAlbum.backupSelection, - ), + deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection), assetIdsToDelete: assetsInDb.map((a) => a.id), ); return true; } - // The below assumes the list is already sorted by createdDate from the filter - String? thumbnailId = assetsInDevice.isNotEmpty - ? assetsInDevice.firstOrNull?.id - : dbAlbum.thumbnailId; - final updatedDeviceAlbum = deviceAlbum.copyWith( - thumbnailId: NullableValue.valueOrEmpty(thumbnailId), backupSelection: dbAlbum.backupSelection, ); diff --git a/mobile/lib/infrastructure/entities/local_album.entity.dart b/mobile/lib/infrastructure/entities/local_album.entity.dart index 4eff2a1154..d6b10da819 100644 --- a/mobile/lib/infrastructure/entities/local_album.entity.dart +++ b/mobile/lib/infrastructure/entities/local_album.entity.dart @@ -1,7 +1,6 @@ import 'package:drift/drift.dart'; import 'package:immich_mobile/domain/models/local_album.model.dart'; import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; class LocalAlbumEntity extends Table with DriftDefaultsMixin { @@ -10,10 +9,8 @@ class LocalAlbumEntity extends Table with DriftDefaultsMixin { TextColumn get id => text()(); TextColumn get name => text()(); DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); - TextColumn get thumbnailId => text() - .nullable() - .references(LocalAssetEntity, #localId, onDelete: KeyAction.setNull)(); IntColumn get backupSelection => intEnum()(); + BoolColumn get marker_ => boolean().withDefault(const Constant(false))(); @override Set get primaryKey => {id}; @@ -26,7 +23,6 @@ extension LocalAlbumEntityX on LocalAlbumEntityData { name: name, updatedAt: updatedAt, assetCount: assetCount, - thumbnailId: thumbnailId, backupSelection: backupSelection, ); } diff --git a/mobile/lib/infrastructure/entities/local_album.entity.drift.dart b/mobile/lib/infrastructure/entities/local_album.entity.drift.dart index ee6c5b8c61..aa031cb861 100644 --- a/mobile/lib/infrastructure/entities/local_album.entity.drift.dart +++ b/mobile/lib/infrastructure/entities/local_album.entity.drift.dart @@ -7,59 +7,24 @@ import 'package:immich_mobile/domain/models/local_album.model.dart' as i2; import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart' as i3; import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4; -import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart' - as i5; -import 'package:drift/internal/modular.dart' as i6; typedef $$LocalAlbumEntityTableCreateCompanionBuilder = i1.LocalAlbumEntityCompanion Function({ required String id, required String name, i0.Value updatedAt, - i0.Value thumbnailId, required i2.BackupSelection backupSelection, + i0.Value marker_, }); typedef $$LocalAlbumEntityTableUpdateCompanionBuilder = i1.LocalAlbumEntityCompanion Function({ i0.Value id, i0.Value name, i0.Value updatedAt, - i0.Value thumbnailId, i0.Value backupSelection, + i0.Value marker_, }); -final class $$LocalAlbumEntityTableReferences extends i0.BaseReferences< - i0.GeneratedDatabase, i1.$LocalAlbumEntityTable, i1.LocalAlbumEntityData> { - $$LocalAlbumEntityTableReferences( - super.$_db, super.$_table, super.$_typedResult); - - static i5.$LocalAssetEntityTable _thumbnailIdTable(i0.GeneratedDatabase db) => - i6.ReadDatabaseContainer(db) - .resultSet('local_asset_entity') - .createAlias(i0.$_aliasNameGenerator( - i6.ReadDatabaseContainer(db) - .resultSet('local_album_entity') - .thumbnailId, - i6.ReadDatabaseContainer(db) - .resultSet('local_asset_entity') - .localId)); - - i5.$$LocalAssetEntityTableProcessedTableManager? get thumbnailId { - final $_column = $_itemColumn('thumbnail_id'); - if ($_column == null) return null; - final manager = i5 - .$$LocalAssetEntityTableTableManager( - $_db, - i6.ReadDatabaseContainer($_db) - .resultSet('local_asset_entity')) - .filter((f) => f.localId.sqlEquals($_column)); - final item = $_typedResult.readTableOrNull(_thumbnailIdTable($_db)); - if (item == null) return manager; - return i0.ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); - } -} - class $$LocalAlbumEntityTableFilterComposer extends i0.Composer { $$LocalAlbumEntityTableFilterComposer({ @@ -83,27 +48,8 @@ class $$LocalAlbumEntityTableFilterComposer column: $table.backupSelection, builder: (column) => i0.ColumnWithTypeConverterFilters(column)); - i5.$$LocalAssetEntityTableFilterComposer get thumbnailId { - final i5.$$LocalAssetEntityTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.thumbnailId, - referencedTable: i6.ReadDatabaseContainer($db) - .resultSet('local_asset_entity'), - getReferencedColumn: (t) => t.localId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - i5.$$LocalAssetEntityTableFilterComposer( - $db: $db, - $table: i6.ReadDatabaseContainer($db) - .resultSet('local_asset_entity'), - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); - return composer; - } + i0.ColumnFilters get marker_ => $composableBuilder( + column: $table.marker_, builder: (column) => i0.ColumnFilters(column)); } class $$LocalAlbumEntityTableOrderingComposer @@ -129,29 +75,8 @@ class $$LocalAlbumEntityTableOrderingComposer column: $table.backupSelection, builder: (column) => i0.ColumnOrderings(column)); - i5.$$LocalAssetEntityTableOrderingComposer get thumbnailId { - final i5.$$LocalAssetEntityTableOrderingComposer composer = - $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.thumbnailId, - referencedTable: i6.ReadDatabaseContainer($db) - .resultSet('local_asset_entity'), - getReferencedColumn: (t) => t.localId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - i5.$$LocalAssetEntityTableOrderingComposer( - $db: $db, - $table: i6.ReadDatabaseContainer($db) - .resultSet( - 'local_asset_entity'), - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); - return composer; - } + i0.ColumnOrderings get marker_ => $composableBuilder( + column: $table.marker_, builder: (column) => i0.ColumnOrderings(column)); } class $$LocalAlbumEntityTableAnnotationComposer @@ -176,29 +101,8 @@ class $$LocalAlbumEntityTableAnnotationComposer get backupSelection => $composableBuilder( column: $table.backupSelection, builder: (column) => column); - i5.$$LocalAssetEntityTableAnnotationComposer get thumbnailId { - final i5.$$LocalAssetEntityTableAnnotationComposer composer = - $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.thumbnailId, - referencedTable: i6.ReadDatabaseContainer($db) - .resultSet('local_asset_entity'), - getReferencedColumn: (t) => t.localId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - i5.$$LocalAssetEntityTableAnnotationComposer( - $db: $db, - $table: i6.ReadDatabaseContainer($db) - .resultSet( - 'local_asset_entity'), - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); - return composer; - } + i0.GeneratedColumn get marker_ => + $composableBuilder(column: $table.marker_, builder: (column) => column); } class $$LocalAlbumEntityTableTableManager extends i0.RootTableManager< @@ -210,9 +114,13 @@ class $$LocalAlbumEntityTableTableManager extends i0.RootTableManager< i1.$$LocalAlbumEntityTableAnnotationComposer, $$LocalAlbumEntityTableCreateCompanionBuilder, $$LocalAlbumEntityTableUpdateCompanionBuilder, - (i1.LocalAlbumEntityData, i1.$$LocalAlbumEntityTableReferences), + ( + i1.LocalAlbumEntityData, + i0.BaseReferences + ), i1.LocalAlbumEntityData, - i0.PrefetchHooks Function({bool thumbnailId})> { + i0.PrefetchHooks Function()> { $$LocalAlbumEntityTableTableManager( i0.GeneratedDatabase db, i1.$LocalAlbumEntityTable table) : super(i0.TableManagerState( @@ -229,73 +137,35 @@ class $$LocalAlbumEntityTableTableManager extends i0.RootTableManager< i0.Value id = const i0.Value.absent(), i0.Value name = const i0.Value.absent(), i0.Value updatedAt = const i0.Value.absent(), - i0.Value thumbnailId = const i0.Value.absent(), i0.Value backupSelection = const i0.Value.absent(), + i0.Value marker_ = const i0.Value.absent(), }) => i1.LocalAlbumEntityCompanion( id: id, name: name, updatedAt: updatedAt, - thumbnailId: thumbnailId, backupSelection: backupSelection, + marker_: marker_, ), createCompanionCallback: ({ required String id, required String name, i0.Value updatedAt = const i0.Value.absent(), - i0.Value thumbnailId = const i0.Value.absent(), required i2.BackupSelection backupSelection, + i0.Value marker_ = const i0.Value.absent(), }) => i1.LocalAlbumEntityCompanion.insert( id: id, name: name, updatedAt: updatedAt, - thumbnailId: thumbnailId, backupSelection: backupSelection, + marker_: marker_, ), withReferenceMapper: (p0) => p0 - .map((e) => ( - e.readTable(table), - i1.$$LocalAlbumEntityTableReferences(db, table, e) - )) + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) .toList(), - prefetchHooksCallback: ({thumbnailId = false}) { - return i0.PrefetchHooks( - db: db, - explicitlyWatchedTables: [], - addJoins: < - T extends i0.TableManagerState< - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic>>(state) { - if (thumbnailId) { - state = state.withJoin( - currentTable: table, - currentColumn: table.thumbnailId, - referencedTable: i1.$$LocalAlbumEntityTableReferences - ._thumbnailIdTable(db), - referencedColumn: i1.$$LocalAlbumEntityTableReferences - ._thumbnailIdTable(db) - .localId, - ) as T; - } - - return state; - }, - getPrefetchedDataCallback: (items) async { - return []; - }, - ); - }, + prefetchHooksCallback: null, )); } @@ -308,9 +178,13 @@ typedef $$LocalAlbumEntityTableProcessedTableManager = i0.ProcessedTableManager< i1.$$LocalAlbumEntityTableAnnotationComposer, $$LocalAlbumEntityTableCreateCompanionBuilder, $$LocalAlbumEntityTableUpdateCompanionBuilder, - (i1.LocalAlbumEntityData, i1.$$LocalAlbumEntityTableReferences), + ( + i1.LocalAlbumEntityData, + i0.BaseReferences + ), i1.LocalAlbumEntityData, - i0.PrefetchHooks Function({bool thumbnailId})>; + i0.PrefetchHooks Function()>; class $LocalAlbumEntityTable extends i3.LocalAlbumEntity with i0.TableInfo<$LocalAlbumEntityTable, i1.LocalAlbumEntityData> { @@ -337,15 +211,6 @@ class $LocalAlbumEntityTable extends i3.LocalAlbumEntity type: i0.DriftSqlType.dateTime, requiredDuringInsert: false, defaultValue: i4.currentDateAndTime); - static const i0.VerificationMeta _thumbnailIdMeta = - const i0.VerificationMeta('thumbnailId'); - @override - late final i0.GeneratedColumn thumbnailId = - i0.GeneratedColumn('thumbnail_id', aliasedName, true, - type: i0.DriftSqlType.string, - requiredDuringInsert: false, - defaultConstraints: i0.GeneratedColumn.constraintIsAlways( - 'REFERENCES local_asset_entity (local_id) ON DELETE SET NULL')); @override late final i0.GeneratedColumnWithTypeConverter backupSelection = i0.GeneratedColumn( @@ -353,9 +218,19 @@ class $LocalAlbumEntityTable extends i3.LocalAlbumEntity type: i0.DriftSqlType.int, requiredDuringInsert: true) .withConverter( i1.$LocalAlbumEntityTable.$converterbackupSelection); + static const i0.VerificationMeta _marker_Meta = + const i0.VerificationMeta('marker_'); + @override + late final i0.GeneratedColumn marker_ = i0.GeneratedColumn( + 'marker', aliasedName, false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + i0.GeneratedColumn.constraintIsAlways('CHECK ("marker" IN (0, 1))'), + defaultValue: const i4.Constant(false)); @override List get $columns => - [id, name, updatedAt, thumbnailId, backupSelection]; + [id, name, updatedAt, backupSelection, marker_]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -382,11 +257,9 @@ class $LocalAlbumEntityTable extends i3.LocalAlbumEntity context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } - if (data.containsKey('thumbnail_id')) { - context.handle( - _thumbnailIdMeta, - thumbnailId.isAcceptableOrUnknown( - data['thumbnail_id']!, _thumbnailIdMeta)); + if (data.containsKey('marker')) { + context.handle(_marker_Meta, + marker_.isAcceptableOrUnknown(data['marker']!, _marker_Meta)); } return context; } @@ -404,11 +277,11 @@ class $LocalAlbumEntityTable extends i3.LocalAlbumEntity .read(i0.DriftSqlType.string, data['${effectivePrefix}name'])!, updatedAt: attachedDatabase.typeMapping.read( i0.DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, - thumbnailId: attachedDatabase.typeMapping - .read(i0.DriftSqlType.string, data['${effectivePrefix}thumbnail_id']), backupSelection: i1.$LocalAlbumEntityTable.$converterbackupSelection .fromSql(attachedDatabase.typeMapping.read(i0.DriftSqlType.int, data['${effectivePrefix}backup_selection'])!), + marker_: attachedDatabase.typeMapping + .read(i0.DriftSqlType.bool, data['${effectivePrefix}marker'])!, ); } @@ -432,28 +305,26 @@ class LocalAlbumEntityData extends i0.DataClass final String id; final String name; final DateTime updatedAt; - final String? thumbnailId; final i2.BackupSelection backupSelection; + final bool marker_; const LocalAlbumEntityData( {required this.id, required this.name, required this.updatedAt, - this.thumbnailId, - required this.backupSelection}); + required this.backupSelection, + required this.marker_}); @override Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = i0.Variable(id); map['name'] = i0.Variable(name); map['updated_at'] = i0.Variable(updatedAt); - if (!nullToAbsent || thumbnailId != null) { - map['thumbnail_id'] = i0.Variable(thumbnailId); - } { map['backup_selection'] = i0.Variable(i1 .$LocalAlbumEntityTable.$converterbackupSelection .toSql(backupSelection)); } + map['marker'] = i0.Variable(marker_); return map; } @@ -464,9 +335,9 @@ class LocalAlbumEntityData extends i0.DataClass id: serializer.fromJson(json['id']), name: serializer.fromJson(json['name']), updatedAt: serializer.fromJson(json['updatedAt']), - thumbnailId: serializer.fromJson(json['thumbnailId']), backupSelection: i1.$LocalAlbumEntityTable.$converterbackupSelection .fromJson(serializer.fromJson(json['backupSelection'])), + marker_: serializer.fromJson(json['marker_']), ); } @override @@ -476,10 +347,10 @@ class LocalAlbumEntityData extends i0.DataClass 'id': serializer.toJson(id), 'name': serializer.toJson(name), 'updatedAt': serializer.toJson(updatedAt), - 'thumbnailId': serializer.toJson(thumbnailId), 'backupSelection': serializer.toJson(i1 .$LocalAlbumEntityTable.$converterbackupSelection .toJson(backupSelection)), + 'marker_': serializer.toJson(marker_), }; } @@ -487,25 +358,24 @@ class LocalAlbumEntityData extends i0.DataClass {String? id, String? name, DateTime? updatedAt, - i0.Value thumbnailId = const i0.Value.absent(), - i2.BackupSelection? backupSelection}) => + i2.BackupSelection? backupSelection, + bool? marker_}) => i1.LocalAlbumEntityData( id: id ?? this.id, name: name ?? this.name, updatedAt: updatedAt ?? this.updatedAt, - thumbnailId: thumbnailId.present ? thumbnailId.value : this.thumbnailId, backupSelection: backupSelection ?? this.backupSelection, + marker_: marker_ ?? this.marker_, ); LocalAlbumEntityData copyWithCompanion(i1.LocalAlbumEntityCompanion data) { return LocalAlbumEntityData( id: data.id.present ? data.id.value : this.id, name: data.name.present ? data.name.value : this.name, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, - thumbnailId: - data.thumbnailId.present ? data.thumbnailId.value : this.thumbnailId, backupSelection: data.backupSelection.present ? data.backupSelection.value : this.backupSelection, + marker_: data.marker_.present ? data.marker_.value : this.marker_, ); } @@ -515,15 +385,15 @@ class LocalAlbumEntityData extends i0.DataClass ..write('id: $id, ') ..write('name: $name, ') ..write('updatedAt: $updatedAt, ') - ..write('thumbnailId: $thumbnailId, ') - ..write('backupSelection: $backupSelection') + ..write('backupSelection: $backupSelection, ') + ..write('marker_: $marker_') ..write(')')) .toString(); } @override int get hashCode => - Object.hash(id, name, updatedAt, thumbnailId, backupSelection); + Object.hash(id, name, updatedAt, backupSelection, marker_); @override bool operator ==(Object other) => identical(this, other) || @@ -531,8 +401,8 @@ class LocalAlbumEntityData extends i0.DataClass other.id == this.id && other.name == this.name && other.updatedAt == this.updatedAt && - other.thumbnailId == this.thumbnailId && - other.backupSelection == this.backupSelection); + other.backupSelection == this.backupSelection && + other.marker_ == this.marker_); } class LocalAlbumEntityCompanion @@ -540,21 +410,21 @@ class LocalAlbumEntityCompanion final i0.Value id; final i0.Value name; final i0.Value updatedAt; - final i0.Value thumbnailId; final i0.Value backupSelection; + final i0.Value marker_; const LocalAlbumEntityCompanion({ this.id = const i0.Value.absent(), this.name = const i0.Value.absent(), this.updatedAt = const i0.Value.absent(), - this.thumbnailId = const i0.Value.absent(), this.backupSelection = const i0.Value.absent(), + this.marker_ = const i0.Value.absent(), }); LocalAlbumEntityCompanion.insert({ required String id, required String name, this.updatedAt = const i0.Value.absent(), - this.thumbnailId = const i0.Value.absent(), required i2.BackupSelection backupSelection, + this.marker_ = const i0.Value.absent(), }) : id = i0.Value(id), name = i0.Value(name), backupSelection = i0.Value(backupSelection); @@ -562,15 +432,15 @@ class LocalAlbumEntityCompanion i0.Expression? id, i0.Expression? name, i0.Expression? updatedAt, - i0.Expression? thumbnailId, i0.Expression? backupSelection, + i0.Expression? marker_, }) { return i0.RawValuesInsertable({ if (id != null) 'id': id, if (name != null) 'name': name, if (updatedAt != null) 'updated_at': updatedAt, - if (thumbnailId != null) 'thumbnail_id': thumbnailId, if (backupSelection != null) 'backup_selection': backupSelection, + if (marker_ != null) 'marker': marker_, }); } @@ -578,14 +448,14 @@ class LocalAlbumEntityCompanion {i0.Value? id, i0.Value? name, i0.Value? updatedAt, - i0.Value? thumbnailId, - i0.Value? backupSelection}) { + i0.Value? backupSelection, + i0.Value? marker_}) { return i1.LocalAlbumEntityCompanion( id: id ?? this.id, name: name ?? this.name, updatedAt: updatedAt ?? this.updatedAt, - thumbnailId: thumbnailId ?? this.thumbnailId, backupSelection: backupSelection ?? this.backupSelection, + marker_: marker_ ?? this.marker_, ); } @@ -601,14 +471,14 @@ class LocalAlbumEntityCompanion if (updatedAt.present) { map['updated_at'] = i0.Variable(updatedAt.value); } - if (thumbnailId.present) { - map['thumbnail_id'] = i0.Variable(thumbnailId.value); - } if (backupSelection.present) { map['backup_selection'] = i0.Variable(i1 .$LocalAlbumEntityTable.$converterbackupSelection .toSql(backupSelection.value)); } + if (marker_.present) { + map['marker'] = i0.Variable(marker_.value); + } return map; } @@ -618,8 +488,8 @@ class LocalAlbumEntityCompanion ..write('id: $id, ') ..write('name: $name, ') ..write('updatedAt: $updatedAt, ') - ..write('thumbnailId: $thumbnailId, ') - ..write('backupSelection: $backupSelection') + ..write('backupSelection: $backupSelection, ') + ..write('marker_: $marker_') ..write(')')) .toString(); } diff --git a/mobile/lib/infrastructure/repositories/album_media.repository.dart b/mobile/lib/infrastructure/repositories/album_media.repository.dart index 0a2da332fc..81dec4fe5b 100644 --- a/mobile/lib/infrastructure/repositories/album_media.repository.dart +++ b/mobile/lib/infrastructure/repositories/album_media.repository.dart @@ -41,12 +41,10 @@ class AlbumMediaRepository implements IAlbumMediaRepository { @override Future> getAll() { - final filter = AdvancedCustomFilter( - orderBy: [OrderByItem.asc(CustomColumns.base.id)], - ); - - return PhotoManager.getAssetPathList(hasAll: true, filterOption: filter) - .then((e) { + return PhotoManager.getAssetPathList( + hasAll: true, + filterOption: AdvancedCustomFilter(), + ).then((e) { if (_platform.isAndroid) { e.removeWhere((a) => a.isAll); } diff --git a/mobile/lib/infrastructure/repositories/db.repository.drift.dart b/mobile/lib/infrastructure/repositories/db.repository.drift.dart index 1935e38f3e..cfaca56983 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.drift.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.drift.dart @@ -7,9 +7,9 @@ import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift as i2; import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart' as i3; -import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart' - as i4; import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart' + as i4; +import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart' as i5; import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart' as i6; @@ -26,10 +26,10 @@ abstract class $Drift extends i0.GeneratedDatabase { i2.$UserMetadataEntityTable(this); late final i3.$PartnerEntityTable partnerEntity = i3.$PartnerEntityTable(this); - late final i4.$LocalAssetEntityTable localAssetEntity = - i4.$LocalAssetEntityTable(this); - late final i5.$LocalAlbumEntityTable localAlbumEntity = - i5.$LocalAlbumEntityTable(this); + late final i4.$LocalAlbumEntityTable localAlbumEntity = + i4.$LocalAlbumEntityTable(this); + late final i5.$LocalAssetEntityTable localAssetEntity = + i5.$LocalAssetEntityTable(this); late final i6.$LocalAlbumAssetEntityTable localAlbumAssetEntity = i6.$LocalAlbumAssetEntityTable(this); late final i7.$RemoteAssetEntityTable remoteAssetEntity = @@ -43,12 +43,12 @@ abstract class $Drift extends i0.GeneratedDatabase { userEntity, userMetadataEntity, partnerEntity, - localAssetEntity, localAlbumEntity, + localAssetEntity, localAlbumAssetEntity, remoteAssetEntity, exifEntity, - i4.localAssetChecksum, + i5.localAssetChecksum, i7.remoteAssetChecksum ]; @override @@ -77,13 +77,6 @@ abstract class $Drift extends i0.GeneratedDatabase { i0.TableUpdate('partner_entity', kind: i0.UpdateKind.delete), ], ), - i0.WritePropagation( - on: i0.TableUpdateQuery.onTableName('local_asset_entity', - limitUpdateKind: i0.UpdateKind.delete), - result: [ - i0.TableUpdate('local_album_entity', kind: i0.UpdateKind.update), - ], - ), i0.WritePropagation( on: i0.TableUpdateQuery.onTableName('local_asset_entity', limitUpdateKind: i0.UpdateKind.delete), @@ -130,10 +123,10 @@ class $DriftManager { i2.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity); i3.$$PartnerEntityTableTableManager get partnerEntity => i3.$$PartnerEntityTableTableManager(_db, _db.partnerEntity); - i4.$$LocalAssetEntityTableTableManager get localAssetEntity => - i4.$$LocalAssetEntityTableTableManager(_db, _db.localAssetEntity); - i5.$$LocalAlbumEntityTableTableManager get localAlbumEntity => - i5.$$LocalAlbumEntityTableTableManager(_db, _db.localAlbumEntity); + i4.$$LocalAlbumEntityTableTableManager get localAlbumEntity => + i4.$$LocalAlbumEntityTableTableManager(_db, _db.localAlbumEntity); + i5.$$LocalAssetEntityTableTableManager get localAssetEntity => + i5.$$LocalAssetEntityTableTableManager(_db, _db.localAssetEntity); i6.$$LocalAlbumAssetEntityTableTableManager get localAlbumAssetEntity => i6 .$$LocalAlbumAssetEntityTableTableManager(_db, _db.localAlbumAssetEntity); i7.$$RemoteAssetEntityTableTableManager get remoteAssetEntity => diff --git a/mobile/lib/infrastructure/repositories/local_album.repository.dart b/mobile/lib/infrastructure/repositories/local_album.repository.dart index 0ae0d0daf7..12d3066de9 100644 --- a/mobile/lib/infrastructure/repositories/local_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/local_album.repository.dart @@ -8,6 +8,7 @@ import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.d import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:immich_mobile/platform/messages.g.dart' as platform; import 'package:platform/platform.dart'; class DriftLocalAlbumRepository extends DriftDatabaseRepository @@ -65,7 +66,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository transaction(() async { await _upsertAssets(assets); // Needs to be after asset upsert to link the thumbnail - await _upsertAlbum(localAlbum); + await update(localAlbum); await _linkAssetsToAlbum(localAlbum.id, assets); }); @@ -112,7 +113,46 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository } @override - Future update(LocalAlbum localAlbum) => _upsertAlbum(localAlbum); + Future update(LocalAlbum localAlbum) { + final companion = LocalAlbumEntityCompanion.insert( + id: localAlbum.id, + name: localAlbum.name, + updatedAt: Value(localAlbum.updatedAt), + backupSelection: localAlbum.backupSelection, + ); + + return _db.localAlbumEntity + .insertOne(companion, onConflict: DoUpdate((_) => companion)); + } + + @override + Future updateAll(Iterable albums) { + return _db.transaction(() async { + await _db.localAlbumEntity + .update() + .write(const LocalAlbumEntityCompanion(marker_: Value(false))); + + await _db.batch((batch) { + for (final album in albums) { + final companion = LocalAlbumEntityCompanion.insert( + id: album.id, + name: album.name, + updatedAt: Value(album.updatedAt), + backupSelection: album.backupSelection, + marker_: const Value(true), + ); + + batch.insert( + _db.localAlbumEntity, + companion, + onConflict: DoUpdate((_) => companion), + ); + } + }); + + await _db.localAlbumEntity.deleteWhere((f) => f.marker_.equals(false)); + }); + } @override Future> getAssetsForAlbum(String albumId) { @@ -132,17 +172,30 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository .get(); } - Future _upsertAlbum(LocalAlbum localAlbum) { - final companion = LocalAlbumEntityCompanion.insert( - id: localAlbum.id, - name: localAlbum.name, - updatedAt: Value(localAlbum.updatedAt), - thumbnailId: Value.absentIfNull(localAlbum.thumbnailId), - backupSelection: localAlbum.backupSelection, - ); + @override + Future handleSyncDelta(platform.SyncDelta delta) { + return _db.transaction(() async { + await _deleteAssets(delta.deletes); - return _db.localAlbumEntity - .insertOne(companion, onConflict: DoUpdate((_) => companion)); + await _upsertAssets(delta.updates.map((a) => a.toLocalAsset())); + await _db.batch((batch) { + batch.deleteWhere( + _db.localAlbumAssetEntity, + (f) => f.assetId.isIn(delta.updates.map((a) => a.id)), + ); + for (final asset in delta.updates) { + batch.insertAll( + _db.localAlbumAssetEntity, + asset.albumIds.map( + (albumId) => LocalAlbumAssetEntityCompanion.insert( + assetId: asset.id, + albumId: albumId, + ), + ), + ); + } + }); + }); } Future _linkAssetsToAlbum( @@ -240,3 +293,18 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository ); } } + +extension on platform.Asset { + LocalAsset toLocalAsset() { + return LocalAsset( + id: id, + name: name, + type: AssetType.values.elementAtOrNull(type) ?? AssetType.other, + createdAt: + createdAt == null ? DateTime.now() : DateTime.parse(createdAt!), + updatedAt: + updatedAt == null ? DateTime.now() : DateTime.parse(updatedAt!), + durationInSeconds: durationInSeconds, + ); + } +} diff --git a/mobile/lib/infrastructure/repositories/local_asset.repository.dart b/mobile/lib/infrastructure/repositories/local_asset.repository.dart index cb9119b653..2f28176d8d 100644 --- a/mobile/lib/infrastructure/repositories/local_asset.repository.dart +++ b/mobile/lib/infrastructure/repositories/local_asset.repository.dart @@ -9,8 +9,8 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository const DriftLocalAssetRepository(this._db) : super(_db); @override - Future get(String assetId) => _db.managers.localAssetEntity - .filter((f) => f.localId(assetId)) + Future get(String id) => _db.managers.localAssetEntity + .filter((f) => f.localId(id)) .map((a) => a.toDto()) .getSingle(); } diff --git a/mobile/lib/platform/messages.dart b/mobile/lib/platform/messages.dart new file mode 100644 index 0000000000..84fdb04269 --- /dev/null +++ b/mobile/lib/platform/messages.dart @@ -0,0 +1,55 @@ +// ignore: depend_on_referenced_packages +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/platform/messages.g.dart', + swiftOut: 'ios/Runner/Platform/Messages.g.swift', + swiftOptions: SwiftOptions(), + kotlinOut: + 'android/app/src/main/kotlin/app/alextran/immich/platform/messages.g.kt', + kotlinOptions: KotlinOptions(), + dartOptions: DartOptions(), + ), +) +class Asset { + final String id; + final String name; + final int type; // follows AssetType enum from base_asset.model.dart + final String? createdAt; + final String? updatedAt; + final int durationInSeconds; + final List albumIds; + + const Asset({ + required this.id, + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + required this.durationInSeconds, + required this.albumIds, + }); +} + +class SyncDelta { + SyncDelta({this.updates = const [], this.deletes = const []}); + List updates; + List deletes; +} + +@HostApi() +abstract class ImHostService { + @async + bool shouldFullSync(); + + @async + bool hasMediaChanges(); + + @async + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + SyncDelta getMediaChanges(); + + @async + void checkpointSync(); +} diff --git a/mobile/lib/platform/messages.g.dart b/mobile/lib/platform/messages.g.dart new file mode 100644 index 0000000000..f4751ebf20 --- /dev/null +++ b/mobile/lib/platform/messages.g.dart @@ -0,0 +1,310 @@ +// Autogenerated from Pigeon (v25.3.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +bool _deepEquals(Object? a, Object? b) { + if (a is List && b is List) { + return a.length == b.length && + a.indexed + .every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); + } + if (a is Map && b is Map) { + return a.length == b.length && + a.entries.every((MapEntry entry) => + (b as Map).containsKey(entry.key) && + _deepEquals(entry.value, b[entry.key])); + } + return a == b; +} + +class Asset { + Asset({ + required this.id, + required this.name, + required this.type, + this.createdAt, + this.updatedAt, + required this.durationInSeconds, + required this.albumIds, + }); + + String id; + + String name; + + int type; + + String? createdAt; + + String? updatedAt; + + int durationInSeconds; + + List albumIds; + + List _toList() { + return [ + id, + name, + type, + createdAt, + updatedAt, + durationInSeconds, + albumIds, + ]; + } + + Object encode() { + return _toList(); + } + + static Asset decode(Object result) { + result as List; + return Asset( + id: result[0]! as String, + name: result[1]! as String, + type: result[2]! as int, + createdAt: result[3] as String?, + updatedAt: result[4] as String?, + durationInSeconds: result[5]! as int, + albumIds: (result[6] as List?)!.cast(), + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! Asset || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); +} + +class SyncDelta { + SyncDelta({ + this.updates = const [], + this.deletes = const [], + }); + + List updates; + + List deletes; + + List _toList() { + return [ + updates, + deletes, + ]; + } + + Object encode() { + return _toList(); + } + + static SyncDelta decode(Object result) { + result as List; + return SyncDelta( + updates: (result[0] as List?)!.cast(), + deletes: (result[1] as List?)!.cast(), + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! SyncDelta || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else if (value is Asset) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else if (value is SyncDelta) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + return Asset.decode(readValue(buffer)!); + case 130: + return SyncDelta.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class ImHostService { + /// Constructor for [ImHostService]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + ImHostService( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future shouldFullSync() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.ImHostService.shouldFullSync$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + Future hasMediaChanges() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.ImHostService.hasMediaChanges$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + Future getMediaChanges() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.ImHostService.getMediaChanges$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as SyncDelta?)!; + } + } + + Future checkpointSync() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.ImHostService.checkpointSync$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } +} diff --git a/mobile/lib/providers/infrastructure/platform.provider.dart b/mobile/lib/providers/infrastructure/platform.provider.dart new file mode 100644 index 0000000000..5dc4f4eb48 --- /dev/null +++ b/mobile/lib/providers/infrastructure/platform.provider.dart @@ -0,0 +1,4 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/platform/messages.g.dart'; + +final platformMessagesImpl = Provider((_) => ImHostService()); diff --git a/mobile/lib/providers/infrastructure/sync.provider.dart b/mobile/lib/providers/infrastructure/sync.provider.dart index 94b654c9d2..a4d85ba7e2 100644 --- a/mobile/lib/providers/infrastructure/sync.provider.dart +++ b/mobile/lib/providers/infrastructure/sync.provider.dart @@ -5,15 +5,15 @@ import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.da import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; final deviceSyncServiceProvider = Provider( (ref) => DeviceSyncService( albumMediaRepository: ref.watch(albumMediaRepositoryProvider), localAlbumRepository: ref.watch(localAlbumRepository), - localAssetRepository: ref.watch(localAssetProvider), + hostService: ref.watch(platformMessagesImpl), ), ); diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index 40025e8597..be3c7a7a71 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -1,3 +1,4 @@ +// dart format width=80 // GENERATED CODE - DO NOT MODIFY BY HAND // ************************************************************************** @@ -13,10 +14,7 @@ part of 'router.dart'; /// [ActivitiesPage] class ActivitiesRoute extends PageRouteInfo { const ActivitiesRoute({List? children}) - : super( - ActivitiesRoute.name, - initialChildren: children, - ); + : super(ActivitiesRoute.name, initialChildren: children); static const String name = 'ActivitiesRoute'; @@ -37,13 +35,13 @@ class AlbumAdditionalSharedUserSelectionRoute required Album album, List? children, }) : super( - AlbumAdditionalSharedUserSelectionRoute.name, - args: AlbumAdditionalSharedUserSelectionRouteArgs( - key: key, - album: album, - ), - initialChildren: children, - ); + AlbumAdditionalSharedUserSelectionRoute.name, + args: AlbumAdditionalSharedUserSelectionRouteArgs( + key: key, + album: album, + ), + initialChildren: children, + ); static const String name = 'AlbumAdditionalSharedUserSelectionRoute'; @@ -85,14 +83,14 @@ class AlbumAssetSelectionRoute bool canDeselect = false, List? children, }) : super( - AlbumAssetSelectionRoute.name, - args: AlbumAssetSelectionRouteArgs( - key: key, - existingAssets: existingAssets, - canDeselect: canDeselect, - ), - initialChildren: children, - ); + AlbumAssetSelectionRoute.name, + args: AlbumAssetSelectionRouteArgs( + key: key, + existingAssets: existingAssets, + canDeselect: canDeselect, + ), + initialChildren: children, + ); static const String name = 'AlbumAssetSelectionRoute'; @@ -132,10 +130,7 @@ class AlbumAssetSelectionRouteArgs { /// [AlbumOptionsPage] class AlbumOptionsRoute extends PageRouteInfo { const AlbumOptionsRoute({List? children}) - : super( - AlbumOptionsRoute.name, - initialChildren: children, - ); + : super(AlbumOptionsRoute.name, initialChildren: children); static const String name = 'AlbumOptionsRoute'; @@ -155,13 +150,10 @@ class AlbumPreviewRoute extends PageRouteInfo { required Album album, List? children, }) : super( - AlbumPreviewRoute.name, - args: AlbumPreviewRouteArgs( - key: key, - album: album, - ), - initialChildren: children, - ); + AlbumPreviewRoute.name, + args: AlbumPreviewRouteArgs(key: key, album: album), + initialChildren: children, + ); static const String name = 'AlbumPreviewRoute'; @@ -169,19 +161,13 @@ class AlbumPreviewRoute extends PageRouteInfo { name, builder: (data) { final args = data.argsAs(); - return AlbumPreviewPage( - key: args.key, - album: args.album, - ); + return AlbumPreviewPage(key: args.key, album: args.album); }, ); } class AlbumPreviewRouteArgs { - const AlbumPreviewRouteArgs({ - this.key, - required this.album, - }); + const AlbumPreviewRouteArgs({this.key, required this.album}); final Key? key; @@ -202,13 +188,10 @@ class AlbumSharedUserSelectionRoute required Set assets, List? children, }) : super( - AlbumSharedUserSelectionRoute.name, - args: AlbumSharedUserSelectionRouteArgs( - key: key, - assets: assets, - ), - initialChildren: children, - ); + AlbumSharedUserSelectionRoute.name, + args: AlbumSharedUserSelectionRouteArgs(key: key, assets: assets), + initialChildren: children, + ); static const String name = 'AlbumSharedUserSelectionRoute'; @@ -216,19 +199,13 @@ class AlbumSharedUserSelectionRoute name, builder: (data) { final args = data.argsAs(); - return AlbumSharedUserSelectionPage( - key: args.key, - assets: args.assets, - ); + return AlbumSharedUserSelectionPage(key: args.key, assets: args.assets); }, ); } class AlbumSharedUserSelectionRouteArgs { - const AlbumSharedUserSelectionRouteArgs({ - this.key, - required this.assets, - }); + const AlbumSharedUserSelectionRouteArgs({this.key, required this.assets}); final Key? key; @@ -248,13 +225,10 @@ class AlbumViewerRoute extends PageRouteInfo { required int albumId, List? children, }) : super( - AlbumViewerRoute.name, - args: AlbumViewerRouteArgs( - key: key, - albumId: albumId, - ), - initialChildren: children, - ); + AlbumViewerRoute.name, + args: AlbumViewerRouteArgs(key: key, albumId: albumId), + initialChildren: children, + ); static const String name = 'AlbumViewerRoute'; @@ -262,19 +236,13 @@ class AlbumViewerRoute extends PageRouteInfo { name, builder: (data) { final args = data.argsAs(); - return AlbumViewerPage( - key: args.key, - albumId: args.albumId, - ); + return AlbumViewerPage(key: args.key, albumId: args.albumId); }, ); } class AlbumViewerRouteArgs { - const AlbumViewerRouteArgs({ - this.key, - required this.albumId, - }); + const AlbumViewerRouteArgs({this.key, required this.albumId}); final Key? key; @@ -290,10 +258,7 @@ class AlbumViewerRouteArgs { /// [AlbumsPage] class AlbumsRoute extends PageRouteInfo { const AlbumsRoute({List? children}) - : super( - AlbumsRoute.name, - initialChildren: children, - ); + : super(AlbumsRoute.name, initialChildren: children); static const String name = 'AlbumsRoute'; @@ -309,10 +274,7 @@ class AlbumsRoute extends PageRouteInfo { /// [AllMotionPhotosPage] class AllMotionPhotosRoute extends PageRouteInfo { const AllMotionPhotosRoute({List? children}) - : super( - AllMotionPhotosRoute.name, - initialChildren: children, - ); + : super(AllMotionPhotosRoute.name, initialChildren: children); static const String name = 'AllMotionPhotosRoute'; @@ -328,10 +290,7 @@ class AllMotionPhotosRoute extends PageRouteInfo { /// [AllPeoplePage] class AllPeopleRoute extends PageRouteInfo { const AllPeopleRoute({List? children}) - : super( - AllPeopleRoute.name, - initialChildren: children, - ); + : super(AllPeopleRoute.name, initialChildren: children); static const String name = 'AllPeopleRoute'; @@ -347,10 +306,7 @@ class AllPeopleRoute extends PageRouteInfo { /// [AllPlacesPage] class AllPlacesRoute extends PageRouteInfo { const AllPlacesRoute({List? children}) - : super( - AllPlacesRoute.name, - initialChildren: children, - ); + : super(AllPlacesRoute.name, initialChildren: children); static const String name = 'AllPlacesRoute'; @@ -366,10 +322,7 @@ class AllPlacesRoute extends PageRouteInfo { /// [AllVideosPage] class AllVideosRoute extends PageRouteInfo { const AllVideosRoute({List? children}) - : super( - AllVideosRoute.name, - initialChildren: children, - ); + : super(AllVideosRoute.name, initialChildren: children); static const String name = 'AllVideosRoute'; @@ -389,13 +342,10 @@ class AppLogDetailRoute extends PageRouteInfo { required LogMessage logMessage, List? children, }) : super( - AppLogDetailRoute.name, - args: AppLogDetailRouteArgs( - key: key, - logMessage: logMessage, - ), - initialChildren: children, - ); + AppLogDetailRoute.name, + args: AppLogDetailRouteArgs(key: key, logMessage: logMessage), + initialChildren: children, + ); static const String name = 'AppLogDetailRoute'; @@ -403,19 +353,13 @@ class AppLogDetailRoute extends PageRouteInfo { name, builder: (data) { final args = data.argsAs(); - return AppLogDetailPage( - key: args.key, - logMessage: args.logMessage, - ); + return AppLogDetailPage(key: args.key, logMessage: args.logMessage); }, ); } class AppLogDetailRouteArgs { - const AppLogDetailRouteArgs({ - this.key, - required this.logMessage, - }); + const AppLogDetailRouteArgs({this.key, required this.logMessage}); final Key? key; @@ -431,10 +375,7 @@ class AppLogDetailRouteArgs { /// [AppLogPage] class AppLogRoute extends PageRouteInfo { const AppLogRoute({List? children}) - : super( - AppLogRoute.name, - initialChildren: children, - ); + : super(AppLogRoute.name, initialChildren: children); static const String name = 'AppLogRoute'; @@ -450,10 +391,7 @@ class AppLogRoute extends PageRouteInfo { /// [ArchivePage] class ArchiveRoute extends PageRouteInfo { const ArchiveRoute({List? children}) - : super( - ArchiveRoute.name, - initialChildren: children, - ); + : super(ArchiveRoute.name, initialChildren: children); static const String name = 'ArchiveRoute'; @@ -469,10 +407,7 @@ class ArchiveRoute extends PageRouteInfo { /// [BackupAlbumSelectionPage] class BackupAlbumSelectionRoute extends PageRouteInfo { const BackupAlbumSelectionRoute({List? children}) - : super( - BackupAlbumSelectionRoute.name, - initialChildren: children, - ); + : super(BackupAlbumSelectionRoute.name, initialChildren: children); static const String name = 'BackupAlbumSelectionRoute'; @@ -488,10 +423,7 @@ class BackupAlbumSelectionRoute extends PageRouteInfo { /// [BackupControllerPage] class BackupControllerRoute extends PageRouteInfo { const BackupControllerRoute({List? children}) - : super( - BackupControllerRoute.name, - initialChildren: children, - ); + : super(BackupControllerRoute.name, initialChildren: children); static const String name = 'BackupControllerRoute'; @@ -507,10 +439,7 @@ class BackupControllerRoute extends PageRouteInfo { /// [BackupOptionsPage] class BackupOptionsRoute extends PageRouteInfo { const BackupOptionsRoute({List? children}) - : super( - BackupOptionsRoute.name, - initialChildren: children, - ); + : super(BackupOptionsRoute.name, initialChildren: children); static const String name = 'BackupOptionsRoute'; @@ -526,10 +455,7 @@ class BackupOptionsRoute extends PageRouteInfo { /// [ChangePasswordPage] class ChangePasswordRoute extends PageRouteInfo { const ChangePasswordRoute({List? children}) - : super( - ChangePasswordRoute.name, - initialChildren: children, - ); + : super(ChangePasswordRoute.name, initialChildren: children); static const String name = 'ChangePasswordRoute'; @@ -549,13 +475,10 @@ class CreateAlbumRoute extends PageRouteInfo { List? assets, List? children, }) : super( - CreateAlbumRoute.name, - args: CreateAlbumRouteArgs( - key: key, - assets: assets, - ), - initialChildren: children, - ); + CreateAlbumRoute.name, + args: CreateAlbumRouteArgs(key: key, assets: assets), + initialChildren: children, + ); static const String name = 'CreateAlbumRoute'; @@ -563,20 +486,15 @@ class CreateAlbumRoute extends PageRouteInfo { name, builder: (data) { final args = data.argsAs( - orElse: () => const CreateAlbumRouteArgs()); - return CreateAlbumPage( - key: args.key, - assets: args.assets, + orElse: () => const CreateAlbumRouteArgs(), ); + return CreateAlbumPage(key: args.key, assets: args.assets); }, ); } class CreateAlbumRouteArgs { - const CreateAlbumRouteArgs({ - this.key, - this.assets, - }); + const CreateAlbumRouteArgs({this.key, this.assets}); final Key? key; @@ -597,14 +515,10 @@ class CropImageRoute extends PageRouteInfo { required Asset asset, List? children, }) : super( - CropImageRoute.name, - args: CropImageRouteArgs( - key: key, - image: image, - asset: asset, - ), - initialChildren: children, - ); + CropImageRoute.name, + args: CropImageRouteArgs(key: key, image: image, asset: asset), + initialChildren: children, + ); static const String name = 'CropImageRoute'; @@ -612,11 +526,7 @@ class CropImageRoute extends PageRouteInfo { name, builder: (data) { final args = data.argsAs(); - return CropImagePage( - key: args.key, - image: args.image, - asset: args.asset, - ); + return CropImagePage(key: args.key, image: args.image, asset: args.asset); }, ); } @@ -650,15 +560,15 @@ class EditImageRoute extends PageRouteInfo { required bool isEdited, List? children, }) : super( - EditImageRoute.name, - args: EditImageRouteArgs( - key: key, - asset: asset, - image: image, - isEdited: isEdited, - ), - initialChildren: children, - ); + EditImageRoute.name, + args: EditImageRouteArgs( + key: key, + asset: asset, + image: image, + isEdited: isEdited, + ), + initialChildren: children, + ); static const String name = 'EditImageRoute'; @@ -702,10 +612,7 @@ class EditImageRouteArgs { /// [FailedBackupStatusPage] class FailedBackupStatusRoute extends PageRouteInfo { const FailedBackupStatusRoute({List? children}) - : super( - FailedBackupStatusRoute.name, - initialChildren: children, - ); + : super(FailedBackupStatusRoute.name, initialChildren: children); static const String name = 'FailedBackupStatusRoute'; @@ -721,10 +628,7 @@ class FailedBackupStatusRoute extends PageRouteInfo { /// [FavoritesPage] class FavoritesRoute extends PageRouteInfo { const FavoritesRoute({List? children}) - : super( - FavoritesRoute.name, - initialChildren: children, - ); + : super(FavoritesRoute.name, initialChildren: children); static const String name = 'FavoritesRoute'; @@ -740,10 +644,7 @@ class FavoritesRoute extends PageRouteInfo { /// [FeatInDevPage] class FeatInDevRoute extends PageRouteInfo { const FeatInDevRoute({List? children}) - : super( - FeatInDevRoute.name, - initialChildren: children, - ); + : super(FeatInDevRoute.name, initialChildren: children); static const String name = 'FeatInDevRoute'; @@ -764,14 +665,10 @@ class FilterImageRoute extends PageRouteInfo { required Asset asset, List? children, }) : super( - FilterImageRoute.name, - args: FilterImageRouteArgs( - key: key, - image: image, - asset: asset, - ), - initialChildren: children, - ); + FilterImageRoute.name, + args: FilterImageRouteArgs(key: key, image: image, asset: asset), + initialChildren: children, + ); static const String name = 'FilterImageRoute'; @@ -815,34 +712,26 @@ class FolderRoute extends PageRouteInfo { RecursiveFolder? folder, List? children, }) : super( - FolderRoute.name, - args: FolderRouteArgs( - key: key, - folder: folder, - ), - initialChildren: children, - ); + FolderRoute.name, + args: FolderRouteArgs(key: key, folder: folder), + initialChildren: children, + ); static const String name = 'FolderRoute'; static PageInfo page = PageInfo( name, builder: (data) { - final args = - data.argsAs(orElse: () => const FolderRouteArgs()); - return FolderPage( - key: args.key, - folder: args.folder, + final args = data.argsAs( + orElse: () => const FolderRouteArgs(), ); + return FolderPage(key: args.key, folder: args.folder); }, ); } class FolderRouteArgs { - const FolderRouteArgs({ - this.key, - this.folder, - }); + const FolderRouteArgs({this.key, this.folder}); final Key? key; @@ -865,16 +754,16 @@ class GalleryViewerRoute extends PageRouteInfo { bool showStack = false, List? children, }) : super( - GalleryViewerRoute.name, - args: GalleryViewerRouteArgs( - key: key, - renderList: renderList, - initialIndex: initialIndex, - heroOffset: heroOffset, - showStack: showStack, - ), - initialChildren: children, - ); + GalleryViewerRoute.name, + args: GalleryViewerRouteArgs( + key: key, + renderList: renderList, + initialIndex: initialIndex, + heroOffset: heroOffset, + showStack: showStack, + ), + initialChildren: children, + ); static const String name = 'GalleryViewerRoute'; @@ -922,10 +811,7 @@ class GalleryViewerRouteArgs { /// [HeaderSettingsPage] class HeaderSettingsRoute extends PageRouteInfo { const HeaderSettingsRoute({List? children}) - : super( - HeaderSettingsRoute.name, - initialChildren: children, - ); + : super(HeaderSettingsRoute.name, initialChildren: children); static const String name = 'HeaderSettingsRoute'; @@ -941,10 +827,7 @@ class HeaderSettingsRoute extends PageRouteInfo { /// [LibraryPage] class LibraryRoute extends PageRouteInfo { const LibraryRoute({List? children}) - : super( - LibraryRoute.name, - initialChildren: children, - ); + : super(LibraryRoute.name, initialChildren: children); static const String name = 'LibraryRoute'; @@ -960,10 +843,7 @@ class LibraryRoute extends PageRouteInfo { /// [LocalAlbumsPage] class LocalAlbumsRoute extends PageRouteInfo { const LocalAlbumsRoute({List? children}) - : super( - LocalAlbumsRoute.name, - initialChildren: children, - ); + : super(LocalAlbumsRoute.name, initialChildren: children); static const String name = 'LocalAlbumsRoute'; @@ -979,10 +859,7 @@ class LocalAlbumsRoute extends PageRouteInfo { /// [LoginPage] class LoginRoute extends PageRouteInfo { const LoginRoute({List? children}) - : super( - LoginRoute.name, - initialChildren: children, - ); + : super(LoginRoute.name, initialChildren: children); static const String name = 'LoginRoute'; @@ -1002,13 +879,13 @@ class MapLocationPickerRoute extends PageRouteInfo { LatLng initialLatLng = const LatLng(0, 0), List? children, }) : super( - MapLocationPickerRoute.name, - args: MapLocationPickerRouteArgs( - key: key, - initialLatLng: initialLatLng, - ), - initialChildren: children, - ); + MapLocationPickerRoute.name, + args: MapLocationPickerRouteArgs( + key: key, + initialLatLng: initialLatLng, + ), + initialChildren: children, + ); static const String name = 'MapLocationPickerRoute'; @@ -1016,7 +893,8 @@ class MapLocationPickerRoute extends PageRouteInfo { name, builder: (data) { final args = data.argsAs( - orElse: () => const MapLocationPickerRouteArgs()); + orElse: () => const MapLocationPickerRouteArgs(), + ); return MapLocationPickerPage( key: args.key, initialLatLng: args.initialLatLng, @@ -1044,39 +922,28 @@ class MapLocationPickerRouteArgs { /// generated route for /// [MapPage] class MapRoute extends PageRouteInfo { - MapRoute({ - Key? key, - LatLng? initialLocation, - List? children, - }) : super( - MapRoute.name, - args: MapRouteArgs( - key: key, - initialLocation: initialLocation, - ), - initialChildren: children, - ); + MapRoute({Key? key, LatLng? initialLocation, List? children}) + : super( + MapRoute.name, + args: MapRouteArgs(key: key, initialLocation: initialLocation), + initialChildren: children, + ); static const String name = 'MapRoute'; static PageInfo page = PageInfo( name, builder: (data) { - final args = - data.argsAs(orElse: () => const MapRouteArgs()); - return MapPage( - key: args.key, - initialLocation: args.initialLocation, + final args = data.argsAs( + orElse: () => const MapRouteArgs(), ); + return MapPage(key: args.key, initialLocation: args.initialLocation); }, ); } class MapRouteArgs { - const MapRouteArgs({ - this.key, - this.initialLocation, - }); + const MapRouteArgs({this.key, this.initialLocation}); final Key? key; @@ -1097,14 +964,14 @@ class MemoryRoute extends PageRouteInfo { Key? key, List? children, }) : super( - MemoryRoute.name, - args: MemoryRouteArgs( - memories: memories, - memoryIndex: memoryIndex, - key: key, - ), - initialChildren: children, - ); + MemoryRoute.name, + args: MemoryRouteArgs( + memories: memories, + memoryIndex: memoryIndex, + key: key, + ), + initialChildren: children, + ); static const String name = 'MemoryRoute'; @@ -1151,16 +1018,16 @@ class NativeVideoViewerRoute extends PageRouteInfo { int playbackDelayFactor = 1, List? children, }) : super( - NativeVideoViewerRoute.name, - args: NativeVideoViewerRouteArgs( - key: key, - asset: asset, - image: image, - showControls: showControls, - playbackDelayFactor: playbackDelayFactor, - ), - initialChildren: children, - ); + NativeVideoViewerRoute.name, + args: NativeVideoViewerRouteArgs( + key: key, + asset: asset, + image: image, + showControls: showControls, + playbackDelayFactor: playbackDelayFactor, + ), + initialChildren: children, + ); static const String name = 'NativeVideoViewerRoute'; @@ -1212,13 +1079,10 @@ class PartnerDetailRoute extends PageRouteInfo { required UserDto partner, List? children, }) : super( - PartnerDetailRoute.name, - args: PartnerDetailRouteArgs( - key: key, - partner: partner, - ), - initialChildren: children, - ); + PartnerDetailRoute.name, + args: PartnerDetailRouteArgs(key: key, partner: partner), + initialChildren: children, + ); static const String name = 'PartnerDetailRoute'; @@ -1226,19 +1090,13 @@ class PartnerDetailRoute extends PageRouteInfo { name, builder: (data) { final args = data.argsAs(); - return PartnerDetailPage( - key: args.key, - partner: args.partner, - ); + return PartnerDetailPage(key: args.key, partner: args.partner); }, ); } class PartnerDetailRouteArgs { - const PartnerDetailRouteArgs({ - this.key, - required this.partner, - }); + const PartnerDetailRouteArgs({this.key, required this.partner}); final Key? key; @@ -1254,10 +1112,7 @@ class PartnerDetailRouteArgs { /// [PartnerPage] class PartnerRoute extends PageRouteInfo { const PartnerRoute({List? children}) - : super( - PartnerRoute.name, - initialChildren: children, - ); + : super(PartnerRoute.name, initialChildren: children); static const String name = 'PartnerRoute'; @@ -1273,10 +1128,7 @@ class PartnerRoute extends PageRouteInfo { /// [PeopleCollectionPage] class PeopleCollectionRoute extends PageRouteInfo { const PeopleCollectionRoute({List? children}) - : super( - PeopleCollectionRoute.name, - initialChildren: children, - ); + : super(PeopleCollectionRoute.name, initialChildren: children); static const String name = 'PeopleCollectionRoute'; @@ -1292,10 +1144,7 @@ class PeopleCollectionRoute extends PageRouteInfo { /// [PermissionOnboardingPage] class PermissionOnboardingRoute extends PageRouteInfo { const PermissionOnboardingRoute({List? children}) - : super( - PermissionOnboardingRoute.name, - initialChildren: children, - ); + : super(PermissionOnboardingRoute.name, initialChildren: children); static const String name = 'PermissionOnboardingRoute'; @@ -1316,14 +1165,14 @@ class PersonResultRoute extends PageRouteInfo { required String personName, List? children, }) : super( - PersonResultRoute.name, - args: PersonResultRouteArgs( - key: key, - personId: personId, - personName: personName, - ), - initialChildren: children, - ); + PersonResultRoute.name, + args: PersonResultRouteArgs( + key: key, + personId: personId, + personName: personName, + ), + initialChildren: children, + ); static const String name = 'PersonResultRoute'; @@ -1363,10 +1212,7 @@ class PersonResultRouteArgs { /// [PhotosPage] class PhotosRoute extends PageRouteInfo { const PhotosRoute({List? children}) - : super( - PhotosRoute.name, - initialChildren: children, - ); + : super(PhotosRoute.name, initialChildren: children); static const String name = 'PhotosRoute'; @@ -1386,13 +1232,13 @@ class PlacesCollectionRoute extends PageRouteInfo { LatLng? currentLocation, List? children, }) : super( - PlacesCollectionRoute.name, - args: PlacesCollectionRouteArgs( - key: key, - currentLocation: currentLocation, - ), - initialChildren: children, - ); + PlacesCollectionRoute.name, + args: PlacesCollectionRouteArgs( + key: key, + currentLocation: currentLocation, + ), + initialChildren: children, + ); static const String name = 'PlacesCollectionRoute'; @@ -1400,7 +1246,8 @@ class PlacesCollectionRoute extends PageRouteInfo { name, builder: (data) { final args = data.argsAs( - orElse: () => const PlacesCollectionRouteArgs()); + orElse: () => const PlacesCollectionRouteArgs(), + ); return PlacesCollectionPage( key: args.key, currentLocation: args.currentLocation, @@ -1410,10 +1257,7 @@ class PlacesCollectionRoute extends PageRouteInfo { } class PlacesCollectionRouteArgs { - const PlacesCollectionRouteArgs({ - this.key, - this.currentLocation, - }); + const PlacesCollectionRouteArgs({this.key, this.currentLocation}); final Key? key; @@ -1429,10 +1273,7 @@ class PlacesCollectionRouteArgs { /// [RecentlyTakenPage] class RecentlyTakenRoute extends PageRouteInfo { const RecentlyTakenRoute({List? children}) - : super( - RecentlyTakenRoute.name, - initialChildren: children, - ); + : super(RecentlyTakenRoute.name, initialChildren: children); static const String name = 'RecentlyTakenRoute'; @@ -1452,34 +1293,26 @@ class SearchRoute extends PageRouteInfo { SearchFilter? prefilter, List? children, }) : super( - SearchRoute.name, - args: SearchRouteArgs( - key: key, - prefilter: prefilter, - ), - initialChildren: children, - ); + SearchRoute.name, + args: SearchRouteArgs(key: key, prefilter: prefilter), + initialChildren: children, + ); static const String name = 'SearchRoute'; static PageInfo page = PageInfo( name, builder: (data) { - final args = - data.argsAs(orElse: () => const SearchRouteArgs()); - return SearchPage( - key: args.key, - prefilter: args.prefilter, + final args = data.argsAs( + orElse: () => const SearchRouteArgs(), ); + return SearchPage(key: args.key, prefilter: args.prefilter); }, ); } class SearchRouteArgs { - const SearchRouteArgs({ - this.key, - this.prefilter, - }); + const SearchRouteArgs({this.key, this.prefilter}); final Key? key; @@ -1495,10 +1328,7 @@ class SearchRouteArgs { /// [SettingsPage] class SettingsRoute extends PageRouteInfo { const SettingsRoute({List? children}) - : super( - SettingsRoute.name, - initialChildren: children, - ); + : super(SettingsRoute.name, initialChildren: children); static const String name = 'SettingsRoute'; @@ -1518,13 +1348,10 @@ class SettingsSubRoute extends PageRouteInfo { Key? key, List? children, }) : super( - SettingsSubRoute.name, - args: SettingsSubRouteArgs( - section: section, - key: key, - ), - initialChildren: children, - ); + SettingsSubRoute.name, + args: SettingsSubRouteArgs(section: section, key: key), + initialChildren: children, + ); static const String name = 'SettingsSubRoute'; @@ -1532,19 +1359,13 @@ class SettingsSubRoute extends PageRouteInfo { name, builder: (data) { final args = data.argsAs(); - return SettingsSubPage( - args.section, - key: args.key, - ); + return SettingsSubPage(args.section, key: args.key); }, ); } class SettingsSubRouteArgs { - const SettingsSubRouteArgs({ - required this.section, - this.key, - }); + const SettingsSubRouteArgs({required this.section, this.key}); final SettingSection section; @@ -1564,13 +1385,10 @@ class ShareIntentRoute extends PageRouteInfo { required List attachments, List? children, }) : super( - ShareIntentRoute.name, - args: ShareIntentRouteArgs( - key: key, - attachments: attachments, - ), - initialChildren: children, - ); + ShareIntentRoute.name, + args: ShareIntentRouteArgs(key: key, attachments: attachments), + initialChildren: children, + ); static const String name = 'ShareIntentRoute'; @@ -1578,19 +1396,13 @@ class ShareIntentRoute extends PageRouteInfo { name, builder: (data) { final args = data.argsAs(); - return ShareIntentPage( - key: args.key, - attachments: args.attachments, - ); + return ShareIntentPage(key: args.key, attachments: args.attachments); }, ); } class ShareIntentRouteArgs { - const ShareIntentRouteArgs({ - this.key, - required this.attachments, - }); + const ShareIntentRouteArgs({this.key, required this.attachments}); final Key? key; @@ -1612,15 +1424,15 @@ class SharedLinkEditRoute extends PageRouteInfo { String? albumId, List? children, }) : super( - SharedLinkEditRoute.name, - args: SharedLinkEditRouteArgs( - key: key, - existingLink: existingLink, - assetsList: assetsList, - albumId: albumId, - ), - initialChildren: children, - ); + SharedLinkEditRoute.name, + args: SharedLinkEditRouteArgs( + key: key, + existingLink: existingLink, + assetsList: assetsList, + albumId: albumId, + ), + initialChildren: children, + ); static const String name = 'SharedLinkEditRoute'; @@ -1628,7 +1440,8 @@ class SharedLinkEditRoute extends PageRouteInfo { name, builder: (data) { final args = data.argsAs( - orElse: () => const SharedLinkEditRouteArgs()); + orElse: () => const SharedLinkEditRouteArgs(), + ); return SharedLinkEditPage( key: args.key, existingLink: args.existingLink, @@ -1665,10 +1478,7 @@ class SharedLinkEditRouteArgs { /// [SharedLinkPage] class SharedLinkRoute extends PageRouteInfo { const SharedLinkRoute({List? children}) - : super( - SharedLinkRoute.name, - initialChildren: children, - ); + : super(SharedLinkRoute.name, initialChildren: children); static const String name = 'SharedLinkRoute'; @@ -1684,10 +1494,7 @@ class SharedLinkRoute extends PageRouteInfo { /// [SplashScreenPage] class SplashScreenRoute extends PageRouteInfo { const SplashScreenRoute({List? children}) - : super( - SplashScreenRoute.name, - initialChildren: children, - ); + : super(SplashScreenRoute.name, initialChildren: children); static const String name = 'SplashScreenRoute'; @@ -1703,10 +1510,7 @@ class SplashScreenRoute extends PageRouteInfo { /// [TabControllerPage] class TabControllerRoute extends PageRouteInfo { const TabControllerRoute({List? children}) - : super( - TabControllerRoute.name, - initialChildren: children, - ); + : super(TabControllerRoute.name, initialChildren: children); static const String name = 'TabControllerRoute'; @@ -1722,10 +1526,7 @@ class TabControllerRoute extends PageRouteInfo { /// [TrashPage] class TrashRoute extends PageRouteInfo { const TrashRoute({List? children}) - : super( - TrashRoute.name, - initialChildren: children, - ); + : super(TrashRoute.name, initialChildren: children); static const String name = 'TrashRoute'; diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index d9e821d332..b30c86b3f0 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -5,31 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 url: "https://pub.dev" source: hosted - version: "76.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.3" + version: "80.0.0" analyzer: - dependency: "direct overridden" + dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "7.3.0" analyzer_plugin: - dependency: "direct overridden" + dependency: transitive description: name: analyzer_plugin - sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161" + sha256: b3075265c5ab222f8b3188342dcb50b476286394a40323e85d1fa725035d40a4 url: "https://pub.dev" source: hosted - version: "0.11.3" + version: "0.13.0" ansicolor: dependency: transitive description: @@ -74,10 +69,10 @@ packages: dependency: "direct dev" description: name: auto_route_generator - sha256: c9086eb07271e51b44071ad5cff34e889f3156710b964a308c2ab590769e79e6 + sha256: c2e359d8932986d4d1bcad7a428143f81384ce10fef8d4aa5bc29e1f83766a46 url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "9.3.1" background_downloader: dependency: "direct main" description: @@ -322,34 +317,42 @@ packages: dependency: "direct dev" description: name: custom_lint - sha256: "4500e88854e7581ee43586abeaf4443cb22375d6d289241a87b1aadf678d5545" + sha256: "409c485fd14f544af1da965d5a0d160ee57cd58b63eeaa7280a4f28cf5bda7f1" url: "https://pub.dev" source: hosted - version: "0.6.10" + version: "0.7.5" custom_lint_builder: dependency: transitive description: name: custom_lint_builder - sha256: "5a95eff100da256fbf086b329c17c8b49058c261cdf56d3a4157d3c31c511d78" + sha256: "107e0a43606138015777590ee8ce32f26ba7415c25b722ff0908a6f5d7a4c228" url: "https://pub.dev" source: hosted - version: "0.6.10" + version: "0.7.5" custom_lint_core: dependency: transitive description: name: custom_lint_core - sha256: "76a4046cc71d976222a078a8fd4a65e198b70545a8d690a75196dd14f08510f6" + sha256: "31110af3dde9d29fb10828ca33f1dce24d2798477b167675543ce3d208dee8be" url: "https://pub.dev" source: hosted - version: "0.6.10" + version: "0.7.5" + custom_lint_visitor: + dependency: transitive + description: + name: custom_lint_visitor + sha256: "36282d85714af494ee2d7da8c8913630aa6694da99f104fb2ed4afcf8fc857d8" + url: "https://pub.dev" + source: hosted + version: "1.0.0+7.3.0" dart_style: dependency: transitive description: name: dart_style - sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" + sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" url: "https://pub.dev" source: hosted - version: "2.3.8" + version: "3.0.1" dartx: dependency: transitive description: @@ -675,10 +678,10 @@ packages: dependency: transitive description: name: freezed_annotation - sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "3.0.0" frontend_server_client: dependency: transitive description: @@ -923,10 +926,11 @@ packages: isar_generator: dependency: "direct dev" description: - name: isar_generator - sha256: "484e73d3b7e81dbd816852fe0b9497333118a9aeb646fd2d349a62cc8980ffe1" - url: "https://pub.isar-community.dev" - source: hosted + path: "packages/isar_generator" + ref: v3 + resolved-ref: ad574f60ed6f39d2995cd16fc7dc3de9a646ef30 + url: "https://github.com/callumw-k/isar" + source: git version: "3.1.8" js: dependency: transitive @@ -984,14 +988,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.dev" - source: hosted - version: "0.1.3-main.0" maplibre_gl: dependency: "direct main" description: @@ -1033,7 +1029,7 @@ packages: source: hosted version: "0.11.1" meta: - dependency: "direct overridden" + dependency: transitive description: name: meta sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c @@ -1264,6 +1260,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + pigeon: + dependency: "direct dev" + description: + name: pigeon + sha256: "3e4e6258f22760fa11f86d2a5202fb3f8367cb361d33bd9a93de85a7959e9976" + url: "https://pub.dev" + source: hosted + version: "25.3.1" platform: dependency: "direct main" description: @@ -1348,10 +1352,10 @@ packages: dependency: transitive description: name: riverpod_analyzer_utils - sha256: "0dcb0af32d561f8fa000c6a6d95633c9fb08ea8a8df46e3f9daca59f11218167" + sha256: "03a17170088c63aab6c54c44456f5ab78876a1ddb6032ffde1662ddab4959611" url: "https://pub.dev" source: hosted - version: "0.5.6" + version: "0.5.10" riverpod_annotation: dependency: "direct main" description: @@ -1364,18 +1368,18 @@ packages: dependency: "direct dev" description: name: riverpod_generator - sha256: "851aedac7ad52693d12af3bf6d92b1626d516ed6b764eb61bf19e968b5e0b931" + sha256: "44a0992d54473eb199ede00e2260bd3c262a86560e3c6f6374503d86d0580e36" url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "2.6.5" riverpod_lint: dependency: "direct dev" description: name: riverpod_lint - sha256: "0684c21a9a4582c28c897d55c7b611fa59a351579061b43f8c92c005804e63a8" + sha256: "89a52b7334210dbff8605c3edf26cfe69b15062beed5cbfeff2c3812c33c9e35" url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "2.6.5" rxdart: dependency: transitive description: @@ -1537,10 +1541,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "2.0.0" source_span: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index cf0b9b9106..30bffa85e2 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -82,11 +82,6 @@ dependencies: drift: ^2.23.1 drift_flutter: ^0.2.4 -dependency_overrides: - analyzer: ^6.0.0 - meta: ^1.11.0 - analyzer_plugin: ^0.11.3 - dev_dependencies: flutter_test: sdk: flutter @@ -96,11 +91,13 @@ dev_dependencies: flutter_launcher_icons: ^0.14.3 flutter_native_splash: ^2.4.5 isar_generator: - version: *isar_version - hosted: https://pub.isar-community.dev/ + git: + url: https://github.com/callumw-k/isar + ref: v3 + path: packages/isar_generator/ integration_test: sdk: flutter - custom_lint: ^0.6.4 + custom_lint: ^0.7.5 riverpod_lint: ^2.6.1 riverpod_generator: ^2.6.1 mocktail: ^1.0.4 @@ -110,6 +107,8 @@ dev_dependencies: file: ^7.0.1 # for MemoryFileSystem # Drift generator drift_dev: ^2.23.1 + # Type safe platform code + pigeon: ^25.3.1 flutter: uses-material-design: true diff --git a/mobile/test/domain/service.mock.dart b/mobile/test/domain/service.mock.dart index 97a3f30294..ba1bf1dc72 100644 --- a/mobile/test/domain/service.mock.dart +++ b/mobile/test/domain/service.mock.dart @@ -1,6 +1,7 @@ import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/domain/utils/background_sync.dart'; +import 'package:immich_mobile/platform/messages.g.dart'; import 'package:mocktail/mocktail.dart'; class MockStoreService extends Mock implements StoreService {} @@ -8,3 +9,5 @@ class MockStoreService extends Mock implements StoreService {} class MockUserService extends Mock implements UserService {} class MockBackgroundSyncManager extends Mock implements BackgroundSyncManager {} + +class MockHostService extends Mock implements ImHostService {} diff --git a/mobile/test/domain/services/device_sync_service_test.dart b/mobile/test/domain/services/device_sync_service_test.dart index d8424f903e..2db309ff40 100644 --- a/mobile/test/domain/services/device_sync_service_test.dart +++ b/mobile/test/domain/services/device_sync_service_test.dart @@ -2,21 +2,21 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/domain/interfaces/album_media.interface.dart'; import 'package:immich_mobile/domain/interfaces/local_album.interface.dart'; -import 'package:immich_mobile/domain/interfaces/local_asset.interface.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/local_album.model.dart'; import 'package:immich_mobile/domain/services/device_sync.service.dart'; -import 'package:immich_mobile/utils/nullable_value.dart'; +import 'package:immich_mobile/platform/messages.g.dart'; import 'package:mocktail/mocktail.dart'; import '../../fixtures/local_album.stub.dart'; import '../../fixtures/local_asset.stub.dart'; import '../../infrastructure/repository.mock.dart'; +import '../service.mock.dart'; void main() { late IAlbumMediaRepository mockAlbumMediaRepo; late ILocalAlbumRepository mockLocalAlbumRepo; - late ILocalAssetRepository mockLocalAssetRepo; + late ImHostService mockHostService; late DeviceSyncService sut; Future mockTransaction(Future Function() action) => action(); @@ -24,12 +24,12 @@ void main() { setUp(() { mockAlbumMediaRepo = MockAlbumMediaRepository(); mockLocalAlbumRepo = MockLocalAlbumRepository(); - mockLocalAssetRepo = MockLocalAssetRepository(); + mockHostService = MockHostService(); sut = DeviceSyncService( albumMediaRepository: mockAlbumMediaRepo, localAlbumRepository: mockLocalAlbumRepo, - localAssetRepository: mockLocalAssetRepo, + hostService: mockHostService, ); registerFallbackValue(LocalAlbumStub.album1); @@ -51,8 +51,6 @@ void main() { .thenAnswer((_) async => true); when(() => mockLocalAlbumRepo.getAssetsForAlbum(any())) .thenAnswer((_) async => []); - when(() => mockLocalAssetRepo.get(any())) - .thenAnswer((_) async => LocalAssetStub.image1); when(() => mockAlbumMediaRepo.refresh(any())).thenAnswer( (inv) async => LocalAlbumStub.album1.copyWith(id: inv.positionalArguments.first), @@ -250,7 +248,7 @@ void main() { group('addAlbum', () { test( - 'refreshes, gets assets, sets thumbnail, and inserts for non-empty album', + 'refreshes, gets assets, and inserts for non-empty album', () async { final newAlbum = LocalAlbumStub.album1.copyWith(assetCount: 0); final refreshedAlbum = @@ -284,38 +282,33 @@ void main() { expect(capturedAlbum.id, newAlbum.id); expect(capturedAlbum.assetCount, refreshedAlbum.assetCount); expect(capturedAlbum.updatedAt, refreshedAlbum.updatedAt); - expect(capturedAlbum.thumbnailId, assets.first.id); expect(listEquals(capturedAssets, assets), isTrue); }, ); - test( - 'refreshes, skips assets, sets null thumbnail, and inserts for empty album', - () async { - final newAlbum = LocalAlbumStub.album1.copyWith(assetCount: 0); - final refreshedAlbum = - newAlbum.copyWith(updatedAt: DateTime(2024), assetCount: 0); + test('refreshes, skips assets, and inserts for empty album', () async { + final newAlbum = LocalAlbumStub.album1.copyWith(assetCount: 0); + final refreshedAlbum = + newAlbum.copyWith(updatedAt: DateTime(2024), assetCount: 0); - when(() => mockAlbumMediaRepo.refresh(newAlbum.id)) - .thenAnswer((_) async => refreshedAlbum); + when(() => mockAlbumMediaRepo.refresh(newAlbum.id)) + .thenAnswer((_) async => refreshedAlbum); - await sut.addAlbum(newAlbum); + await sut.addAlbum(newAlbum); - verify(() => mockAlbumMediaRepo.refresh(newAlbum.id)).called(1); - verifyNever(() => mockAlbumMediaRepo.getAssetsForAlbum(newAlbum.id)); + verify(() => mockAlbumMediaRepo.refresh(newAlbum.id)).called(1); + verifyNever(() => mockAlbumMediaRepo.getAssetsForAlbum(newAlbum.id)); - final captured = - verify(() => mockLocalAlbumRepo.insert(captureAny(), captureAny())) - .captured; - final capturedAlbum = captured.first as LocalAlbum; - final capturedAssets = captured[1] as List; + final captured = + verify(() => mockLocalAlbumRepo.insert(captureAny(), captureAny())) + .captured; + final capturedAlbum = captured.first as LocalAlbum; + final capturedAssets = captured[1] as List; - expect(capturedAlbum.id, newAlbum.id); - expect(capturedAlbum.assetCount, 0); - expect(capturedAlbum.thumbnailId, isNull); - expect(capturedAssets, isEmpty); - }, - ); + expect(capturedAlbum.id, newAlbum.id); + expect(capturedAlbum.assetCount, 0); + expect(capturedAssets, isEmpty); + }); }); group('removeAlbum', () { @@ -361,11 +354,7 @@ void main() { updateTimeCond: any(named: 'updateTimeCond'), ), ).thenAnswer((_) async => [newAsset]); - final dbAlbumNoThumb = - dbAlbum.copyWith(thumbnailId: const NullableValue.absent()); - - final result = - await sut.updateAlbum(dbAlbumNoThumb, LocalAlbumStub.album1); + final result = await sut.updateAlbum(dbAlbum, LocalAlbumStub.album1); expect(result, isTrue); verify(() => mockAlbumMediaRepo.refresh(dbAlbum.id)).called(1); @@ -376,7 +365,6 @@ void main() { updateTimeCond: any(named: 'updateTimeCond'), ), ).called(1); - verifyNever(() => mockLocalAssetRepo.get(any())); verify(() => mockLocalAlbumRepo.transaction(any())).called(1); verify(() => mockLocalAlbumRepo.addAssets(dbAlbum.id, [newAsset])) @@ -385,10 +373,7 @@ void main() { () => mockLocalAlbumRepo.update( any( that: predicate( - (a) => - a.id == dbAlbum.id && - a.assetCount == 2 && - a.thumbnailId == newAsset.id, + (a) => a.id == dbAlbum.id && a.assetCount == 2, ), ), ), @@ -437,10 +422,7 @@ void main() { () => mockLocalAlbumRepo.update( any( that: predicate( - (a) => - a.id == dbAlbum.id && - a.assetCount == 0 && - a.thumbnailId == null, + (a) => a.id == dbAlbum.id && a.assetCount == 0, ), ), ), @@ -508,11 +490,8 @@ void main() { }); group('checkAddition', () { - final dbAlbum = LocalAlbumStub.album1.copyWith( - updatedAt: DateTime(2024, 1, 1, 10, 0, 0), - assetCount: 1, - thumbnailId: const NullableValue.value("thumb1"), - ); + final dbAlbum = LocalAlbumStub.album1 + .copyWith(updatedAt: DateTime(2024, 1, 1, 10, 0, 0), assetCount: 1); final refreshedAlbum = dbAlbum.copyWith( updatedAt: DateTime(2024, 1, 1, 11, 0, 0), assetCount: 2, @@ -530,13 +509,6 @@ void main() { ), ).thenAnswer((_) async => [newAsset]); - when(() => mockLocalAssetRepo.get("thumb1")).thenAnswer( - (_) async => LocalAssetStub.image1.copyWith( - id: "thumb1", - createdAt: DateTime(2024, 1, 1, 9, 0, 0), - ), - ); - final result = await sut.checkAddition(dbAlbum, refreshedAlbum); expect(result, isTrue); @@ -546,19 +518,15 @@ void main() { updateTimeCond: any(named: 'updateTimeCond'), ), ).called(1); - verify(() => mockLocalAssetRepo.get("thumb1")).called(1); verify(() => mockLocalAlbumRepo.addAssets(dbAlbum.id, [newAsset])) .called(1); verify( () => mockLocalAlbumRepo.update( any( - that: predicate( - (a) => - a.id == dbAlbum.id && - a.assetCount == 2 && - a.updatedAt == refreshedAlbum.updatedAt && - a.thumbnailId == newAsset.id, - ), + that: predicate((a) => + a.id == dbAlbum.id && + a.assetCount == 2 && + a.updatedAt == refreshedAlbum.updatedAt), ), ), ).called(1); @@ -566,93 +534,6 @@ void main() { .called(1); }); - test('returns true and keeps old thumbnail if newer', () async { - final newAsset = LocalAssetStub.image2.copyWith( - id: "asset2", - createdAt: DateTime(2024, 1, 1, 8, 0, 0), - ); - when( - () => mockAlbumMediaRepo.getAssetsForAlbum( - dbAlbum.id, - updateTimeCond: any(named: 'updateTimeCond'), - ), - ).thenAnswer((_) async => [newAsset]); - - when(() => mockLocalAssetRepo.get("thumb1")).thenAnswer( - (_) async => LocalAssetStub.image1.copyWith( - id: "thumb1", - createdAt: DateTime(2024, 1, 1, 9, 0, 0), - ), - ); - - final result = await sut.checkAddition(dbAlbum, refreshedAlbum); - - expect(result, isTrue); - verify( - () => mockAlbumMediaRepo.getAssetsForAlbum( - dbAlbum.id, - updateTimeCond: any(named: 'updateTimeCond'), - ), - ).called(1); - verify(() => mockLocalAssetRepo.get("thumb1")).called(1); - verify(() => mockLocalAlbumRepo.addAssets(dbAlbum.id, [newAsset])) - .called(1); - verify( - () => mockLocalAlbumRepo.update( - any( - that: predicate( - (a) => - a.id == dbAlbum.id && - a.assetCount == 2 && - a.updatedAt == refreshedAlbum.updatedAt && - a.thumbnailId == "thumb1", - ), - ), - ), - ).called(1); - }); - - test('returns true and sets new thumbnail if db thumb is null', () async { - final dbAlbumNoThumb = - dbAlbum.copyWith(thumbnailId: const NullableValue.empty()); - final newAsset = LocalAssetStub.image2.copyWith( - id: "asset2", - createdAt: DateTime(2024, 1, 1, 10, 30, 0), - ); - when( - () => mockAlbumMediaRepo.getAssetsForAlbum( - dbAlbum.id, - updateTimeCond: any(named: 'updateTimeCond'), - ), - ).thenAnswer((_) async => [newAsset]); - - final result = await sut.checkAddition(dbAlbumNoThumb, refreshedAlbum); - - expect(result, isTrue); - verify( - () => mockAlbumMediaRepo.getAssetsForAlbum( - dbAlbum.id, - updateTimeCond: any(named: 'updateTimeCond'), - ), - ).called(1); - verifyNever(() => mockLocalAssetRepo.get(any())); - verify(() => mockLocalAlbumRepo.addAssets(dbAlbum.id, [newAsset])) - .called(1); - verify( - () => mockLocalAlbumRepo.update( - any( - that: predicate( - (a) => - a.id == dbAlbum.id && - a.assetCount == 2 && - a.updatedAt == refreshedAlbum.updatedAt && - a.thumbnailId == newAsset.id, - ), - ), - ), - ).called(1); - }); - test('returns false if assetCount decreased', () async { final decreasedCountAlbum = refreshedAlbum.copyWith(assetCount: 0); final result = await sut.checkAddition(dbAlbum, decreasedCountAlbum); @@ -725,7 +606,6 @@ void main() { final dbAlbum = LocalAlbumStub.album1.copyWith( updatedAt: DateTime(2024, 1, 1), assetCount: 2, - thumbnailId: const NullableValue.value("asset1"), ); final refreshedAlbum = dbAlbum.copyWith( updatedAt: DateTime(2024, 1, 2), @@ -760,7 +640,7 @@ void main() { when(() => mockLocalAlbumRepo.getAssetsForAlbum(dbAlbum.id)) .thenAnswer((_) async => [dbAsset1, dbAsset2]); - final result = await sut.fullSync(dbAlbum, emptyRefreshedAlbum); + final result = await sut.fullDiff(dbAlbum, emptyRefreshedAlbum); expect(result, isTrue); verifyNever( @@ -773,13 +653,10 @@ void main() { verify( () => mockLocalAlbumRepo.update( any( - that: predicate( - (a) => - a.id == dbAlbum.id && - a.assetCount == 0 && - a.updatedAt == emptyRefreshedAlbum.updatedAt && - a.thumbnailId == null, - ), + that: predicate((a) => + a.id == dbAlbum.id && + a.assetCount == 0 && + a.updatedAt == emptyRefreshedAlbum.updatedAt), ), ), ).called(1); @@ -789,10 +666,7 @@ void main() { }); test('handles empty DB album -> adds all device assets', () async { - final emptyDbAlbum = dbAlbum.copyWith( - assetCount: 0, - thumbnailId: const NullableValue.empty(), - ); + final emptyDbAlbum = dbAlbum.copyWith(assetCount: 0); final deviceAssets = [deviceAsset1, deviceAsset3]; deviceAssets.sort((a, b) => a.createdAt.compareTo(b.createdAt)); @@ -804,7 +678,7 @@ void main() { when(() => mockLocalAlbumRepo.getAssetsForAlbum(emptyDbAlbum.id)) .thenAnswer((_) async => []); - final result = await sut.fullSync(emptyDbAlbum, refreshedWithAssets); + final result = await sut.fullDiff(emptyDbAlbum, refreshedWithAssets); expect(result, isTrue); verify(() => mockAlbumMediaRepo.getAssetsForAlbum(emptyDbAlbum.id)) @@ -816,13 +690,10 @@ void main() { verify( () => mockLocalAlbumRepo.update( any( - that: predicate( - (a) => - a.id == emptyDbAlbum.id && - a.assetCount == deviceAssets.length && - a.updatedAt == refreshedWithAssets.updatedAt && - a.thumbnailId == deviceAssets.first.id, - ), + that: predicate((a) => + a.id == emptyDbAlbum.id && + a.assetCount == deviceAssets.length && + a.updatedAt == refreshedWithAssets.updatedAt), ), ), ).called(1); @@ -844,7 +715,7 @@ void main() { (_) async => dbAssets, ); - final result = await sut.fullSync(dbAlbum, currentRefreshedAlbum); + final result = await sut.fullDiff(dbAlbum, currentRefreshedAlbum); expect(result, isTrue); verify(() => mockAlbumMediaRepo.getAssetsForAlbum(dbAlbum.id)).called(1); @@ -871,13 +742,10 @@ void main() { verify( () => mockLocalAlbumRepo.update( any( - that: predicate( - (a) => - a.id == dbAlbum.id && - a.assetCount == 2 && - a.updatedAt == currentRefreshedAlbum.updatedAt && - a.thumbnailId == deviceAssets.first.id, - ), + that: predicate((a) => + a.id == dbAlbum.id && + a.assetCount == 2 && + a.updatedAt == currentRefreshedAlbum.updatedAt), ), ), ).called(1); @@ -901,7 +769,7 @@ void main() { when(() => mockLocalAlbumRepo.getAssetsForAlbum(dbAlbum.id)) .thenAnswer((_) async => dbAssets); - final result = await sut.fullSync(dbAlbum, currentRefreshedAlbum); + final result = await sut.fullDiff(dbAlbum, currentRefreshedAlbum); expect(result, isTrue); verify(() => mockAlbumMediaRepo.getAssetsForAlbum(dbAlbum.id)).called(1); @@ -912,13 +780,10 @@ void main() { verify( () => mockLocalAlbumRepo.update( any( - that: predicate( - (a) => - a.id == dbAlbum.id && - a.assetCount == 2 && - a.updatedAt == currentRefreshedAlbum.updatedAt && - a.thumbnailId == deviceAssets.first.id, - ), + that: predicate((a) => + a.id == dbAlbum.id && + a.assetCount == 2 && + a.updatedAt == currentRefreshedAlbum.updatedAt), ), ), ).called(1);