mirror of
https://github.com/immich-app/immich.git
synced 2025-05-24 01:12:58 -04:00
feat: native sync ios
This commit is contained in:
parent
dbe1a127c9
commit
214893d2f4
@ -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<Any?> {
|
||||||
|
return listOf(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun wrapError(exception: Throwable): List<Any?> {
|
||||||
|
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<Any?, Any?>).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<String>
|
||||||
|
)
|
||||||
|
{
|
||||||
|
companion object {
|
||||||
|
fun fromList(pigeonVar_list: List<Any?>): 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<String>
|
||||||
|
return Asset(id, name, type, createdAt, updatedAt, durationInSeconds, albumIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
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<Asset>,
|
||||||
|
val deletes: List<String>
|
||||||
|
)
|
||||||
|
{
|
||||||
|
companion object {
|
||||||
|
fun fromList(pigeonVar_list: List<Any?>): SyncDelta {
|
||||||
|
val updates = pigeonVar_list[0] as List<Asset>
|
||||||
|
val deletes = pigeonVar_list[1] as List<String>
|
||||||
|
return SyncDelta(updates, deletes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
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<Any?>)?.let {
|
||||||
|
Asset.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
130.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.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<Boolean>) -> Unit)
|
||||||
|
fun hasMediaChanges(callback: (Result<Boolean>) -> Unit)
|
||||||
|
fun getMediaChanges(callback: (Result<SyncDelta>) -> Unit)
|
||||||
|
fun checkpointSync(callback: (Result<Unit>) -> Unit)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** The codec used by ImHostService. */
|
||||||
|
val codec: MessageCodec<Any?> 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<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ImHostService.shouldFullSync$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { _, reply ->
|
||||||
|
api.shouldFullSync{ result: Result<Boolean> ->
|
||||||
|
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<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ImHostService.hasMediaChanges$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { _, reply ->
|
||||||
|
api.hasMediaChanges{ result: Result<Boolean> ->
|
||||||
|
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<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ImHostService.getMediaChanges$separatedMessageChannelSuffix", codec, taskQueue)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { _, reply ->
|
||||||
|
api.getMediaChanges{ result: Result<SyncDelta> ->
|
||||||
|
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<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ImHostService.checkpointSync$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { _, reply ->
|
||||||
|
api.checkpointSync{ result: Result<Unit> ->
|
||||||
|
val error = result.exceptionOrNull()
|
||||||
|
if (error != null) {
|
||||||
|
reply.reply(MessagesPigeonUtils.wrapError(error))
|
||||||
|
} else {
|
||||||
|
reply.reply(MessagesPigeonUtils.wrapResult(null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
mobile/drift_schemas/main/drift_schema_v1.json
generated
2
mobile/drift_schemas/main/drift_schema_v1.json
generated
File diff suppressed because one or more lines are too long
@ -5,31 +5,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
|
sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "76.0.0"
|
version: "80.0.0"
|
||||||
_macros:
|
|
||||||
dependency: transitive
|
|
||||||
description: dart
|
|
||||||
source: sdk
|
|
||||||
version: "0.3.3"
|
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
|
sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.11.0"
|
version: "7.3.0"
|
||||||
analyzer_plugin:
|
analyzer_plugin:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: analyzer_plugin
|
name: analyzer_plugin
|
||||||
sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161"
|
sha256: b3075265c5ab222f8b3188342dcb50b476286394a40323e85d1fa725035d40a4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.3"
|
version: "0.13.0"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -106,34 +101,42 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: custom_lint
|
name: custom_lint
|
||||||
sha256: "4500e88854e7581ee43586abeaf4443cb22375d6d289241a87b1aadf678d5545"
|
sha256: "409c485fd14f544af1da965d5a0d160ee57cd58b63eeaa7280a4f28cf5bda7f1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.10"
|
version: "0.7.5"
|
||||||
custom_lint_builder:
|
custom_lint_builder:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: custom_lint_builder
|
name: custom_lint_builder
|
||||||
sha256: "5a95eff100da256fbf086b329c17c8b49058c261cdf56d3a4157d3c31c511d78"
|
sha256: "107e0a43606138015777590ee8ce32f26ba7415c25b722ff0908a6f5d7a4c228"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.10"
|
version: "0.7.5"
|
||||||
custom_lint_core:
|
custom_lint_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: custom_lint_core
|
name: custom_lint_core
|
||||||
sha256: "76a4046cc71d976222a078a8fd4a65e198b70545a8d690a75196dd14f08510f6"
|
sha256: "31110af3dde9d29fb10828ca33f1dce24d2798477b167675543ce3d208dee8be"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
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:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
|
sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.8"
|
version: "3.0.1"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -154,10 +157,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: freezed_annotation
|
name: freezed_annotation
|
||||||
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
|
sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.4"
|
version: "3.0.0"
|
||||||
glob:
|
glob:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -198,14 +201,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
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:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -5,9 +5,9 @@ environment:
|
|||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
analyzer: ^6.0.0
|
analyzer: ^7.0.0
|
||||||
analyzer_plugin: ^0.11.3
|
analyzer_plugin: ^0.13.0
|
||||||
custom_lint_builder: ^0.6.4
|
custom_lint_builder: ^0.7.5
|
||||||
glob: ^2.1.2
|
glob: ^2.1.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
@ -89,6 +89,20 @@
|
|||||||
FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerProfile.entitlements; sourceTree = "<group>"; };
|
FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerProfile.entitlements; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
B28F36282DC3150F00B18015 /* Platform */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
);
|
||||||
|
explicitFileTypes = {
|
||||||
|
};
|
||||||
|
explicitFolders = (
|
||||||
|
);
|
||||||
|
path = Platform;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
@ -175,6 +189,7 @@
|
|||||||
97C146F01CF9000F007C117D /* Runner */ = {
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
B28F36282DC3150F00B18015 /* Platform */,
|
||||||
FA9973382CF6DF4B000EF859 /* Runner.entitlements */,
|
FA9973382CF6DF4B000EF859 /* Runner.entitlements */,
|
||||||
65DD438629917FAD0047FFA8 /* BackgroundSync */,
|
65DD438629917FAD0047FFA8 /* BackgroundSync */,
|
||||||
FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */,
|
FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */,
|
||||||
@ -224,6 +239,9 @@
|
|||||||
dependencies = (
|
dependencies = (
|
||||||
FAC6F8992D287C890078CB2F /* PBXTargetDependency */,
|
FAC6F8992D287C890078CB2F /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
B28F36282DC3150F00B18015 /* Platform */,
|
||||||
|
);
|
||||||
name = Runner;
|
name = Runner;
|
||||||
productName = Runner;
|
productName = Runner;
|
||||||
productReference = 97C146EE1CF9000F007C117D /* Immich-Debug.app */;
|
productReference = 97C146EE1CF9000F007C117D /* Immich-Debug.app */;
|
||||||
|
@ -22,6 +22,10 @@ import UIKit
|
|||||||
BackgroundServicePlugin.registerBackgroundProcessing()
|
BackgroundServicePlugin.registerBackgroundProcessing()
|
||||||
|
|
||||||
BackgroundServicePlugin.register(with: self.registrar(forPlugin: "BackgroundServicePlugin")!)
|
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
|
BackgroundServicePlugin.setPluginRegistrantCallback { registry in
|
||||||
if !registry.hasPlugin("org.cocoapods.path-provider-foundation") {
|
if !registry.hasPlugin("org.cocoapods.path-provider-foundation") {
|
||||||
|
152
mobile/ios/Runner/Platform/MediaManager.swift
Normal file
152
mobile/ios/Runner/Platform/MediaManager.swift
Normal file
@ -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, any Error>) -> Void) {
|
||||||
|
_saveChangeToken(token: PHPhotoLibrary.shared().currentChangeToken)
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 16, *)
|
||||||
|
func shouldFullSync(completion: @escaping (Result<Bool, Error>) -> 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<Bool, Error>) -> 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<SyncDelta, Error>) -> 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
333
mobile/ios/Runner/Platform/Messages.g.swift
Normal file
333
mobile/ios/Runner/Platform/Messages.g.swift
Normal file
@ -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 ?? "<nil>"), details: \(details ?? "<nil>")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<T>(_ 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<Bool, Error>) -> Void)
|
||||||
|
func hasMediaChanges(completion: @escaping (Result<Bool, Error>) -> Void)
|
||||||
|
func getMediaChanges(completion: @escaping (Result<SyncDelta, Error>) -> Void)
|
||||||
|
func checkpointSync(completion: @escaping (Result<Void, Error>) -> 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
mobile/ios/Runner/Platform/MessagesImpl.swift
Normal file
44
mobile/ios/Runner/Platform/MessagesImpl.swift
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import Photos
|
||||||
|
|
||||||
|
class ImHostServiceImpl: ImHostService {
|
||||||
|
let _mediaManager: MediaManager
|
||||||
|
|
||||||
|
init() {
|
||||||
|
_mediaManager = MediaManager()
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldFullSync(completion: @escaping (Result<Bool, Error>) -> 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<Bool, Error>) -> 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<SyncDelta, Error>) -> 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, any Error>) -> Void) {
|
||||||
|
if #available(iOS 16, *) {
|
||||||
|
_mediaManager.checkpointSync(completion: completion)
|
||||||
|
} else {
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
|
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/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/local_album.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 {
|
abstract interface class ILocalAlbumRepository implements IDatabaseRepository {
|
||||||
Future<void> insert(LocalAlbum album, Iterable<LocalAsset> assets);
|
Future<void> insert(LocalAlbum album, Iterable<LocalAsset> assets);
|
||||||
@ -13,6 +14,10 @@ abstract interface class ILocalAlbumRepository implements IDatabaseRepository {
|
|||||||
|
|
||||||
Future<void> update(LocalAlbum album);
|
Future<void> update(LocalAlbum album);
|
||||||
|
|
||||||
|
Future<void> updateAll(Iterable<LocalAlbum> albums);
|
||||||
|
|
||||||
|
Future<void> handleSyncDelta(SyncDelta delta);
|
||||||
|
|
||||||
Future<void> delete(String albumId);
|
Future<void> delete(String albumId);
|
||||||
|
|
||||||
Future<void> removeAssets(String albumId, Iterable<String> assetIds);
|
Future<void> removeAssets(String albumId, Iterable<String> assetIds);
|
||||||
|
@ -2,5 +2,5 @@ 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/asset/base_asset.model.dart';
|
||||||
|
|
||||||
abstract interface class ILocalAssetRepository implements IDatabaseRepository {
|
abstract interface class ILocalAssetRepository implements IDatabaseRepository {
|
||||||
Future<LocalAsset> get(String assetId);
|
Future<LocalAsset> get(String id);
|
||||||
}
|
}
|
||||||
|
@ -4,28 +4,50 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/album_media.interface.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_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/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/local_album.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/diff.dart';
|
||||||
import 'package:immich_mobile/utils/nullable_value.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
class DeviceSyncService {
|
class DeviceSyncService {
|
||||||
final IAlbumMediaRepository _albumMediaRepository;
|
final IAlbumMediaRepository _albumMediaRepository;
|
||||||
final ILocalAlbumRepository _localAlbumRepository;
|
final ILocalAlbumRepository _localAlbumRepository;
|
||||||
final ILocalAssetRepository _localAssetRepository;
|
final platform.ImHostService _hostService;
|
||||||
final Logger _log = Logger("SyncService");
|
final Logger _log = Logger("SyncService");
|
||||||
|
|
||||||
DeviceSyncService({
|
DeviceSyncService({
|
||||||
required IAlbumMediaRepository albumMediaRepository,
|
required IAlbumMediaRepository albumMediaRepository,
|
||||||
required ILocalAlbumRepository localAlbumRepository,
|
required ILocalAlbumRepository localAlbumRepository,
|
||||||
required ILocalAssetRepository localAssetRepository,
|
required platform.ImHostService hostService,
|
||||||
}) : _albumMediaRepository = albumMediaRepository,
|
}) : _albumMediaRepository = albumMediaRepository,
|
||||||
_localAlbumRepository = localAlbumRepository,
|
_localAlbumRepository = localAlbumRepository,
|
||||||
_localAssetRepository = localAssetRepository;
|
_hostService = hostService;
|
||||||
|
|
||||||
Future<void> sync() async {
|
Future<void> 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<void> fullSync() async {
|
||||||
try {
|
try {
|
||||||
final Stopwatch stopwatch = Stopwatch()..start();
|
final Stopwatch stopwatch = Stopwatch()..start();
|
||||||
// The deviceAlbums will not have the updatedAt field
|
// The deviceAlbums will not have the updatedAt field
|
||||||
@ -47,6 +69,7 @@ class DeviceSyncService {
|
|||||||
onlySecond: addAlbum,
|
onlySecond: addAlbum,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
_hostService.checkpointSync();
|
||||||
stopwatch.stop();
|
stopwatch.stop();
|
||||||
_log.info("Full device sync took - ${stopwatch.elapsedMilliseconds}ms");
|
_log.info("Full device sync took - ${stopwatch.elapsedMilliseconds}ms");
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
@ -57,17 +80,12 @@ class DeviceSyncService {
|
|||||||
Future<void> addAlbum(LocalAlbum newAlbum) async {
|
Future<void> addAlbum(LocalAlbum newAlbum) async {
|
||||||
try {
|
try {
|
||||||
_log.info("Adding device album ${newAlbum.name}");
|
_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
|
final assets = album.assetCount > 0
|
||||||
? await _albumMediaRepository.getAssetsForAlbum(deviceAlbum.id)
|
? await _albumMediaRepository.getAssetsForAlbum(album.id)
|
||||||
: <LocalAsset>[];
|
: <LocalAsset>[];
|
||||||
|
|
||||||
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);
|
await _localAlbumRepository.insert(album, assets);
|
||||||
_log.fine("Successfully added device album ${album.name}");
|
_log.fine("Successfully added device album ${album.name}");
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
@ -110,7 +128,7 @@ class DeviceSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Slower path - full sync
|
// Slower path - full sync
|
||||||
return await fullSync(dbAlbum, deviceAlbum);
|
return await fullDiff(dbAlbum, deviceAlbum);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
_log.warning("Error while diff device album", e, s);
|
_log.warning("Error while diff device album", e, s);
|
||||||
}
|
}
|
||||||
@ -155,25 +173,8 @@ class DeviceSyncService {
|
|||||||
return false;
|
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(
|
await _updateAlbum(
|
||||||
deviceAlbum.copyWith(
|
deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection),
|
||||||
thumbnailId: NullableValue.valueOrEmpty(thumbnailId),
|
|
||||||
backupSelection: dbAlbum.backupSelection,
|
|
||||||
),
|
|
||||||
assetsToUpsert: newAssets,
|
assetsToUpsert: newAssets,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -187,7 +188,7 @@ class DeviceSyncService {
|
|||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
// The [deviceAlbum] is expected to be refreshed before calling this method
|
// The [deviceAlbum] is expected to be refreshed before calling this method
|
||||||
// with modified time and asset count
|
// with modified time and asset count
|
||||||
Future<bool> fullSync(LocalAlbum dbAlbum, LocalAlbum deviceAlbum) async {
|
Future<bool> fullDiff(LocalAlbum dbAlbum, LocalAlbum deviceAlbum) async {
|
||||||
try {
|
try {
|
||||||
final assetsInDevice = deviceAlbum.assetCount > 0
|
final assetsInDevice = deviceAlbum.assetCount > 0
|
||||||
? await _albumMediaRepository.getAssetsForAlbum(deviceAlbum.id)
|
? await _albumMediaRepository.getAssetsForAlbum(deviceAlbum.id)
|
||||||
@ -201,23 +202,13 @@ class DeviceSyncService {
|
|||||||
"Device album ${deviceAlbum.name} is empty. Removing assets from DB.",
|
"Device album ${deviceAlbum.name} is empty. Removing assets from DB.",
|
||||||
);
|
);
|
||||||
await _updateAlbum(
|
await _updateAlbum(
|
||||||
deviceAlbum.copyWith(
|
deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection),
|
||||||
// Clear thumbnail for empty album
|
|
||||||
thumbnailId: const NullableValue.empty(),
|
|
||||||
backupSelection: dbAlbum.backupSelection,
|
|
||||||
),
|
|
||||||
assetIdsToDelete: assetsInDb.map((a) => a.id),
|
assetIdsToDelete: assetsInDb.map((a) => a.id),
|
||||||
);
|
);
|
||||||
return true;
|
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(
|
final updatedDeviceAlbum = deviceAlbum.copyWith(
|
||||||
thumbnailId: NullableValue.valueOrEmpty(thumbnailId),
|
|
||||||
backupSelection: dbAlbum.backupSelection,
|
backupSelection: dbAlbum.backupSelection,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:immich_mobile/domain/models/local_album.model.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_album.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
|
|
||||||
class LocalAlbumEntity extends Table with DriftDefaultsMixin {
|
class LocalAlbumEntity extends Table with DriftDefaultsMixin {
|
||||||
@ -10,10 +9,8 @@ class LocalAlbumEntity extends Table with DriftDefaultsMixin {
|
|||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
TextColumn get name => text()();
|
TextColumn get name => text()();
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
TextColumn get thumbnailId => text()
|
|
||||||
.nullable()
|
|
||||||
.references(LocalAssetEntity, #localId, onDelete: KeyAction.setNull)();
|
|
||||||
IntColumn get backupSelection => intEnum<BackupSelection>()();
|
IntColumn get backupSelection => intEnum<BackupSelection>()();
|
||||||
|
BoolColumn get marker_ => boolean().withDefault(const Constant(false))();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
@ -26,7 +23,6 @@ extension LocalAlbumEntityX on LocalAlbumEntityData {
|
|||||||
name: name,
|
name: name,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
assetCount: assetCount,
|
assetCount: assetCount,
|
||||||
thumbnailId: thumbnailId,
|
|
||||||
backupSelection: backupSelection,
|
backupSelection: backupSelection,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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'
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart'
|
||||||
as i3;
|
as i3;
|
||||||
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
|
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
|
typedef $$LocalAlbumEntityTableCreateCompanionBuilder
|
||||||
= i1.LocalAlbumEntityCompanion Function({
|
= i1.LocalAlbumEntityCompanion Function({
|
||||||
required String id,
|
required String id,
|
||||||
required String name,
|
required String name,
|
||||||
i0.Value<DateTime> updatedAt,
|
i0.Value<DateTime> updatedAt,
|
||||||
i0.Value<String?> thumbnailId,
|
|
||||||
required i2.BackupSelection backupSelection,
|
required i2.BackupSelection backupSelection,
|
||||||
|
i0.Value<bool> marker_,
|
||||||
});
|
});
|
||||||
typedef $$LocalAlbumEntityTableUpdateCompanionBuilder
|
typedef $$LocalAlbumEntityTableUpdateCompanionBuilder
|
||||||
= i1.LocalAlbumEntityCompanion Function({
|
= i1.LocalAlbumEntityCompanion Function({
|
||||||
i0.Value<String> id,
|
i0.Value<String> id,
|
||||||
i0.Value<String> name,
|
i0.Value<String> name,
|
||||||
i0.Value<DateTime> updatedAt,
|
i0.Value<DateTime> updatedAt,
|
||||||
i0.Value<String?> thumbnailId,
|
|
||||||
i0.Value<i2.BackupSelection> backupSelection,
|
i0.Value<i2.BackupSelection> backupSelection,
|
||||||
|
i0.Value<bool> 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<i5.$LocalAssetEntityTable>('local_asset_entity')
|
|
||||||
.createAlias(i0.$_aliasNameGenerator(
|
|
||||||
i6.ReadDatabaseContainer(db)
|
|
||||||
.resultSet<i1.$LocalAlbumEntityTable>('local_album_entity')
|
|
||||||
.thumbnailId,
|
|
||||||
i6.ReadDatabaseContainer(db)
|
|
||||||
.resultSet<i5.$LocalAssetEntityTable>('local_asset_entity')
|
|
||||||
.localId));
|
|
||||||
|
|
||||||
i5.$$LocalAssetEntityTableProcessedTableManager? get thumbnailId {
|
|
||||||
final $_column = $_itemColumn<String>('thumbnail_id');
|
|
||||||
if ($_column == null) return null;
|
|
||||||
final manager = i5
|
|
||||||
.$$LocalAssetEntityTableTableManager(
|
|
||||||
$_db,
|
|
||||||
i6.ReadDatabaseContainer($_db)
|
|
||||||
.resultSet<i5.$LocalAssetEntityTable>('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
|
class $$LocalAlbumEntityTableFilterComposer
|
||||||
extends i0.Composer<i0.GeneratedDatabase, i1.$LocalAlbumEntityTable> {
|
extends i0.Composer<i0.GeneratedDatabase, i1.$LocalAlbumEntityTable> {
|
||||||
$$LocalAlbumEntityTableFilterComposer({
|
$$LocalAlbumEntityTableFilterComposer({
|
||||||
@ -83,27 +48,8 @@ class $$LocalAlbumEntityTableFilterComposer
|
|||||||
column: $table.backupSelection,
|
column: $table.backupSelection,
|
||||||
builder: (column) => i0.ColumnWithTypeConverterFilters(column));
|
builder: (column) => i0.ColumnWithTypeConverterFilters(column));
|
||||||
|
|
||||||
i5.$$LocalAssetEntityTableFilterComposer get thumbnailId {
|
i0.ColumnFilters<bool> get marker_ => $composableBuilder(
|
||||||
final i5.$$LocalAssetEntityTableFilterComposer composer = $composerBuilder(
|
column: $table.marker_, builder: (column) => i0.ColumnFilters(column));
|
||||||
composer: this,
|
|
||||||
getCurrentColumn: (t) => t.thumbnailId,
|
|
||||||
referencedTable: i6.ReadDatabaseContainer($db)
|
|
||||||
.resultSet<i5.$LocalAssetEntityTable>('local_asset_entity'),
|
|
||||||
getReferencedColumn: (t) => t.localId,
|
|
||||||
builder: (joinBuilder,
|
|
||||||
{$addJoinBuilderToRootComposer,
|
|
||||||
$removeJoinBuilderFromRootComposer}) =>
|
|
||||||
i5.$$LocalAssetEntityTableFilterComposer(
|
|
||||||
$db: $db,
|
|
||||||
$table: i6.ReadDatabaseContainer($db)
|
|
||||||
.resultSet<i5.$LocalAssetEntityTable>('local_asset_entity'),
|
|
||||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
|
||||||
joinBuilder: joinBuilder,
|
|
||||||
$removeJoinBuilderFromRootComposer:
|
|
||||||
$removeJoinBuilderFromRootComposer,
|
|
||||||
));
|
|
||||||
return composer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$LocalAlbumEntityTableOrderingComposer
|
class $$LocalAlbumEntityTableOrderingComposer
|
||||||
@ -129,29 +75,8 @@ class $$LocalAlbumEntityTableOrderingComposer
|
|||||||
column: $table.backupSelection,
|
column: $table.backupSelection,
|
||||||
builder: (column) => i0.ColumnOrderings(column));
|
builder: (column) => i0.ColumnOrderings(column));
|
||||||
|
|
||||||
i5.$$LocalAssetEntityTableOrderingComposer get thumbnailId {
|
i0.ColumnOrderings<bool> get marker_ => $composableBuilder(
|
||||||
final i5.$$LocalAssetEntityTableOrderingComposer composer =
|
column: $table.marker_, builder: (column) => i0.ColumnOrderings(column));
|
||||||
$composerBuilder(
|
|
||||||
composer: this,
|
|
||||||
getCurrentColumn: (t) => t.thumbnailId,
|
|
||||||
referencedTable: i6.ReadDatabaseContainer($db)
|
|
||||||
.resultSet<i5.$LocalAssetEntityTable>('local_asset_entity'),
|
|
||||||
getReferencedColumn: (t) => t.localId,
|
|
||||||
builder: (joinBuilder,
|
|
||||||
{$addJoinBuilderToRootComposer,
|
|
||||||
$removeJoinBuilderFromRootComposer}) =>
|
|
||||||
i5.$$LocalAssetEntityTableOrderingComposer(
|
|
||||||
$db: $db,
|
|
||||||
$table: i6.ReadDatabaseContainer($db)
|
|
||||||
.resultSet<i5.$LocalAssetEntityTable>(
|
|
||||||
'local_asset_entity'),
|
|
||||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
|
||||||
joinBuilder: joinBuilder,
|
|
||||||
$removeJoinBuilderFromRootComposer:
|
|
||||||
$removeJoinBuilderFromRootComposer,
|
|
||||||
));
|
|
||||||
return composer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$LocalAlbumEntityTableAnnotationComposer
|
class $$LocalAlbumEntityTableAnnotationComposer
|
||||||
@ -176,29 +101,8 @@ class $$LocalAlbumEntityTableAnnotationComposer
|
|||||||
get backupSelection => $composableBuilder(
|
get backupSelection => $composableBuilder(
|
||||||
column: $table.backupSelection, builder: (column) => column);
|
column: $table.backupSelection, builder: (column) => column);
|
||||||
|
|
||||||
i5.$$LocalAssetEntityTableAnnotationComposer get thumbnailId {
|
i0.GeneratedColumn<bool> get marker_ =>
|
||||||
final i5.$$LocalAssetEntityTableAnnotationComposer composer =
|
$composableBuilder(column: $table.marker_, builder: (column) => column);
|
||||||
$composerBuilder(
|
|
||||||
composer: this,
|
|
||||||
getCurrentColumn: (t) => t.thumbnailId,
|
|
||||||
referencedTable: i6.ReadDatabaseContainer($db)
|
|
||||||
.resultSet<i5.$LocalAssetEntityTable>('local_asset_entity'),
|
|
||||||
getReferencedColumn: (t) => t.localId,
|
|
||||||
builder: (joinBuilder,
|
|
||||||
{$addJoinBuilderToRootComposer,
|
|
||||||
$removeJoinBuilderFromRootComposer}) =>
|
|
||||||
i5.$$LocalAssetEntityTableAnnotationComposer(
|
|
||||||
$db: $db,
|
|
||||||
$table: i6.ReadDatabaseContainer($db)
|
|
||||||
.resultSet<i5.$LocalAssetEntityTable>(
|
|
||||||
'local_asset_entity'),
|
|
||||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
|
||||||
joinBuilder: joinBuilder,
|
|
||||||
$removeJoinBuilderFromRootComposer:
|
|
||||||
$removeJoinBuilderFromRootComposer,
|
|
||||||
));
|
|
||||||
return composer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$LocalAlbumEntityTableTableManager extends i0.RootTableManager<
|
class $$LocalAlbumEntityTableTableManager extends i0.RootTableManager<
|
||||||
@ -210,9 +114,13 @@ class $$LocalAlbumEntityTableTableManager extends i0.RootTableManager<
|
|||||||
i1.$$LocalAlbumEntityTableAnnotationComposer,
|
i1.$$LocalAlbumEntityTableAnnotationComposer,
|
||||||
$$LocalAlbumEntityTableCreateCompanionBuilder,
|
$$LocalAlbumEntityTableCreateCompanionBuilder,
|
||||||
$$LocalAlbumEntityTableUpdateCompanionBuilder,
|
$$LocalAlbumEntityTableUpdateCompanionBuilder,
|
||||||
(i1.LocalAlbumEntityData, i1.$$LocalAlbumEntityTableReferences),
|
(
|
||||||
|
i1.LocalAlbumEntityData,
|
||||||
|
i0.BaseReferences<i0.GeneratedDatabase, i1.$LocalAlbumEntityTable,
|
||||||
|
i1.LocalAlbumEntityData>
|
||||||
|
),
|
||||||
i1.LocalAlbumEntityData,
|
i1.LocalAlbumEntityData,
|
||||||
i0.PrefetchHooks Function({bool thumbnailId})> {
|
i0.PrefetchHooks Function()> {
|
||||||
$$LocalAlbumEntityTableTableManager(
|
$$LocalAlbumEntityTableTableManager(
|
||||||
i0.GeneratedDatabase db, i1.$LocalAlbumEntityTable table)
|
i0.GeneratedDatabase db, i1.$LocalAlbumEntityTable table)
|
||||||
: super(i0.TableManagerState(
|
: super(i0.TableManagerState(
|
||||||
@ -229,73 +137,35 @@ class $$LocalAlbumEntityTableTableManager extends i0.RootTableManager<
|
|||||||
i0.Value<String> id = const i0.Value.absent(),
|
i0.Value<String> id = const i0.Value.absent(),
|
||||||
i0.Value<String> name = const i0.Value.absent(),
|
i0.Value<String> name = const i0.Value.absent(),
|
||||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
i0.Value<String?> thumbnailId = const i0.Value.absent(),
|
|
||||||
i0.Value<i2.BackupSelection> backupSelection =
|
i0.Value<i2.BackupSelection> backupSelection =
|
||||||
const i0.Value.absent(),
|
const i0.Value.absent(),
|
||||||
|
i0.Value<bool> marker_ = const i0.Value.absent(),
|
||||||
}) =>
|
}) =>
|
||||||
i1.LocalAlbumEntityCompanion(
|
i1.LocalAlbumEntityCompanion(
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
thumbnailId: thumbnailId,
|
|
||||||
backupSelection: backupSelection,
|
backupSelection: backupSelection,
|
||||||
|
marker_: marker_,
|
||||||
),
|
),
|
||||||
createCompanionCallback: ({
|
createCompanionCallback: ({
|
||||||
required String id,
|
required String id,
|
||||||
required String name,
|
required String name,
|
||||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
i0.Value<String?> thumbnailId = const i0.Value.absent(),
|
|
||||||
required i2.BackupSelection backupSelection,
|
required i2.BackupSelection backupSelection,
|
||||||
|
i0.Value<bool> marker_ = const i0.Value.absent(),
|
||||||
}) =>
|
}) =>
|
||||||
i1.LocalAlbumEntityCompanion.insert(
|
i1.LocalAlbumEntityCompanion.insert(
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
thumbnailId: thumbnailId,
|
|
||||||
backupSelection: backupSelection,
|
backupSelection: backupSelection,
|
||||||
|
marker_: marker_,
|
||||||
),
|
),
|
||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
.map((e) => (
|
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||||
e.readTable(table),
|
|
||||||
i1.$$LocalAlbumEntityTableReferences(db, table, e)
|
|
||||||
))
|
|
||||||
.toList(),
|
.toList(),
|
||||||
prefetchHooksCallback: ({thumbnailId = false}) {
|
prefetchHooksCallback: null,
|
||||||
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 [];
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,9 +178,13 @@ typedef $$LocalAlbumEntityTableProcessedTableManager = i0.ProcessedTableManager<
|
|||||||
i1.$$LocalAlbumEntityTableAnnotationComposer,
|
i1.$$LocalAlbumEntityTableAnnotationComposer,
|
||||||
$$LocalAlbumEntityTableCreateCompanionBuilder,
|
$$LocalAlbumEntityTableCreateCompanionBuilder,
|
||||||
$$LocalAlbumEntityTableUpdateCompanionBuilder,
|
$$LocalAlbumEntityTableUpdateCompanionBuilder,
|
||||||
(i1.LocalAlbumEntityData, i1.$$LocalAlbumEntityTableReferences),
|
(
|
||||||
|
i1.LocalAlbumEntityData,
|
||||||
|
i0.BaseReferences<i0.GeneratedDatabase, i1.$LocalAlbumEntityTable,
|
||||||
|
i1.LocalAlbumEntityData>
|
||||||
|
),
|
||||||
i1.LocalAlbumEntityData,
|
i1.LocalAlbumEntityData,
|
||||||
i0.PrefetchHooks Function({bool thumbnailId})>;
|
i0.PrefetchHooks Function()>;
|
||||||
|
|
||||||
class $LocalAlbumEntityTable extends i3.LocalAlbumEntity
|
class $LocalAlbumEntityTable extends i3.LocalAlbumEntity
|
||||||
with i0.TableInfo<$LocalAlbumEntityTable, i1.LocalAlbumEntityData> {
|
with i0.TableInfo<$LocalAlbumEntityTable, i1.LocalAlbumEntityData> {
|
||||||
@ -337,15 +211,6 @@ class $LocalAlbumEntityTable extends i3.LocalAlbumEntity
|
|||||||
type: i0.DriftSqlType.dateTime,
|
type: i0.DriftSqlType.dateTime,
|
||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
defaultValue: i4.currentDateAndTime);
|
defaultValue: i4.currentDateAndTime);
|
||||||
static const i0.VerificationMeta _thumbnailIdMeta =
|
|
||||||
const i0.VerificationMeta('thumbnailId');
|
|
||||||
@override
|
|
||||||
late final i0.GeneratedColumn<String> thumbnailId =
|
|
||||||
i0.GeneratedColumn<String>('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
|
@override
|
||||||
late final i0.GeneratedColumnWithTypeConverter<i2.BackupSelection, int>
|
late final i0.GeneratedColumnWithTypeConverter<i2.BackupSelection, int>
|
||||||
backupSelection = i0.GeneratedColumn<int>(
|
backupSelection = i0.GeneratedColumn<int>(
|
||||||
@ -353,9 +218,19 @@ class $LocalAlbumEntityTable extends i3.LocalAlbumEntity
|
|||||||
type: i0.DriftSqlType.int, requiredDuringInsert: true)
|
type: i0.DriftSqlType.int, requiredDuringInsert: true)
|
||||||
.withConverter<i2.BackupSelection>(
|
.withConverter<i2.BackupSelection>(
|
||||||
i1.$LocalAlbumEntityTable.$converterbackupSelection);
|
i1.$LocalAlbumEntityTable.$converterbackupSelection);
|
||||||
|
static const i0.VerificationMeta _marker_Meta =
|
||||||
|
const i0.VerificationMeta('marker_');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<bool> marker_ = i0.GeneratedColumn<bool>(
|
||||||
|
'marker', aliasedName, false,
|
||||||
|
type: i0.DriftSqlType.bool,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultConstraints:
|
||||||
|
i0.GeneratedColumn.constraintIsAlways('CHECK ("marker" IN (0, 1))'),
|
||||||
|
defaultValue: const i4.Constant(false));
|
||||||
@override
|
@override
|
||||||
List<i0.GeneratedColumn> get $columns =>
|
List<i0.GeneratedColumn> get $columns =>
|
||||||
[id, name, updatedAt, thumbnailId, backupSelection];
|
[id, name, updatedAt, backupSelection, marker_];
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? actualTableName;
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
@override
|
@override
|
||||||
@ -382,11 +257,9 @@ class $LocalAlbumEntityTable extends i3.LocalAlbumEntity
|
|||||||
context.handle(_updatedAtMeta,
|
context.handle(_updatedAtMeta,
|
||||||
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta));
|
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta));
|
||||||
}
|
}
|
||||||
if (data.containsKey('thumbnail_id')) {
|
if (data.containsKey('marker')) {
|
||||||
context.handle(
|
context.handle(_marker_Meta,
|
||||||
_thumbnailIdMeta,
|
marker_.isAcceptableOrUnknown(data['marker']!, _marker_Meta));
|
||||||
thumbnailId.isAcceptableOrUnknown(
|
|
||||||
data['thumbnail_id']!, _thumbnailIdMeta));
|
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
@ -404,11 +277,11 @@ class $LocalAlbumEntityTable extends i3.LocalAlbumEntity
|
|||||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}name'])!,
|
.read(i0.DriftSqlType.string, data['${effectivePrefix}name'])!,
|
||||||
updatedAt: attachedDatabase.typeMapping.read(
|
updatedAt: attachedDatabase.typeMapping.read(
|
||||||
i0.DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!,
|
i0.DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!,
|
||||||
thumbnailId: attachedDatabase.typeMapping
|
|
||||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}thumbnail_id']),
|
|
||||||
backupSelection: i1.$LocalAlbumEntityTable.$converterbackupSelection
|
backupSelection: i1.$LocalAlbumEntityTable.$converterbackupSelection
|
||||||
.fromSql(attachedDatabase.typeMapping.read(i0.DriftSqlType.int,
|
.fromSql(attachedDatabase.typeMapping.read(i0.DriftSqlType.int,
|
||||||
data['${effectivePrefix}backup_selection'])!),
|
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 id;
|
||||||
final String name;
|
final String name;
|
||||||
final DateTime updatedAt;
|
final DateTime updatedAt;
|
||||||
final String? thumbnailId;
|
|
||||||
final i2.BackupSelection backupSelection;
|
final i2.BackupSelection backupSelection;
|
||||||
|
final bool marker_;
|
||||||
const LocalAlbumEntityData(
|
const LocalAlbumEntityData(
|
||||||
{required this.id,
|
{required this.id,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
this.thumbnailId,
|
required this.backupSelection,
|
||||||
required this.backupSelection});
|
required this.marker_});
|
||||||
@override
|
@override
|
||||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, i0.Expression>{};
|
final map = <String, i0.Expression>{};
|
||||||
map['id'] = i0.Variable<String>(id);
|
map['id'] = i0.Variable<String>(id);
|
||||||
map['name'] = i0.Variable<String>(name);
|
map['name'] = i0.Variable<String>(name);
|
||||||
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
||||||
if (!nullToAbsent || thumbnailId != null) {
|
|
||||||
map['thumbnail_id'] = i0.Variable<String>(thumbnailId);
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
map['backup_selection'] = i0.Variable<int>(i1
|
map['backup_selection'] = i0.Variable<int>(i1
|
||||||
.$LocalAlbumEntityTable.$converterbackupSelection
|
.$LocalAlbumEntityTable.$converterbackupSelection
|
||||||
.toSql(backupSelection));
|
.toSql(backupSelection));
|
||||||
}
|
}
|
||||||
|
map['marker'] = i0.Variable<bool>(marker_);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,9 +335,9 @@ class LocalAlbumEntityData extends i0.DataClass
|
|||||||
id: serializer.fromJson<String>(json['id']),
|
id: serializer.fromJson<String>(json['id']),
|
||||||
name: serializer.fromJson<String>(json['name']),
|
name: serializer.fromJson<String>(json['name']),
|
||||||
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
||||||
thumbnailId: serializer.fromJson<String?>(json['thumbnailId']),
|
|
||||||
backupSelection: i1.$LocalAlbumEntityTable.$converterbackupSelection
|
backupSelection: i1.$LocalAlbumEntityTable.$converterbackupSelection
|
||||||
.fromJson(serializer.fromJson<int>(json['backupSelection'])),
|
.fromJson(serializer.fromJson<int>(json['backupSelection'])),
|
||||||
|
marker_: serializer.fromJson<bool>(json['marker_']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
@ -476,10 +347,10 @@ class LocalAlbumEntityData extends i0.DataClass
|
|||||||
'id': serializer.toJson<String>(id),
|
'id': serializer.toJson<String>(id),
|
||||||
'name': serializer.toJson<String>(name),
|
'name': serializer.toJson<String>(name),
|
||||||
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
||||||
'thumbnailId': serializer.toJson<String?>(thumbnailId),
|
|
||||||
'backupSelection': serializer.toJson<int>(i1
|
'backupSelection': serializer.toJson<int>(i1
|
||||||
.$LocalAlbumEntityTable.$converterbackupSelection
|
.$LocalAlbumEntityTable.$converterbackupSelection
|
||||||
.toJson(backupSelection)),
|
.toJson(backupSelection)),
|
||||||
|
'marker_': serializer.toJson<bool>(marker_),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -487,25 +358,24 @@ class LocalAlbumEntityData extends i0.DataClass
|
|||||||
{String? id,
|
{String? id,
|
||||||
String? name,
|
String? name,
|
||||||
DateTime? updatedAt,
|
DateTime? updatedAt,
|
||||||
i0.Value<String?> thumbnailId = const i0.Value.absent(),
|
i2.BackupSelection? backupSelection,
|
||||||
i2.BackupSelection? backupSelection}) =>
|
bool? marker_}) =>
|
||||||
i1.LocalAlbumEntityData(
|
i1.LocalAlbumEntityData(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
thumbnailId: thumbnailId.present ? thumbnailId.value : this.thumbnailId,
|
|
||||||
backupSelection: backupSelection ?? this.backupSelection,
|
backupSelection: backupSelection ?? this.backupSelection,
|
||||||
|
marker_: marker_ ?? this.marker_,
|
||||||
);
|
);
|
||||||
LocalAlbumEntityData copyWithCompanion(i1.LocalAlbumEntityCompanion data) {
|
LocalAlbumEntityData copyWithCompanion(i1.LocalAlbumEntityCompanion data) {
|
||||||
return LocalAlbumEntityData(
|
return LocalAlbumEntityData(
|
||||||
id: data.id.present ? data.id.value : this.id,
|
id: data.id.present ? data.id.value : this.id,
|
||||||
name: data.name.present ? data.name.value : this.name,
|
name: data.name.present ? data.name.value : this.name,
|
||||||
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
||||||
thumbnailId:
|
|
||||||
data.thumbnailId.present ? data.thumbnailId.value : this.thumbnailId,
|
|
||||||
backupSelection: data.backupSelection.present
|
backupSelection: data.backupSelection.present
|
||||||
? data.backupSelection.value
|
? data.backupSelection.value
|
||||||
: this.backupSelection,
|
: this.backupSelection,
|
||||||
|
marker_: data.marker_.present ? data.marker_.value : this.marker_,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,15 +385,15 @@ class LocalAlbumEntityData extends i0.DataClass
|
|||||||
..write('id: $id, ')
|
..write('id: $id, ')
|
||||||
..write('name: $name, ')
|
..write('name: $name, ')
|
||||||
..write('updatedAt: $updatedAt, ')
|
..write('updatedAt: $updatedAt, ')
|
||||||
..write('thumbnailId: $thumbnailId, ')
|
..write('backupSelection: $backupSelection, ')
|
||||||
..write('backupSelection: $backupSelection')
|
..write('marker_: $marker_')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
Object.hash(id, name, updatedAt, thumbnailId, backupSelection);
|
Object.hash(id, name, updatedAt, backupSelection, marker_);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
@ -531,8 +401,8 @@ class LocalAlbumEntityData extends i0.DataClass
|
|||||||
other.id == this.id &&
|
other.id == this.id &&
|
||||||
other.name == this.name &&
|
other.name == this.name &&
|
||||||
other.updatedAt == this.updatedAt &&
|
other.updatedAt == this.updatedAt &&
|
||||||
other.thumbnailId == this.thumbnailId &&
|
other.backupSelection == this.backupSelection &&
|
||||||
other.backupSelection == this.backupSelection);
|
other.marker_ == this.marker_);
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalAlbumEntityCompanion
|
class LocalAlbumEntityCompanion
|
||||||
@ -540,21 +410,21 @@ class LocalAlbumEntityCompanion
|
|||||||
final i0.Value<String> id;
|
final i0.Value<String> id;
|
||||||
final i0.Value<String> name;
|
final i0.Value<String> name;
|
||||||
final i0.Value<DateTime> updatedAt;
|
final i0.Value<DateTime> updatedAt;
|
||||||
final i0.Value<String?> thumbnailId;
|
|
||||||
final i0.Value<i2.BackupSelection> backupSelection;
|
final i0.Value<i2.BackupSelection> backupSelection;
|
||||||
|
final i0.Value<bool> marker_;
|
||||||
const LocalAlbumEntityCompanion({
|
const LocalAlbumEntityCompanion({
|
||||||
this.id = const i0.Value.absent(),
|
this.id = const i0.Value.absent(),
|
||||||
this.name = const i0.Value.absent(),
|
this.name = const i0.Value.absent(),
|
||||||
this.updatedAt = const i0.Value.absent(),
|
this.updatedAt = const i0.Value.absent(),
|
||||||
this.thumbnailId = const i0.Value.absent(),
|
|
||||||
this.backupSelection = const i0.Value.absent(),
|
this.backupSelection = const i0.Value.absent(),
|
||||||
|
this.marker_ = const i0.Value.absent(),
|
||||||
});
|
});
|
||||||
LocalAlbumEntityCompanion.insert({
|
LocalAlbumEntityCompanion.insert({
|
||||||
required String id,
|
required String id,
|
||||||
required String name,
|
required String name,
|
||||||
this.updatedAt = const i0.Value.absent(),
|
this.updatedAt = const i0.Value.absent(),
|
||||||
this.thumbnailId = const i0.Value.absent(),
|
|
||||||
required i2.BackupSelection backupSelection,
|
required i2.BackupSelection backupSelection,
|
||||||
|
this.marker_ = const i0.Value.absent(),
|
||||||
}) : id = i0.Value(id),
|
}) : id = i0.Value(id),
|
||||||
name = i0.Value(name),
|
name = i0.Value(name),
|
||||||
backupSelection = i0.Value(backupSelection);
|
backupSelection = i0.Value(backupSelection);
|
||||||
@ -562,15 +432,15 @@ class LocalAlbumEntityCompanion
|
|||||||
i0.Expression<String>? id,
|
i0.Expression<String>? id,
|
||||||
i0.Expression<String>? name,
|
i0.Expression<String>? name,
|
||||||
i0.Expression<DateTime>? updatedAt,
|
i0.Expression<DateTime>? updatedAt,
|
||||||
i0.Expression<String>? thumbnailId,
|
|
||||||
i0.Expression<int>? backupSelection,
|
i0.Expression<int>? backupSelection,
|
||||||
|
i0.Expression<bool>? marker_,
|
||||||
}) {
|
}) {
|
||||||
return i0.RawValuesInsertable({
|
return i0.RawValuesInsertable({
|
||||||
if (id != null) 'id': id,
|
if (id != null) 'id': id,
|
||||||
if (name != null) 'name': name,
|
if (name != null) 'name': name,
|
||||||
if (updatedAt != null) 'updated_at': updatedAt,
|
if (updatedAt != null) 'updated_at': updatedAt,
|
||||||
if (thumbnailId != null) 'thumbnail_id': thumbnailId,
|
|
||||||
if (backupSelection != null) 'backup_selection': backupSelection,
|
if (backupSelection != null) 'backup_selection': backupSelection,
|
||||||
|
if (marker_ != null) 'marker': marker_,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,14 +448,14 @@ class LocalAlbumEntityCompanion
|
|||||||
{i0.Value<String>? id,
|
{i0.Value<String>? id,
|
||||||
i0.Value<String>? name,
|
i0.Value<String>? name,
|
||||||
i0.Value<DateTime>? updatedAt,
|
i0.Value<DateTime>? updatedAt,
|
||||||
i0.Value<String?>? thumbnailId,
|
i0.Value<i2.BackupSelection>? backupSelection,
|
||||||
i0.Value<i2.BackupSelection>? backupSelection}) {
|
i0.Value<bool>? marker_}) {
|
||||||
return i1.LocalAlbumEntityCompanion(
|
return i1.LocalAlbumEntityCompanion(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
thumbnailId: thumbnailId ?? this.thumbnailId,
|
|
||||||
backupSelection: backupSelection ?? this.backupSelection,
|
backupSelection: backupSelection ?? this.backupSelection,
|
||||||
|
marker_: marker_ ?? this.marker_,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -601,14 +471,14 @@ class LocalAlbumEntityCompanion
|
|||||||
if (updatedAt.present) {
|
if (updatedAt.present) {
|
||||||
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
|
||||||
}
|
}
|
||||||
if (thumbnailId.present) {
|
|
||||||
map['thumbnail_id'] = i0.Variable<String>(thumbnailId.value);
|
|
||||||
}
|
|
||||||
if (backupSelection.present) {
|
if (backupSelection.present) {
|
||||||
map['backup_selection'] = i0.Variable<int>(i1
|
map['backup_selection'] = i0.Variable<int>(i1
|
||||||
.$LocalAlbumEntityTable.$converterbackupSelection
|
.$LocalAlbumEntityTable.$converterbackupSelection
|
||||||
.toSql(backupSelection.value));
|
.toSql(backupSelection.value));
|
||||||
}
|
}
|
||||||
|
if (marker_.present) {
|
||||||
|
map['marker'] = i0.Variable<bool>(marker_.value);
|
||||||
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -618,8 +488,8 @@ class LocalAlbumEntityCompanion
|
|||||||
..write('id: $id, ')
|
..write('id: $id, ')
|
||||||
..write('name: $name, ')
|
..write('name: $name, ')
|
||||||
..write('updatedAt: $updatedAt, ')
|
..write('updatedAt: $updatedAt, ')
|
||||||
..write('thumbnailId: $thumbnailId, ')
|
..write('backupSelection: $backupSelection, ')
|
||||||
..write('backupSelection: $backupSelection')
|
..write('marker_: $marker_')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
@ -41,12 +41,10 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<LocalAlbum>> getAll() {
|
Future<List<LocalAlbum>> getAll() {
|
||||||
final filter = AdvancedCustomFilter(
|
return PhotoManager.getAssetPathList(
|
||||||
orderBy: [OrderByItem.asc(CustomColumns.base.id)],
|
hasAll: true,
|
||||||
);
|
filterOption: AdvancedCustomFilter(),
|
||||||
|
).then((e) {
|
||||||
return PhotoManager.getAssetPathList(hasAll: true, filterOption: filter)
|
|
||||||
.then((e) {
|
|
||||||
if (_platform.isAndroid) {
|
if (_platform.isAndroid) {
|
||||||
e.removeWhere((a) => a.isAll);
|
e.removeWhere((a) => a.isAll);
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,9 @@ import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift
|
|||||||
as i2;
|
as i2;
|
||||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'
|
||||||
as i3;
|
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'
|
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;
|
as i5;
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'
|
||||||
as i6;
|
as i6;
|
||||||
@ -26,10 +26,10 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
i2.$UserMetadataEntityTable(this);
|
i2.$UserMetadataEntityTable(this);
|
||||||
late final i3.$PartnerEntityTable partnerEntity =
|
late final i3.$PartnerEntityTable partnerEntity =
|
||||||
i3.$PartnerEntityTable(this);
|
i3.$PartnerEntityTable(this);
|
||||||
late final i4.$LocalAssetEntityTable localAssetEntity =
|
late final i4.$LocalAlbumEntityTable localAlbumEntity =
|
||||||
i4.$LocalAssetEntityTable(this);
|
i4.$LocalAlbumEntityTable(this);
|
||||||
late final i5.$LocalAlbumEntityTable localAlbumEntity =
|
late final i5.$LocalAssetEntityTable localAssetEntity =
|
||||||
i5.$LocalAlbumEntityTable(this);
|
i5.$LocalAssetEntityTable(this);
|
||||||
late final i6.$LocalAlbumAssetEntityTable localAlbumAssetEntity =
|
late final i6.$LocalAlbumAssetEntityTable localAlbumAssetEntity =
|
||||||
i6.$LocalAlbumAssetEntityTable(this);
|
i6.$LocalAlbumAssetEntityTable(this);
|
||||||
late final i7.$RemoteAssetEntityTable remoteAssetEntity =
|
late final i7.$RemoteAssetEntityTable remoteAssetEntity =
|
||||||
@ -43,12 +43,12 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
userEntity,
|
userEntity,
|
||||||
userMetadataEntity,
|
userMetadataEntity,
|
||||||
partnerEntity,
|
partnerEntity,
|
||||||
localAssetEntity,
|
|
||||||
localAlbumEntity,
|
localAlbumEntity,
|
||||||
|
localAssetEntity,
|
||||||
localAlbumAssetEntity,
|
localAlbumAssetEntity,
|
||||||
remoteAssetEntity,
|
remoteAssetEntity,
|
||||||
exifEntity,
|
exifEntity,
|
||||||
i4.localAssetChecksum,
|
i5.localAssetChecksum,
|
||||||
i7.remoteAssetChecksum
|
i7.remoteAssetChecksum
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
@ -77,13 +77,6 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
i0.TableUpdate('partner_entity', kind: i0.UpdateKind.delete),
|
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(
|
i0.WritePropagation(
|
||||||
on: i0.TableUpdateQuery.onTableName('local_asset_entity',
|
on: i0.TableUpdateQuery.onTableName('local_asset_entity',
|
||||||
limitUpdateKind: i0.UpdateKind.delete),
|
limitUpdateKind: i0.UpdateKind.delete),
|
||||||
@ -130,10 +123,10 @@ class $DriftManager {
|
|||||||
i2.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity);
|
i2.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity);
|
||||||
i3.$$PartnerEntityTableTableManager get partnerEntity =>
|
i3.$$PartnerEntityTableTableManager get partnerEntity =>
|
||||||
i3.$$PartnerEntityTableTableManager(_db, _db.partnerEntity);
|
i3.$$PartnerEntityTableTableManager(_db, _db.partnerEntity);
|
||||||
i4.$$LocalAssetEntityTableTableManager get localAssetEntity =>
|
i4.$$LocalAlbumEntityTableTableManager get localAlbumEntity =>
|
||||||
i4.$$LocalAssetEntityTableTableManager(_db, _db.localAssetEntity);
|
i4.$$LocalAlbumEntityTableTableManager(_db, _db.localAlbumEntity);
|
||||||
i5.$$LocalAlbumEntityTableTableManager get localAlbumEntity =>
|
i5.$$LocalAssetEntityTableTableManager get localAssetEntity =>
|
||||||
i5.$$LocalAlbumEntityTableTableManager(_db, _db.localAlbumEntity);
|
i5.$$LocalAssetEntityTableTableManager(_db, _db.localAssetEntity);
|
||||||
i6.$$LocalAlbumAssetEntityTableTableManager get localAlbumAssetEntity => i6
|
i6.$$LocalAlbumAssetEntityTableTableManager get localAlbumAssetEntity => i6
|
||||||
.$$LocalAlbumAssetEntityTableTableManager(_db, _db.localAlbumAssetEntity);
|
.$$LocalAlbumAssetEntityTableTableManager(_db, _db.localAlbumAssetEntity);
|
||||||
i7.$$RemoteAssetEntityTableTableManager get remoteAssetEntity =>
|
i7.$$RemoteAssetEntityTableTableManager get remoteAssetEntity =>
|
||||||
|
@ -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.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.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/infrastructure/repositories/db.repository.dart';
|
||||||
|
import 'package:immich_mobile/platform/messages.g.dart' as platform;
|
||||||
import 'package:platform/platform.dart';
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
||||||
@ -65,7 +66,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
|||||||
transaction(() async {
|
transaction(() async {
|
||||||
await _upsertAssets(assets);
|
await _upsertAssets(assets);
|
||||||
// Needs to be after asset upsert to link the thumbnail
|
// Needs to be after asset upsert to link the thumbnail
|
||||||
await _upsertAlbum(localAlbum);
|
await update(localAlbum);
|
||||||
await _linkAssetsToAlbum(localAlbum.id, assets);
|
await _linkAssetsToAlbum(localAlbum.id, assets);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -112,7 +113,46 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> update(LocalAlbum localAlbum) => _upsertAlbum(localAlbum);
|
Future<void> 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<void> updateAll(Iterable<LocalAlbum> 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
|
@override
|
||||||
Future<List<LocalAsset>> getAssetsForAlbum(String albumId) {
|
Future<List<LocalAsset>> getAssetsForAlbum(String albumId) {
|
||||||
@ -132,17 +172,30 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
|||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _upsertAlbum(LocalAlbum localAlbum) {
|
@override
|
||||||
final companion = LocalAlbumEntityCompanion.insert(
|
Future<void> handleSyncDelta(platform.SyncDelta delta) {
|
||||||
id: localAlbum.id,
|
return _db.transaction(() async {
|
||||||
name: localAlbum.name,
|
await _deleteAssets(delta.deletes);
|
||||||
updatedAt: Value(localAlbum.updatedAt),
|
|
||||||
thumbnailId: Value.absentIfNull(localAlbum.thumbnailId),
|
|
||||||
backupSelection: localAlbum.backupSelection,
|
|
||||||
);
|
|
||||||
|
|
||||||
return _db.localAlbumEntity
|
await _upsertAssets(delta.updates.map((a) => a.toLocalAsset()));
|
||||||
.insertOne(companion, onConflict: DoUpdate((_) => companion));
|
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<void> _linkAssetsToAlbum(
|
Future<void> _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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,8 +9,8 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository
|
|||||||
const DriftLocalAssetRepository(this._db) : super(_db);
|
const DriftLocalAssetRepository(this._db) : super(_db);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LocalAsset> get(String assetId) => _db.managers.localAssetEntity
|
Future<LocalAsset> get(String id) => _db.managers.localAssetEntity
|
||||||
.filter((f) => f.localId(assetId))
|
.filter((f) => f.localId(id))
|
||||||
.map((a) => a.toDto())
|
.map((a) => a.toDto())
|
||||||
.getSingle();
|
.getSingle();
|
||||||
}
|
}
|
||||||
|
55
mobile/lib/platform/messages.dart
Normal file
55
mobile/lib/platform/messages.dart
Normal file
@ -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<String> 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<Asset> updates;
|
||||||
|
List<String> deletes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostApi()
|
||||||
|
abstract class ImHostService {
|
||||||
|
@async
|
||||||
|
bool shouldFullSync();
|
||||||
|
|
||||||
|
@async
|
||||||
|
bool hasMediaChanges();
|
||||||
|
|
||||||
|
@async
|
||||||
|
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||||
|
SyncDelta getMediaChanges();
|
||||||
|
|
||||||
|
@async
|
||||||
|
void checkpointSync();
|
||||||
|
}
|
310
mobile/lib/platform/messages.g.dart
generated
Normal file
310
mobile/lib/platform/messages.g.dart
generated
Normal file
@ -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<Object?, Object?> entry) =>
|
||||||
|
(b as Map<Object?, Object?>).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<String> albumIds;
|
||||||
|
|
||||||
|
List<Object?> _toList() {
|
||||||
|
return <Object?>[
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
durationInSeconds,
|
||||||
|
albumIds,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object encode() {
|
||||||
|
return _toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Asset decode(Object result) {
|
||||||
|
result as List<Object?>;
|
||||||
|
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<Object?>?)!.cast<String>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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<Asset> updates;
|
||||||
|
|
||||||
|
List<String> deletes;
|
||||||
|
|
||||||
|
List<Object?> _toList() {
|
||||||
|
return <Object?>[
|
||||||
|
updates,
|
||||||
|
deletes,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object encode() {
|
||||||
|
return _toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static SyncDelta decode(Object result) {
|
||||||
|
result as List<Object?>;
|
||||||
|
return SyncDelta(
|
||||||
|
updates: (result[0] as List<Object?>?)!.cast<Asset>(),
|
||||||
|
deletes: (result[1] as List<Object?>?)!.cast<String>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
|
|
||||||
|
final String pigeonVar_messageChannelSuffix;
|
||||||
|
|
||||||
|
Future<bool> shouldFullSync() async {
|
||||||
|
final String pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.immich_mobile.ImHostService.shouldFullSync$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel =
|
||||||
|
BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
||||||
|
final List<Object?>? pigeonVar_replyList =
|
||||||
|
await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
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<bool> hasMediaChanges() async {
|
||||||
|
final String pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.immich_mobile.ImHostService.hasMediaChanges$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel =
|
||||||
|
BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
||||||
|
final List<Object?>? pigeonVar_replyList =
|
||||||
|
await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
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<SyncDelta> getMediaChanges() async {
|
||||||
|
final String pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.immich_mobile.ImHostService.getMediaChanges$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel =
|
||||||
|
BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
||||||
|
final List<Object?>? pigeonVar_replyList =
|
||||||
|
await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
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<void> checkpointSync() async {
|
||||||
|
final String pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.immich_mobile.ImHostService.checkpointSync$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel =
|
||||||
|
BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
||||||
|
final List<Object?>? pigeonVar_replyList =
|
||||||
|
await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/platform/messages.g.dart';
|
||||||
|
|
||||||
|
final platformMessagesImpl = Provider<ImHostService>((_) => ImHostService());
|
@ -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/infrastructure/repositories/sync_stream.repository.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/album.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/cancel.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
|
|
||||||
final deviceSyncServiceProvider = Provider(
|
final deviceSyncServiceProvider = Provider(
|
||||||
(ref) => DeviceSyncService(
|
(ref) => DeviceSyncService(
|
||||||
albumMediaRepository: ref.watch(albumMediaRepositoryProvider),
|
albumMediaRepository: ref.watch(albumMediaRepositoryProvider),
|
||||||
localAlbumRepository: ref.watch(localAlbumRepository),
|
localAlbumRepository: ref.watch(localAlbumRepository),
|
||||||
localAssetRepository: ref.watch(localAssetProvider),
|
hostService: ref.watch(platformMessagesImpl),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -5,31 +5,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
|
sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "76.0.0"
|
version: "80.0.0"
|
||||||
_macros:
|
|
||||||
dependency: transitive
|
|
||||||
description: dart
|
|
||||||
source: sdk
|
|
||||||
version: "0.3.3"
|
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: "direct overridden"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
|
sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.11.0"
|
version: "7.3.0"
|
||||||
analyzer_plugin:
|
analyzer_plugin:
|
||||||
dependency: "direct overridden"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer_plugin
|
name: analyzer_plugin
|
||||||
sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161"
|
sha256: b3075265c5ab222f8b3188342dcb50b476286394a40323e85d1fa725035d40a4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.3"
|
version: "0.13.0"
|
||||||
ansicolor:
|
ansicolor:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -74,10 +69,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: auto_route_generator
|
name: auto_route_generator
|
||||||
sha256: c9086eb07271e51b44071ad5cff34e889f3156710b964a308c2ab590769e79e6
|
sha256: c2e359d8932986d4d1bcad7a428143f81384ce10fef8d4aa5bc29e1f83766a46
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.0.0"
|
version: "9.3.1"
|
||||||
background_downloader:
|
background_downloader:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -322,34 +317,42 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: custom_lint
|
name: custom_lint
|
||||||
sha256: "4500e88854e7581ee43586abeaf4443cb22375d6d289241a87b1aadf678d5545"
|
sha256: "409c485fd14f544af1da965d5a0d160ee57cd58b63eeaa7280a4f28cf5bda7f1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.10"
|
version: "0.7.5"
|
||||||
custom_lint_builder:
|
custom_lint_builder:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: custom_lint_builder
|
name: custom_lint_builder
|
||||||
sha256: "5a95eff100da256fbf086b329c17c8b49058c261cdf56d3a4157d3c31c511d78"
|
sha256: "107e0a43606138015777590ee8ce32f26ba7415c25b722ff0908a6f5d7a4c228"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.10"
|
version: "0.7.5"
|
||||||
custom_lint_core:
|
custom_lint_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: custom_lint_core
|
name: custom_lint_core
|
||||||
sha256: "76a4046cc71d976222a078a8fd4a65e198b70545a8d690a75196dd14f08510f6"
|
sha256: "31110af3dde9d29fb10828ca33f1dce24d2798477b167675543ce3d208dee8be"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
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:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
|
sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.8"
|
version: "3.0.1"
|
||||||
dartx:
|
dartx:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -675,10 +678,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: freezed_annotation
|
name: freezed_annotation
|
||||||
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
|
sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.4"
|
version: "3.0.0"
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -923,10 +926,11 @@ packages:
|
|||||||
isar_generator:
|
isar_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: isar_generator
|
path: "packages/isar_generator"
|
||||||
sha256: "484e73d3b7e81dbd816852fe0b9497333118a9aeb646fd2d349a62cc8980ffe1"
|
ref: v3
|
||||||
url: "https://pub.isar-community.dev"
|
resolved-ref: ad574f60ed6f39d2995cd16fc7dc3de9a646ef30
|
||||||
source: hosted
|
url: "https://github.com/callumw-k/isar"
|
||||||
|
source: git
|
||||||
version: "3.1.8"
|
version: "3.1.8"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
@ -984,14 +988,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
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:
|
maplibre_gl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1033,7 +1029,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: "direct overridden"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
@ -1264,6 +1260,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
pigeon:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: pigeon
|
||||||
|
sha256: "3e4e6258f22760fa11f86d2a5202fb3f8367cb361d33bd9a93de85a7959e9976"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "25.3.1"
|
||||||
platform:
|
platform:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1348,10 +1352,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: riverpod_analyzer_utils
|
name: riverpod_analyzer_utils
|
||||||
sha256: "0dcb0af32d561f8fa000c6a6d95633c9fb08ea8a8df46e3f9daca59f11218167"
|
sha256: "03a17170088c63aab6c54c44456f5ab78876a1ddb6032ffde1662ddab4959611"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.6"
|
version: "0.5.10"
|
||||||
riverpod_annotation:
|
riverpod_annotation:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1364,18 +1368,18 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: riverpod_generator
|
name: riverpod_generator
|
||||||
sha256: "851aedac7ad52693d12af3bf6d92b1626d516ed6b764eb61bf19e968b5e0b931"
|
sha256: "44a0992d54473eb199ede00e2260bd3c262a86560e3c6f6374503d86d0580e36"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.6.1"
|
version: "2.6.5"
|
||||||
riverpod_lint:
|
riverpod_lint:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: riverpod_lint
|
name: riverpod_lint
|
||||||
sha256: "0684c21a9a4582c28c897d55c7b611fa59a351579061b43f8c92c005804e63a8"
|
sha256: "89a52b7334210dbff8605c3edf26cfe69b15062beed5cbfeff2c3812c33c9e35"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.6.1"
|
version: "2.6.5"
|
||||||
rxdart:
|
rxdart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1537,10 +1541,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_gen
|
name: source_gen
|
||||||
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
|
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "2.0.0"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -82,11 +82,6 @@ dependencies:
|
|||||||
drift: ^2.23.1
|
drift: ^2.23.1
|
||||||
drift_flutter: ^0.2.4
|
drift_flutter: ^0.2.4
|
||||||
|
|
||||||
dependency_overrides:
|
|
||||||
analyzer: ^6.0.0
|
|
||||||
meta: ^1.11.0
|
|
||||||
analyzer_plugin: ^0.11.3
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
@ -96,11 +91,13 @@ dev_dependencies:
|
|||||||
flutter_launcher_icons: ^0.14.3
|
flutter_launcher_icons: ^0.14.3
|
||||||
flutter_native_splash: ^2.4.5
|
flutter_native_splash: ^2.4.5
|
||||||
isar_generator:
|
isar_generator:
|
||||||
version: *isar_version
|
git:
|
||||||
hosted: https://pub.isar-community.dev/
|
url: https://github.com/callumw-k/isar
|
||||||
|
ref: v3
|
||||||
|
path: packages/isar_generator/
|
||||||
integration_test:
|
integration_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
custom_lint: ^0.6.4
|
custom_lint: ^0.7.5
|
||||||
riverpod_lint: ^2.6.1
|
riverpod_lint: ^2.6.1
|
||||||
riverpod_generator: ^2.6.1
|
riverpod_generator: ^2.6.1
|
||||||
mocktail: ^1.0.4
|
mocktail: ^1.0.4
|
||||||
@ -110,6 +107,8 @@ dev_dependencies:
|
|||||||
file: ^7.0.1 # for MemoryFileSystem
|
file: ^7.0.1 # for MemoryFileSystem
|
||||||
# Drift generator
|
# Drift generator
|
||||||
drift_dev: ^2.23.1
|
drift_dev: ^2.23.1
|
||||||
|
# Type safe platform code
|
||||||
|
pigeon: ^25.3.1
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||||
import 'package:immich_mobile/domain/services/user.service.dart';
|
import 'package:immich_mobile/domain/services/user.service.dart';
|
||||||
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
||||||
|
import 'package:immich_mobile/platform/messages.g.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
class MockStoreService extends Mock implements StoreService {}
|
class MockStoreService extends Mock implements StoreService {}
|
||||||
@ -8,3 +9,5 @@ class MockStoreService extends Mock implements StoreService {}
|
|||||||
class MockUserService extends Mock implements UserService {}
|
class MockUserService extends Mock implements UserService {}
|
||||||
|
|
||||||
class MockBackgroundSyncManager extends Mock implements BackgroundSyncManager {}
|
class MockBackgroundSyncManager extends Mock implements BackgroundSyncManager {}
|
||||||
|
|
||||||
|
class MockHostService extends Mock implements ImHostService {}
|
||||||
|
@ -2,21 +2,21 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/album_media.interface.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_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/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/local_album.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/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 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
import '../../fixtures/local_album.stub.dart';
|
import '../../fixtures/local_album.stub.dart';
|
||||||
import '../../fixtures/local_asset.stub.dart';
|
import '../../fixtures/local_asset.stub.dart';
|
||||||
import '../../infrastructure/repository.mock.dart';
|
import '../../infrastructure/repository.mock.dart';
|
||||||
|
import '../service.mock.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
late IAlbumMediaRepository mockAlbumMediaRepo;
|
late IAlbumMediaRepository mockAlbumMediaRepo;
|
||||||
late ILocalAlbumRepository mockLocalAlbumRepo;
|
late ILocalAlbumRepository mockLocalAlbumRepo;
|
||||||
late ILocalAssetRepository mockLocalAssetRepo;
|
late ImHostService mockHostService;
|
||||||
late DeviceSyncService sut;
|
late DeviceSyncService sut;
|
||||||
|
|
||||||
Future<T> mockTransaction<T>(Future<T> Function() action) => action();
|
Future<T> mockTransaction<T>(Future<T> Function() action) => action();
|
||||||
@ -24,12 +24,12 @@ void main() {
|
|||||||
setUp(() {
|
setUp(() {
|
||||||
mockAlbumMediaRepo = MockAlbumMediaRepository();
|
mockAlbumMediaRepo = MockAlbumMediaRepository();
|
||||||
mockLocalAlbumRepo = MockLocalAlbumRepository();
|
mockLocalAlbumRepo = MockLocalAlbumRepository();
|
||||||
mockLocalAssetRepo = MockLocalAssetRepository();
|
mockHostService = MockHostService();
|
||||||
|
|
||||||
sut = DeviceSyncService(
|
sut = DeviceSyncService(
|
||||||
albumMediaRepository: mockAlbumMediaRepo,
|
albumMediaRepository: mockAlbumMediaRepo,
|
||||||
localAlbumRepository: mockLocalAlbumRepo,
|
localAlbumRepository: mockLocalAlbumRepo,
|
||||||
localAssetRepository: mockLocalAssetRepo,
|
hostService: mockHostService,
|
||||||
);
|
);
|
||||||
|
|
||||||
registerFallbackValue(LocalAlbumStub.album1);
|
registerFallbackValue(LocalAlbumStub.album1);
|
||||||
@ -51,8 +51,6 @@ void main() {
|
|||||||
.thenAnswer((_) async => true);
|
.thenAnswer((_) async => true);
|
||||||
when(() => mockLocalAlbumRepo.getAssetsForAlbum(any()))
|
when(() => mockLocalAlbumRepo.getAssetsForAlbum(any()))
|
||||||
.thenAnswer((_) async => []);
|
.thenAnswer((_) async => []);
|
||||||
when(() => mockLocalAssetRepo.get(any()))
|
|
||||||
.thenAnswer((_) async => LocalAssetStub.image1);
|
|
||||||
when(() => mockAlbumMediaRepo.refresh(any())).thenAnswer(
|
when(() => mockAlbumMediaRepo.refresh(any())).thenAnswer(
|
||||||
(inv) async =>
|
(inv) async =>
|
||||||
LocalAlbumStub.album1.copyWith(id: inv.positionalArguments.first),
|
LocalAlbumStub.album1.copyWith(id: inv.positionalArguments.first),
|
||||||
@ -250,7 +248,7 @@ void main() {
|
|||||||
|
|
||||||
group('addAlbum', () {
|
group('addAlbum', () {
|
||||||
test(
|
test(
|
||||||
'refreshes, gets assets, sets thumbnail, and inserts for non-empty album',
|
'refreshes, gets assets, and inserts for non-empty album',
|
||||||
() async {
|
() async {
|
||||||
final newAlbum = LocalAlbumStub.album1.copyWith(assetCount: 0);
|
final newAlbum = LocalAlbumStub.album1.copyWith(assetCount: 0);
|
||||||
final refreshedAlbum =
|
final refreshedAlbum =
|
||||||
@ -284,38 +282,33 @@ void main() {
|
|||||||
expect(capturedAlbum.id, newAlbum.id);
|
expect(capturedAlbum.id, newAlbum.id);
|
||||||
expect(capturedAlbum.assetCount, refreshedAlbum.assetCount);
|
expect(capturedAlbum.assetCount, refreshedAlbum.assetCount);
|
||||||
expect(capturedAlbum.updatedAt, refreshedAlbum.updatedAt);
|
expect(capturedAlbum.updatedAt, refreshedAlbum.updatedAt);
|
||||||
expect(capturedAlbum.thumbnailId, assets.first.id);
|
|
||||||
expect(listEquals(capturedAssets, assets), isTrue);
|
expect(listEquals(capturedAssets, assets), isTrue);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
test('refreshes, skips assets, and inserts for empty album', () async {
|
||||||
'refreshes, skips assets, sets null thumbnail, and inserts for empty album',
|
final newAlbum = LocalAlbumStub.album1.copyWith(assetCount: 0);
|
||||||
() async {
|
final refreshedAlbum =
|
||||||
final newAlbum = LocalAlbumStub.album1.copyWith(assetCount: 0);
|
newAlbum.copyWith(updatedAt: DateTime(2024), assetCount: 0);
|
||||||
final refreshedAlbum =
|
|
||||||
newAlbum.copyWith(updatedAt: DateTime(2024), assetCount: 0);
|
|
||||||
|
|
||||||
when(() => mockAlbumMediaRepo.refresh(newAlbum.id))
|
when(() => mockAlbumMediaRepo.refresh(newAlbum.id))
|
||||||
.thenAnswer((_) async => refreshedAlbum);
|
.thenAnswer((_) async => refreshedAlbum);
|
||||||
|
|
||||||
await sut.addAlbum(newAlbum);
|
await sut.addAlbum(newAlbum);
|
||||||
|
|
||||||
verify(() => mockAlbumMediaRepo.refresh(newAlbum.id)).called(1);
|
verify(() => mockAlbumMediaRepo.refresh(newAlbum.id)).called(1);
|
||||||
verifyNever(() => mockAlbumMediaRepo.getAssetsForAlbum(newAlbum.id));
|
verifyNever(() => mockAlbumMediaRepo.getAssetsForAlbum(newAlbum.id));
|
||||||
|
|
||||||
final captured =
|
final captured =
|
||||||
verify(() => mockLocalAlbumRepo.insert(captureAny(), captureAny()))
|
verify(() => mockLocalAlbumRepo.insert(captureAny(), captureAny()))
|
||||||
.captured;
|
.captured;
|
||||||
final capturedAlbum = captured.first as LocalAlbum;
|
final capturedAlbum = captured.first as LocalAlbum;
|
||||||
final capturedAssets = captured[1] as List<Object?>;
|
final capturedAssets = captured[1] as List<Object?>;
|
||||||
|
|
||||||
expect(capturedAlbum.id, newAlbum.id);
|
expect(capturedAlbum.id, newAlbum.id);
|
||||||
expect(capturedAlbum.assetCount, 0);
|
expect(capturedAlbum.assetCount, 0);
|
||||||
expect(capturedAlbum.thumbnailId, isNull);
|
expect(capturedAssets, isEmpty);
|
||||||
expect(capturedAssets, isEmpty);
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
group('removeAlbum', () {
|
group('removeAlbum', () {
|
||||||
@ -361,11 +354,7 @@ void main() {
|
|||||||
updateTimeCond: any(named: 'updateTimeCond'),
|
updateTimeCond: any(named: 'updateTimeCond'),
|
||||||
),
|
),
|
||||||
).thenAnswer((_) async => [newAsset]);
|
).thenAnswer((_) async => [newAsset]);
|
||||||
final dbAlbumNoThumb =
|
final result = await sut.updateAlbum(dbAlbum, LocalAlbumStub.album1);
|
||||||
dbAlbum.copyWith(thumbnailId: const NullableValue.absent());
|
|
||||||
|
|
||||||
final result =
|
|
||||||
await sut.updateAlbum(dbAlbumNoThumb, LocalAlbumStub.album1);
|
|
||||||
|
|
||||||
expect(result, isTrue);
|
expect(result, isTrue);
|
||||||
verify(() => mockAlbumMediaRepo.refresh(dbAlbum.id)).called(1);
|
verify(() => mockAlbumMediaRepo.refresh(dbAlbum.id)).called(1);
|
||||||
@ -376,7 +365,6 @@ void main() {
|
|||||||
updateTimeCond: any(named: 'updateTimeCond'),
|
updateTimeCond: any(named: 'updateTimeCond'),
|
||||||
),
|
),
|
||||||
).called(1);
|
).called(1);
|
||||||
verifyNever(() => mockLocalAssetRepo.get(any()));
|
|
||||||
|
|
||||||
verify(() => mockLocalAlbumRepo.transaction<void>(any())).called(1);
|
verify(() => mockLocalAlbumRepo.transaction<void>(any())).called(1);
|
||||||
verify(() => mockLocalAlbumRepo.addAssets(dbAlbum.id, [newAsset]))
|
verify(() => mockLocalAlbumRepo.addAssets(dbAlbum.id, [newAsset]))
|
||||||
@ -385,10 +373,7 @@ void main() {
|
|||||||
() => mockLocalAlbumRepo.update(
|
() => mockLocalAlbumRepo.update(
|
||||||
any(
|
any(
|
||||||
that: predicate<LocalAlbum>(
|
that: predicate<LocalAlbum>(
|
||||||
(a) =>
|
(a) => a.id == dbAlbum.id && a.assetCount == 2,
|
||||||
a.id == dbAlbum.id &&
|
|
||||||
a.assetCount == 2 &&
|
|
||||||
a.thumbnailId == newAsset.id,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -437,10 +422,7 @@ void main() {
|
|||||||
() => mockLocalAlbumRepo.update(
|
() => mockLocalAlbumRepo.update(
|
||||||
any(
|
any(
|
||||||
that: predicate<LocalAlbum>(
|
that: predicate<LocalAlbum>(
|
||||||
(a) =>
|
(a) => a.id == dbAlbum.id && a.assetCount == 0,
|
||||||
a.id == dbAlbum.id &&
|
|
||||||
a.assetCount == 0 &&
|
|
||||||
a.thumbnailId == null,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -508,11 +490,8 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('checkAddition', () {
|
group('checkAddition', () {
|
||||||
final dbAlbum = LocalAlbumStub.album1.copyWith(
|
final dbAlbum = LocalAlbumStub.album1
|
||||||
updatedAt: DateTime(2024, 1, 1, 10, 0, 0),
|
.copyWith(updatedAt: DateTime(2024, 1, 1, 10, 0, 0), assetCount: 1);
|
||||||
assetCount: 1,
|
|
||||||
thumbnailId: const NullableValue.value("thumb1"),
|
|
||||||
);
|
|
||||||
final refreshedAlbum = dbAlbum.copyWith(
|
final refreshedAlbum = dbAlbum.copyWith(
|
||||||
updatedAt: DateTime(2024, 1, 1, 11, 0, 0),
|
updatedAt: DateTime(2024, 1, 1, 11, 0, 0),
|
||||||
assetCount: 2,
|
assetCount: 2,
|
||||||
@ -530,13 +509,6 @@ void main() {
|
|||||||
),
|
),
|
||||||
).thenAnswer((_) async => [newAsset]);
|
).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);
|
final result = await sut.checkAddition(dbAlbum, refreshedAlbum);
|
||||||
|
|
||||||
expect(result, isTrue);
|
expect(result, isTrue);
|
||||||
@ -546,19 +518,15 @@ void main() {
|
|||||||
updateTimeCond: any(named: 'updateTimeCond'),
|
updateTimeCond: any(named: 'updateTimeCond'),
|
||||||
),
|
),
|
||||||
).called(1);
|
).called(1);
|
||||||
verify(() => mockLocalAssetRepo.get("thumb1")).called(1);
|
|
||||||
verify(() => mockLocalAlbumRepo.addAssets(dbAlbum.id, [newAsset]))
|
verify(() => mockLocalAlbumRepo.addAssets(dbAlbum.id, [newAsset]))
|
||||||
.called(1);
|
.called(1);
|
||||||
verify(
|
verify(
|
||||||
() => mockLocalAlbumRepo.update(
|
() => mockLocalAlbumRepo.update(
|
||||||
any(
|
any(
|
||||||
that: predicate<LocalAlbum>(
|
that: predicate<LocalAlbum>((a) =>
|
||||||
(a) =>
|
a.id == dbAlbum.id &&
|
||||||
a.id == dbAlbum.id &&
|
a.assetCount == 2 &&
|
||||||
a.assetCount == 2 &&
|
a.updatedAt == refreshedAlbum.updatedAt),
|
||||||
a.updatedAt == refreshedAlbum.updatedAt &&
|
|
||||||
a.thumbnailId == newAsset.id,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).called(1);
|
).called(1);
|
||||||
@ -566,93 +534,6 @@ void main() {
|
|||||||
.called(1);
|
.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<LocalAlbum>(
|
|
||||||
(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<LocalAlbum>(
|
|
||||||
(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 {
|
test('returns false if assetCount decreased', () async {
|
||||||
final decreasedCountAlbum = refreshedAlbum.copyWith(assetCount: 0);
|
final decreasedCountAlbum = refreshedAlbum.copyWith(assetCount: 0);
|
||||||
final result = await sut.checkAddition(dbAlbum, decreasedCountAlbum);
|
final result = await sut.checkAddition(dbAlbum, decreasedCountAlbum);
|
||||||
@ -725,7 +606,6 @@ void main() {
|
|||||||
final dbAlbum = LocalAlbumStub.album1.copyWith(
|
final dbAlbum = LocalAlbumStub.album1.copyWith(
|
||||||
updatedAt: DateTime(2024, 1, 1),
|
updatedAt: DateTime(2024, 1, 1),
|
||||||
assetCount: 2,
|
assetCount: 2,
|
||||||
thumbnailId: const NullableValue.value("asset1"),
|
|
||||||
);
|
);
|
||||||
final refreshedAlbum = dbAlbum.copyWith(
|
final refreshedAlbum = dbAlbum.copyWith(
|
||||||
updatedAt: DateTime(2024, 1, 2),
|
updatedAt: DateTime(2024, 1, 2),
|
||||||
@ -760,7 +640,7 @@ void main() {
|
|||||||
when(() => mockLocalAlbumRepo.getAssetsForAlbum(dbAlbum.id))
|
when(() => mockLocalAlbumRepo.getAssetsForAlbum(dbAlbum.id))
|
||||||
.thenAnswer((_) async => [dbAsset1, dbAsset2]);
|
.thenAnswer((_) async => [dbAsset1, dbAsset2]);
|
||||||
|
|
||||||
final result = await sut.fullSync(dbAlbum, emptyRefreshedAlbum);
|
final result = await sut.fullDiff(dbAlbum, emptyRefreshedAlbum);
|
||||||
|
|
||||||
expect(result, isTrue);
|
expect(result, isTrue);
|
||||||
verifyNever(
|
verifyNever(
|
||||||
@ -773,13 +653,10 @@ void main() {
|
|||||||
verify(
|
verify(
|
||||||
() => mockLocalAlbumRepo.update(
|
() => mockLocalAlbumRepo.update(
|
||||||
any(
|
any(
|
||||||
that: predicate<LocalAlbum>(
|
that: predicate<LocalAlbum>((a) =>
|
||||||
(a) =>
|
a.id == dbAlbum.id &&
|
||||||
a.id == dbAlbum.id &&
|
a.assetCount == 0 &&
|
||||||
a.assetCount == 0 &&
|
a.updatedAt == emptyRefreshedAlbum.updatedAt),
|
||||||
a.updatedAt == emptyRefreshedAlbum.updatedAt &&
|
|
||||||
a.thumbnailId == null,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).called(1);
|
).called(1);
|
||||||
@ -789,10 +666,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('handles empty DB album -> adds all device assets', () async {
|
test('handles empty DB album -> adds all device assets', () async {
|
||||||
final emptyDbAlbum = dbAlbum.copyWith(
|
final emptyDbAlbum = dbAlbum.copyWith(assetCount: 0);
|
||||||
assetCount: 0,
|
|
||||||
thumbnailId: const NullableValue.empty(),
|
|
||||||
);
|
|
||||||
final deviceAssets = [deviceAsset1, deviceAsset3];
|
final deviceAssets = [deviceAsset1, deviceAsset3];
|
||||||
|
|
||||||
deviceAssets.sort((a, b) => a.createdAt.compareTo(b.createdAt));
|
deviceAssets.sort((a, b) => a.createdAt.compareTo(b.createdAt));
|
||||||
@ -804,7 +678,7 @@ void main() {
|
|||||||
when(() => mockLocalAlbumRepo.getAssetsForAlbum(emptyDbAlbum.id))
|
when(() => mockLocalAlbumRepo.getAssetsForAlbum(emptyDbAlbum.id))
|
||||||
.thenAnswer((_) async => []);
|
.thenAnswer((_) async => []);
|
||||||
|
|
||||||
final result = await sut.fullSync(emptyDbAlbum, refreshedWithAssets);
|
final result = await sut.fullDiff(emptyDbAlbum, refreshedWithAssets);
|
||||||
|
|
||||||
expect(result, isTrue);
|
expect(result, isTrue);
|
||||||
verify(() => mockAlbumMediaRepo.getAssetsForAlbum(emptyDbAlbum.id))
|
verify(() => mockAlbumMediaRepo.getAssetsForAlbum(emptyDbAlbum.id))
|
||||||
@ -816,13 +690,10 @@ void main() {
|
|||||||
verify(
|
verify(
|
||||||
() => mockLocalAlbumRepo.update(
|
() => mockLocalAlbumRepo.update(
|
||||||
any(
|
any(
|
||||||
that: predicate<LocalAlbum>(
|
that: predicate<LocalAlbum>((a) =>
|
||||||
(a) =>
|
a.id == emptyDbAlbum.id &&
|
||||||
a.id == emptyDbAlbum.id &&
|
a.assetCount == deviceAssets.length &&
|
||||||
a.assetCount == deviceAssets.length &&
|
a.updatedAt == refreshedWithAssets.updatedAt),
|
||||||
a.updatedAt == refreshedWithAssets.updatedAt &&
|
|
||||||
a.thumbnailId == deviceAssets.first.id,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).called(1);
|
).called(1);
|
||||||
@ -844,7 +715,7 @@ void main() {
|
|||||||
(_) async => dbAssets,
|
(_) async => dbAssets,
|
||||||
);
|
);
|
||||||
|
|
||||||
final result = await sut.fullSync(dbAlbum, currentRefreshedAlbum);
|
final result = await sut.fullDiff(dbAlbum, currentRefreshedAlbum);
|
||||||
|
|
||||||
expect(result, isTrue);
|
expect(result, isTrue);
|
||||||
verify(() => mockAlbumMediaRepo.getAssetsForAlbum(dbAlbum.id)).called(1);
|
verify(() => mockAlbumMediaRepo.getAssetsForAlbum(dbAlbum.id)).called(1);
|
||||||
@ -871,13 +742,10 @@ void main() {
|
|||||||
verify(
|
verify(
|
||||||
() => mockLocalAlbumRepo.update(
|
() => mockLocalAlbumRepo.update(
|
||||||
any(
|
any(
|
||||||
that: predicate<LocalAlbum>(
|
that: predicate<LocalAlbum>((a) =>
|
||||||
(a) =>
|
a.id == dbAlbum.id &&
|
||||||
a.id == dbAlbum.id &&
|
a.assetCount == 2 &&
|
||||||
a.assetCount == 2 &&
|
a.updatedAt == currentRefreshedAlbum.updatedAt),
|
||||||
a.updatedAt == currentRefreshedAlbum.updatedAt &&
|
|
||||||
a.thumbnailId == deviceAssets.first.id,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).called(1);
|
).called(1);
|
||||||
@ -901,7 +769,7 @@ void main() {
|
|||||||
when(() => mockLocalAlbumRepo.getAssetsForAlbum(dbAlbum.id))
|
when(() => mockLocalAlbumRepo.getAssetsForAlbum(dbAlbum.id))
|
||||||
.thenAnswer((_) async => dbAssets);
|
.thenAnswer((_) async => dbAssets);
|
||||||
|
|
||||||
final result = await sut.fullSync(dbAlbum, currentRefreshedAlbum);
|
final result = await sut.fullDiff(dbAlbum, currentRefreshedAlbum);
|
||||||
|
|
||||||
expect(result, isTrue);
|
expect(result, isTrue);
|
||||||
verify(() => mockAlbumMediaRepo.getAssetsForAlbum(dbAlbum.id)).called(1);
|
verify(() => mockAlbumMediaRepo.getAssetsForAlbum(dbAlbum.id)).called(1);
|
||||||
@ -912,13 +780,10 @@ void main() {
|
|||||||
verify(
|
verify(
|
||||||
() => mockLocalAlbumRepo.update(
|
() => mockLocalAlbumRepo.update(
|
||||||
any(
|
any(
|
||||||
that: predicate<LocalAlbum>(
|
that: predicate<LocalAlbum>((a) =>
|
||||||
(a) =>
|
a.id == dbAlbum.id &&
|
||||||
a.id == dbAlbum.id &&
|
a.assetCount == 2 &&
|
||||||
a.assetCount == 2 &&
|
a.updatedAt == currentRefreshedAlbum.updatedAt),
|
||||||
a.updatedAt == currentRefreshedAlbum.updatedAt &&
|
|
||||||
a.thumbnailId == deviceAssets.first.id,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).called(1);
|
).called(1);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user