mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 20:25:32 -04:00
feat: delta sync
This commit is contained in:
parent
bc906f7343
commit
34b83233e5
3
.gitattributes
vendored
3
.gitattributes
vendored
@ -9,6 +9,9 @@ mobile/lib/**/*.g.dart linguist-generated=true
|
|||||||
mobile/lib/**/*.drift.dart -diff -merge
|
mobile/lib/**/*.drift.dart -diff -merge
|
||||||
mobile/lib/**/*.drift.dart linguist-generated=true
|
mobile/lib/**/*.drift.dart linguist-generated=true
|
||||||
|
|
||||||
|
mobile/drift_schemas/main/drift_schema_*.json -diff -merge
|
||||||
|
mobile/drift_schemas/main/drift_schema_*.json linguist-generated=true
|
||||||
|
|
||||||
open-api/typescript-sdk/fetch-client.ts -diff -merge
|
open-api/typescript-sdk/fetch-client.ts -diff -merge
|
||||||
open-api/typescript-sdk/fetch-client.ts linguist-generated=true
|
open-api/typescript-sdk/fetch-client.ts linguist-generated=true
|
||||||
|
|
||||||
|
4
.github/workflows/build-mobile.yml
vendored
4
.github/workflows/build-mobile.yml
vendored
@ -93,6 +93,10 @@ jobs:
|
|||||||
run: make translation
|
run: make translation
|
||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
|
|
||||||
|
- name: Generate platform APIs
|
||||||
|
run: make pigeon
|
||||||
|
working-directory: ./mobile
|
||||||
|
|
||||||
- name: Build Android App Bundle
|
- name: Build Android App Bundle
|
||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
env:
|
env:
|
||||||
|
4
.github/workflows/static_analysis.yml
vendored
4
.github/workflows/static_analysis.yml
vendored
@ -65,6 +65,10 @@ jobs:
|
|||||||
- name: Run Build Runner
|
- name: Run Build Runner
|
||||||
run: make build
|
run: make build
|
||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
|
|
||||||
|
- name: Generate platform API
|
||||||
|
run: make pigeon; dart format ib/platform/native_sync_api.g.dart
|
||||||
|
working-directory: ./mobile
|
||||||
|
|
||||||
- name: Find file changes
|
- name: Find file changes
|
||||||
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
||||||
|
@ -55,6 +55,7 @@ custom_lint:
|
|||||||
restrict: package:photo_manager
|
restrict: package:photo_manager
|
||||||
allowed:
|
allowed:
|
||||||
# required / wanted
|
# required / wanted
|
||||||
|
- 'lib/infrastructure/repositories/album_media.repository.dart'
|
||||||
- 'lib/repositories/{album,asset,file}_media.repository.dart'
|
- 'lib/repositories/{album,asset,file}_media.repository.dart'
|
||||||
# acceptable exceptions for the time being
|
# acceptable exceptions for the time being
|
||||||
- lib/entities/asset.entity.dart # to provide local AssetEntity for now
|
- lib/entities/asset.entity.dart # to provide local AssetEntity for now
|
||||||
|
@ -1,103 +1,106 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "com.android.application"
|
id "com.android.application"
|
||||||
id "kotlin-android"
|
id "kotlin-android"
|
||||||
id "dev.flutter.flutter-gradle-plugin"
|
id "dev.flutter.flutter-gradle-plugin"
|
||||||
id 'com.google.devtools.ksp'
|
id 'com.google.devtools.ksp'
|
||||||
}
|
}
|
||||||
|
|
||||||
def localProperties = new Properties()
|
def localProperties = new Properties()
|
||||||
def localPropertiesFile = rootProject.file('local.properties')
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
if (localPropertiesFile.exists()) {
|
if (localPropertiesFile.exists()) {
|
||||||
localPropertiesFile.withInputStream { localProperties.load(it) }
|
localPropertiesFile.withInputStream { localProperties.load(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
if (flutterVersionCode == null) {
|
if (flutterVersionCode == null) {
|
||||||
flutterVersionCode = '1'
|
flutterVersionCode = '1'
|
||||||
}
|
}
|
||||||
|
|
||||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||||
if (flutterVersionName == null) {
|
if (flutterVersionName == null) {
|
||||||
flutterVersionName = '1.0'
|
flutterVersionName = '1.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
def keystoreProperties = new Properties()
|
def keystoreProperties = new Properties()
|
||||||
def keystorePropertiesFile = rootProject.file('key.properties')
|
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||||
if (keystorePropertiesFile.exists()) {
|
if (keystorePropertiesFile.exists()) {
|
||||||
keystorePropertiesFile.withInputStream { keystoreProperties.load(it) }
|
keystorePropertiesFile.withInputStream { keystoreProperties.load(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 35
|
compileSdkVersion 35
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_17
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
targetCompatibility JavaVersion.VERSION_17
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
coreLibraryDesugaringEnabled true
|
coreLibraryDesugaringEnabled true
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '17'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "app.alextran.immich"
|
||||||
|
minSdkVersion 26
|
||||||
|
targetSdkVersion 35
|
||||||
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
versionName flutterVersionName
|
||||||
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
def keyAliasVal = System.getenv("ALIAS")
|
||||||
|
def keyPasswordVal = System.getenv("ANDROID_KEY_PASSWORD")
|
||||||
|
def storePasswordVal = System.getenv("ANDROID_STORE_PASSWORD")
|
||||||
|
|
||||||
|
keyAlias keyAliasVal ? keyAliasVal : keystoreProperties['keyAlias']
|
||||||
|
keyPassword keyPasswordVal ? keyPasswordVal : keystoreProperties['keyPassword']
|
||||||
|
storeFile file("../key.jks") ? file("../key.jks") : file(keystoreProperties['storeFile'])
|
||||||
|
storePassword storePasswordVal ? storePasswordVal : keystoreProperties['storePassword']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
applicationIdSuffix '.debug'
|
||||||
|
versionNameSuffix '-DEBUG'
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
release {
|
||||||
jvmTarget = '17'
|
signingConfig signingConfigs.release
|
||||||
}
|
}
|
||||||
|
}
|
||||||
sourceSets {
|
namespace 'app.alextran.immich'
|
||||||
main.java.srcDirs += 'src/main/kotlin'
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
applicationId "app.alextran.immich"
|
|
||||||
minSdkVersion 26
|
|
||||||
targetSdkVersion 35
|
|
||||||
versionCode flutterVersionCode.toInteger()
|
|
||||||
versionName flutterVersionName
|
|
||||||
}
|
|
||||||
|
|
||||||
signingConfigs {
|
|
||||||
release {
|
|
||||||
def keyAliasVal = System.getenv("ALIAS")
|
|
||||||
def keyPasswordVal = System.getenv("ANDROID_KEY_PASSWORD")
|
|
||||||
def storePasswordVal = System.getenv("ANDROID_STORE_PASSWORD")
|
|
||||||
|
|
||||||
keyAlias keyAliasVal ? keyAliasVal : keystoreProperties['keyAlias']
|
|
||||||
keyPassword keyPasswordVal ? keyPasswordVal : keystoreProperties['keyPassword']
|
|
||||||
storeFile file("../key.jks") ? file("../key.jks") : file(keystoreProperties['storeFile'])
|
|
||||||
storePassword storePasswordVal ? storePasswordVal : keystoreProperties['storePassword']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
debug {
|
|
||||||
applicationIdSuffix '.debug'
|
|
||||||
versionNameSuffix '-DEBUG'
|
|
||||||
}
|
|
||||||
|
|
||||||
release {
|
|
||||||
signingConfig signingConfigs.release
|
|
||||||
}
|
|
||||||
}
|
|
||||||
namespace 'app.alextran.immich'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
source '../..'
|
source '../..'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
def kotlin_version = '2.0.20'
|
def kotlin_version = '2.0.20'
|
||||||
def kotlin_coroutines_version = '1.9.0'
|
def kotlin_coroutines_version = '1.9.0'
|
||||||
def work_version = '2.9.1'
|
def work_version = '2.9.1'
|
||||||
def concurrent_version = '1.2.0'
|
def concurrent_version = '1.2.0'
|
||||||
def guava_version = '33.3.1-android'
|
def guava_version = '33.3.1-android'
|
||||||
def glide_version = '4.16.0'
|
def glide_version = '4.16.0'
|
||||||
|
def serialization_version = '1.8.1'
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
|
||||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||||
implementation "androidx.concurrent:concurrent-futures:$concurrent_version"
|
implementation "androidx.concurrent:concurrent-futures:$concurrent_version"
|
||||||
implementation "com.google.guava:guava:$guava_version"
|
implementation "com.google.guava:guava:$guava_version"
|
||||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||||
ksp "com.github.bumptech.glide:ksp:$glide_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization_version"
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.2'
|
|
||||||
|
ksp "com.github.bumptech.glide:ksp:$glide_version"
|
||||||
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is uncommented in F-Droid build script
|
// This is uncommented in F-Droid build script
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
package app.alextran.immich
|
package app.alextran.immich
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.ext.SdkExtensions
|
||||||
import androidx.annotation.NonNull
|
import androidx.annotation.NonNull
|
||||||
|
import app.alextran.immich.sync.NativeSyncApi
|
||||||
|
import app.alextran.immich.sync.NativeSyncApiImpl26
|
||||||
|
import app.alextran.immich.sync.NativeSyncApiImpl30
|
||||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
|
||||||
@ -10,5 +15,13 @@ class MainActivity : FlutterFragmentActivity() {
|
|||||||
flutterEngine.plugins.add(BackgroundServicePlugin())
|
flutterEngine.plugins.add(BackgroundServicePlugin())
|
||||||
flutterEngine.plugins.add(HttpSSLOptionsPlugin())
|
flutterEngine.plugins.add(HttpSSLOptionsPlugin())
|
||||||
// No need to set up method channel here as it's now handled in the plugin
|
// No need to set up method channel here as it's now handled in the plugin
|
||||||
|
|
||||||
|
val nativeSyncApiImpl =
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R) < 1) {
|
||||||
|
NativeSyncApiImpl26(this)
|
||||||
|
} else {
|
||||||
|
NativeSyncApiImpl30(this)
|
||||||
|
}
|
||||||
|
NativeSyncApi.setUp(flutterEngine.dartExecutor.binaryMessenger, nativeSyncApiImpl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,355 @@
|
|||||||
|
// Autogenerated from Pigeon (v25.3.2), do not edit directly.
|
||||||
|
// See also: https://pub.dev/packages/pigeon
|
||||||
|
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
|
||||||
|
|
||||||
|
package app.alextran.immich.sync
|
||||||
|
|
||||||
|
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 ImAsset (
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val type: Long,
|
||||||
|
val createdAt: Long? = null,
|
||||||
|
val updatedAt: Long? = null,
|
||||||
|
val durationInSeconds: Long
|
||||||
|
)
|
||||||
|
{
|
||||||
|
companion object {
|
||||||
|
fun fromList(pigeonVar_list: List<Any?>): ImAsset {
|
||||||
|
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 Long?
|
||||||
|
val updatedAt = pigeonVar_list[4] as Long?
|
||||||
|
val durationInSeconds = pigeonVar_list[5] as Long
|
||||||
|
return ImAsset(id, name, type, createdAt, updatedAt, durationInSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf(
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
durationInSeconds,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is ImAsset) {
|
||||||
|
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 ImAlbum (
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val updatedAt: Long? = null,
|
||||||
|
val isCloud: Boolean,
|
||||||
|
val assetCount: Long
|
||||||
|
)
|
||||||
|
{
|
||||||
|
companion object {
|
||||||
|
fun fromList(pigeonVar_list: List<Any?>): ImAlbum {
|
||||||
|
val id = pigeonVar_list[0] as String
|
||||||
|
val name = pigeonVar_list[1] as String
|
||||||
|
val updatedAt = pigeonVar_list[2] as Long?
|
||||||
|
val isCloud = pigeonVar_list[3] as Boolean
|
||||||
|
val assetCount = pigeonVar_list[4] as Long
|
||||||
|
return ImAlbum(id, name, updatedAt, isCloud, assetCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf(
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
updatedAt,
|
||||||
|
isCloud,
|
||||||
|
assetCount,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is ImAlbum) {
|
||||||
|
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 hasChanges: Boolean,
|
||||||
|
val updates: List<ImAsset>,
|
||||||
|
val deletes: List<String>,
|
||||||
|
val assetAlbums: Map<String, List<String>>
|
||||||
|
)
|
||||||
|
{
|
||||||
|
companion object {
|
||||||
|
fun fromList(pigeonVar_list: List<Any?>): SyncDelta {
|
||||||
|
val hasChanges = pigeonVar_list[0] as Boolean
|
||||||
|
val updates = pigeonVar_list[1] as List<ImAsset>
|
||||||
|
val deletes = pigeonVar_list[2] as List<String>
|
||||||
|
val assetAlbums = pigeonVar_list[3] as Map<String, List<String>>
|
||||||
|
return SyncDelta(hasChanges, updates, deletes, assetAlbums)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf(
|
||||||
|
hasChanges,
|
||||||
|
updates,
|
||||||
|
deletes,
|
||||||
|
assetAlbums,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
ImAsset.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
130.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
ImAlbum.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
131.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 ImAsset -> {
|
||||||
|
stream.write(129)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
|
is ImAlbum -> {
|
||||||
|
stream.write(130)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
|
is SyncDelta -> {
|
||||||
|
stream.write(131)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
|
else -> super.writeValue(stream, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||||
|
interface NativeSyncApi {
|
||||||
|
fun shouldFullSync(): Boolean
|
||||||
|
fun getMediaChanges(): SyncDelta
|
||||||
|
fun checkpointSync()
|
||||||
|
fun clearSyncCheckpoint()
|
||||||
|
fun getAssetIdsForAlbum(albumId: String): List<String>
|
||||||
|
fun getAlbums(): List<ImAlbum>
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** The codec used by NativeSyncApi. */
|
||||||
|
val codec: MessageCodec<Any?> by lazy {
|
||||||
|
MessagesPigeonCodec()
|
||||||
|
}
|
||||||
|
/** Sets up an instance of `NativeSyncApi` to handle messages through the `binaryMessenger`. */
|
||||||
|
@JvmOverloads
|
||||||
|
fun setUp(binaryMessenger: BinaryMessenger, api: NativeSyncApi?, messageChannelSuffix: String = "") {
|
||||||
|
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||||
|
val taskQueue = binaryMessenger.makeBackgroundTaskQueue()
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.shouldFullSync$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { _, reply ->
|
||||||
|
val wrapped: List<Any?> = try {
|
||||||
|
listOf(api.shouldFullSync())
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
MessagesPigeonUtils.wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges$separatedMessageChannelSuffix", codec, taskQueue)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { _, reply ->
|
||||||
|
val wrapped: List<Any?> = try {
|
||||||
|
listOf(api.getMediaChanges())
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
MessagesPigeonUtils.wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.checkpointSync$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { _, reply ->
|
||||||
|
val wrapped: List<Any?> = try {
|
||||||
|
api.checkpointSync()
|
||||||
|
listOf(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
MessagesPigeonUtils.wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.clearSyncCheckpoint$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { _, reply ->
|
||||||
|
val wrapped: List<Any?> = try {
|
||||||
|
api.clearSyncCheckpoint()
|
||||||
|
listOf(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
MessagesPigeonUtils.wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum$separatedMessageChannelSuffix", codec, taskQueue)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val albumIdArg = args[0] as String
|
||||||
|
val wrapped: List<Any?> = try {
|
||||||
|
listOf(api.getAssetIdsForAlbum(albumIdArg))
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
MessagesPigeonUtils.wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums$separatedMessageChannelSuffix", codec, taskQueue)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { _, reply ->
|
||||||
|
val wrapped: List<Any?> = try {
|
||||||
|
listOf(api.getAlbums())
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
MessagesPigeonUtils.wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package app.alextran.immich.sync
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
|
||||||
|
class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), NativeSyncApi {
|
||||||
|
override fun shouldFullSync(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// No-op for Android 10 and below
|
||||||
|
override fun checkpointSync() {
|
||||||
|
// Cannot throw exception as this is called from the Dart side
|
||||||
|
// during the full sync process as well
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearSyncCheckpoint() {
|
||||||
|
// No-op for Android 10 and below
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAssetIdsForAlbum(albumId: String): List<String> {
|
||||||
|
throw IllegalStateException("Method not supported on this Android version.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMediaChanges(): SyncDelta {
|
||||||
|
throw IllegalStateException("Method not supported on this Android version.")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
package app.alextran.immich.sync
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.annotation.RequiresExtension
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
@RequiresExtension(extension = Build.VERSION_CODES.R, version = 1)
|
||||||
|
class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), NativeSyncApi {
|
||||||
|
private val ctx: Context = context.applicationContext
|
||||||
|
private val prefs = ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val SHARED_PREF_NAME = "Immich::MediaManager"
|
||||||
|
const val SHARED_PREF_MEDIA_STORE_VERSION_KEY = "MediaStore::getVersion"
|
||||||
|
const val SHARED_PREF_MEDIA_STORE_GEN_KEY = "MediaStore::getGeneration"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSavedGenerationMap(): Map<String, Long> {
|
||||||
|
return prefs.getString(SHARED_PREF_MEDIA_STORE_GEN_KEY, null)?.let {
|
||||||
|
Json.decodeFromString<Map<String, Long>>(it)
|
||||||
|
} ?: emptyMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearSyncCheckpoint() {
|
||||||
|
prefs.edit().apply {
|
||||||
|
remove(SHARED_PREF_MEDIA_STORE_VERSION_KEY)
|
||||||
|
remove(SHARED_PREF_MEDIA_STORE_GEN_KEY)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldFullSync(): Boolean =
|
||||||
|
MediaStore.getVersion(ctx) != prefs.getString(SHARED_PREF_MEDIA_STORE_VERSION_KEY, null)
|
||||||
|
|
||||||
|
override fun checkpointSync() {
|
||||||
|
val genMap = MediaStore.getExternalVolumeNames(ctx)
|
||||||
|
.associateWith { MediaStore.getGeneration(ctx, it) }
|
||||||
|
|
||||||
|
prefs.edit().apply {
|
||||||
|
putString(SHARED_PREF_MEDIA_STORE_VERSION_KEY, MediaStore.getVersion(ctx))
|
||||||
|
putString(SHARED_PREF_MEDIA_STORE_GEN_KEY, Json.encodeToString(genMap))
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAssetIdsForAlbum(albumId: String): List<String> = getAssets(
|
||||||
|
MediaStore.VOLUME_EXTERNAL,
|
||||||
|
"${MediaStore.Files.FileColumns.BUCKET_ID} = ? AND $MEDIA_SELECTION",
|
||||||
|
arrayOf(albumId, *MEDIA_SELECTION_ARGS)
|
||||||
|
).mapNotNull { (it as? AssetResult.ValidAsset)?.asset?.id }.toList()
|
||||||
|
|
||||||
|
override fun getMediaChanges(): SyncDelta {
|
||||||
|
val genMap = getSavedGenerationMap()
|
||||||
|
val currentVolumes = MediaStore.getExternalVolumeNames(ctx)
|
||||||
|
val changed = mutableListOf<ImAsset>()
|
||||||
|
val deleted = mutableListOf<String>()
|
||||||
|
val assetAlbums = mutableMapOf<String, List<String>>()
|
||||||
|
var hasChanges = genMap.keys != currentVolumes
|
||||||
|
|
||||||
|
for (volume in currentVolumes) {
|
||||||
|
val currentGen = MediaStore.getGeneration(ctx, volume)
|
||||||
|
val storedGen = genMap[volume] ?: 0
|
||||||
|
if (currentGen <= storedGen) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
hasChanges = true
|
||||||
|
|
||||||
|
val selection =
|
||||||
|
"$MEDIA_SELECTION AND (${MediaStore.MediaColumns.GENERATION_MODIFIED} > ? OR ${MediaStore.MediaColumns.GENERATION_ADDED} > ?)"
|
||||||
|
val selectionArgs = arrayOf(
|
||||||
|
*MEDIA_SELECTION_ARGS,
|
||||||
|
storedGen.toString(),
|
||||||
|
storedGen.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
getAssets(volume, selection, selectionArgs).forEach {
|
||||||
|
when (it) {
|
||||||
|
is AssetResult.ValidAsset -> {
|
||||||
|
changed.add(it.asset)
|
||||||
|
assetAlbums[it.asset.id] = listOf(it.albumId)
|
||||||
|
}
|
||||||
|
|
||||||
|
is AssetResult.InvalidAsset -> deleted.add(it.assetId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Unmounted volumes are handled in dart when the album is removed
|
||||||
|
return SyncDelta(hasChanges, changed, deleted, assetAlbums)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
package app.alextran.immich.sync
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
sealed class AssetResult {
|
||||||
|
data class ValidAsset(val asset: ImAsset, val albumId: String) : AssetResult()
|
||||||
|
data class InvalidAsset(val assetId: String) : AssetResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
open class NativeSyncApiImplBase(context: Context) {
|
||||||
|
private val ctx: Context = context.applicationContext
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MEDIA_SELECTION =
|
||||||
|
"(${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?)"
|
||||||
|
val MEDIA_SELECTION_ARGS = arrayOf(
|
||||||
|
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE.toString(),
|
||||||
|
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun getAssets(
|
||||||
|
volume: String,
|
||||||
|
selection: String,
|
||||||
|
selectionArgs: Array<String>,
|
||||||
|
): Sequence<AssetResult> {
|
||||||
|
val projection = arrayOf(
|
||||||
|
MediaStore.MediaColumns._ID,
|
||||||
|
MediaStore.MediaColumns.DATA,
|
||||||
|
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||||
|
MediaStore.MediaColumns.DATE_TAKEN,
|
||||||
|
MediaStore.MediaColumns.DATE_ADDED,
|
||||||
|
MediaStore.MediaColumns.DATE_MODIFIED,
|
||||||
|
MediaStore.Files.FileColumns.MEDIA_TYPE,
|
||||||
|
MediaStore.MediaColumns.BUCKET_ID,
|
||||||
|
MediaStore.MediaColumns.DURATION
|
||||||
|
)
|
||||||
|
|
||||||
|
return sequence {
|
||||||
|
ctx.contentResolver.query(
|
||||||
|
MediaStore.Files.getContentUri(volume),
|
||||||
|
projection,
|
||||||
|
selection,
|
||||||
|
selectionArgs,
|
||||||
|
null
|
||||||
|
)?.use { cursor ->
|
||||||
|
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
|
||||||
|
val dataColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
|
||||||
|
val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)
|
||||||
|
val dateTakenColumn =
|
||||||
|
cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_TAKEN)
|
||||||
|
val dateAddedColumn =
|
||||||
|
cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_ADDED)
|
||||||
|
val dateModifiedColumn =
|
||||||
|
cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)
|
||||||
|
val mediaTypeColumn =
|
||||||
|
cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE)
|
||||||
|
val bucketIdColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.BUCKET_ID)
|
||||||
|
val durationColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DURATION)
|
||||||
|
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
val id = cursor.getLong(idColumn).toString()
|
||||||
|
val path = cursor.getString(dataColumn)
|
||||||
|
if (path.isNullOrBlank() || !File(path).exists()) {
|
||||||
|
yield(AssetResult.InvalidAsset(id))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val mediaType = cursor.getInt(mediaTypeColumn)
|
||||||
|
val name = cursor.getString(nameColumn)
|
||||||
|
// Date taken is milliseconds since epoch, Date added is seconds since epoch
|
||||||
|
val createdAt = (cursor.getLong(dateTakenColumn).takeIf { it > 0 }?.div(1000))
|
||||||
|
?: cursor.getLong(dateAddedColumn)
|
||||||
|
// Date modified is seconds since epoch
|
||||||
|
val modifiedAt = cursor.getLong(dateModifiedColumn)
|
||||||
|
// Duration is milliseconds
|
||||||
|
val duration = if (mediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) 0
|
||||||
|
else cursor.getLong(durationColumn) / 1000
|
||||||
|
val bucketId = cursor.getString(bucketIdColumn)
|
||||||
|
|
||||||
|
yield(
|
||||||
|
AssetResult.ValidAsset(
|
||||||
|
ImAsset(id, name, mediaType.toLong(), createdAt, modifiedAt, duration),
|
||||||
|
bucketId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
fun getAlbums(): List<ImAlbum> {
|
||||||
|
val albums = mutableListOf<ImAlbum>()
|
||||||
|
val albumsCount = mutableMapOf<String, Int>()
|
||||||
|
|
||||||
|
val projection = arrayOf(
|
||||||
|
MediaStore.Files.FileColumns.BUCKET_ID,
|
||||||
|
MediaStore.Files.FileColumns.BUCKET_DISPLAY_NAME,
|
||||||
|
MediaStore.Files.FileColumns.DATE_MODIFIED,
|
||||||
|
)
|
||||||
|
val selection =
|
||||||
|
"(${MediaStore.Files.FileColumns.BUCKET_ID} IS NOT NULL) AND $MEDIA_SELECTION"
|
||||||
|
|
||||||
|
ctx.contentResolver.query(
|
||||||
|
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
|
||||||
|
projection,
|
||||||
|
selection,
|
||||||
|
MEDIA_SELECTION_ARGS,
|
||||||
|
"${MediaStore.Files.FileColumns.DATE_MODIFIED} DESC"
|
||||||
|
)?.use { cursor ->
|
||||||
|
val bucketIdColumn =
|
||||||
|
cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.BUCKET_ID)
|
||||||
|
val bucketNameColumn =
|
||||||
|
cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.BUCKET_DISPLAY_NAME)
|
||||||
|
val dateModified =
|
||||||
|
cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATE_MODIFIED)
|
||||||
|
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
val id = cursor.getString(bucketIdColumn)
|
||||||
|
val count = albumsCount.getOrDefault(id, 0)
|
||||||
|
if (count != 0) {
|
||||||
|
albumsCount[id] = count + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val name = cursor.getString(bucketNameColumn)
|
||||||
|
val updatedAt = cursor.getLong(dateModified)
|
||||||
|
albums.add(ImAlbum(id, name, updatedAt, false, 0))
|
||||||
|
albumsCount[id] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return albums.map { album ->
|
||||||
|
val count = albumsCount[album.id] ?: 0
|
||||||
|
album.copy(assetCount = count.toLong())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +1,27 @@
|
|||||||
pluginManagement {
|
pluginManagement {
|
||||||
def flutterSdkPath = {
|
def flutterSdkPath = {
|
||||||
def properties = new Properties()
|
def properties = new Properties()
|
||||||
file("local.properties").withInputStream { properties.load(it) }
|
file("local.properties").withInputStream { properties.load(it) }
|
||||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
return flutterSdkPath
|
return flutterSdkPath
|
||||||
}()
|
}()
|
||||||
|
|
||||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version '8.7.2' apply false
|
id "com.android.application" version '8.7.2' apply false
|
||||||
id "org.jetbrains.kotlin.android" version "2.0.20" apply false
|
id "org.jetbrains.kotlin.android" version "2.0.20" apply false
|
||||||
id 'com.google.devtools.ksp' version '2.0.20-1.0.24' apply false
|
id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.22' apply false
|
||||||
|
id 'com.google.devtools.ksp' version '2.0.20-1.0.24' apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
include ":app"
|
include ":app"
|
||||||
|
@ -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: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.8"
|
version: "3.1.0"
|
||||||
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:
|
||||||
@ -367,4 +362,4 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.6.0 <4.0.0"
|
dart: ">=3.7.0 <4.0.0"
|
||||||
|
@ -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:
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 54;
|
objectVersion = 77;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@ -89,6 +89,14 @@
|
|||||||
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 */
|
||||||
|
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
path = Sync;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
@ -175,6 +183,7 @@
|
|||||||
97C146F01CF9000F007C117D /* Runner */ = {
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
B2CF7F8C2DDE4EBB00744BF6 /* Sync */,
|
||||||
FA9973382CF6DF4B000EF859 /* Runner.entitlements */,
|
FA9973382CF6DF4B000EF859 /* Runner.entitlements */,
|
||||||
65DD438629917FAD0047FFA8 /* BackgroundSync */,
|
65DD438629917FAD0047FFA8 /* BackgroundSync */,
|
||||||
FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */,
|
FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */,
|
||||||
@ -224,6 +233,9 @@
|
|||||||
dependencies = (
|
dependencies = (
|
||||||
FAC6F8992D287C890078CB2F /* PBXTargetDependency */,
|
FAC6F8992D287C890078CB2F /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
B2CF7F8C2DDE4EBB00744BF6 /* Sync */,
|
||||||
|
);
|
||||||
name = Runner;
|
name = Runner;
|
||||||
productName = Runner;
|
productName = Runner;
|
||||||
productReference = 97C146EE1CF9000F007C117D /* Immich-Debug.app */;
|
productReference = 97C146EE1CF9000F007C117D /* Immich-Debug.app */;
|
||||||
@ -270,7 +282,6 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
compatibilityVersion = "Xcode 9.3";
|
|
||||||
developmentRegion = en;
|
developmentRegion = en;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
@ -278,6 +289,7 @@
|
|||||||
Base,
|
Base,
|
||||||
);
|
);
|
||||||
mainGroup = 97C146E51CF9000F007C117D;
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
preferredProjectObjectVersion = 77;
|
||||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
@ -379,10 +391,14 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
name = "[CP] Copy Pods Resources";
|
name = "[CP] Copy Pods Resources";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
@ -411,10 +427,14 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
|
@ -22,6 +22,9 @@ import UIKit
|
|||||||
BackgroundServicePlugin.registerBackgroundProcessing()
|
BackgroundServicePlugin.registerBackgroundProcessing()
|
||||||
|
|
||||||
BackgroundServicePlugin.register(with: self.registrar(forPlugin: "BackgroundServicePlugin")!)
|
BackgroundServicePlugin.register(with: self.registrar(forPlugin: "BackgroundServicePlugin")!)
|
||||||
|
|
||||||
|
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
|
||||||
|
NativeSyncApiSetup.setUp(binaryMessenger: controller.binaryMessenger, api: NativeSyncApiImpl())
|
||||||
|
|
||||||
BackgroundServicePlugin.setPluginRegistrantCallback { registry in
|
BackgroundServicePlugin.setPluginRegistrantCallback { registry in
|
||||||
if !registry.hasPlugin("org.cocoapods.path-provider-foundation") {
|
if !registry.hasPlugin("org.cocoapods.path-provider-foundation") {
|
||||||
|
408
mobile/ios/Runner/Sync/Messages.g.swift
Normal file
408
mobile/ios/Runner/Sync/Messages.g.swift
Normal file
@ -0,0 +1,408 @@
|
|||||||
|
// Autogenerated from Pigeon (v25.3.2), 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 ImAsset: Hashable {
|
||||||
|
var id: String
|
||||||
|
var name: String
|
||||||
|
var type: Int64
|
||||||
|
var createdAt: Int64? = nil
|
||||||
|
var updatedAt: Int64? = nil
|
||||||
|
var durationInSeconds: Int64
|
||||||
|
|
||||||
|
|
||||||
|
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||||
|
static func fromList(_ pigeonVar_list: [Any?]) -> ImAsset? {
|
||||||
|
let id = pigeonVar_list[0] as! String
|
||||||
|
let name = pigeonVar_list[1] as! String
|
||||||
|
let type = pigeonVar_list[2] as! Int64
|
||||||
|
let createdAt: Int64? = nilOrValue(pigeonVar_list[3])
|
||||||
|
let updatedAt: Int64? = nilOrValue(pigeonVar_list[4])
|
||||||
|
let durationInSeconds = pigeonVar_list[5] as! Int64
|
||||||
|
|
||||||
|
return ImAsset(
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
type: type,
|
||||||
|
createdAt: createdAt,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
durationInSeconds: durationInSeconds
|
||||||
|
)
|
||||||
|
}
|
||||||
|
func toList() -> [Any?] {
|
||||||
|
return [
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
durationInSeconds,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
static func == (lhs: ImAsset, rhs: ImAsset) -> 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 ImAlbum: Hashable {
|
||||||
|
var id: String
|
||||||
|
var name: String
|
||||||
|
var updatedAt: Int64? = nil
|
||||||
|
var isCloud: Bool
|
||||||
|
var assetCount: Int64
|
||||||
|
|
||||||
|
|
||||||
|
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||||
|
static func fromList(_ pigeonVar_list: [Any?]) -> ImAlbum? {
|
||||||
|
let id = pigeonVar_list[0] as! String
|
||||||
|
let name = pigeonVar_list[1] as! String
|
||||||
|
let updatedAt: Int64? = nilOrValue(pigeonVar_list[2])
|
||||||
|
let isCloud = pigeonVar_list[3] as! Bool
|
||||||
|
let assetCount = pigeonVar_list[4] as! Int64
|
||||||
|
|
||||||
|
return ImAlbum(
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
isCloud: isCloud,
|
||||||
|
assetCount: assetCount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
func toList() -> [Any?] {
|
||||||
|
return [
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
updatedAt,
|
||||||
|
isCloud,
|
||||||
|
assetCount,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
static func == (lhs: ImAlbum, rhs: ImAlbum) -> 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 hasChanges: Bool
|
||||||
|
var updates: [ImAsset]
|
||||||
|
var deletes: [String]
|
||||||
|
var assetAlbums: [String: [String]]
|
||||||
|
|
||||||
|
|
||||||
|
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||||
|
static func fromList(_ pigeonVar_list: [Any?]) -> SyncDelta? {
|
||||||
|
let hasChanges = pigeonVar_list[0] as! Bool
|
||||||
|
let updates = pigeonVar_list[1] as! [ImAsset]
|
||||||
|
let deletes = pigeonVar_list[2] as! [String]
|
||||||
|
let assetAlbums = pigeonVar_list[3] as! [String: [String]]
|
||||||
|
|
||||||
|
return SyncDelta(
|
||||||
|
hasChanges: hasChanges,
|
||||||
|
updates: updates,
|
||||||
|
deletes: deletes,
|
||||||
|
assetAlbums: assetAlbums
|
||||||
|
)
|
||||||
|
}
|
||||||
|
func toList() -> [Any?] {
|
||||||
|
return [
|
||||||
|
hasChanges,
|
||||||
|
updates,
|
||||||
|
deletes,
|
||||||
|
assetAlbums,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
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 ImAsset.fromList(self.readValue() as! [Any?])
|
||||||
|
case 130:
|
||||||
|
return ImAlbum.fromList(self.readValue() as! [Any?])
|
||||||
|
case 131:
|
||||||
|
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? ImAsset {
|
||||||
|
super.writeByte(129)
|
||||||
|
super.writeValue(value.toList())
|
||||||
|
} else if let value = value as? ImAlbum {
|
||||||
|
super.writeByte(130)
|
||||||
|
super.writeValue(value.toList())
|
||||||
|
} else if let value = value as? SyncDelta {
|
||||||
|
super.writeByte(131)
|
||||||
|
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 NativeSyncApi {
|
||||||
|
func shouldFullSync() throws -> Bool
|
||||||
|
func getMediaChanges() throws -> SyncDelta
|
||||||
|
func checkpointSync() throws
|
||||||
|
func clearSyncCheckpoint() throws
|
||||||
|
func getAssetIdsForAlbum(albumId: String) throws -> [String]
|
||||||
|
func getAlbums() throws -> [ImAlbum]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
||||||
|
class NativeSyncApiSetup {
|
||||||
|
static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared }
|
||||||
|
/// Sets up an instance of `NativeSyncApi` to handle messages through the `binaryMessenger`.
|
||||||
|
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: NativeSyncApi?, 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.NativeSyncApi.shouldFullSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
|
if let api = api {
|
||||||
|
shouldFullSyncChannel.setMessageHandler { _, reply in
|
||||||
|
do {
|
||||||
|
let result = try api.shouldFullSync()
|
||||||
|
reply(wrapResult(result))
|
||||||
|
} catch {
|
||||||
|
reply(wrapError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
shouldFullSyncChannel.setMessageHandler(nil)
|
||||||
|
}
|
||||||
|
let getMediaChangesChannel = taskQueue == nil
|
||||||
|
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
|
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
|
||||||
|
if let api = api {
|
||||||
|
getMediaChangesChannel.setMessageHandler { _, reply in
|
||||||
|
do {
|
||||||
|
let result = try api.getMediaChanges()
|
||||||
|
reply(wrapResult(result))
|
||||||
|
} catch {
|
||||||
|
reply(wrapError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getMediaChangesChannel.setMessageHandler(nil)
|
||||||
|
}
|
||||||
|
let checkpointSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.checkpointSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
|
if let api = api {
|
||||||
|
checkpointSyncChannel.setMessageHandler { _, reply in
|
||||||
|
do {
|
||||||
|
try api.checkpointSync()
|
||||||
|
reply(wrapResult(nil))
|
||||||
|
} catch {
|
||||||
|
reply(wrapError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
checkpointSyncChannel.setMessageHandler(nil)
|
||||||
|
}
|
||||||
|
let clearSyncCheckpointChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.clearSyncCheckpoint\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
|
if let api = api {
|
||||||
|
clearSyncCheckpointChannel.setMessageHandler { _, reply in
|
||||||
|
do {
|
||||||
|
try api.clearSyncCheckpoint()
|
||||||
|
reply(wrapResult(nil))
|
||||||
|
} catch {
|
||||||
|
reply(wrapError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clearSyncCheckpointChannel.setMessageHandler(nil)
|
||||||
|
}
|
||||||
|
let getAssetIdsForAlbumChannel = taskQueue == nil
|
||||||
|
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
|
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
|
||||||
|
if let api = api {
|
||||||
|
getAssetIdsForAlbumChannel.setMessageHandler { message, reply in
|
||||||
|
let args = message as! [Any?]
|
||||||
|
let albumIdArg = args[0] as! String
|
||||||
|
do {
|
||||||
|
let result = try api.getAssetIdsForAlbum(albumId: albumIdArg)
|
||||||
|
reply(wrapResult(result))
|
||||||
|
} catch {
|
||||||
|
reply(wrapError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getAssetIdsForAlbumChannel.setMessageHandler(nil)
|
||||||
|
}
|
||||||
|
let getAlbumsChannel = taskQueue == nil
|
||||||
|
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
|
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
|
||||||
|
if let api = api {
|
||||||
|
getAlbumsChannel.setMessageHandler { _, reply in
|
||||||
|
do {
|
||||||
|
let result = try api.getAlbums()
|
||||||
|
reply(wrapResult(result))
|
||||||
|
} catch {
|
||||||
|
reply(wrapError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getAlbumsChannel.setMessageHandler(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
202
mobile/ios/Runner/Sync/MessagesImpl.swift
Normal file
202
mobile/ios/Runner/Sync/MessagesImpl.swift
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
import Photos
|
||||||
|
|
||||||
|
struct AssetWrapper: Hashable, Equatable {
|
||||||
|
let asset: ImAsset
|
||||||
|
|
||||||
|
init(with asset: ImAsset) {
|
||||||
|
self.asset = asset
|
||||||
|
}
|
||||||
|
|
||||||
|
func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(self.asset.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func == (lhs: AssetWrapper, rhs: AssetWrapper) -> Bool {
|
||||||
|
return lhs.asset.id == rhs.asset.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PHAsset {
|
||||||
|
func toImAsset() -> ImAsset {
|
||||||
|
return ImAsset(
|
||||||
|
id: localIdentifier,
|
||||||
|
name: PHAssetResource.assetResources(for: self).first?.originalFilename ?? title(),
|
||||||
|
type: Int64(mediaType.rawValue),
|
||||||
|
createdAt: creationDate.map { Int64($0.timeIntervalSince1970) },
|
||||||
|
updatedAt: modificationDate.map { Int64($0.timeIntervalSince1970) },
|
||||||
|
durationInSeconds: Int64(duration),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NativeSyncApiImpl: NativeSyncApi {
|
||||||
|
private let defaults: UserDefaults
|
||||||
|
private let changeTokenKey = "immich:changeToken"
|
||||||
|
private let albumTypes: [PHAssetCollectionType] = [.album, .smartAlbum]
|
||||||
|
|
||||||
|
init(with defaults: UserDefaults = .standard) {
|
||||||
|
self.defaults = defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 16, *)
|
||||||
|
private func getChangeToken() -> PHPersistentChangeToken? {
|
||||||
|
guard let data = defaults.data(forKey: changeTokenKey) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return try? NSKeyedUnarchiver.unarchivedObject(ofClass: PHPersistentChangeToken.self, from: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 16, *)
|
||||||
|
private func saveChangeToken(token: PHPersistentChangeToken) -> Void {
|
||||||
|
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: token, requiringSecureCoding: true) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defaults.set(data, forKey: changeTokenKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearSyncCheckpoint() -> Void {
|
||||||
|
defaults.removeObject(forKey: changeTokenKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkpointSync() {
|
||||||
|
guard #available(iOS 16, *) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
saveChangeToken(token: PHPhotoLibrary.shared().currentChangeToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldFullSync() -> Bool {
|
||||||
|
guard #available(iOS 16, *),
|
||||||
|
PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized,
|
||||||
|
let storedToken = getChangeToken() else {
|
||||||
|
// When we do not have access to photo library, older iOS version or No token available, fallback to full sync
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let _ = try? PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken) else {
|
||||||
|
// Cannot fetch persistent changes
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAlbums() throws -> [ImAlbum] {
|
||||||
|
var albums: [ImAlbum] = []
|
||||||
|
|
||||||
|
albumTypes.forEach { type in
|
||||||
|
let collections = PHAssetCollection.fetchAssetCollections(with: type, subtype: .any, options: nil)
|
||||||
|
collections.enumerateObjects { (album, _, _) in
|
||||||
|
let options = PHFetchOptions()
|
||||||
|
options.sortDescriptors = [NSSortDescriptor(key: "modificationDate", ascending: false)]
|
||||||
|
let assets = PHAsset.fetchAssets(in: album, options: options)
|
||||||
|
let isCloud = album.assetCollectionSubtype == .albumCloudShared || album.assetCollectionSubtype == .albumMyPhotoStream
|
||||||
|
|
||||||
|
var domainAlbum = ImAlbum(
|
||||||
|
id: album.localIdentifier,
|
||||||
|
name: album.localizedTitle!,
|
||||||
|
updatedAt: nil,
|
||||||
|
isCloud: isCloud,
|
||||||
|
assetCount: Int64(assets.count)
|
||||||
|
)
|
||||||
|
|
||||||
|
if let firstAsset = assets.firstObject {
|
||||||
|
domainAlbum.updatedAt = firstAsset.modificationDate.map { Int64($0.timeIntervalSince1970) }
|
||||||
|
}
|
||||||
|
|
||||||
|
albums.append(domainAlbum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return albums.sorted { $0.id < $1.id }
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMediaChanges() throws -> SyncDelta {
|
||||||
|
guard #available(iOS 16, *) else {
|
||||||
|
throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature requires iOS 16 or later.", details: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized else {
|
||||||
|
throw PigeonError(code: "NO_AUTH", message: "No photo library access", details: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let storedToken = getChangeToken() else {
|
||||||
|
// No token exists, definitely need a full sync
|
||||||
|
print("MediaManager::getMediaChanges: No token found")
|
||||||
|
throw PigeonError(code: "NO_TOKEN", message: "No stored change token", details: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentToken = PHPhotoLibrary.shared().currentChangeToken
|
||||||
|
if storedToken == currentToken {
|
||||||
|
return SyncDelta(hasChanges: false, updates: [], deletes: [], assetAlbums: [:])
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let changes = try PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken)
|
||||||
|
|
||||||
|
var updatedAssets: Set<AssetWrapper> = []
|
||||||
|
var deletedAssets: Set<String> = []
|
||||||
|
|
||||||
|
for change in changes {
|
||||||
|
guard let details = try? change.changeDetails(for: PHObjectType.asset) else { continue }
|
||||||
|
|
||||||
|
let updated = details.updatedLocalIdentifiers.union(details.insertedLocalIdentifiers)
|
||||||
|
deletedAssets.formUnion(details.deletedLocalIdentifiers)
|
||||||
|
|
||||||
|
if (updated.isEmpty) { continue }
|
||||||
|
|
||||||
|
let result = PHAsset.fetchAssets(withLocalIdentifiers: Array(updated), options: nil)
|
||||||
|
for i in 0..<result.count {
|
||||||
|
let asset = result.object(at: i)
|
||||||
|
|
||||||
|
// Asset wrapper only uses the id for comparison. Multiple change can contain the same asset, skip duplicate changes
|
||||||
|
let predicate = ImAsset(id: asset.localIdentifier, name: "", type: 0, createdAt: nil, updatedAt: nil, durationInSeconds: 0)
|
||||||
|
if (updatedAssets.contains(AssetWrapper(with: predicate))) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let domainAsset = AssetWrapper(with: asset.toImAsset())
|
||||||
|
updatedAssets.insert(domainAsset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let updates = Array(updatedAssets.map { $0.asset })
|
||||||
|
return SyncDelta(hasChanges: true, updates: updates, deletes: Array(deletedAssets), assetAlbums: buildAssetAlbumsMap(assets: updates))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private func buildAssetAlbumsMap(assets: Array<ImAsset>) -> [String: [String]] {
|
||||||
|
guard !assets.isEmpty else {
|
||||||
|
return [:]
|
||||||
|
}
|
||||||
|
|
||||||
|
var albumAssets: [String: [String]] = [:]
|
||||||
|
|
||||||
|
for type in albumTypes {
|
||||||
|
let collections = PHAssetCollection.fetchAssetCollections(with: type, subtype: .any, options: nil)
|
||||||
|
collections.enumerateObjects { (album, _, _) in
|
||||||
|
let options = PHFetchOptions()
|
||||||
|
options.predicate = NSPredicate(format: "localIdentifier IN %@", assets.map(\.id))
|
||||||
|
let result = PHAsset.fetchAssets(in: album, options: options)
|
||||||
|
result.enumerateObjects { (asset, _, _) in
|
||||||
|
albumAssets[asset.localIdentifier, default: []].append(album.localIdentifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return albumAssets
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAssetIdsForAlbum(albumId: String) throws -> [String] {
|
||||||
|
let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil)
|
||||||
|
guard let album = collections.firstObject else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
var ids: [String] = []
|
||||||
|
let assets = PHAsset.fetchAssets(in: album, options: nil)
|
||||||
|
assets.enumerateObjects { (asset, _, _) in
|
||||||
|
ids.append(asset.localIdentifier)
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ const int kLogTruncateLimit = 250;
|
|||||||
|
|
||||||
// Sync
|
// Sync
|
||||||
const int kSyncEventBatchSize = 5000;
|
const int kSyncEventBatchSize = 5000;
|
||||||
|
const int kFetchLocalAssetsBatchSize = 40000;
|
||||||
|
|
||||||
// Hash batch limits
|
// Hash batch limits
|
||||||
const int kBatchHashFileLimit = 128;
|
const int kBatchHashFileLimit = 128;
|
||||||
|
15
mobile/lib/domain/interfaces/album_media.interface.dart
Normal file
15
mobile/lib/domain/interfaces/album_media.interface.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
|
||||||
|
abstract interface class IAlbumMediaRepository {
|
||||||
|
Future<List<LocalAsset>> getAssetsForAlbum(
|
||||||
|
String albumId, {
|
||||||
|
DateTimeFilter? updateTimeCond,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class DateTimeFilter {
|
||||||
|
final DateTime min;
|
||||||
|
final DateTime max;
|
||||||
|
|
||||||
|
const DateTimeFilter({required this.min, required this.max});
|
||||||
|
}
|
31
mobile/lib/domain/interfaces/local_album.interface.dart
Normal file
31
mobile/lib/domain/interfaces/local_album.interface.dart
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/local_album.model.dart';
|
||||||
|
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||||
|
|
||||||
|
abstract interface class ILocalAlbumRepository implements IDatabaseRepository {
|
||||||
|
Future<List<LocalAlbum>> getAll({SortLocalAlbumsBy? sortBy});
|
||||||
|
|
||||||
|
Future<List<LocalAsset>> getAssetsForAlbum(String albumId);
|
||||||
|
|
||||||
|
Future<List<String>> getAssetIdsForAlbum(String albumId);
|
||||||
|
|
||||||
|
Future<void> upsert(
|
||||||
|
LocalAlbum album, {
|
||||||
|
Iterable<LocalAsset> toUpsert = const [],
|
||||||
|
Iterable<String> toDelete = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> updateAll(Iterable<LocalAlbum> albums);
|
||||||
|
|
||||||
|
Future<void> delete(String albumId);
|
||||||
|
|
||||||
|
Future<void> processDelta(SyncDelta delta);
|
||||||
|
|
||||||
|
Future<void> syncAlbumDeletes(
|
||||||
|
String albumId,
|
||||||
|
Iterable<String> assetIdsToKeep,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SortLocalAlbumsBy { id }
|
47
mobile/lib/domain/models/asset/asset.model.dart
Normal file
47
mobile/lib/domain/models/asset/asset.model.dart
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
part of 'base_asset.model.dart';
|
||||||
|
|
||||||
|
// Model for an asset stored in the server
|
||||||
|
class Asset extends BaseAsset {
|
||||||
|
final String id;
|
||||||
|
final String? localId;
|
||||||
|
|
||||||
|
const Asset({
|
||||||
|
required this.id,
|
||||||
|
this.localId,
|
||||||
|
required super.name,
|
||||||
|
required super.checksum,
|
||||||
|
required super.type,
|
||||||
|
required super.createdAt,
|
||||||
|
required super.updatedAt,
|
||||||
|
super.width,
|
||||||
|
super.height,
|
||||||
|
super.durationInSeconds,
|
||||||
|
super.isFavorite = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '''Asset {
|
||||||
|
id: $id,
|
||||||
|
name: $name,
|
||||||
|
type: $type,
|
||||||
|
createdAt: $createdAt,
|
||||||
|
updatedAt: $updatedAt,
|
||||||
|
width: ${width ?? "<NA>"},
|
||||||
|
height: ${height ?? "<NA>"},
|
||||||
|
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
||||||
|
localId: ${localId ?? "<NA>"},
|
||||||
|
isFavorite: $isFavorite,
|
||||||
|
}''';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (other is! Asset) return false;
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
return super == other && id == other.id && localId == other.localId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => super.hashCode ^ id.hashCode ^ localId.hashCode;
|
||||||
|
}
|
76
mobile/lib/domain/models/asset/base_asset.model.dart
Normal file
76
mobile/lib/domain/models/asset/base_asset.model.dart
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
part 'asset.model.dart';
|
||||||
|
part 'local_asset.model.dart';
|
||||||
|
|
||||||
|
enum AssetType {
|
||||||
|
// do not change this order!
|
||||||
|
other,
|
||||||
|
image,
|
||||||
|
video,
|
||||||
|
audio,
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class BaseAsset {
|
||||||
|
final String name;
|
||||||
|
final String? checksum;
|
||||||
|
final AssetType type;
|
||||||
|
final DateTime createdAt;
|
||||||
|
final DateTime updatedAt;
|
||||||
|
final int? width;
|
||||||
|
final int? height;
|
||||||
|
final int? durationInSeconds;
|
||||||
|
final bool isFavorite;
|
||||||
|
|
||||||
|
const BaseAsset({
|
||||||
|
required this.name,
|
||||||
|
required this.checksum,
|
||||||
|
required this.type,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
this.width,
|
||||||
|
this.height,
|
||||||
|
this.durationInSeconds,
|
||||||
|
this.isFavorite = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '''BaseAsset {
|
||||||
|
name: $name,
|
||||||
|
type: $type,
|
||||||
|
createdAt: $createdAt,
|
||||||
|
updatedAt: $updatedAt,
|
||||||
|
width: ${width ?? "<NA>"},
|
||||||
|
height: ${height ?? "<NA>"},
|
||||||
|
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
||||||
|
isFavorite: $isFavorite,
|
||||||
|
}''';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
if (other is BaseAsset) {
|
||||||
|
return name == other.name &&
|
||||||
|
type == other.type &&
|
||||||
|
createdAt == other.createdAt &&
|
||||||
|
updatedAt == other.updatedAt &&
|
||||||
|
width == other.width &&
|
||||||
|
height == other.height &&
|
||||||
|
durationInSeconds == other.durationInSeconds &&
|
||||||
|
isFavorite == other.isFavorite;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return name.hashCode ^
|
||||||
|
type.hashCode ^
|
||||||
|
createdAt.hashCode ^
|
||||||
|
updatedAt.hashCode ^
|
||||||
|
width.hashCode ^
|
||||||
|
height.hashCode ^
|
||||||
|
durationInSeconds.hashCode ^
|
||||||
|
isFavorite.hashCode;
|
||||||
|
}
|
||||||
|
}
|
74
mobile/lib/domain/models/asset/local_asset.model.dart
Normal file
74
mobile/lib/domain/models/asset/local_asset.model.dart
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
part of 'base_asset.model.dart';
|
||||||
|
|
||||||
|
class LocalAsset extends BaseAsset {
|
||||||
|
final String id;
|
||||||
|
final String? remoteId;
|
||||||
|
|
||||||
|
const LocalAsset({
|
||||||
|
required this.id,
|
||||||
|
this.remoteId,
|
||||||
|
required super.name,
|
||||||
|
super.checksum,
|
||||||
|
required super.type,
|
||||||
|
required super.createdAt,
|
||||||
|
required super.updatedAt,
|
||||||
|
super.width,
|
||||||
|
super.height,
|
||||||
|
super.durationInSeconds,
|
||||||
|
super.isFavorite = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '''LocalAsset {
|
||||||
|
id: $id,
|
||||||
|
name: $name,
|
||||||
|
type: $type,
|
||||||
|
createdAt: $createdAt,
|
||||||
|
updatedAt: $updatedAt,
|
||||||
|
width: ${width ?? "<NA>"},
|
||||||
|
height: ${height ?? "<NA>"},
|
||||||
|
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
||||||
|
remoteId: ${remoteId ?? "<NA>"}
|
||||||
|
isFavorite: $isFavorite,
|
||||||
|
}''';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (other is! LocalAsset) return false;
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
return super == other && id == other.id && remoteId == other.remoteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => super.hashCode ^ id.hashCode ^ remoteId.hashCode;
|
||||||
|
|
||||||
|
LocalAsset copyWith({
|
||||||
|
String? id,
|
||||||
|
String? remoteId,
|
||||||
|
String? name,
|
||||||
|
String? checksum,
|
||||||
|
AssetType? type,
|
||||||
|
DateTime? createdAt,
|
||||||
|
DateTime? updatedAt,
|
||||||
|
int? width,
|
||||||
|
int? height,
|
||||||
|
int? durationInSeconds,
|
||||||
|
bool? isFavorite,
|
||||||
|
}) {
|
||||||
|
return LocalAsset(
|
||||||
|
id: id ?? this.id,
|
||||||
|
remoteId: remoteId ?? this.remoteId,
|
||||||
|
name: name ?? this.name,
|
||||||
|
checksum: checksum ?? this.checksum,
|
||||||
|
type: type ?? this.type,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
width: width ?? this.width,
|
||||||
|
height: height ?? this.height,
|
||||||
|
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
|
||||||
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
70
mobile/lib/domain/models/local_album.model.dart
Normal file
70
mobile/lib/domain/models/local_album.model.dart
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
enum BackupSelection {
|
||||||
|
none,
|
||||||
|
selected,
|
||||||
|
excluded,
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalAlbum {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final DateTime updatedAt;
|
||||||
|
|
||||||
|
final int assetCount;
|
||||||
|
final BackupSelection backupSelection;
|
||||||
|
|
||||||
|
const LocalAlbum({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.updatedAt,
|
||||||
|
this.assetCount = 0,
|
||||||
|
this.backupSelection = BackupSelection.none,
|
||||||
|
});
|
||||||
|
|
||||||
|
LocalAlbum copyWith({
|
||||||
|
String? id,
|
||||||
|
String? name,
|
||||||
|
DateTime? updatedAt,
|
||||||
|
int? assetCount,
|
||||||
|
BackupSelection? backupSelection,
|
||||||
|
}) {
|
||||||
|
return LocalAlbum(
|
||||||
|
id: id ?? this.id,
|
||||||
|
name: name ?? this.name,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
assetCount: assetCount ?? this.assetCount,
|
||||||
|
backupSelection: backupSelection ?? this.backupSelection,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (other is! LocalAlbum) return false;
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other.id == id &&
|
||||||
|
other.name == name &&
|
||||||
|
other.updatedAt == updatedAt &&
|
||||||
|
other.assetCount == assetCount &&
|
||||||
|
other.backupSelection == backupSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return id.hashCode ^
|
||||||
|
name.hashCode ^
|
||||||
|
updatedAt.hashCode ^
|
||||||
|
assetCount.hashCode ^
|
||||||
|
backupSelection.hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '''LocalAlbum: {
|
||||||
|
id: $id,
|
||||||
|
name: $name,
|
||||||
|
updatedAt: $updatedAt,
|
||||||
|
assetCount: $assetCount,
|
||||||
|
backupSelection: $backupSelection,
|
||||||
|
}''';
|
||||||
|
}
|
||||||
|
}
|
309
mobile/lib/domain/services/device_sync.service.dart
Normal file
309
mobile/lib/domain/services/device_sync.service.dart
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/album_media.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/local_album.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/local_album.model.dart';
|
||||||
|
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||||
|
import 'package:immich_mobile/utils/diff.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
|
class DeviceSyncService {
|
||||||
|
final IAlbumMediaRepository _albumMediaRepository;
|
||||||
|
final ILocalAlbumRepository _localAlbumRepository;
|
||||||
|
final Platform _platform;
|
||||||
|
final NativeSyncApi _nativeSyncApi;
|
||||||
|
final Logger _log = Logger("DeviceSyncService");
|
||||||
|
|
||||||
|
DeviceSyncService({
|
||||||
|
required IAlbumMediaRepository albumMediaRepository,
|
||||||
|
required ILocalAlbumRepository localAlbumRepository,
|
||||||
|
required NativeSyncApi nativeSyncApi,
|
||||||
|
Platform? platform,
|
||||||
|
}) : _albumMediaRepository = albumMediaRepository,
|
||||||
|
_localAlbumRepository = localAlbumRepository,
|
||||||
|
_platform = platform ?? const LocalPlatform(),
|
||||||
|
_nativeSyncApi = nativeSyncApi;
|
||||||
|
|
||||||
|
Future<void> sync() async {
|
||||||
|
final Stopwatch stopwatch = Stopwatch()..start();
|
||||||
|
try {
|
||||||
|
if (await _nativeSyncApi.shouldFullSync()) {
|
||||||
|
_log.fine("Cannot use partial sync. Performing full sync");
|
||||||
|
return await fullSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
final delta = await _nativeSyncApi.getMediaChanges();
|
||||||
|
if (!delta.hasChanges) {
|
||||||
|
_log.fine("No media changes detected. Skipping sync");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final deviceAlbums = await _nativeSyncApi.getAlbums();
|
||||||
|
await _localAlbumRepository.updateAll(deviceAlbums.toLocalAlbums());
|
||||||
|
await _localAlbumRepository.processDelta(delta);
|
||||||
|
|
||||||
|
if (_platform.isAndroid) {
|
||||||
|
final dbAlbums = await _localAlbumRepository.getAll();
|
||||||
|
for (final album in dbAlbums) {
|
||||||
|
final deviceIds = await _nativeSyncApi.getAssetIdsForAlbum(album.id);
|
||||||
|
await _localAlbumRepository.syncAlbumDeletes(album.id, deviceIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _nativeSyncApi.checkpointSync();
|
||||||
|
} catch (e, s) {
|
||||||
|
_log.severe("Error performing device sync", e, s);
|
||||||
|
} finally {
|
||||||
|
stopwatch.stop();
|
||||||
|
_log.info("Device sync took - ${stopwatch.elapsedMilliseconds}ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> fullSync() async {
|
||||||
|
try {
|
||||||
|
final Stopwatch stopwatch = Stopwatch()..start();
|
||||||
|
|
||||||
|
final deviceAlbums = (await _nativeSyncApi.getAlbums()).toLocalAlbums();
|
||||||
|
|
||||||
|
final dbAlbums =
|
||||||
|
await _localAlbumRepository.getAll(sortBy: SortLocalAlbumsBy.id);
|
||||||
|
|
||||||
|
await diffSortedLists(
|
||||||
|
dbAlbums,
|
||||||
|
deviceAlbums,
|
||||||
|
compare: (a, b) => a.id.compareTo(b.id),
|
||||||
|
both: updateAlbum,
|
||||||
|
onlyFirst: removeAlbum,
|
||||||
|
onlySecond: addAlbum,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _nativeSyncApi.checkpointSync();
|
||||||
|
stopwatch.stop();
|
||||||
|
_log.info("Full device sync took - ${stopwatch.elapsedMilliseconds}ms");
|
||||||
|
} catch (e, s) {
|
||||||
|
_log.severe("Error performing full device sync", e, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addAlbum(LocalAlbum album) async {
|
||||||
|
try {
|
||||||
|
_log.fine("Adding device album ${album.name}");
|
||||||
|
|
||||||
|
final assets = album.assetCount > 0
|
||||||
|
? await _albumMediaRepository.getAssetsForAlbum(album.id)
|
||||||
|
: <LocalAsset>[];
|
||||||
|
|
||||||
|
await _localAlbumRepository.upsert(album, toUpsert: assets);
|
||||||
|
_log.fine("Successfully added device album ${album.name}");
|
||||||
|
} catch (e, s) {
|
||||||
|
_log.warning("Error while adding device album", e, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> removeAlbum(LocalAlbum a) async {
|
||||||
|
_log.fine("Removing device album ${a.name}");
|
||||||
|
try {
|
||||||
|
// Asset deletion is handled in the repository
|
||||||
|
await _localAlbumRepository.delete(a.id);
|
||||||
|
} catch (e, s) {
|
||||||
|
_log.warning("Error while removing device album", e, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The deviceAlbum is ignored since we are going to refresh it anyways
|
||||||
|
FutureOr<bool> updateAlbum(LocalAlbum dbAlbum, LocalAlbum deviceAlbum) async {
|
||||||
|
try {
|
||||||
|
_log.fine("Syncing device album ${dbAlbum.name}");
|
||||||
|
|
||||||
|
if (_albumsEqual(deviceAlbum, dbAlbum)) {
|
||||||
|
_log.fine(
|
||||||
|
"Device album ${dbAlbum.name} has not changed. Skipping sync.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_log.fine("Device album ${dbAlbum.name} has changed. Syncing...");
|
||||||
|
|
||||||
|
// Faster path - only new assets added
|
||||||
|
if (await checkAddition(dbAlbum, deviceAlbum)) {
|
||||||
|
_log.fine("Fast synced device album ${dbAlbum.name}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slower path - full sync
|
||||||
|
return await fullDiff(dbAlbum, deviceAlbum);
|
||||||
|
} catch (e, s) {
|
||||||
|
_log.warning("Error while diff device album", e, s);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
// The [deviceAlbum] is expected to be refreshed before calling this method
|
||||||
|
// with modified time and asset count
|
||||||
|
Future<bool> checkAddition(
|
||||||
|
LocalAlbum dbAlbum,
|
||||||
|
LocalAlbum deviceAlbum,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
_log.fine("Fast syncing device album ${dbAlbum.name}");
|
||||||
|
// Assets has been modified
|
||||||
|
if (deviceAlbum.assetCount <= dbAlbum.assetCount) {
|
||||||
|
_log.fine("Local album has modifications. Proceeding to full sync");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all assets that are modified after the last known modifiedTime
|
||||||
|
final newAssets = await _albumMediaRepository.getAssetsForAlbum(
|
||||||
|
deviceAlbum.id,
|
||||||
|
updateTimeCond: DateTimeFilter(
|
||||||
|
min: dbAlbum.updatedAt.add(const Duration(seconds: 1)),
|
||||||
|
max: deviceAlbum.updatedAt,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Early return if no new assets were found
|
||||||
|
if (newAssets.isEmpty) {
|
||||||
|
_log.fine(
|
||||||
|
"No new assets found despite album having changes. Proceeding to full sync for ${dbAlbum.name}",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether there is only addition or if there has been deletions
|
||||||
|
if (deviceAlbum.assetCount != dbAlbum.assetCount + newAssets.length) {
|
||||||
|
_log.fine("Local album has modifications. Proceeding to full sync");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _localAlbumRepository.upsert(
|
||||||
|
deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection),
|
||||||
|
toUpsert: newAssets,
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e, s) {
|
||||||
|
_log.warning("Error on fast syncing local album: ${dbAlbum.name}", e, s);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
// The [deviceAlbum] is expected to be refreshed before calling this method
|
||||||
|
// with modified time and asset count
|
||||||
|
Future<bool> fullDiff(LocalAlbum dbAlbum, LocalAlbum deviceAlbum) async {
|
||||||
|
try {
|
||||||
|
final assetsInDevice = deviceAlbum.assetCount > 0
|
||||||
|
? await _albumMediaRepository.getAssetsForAlbum(deviceAlbum.id)
|
||||||
|
: <LocalAsset>[];
|
||||||
|
final assetsInDb = dbAlbum.assetCount > 0
|
||||||
|
? await _localAlbumRepository.getAssetsForAlbum(dbAlbum.id)
|
||||||
|
: <LocalAsset>[];
|
||||||
|
|
||||||
|
if (deviceAlbum.assetCount == 0) {
|
||||||
|
_log.fine(
|
||||||
|
"Device album ${deviceAlbum.name} is empty. Removing assets from DB.",
|
||||||
|
);
|
||||||
|
await _localAlbumRepository.upsert(
|
||||||
|
deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection),
|
||||||
|
toDelete: assetsInDb.map((a) => a.id),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final updatedDeviceAlbum = deviceAlbum.copyWith(
|
||||||
|
backupSelection: dbAlbum.backupSelection,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dbAlbum.assetCount == 0) {
|
||||||
|
_log.fine(
|
||||||
|
"Device album ${deviceAlbum.name} is empty. Adding assets to DB.",
|
||||||
|
);
|
||||||
|
await _localAlbumRepository.upsert(
|
||||||
|
updatedDeviceAlbum,
|
||||||
|
toUpsert: assetsInDevice,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(assetsInDb.isSortedBy((a) => a.id));
|
||||||
|
assetsInDevice.sort((a, b) => a.id.compareTo(b.id));
|
||||||
|
|
||||||
|
final assetsToUpsert = <LocalAsset>[];
|
||||||
|
final assetsToDelete = <String>[];
|
||||||
|
|
||||||
|
diffSortedListsSync(
|
||||||
|
assetsInDb,
|
||||||
|
assetsInDevice,
|
||||||
|
compare: (a, b) => a.id.compareTo(b.id),
|
||||||
|
both: (dbAsset, deviceAsset) {
|
||||||
|
// Custom comparison to check if the asset has been modified without
|
||||||
|
// comparing the checksum
|
||||||
|
if (!_assetsEqual(dbAsset, deviceAsset)) {
|
||||||
|
assetsToUpsert.add(deviceAsset);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
onlyFirst: (dbAsset) => assetsToDelete.add(dbAsset.id),
|
||||||
|
onlySecond: (deviceAsset) => assetsToUpsert.add(deviceAsset),
|
||||||
|
);
|
||||||
|
|
||||||
|
_log.fine(
|
||||||
|
"Syncing ${deviceAlbum.name}. ${assetsToUpsert.length} assets to add/update and ${assetsToDelete.length} assets to delete",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (assetsToUpsert.isEmpty && assetsToDelete.isEmpty) {
|
||||||
|
_log.fine(
|
||||||
|
"No asset changes detected in album ${deviceAlbum.name}. Updating metadata.",
|
||||||
|
);
|
||||||
|
_localAlbumRepository.upsert(updatedDeviceAlbum);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _localAlbumRepository.upsert(
|
||||||
|
updatedDeviceAlbum,
|
||||||
|
toUpsert: assetsToUpsert,
|
||||||
|
toDelete: assetsToDelete,
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e, s) {
|
||||||
|
_log.warning("Error on full syncing local album: ${dbAlbum.name}", e, s);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _assetsEqual(LocalAsset a, LocalAsset b) {
|
||||||
|
return a.updatedAt.isAtSameMomentAs(b.updatedAt) &&
|
||||||
|
a.createdAt.isAtSameMomentAs(b.createdAt) &&
|
||||||
|
a.width == b.width &&
|
||||||
|
a.height == b.height &&
|
||||||
|
a.durationInSeconds == b.durationInSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _albumsEqual(LocalAlbum a, LocalAlbum b) {
|
||||||
|
return a.name == b.name &&
|
||||||
|
a.assetCount == b.assetCount &&
|
||||||
|
a.updatedAt.isAtSameMomentAs(b.updatedAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on Iterable<ImAlbum> {
|
||||||
|
List<LocalAlbum> toLocalAlbums() {
|
||||||
|
return map(
|
||||||
|
(e) => LocalAlbum(
|
||||||
|
id: e.id,
|
||||||
|
name: e.name,
|
||||||
|
updatedAt: e.updatedAt == null
|
||||||
|
? DateTime.now()
|
||||||
|
: DateTime.fromMillisecondsSinceEpoch(e.updatedAt! * 1000),
|
||||||
|
assetCount: e.assetCount,
|
||||||
|
),
|
||||||
|
).toList();
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,12 @@
|
|||||||
// ignore_for_file: avoid-passing-async-when-sync-expected
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/sync_stream.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/sync.provider.dart';
|
||||||
import 'package:immich_mobile/utils/isolate.dart';
|
import 'package:immich_mobile/utils/isolate.dart';
|
||||||
import 'package:worker_manager/worker_manager.dart';
|
import 'package:worker_manager/worker_manager.dart';
|
||||||
|
|
||||||
class BackgroundSyncManager {
|
class BackgroundSyncManager {
|
||||||
Cancelable<void>? _syncTask;
|
Cancelable<void>? _syncTask;
|
||||||
|
Cancelable<void>? _deviceAlbumSyncTask;
|
||||||
|
|
||||||
BackgroundSyncManager();
|
BackgroundSyncManager();
|
||||||
|
|
||||||
@ -23,7 +22,22 @@ class BackgroundSyncManager {
|
|||||||
return Future.wait(futures);
|
return Future.wait(futures);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sync() {
|
// No need to cancel the task, as it can also be run when the user logs out
|
||||||
|
Future<void> syncLocal() {
|
||||||
|
if (_deviceAlbumSyncTask != null) {
|
||||||
|
return _deviceAlbumSyncTask!.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
_deviceAlbumSyncTask = runInIsolateGentle(
|
||||||
|
computation: (ref) => ref.read(deviceSyncServiceProvider).sync(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return _deviceAlbumSyncTask!.whenComplete(() {
|
||||||
|
_deviceAlbumSyncTask = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> syncRemote() {
|
||||||
if (_syncTask != null) {
|
if (_syncTask != null) {
|
||||||
return _syncTask!.future;
|
return _syncTask!.future;
|
||||||
}
|
}
|
||||||
@ -31,9 +45,8 @@ class BackgroundSyncManager {
|
|||||||
_syncTask = runInIsolateGentle(
|
_syncTask = runInIsolateGentle(
|
||||||
computation: (ref) => ref.read(syncStreamServiceProvider).sync(),
|
computation: (ref) => ref.read(syncStreamServiceProvider).sync(),
|
||||||
);
|
);
|
||||||
_syncTask!.whenComplete(() {
|
return _syncTask!.whenComplete(() {
|
||||||
_syncTask = null;
|
_syncTask = null;
|
||||||
});
|
});
|
||||||
return _syncTask!.future;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
mobile/lib/infrastructure/entities/local_album.entity.dart
Normal file
18
mobile/lib/infrastructure/entities/local_album.entity.dart
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/local_album.model.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
|
|
||||||
|
class LocalAlbumEntity extends Table with DriftDefaultsMixin {
|
||||||
|
const LocalAlbumEntity();
|
||||||
|
|
||||||
|
TextColumn get id => text()();
|
||||||
|
TextColumn get name => text()();
|
||||||
|
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
IntColumn get backupSelection => intEnum<BackupSelection>()();
|
||||||
|
|
||||||
|
// Used for mark & sweep
|
||||||
|
BoolColumn get marker_ => boolean().nullable()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {id};
|
||||||
|
}
|
497
mobile/lib/infrastructure/entities/local_album.entity.drift.dart
generated
Normal file
497
mobile/lib/infrastructure/entities/local_album.entity.drift.dart
generated
Normal file
@ -0,0 +1,497 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
import 'package:drift/drift.dart' as i0;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'
|
||||||
|
as i1;
|
||||||
|
import 'package:immich_mobile/domain/models/local_album.model.dart' as i2;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart'
|
||||||
|
as i3;
|
||||||
|
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
|
||||||
|
|
||||||
|
typedef $$LocalAlbumEntityTableCreateCompanionBuilder
|
||||||
|
= i1.LocalAlbumEntityCompanion Function({
|
||||||
|
required String id,
|
||||||
|
required String name,
|
||||||
|
i0.Value<DateTime> updatedAt,
|
||||||
|
required i2.BackupSelection backupSelection,
|
||||||
|
i0.Value<bool?> marker_,
|
||||||
|
});
|
||||||
|
typedef $$LocalAlbumEntityTableUpdateCompanionBuilder
|
||||||
|
= i1.LocalAlbumEntityCompanion Function({
|
||||||
|
i0.Value<String> id,
|
||||||
|
i0.Value<String> name,
|
||||||
|
i0.Value<DateTime> updatedAt,
|
||||||
|
i0.Value<i2.BackupSelection> backupSelection,
|
||||||
|
i0.Value<bool?> marker_,
|
||||||
|
});
|
||||||
|
|
||||||
|
class $$LocalAlbumEntityTableFilterComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$LocalAlbumEntityTable> {
|
||||||
|
$$LocalAlbumEntityTableFilterComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.ColumnFilters<String> get id => $composableBuilder(
|
||||||
|
column: $table.id, builder: (column) => i0.ColumnFilters(column));
|
||||||
|
|
||||||
|
i0.ColumnFilters<String> get name => $composableBuilder(
|
||||||
|
column: $table.name, builder: (column) => i0.ColumnFilters(column));
|
||||||
|
|
||||||
|
i0.ColumnFilters<DateTime> get updatedAt => $composableBuilder(
|
||||||
|
column: $table.updatedAt, builder: (column) => i0.ColumnFilters(column));
|
||||||
|
|
||||||
|
i0.ColumnWithTypeConverterFilters<i2.BackupSelection, i2.BackupSelection, int>
|
||||||
|
get backupSelection => $composableBuilder(
|
||||||
|
column: $table.backupSelection,
|
||||||
|
builder: (column) => i0.ColumnWithTypeConverterFilters(column));
|
||||||
|
|
||||||
|
i0.ColumnFilters<bool> get marker_ => $composableBuilder(
|
||||||
|
column: $table.marker_, builder: (column) => i0.ColumnFilters(column));
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$LocalAlbumEntityTableOrderingComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$LocalAlbumEntityTable> {
|
||||||
|
$$LocalAlbumEntityTableOrderingComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.ColumnOrderings<String> get id => $composableBuilder(
|
||||||
|
column: $table.id, builder: (column) => i0.ColumnOrderings(column));
|
||||||
|
|
||||||
|
i0.ColumnOrderings<String> get name => $composableBuilder(
|
||||||
|
column: $table.name, builder: (column) => i0.ColumnOrderings(column));
|
||||||
|
|
||||||
|
i0.ColumnOrderings<DateTime> get updatedAt => $composableBuilder(
|
||||||
|
column: $table.updatedAt,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column));
|
||||||
|
|
||||||
|
i0.ColumnOrderings<int> get backupSelection => $composableBuilder(
|
||||||
|
column: $table.backupSelection,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column));
|
||||||
|
|
||||||
|
i0.ColumnOrderings<bool> get marker_ => $composableBuilder(
|
||||||
|
column: $table.marker_, builder: (column) => i0.ColumnOrderings(column));
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$LocalAlbumEntityTableAnnotationComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$LocalAlbumEntityTable> {
|
||||||
|
$$LocalAlbumEntityTableAnnotationComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.GeneratedColumn<String> get id =>
|
||||||
|
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<String> get name =>
|
||||||
|
$composableBuilder(column: $table.name, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<DateTime> get updatedAt =>
|
||||||
|
$composableBuilder(column: $table.updatedAt, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumnWithTypeConverter<i2.BackupSelection, int>
|
||||||
|
get backupSelection => $composableBuilder(
|
||||||
|
column: $table.backupSelection, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<bool> get marker_ =>
|
||||||
|
$composableBuilder(column: $table.marker_, builder: (column) => column);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$LocalAlbumEntityTableTableManager extends i0.RootTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$LocalAlbumEntityTable,
|
||||||
|
i1.LocalAlbumEntityData,
|
||||||
|
i1.$$LocalAlbumEntityTableFilterComposer,
|
||||||
|
i1.$$LocalAlbumEntityTableOrderingComposer,
|
||||||
|
i1.$$LocalAlbumEntityTableAnnotationComposer,
|
||||||
|
$$LocalAlbumEntityTableCreateCompanionBuilder,
|
||||||
|
$$LocalAlbumEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.LocalAlbumEntityData,
|
||||||
|
i0.BaseReferences<i0.GeneratedDatabase, i1.$LocalAlbumEntityTable,
|
||||||
|
i1.LocalAlbumEntityData>
|
||||||
|
),
|
||||||
|
i1.LocalAlbumEntityData,
|
||||||
|
i0.PrefetchHooks Function()> {
|
||||||
|
$$LocalAlbumEntityTableTableManager(
|
||||||
|
i0.GeneratedDatabase db, i1.$LocalAlbumEntityTable table)
|
||||||
|
: super(i0.TableManagerState(
|
||||||
|
db: db,
|
||||||
|
table: table,
|
||||||
|
createFilteringComposer: () =>
|
||||||
|
i1.$$LocalAlbumEntityTableFilterComposer($db: db, $table: table),
|
||||||
|
createOrderingComposer: () => i1
|
||||||
|
.$$LocalAlbumEntityTableOrderingComposer($db: db, $table: table),
|
||||||
|
createComputedFieldComposer: () =>
|
||||||
|
i1.$$LocalAlbumEntityTableAnnotationComposer(
|
||||||
|
$db: db, $table: table),
|
||||||
|
updateCompanionCallback: ({
|
||||||
|
i0.Value<String> id = const i0.Value.absent(),
|
||||||
|
i0.Value<String> name = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
|
i0.Value<i2.BackupSelection> backupSelection =
|
||||||
|
const i0.Value.absent(),
|
||||||
|
i0.Value<bool?> marker_ = const i0.Value.absent(),
|
||||||
|
}) =>
|
||||||
|
i1.LocalAlbumEntityCompanion(
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
backupSelection: backupSelection,
|
||||||
|
marker_: marker_,
|
||||||
|
),
|
||||||
|
createCompanionCallback: ({
|
||||||
|
required String id,
|
||||||
|
required String name,
|
||||||
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
|
required i2.BackupSelection backupSelection,
|
||||||
|
i0.Value<bool?> marker_ = const i0.Value.absent(),
|
||||||
|
}) =>
|
||||||
|
i1.LocalAlbumEntityCompanion.insert(
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
backupSelection: backupSelection,
|
||||||
|
marker_: marker_,
|
||||||
|
),
|
||||||
|
withReferenceMapper: (p0) => p0
|
||||||
|
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||||
|
.toList(),
|
||||||
|
prefetchHooksCallback: null,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef $$LocalAlbumEntityTableProcessedTableManager = i0.ProcessedTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$LocalAlbumEntityTable,
|
||||||
|
i1.LocalAlbumEntityData,
|
||||||
|
i1.$$LocalAlbumEntityTableFilterComposer,
|
||||||
|
i1.$$LocalAlbumEntityTableOrderingComposer,
|
||||||
|
i1.$$LocalAlbumEntityTableAnnotationComposer,
|
||||||
|
$$LocalAlbumEntityTableCreateCompanionBuilder,
|
||||||
|
$$LocalAlbumEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.LocalAlbumEntityData,
|
||||||
|
i0.BaseReferences<i0.GeneratedDatabase, i1.$LocalAlbumEntityTable,
|
||||||
|
i1.LocalAlbumEntityData>
|
||||||
|
),
|
||||||
|
i1.LocalAlbumEntityData,
|
||||||
|
i0.PrefetchHooks Function()>;
|
||||||
|
|
||||||
|
class $LocalAlbumEntityTable extends i3.LocalAlbumEntity
|
||||||
|
with i0.TableInfo<$LocalAlbumEntityTable, i1.LocalAlbumEntityData> {
|
||||||
|
@override
|
||||||
|
final i0.GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
$LocalAlbumEntityTable(this.attachedDatabase, [this._alias]);
|
||||||
|
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> id = i0.GeneratedColumn<String>(
|
||||||
|
'id', aliasedName, false,
|
||||||
|
type: i0.DriftSqlType.string, requiredDuringInsert: true);
|
||||||
|
static const i0.VerificationMeta _nameMeta =
|
||||||
|
const i0.VerificationMeta('name');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> name = i0.GeneratedColumn<String>(
|
||||||
|
'name', aliasedName, false,
|
||||||
|
type: i0.DriftSqlType.string, requiredDuringInsert: true);
|
||||||
|
static const i0.VerificationMeta _updatedAtMeta =
|
||||||
|
const i0.VerificationMeta('updatedAt');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<DateTime> updatedAt =
|
||||||
|
i0.GeneratedColumn<DateTime>('updated_at', aliasedName, false,
|
||||||
|
type: i0.DriftSqlType.dateTime,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: i4.currentDateAndTime);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumnWithTypeConverter<i2.BackupSelection, int>
|
||||||
|
backupSelection = i0.GeneratedColumn<int>(
|
||||||
|
'backup_selection', aliasedName, false,
|
||||||
|
type: i0.DriftSqlType.int, requiredDuringInsert: true)
|
||||||
|
.withConverter<i2.BackupSelection>(
|
||||||
|
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, true,
|
||||||
|
type: i0.DriftSqlType.bool,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultConstraints:
|
||||||
|
i0.GeneratedColumn.constraintIsAlways('CHECK ("marker" IN (0, 1))'));
|
||||||
|
@override
|
||||||
|
List<i0.GeneratedColumn> get $columns =>
|
||||||
|
[id, name, updatedAt, backupSelection, marker_];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'local_album_entity';
|
||||||
|
@override
|
||||||
|
i0.VerificationContext validateIntegrity(
|
||||||
|
i0.Insertable<i1.LocalAlbumEntityData> instance,
|
||||||
|
{bool isInserting = false}) {
|
||||||
|
final context = i0.VerificationContext();
|
||||||
|
final data = instance.toColumns(true);
|
||||||
|
if (data.containsKey('id')) {
|
||||||
|
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_idMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('name')) {
|
||||||
|
context.handle(
|
||||||
|
_nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta));
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_nameMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('updated_at')) {
|
||||||
|
context.handle(_updatedAtMeta,
|
||||||
|
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta));
|
||||||
|
}
|
||||||
|
if (data.containsKey('marker')) {
|
||||||
|
context.handle(_marker_Meta,
|
||||||
|
marker_.isAcceptableOrUnknown(data['marker']!, _marker_Meta));
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<i0.GeneratedColumn> get $primaryKey => {id};
|
||||||
|
@override
|
||||||
|
i1.LocalAlbumEntityData map(Map<String, dynamic> data,
|
||||||
|
{String? tablePrefix}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return i1.LocalAlbumEntityData(
|
||||||
|
id: attachedDatabase.typeMapping
|
||||||
|
.read(i0.DriftSqlType.string, data['${effectivePrefix}id'])!,
|
||||||
|
name: attachedDatabase.typeMapping
|
||||||
|
.read(i0.DriftSqlType.string, data['${effectivePrefix}name'])!,
|
||||||
|
updatedAt: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!,
|
||||||
|
backupSelection: i1.$LocalAlbumEntityTable.$converterbackupSelection
|
||||||
|
.fromSql(attachedDatabase.typeMapping.read(i0.DriftSqlType.int,
|
||||||
|
data['${effectivePrefix}backup_selection'])!),
|
||||||
|
marker_: attachedDatabase.typeMapping
|
||||||
|
.read(i0.DriftSqlType.bool, data['${effectivePrefix}marker']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
$LocalAlbumEntityTable createAlias(String alias) {
|
||||||
|
return $LocalAlbumEntityTable(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
static i0.JsonTypeConverter2<i2.BackupSelection, int, int>
|
||||||
|
$converterbackupSelection =
|
||||||
|
const i0.EnumIndexConverter<i2.BackupSelection>(
|
||||||
|
i2.BackupSelection.values);
|
||||||
|
@override
|
||||||
|
bool get withoutRowId => true;
|
||||||
|
@override
|
||||||
|
bool get isStrict => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalAlbumEntityData extends i0.DataClass
|
||||||
|
implements i0.Insertable<i1.LocalAlbumEntityData> {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final DateTime updatedAt;
|
||||||
|
final i2.BackupSelection backupSelection;
|
||||||
|
final bool? marker_;
|
||||||
|
const LocalAlbumEntityData(
|
||||||
|
{required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.backupSelection,
|
||||||
|
this.marker_});
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
map['id'] = i0.Variable<String>(id);
|
||||||
|
map['name'] = i0.Variable<String>(name);
|
||||||
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
||||||
|
{
|
||||||
|
map['backup_selection'] = i0.Variable<int>(i1
|
||||||
|
.$LocalAlbumEntityTable.$converterbackupSelection
|
||||||
|
.toSql(backupSelection));
|
||||||
|
}
|
||||||
|
if (!nullToAbsent || marker_ != null) {
|
||||||
|
map['marker'] = i0.Variable<bool>(marker_);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory LocalAlbumEntityData.fromJson(Map<String, dynamic> json,
|
||||||
|
{i0.ValueSerializer? serializer}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return LocalAlbumEntityData(
|
||||||
|
id: serializer.fromJson<String>(json['id']),
|
||||||
|
name: serializer.fromJson<String>(json['name']),
|
||||||
|
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
||||||
|
backupSelection: i1.$LocalAlbumEntityTable.$converterbackupSelection
|
||||||
|
.fromJson(serializer.fromJson<int>(json['backupSelection'])),
|
||||||
|
marker_: serializer.fromJson<bool?>(json['marker_']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'id': serializer.toJson<String>(id),
|
||||||
|
'name': serializer.toJson<String>(name),
|
||||||
|
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
||||||
|
'backupSelection': serializer.toJson<int>(i1
|
||||||
|
.$LocalAlbumEntityTable.$converterbackupSelection
|
||||||
|
.toJson(backupSelection)),
|
||||||
|
'marker_': serializer.toJson<bool?>(marker_),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.LocalAlbumEntityData copyWith(
|
||||||
|
{String? id,
|
||||||
|
String? name,
|
||||||
|
DateTime? updatedAt,
|
||||||
|
i2.BackupSelection? backupSelection,
|
||||||
|
i0.Value<bool?> marker_ = const i0.Value.absent()}) =>
|
||||||
|
i1.LocalAlbumEntityData(
|
||||||
|
id: id ?? this.id,
|
||||||
|
name: name ?? this.name,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
backupSelection: backupSelection ?? this.backupSelection,
|
||||||
|
marker_: marker_.present ? marker_.value : this.marker_,
|
||||||
|
);
|
||||||
|
LocalAlbumEntityData copyWithCompanion(i1.LocalAlbumEntityCompanion data) {
|
||||||
|
return LocalAlbumEntityData(
|
||||||
|
id: data.id.present ? data.id.value : this.id,
|
||||||
|
name: data.name.present ? data.name.value : this.name,
|
||||||
|
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
||||||
|
backupSelection: data.backupSelection.present
|
||||||
|
? data.backupSelection.value
|
||||||
|
: this.backupSelection,
|
||||||
|
marker_: data.marker_.present ? data.marker_.value : this.marker_,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('LocalAlbumEntityData(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('name: $name, ')
|
||||||
|
..write('updatedAt: $updatedAt, ')
|
||||||
|
..write('backupSelection: $backupSelection, ')
|
||||||
|
..write('marker_: $marker_')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
Object.hash(id, name, updatedAt, backupSelection, marker_);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is i1.LocalAlbumEntityData &&
|
||||||
|
other.id == this.id &&
|
||||||
|
other.name == this.name &&
|
||||||
|
other.updatedAt == this.updatedAt &&
|
||||||
|
other.backupSelection == this.backupSelection &&
|
||||||
|
other.marker_ == this.marker_);
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalAlbumEntityCompanion
|
||||||
|
extends i0.UpdateCompanion<i1.LocalAlbumEntityData> {
|
||||||
|
final i0.Value<String> id;
|
||||||
|
final i0.Value<String> name;
|
||||||
|
final i0.Value<DateTime> updatedAt;
|
||||||
|
final i0.Value<i2.BackupSelection> backupSelection;
|
||||||
|
final i0.Value<bool?> marker_;
|
||||||
|
const LocalAlbumEntityCompanion({
|
||||||
|
this.id = const i0.Value.absent(),
|
||||||
|
this.name = const i0.Value.absent(),
|
||||||
|
this.updatedAt = const i0.Value.absent(),
|
||||||
|
this.backupSelection = const i0.Value.absent(),
|
||||||
|
this.marker_ = const i0.Value.absent(),
|
||||||
|
});
|
||||||
|
LocalAlbumEntityCompanion.insert({
|
||||||
|
required String id,
|
||||||
|
required String name,
|
||||||
|
this.updatedAt = const i0.Value.absent(),
|
||||||
|
required i2.BackupSelection backupSelection,
|
||||||
|
this.marker_ = const i0.Value.absent(),
|
||||||
|
}) : id = i0.Value(id),
|
||||||
|
name = i0.Value(name),
|
||||||
|
backupSelection = i0.Value(backupSelection);
|
||||||
|
static i0.Insertable<i1.LocalAlbumEntityData> custom({
|
||||||
|
i0.Expression<String>? id,
|
||||||
|
i0.Expression<String>? name,
|
||||||
|
i0.Expression<DateTime>? updatedAt,
|
||||||
|
i0.Expression<int>? backupSelection,
|
||||||
|
i0.Expression<bool>? marker_,
|
||||||
|
}) {
|
||||||
|
return i0.RawValuesInsertable({
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (name != null) 'name': name,
|
||||||
|
if (updatedAt != null) 'updated_at': updatedAt,
|
||||||
|
if (backupSelection != null) 'backup_selection': backupSelection,
|
||||||
|
if (marker_ != null) 'marker': marker_,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.LocalAlbumEntityCompanion copyWith(
|
||||||
|
{i0.Value<String>? id,
|
||||||
|
i0.Value<String>? name,
|
||||||
|
i0.Value<DateTime>? updatedAt,
|
||||||
|
i0.Value<i2.BackupSelection>? backupSelection,
|
||||||
|
i0.Value<bool?>? marker_}) {
|
||||||
|
return i1.LocalAlbumEntityCompanion(
|
||||||
|
id: id ?? this.id,
|
||||||
|
name: name ?? this.name,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
backupSelection: backupSelection ?? this.backupSelection,
|
||||||
|
marker_: marker_ ?? this.marker_,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
if (id.present) {
|
||||||
|
map['id'] = i0.Variable<String>(id.value);
|
||||||
|
}
|
||||||
|
if (name.present) {
|
||||||
|
map['name'] = i0.Variable<String>(name.value);
|
||||||
|
}
|
||||||
|
if (updatedAt.present) {
|
||||||
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
|
||||||
|
}
|
||||||
|
if (backupSelection.present) {
|
||||||
|
map['backup_selection'] = i0.Variable<int>(i1
|
||||||
|
.$LocalAlbumEntityTable.$converterbackupSelection
|
||||||
|
.toSql(backupSelection.value));
|
||||||
|
}
|
||||||
|
if (marker_.present) {
|
||||||
|
map['marker'] = i0.Variable<bool>(marker_.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('LocalAlbumEntityCompanion(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('name: $name, ')
|
||||||
|
..write('updatedAt: $updatedAt, ')
|
||||||
|
..write('backupSelection: $backupSelection, ')
|
||||||
|
..write('marker_: $marker_')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
|
|
||||||
|
class LocalAlbumAssetEntity extends Table with DriftDefaultsMixin {
|
||||||
|
const LocalAlbumAssetEntity();
|
||||||
|
|
||||||
|
TextColumn get assetId =>
|
||||||
|
text().references(LocalAssetEntity, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
|
TextColumn get albumId =>
|
||||||
|
text().references(LocalAlbumEntity, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {assetId, albumId};
|
||||||
|
}
|
565
mobile/lib/infrastructure/entities/local_album_asset.entity.drift.dart
generated
Normal file
565
mobile/lib/infrastructure/entities/local_album_asset.entity.drift.dart
generated
Normal file
@ -0,0 +1,565 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
import 'package:drift/drift.dart' as i0;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'
|
||||||
|
as i1;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart'
|
||||||
|
as i2;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'
|
||||||
|
as i3;
|
||||||
|
import 'package:drift/internal/modular.dart' as i4;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'
|
||||||
|
as i5;
|
||||||
|
|
||||||
|
typedef $$LocalAlbumAssetEntityTableCreateCompanionBuilder
|
||||||
|
= i1.LocalAlbumAssetEntityCompanion Function({
|
||||||
|
required String assetId,
|
||||||
|
required String albumId,
|
||||||
|
});
|
||||||
|
typedef $$LocalAlbumAssetEntityTableUpdateCompanionBuilder
|
||||||
|
= i1.LocalAlbumAssetEntityCompanion Function({
|
||||||
|
i0.Value<String> assetId,
|
||||||
|
i0.Value<String> albumId,
|
||||||
|
});
|
||||||
|
|
||||||
|
final class $$LocalAlbumAssetEntityTableReferences extends i0.BaseReferences<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$LocalAlbumAssetEntityTable,
|
||||||
|
i1.LocalAlbumAssetEntityData> {
|
||||||
|
$$LocalAlbumAssetEntityTableReferences(
|
||||||
|
super.$_db, super.$_table, super.$_typedResult);
|
||||||
|
|
||||||
|
static i3.$LocalAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) =>
|
||||||
|
i4.ReadDatabaseContainer(db)
|
||||||
|
.resultSet<i3.$LocalAssetEntityTable>('local_asset_entity')
|
||||||
|
.createAlias(i0.$_aliasNameGenerator(
|
||||||
|
i4.ReadDatabaseContainer(db)
|
||||||
|
.resultSet<i1.$LocalAlbumAssetEntityTable>(
|
||||||
|
'local_album_asset_entity')
|
||||||
|
.assetId,
|
||||||
|
i4.ReadDatabaseContainer(db)
|
||||||
|
.resultSet<i3.$LocalAssetEntityTable>('local_asset_entity')
|
||||||
|
.id));
|
||||||
|
|
||||||
|
i3.$$LocalAssetEntityTableProcessedTableManager get assetId {
|
||||||
|
final $_column = $_itemColumn<String>('asset_id')!;
|
||||||
|
|
||||||
|
final manager = i3
|
||||||
|
.$$LocalAssetEntityTableTableManager(
|
||||||
|
$_db,
|
||||||
|
i4.ReadDatabaseContainer($_db)
|
||||||
|
.resultSet<i3.$LocalAssetEntityTable>('local_asset_entity'))
|
||||||
|
.filter((f) => f.id.sqlEquals($_column));
|
||||||
|
final item = $_typedResult.readTableOrNull(_assetIdTable($_db));
|
||||||
|
if (item == null) return manager;
|
||||||
|
return i0.ProcessedTableManager(
|
||||||
|
manager.$state.copyWith(prefetchedData: [item]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static i5.$LocalAlbumEntityTable _albumIdTable(i0.GeneratedDatabase db) =>
|
||||||
|
i4.ReadDatabaseContainer(db)
|
||||||
|
.resultSet<i5.$LocalAlbumEntityTable>('local_album_entity')
|
||||||
|
.createAlias(i0.$_aliasNameGenerator(
|
||||||
|
i4.ReadDatabaseContainer(db)
|
||||||
|
.resultSet<i1.$LocalAlbumAssetEntityTable>(
|
||||||
|
'local_album_asset_entity')
|
||||||
|
.albumId,
|
||||||
|
i4.ReadDatabaseContainer(db)
|
||||||
|
.resultSet<i5.$LocalAlbumEntityTable>('local_album_entity')
|
||||||
|
.id));
|
||||||
|
|
||||||
|
i5.$$LocalAlbumEntityTableProcessedTableManager get albumId {
|
||||||
|
final $_column = $_itemColumn<String>('album_id')!;
|
||||||
|
|
||||||
|
final manager = i5
|
||||||
|
.$$LocalAlbumEntityTableTableManager(
|
||||||
|
$_db,
|
||||||
|
i4.ReadDatabaseContainer($_db)
|
||||||
|
.resultSet<i5.$LocalAlbumEntityTable>('local_album_entity'))
|
||||||
|
.filter((f) => f.id.sqlEquals($_column));
|
||||||
|
final item = $_typedResult.readTableOrNull(_albumIdTable($_db));
|
||||||
|
if (item == null) return manager;
|
||||||
|
return i0.ProcessedTableManager(
|
||||||
|
manager.$state.copyWith(prefetchedData: [item]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$LocalAlbumAssetEntityTableFilterComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$LocalAlbumAssetEntityTable> {
|
||||||
|
$$LocalAlbumAssetEntityTableFilterComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i3.$$LocalAssetEntityTableFilterComposer get assetId {
|
||||||
|
final i3.$$LocalAssetEntityTableFilterComposer composer = $composerBuilder(
|
||||||
|
composer: this,
|
||||||
|
getCurrentColumn: (t) => t.assetId,
|
||||||
|
referencedTable: i4.ReadDatabaseContainer($db)
|
||||||
|
.resultSet<i3.$LocalAssetEntityTable>('local_asset_entity'),
|
||||||
|
getReferencedColumn: (t) => t.id,
|
||||||
|
builder: (joinBuilder,
|
||||||
|
{$addJoinBuilderToRootComposer,
|
||||||
|
$removeJoinBuilderFromRootComposer}) =>
|
||||||
|
i3.$$LocalAssetEntityTableFilterComposer(
|
||||||
|
$db: $db,
|
||||||
|
$table: i4.ReadDatabaseContainer($db)
|
||||||
|
.resultSet<i3.$LocalAssetEntityTable>('local_asset_entity'),
|
||||||
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
|
joinBuilder: joinBuilder,
|
||||||
|
$removeJoinBuilderFromRootComposer:
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
));
|
||||||
|
return composer;
|
||||||
|
}
|
||||||
|
|
||||||
|
i5.$$LocalAlbumEntityTableFilterComposer get albumId {
|
||||||
|
final i5.$$LocalAlbumEntityTableFilterComposer composer = $composerBuilder(
|
||||||
|
composer: this,
|
||||||
|
getCurrentColumn: (t) => t.albumId,
|
||||||
|
referencedTable: i4.ReadDatabaseContainer($db)
|
||||||
|
.resultSet<i5.$LocalAlbumEntityTable>('local_album_entity'),
|
||||||
|
getReferencedColumn: (t) => t.id,
|
||||||
|
builder: (joinBuilder,
|
||||||
|
{$addJoinBuilderToRootComposer,
|
||||||
|
$removeJoinBuilderFromRootComposer}) =>
|
||||||
|
i5.$$LocalAlbumEntityTableFilterComposer(
|
||||||
|
$db: $db,
|
||||||
|
$table: i4.ReadDatabaseContainer($db)
|
||||||
|
.resultSet<i5.$LocalAlbumEntityTable>('local_album_entity'),
|
||||||
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
|
joinBuilder: joinBuilder,
|
||||||
|
$removeJoinBuilderFromRootComposer:
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
));
|
||||||
|
return composer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$LocalAlbumAssetEntityTableOrderingComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$LocalAlbumAssetEntityTable> {
|
||||||
|
$$LocalAlbumAssetEntityTableOrderingComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i3.$$LocalAssetEntityTableOrderingComposer get assetId {
|
||||||
|
final i3.$$LocalAssetEntityTableOrderingComposer composer =
|
||||||
|
$composerBuilder(
|
||||||
|
composer: this,
|
||||||
|
getCurrentColumn: (t) => t.assetId,
|
||||||
|
referencedTable: i4.ReadDatabaseContainer($db)
|
||||||
|
.resultSet<i3.$LocalAssetEntityTable>('local_asset_entity'),
|
||||||
|
getReferencedColumn: (t) => t.id,
|
||||||
|
builder: (joinBuilder,
|
||||||
|
{$addJoinBuilderToRootComposer,
|
||||||
|
$removeJoinBuilderFromRootComposer}) =>
|
||||||
|
i3.$$LocalAssetEntityTableOrderingComposer(
|
||||||
|
$db: $db,
|
||||||
|
$table: i4.ReadDatabaseContainer($db)
|
||||||
|
.resultSet<i3.$LocalAssetEntityTable>(
|
||||||
|
'local_asset_entity'),
|
||||||
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
|
joinBuilder: joinBuilder,
|
||||||
|
$removeJoinBuilderFromRootComposer:
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
));
|
||||||
|
return composer;
|
||||||
|
}
|
||||||
|
|
||||||
|
i5.$$LocalAlbumEntityTableOrderingComposer get albumId {
|
||||||
|
final i5.$$LocalAlbumEntityTableOrderingComposer composer =
|
||||||
|
$composerBuilder(
|
||||||
|
composer: this,
|
||||||
|
getCurrentColumn: (t) => t.albumId,
|
||||||
|
referencedTable: i4.ReadDatabaseContainer($db)
|
||||||
|
.resultSet<i5.$LocalAlbumEntityTable>('local_album_entity'),
|
||||||
|
getReferencedColumn: (t) => t.id,
|
||||||
|
builder: (joinBuilder,
|
||||||
|
{$addJoinBuilderToRootComposer,
|
||||||
|
$removeJoinBuilderFromRootComposer}) =>
|
||||||
|
i5.$$LocalAlbumEntityTableOrderingComposer(
|
||||||
|
$db: $db,
|
||||||
|
$table: i4.ReadDatabaseContainer($db)
|
||||||
|
.resultSet<i5.$LocalAlbumEntityTable>(
|
||||||
|
'local_album_entity'),
|
||||||
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
|
joinBuilder: joinBuilder,
|
||||||
|
$removeJoinBuilderFromRootComposer:
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
));
|
||||||
|
return composer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$LocalAlbumAssetEntityTableAnnotationComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$LocalAlbumAssetEntityTable> {
|
||||||
|
$$LocalAlbumAssetEntityTableAnnotationComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i3.$$LocalAssetEntityTableAnnotationComposer get assetId {
|
||||||
|
final i3.$$LocalAssetEntityTableAnnotationComposer composer =
|
||||||
|
$composerBuilder(
|
||||||
|
composer: this,
|
||||||
|
getCurrentColumn: (t) => t.assetId,
|
||||||
|
referencedTable: i4.ReadDatabaseContainer($db)
|
||||||
|
.resultSet<i3.$LocalAssetEntityTable>('local_asset_entity'),
|
||||||
|
getReferencedColumn: (t) => t.id,
|
||||||
|
builder: (joinBuilder,
|
||||||
|
{$addJoinBuilderToRootComposer,
|
||||||
|
$removeJoinBuilderFromRootComposer}) =>
|
||||||
|
i3.$$LocalAssetEntityTableAnnotationComposer(
|
||||||
|
$db: $db,
|
||||||
|
$table: i4.ReadDatabaseContainer($db)
|
||||||
|
.resultSet<i3.$LocalAssetEntityTable>(
|
||||||
|
'local_asset_entity'),
|
||||||
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
|
joinBuilder: joinBuilder,
|
||||||
|
$removeJoinBuilderFromRootComposer:
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
));
|
||||||
|
return composer;
|
||||||
|
}
|
||||||
|
|
||||||
|
i5.$$LocalAlbumEntityTableAnnotationComposer get albumId {
|
||||||
|
final i5.$$LocalAlbumEntityTableAnnotationComposer composer =
|
||||||
|
$composerBuilder(
|
||||||
|
composer: this,
|
||||||
|
getCurrentColumn: (t) => t.albumId,
|
||||||
|
referencedTable: i4.ReadDatabaseContainer($db)
|
||||||
|
.resultSet<i5.$LocalAlbumEntityTable>('local_album_entity'),
|
||||||
|
getReferencedColumn: (t) => t.id,
|
||||||
|
builder: (joinBuilder,
|
||||||
|
{$addJoinBuilderToRootComposer,
|
||||||
|
$removeJoinBuilderFromRootComposer}) =>
|
||||||
|
i5.$$LocalAlbumEntityTableAnnotationComposer(
|
||||||
|
$db: $db,
|
||||||
|
$table: i4.ReadDatabaseContainer($db)
|
||||||
|
.resultSet<i5.$LocalAlbumEntityTable>(
|
||||||
|
'local_album_entity'),
|
||||||
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
|
joinBuilder: joinBuilder,
|
||||||
|
$removeJoinBuilderFromRootComposer:
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
));
|
||||||
|
return composer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$LocalAlbumAssetEntityTableTableManager extends i0.RootTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$LocalAlbumAssetEntityTable,
|
||||||
|
i1.LocalAlbumAssetEntityData,
|
||||||
|
i1.$$LocalAlbumAssetEntityTableFilterComposer,
|
||||||
|
i1.$$LocalAlbumAssetEntityTableOrderingComposer,
|
||||||
|
i1.$$LocalAlbumAssetEntityTableAnnotationComposer,
|
||||||
|
$$LocalAlbumAssetEntityTableCreateCompanionBuilder,
|
||||||
|
$$LocalAlbumAssetEntityTableUpdateCompanionBuilder,
|
||||||
|
(i1.LocalAlbumAssetEntityData, i1.$$LocalAlbumAssetEntityTableReferences),
|
||||||
|
i1.LocalAlbumAssetEntityData,
|
||||||
|
i0.PrefetchHooks Function({bool assetId, bool albumId})> {
|
||||||
|
$$LocalAlbumAssetEntityTableTableManager(
|
||||||
|
i0.GeneratedDatabase db, i1.$LocalAlbumAssetEntityTable table)
|
||||||
|
: super(i0.TableManagerState(
|
||||||
|
db: db,
|
||||||
|
table: table,
|
||||||
|
createFilteringComposer: () =>
|
||||||
|
i1.$$LocalAlbumAssetEntityTableFilterComposer(
|
||||||
|
$db: db, $table: table),
|
||||||
|
createOrderingComposer: () =>
|
||||||
|
i1.$$LocalAlbumAssetEntityTableOrderingComposer(
|
||||||
|
$db: db, $table: table),
|
||||||
|
createComputedFieldComposer: () =>
|
||||||
|
i1.$$LocalAlbumAssetEntityTableAnnotationComposer(
|
||||||
|
$db: db, $table: table),
|
||||||
|
updateCompanionCallback: ({
|
||||||
|
i0.Value<String> assetId = const i0.Value.absent(),
|
||||||
|
i0.Value<String> albumId = const i0.Value.absent(),
|
||||||
|
}) =>
|
||||||
|
i1.LocalAlbumAssetEntityCompanion(
|
||||||
|
assetId: assetId,
|
||||||
|
albumId: albumId,
|
||||||
|
),
|
||||||
|
createCompanionCallback: ({
|
||||||
|
required String assetId,
|
||||||
|
required String albumId,
|
||||||
|
}) =>
|
||||||
|
i1.LocalAlbumAssetEntityCompanion.insert(
|
||||||
|
assetId: assetId,
|
||||||
|
albumId: albumId,
|
||||||
|
),
|
||||||
|
withReferenceMapper: (p0) => p0
|
||||||
|
.map((e) => (
|
||||||
|
e.readTable(table),
|
||||||
|
i1.$$LocalAlbumAssetEntityTableReferences(db, table, e)
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
prefetchHooksCallback: ({assetId = false, albumId = false}) {
|
||||||
|
return i0.PrefetchHooks(
|
||||||
|
db: db,
|
||||||
|
explicitlyWatchedTables: [],
|
||||||
|
addJoins: <
|
||||||
|
T extends i0.TableManagerState<
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic>>(state) {
|
||||||
|
if (assetId) {
|
||||||
|
state = state.withJoin(
|
||||||
|
currentTable: table,
|
||||||
|
currentColumn: table.assetId,
|
||||||
|
referencedTable: i1.$$LocalAlbumAssetEntityTableReferences
|
||||||
|
._assetIdTable(db),
|
||||||
|
referencedColumn: i1.$$LocalAlbumAssetEntityTableReferences
|
||||||
|
._assetIdTable(db)
|
||||||
|
.id,
|
||||||
|
) as T;
|
||||||
|
}
|
||||||
|
if (albumId) {
|
||||||
|
state = state.withJoin(
|
||||||
|
currentTable: table,
|
||||||
|
currentColumn: table.albumId,
|
||||||
|
referencedTable: i1.$$LocalAlbumAssetEntityTableReferences
|
||||||
|
._albumIdTable(db),
|
||||||
|
referencedColumn: i1.$$LocalAlbumAssetEntityTableReferences
|
||||||
|
._albumIdTable(db)
|
||||||
|
.id,
|
||||||
|
) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
getPrefetchedDataCallback: (items) async {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef $$LocalAlbumAssetEntityTableProcessedTableManager
|
||||||
|
= i0.ProcessedTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$LocalAlbumAssetEntityTable,
|
||||||
|
i1.LocalAlbumAssetEntityData,
|
||||||
|
i1.$$LocalAlbumAssetEntityTableFilterComposer,
|
||||||
|
i1.$$LocalAlbumAssetEntityTableOrderingComposer,
|
||||||
|
i1.$$LocalAlbumAssetEntityTableAnnotationComposer,
|
||||||
|
$$LocalAlbumAssetEntityTableCreateCompanionBuilder,
|
||||||
|
$$LocalAlbumAssetEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.LocalAlbumAssetEntityData,
|
||||||
|
i1.$$LocalAlbumAssetEntityTableReferences
|
||||||
|
),
|
||||||
|
i1.LocalAlbumAssetEntityData,
|
||||||
|
i0.PrefetchHooks Function({bool assetId, bool albumId})>;
|
||||||
|
|
||||||
|
class $LocalAlbumAssetEntityTable extends i2.LocalAlbumAssetEntity
|
||||||
|
with
|
||||||
|
i0
|
||||||
|
.TableInfo<$LocalAlbumAssetEntityTable, i1.LocalAlbumAssetEntityData> {
|
||||||
|
@override
|
||||||
|
final i0.GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
$LocalAlbumAssetEntityTable(this.attachedDatabase, [this._alias]);
|
||||||
|
static const i0.VerificationMeta _assetIdMeta =
|
||||||
|
const i0.VerificationMeta('assetId');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> assetId = i0.GeneratedColumn<String>(
|
||||||
|
'asset_id', aliasedName, false,
|
||||||
|
type: i0.DriftSqlType.string,
|
||||||
|
requiredDuringInsert: true,
|
||||||
|
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||||
|
'REFERENCES local_asset_entity (id) ON DELETE CASCADE'));
|
||||||
|
static const i0.VerificationMeta _albumIdMeta =
|
||||||
|
const i0.VerificationMeta('albumId');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> albumId = i0.GeneratedColumn<String>(
|
||||||
|
'album_id', aliasedName, false,
|
||||||
|
type: i0.DriftSqlType.string,
|
||||||
|
requiredDuringInsert: true,
|
||||||
|
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||||
|
'REFERENCES local_album_entity (id) ON DELETE CASCADE'));
|
||||||
|
@override
|
||||||
|
List<i0.GeneratedColumn> get $columns => [assetId, albumId];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'local_album_asset_entity';
|
||||||
|
@override
|
||||||
|
i0.VerificationContext validateIntegrity(
|
||||||
|
i0.Insertable<i1.LocalAlbumAssetEntityData> instance,
|
||||||
|
{bool isInserting = false}) {
|
||||||
|
final context = i0.VerificationContext();
|
||||||
|
final data = instance.toColumns(true);
|
||||||
|
if (data.containsKey('asset_id')) {
|
||||||
|
context.handle(_assetIdMeta,
|
||||||
|
assetId.isAcceptableOrUnknown(data['asset_id']!, _assetIdMeta));
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_assetIdMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('album_id')) {
|
||||||
|
context.handle(_albumIdMeta,
|
||||||
|
albumId.isAcceptableOrUnknown(data['album_id']!, _albumIdMeta));
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_albumIdMeta);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<i0.GeneratedColumn> get $primaryKey => {assetId, albumId};
|
||||||
|
@override
|
||||||
|
i1.LocalAlbumAssetEntityData map(Map<String, dynamic> data,
|
||||||
|
{String? tablePrefix}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return i1.LocalAlbumAssetEntityData(
|
||||||
|
assetId: attachedDatabase.typeMapping
|
||||||
|
.read(i0.DriftSqlType.string, data['${effectivePrefix}asset_id'])!,
|
||||||
|
albumId: attachedDatabase.typeMapping
|
||||||
|
.read(i0.DriftSqlType.string, data['${effectivePrefix}album_id'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
$LocalAlbumAssetEntityTable createAlias(String alias) {
|
||||||
|
return $LocalAlbumAssetEntityTable(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get withoutRowId => true;
|
||||||
|
@override
|
||||||
|
bool get isStrict => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalAlbumAssetEntityData extends i0.DataClass
|
||||||
|
implements i0.Insertable<i1.LocalAlbumAssetEntityData> {
|
||||||
|
final String assetId;
|
||||||
|
final String albumId;
|
||||||
|
const LocalAlbumAssetEntityData(
|
||||||
|
{required this.assetId, required this.albumId});
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
map['asset_id'] = i0.Variable<String>(assetId);
|
||||||
|
map['album_id'] = i0.Variable<String>(albumId);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory LocalAlbumAssetEntityData.fromJson(Map<String, dynamic> json,
|
||||||
|
{i0.ValueSerializer? serializer}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return LocalAlbumAssetEntityData(
|
||||||
|
assetId: serializer.fromJson<String>(json['assetId']),
|
||||||
|
albumId: serializer.fromJson<String>(json['albumId']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'assetId': serializer.toJson<String>(assetId),
|
||||||
|
'albumId': serializer.toJson<String>(albumId),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.LocalAlbumAssetEntityData copyWith({String? assetId, String? albumId}) =>
|
||||||
|
i1.LocalAlbumAssetEntityData(
|
||||||
|
assetId: assetId ?? this.assetId,
|
||||||
|
albumId: albumId ?? this.albumId,
|
||||||
|
);
|
||||||
|
LocalAlbumAssetEntityData copyWithCompanion(
|
||||||
|
i1.LocalAlbumAssetEntityCompanion data) {
|
||||||
|
return LocalAlbumAssetEntityData(
|
||||||
|
assetId: data.assetId.present ? data.assetId.value : this.assetId,
|
||||||
|
albumId: data.albumId.present ? data.albumId.value : this.albumId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('LocalAlbumAssetEntityData(')
|
||||||
|
..write('assetId: $assetId, ')
|
||||||
|
..write('albumId: $albumId')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(assetId, albumId);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is i1.LocalAlbumAssetEntityData &&
|
||||||
|
other.assetId == this.assetId &&
|
||||||
|
other.albumId == this.albumId);
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalAlbumAssetEntityCompanion
|
||||||
|
extends i0.UpdateCompanion<i1.LocalAlbumAssetEntityData> {
|
||||||
|
final i0.Value<String> assetId;
|
||||||
|
final i0.Value<String> albumId;
|
||||||
|
const LocalAlbumAssetEntityCompanion({
|
||||||
|
this.assetId = const i0.Value.absent(),
|
||||||
|
this.albumId = const i0.Value.absent(),
|
||||||
|
});
|
||||||
|
LocalAlbumAssetEntityCompanion.insert({
|
||||||
|
required String assetId,
|
||||||
|
required String albumId,
|
||||||
|
}) : assetId = i0.Value(assetId),
|
||||||
|
albumId = i0.Value(albumId);
|
||||||
|
static i0.Insertable<i1.LocalAlbumAssetEntityData> custom({
|
||||||
|
i0.Expression<String>? assetId,
|
||||||
|
i0.Expression<String>? albumId,
|
||||||
|
}) {
|
||||||
|
return i0.RawValuesInsertable({
|
||||||
|
if (assetId != null) 'asset_id': assetId,
|
||||||
|
if (albumId != null) 'album_id': albumId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.LocalAlbumAssetEntityCompanion copyWith(
|
||||||
|
{i0.Value<String>? assetId, i0.Value<String>? albumId}) {
|
||||||
|
return i1.LocalAlbumAssetEntityCompanion(
|
||||||
|
assetId: assetId ?? this.assetId,
|
||||||
|
albumId: albumId ?? this.albumId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
if (assetId.present) {
|
||||||
|
map['asset_id'] = i0.Variable<String>(assetId.value);
|
||||||
|
}
|
||||||
|
if (albumId.present) {
|
||||||
|
map['album_id'] = i0.Variable<String>(albumId.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('LocalAlbumAssetEntityCompanion(')
|
||||||
|
..write('assetId: $assetId, ')
|
||||||
|
..write('albumId: $albumId')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
17
mobile/lib/infrastructure/entities/local_asset.entity.dart
Normal file
17
mobile/lib/infrastructure/entities/local_asset.entity.dart
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/utils/asset.mixin.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
|
|
||||||
|
@TableIndex(name: 'local_asset_checksum', columns: {#checksum})
|
||||||
|
class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
|
||||||
|
const LocalAssetEntity();
|
||||||
|
|
||||||
|
TextColumn get id => text()();
|
||||||
|
TextColumn get checksum => text().nullable()();
|
||||||
|
|
||||||
|
// Only used during backup to mirror the favorite status of the asset in the server
|
||||||
|
BoolColumn get isFavorite => boolean().withDefault(const Constant(false))();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {id};
|
||||||
|
}
|
658
mobile/lib/infrastructure/entities/local_asset.entity.drift.dart
generated
Normal file
658
mobile/lib/infrastructure/entities/local_asset.entity.drift.dart
generated
Normal file
@ -0,0 +1,658 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
import 'package:drift/drift.dart' as i0;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'
|
||||||
|
as i1;
|
||||||
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as i2;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'
|
||||||
|
as i3;
|
||||||
|
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
|
||||||
|
|
||||||
|
typedef $$LocalAssetEntityTableCreateCompanionBuilder
|
||||||
|
= i1.LocalAssetEntityCompanion Function({
|
||||||
|
required String name,
|
||||||
|
required i2.AssetType type,
|
||||||
|
i0.Value<DateTime> createdAt,
|
||||||
|
i0.Value<DateTime> updatedAt,
|
||||||
|
i0.Value<int?> durationInSeconds,
|
||||||
|
required String id,
|
||||||
|
i0.Value<String?> checksum,
|
||||||
|
i0.Value<bool> isFavorite,
|
||||||
|
});
|
||||||
|
typedef $$LocalAssetEntityTableUpdateCompanionBuilder
|
||||||
|
= i1.LocalAssetEntityCompanion Function({
|
||||||
|
i0.Value<String> name,
|
||||||
|
i0.Value<i2.AssetType> type,
|
||||||
|
i0.Value<DateTime> createdAt,
|
||||||
|
i0.Value<DateTime> updatedAt,
|
||||||
|
i0.Value<int?> durationInSeconds,
|
||||||
|
i0.Value<String> id,
|
||||||
|
i0.Value<String?> checksum,
|
||||||
|
i0.Value<bool> isFavorite,
|
||||||
|
});
|
||||||
|
|
||||||
|
class $$LocalAssetEntityTableFilterComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$LocalAssetEntityTable> {
|
||||||
|
$$LocalAssetEntityTableFilterComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.ColumnFilters<String> get name => $composableBuilder(
|
||||||
|
column: $table.name, builder: (column) => i0.ColumnFilters(column));
|
||||||
|
|
||||||
|
i0.ColumnWithTypeConverterFilters<i2.AssetType, i2.AssetType, int> get type =>
|
||||||
|
$composableBuilder(
|
||||||
|
column: $table.type,
|
||||||
|
builder: (column) => i0.ColumnWithTypeConverterFilters(column));
|
||||||
|
|
||||||
|
i0.ColumnFilters<DateTime> get createdAt => $composableBuilder(
|
||||||
|
column: $table.createdAt, builder: (column) => i0.ColumnFilters(column));
|
||||||
|
|
||||||
|
i0.ColumnFilters<DateTime> get updatedAt => $composableBuilder(
|
||||||
|
column: $table.updatedAt, builder: (column) => i0.ColumnFilters(column));
|
||||||
|
|
||||||
|
i0.ColumnFilters<int> get durationInSeconds => $composableBuilder(
|
||||||
|
column: $table.durationInSeconds,
|
||||||
|
builder: (column) => i0.ColumnFilters(column));
|
||||||
|
|
||||||
|
i0.ColumnFilters<String> get id => $composableBuilder(
|
||||||
|
column: $table.id, builder: (column) => i0.ColumnFilters(column));
|
||||||
|
|
||||||
|
i0.ColumnFilters<String> get checksum => $composableBuilder(
|
||||||
|
column: $table.checksum, builder: (column) => i0.ColumnFilters(column));
|
||||||
|
|
||||||
|
i0.ColumnFilters<bool> get isFavorite => $composableBuilder(
|
||||||
|
column: $table.isFavorite, builder: (column) => i0.ColumnFilters(column));
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$LocalAssetEntityTableOrderingComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$LocalAssetEntityTable> {
|
||||||
|
$$LocalAssetEntityTableOrderingComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.ColumnOrderings<String> get name => $composableBuilder(
|
||||||
|
column: $table.name, builder: (column) => i0.ColumnOrderings(column));
|
||||||
|
|
||||||
|
i0.ColumnOrderings<int> get type => $composableBuilder(
|
||||||
|
column: $table.type, builder: (column) => i0.ColumnOrderings(column));
|
||||||
|
|
||||||
|
i0.ColumnOrderings<DateTime> get createdAt => $composableBuilder(
|
||||||
|
column: $table.createdAt,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column));
|
||||||
|
|
||||||
|
i0.ColumnOrderings<DateTime> get updatedAt => $composableBuilder(
|
||||||
|
column: $table.updatedAt,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column));
|
||||||
|
|
||||||
|
i0.ColumnOrderings<int> get durationInSeconds => $composableBuilder(
|
||||||
|
column: $table.durationInSeconds,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column));
|
||||||
|
|
||||||
|
i0.ColumnOrderings<String> get id => $composableBuilder(
|
||||||
|
column: $table.id, builder: (column) => i0.ColumnOrderings(column));
|
||||||
|
|
||||||
|
i0.ColumnOrderings<String> get checksum => $composableBuilder(
|
||||||
|
column: $table.checksum, builder: (column) => i0.ColumnOrderings(column));
|
||||||
|
|
||||||
|
i0.ColumnOrderings<bool> get isFavorite => $composableBuilder(
|
||||||
|
column: $table.isFavorite,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column));
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$LocalAssetEntityTableAnnotationComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$LocalAssetEntityTable> {
|
||||||
|
$$LocalAssetEntityTableAnnotationComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.GeneratedColumn<String> get name =>
|
||||||
|
$composableBuilder(column: $table.name, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumnWithTypeConverter<i2.AssetType, int> get type =>
|
||||||
|
$composableBuilder(column: $table.type, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<DateTime> get createdAt =>
|
||||||
|
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<DateTime> get updatedAt =>
|
||||||
|
$composableBuilder(column: $table.updatedAt, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<int> get durationInSeconds => $composableBuilder(
|
||||||
|
column: $table.durationInSeconds, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<String> get id =>
|
||||||
|
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<String> get checksum =>
|
||||||
|
$composableBuilder(column: $table.checksum, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<bool> get isFavorite => $composableBuilder(
|
||||||
|
column: $table.isFavorite, builder: (column) => column);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$LocalAssetEntityTableTableManager extends i0.RootTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$LocalAssetEntityTable,
|
||||||
|
i1.LocalAssetEntityData,
|
||||||
|
i1.$$LocalAssetEntityTableFilterComposer,
|
||||||
|
i1.$$LocalAssetEntityTableOrderingComposer,
|
||||||
|
i1.$$LocalAssetEntityTableAnnotationComposer,
|
||||||
|
$$LocalAssetEntityTableCreateCompanionBuilder,
|
||||||
|
$$LocalAssetEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.LocalAssetEntityData,
|
||||||
|
i0.BaseReferences<i0.GeneratedDatabase, i1.$LocalAssetEntityTable,
|
||||||
|
i1.LocalAssetEntityData>
|
||||||
|
),
|
||||||
|
i1.LocalAssetEntityData,
|
||||||
|
i0.PrefetchHooks Function()> {
|
||||||
|
$$LocalAssetEntityTableTableManager(
|
||||||
|
i0.GeneratedDatabase db, i1.$LocalAssetEntityTable table)
|
||||||
|
: super(i0.TableManagerState(
|
||||||
|
db: db,
|
||||||
|
table: table,
|
||||||
|
createFilteringComposer: () =>
|
||||||
|
i1.$$LocalAssetEntityTableFilterComposer($db: db, $table: table),
|
||||||
|
createOrderingComposer: () => i1
|
||||||
|
.$$LocalAssetEntityTableOrderingComposer($db: db, $table: table),
|
||||||
|
createComputedFieldComposer: () =>
|
||||||
|
i1.$$LocalAssetEntityTableAnnotationComposer(
|
||||||
|
$db: db, $table: table),
|
||||||
|
updateCompanionCallback: ({
|
||||||
|
i0.Value<String> name = const i0.Value.absent(),
|
||||||
|
i0.Value<i2.AssetType> type = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime> createdAt = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
|
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
|
||||||
|
i0.Value<String> id = const i0.Value.absent(),
|
||||||
|
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||||
|
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||||
|
}) =>
|
||||||
|
i1.LocalAssetEntityCompanion(
|
||||||
|
name: name,
|
||||||
|
type: type,
|
||||||
|
createdAt: createdAt,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
durationInSeconds: durationInSeconds,
|
||||||
|
id: id,
|
||||||
|
checksum: checksum,
|
||||||
|
isFavorite: isFavorite,
|
||||||
|
),
|
||||||
|
createCompanionCallback: ({
|
||||||
|
required String name,
|
||||||
|
required i2.AssetType type,
|
||||||
|
i0.Value<DateTime> createdAt = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
|
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
|
||||||
|
required String id,
|
||||||
|
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||||
|
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||||
|
}) =>
|
||||||
|
i1.LocalAssetEntityCompanion.insert(
|
||||||
|
name: name,
|
||||||
|
type: type,
|
||||||
|
createdAt: createdAt,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
durationInSeconds: durationInSeconds,
|
||||||
|
id: id,
|
||||||
|
checksum: checksum,
|
||||||
|
isFavorite: isFavorite,
|
||||||
|
),
|
||||||
|
withReferenceMapper: (p0) => p0
|
||||||
|
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||||
|
.toList(),
|
||||||
|
prefetchHooksCallback: null,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef $$LocalAssetEntityTableProcessedTableManager = i0.ProcessedTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$LocalAssetEntityTable,
|
||||||
|
i1.LocalAssetEntityData,
|
||||||
|
i1.$$LocalAssetEntityTableFilterComposer,
|
||||||
|
i1.$$LocalAssetEntityTableOrderingComposer,
|
||||||
|
i1.$$LocalAssetEntityTableAnnotationComposer,
|
||||||
|
$$LocalAssetEntityTableCreateCompanionBuilder,
|
||||||
|
$$LocalAssetEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.LocalAssetEntityData,
|
||||||
|
i0.BaseReferences<i0.GeneratedDatabase, i1.$LocalAssetEntityTable,
|
||||||
|
i1.LocalAssetEntityData>
|
||||||
|
),
|
||||||
|
i1.LocalAssetEntityData,
|
||||||
|
i0.PrefetchHooks Function()>;
|
||||||
|
i0.Index get localAssetChecksum => i0.Index('local_asset_checksum',
|
||||||
|
'CREATE INDEX local_asset_checksum ON local_asset_entity (checksum)');
|
||||||
|
|
||||||
|
class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
||||||
|
with i0.TableInfo<$LocalAssetEntityTable, i1.LocalAssetEntityData> {
|
||||||
|
@override
|
||||||
|
final i0.GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
$LocalAssetEntityTable(this.attachedDatabase, [this._alias]);
|
||||||
|
static const i0.VerificationMeta _nameMeta =
|
||||||
|
const i0.VerificationMeta('name');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> name = i0.GeneratedColumn<String>(
|
||||||
|
'name', aliasedName, false,
|
||||||
|
type: i0.DriftSqlType.string, requiredDuringInsert: true);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumnWithTypeConverter<i2.AssetType, int> type =
|
||||||
|
i0.GeneratedColumn<int>('type', aliasedName, false,
|
||||||
|
type: i0.DriftSqlType.int, requiredDuringInsert: true)
|
||||||
|
.withConverter<i2.AssetType>(
|
||||||
|
i1.$LocalAssetEntityTable.$convertertype);
|
||||||
|
static const i0.VerificationMeta _createdAtMeta =
|
||||||
|
const i0.VerificationMeta('createdAt');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<DateTime> createdAt =
|
||||||
|
i0.GeneratedColumn<DateTime>('created_at', aliasedName, false,
|
||||||
|
type: i0.DriftSqlType.dateTime,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: i4.currentDateAndTime);
|
||||||
|
static const i0.VerificationMeta _updatedAtMeta =
|
||||||
|
const i0.VerificationMeta('updatedAt');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<DateTime> updatedAt =
|
||||||
|
i0.GeneratedColumn<DateTime>('updated_at', aliasedName, false,
|
||||||
|
type: i0.DriftSqlType.dateTime,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: i4.currentDateAndTime);
|
||||||
|
static const i0.VerificationMeta _durationInSecondsMeta =
|
||||||
|
const i0.VerificationMeta('durationInSeconds');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<int> durationInSeconds =
|
||||||
|
i0.GeneratedColumn<int>('duration_in_seconds', aliasedName, true,
|
||||||
|
type: i0.DriftSqlType.int, requiredDuringInsert: false);
|
||||||
|
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> id = i0.GeneratedColumn<String>(
|
||||||
|
'id', aliasedName, false,
|
||||||
|
type: i0.DriftSqlType.string, requiredDuringInsert: true);
|
||||||
|
static const i0.VerificationMeta _checksumMeta =
|
||||||
|
const i0.VerificationMeta('checksum');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> checksum = i0.GeneratedColumn<String>(
|
||||||
|
'checksum', aliasedName, true,
|
||||||
|
type: i0.DriftSqlType.string, requiredDuringInsert: false);
|
||||||
|
static const i0.VerificationMeta _isFavoriteMeta =
|
||||||
|
const i0.VerificationMeta('isFavorite');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<bool> isFavorite = i0.GeneratedColumn<bool>(
|
||||||
|
'is_favorite', aliasedName, false,
|
||||||
|
type: i0.DriftSqlType.bool,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||||
|
'CHECK ("is_favorite" IN (0, 1))'),
|
||||||
|
defaultValue: const i4.Constant(false));
|
||||||
|
@override
|
||||||
|
List<i0.GeneratedColumn> get $columns => [
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
durationInSeconds,
|
||||||
|
id,
|
||||||
|
checksum,
|
||||||
|
isFavorite
|
||||||
|
];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'local_asset_entity';
|
||||||
|
@override
|
||||||
|
i0.VerificationContext validateIntegrity(
|
||||||
|
i0.Insertable<i1.LocalAssetEntityData> instance,
|
||||||
|
{bool isInserting = false}) {
|
||||||
|
final context = i0.VerificationContext();
|
||||||
|
final data = instance.toColumns(true);
|
||||||
|
if (data.containsKey('name')) {
|
||||||
|
context.handle(
|
||||||
|
_nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta));
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_nameMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('created_at')) {
|
||||||
|
context.handle(_createdAtMeta,
|
||||||
|
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
|
||||||
|
}
|
||||||
|
if (data.containsKey('updated_at')) {
|
||||||
|
context.handle(_updatedAtMeta,
|
||||||
|
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta));
|
||||||
|
}
|
||||||
|
if (data.containsKey('duration_in_seconds')) {
|
||||||
|
context.handle(
|
||||||
|
_durationInSecondsMeta,
|
||||||
|
durationInSeconds.isAcceptableOrUnknown(
|
||||||
|
data['duration_in_seconds']!, _durationInSecondsMeta));
|
||||||
|
}
|
||||||
|
if (data.containsKey('id')) {
|
||||||
|
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_idMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('checksum')) {
|
||||||
|
context.handle(_checksumMeta,
|
||||||
|
checksum.isAcceptableOrUnknown(data['checksum']!, _checksumMeta));
|
||||||
|
}
|
||||||
|
if (data.containsKey('is_favorite')) {
|
||||||
|
context.handle(
|
||||||
|
_isFavoriteMeta,
|
||||||
|
isFavorite.isAcceptableOrUnknown(
|
||||||
|
data['is_favorite']!, _isFavoriteMeta));
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<i0.GeneratedColumn> get $primaryKey => {id};
|
||||||
|
@override
|
||||||
|
i1.LocalAssetEntityData map(Map<String, dynamic> data,
|
||||||
|
{String? tablePrefix}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return i1.LocalAssetEntityData(
|
||||||
|
name: attachedDatabase.typeMapping
|
||||||
|
.read(i0.DriftSqlType.string, data['${effectivePrefix}name'])!,
|
||||||
|
type: i1.$LocalAssetEntityTable.$convertertype.fromSql(attachedDatabase
|
||||||
|
.typeMapping
|
||||||
|
.read(i0.DriftSqlType.int, data['${effectivePrefix}type'])!),
|
||||||
|
createdAt: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
|
||||||
|
updatedAt: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!,
|
||||||
|
durationInSeconds: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.int, data['${effectivePrefix}duration_in_seconds']),
|
||||||
|
id: attachedDatabase.typeMapping
|
||||||
|
.read(i0.DriftSqlType.string, data['${effectivePrefix}id'])!,
|
||||||
|
checksum: attachedDatabase.typeMapping
|
||||||
|
.read(i0.DriftSqlType.string, data['${effectivePrefix}checksum']),
|
||||||
|
isFavorite: attachedDatabase.typeMapping
|
||||||
|
.read(i0.DriftSqlType.bool, data['${effectivePrefix}is_favorite'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
$LocalAssetEntityTable createAlias(String alias) {
|
||||||
|
return $LocalAssetEntityTable(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
static i0.JsonTypeConverter2<i2.AssetType, int, int> $convertertype =
|
||||||
|
const i0.EnumIndexConverter<i2.AssetType>(i2.AssetType.values);
|
||||||
|
@override
|
||||||
|
bool get withoutRowId => true;
|
||||||
|
@override
|
||||||
|
bool get isStrict => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalAssetEntityData extends i0.DataClass
|
||||||
|
implements i0.Insertable<i1.LocalAssetEntityData> {
|
||||||
|
final String name;
|
||||||
|
final i2.AssetType type;
|
||||||
|
final DateTime createdAt;
|
||||||
|
final DateTime updatedAt;
|
||||||
|
final int? durationInSeconds;
|
||||||
|
final String id;
|
||||||
|
final String? checksum;
|
||||||
|
final bool isFavorite;
|
||||||
|
const LocalAssetEntityData(
|
||||||
|
{required this.name,
|
||||||
|
required this.type,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
this.durationInSeconds,
|
||||||
|
required this.id,
|
||||||
|
this.checksum,
|
||||||
|
required this.isFavorite});
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
map['name'] = i0.Variable<String>(name);
|
||||||
|
{
|
||||||
|
map['type'] = i0.Variable<int>(
|
||||||
|
i1.$LocalAssetEntityTable.$convertertype.toSql(type));
|
||||||
|
}
|
||||||
|
map['created_at'] = i0.Variable<DateTime>(createdAt);
|
||||||
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
||||||
|
if (!nullToAbsent || durationInSeconds != null) {
|
||||||
|
map['duration_in_seconds'] = i0.Variable<int>(durationInSeconds);
|
||||||
|
}
|
||||||
|
map['id'] = i0.Variable<String>(id);
|
||||||
|
if (!nullToAbsent || checksum != null) {
|
||||||
|
map['checksum'] = i0.Variable<String>(checksum);
|
||||||
|
}
|
||||||
|
map['is_favorite'] = i0.Variable<bool>(isFavorite);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory LocalAssetEntityData.fromJson(Map<String, dynamic> json,
|
||||||
|
{i0.ValueSerializer? serializer}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return LocalAssetEntityData(
|
||||||
|
name: serializer.fromJson<String>(json['name']),
|
||||||
|
type: i1.$LocalAssetEntityTable.$convertertype
|
||||||
|
.fromJson(serializer.fromJson<int>(json['type'])),
|
||||||
|
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||||
|
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
||||||
|
durationInSeconds: serializer.fromJson<int?>(json['durationInSeconds']),
|
||||||
|
id: serializer.fromJson<String>(json['id']),
|
||||||
|
checksum: serializer.fromJson<String?>(json['checksum']),
|
||||||
|
isFavorite: serializer.fromJson<bool>(json['isFavorite']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'name': serializer.toJson<String>(name),
|
||||||
|
'type': serializer
|
||||||
|
.toJson<int>(i1.$LocalAssetEntityTable.$convertertype.toJson(type)),
|
||||||
|
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||||
|
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
||||||
|
'durationInSeconds': serializer.toJson<int?>(durationInSeconds),
|
||||||
|
'id': serializer.toJson<String>(id),
|
||||||
|
'checksum': serializer.toJson<String?>(checksum),
|
||||||
|
'isFavorite': serializer.toJson<bool>(isFavorite),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.LocalAssetEntityData copyWith(
|
||||||
|
{String? name,
|
||||||
|
i2.AssetType? type,
|
||||||
|
DateTime? createdAt,
|
||||||
|
DateTime? updatedAt,
|
||||||
|
i0.Value<int?> durationInSeconds = const i0.Value.absent(),
|
||||||
|
String? id,
|
||||||
|
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||||
|
bool? isFavorite}) =>
|
||||||
|
i1.LocalAssetEntityData(
|
||||||
|
name: name ?? this.name,
|
||||||
|
type: type ?? this.type,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
durationInSeconds: durationInSeconds.present
|
||||||
|
? durationInSeconds.value
|
||||||
|
: this.durationInSeconds,
|
||||||
|
id: id ?? this.id,
|
||||||
|
checksum: checksum.present ? checksum.value : this.checksum,
|
||||||
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
|
);
|
||||||
|
LocalAssetEntityData copyWithCompanion(i1.LocalAssetEntityCompanion data) {
|
||||||
|
return LocalAssetEntityData(
|
||||||
|
name: data.name.present ? data.name.value : this.name,
|
||||||
|
type: data.type.present ? data.type.value : this.type,
|
||||||
|
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||||
|
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
||||||
|
durationInSeconds: data.durationInSeconds.present
|
||||||
|
? data.durationInSeconds.value
|
||||||
|
: this.durationInSeconds,
|
||||||
|
id: data.id.present ? data.id.value : this.id,
|
||||||
|
checksum: data.checksum.present ? data.checksum.value : this.checksum,
|
||||||
|
isFavorite:
|
||||||
|
data.isFavorite.present ? data.isFavorite.value : this.isFavorite,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('LocalAssetEntityData(')
|
||||||
|
..write('name: $name, ')
|
||||||
|
..write('type: $type, ')
|
||||||
|
..write('createdAt: $createdAt, ')
|
||||||
|
..write('updatedAt: $updatedAt, ')
|
||||||
|
..write('durationInSeconds: $durationInSeconds, ')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('checksum: $checksum, ')
|
||||||
|
..write('isFavorite: $isFavorite')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(name, type, createdAt, updatedAt,
|
||||||
|
durationInSeconds, id, checksum, isFavorite);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is i1.LocalAssetEntityData &&
|
||||||
|
other.name == this.name &&
|
||||||
|
other.type == this.type &&
|
||||||
|
other.createdAt == this.createdAt &&
|
||||||
|
other.updatedAt == this.updatedAt &&
|
||||||
|
other.durationInSeconds == this.durationInSeconds &&
|
||||||
|
other.id == this.id &&
|
||||||
|
other.checksum == this.checksum &&
|
||||||
|
other.isFavorite == this.isFavorite);
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalAssetEntityCompanion
|
||||||
|
extends i0.UpdateCompanion<i1.LocalAssetEntityData> {
|
||||||
|
final i0.Value<String> name;
|
||||||
|
final i0.Value<i2.AssetType> type;
|
||||||
|
final i0.Value<DateTime> createdAt;
|
||||||
|
final i0.Value<DateTime> updatedAt;
|
||||||
|
final i0.Value<int?> durationInSeconds;
|
||||||
|
final i0.Value<String> id;
|
||||||
|
final i0.Value<String?> checksum;
|
||||||
|
final i0.Value<bool> isFavorite;
|
||||||
|
const LocalAssetEntityCompanion({
|
||||||
|
this.name = const i0.Value.absent(),
|
||||||
|
this.type = const i0.Value.absent(),
|
||||||
|
this.createdAt = const i0.Value.absent(),
|
||||||
|
this.updatedAt = const i0.Value.absent(),
|
||||||
|
this.durationInSeconds = const i0.Value.absent(),
|
||||||
|
this.id = const i0.Value.absent(),
|
||||||
|
this.checksum = const i0.Value.absent(),
|
||||||
|
this.isFavorite = const i0.Value.absent(),
|
||||||
|
});
|
||||||
|
LocalAssetEntityCompanion.insert({
|
||||||
|
required String name,
|
||||||
|
required i2.AssetType type,
|
||||||
|
this.createdAt = const i0.Value.absent(),
|
||||||
|
this.updatedAt = const i0.Value.absent(),
|
||||||
|
this.durationInSeconds = const i0.Value.absent(),
|
||||||
|
required String id,
|
||||||
|
this.checksum = const i0.Value.absent(),
|
||||||
|
this.isFavorite = const i0.Value.absent(),
|
||||||
|
}) : name = i0.Value(name),
|
||||||
|
type = i0.Value(type),
|
||||||
|
id = i0.Value(id);
|
||||||
|
static i0.Insertable<i1.LocalAssetEntityData> custom({
|
||||||
|
i0.Expression<String>? name,
|
||||||
|
i0.Expression<int>? type,
|
||||||
|
i0.Expression<DateTime>? createdAt,
|
||||||
|
i0.Expression<DateTime>? updatedAt,
|
||||||
|
i0.Expression<int>? durationInSeconds,
|
||||||
|
i0.Expression<String>? id,
|
||||||
|
i0.Expression<String>? checksum,
|
||||||
|
i0.Expression<bool>? isFavorite,
|
||||||
|
}) {
|
||||||
|
return i0.RawValuesInsertable({
|
||||||
|
if (name != null) 'name': name,
|
||||||
|
if (type != null) 'type': type,
|
||||||
|
if (createdAt != null) 'created_at': createdAt,
|
||||||
|
if (updatedAt != null) 'updated_at': updatedAt,
|
||||||
|
if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds,
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (checksum != null) 'checksum': checksum,
|
||||||
|
if (isFavorite != null) 'is_favorite': isFavorite,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.LocalAssetEntityCompanion copyWith(
|
||||||
|
{i0.Value<String>? name,
|
||||||
|
i0.Value<i2.AssetType>? type,
|
||||||
|
i0.Value<DateTime>? createdAt,
|
||||||
|
i0.Value<DateTime>? updatedAt,
|
||||||
|
i0.Value<int?>? durationInSeconds,
|
||||||
|
i0.Value<String>? id,
|
||||||
|
i0.Value<String?>? checksum,
|
||||||
|
i0.Value<bool>? isFavorite}) {
|
||||||
|
return i1.LocalAssetEntityCompanion(
|
||||||
|
name: name ?? this.name,
|
||||||
|
type: type ?? this.type,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
|
||||||
|
id: id ?? this.id,
|
||||||
|
checksum: checksum ?? this.checksum,
|
||||||
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
if (name.present) {
|
||||||
|
map['name'] = i0.Variable<String>(name.value);
|
||||||
|
}
|
||||||
|
if (type.present) {
|
||||||
|
map['type'] = i0.Variable<int>(
|
||||||
|
i1.$LocalAssetEntityTable.$convertertype.toSql(type.value));
|
||||||
|
}
|
||||||
|
if (createdAt.present) {
|
||||||
|
map['created_at'] = i0.Variable<DateTime>(createdAt.value);
|
||||||
|
}
|
||||||
|
if (updatedAt.present) {
|
||||||
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
|
||||||
|
}
|
||||||
|
if (durationInSeconds.present) {
|
||||||
|
map['duration_in_seconds'] = i0.Variable<int>(durationInSeconds.value);
|
||||||
|
}
|
||||||
|
if (id.present) {
|
||||||
|
map['id'] = i0.Variable<String>(id.value);
|
||||||
|
}
|
||||||
|
if (checksum.present) {
|
||||||
|
map['checksum'] = i0.Variable<String>(checksum.value);
|
||||||
|
}
|
||||||
|
if (isFavorite.present) {
|
||||||
|
map['is_favorite'] = i0.Variable<bool>(isFavorite.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('LocalAssetEntityCompanion(')
|
||||||
|
..write('name: $name, ')
|
||||||
|
..write('type: $type, ')
|
||||||
|
..write('createdAt: $createdAt, ')
|
||||||
|
..write('updatedAt: $updatedAt, ')
|
||||||
|
..write('durationInSeconds: $durationInSeconds, ')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('checksum: $checksum, ')
|
||||||
|
..write('isFavorite: $isFavorite')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/album_media.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'
|
||||||
|
as asset;
|
||||||
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
|
class AlbumMediaRepository implements IAlbumMediaRepository {
|
||||||
|
const AlbumMediaRepository();
|
||||||
|
|
||||||
|
PMFilter _getAlbumFilter({
|
||||||
|
DateTimeFilter? createdTimeCond,
|
||||||
|
DateTimeFilter? updateTimeCond,
|
||||||
|
}) =>
|
||||||
|
FilterOptionGroup(
|
||||||
|
imageOption: const FilterOption(
|
||||||
|
// needTitle is expected to be slow on iOS but is required to fetch the asset title
|
||||||
|
needTitle: true,
|
||||||
|
sizeConstraint: SizeConstraint(ignoreSize: true),
|
||||||
|
),
|
||||||
|
videoOption: const FilterOption(
|
||||||
|
needTitle: true,
|
||||||
|
sizeConstraint: SizeConstraint(ignoreSize: true),
|
||||||
|
durationConstraint: DurationConstraint(allowNullable: true),
|
||||||
|
),
|
||||||
|
// This is needed to get the modified time of the album
|
||||||
|
containsPathModified: true,
|
||||||
|
createTimeCond: createdTimeCond == null
|
||||||
|
? DateTimeCond.def().copyWith(ignore: true)
|
||||||
|
: DateTimeCond(min: createdTimeCond.min, max: createdTimeCond.max),
|
||||||
|
updateTimeCond: updateTimeCond == null
|
||||||
|
? DateTimeCond.def().copyWith(ignore: true)
|
||||||
|
: DateTimeCond(min: updateTimeCond.min, max: updateTimeCond.max),
|
||||||
|
orders: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<asset.LocalAsset>> getAssetsForAlbum(
|
||||||
|
String albumId, {
|
||||||
|
DateTimeFilter? updateTimeCond,
|
||||||
|
}) async {
|
||||||
|
final assetPathEntity = await AssetPathEntity.obtainPathFromProperties(
|
||||||
|
id: albumId,
|
||||||
|
optionGroup: _getAlbumFilter(updateTimeCond: updateTimeCond),
|
||||||
|
);
|
||||||
|
final assets = <AssetEntity>[];
|
||||||
|
int pageNumber = 0, lastPageCount = 0;
|
||||||
|
do {
|
||||||
|
final page = await assetPathEntity.getAssetListPaged(
|
||||||
|
page: pageNumber,
|
||||||
|
size: kFetchLocalAssetsBatchSize,
|
||||||
|
);
|
||||||
|
assets.addAll(page);
|
||||||
|
lastPageCount = page.length;
|
||||||
|
pageNumber++;
|
||||||
|
} while (lastPageCount == kFetchLocalAssetsBatchSize);
|
||||||
|
return Future.wait(assets.map((a) => a.toDto()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on AssetEntity {
|
||||||
|
Future<asset.LocalAsset> toDto() async => asset.LocalAsset(
|
||||||
|
id: id,
|
||||||
|
name: title ?? await titleAsync,
|
||||||
|
type: switch (type) {
|
||||||
|
AssetType.other => asset.AssetType.other,
|
||||||
|
AssetType.image => asset.AssetType.image,
|
||||||
|
AssetType.video => asset.AssetType.video,
|
||||||
|
AssetType.audio => asset.AssetType.audio,
|
||||||
|
},
|
||||||
|
createdAt: createDateTime,
|
||||||
|
updatedAt: modifiedDateTime,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
durationInSeconds: duration,
|
||||||
|
);
|
||||||
|
}
|
@ -3,6 +3,9 @@ import 'dart:async';
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:drift_flutter/drift_flutter.dart';
|
import 'package:drift_flutter/drift_flutter.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart';
|
||||||
@ -25,7 +28,16 @@ class IsarDatabaseRepository implements IDatabaseRepository {
|
|||||||
Zone.current[_kzoneTxn] == null ? _db.writeTxn(callback) : callback();
|
Zone.current[_kzoneTxn] == null ? _db.writeTxn(callback) : callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
@DriftDatabase(tables: [UserEntity, UserMetadataEntity, PartnerEntity])
|
@DriftDatabase(
|
||||||
|
tables: [
|
||||||
|
UserEntity,
|
||||||
|
UserMetadataEntity,
|
||||||
|
PartnerEntity,
|
||||||
|
LocalAlbumEntity,
|
||||||
|
LocalAssetEntity,
|
||||||
|
LocalAlbumAssetEntity,
|
||||||
|
],
|
||||||
|
)
|
||||||
class Drift extends $Drift implements IDatabaseRepository {
|
class Drift extends $Drift implements IDatabaseRepository {
|
||||||
Drift([QueryExecutor? executor])
|
Drift([QueryExecutor? executor])
|
||||||
: super(
|
: super(
|
||||||
@ -42,8 +54,9 @@ class Drift extends $Drift implements IDatabaseRepository {
|
|||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
beforeOpen: (details) async {
|
beforeOpen: (details) async {
|
||||||
await customStatement('PRAGMA journal_mode = WAL');
|
|
||||||
await customStatement('PRAGMA foreign_keys = ON');
|
await customStatement('PRAGMA foreign_keys = ON');
|
||||||
|
await customStatement('PRAGMA synchronous = NORMAL');
|
||||||
|
await customStatement('PRAGMA journal_mode = WAL');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,12 @@ 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_album.entity.drift.dart'
|
||||||
|
as i4;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'
|
||||||
|
as i5;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'
|
||||||
|
as i6;
|
||||||
|
|
||||||
abstract class $Drift extends i0.GeneratedDatabase {
|
abstract class $Drift extends i0.GeneratedDatabase {
|
||||||
$Drift(i0.QueryExecutor e) : super(e);
|
$Drift(i0.QueryExecutor e) : super(e);
|
||||||
@ -16,12 +22,25 @@ 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.$LocalAlbumEntityTable localAlbumEntity =
|
||||||
|
i4.$LocalAlbumEntityTable(this);
|
||||||
|
late final i5.$LocalAssetEntityTable localAssetEntity =
|
||||||
|
i5.$LocalAssetEntityTable(this);
|
||||||
|
late final i6.$LocalAlbumAssetEntityTable localAlbumAssetEntity =
|
||||||
|
i6.$LocalAlbumAssetEntityTable(this);
|
||||||
@override
|
@override
|
||||||
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
||||||
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
||||||
@override
|
@override
|
||||||
List<i0.DatabaseSchemaEntity> get allSchemaEntities =>
|
List<i0.DatabaseSchemaEntity> get allSchemaEntities => [
|
||||||
[userEntity, userMetadataEntity, partnerEntity];
|
userEntity,
|
||||||
|
userMetadataEntity,
|
||||||
|
partnerEntity,
|
||||||
|
localAlbumEntity,
|
||||||
|
localAssetEntity,
|
||||||
|
localAlbumAssetEntity,
|
||||||
|
i5.localAssetChecksum
|
||||||
|
];
|
||||||
@override
|
@override
|
||||||
i0.StreamQueryUpdateRules get streamUpdateRules =>
|
i0.StreamQueryUpdateRules get streamUpdateRules =>
|
||||||
const i0.StreamQueryUpdateRules(
|
const i0.StreamQueryUpdateRules(
|
||||||
@ -48,6 +67,22 @@ 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_asset_entity',
|
||||||
|
kind: i0.UpdateKind.delete),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
i0.WritePropagation(
|
||||||
|
on: i0.TableUpdateQuery.onTableName('local_album_entity',
|
||||||
|
limitUpdateKind: i0.UpdateKind.delete),
|
||||||
|
result: [
|
||||||
|
i0.TableUpdate('local_album_asset_entity',
|
||||||
|
kind: i0.UpdateKind.delete),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
@ -64,4 +99,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.$$LocalAlbumEntityTableTableManager get localAlbumEntity =>
|
||||||
|
i4.$$LocalAlbumEntityTableTableManager(_db, _db.localAlbumEntity);
|
||||||
|
i5.$$LocalAssetEntityTableTableManager get localAssetEntity =>
|
||||||
|
i5.$$LocalAssetEntityTableTableManager(_db, _db.localAssetEntity);
|
||||||
|
i6.$$LocalAlbumAssetEntityTableTableManager get localAlbumAssetEntity => i6
|
||||||
|
.$$LocalAlbumAssetEntityTableTableManager(_db, _db.localAlbumAssetEntity);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,384 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/local_album.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/local_album.model.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_album_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/platform/native_sync_api.g.dart';
|
||||||
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
|
class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
||||||
|
implements ILocalAlbumRepository {
|
||||||
|
final Drift _db;
|
||||||
|
final Platform _platform;
|
||||||
|
const DriftLocalAlbumRepository(this._db, {Platform? platform})
|
||||||
|
: _platform = platform ?? const LocalPlatform(),
|
||||||
|
super(_db);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<LocalAlbum>> getAll({SortLocalAlbumsBy? sortBy}) {
|
||||||
|
final assetCount = _db.localAlbumAssetEntity.assetId.count();
|
||||||
|
|
||||||
|
final query = _db.localAlbumEntity.select().join([
|
||||||
|
leftOuterJoin(
|
||||||
|
_db.localAlbumAssetEntity,
|
||||||
|
_db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
query
|
||||||
|
..addColumns([assetCount])
|
||||||
|
..groupBy([_db.localAlbumEntity.id]);
|
||||||
|
if (sortBy == SortLocalAlbumsBy.id) {
|
||||||
|
query.orderBy([OrderingTerm.asc(_db.localAlbumEntity.id)]);
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
.map(
|
||||||
|
(row) => row
|
||||||
|
.readTable(_db.localAlbumEntity)
|
||||||
|
.toDto(assetCount: row.read(assetCount) ?? 0),
|
||||||
|
)
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> delete(String albumId) => transaction(() async {
|
||||||
|
// Remove all assets that are only in this particular album
|
||||||
|
// We cannot remove all assets in the album because they might be in other albums in iOS
|
||||||
|
// That is not the case on Android since asset <-> album has one:one mapping
|
||||||
|
final assetsToDelete = _platform.isIOS
|
||||||
|
? await _getUniqueAssetsInAlbum(albumId)
|
||||||
|
: await getAssetIdsForAlbum(albumId);
|
||||||
|
await _deleteAssets(assetsToDelete);
|
||||||
|
|
||||||
|
// All the other assets that are still associated will be unlinked automatically on-cascade
|
||||||
|
await _db.managers.localAlbumEntity
|
||||||
|
.filter((a) => a.id.equals(albumId))
|
||||||
|
.delete();
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> syncAlbumDeletes(
|
||||||
|
String albumId,
|
||||||
|
Iterable<String> assetIdsToKeep,
|
||||||
|
) async {
|
||||||
|
if (assetIdsToKeep.isEmpty) {
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
final deleteSmt = _db.localAssetEntity.delete();
|
||||||
|
deleteSmt.where((localAsset) {
|
||||||
|
final subQuery = _db.localAlbumAssetEntity.selectOnly()
|
||||||
|
..addColumns([_db.localAlbumAssetEntity.assetId])
|
||||||
|
..join([
|
||||||
|
innerJoin(
|
||||||
|
_db.localAlbumEntity,
|
||||||
|
_db.localAlbumAssetEntity.albumId
|
||||||
|
.equalsExp(_db.localAlbumEntity.id),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
subQuery.where(
|
||||||
|
_db.localAlbumEntity.id.equals(albumId) &
|
||||||
|
_db.localAlbumAssetEntity.assetId.isNotIn(assetIdsToKeep),
|
||||||
|
);
|
||||||
|
return localAsset.id.isInQuery(subQuery);
|
||||||
|
});
|
||||||
|
await deleteSmt.go();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> upsert(
|
||||||
|
LocalAlbum localAlbum, {
|
||||||
|
Iterable<LocalAsset> toUpsert = const [],
|
||||||
|
Iterable<String> toDelete = const [],
|
||||||
|
}) {
|
||||||
|
final companion = LocalAlbumEntityCompanion.insert(
|
||||||
|
id: localAlbum.id,
|
||||||
|
name: localAlbum.name,
|
||||||
|
updatedAt: Value(localAlbum.updatedAt),
|
||||||
|
backupSelection: localAlbum.backupSelection,
|
||||||
|
);
|
||||||
|
|
||||||
|
return _db.transaction(() async {
|
||||||
|
await _db.localAlbumEntity
|
||||||
|
.insertOne(companion, onConflict: DoUpdate((_) => companion));
|
||||||
|
await _addAssets(localAlbum.id, toUpsert);
|
||||||
|
await _removeAssets(localAlbum.id, toDelete);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> updateAll(Iterable<LocalAlbum> albums) {
|
||||||
|
return _db.transaction(() async {
|
||||||
|
await _db.localAlbumEntity
|
||||||
|
.update()
|
||||||
|
.write(const LocalAlbumEntityCompanion(marker_: Value(true)));
|
||||||
|
|
||||||
|
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(null),
|
||||||
|
);
|
||||||
|
|
||||||
|
batch.insert(
|
||||||
|
_db.localAlbumEntity,
|
||||||
|
companion,
|
||||||
|
onConflict: DoUpdate((_) => companion),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (_platform.isAndroid) {
|
||||||
|
// On Android, an asset can only be in one album
|
||||||
|
// So, get the albums that are marked for deletion
|
||||||
|
// and delete all the assets that are in those albums
|
||||||
|
final deleteSmt = _db.localAssetEntity.delete();
|
||||||
|
deleteSmt.where((localAsset) {
|
||||||
|
final subQuery = _db.localAlbumAssetEntity.selectOnly()
|
||||||
|
..addColumns([_db.localAlbumAssetEntity.assetId])
|
||||||
|
..join([
|
||||||
|
innerJoin(
|
||||||
|
_db.localAlbumEntity,
|
||||||
|
_db.localAlbumAssetEntity.albumId
|
||||||
|
.equalsExp(_db.localAlbumEntity.id),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
subQuery.where(_db.localAlbumEntity.marker_.isNotNull());
|
||||||
|
return localAsset.id.isInQuery(subQuery);
|
||||||
|
});
|
||||||
|
await deleteSmt.go();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _db.localAlbumEntity.deleteWhere((f) => f.marker_.isNotNull());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<LocalAsset>> getAssetsForAlbum(String albumId) {
|
||||||
|
final query = _db.localAlbumAssetEntity.select().join(
|
||||||
|
[
|
||||||
|
innerJoin(
|
||||||
|
_db.localAssetEntity,
|
||||||
|
_db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
..where(_db.localAlbumAssetEntity.albumId.equals(albumId))
|
||||||
|
..orderBy([OrderingTerm.asc(_db.localAssetEntity.id)]);
|
||||||
|
return query
|
||||||
|
.map((row) => row.readTable(_db.localAssetEntity).toDto())
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<String>> getAssetIdsForAlbum(String albumId) {
|
||||||
|
final query = _db.localAlbumAssetEntity.selectOnly()
|
||||||
|
..addColumns([_db.localAlbumAssetEntity.assetId])
|
||||||
|
..where(_db.localAlbumAssetEntity.albumId.equals(albumId));
|
||||||
|
return query
|
||||||
|
.map((row) => row.read(_db.localAlbumAssetEntity.assetId)!)
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> processDelta(SyncDelta delta) {
|
||||||
|
return _db.transaction(() async {
|
||||||
|
await _deleteAssets(delta.deletes);
|
||||||
|
|
||||||
|
await _upsertAssets(delta.updates.map((a) => a.toLocalAsset()));
|
||||||
|
// The ugly casting below is required for now because the generated code
|
||||||
|
// casts the returned values from the platform during decoding them
|
||||||
|
// and iterating over them causes the type to be List<Object?> instead of
|
||||||
|
// List<String>
|
||||||
|
await _db.batch((batch) async {
|
||||||
|
delta.assetAlbums
|
||||||
|
.cast<String, List<Object?>>()
|
||||||
|
.forEach((assetId, albumIds) {
|
||||||
|
batch.deleteWhere(
|
||||||
|
_db.localAlbumAssetEntity,
|
||||||
|
(f) =>
|
||||||
|
f.albumId.isNotIn(albumIds.cast<String?>().nonNulls) &
|
||||||
|
f.assetId.equals(assetId),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await _db.batch((batch) async {
|
||||||
|
delta.assetAlbums
|
||||||
|
.cast<String, List<Object?>>()
|
||||||
|
.forEach((assetId, albumIds) {
|
||||||
|
batch.insertAll(
|
||||||
|
_db.localAlbumAssetEntity,
|
||||||
|
albumIds.cast<String?>().nonNulls.map(
|
||||||
|
(albumId) => LocalAlbumAssetEntityCompanion.insert(
|
||||||
|
assetId: assetId,
|
||||||
|
albumId: albumId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onConflict: DoNothing(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _addAssets(String albumId, Iterable<LocalAsset> assets) {
|
||||||
|
if (assets.isEmpty) {
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
return transaction(() async {
|
||||||
|
await _upsertAssets(assets);
|
||||||
|
await _db.localAlbumAssetEntity.insertAll(
|
||||||
|
assets.map(
|
||||||
|
(a) => LocalAlbumAssetEntityCompanion.insert(
|
||||||
|
assetId: a.id,
|
||||||
|
albumId: albumId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _removeAssets(String albumId, Iterable<String> assetIds) async {
|
||||||
|
if (assetIds.isEmpty) {
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_platform.isAndroid) {
|
||||||
|
return _deleteAssets(assetIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> assetsToDelete = [];
|
||||||
|
List<String> assetsToUnLink = [];
|
||||||
|
|
||||||
|
final uniqueAssets = await _getUniqueAssetsInAlbum(albumId);
|
||||||
|
if (uniqueAssets.isEmpty) {
|
||||||
|
assetsToUnLink = assetIds.toList();
|
||||||
|
} else {
|
||||||
|
// Delete unique assets and unlink others
|
||||||
|
final uniqueSet = uniqueAssets.toSet();
|
||||||
|
|
||||||
|
for (final assetId in assetIds) {
|
||||||
|
if (uniqueSet.contains(assetId)) {
|
||||||
|
assetsToDelete.add(assetId);
|
||||||
|
} else {
|
||||||
|
assetsToUnLink.add(assetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transaction(() async {
|
||||||
|
if (assetsToUnLink.isNotEmpty) {
|
||||||
|
await _db.batch(
|
||||||
|
(batch) => batch.deleteWhere(
|
||||||
|
_db.localAlbumAssetEntity,
|
||||||
|
(f) => f.assetId.isIn(assetsToUnLink) & f.albumId.equals(albumId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _deleteAssets(assetsToDelete);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all asset ids that are only in this album and not in other albums.
|
||||||
|
/// This is useful in cases where the album is a smart album or a user-created album, especially on iOS
|
||||||
|
Future<List<String>> _getUniqueAssetsInAlbum(String albumId) {
|
||||||
|
final assetId = _db.localAlbumAssetEntity.assetId;
|
||||||
|
final query = _db.localAlbumAssetEntity.selectOnly()
|
||||||
|
..addColumns([assetId])
|
||||||
|
..groupBy(
|
||||||
|
[assetId],
|
||||||
|
having: _db.localAlbumAssetEntity.albumId.count().equals(1) &
|
||||||
|
_db.localAlbumAssetEntity.albumId.equals(albumId),
|
||||||
|
);
|
||||||
|
|
||||||
|
return query.map((row) => row.read(assetId)!).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _upsertAssets(Iterable<LocalAsset> localAssets) {
|
||||||
|
if (localAssets.isEmpty) {
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _db.batch((batch) async {
|
||||||
|
batch.insertAllOnConflictUpdate(
|
||||||
|
_db.localAssetEntity,
|
||||||
|
localAssets.map(
|
||||||
|
(a) => LocalAssetEntityCompanion.insert(
|
||||||
|
name: a.name,
|
||||||
|
type: a.type,
|
||||||
|
createdAt: Value(a.createdAt),
|
||||||
|
updatedAt: Value(a.updatedAt),
|
||||||
|
durationInSeconds: Value.absentIfNull(a.durationInSeconds),
|
||||||
|
id: a.id,
|
||||||
|
checksum: Value.absentIfNull(a.checksum),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _deleteAssets(Iterable<String> ids) {
|
||||||
|
if (ids.isEmpty) {
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _db.batch(
|
||||||
|
(batch) => batch.deleteWhere(
|
||||||
|
_db.localAssetEntity,
|
||||||
|
(f) => f.id.isIn(ids),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on ImAsset {
|
||||||
|
LocalAsset toLocalAsset() {
|
||||||
|
return LocalAsset(
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
type: AssetType.values.elementAtOrNull(type) ?? AssetType.other,
|
||||||
|
createdAt: createdAt == null
|
||||||
|
? DateTime.now()
|
||||||
|
: DateTime.fromMillisecondsSinceEpoch(createdAt! * 1000),
|
||||||
|
updatedAt: updatedAt == null
|
||||||
|
? DateTime.now()
|
||||||
|
: DateTime.fromMillisecondsSinceEpoch(updatedAt! * 1000),
|
||||||
|
durationInSeconds: durationInSeconds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LocalAlbumEntityX on LocalAlbumEntityData {
|
||||||
|
LocalAlbum toDto({int assetCount = 0}) {
|
||||||
|
return LocalAlbum(
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
assetCount: assetCount,
|
||||||
|
backupSelection: backupSelection,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on LocalAssetEntityData {
|
||||||
|
LocalAsset toDto() {
|
||||||
|
return LocalAsset(
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
checksum: checksum,
|
||||||
|
type: type,
|
||||||
|
createdAt: createdAt,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
durationInSeconds: durationInSeconds,
|
||||||
|
isFavorite: isFavorite,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
10
mobile/lib/infrastructure/utils/asset.mixin.dart
Normal file
10
mobile/lib/infrastructure/utils/asset.mixin.dart
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
|
||||||
|
mixin AssetEntityMixin on Table {
|
||||||
|
TextColumn get name => text()();
|
||||||
|
IntColumn get type => intEnum<AssetType>()();
|
||||||
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
IntColumn get durationInSeconds => integer().nullable()();
|
||||||
|
}
|
422
mobile/lib/platform/native_sync_api.g.dart
generated
Normal file
422
mobile/lib/platform/native_sync_api.g.dart
generated
Normal file
@ -0,0 +1,422 @@
|
|||||||
|
// Autogenerated from Pigeon (v25.3.2), 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 ImAsset {
|
||||||
|
ImAsset({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.type,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
required this.durationInSeconds,
|
||||||
|
});
|
||||||
|
|
||||||
|
String id;
|
||||||
|
|
||||||
|
String name;
|
||||||
|
|
||||||
|
int type;
|
||||||
|
|
||||||
|
int? createdAt;
|
||||||
|
|
||||||
|
int? updatedAt;
|
||||||
|
|
||||||
|
int durationInSeconds;
|
||||||
|
|
||||||
|
List<Object?> _toList() {
|
||||||
|
return <Object?>[
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
durationInSeconds,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object encode() {
|
||||||
|
return _toList(); }
|
||||||
|
|
||||||
|
static ImAsset decode(Object result) {
|
||||||
|
result as List<Object?>;
|
||||||
|
return ImAsset(
|
||||||
|
id: result[0]! as String,
|
||||||
|
name: result[1]! as String,
|
||||||
|
type: result[2]! as int,
|
||||||
|
createdAt: result[3] as int?,
|
||||||
|
updatedAt: result[4] as int?,
|
||||||
|
durationInSeconds: result[5]! as int,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (other is! ImAsset || 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 ImAlbum {
|
||||||
|
ImAlbum({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
this.updatedAt,
|
||||||
|
required this.isCloud,
|
||||||
|
required this.assetCount,
|
||||||
|
});
|
||||||
|
|
||||||
|
String id;
|
||||||
|
|
||||||
|
String name;
|
||||||
|
|
||||||
|
int? updatedAt;
|
||||||
|
|
||||||
|
bool isCloud;
|
||||||
|
|
||||||
|
int assetCount;
|
||||||
|
|
||||||
|
List<Object?> _toList() {
|
||||||
|
return <Object?>[
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
updatedAt,
|
||||||
|
isCloud,
|
||||||
|
assetCount,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object encode() {
|
||||||
|
return _toList(); }
|
||||||
|
|
||||||
|
static ImAlbum decode(Object result) {
|
||||||
|
result as List<Object?>;
|
||||||
|
return ImAlbum(
|
||||||
|
id: result[0]! as String,
|
||||||
|
name: result[1]! as String,
|
||||||
|
updatedAt: result[2] as int?,
|
||||||
|
isCloud: result[3]! as bool,
|
||||||
|
assetCount: result[4]! as int,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (other is! ImAlbum || 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({
|
||||||
|
required this.hasChanges,
|
||||||
|
required this.updates,
|
||||||
|
required this.deletes,
|
||||||
|
required this.assetAlbums,
|
||||||
|
});
|
||||||
|
|
||||||
|
bool hasChanges;
|
||||||
|
|
||||||
|
List<ImAsset> updates;
|
||||||
|
|
||||||
|
List<String> deletes;
|
||||||
|
|
||||||
|
Map<String, List<String>> assetAlbums;
|
||||||
|
|
||||||
|
List<Object?> _toList() {
|
||||||
|
return <Object?>[
|
||||||
|
hasChanges,
|
||||||
|
updates,
|
||||||
|
deletes,
|
||||||
|
assetAlbums,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object encode() {
|
||||||
|
return _toList(); }
|
||||||
|
|
||||||
|
static SyncDelta decode(Object result) {
|
||||||
|
result as List<Object?>;
|
||||||
|
return SyncDelta(
|
||||||
|
hasChanges: result[0]! as bool,
|
||||||
|
updates: (result[1] as List<Object?>?)!.cast<ImAsset>(),
|
||||||
|
deletes: (result[2] as List<Object?>?)!.cast<String>(),
|
||||||
|
assetAlbums: (result[3] as Map<Object?, Object?>?)!.cast<String, List<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 ImAsset) {
|
||||||
|
buffer.putUint8(129);
|
||||||
|
writeValue(buffer, value.encode());
|
||||||
|
} else if (value is ImAlbum) {
|
||||||
|
buffer.putUint8(130);
|
||||||
|
writeValue(buffer, value.encode());
|
||||||
|
} else if (value is SyncDelta) {
|
||||||
|
buffer.putUint8(131);
|
||||||
|
writeValue(buffer, value.encode());
|
||||||
|
} else {
|
||||||
|
super.writeValue(buffer, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||||
|
switch (type) {
|
||||||
|
case 129:
|
||||||
|
return ImAsset.decode(readValue(buffer)!);
|
||||||
|
case 130:
|
||||||
|
return ImAlbum.decode(readValue(buffer)!);
|
||||||
|
case 131:
|
||||||
|
return SyncDelta.decode(readValue(buffer)!);
|
||||||
|
default:
|
||||||
|
return super.readValueOfType(type, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NativeSyncApi {
|
||||||
|
/// Constructor for [NativeSyncApi]. 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.
|
||||||
|
NativeSyncApi({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.NativeSyncApi.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<SyncDelta> getMediaChanges() async {
|
||||||
|
final String pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.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.NativeSyncApi.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> clearSyncCheckpoint() async {
|
||||||
|
final String pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.clearSyncCheckpoint$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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<String>> getAssetIdsForAlbum(String albumId) async {
|
||||||
|
final String pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[albumId]);
|
||||||
|
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 List<Object?>?)!.cast<String>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<ImAlbum>> getAlbums() async {
|
||||||
|
final String pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums$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 List<Object?>?)!.cast<ImAlbum>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
|
||||||
|
final _features = [
|
||||||
|
_Feature(
|
||||||
|
name: 'Sync Local',
|
||||||
|
icon: Icons.photo_album_rounded,
|
||||||
|
onTap: (_, ref) => ref.read(backgroundSyncProvider).syncLocal(),
|
||||||
|
),
|
||||||
|
_Feature(
|
||||||
|
name: 'Sync Remote',
|
||||||
|
icon: Icons.refresh_rounded,
|
||||||
|
onTap: (_, ref) => ref.read(backgroundSyncProvider).syncRemote(),
|
||||||
|
),
|
||||||
|
_Feature(
|
||||||
|
name: 'WAL Checkpoint',
|
||||||
|
icon: Icons.save_rounded,
|
||||||
|
onTap: (_, ref) => ref
|
||||||
|
.read(driftProvider)
|
||||||
|
.customStatement("pragma wal_checkpoint(truncate)"),
|
||||||
|
),
|
||||||
|
_Feature(
|
||||||
|
name: 'Clear Delta Checkpoint',
|
||||||
|
icon: Icons.delete_rounded,
|
||||||
|
onTap: (_, ref) => ref.read(nativeSyncApiProvider).clearSyncCheckpoint(),
|
||||||
|
),
|
||||||
|
_Feature(
|
||||||
|
name: 'Clear Local Data',
|
||||||
|
icon: Icons.delete_forever_rounded,
|
||||||
|
onTap: (_, ref) async {
|
||||||
|
final db = ref.read(driftProvider);
|
||||||
|
await db.localAssetEntity.deleteAll();
|
||||||
|
await db.localAlbumEntity.deleteAll();
|
||||||
|
await db.localAlbumAssetEntity.deleteAll();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_Feature(
|
||||||
|
name: 'Local Media Summary',
|
||||||
|
icon: Icons.table_chart_rounded,
|
||||||
|
onTap: (ctx, _) => ctx.pushRoute(const LocalMediaSummaryRoute()),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class FeatInDevPage extends StatelessWidget {
|
||||||
|
const FeatInDevPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Features in Development'),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: ListView.builder(
|
||||||
|
itemBuilder: (_, index) {
|
||||||
|
final feat = _features[index];
|
||||||
|
return Consumer(
|
||||||
|
builder: (ctx, ref, _) => ListTile(
|
||||||
|
title: Text(feat.name),
|
||||||
|
trailing: Icon(feat.icon),
|
||||||
|
onTap: () => unawaited(feat.onTap(ctx, ref)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: _features.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Feature {
|
||||||
|
const _Feature({
|
||||||
|
required this.name,
|
||||||
|
required this.icon,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String name;
|
||||||
|
final IconData icon;
|
||||||
|
final Future<void> Function(BuildContext, WidgetRef _) onTap;
|
||||||
|
}
|
125
mobile/lib/presentation/pages/dev/local_media_stat.page.dart
Normal file
125
mobile/lib/presentation/pages/dev/local_media_stat.page.dart
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/local_album.model.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
|
||||||
|
final _stats = [
|
||||||
|
_Stat(
|
||||||
|
name: 'Local Assets',
|
||||||
|
load: (db) => db.managers.localAssetEntity.count(),
|
||||||
|
),
|
||||||
|
_Stat(
|
||||||
|
name: 'Local Albums',
|
||||||
|
load: (db) => db.managers.localAlbumEntity.count(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class LocalMediaSummaryPage extends StatelessWidget {
|
||||||
|
const LocalMediaSummaryPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Local Media Summary')),
|
||||||
|
body: Consumer(
|
||||||
|
builder: (ctx, ref, __) {
|
||||||
|
final db = ref.watch(driftProvider);
|
||||||
|
final albumsFuture = ref.watch(localAlbumRepository).getAll();
|
||||||
|
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverList.builder(
|
||||||
|
itemBuilder: (_, index) {
|
||||||
|
final stat = _stats[index];
|
||||||
|
final countFuture = stat.load(db);
|
||||||
|
return _Summary(name: stat.name, countFuture: countFuture);
|
||||||
|
},
|
||||||
|
itemCount: _stats.length,
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
const Divider(),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 15),
|
||||||
|
child: Text(
|
||||||
|
"Album summary",
|
||||||
|
style: ctx.textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FutureBuilder(
|
||||||
|
future: albumsFuture,
|
||||||
|
initialData: <LocalAlbum>[],
|
||||||
|
builder: (_, snap) {
|
||||||
|
final albums = snap.data!;
|
||||||
|
if (albums.isEmpty) {
|
||||||
|
return const SliverToBoxAdapter(child: SizedBox.shrink());
|
||||||
|
}
|
||||||
|
|
||||||
|
albums.sortBy((a) => a.name);
|
||||||
|
return SliverList.builder(
|
||||||
|
itemBuilder: (_, index) {
|
||||||
|
final album = albums[index];
|
||||||
|
final countFuture = db.managers.localAlbumAssetEntity
|
||||||
|
.filter((f) => f.albumId.id.equals(album.id))
|
||||||
|
.count();
|
||||||
|
return _Summary(
|
||||||
|
name: album.name,
|
||||||
|
countFuture: countFuture,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: albums.length,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore: prefer-single-widget-per-file
|
||||||
|
class _Summary extends StatelessWidget {
|
||||||
|
final String name;
|
||||||
|
final Future<int> countFuture;
|
||||||
|
|
||||||
|
const _Summary({required this.name, required this.countFuture});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FutureBuilder<int>(
|
||||||
|
future: countFuture,
|
||||||
|
builder: (ctx, snapshot) {
|
||||||
|
final Widget subtitle;
|
||||||
|
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
subtitle = const CircularProgressIndicator();
|
||||||
|
} else if (snapshot.hasError) {
|
||||||
|
subtitle = const Icon(Icons.error_rounded);
|
||||||
|
} else {
|
||||||
|
subtitle = Text('${snapshot.data ?? 0}');
|
||||||
|
}
|
||||||
|
return ListTile(title: Text(name), trailing: subtitle);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Stat {
|
||||||
|
const _Stat({required this.name, required this.load});
|
||||||
|
|
||||||
|
final String name;
|
||||||
|
final Future<int> Function(Drift _) load;
|
||||||
|
}
|
13
mobile/lib/providers/infrastructure/album.provider.dart
Normal file
13
mobile/lib/providers/infrastructure/album.provider.dart
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.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/infrastructure/repositories/album_media.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
|
||||||
|
final albumMediaRepositoryProvider =
|
||||||
|
Provider<IAlbumMediaRepository>((ref) => const AlbumMediaRepository());
|
||||||
|
|
||||||
|
final localAlbumRepository = Provider<ILocalAlbumRepository>(
|
||||||
|
(ref) => DriftLocalAlbumRepository(ref.watch(driftProvider)),
|
||||||
|
);
|
@ -0,0 +1,4 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||||
|
|
||||||
|
final nativeSyncApiProvider = Provider<NativeSyncApi>((_) => NativeSyncApi());
|
@ -1,10 +1,13 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/device_sync.service.dart';
|
||||||
import 'package:immich_mobile/domain/services/sync_stream.service.dart';
|
import 'package:immich_mobile/domain/services/sync_stream.service.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
|
||||||
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/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 syncStreamServiceProvider = Provider(
|
final syncStreamServiceProvider = Provider(
|
||||||
(ref) => SyncStreamService(
|
(ref) => SyncStreamService(
|
||||||
@ -21,3 +24,11 @@ final syncApiRepositoryProvider = Provider(
|
|||||||
final syncStreamRepositoryProvider = Provider(
|
final syncStreamRepositoryProvider = Provider(
|
||||||
(ref) => DriftSyncStreamRepository(ref.watch(driftProvider)),
|
(ref) => DriftSyncStreamRepository(ref.watch(driftProvider)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final deviceSyncServiceProvider = Provider(
|
||||||
|
(ref) => DeviceSyncService(
|
||||||
|
albumMediaRepository: ref.watch(albumMediaRepositoryProvider),
|
||||||
|
localAlbumRepository: ref.watch(localAlbumRepository),
|
||||||
|
nativeSyncApi: ref.watch(nativeSyncApiProvider),
|
||||||
|
),
|
||||||
|
);
|
@ -63,6 +63,8 @@ import 'package:immich_mobile/pages/search/person_result.page.dart';
|
|||||||
import 'package:immich_mobile/pages/search/recently_taken.page.dart';
|
import 'package:immich_mobile/pages/search/recently_taken.page.dart';
|
||||||
import 'package:immich_mobile/pages/search/search.page.dart';
|
import 'package:immich_mobile/pages/search/search.page.dart';
|
||||||
import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
|
import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
|
||||||
|
import 'package:immich_mobile/presentation/pages/dev/feat_in_development.page.dart';
|
||||||
|
import 'package:immich_mobile/presentation/pages/dev/local_media_stat.page.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||||
import 'package:immich_mobile/routing/auth_guard.dart';
|
import 'package:immich_mobile/routing/auth_guard.dart';
|
||||||
@ -316,5 +318,13 @@ class AppRouter extends RootStackRouter {
|
|||||||
page: PinAuthRoute.page,
|
page: PinAuthRoute.page,
|
||||||
guards: [_authGuard, _duplicateGuard],
|
guards: [_authGuard, _duplicateGuard],
|
||||||
),
|
),
|
||||||
|
AutoRoute(
|
||||||
|
page: FeatInDevRoute.page,
|
||||||
|
guards: [_authGuard, _duplicateGuard],
|
||||||
|
),
|
||||||
|
AutoRoute(
|
||||||
|
page: LocalMediaSummaryRoute.page,
|
||||||
|
guards: [_authGuard, _duplicateGuard],
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// dart format width=80
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
@ -13,10 +14,7 @@ part of 'router.dart';
|
|||||||
/// [ActivitiesPage]
|
/// [ActivitiesPage]
|
||||||
class ActivitiesRoute extends PageRouteInfo<void> {
|
class ActivitiesRoute extends PageRouteInfo<void> {
|
||||||
const ActivitiesRoute({List<PageRouteInfo>? children})
|
const ActivitiesRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(ActivitiesRoute.name, initialChildren: children);
|
||||||
ActivitiesRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'ActivitiesRoute';
|
static const String name = 'ActivitiesRoute';
|
||||||
|
|
||||||
@ -132,10 +130,7 @@ class AlbumAssetSelectionRouteArgs {
|
|||||||
/// [AlbumOptionsPage]
|
/// [AlbumOptionsPage]
|
||||||
class AlbumOptionsRoute extends PageRouteInfo<void> {
|
class AlbumOptionsRoute extends PageRouteInfo<void> {
|
||||||
const AlbumOptionsRoute({List<PageRouteInfo>? children})
|
const AlbumOptionsRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(AlbumOptionsRoute.name, initialChildren: children);
|
||||||
AlbumOptionsRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'AlbumOptionsRoute';
|
static const String name = 'AlbumOptionsRoute';
|
||||||
|
|
||||||
@ -156,10 +151,7 @@ class AlbumPreviewRoute extends PageRouteInfo<AlbumPreviewRouteArgs> {
|
|||||||
List<PageRouteInfo>? children,
|
List<PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
AlbumPreviewRoute.name,
|
AlbumPreviewRoute.name,
|
||||||
args: AlbumPreviewRouteArgs(
|
args: AlbumPreviewRouteArgs(key: key, album: album),
|
||||||
key: key,
|
|
||||||
album: album,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -169,19 +161,13 @@ class AlbumPreviewRoute extends PageRouteInfo<AlbumPreviewRouteArgs> {
|
|||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args = data.argsAs<AlbumPreviewRouteArgs>();
|
final args = data.argsAs<AlbumPreviewRouteArgs>();
|
||||||
return AlbumPreviewPage(
|
return AlbumPreviewPage(key: args.key, album: args.album);
|
||||||
key: args.key,
|
|
||||||
album: args.album,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlbumPreviewRouteArgs {
|
class AlbumPreviewRouteArgs {
|
||||||
const AlbumPreviewRouteArgs({
|
const AlbumPreviewRouteArgs({this.key, required this.album});
|
||||||
this.key,
|
|
||||||
required this.album,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
final Key? key;
|
||||||
|
|
||||||
@ -203,10 +189,7 @@ class AlbumSharedUserSelectionRoute
|
|||||||
List<PageRouteInfo>? children,
|
List<PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
AlbumSharedUserSelectionRoute.name,
|
AlbumSharedUserSelectionRoute.name,
|
||||||
args: AlbumSharedUserSelectionRouteArgs(
|
args: AlbumSharedUserSelectionRouteArgs(key: key, assets: assets),
|
||||||
key: key,
|
|
||||||
assets: assets,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -216,19 +199,13 @@ class AlbumSharedUserSelectionRoute
|
|||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args = data.argsAs<AlbumSharedUserSelectionRouteArgs>();
|
final args = data.argsAs<AlbumSharedUserSelectionRouteArgs>();
|
||||||
return AlbumSharedUserSelectionPage(
|
return AlbumSharedUserSelectionPage(key: args.key, assets: args.assets);
|
||||||
key: args.key,
|
|
||||||
assets: args.assets,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlbumSharedUserSelectionRouteArgs {
|
class AlbumSharedUserSelectionRouteArgs {
|
||||||
const AlbumSharedUserSelectionRouteArgs({
|
const AlbumSharedUserSelectionRouteArgs({this.key, required this.assets});
|
||||||
this.key,
|
|
||||||
required this.assets,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
final Key? key;
|
||||||
|
|
||||||
@ -249,10 +226,7 @@ class AlbumViewerRoute extends PageRouteInfo<AlbumViewerRouteArgs> {
|
|||||||
List<PageRouteInfo>? children,
|
List<PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
AlbumViewerRoute.name,
|
AlbumViewerRoute.name,
|
||||||
args: AlbumViewerRouteArgs(
|
args: AlbumViewerRouteArgs(key: key, albumId: albumId),
|
||||||
key: key,
|
|
||||||
albumId: albumId,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -262,19 +236,13 @@ class AlbumViewerRoute extends PageRouteInfo<AlbumViewerRouteArgs> {
|
|||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args = data.argsAs<AlbumViewerRouteArgs>();
|
final args = data.argsAs<AlbumViewerRouteArgs>();
|
||||||
return AlbumViewerPage(
|
return AlbumViewerPage(key: args.key, albumId: args.albumId);
|
||||||
key: args.key,
|
|
||||||
albumId: args.albumId,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlbumViewerRouteArgs {
|
class AlbumViewerRouteArgs {
|
||||||
const AlbumViewerRouteArgs({
|
const AlbumViewerRouteArgs({this.key, required this.albumId});
|
||||||
this.key,
|
|
||||||
required this.albumId,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
final Key? key;
|
||||||
|
|
||||||
@ -290,10 +258,7 @@ class AlbumViewerRouteArgs {
|
|||||||
/// [AlbumsPage]
|
/// [AlbumsPage]
|
||||||
class AlbumsRoute extends PageRouteInfo<void> {
|
class AlbumsRoute extends PageRouteInfo<void> {
|
||||||
const AlbumsRoute({List<PageRouteInfo>? children})
|
const AlbumsRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(AlbumsRoute.name, initialChildren: children);
|
||||||
AlbumsRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'AlbumsRoute';
|
static const String name = 'AlbumsRoute';
|
||||||
|
|
||||||
@ -309,10 +274,7 @@ class AlbumsRoute extends PageRouteInfo<void> {
|
|||||||
/// [AllMotionPhotosPage]
|
/// [AllMotionPhotosPage]
|
||||||
class AllMotionPhotosRoute extends PageRouteInfo<void> {
|
class AllMotionPhotosRoute extends PageRouteInfo<void> {
|
||||||
const AllMotionPhotosRoute({List<PageRouteInfo>? children})
|
const AllMotionPhotosRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(AllMotionPhotosRoute.name, initialChildren: children);
|
||||||
AllMotionPhotosRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'AllMotionPhotosRoute';
|
static const String name = 'AllMotionPhotosRoute';
|
||||||
|
|
||||||
@ -328,10 +290,7 @@ class AllMotionPhotosRoute extends PageRouteInfo<void> {
|
|||||||
/// [AllPeoplePage]
|
/// [AllPeoplePage]
|
||||||
class AllPeopleRoute extends PageRouteInfo<void> {
|
class AllPeopleRoute extends PageRouteInfo<void> {
|
||||||
const AllPeopleRoute({List<PageRouteInfo>? children})
|
const AllPeopleRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(AllPeopleRoute.name, initialChildren: children);
|
||||||
AllPeopleRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'AllPeopleRoute';
|
static const String name = 'AllPeopleRoute';
|
||||||
|
|
||||||
@ -347,10 +306,7 @@ class AllPeopleRoute extends PageRouteInfo<void> {
|
|||||||
/// [AllPlacesPage]
|
/// [AllPlacesPage]
|
||||||
class AllPlacesRoute extends PageRouteInfo<void> {
|
class AllPlacesRoute extends PageRouteInfo<void> {
|
||||||
const AllPlacesRoute({List<PageRouteInfo>? children})
|
const AllPlacesRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(AllPlacesRoute.name, initialChildren: children);
|
||||||
AllPlacesRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'AllPlacesRoute';
|
static const String name = 'AllPlacesRoute';
|
||||||
|
|
||||||
@ -366,10 +322,7 @@ class AllPlacesRoute extends PageRouteInfo<void> {
|
|||||||
/// [AllVideosPage]
|
/// [AllVideosPage]
|
||||||
class AllVideosRoute extends PageRouteInfo<void> {
|
class AllVideosRoute extends PageRouteInfo<void> {
|
||||||
const AllVideosRoute({List<PageRouteInfo>? children})
|
const AllVideosRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(AllVideosRoute.name, initialChildren: children);
|
||||||
AllVideosRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'AllVideosRoute';
|
static const String name = 'AllVideosRoute';
|
||||||
|
|
||||||
@ -390,10 +343,7 @@ class AppLogDetailRoute extends PageRouteInfo<AppLogDetailRouteArgs> {
|
|||||||
List<PageRouteInfo>? children,
|
List<PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
AppLogDetailRoute.name,
|
AppLogDetailRoute.name,
|
||||||
args: AppLogDetailRouteArgs(
|
args: AppLogDetailRouteArgs(key: key, logMessage: logMessage),
|
||||||
key: key,
|
|
||||||
logMessage: logMessage,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -403,19 +353,13 @@ class AppLogDetailRoute extends PageRouteInfo<AppLogDetailRouteArgs> {
|
|||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args = data.argsAs<AppLogDetailRouteArgs>();
|
final args = data.argsAs<AppLogDetailRouteArgs>();
|
||||||
return AppLogDetailPage(
|
return AppLogDetailPage(key: args.key, logMessage: args.logMessage);
|
||||||
key: args.key,
|
|
||||||
logMessage: args.logMessage,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppLogDetailRouteArgs {
|
class AppLogDetailRouteArgs {
|
||||||
const AppLogDetailRouteArgs({
|
const AppLogDetailRouteArgs({this.key, required this.logMessage});
|
||||||
this.key,
|
|
||||||
required this.logMessage,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
final Key? key;
|
||||||
|
|
||||||
@ -431,10 +375,7 @@ class AppLogDetailRouteArgs {
|
|||||||
/// [AppLogPage]
|
/// [AppLogPage]
|
||||||
class AppLogRoute extends PageRouteInfo<void> {
|
class AppLogRoute extends PageRouteInfo<void> {
|
||||||
const AppLogRoute({List<PageRouteInfo>? children})
|
const AppLogRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(AppLogRoute.name, initialChildren: children);
|
||||||
AppLogRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'AppLogRoute';
|
static const String name = 'AppLogRoute';
|
||||||
|
|
||||||
@ -450,10 +391,7 @@ class AppLogRoute extends PageRouteInfo<void> {
|
|||||||
/// [ArchivePage]
|
/// [ArchivePage]
|
||||||
class ArchiveRoute extends PageRouteInfo<void> {
|
class ArchiveRoute extends PageRouteInfo<void> {
|
||||||
const ArchiveRoute({List<PageRouteInfo>? children})
|
const ArchiveRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(ArchiveRoute.name, initialChildren: children);
|
||||||
ArchiveRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'ArchiveRoute';
|
static const String name = 'ArchiveRoute';
|
||||||
|
|
||||||
@ -469,10 +407,7 @@ class ArchiveRoute extends PageRouteInfo<void> {
|
|||||||
/// [BackupAlbumSelectionPage]
|
/// [BackupAlbumSelectionPage]
|
||||||
class BackupAlbumSelectionRoute extends PageRouteInfo<void> {
|
class BackupAlbumSelectionRoute extends PageRouteInfo<void> {
|
||||||
const BackupAlbumSelectionRoute({List<PageRouteInfo>? children})
|
const BackupAlbumSelectionRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(BackupAlbumSelectionRoute.name, initialChildren: children);
|
||||||
BackupAlbumSelectionRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'BackupAlbumSelectionRoute';
|
static const String name = 'BackupAlbumSelectionRoute';
|
||||||
|
|
||||||
@ -488,10 +423,7 @@ class BackupAlbumSelectionRoute extends PageRouteInfo<void> {
|
|||||||
/// [BackupControllerPage]
|
/// [BackupControllerPage]
|
||||||
class BackupControllerRoute extends PageRouteInfo<void> {
|
class BackupControllerRoute extends PageRouteInfo<void> {
|
||||||
const BackupControllerRoute({List<PageRouteInfo>? children})
|
const BackupControllerRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(BackupControllerRoute.name, initialChildren: children);
|
||||||
BackupControllerRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'BackupControllerRoute';
|
static const String name = 'BackupControllerRoute';
|
||||||
|
|
||||||
@ -507,10 +439,7 @@ class BackupControllerRoute extends PageRouteInfo<void> {
|
|||||||
/// [BackupOptionsPage]
|
/// [BackupOptionsPage]
|
||||||
class BackupOptionsRoute extends PageRouteInfo<void> {
|
class BackupOptionsRoute extends PageRouteInfo<void> {
|
||||||
const BackupOptionsRoute({List<PageRouteInfo>? children})
|
const BackupOptionsRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(BackupOptionsRoute.name, initialChildren: children);
|
||||||
BackupOptionsRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'BackupOptionsRoute';
|
static const String name = 'BackupOptionsRoute';
|
||||||
|
|
||||||
@ -526,10 +455,7 @@ class BackupOptionsRoute extends PageRouteInfo<void> {
|
|||||||
/// [ChangePasswordPage]
|
/// [ChangePasswordPage]
|
||||||
class ChangePasswordRoute extends PageRouteInfo<void> {
|
class ChangePasswordRoute extends PageRouteInfo<void> {
|
||||||
const ChangePasswordRoute({List<PageRouteInfo>? children})
|
const ChangePasswordRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(ChangePasswordRoute.name, initialChildren: children);
|
||||||
ChangePasswordRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'ChangePasswordRoute';
|
static const String name = 'ChangePasswordRoute';
|
||||||
|
|
||||||
@ -550,10 +476,7 @@ class CreateAlbumRoute extends PageRouteInfo<CreateAlbumRouteArgs> {
|
|||||||
List<PageRouteInfo>? children,
|
List<PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
CreateAlbumRoute.name,
|
CreateAlbumRoute.name,
|
||||||
args: CreateAlbumRouteArgs(
|
args: CreateAlbumRouteArgs(key: key, assets: assets),
|
||||||
key: key,
|
|
||||||
assets: assets,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -563,20 +486,15 @@ class CreateAlbumRoute extends PageRouteInfo<CreateAlbumRouteArgs> {
|
|||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args = data.argsAs<CreateAlbumRouteArgs>(
|
final args = data.argsAs<CreateAlbumRouteArgs>(
|
||||||
orElse: () => const CreateAlbumRouteArgs());
|
orElse: () => const CreateAlbumRouteArgs(),
|
||||||
return CreateAlbumPage(
|
|
||||||
key: args.key,
|
|
||||||
assets: args.assets,
|
|
||||||
);
|
);
|
||||||
|
return CreateAlbumPage(key: args.key, assets: args.assets);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreateAlbumRouteArgs {
|
class CreateAlbumRouteArgs {
|
||||||
const CreateAlbumRouteArgs({
|
const CreateAlbumRouteArgs({this.key, this.assets});
|
||||||
this.key,
|
|
||||||
this.assets,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
final Key? key;
|
||||||
|
|
||||||
@ -598,11 +516,7 @@ class CropImageRoute extends PageRouteInfo<CropImageRouteArgs> {
|
|||||||
List<PageRouteInfo>? children,
|
List<PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
CropImageRoute.name,
|
CropImageRoute.name,
|
||||||
args: CropImageRouteArgs(
|
args: CropImageRouteArgs(key: key, image: image, asset: asset),
|
||||||
key: key,
|
|
||||||
image: image,
|
|
||||||
asset: asset,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -612,11 +526,7 @@ class CropImageRoute extends PageRouteInfo<CropImageRouteArgs> {
|
|||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args = data.argsAs<CropImageRouteArgs>();
|
final args = data.argsAs<CropImageRouteArgs>();
|
||||||
return CropImagePage(
|
return CropImagePage(key: args.key, image: args.image, asset: args.asset);
|
||||||
key: args.key,
|
|
||||||
image: args.image,
|
|
||||||
asset: args.asset,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -702,10 +612,7 @@ class EditImageRouteArgs {
|
|||||||
/// [FailedBackupStatusPage]
|
/// [FailedBackupStatusPage]
|
||||||
class FailedBackupStatusRoute extends PageRouteInfo<void> {
|
class FailedBackupStatusRoute extends PageRouteInfo<void> {
|
||||||
const FailedBackupStatusRoute({List<PageRouteInfo>? children})
|
const FailedBackupStatusRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(FailedBackupStatusRoute.name, initialChildren: children);
|
||||||
FailedBackupStatusRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'FailedBackupStatusRoute';
|
static const String name = 'FailedBackupStatusRoute';
|
||||||
|
|
||||||
@ -721,10 +628,7 @@ class FailedBackupStatusRoute extends PageRouteInfo<void> {
|
|||||||
/// [FavoritesPage]
|
/// [FavoritesPage]
|
||||||
class FavoritesRoute extends PageRouteInfo<void> {
|
class FavoritesRoute extends PageRouteInfo<void> {
|
||||||
const FavoritesRoute({List<PageRouteInfo>? children})
|
const FavoritesRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(FavoritesRoute.name, initialChildren: children);
|
||||||
FavoritesRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'FavoritesRoute';
|
static const String name = 'FavoritesRoute';
|
||||||
|
|
||||||
@ -736,6 +640,22 @@ class FavoritesRoute extends PageRouteInfo<void> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [FeatInDevPage]
|
||||||
|
class FeatInDevRoute extends PageRouteInfo<void> {
|
||||||
|
const FeatInDevRoute({List<PageRouteInfo>? children})
|
||||||
|
: super(FeatInDevRoute.name, initialChildren: children);
|
||||||
|
|
||||||
|
static const String name = 'FeatInDevRoute';
|
||||||
|
|
||||||
|
static PageInfo page = PageInfo(
|
||||||
|
name,
|
||||||
|
builder: (data) {
|
||||||
|
return const FeatInDevPage();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [FilterImagePage]
|
/// [FilterImagePage]
|
||||||
class FilterImageRoute extends PageRouteInfo<FilterImageRouteArgs> {
|
class FilterImageRoute extends PageRouteInfo<FilterImageRouteArgs> {
|
||||||
@ -746,11 +666,7 @@ class FilterImageRoute extends PageRouteInfo<FilterImageRouteArgs> {
|
|||||||
List<PageRouteInfo>? children,
|
List<PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
FilterImageRoute.name,
|
FilterImageRoute.name,
|
||||||
args: FilterImageRouteArgs(
|
args: FilterImageRouteArgs(key: key, image: image, asset: asset),
|
||||||
key: key,
|
|
||||||
image: image,
|
|
||||||
asset: asset,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -797,10 +713,7 @@ class FolderRoute extends PageRouteInfo<FolderRouteArgs> {
|
|||||||
List<PageRouteInfo>? children,
|
List<PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
FolderRoute.name,
|
FolderRoute.name,
|
||||||
args: FolderRouteArgs(
|
args: FolderRouteArgs(key: key, folder: folder),
|
||||||
key: key,
|
|
||||||
folder: folder,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -809,21 +722,16 @@ class FolderRoute extends PageRouteInfo<FolderRouteArgs> {
|
|||||||
static PageInfo page = PageInfo(
|
static PageInfo page = PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args =
|
final args = data.argsAs<FolderRouteArgs>(
|
||||||
data.argsAs<FolderRouteArgs>(orElse: () => const FolderRouteArgs());
|
orElse: () => const FolderRouteArgs(),
|
||||||
return FolderPage(
|
|
||||||
key: args.key,
|
|
||||||
folder: args.folder,
|
|
||||||
);
|
);
|
||||||
|
return FolderPage(key: args.key, folder: args.folder);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class FolderRouteArgs {
|
class FolderRouteArgs {
|
||||||
const FolderRouteArgs({
|
const FolderRouteArgs({this.key, this.folder});
|
||||||
this.key,
|
|
||||||
this.folder,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
final Key? key;
|
||||||
|
|
||||||
@ -903,10 +811,7 @@ class GalleryViewerRouteArgs {
|
|||||||
/// [HeaderSettingsPage]
|
/// [HeaderSettingsPage]
|
||||||
class HeaderSettingsRoute extends PageRouteInfo<void> {
|
class HeaderSettingsRoute extends PageRouteInfo<void> {
|
||||||
const HeaderSettingsRoute({List<PageRouteInfo>? children})
|
const HeaderSettingsRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(HeaderSettingsRoute.name, initialChildren: children);
|
||||||
HeaderSettingsRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'HeaderSettingsRoute';
|
static const String name = 'HeaderSettingsRoute';
|
||||||
|
|
||||||
@ -922,10 +827,7 @@ class HeaderSettingsRoute extends PageRouteInfo<void> {
|
|||||||
/// [LibraryPage]
|
/// [LibraryPage]
|
||||||
class LibraryRoute extends PageRouteInfo<void> {
|
class LibraryRoute extends PageRouteInfo<void> {
|
||||||
const LibraryRoute({List<PageRouteInfo>? children})
|
const LibraryRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(LibraryRoute.name, initialChildren: children);
|
||||||
LibraryRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'LibraryRoute';
|
static const String name = 'LibraryRoute';
|
||||||
|
|
||||||
@ -941,10 +843,7 @@ class LibraryRoute extends PageRouteInfo<void> {
|
|||||||
/// [LocalAlbumsPage]
|
/// [LocalAlbumsPage]
|
||||||
class LocalAlbumsRoute extends PageRouteInfo<void> {
|
class LocalAlbumsRoute extends PageRouteInfo<void> {
|
||||||
const LocalAlbumsRoute({List<PageRouteInfo>? children})
|
const LocalAlbumsRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(LocalAlbumsRoute.name, initialChildren: children);
|
||||||
LocalAlbumsRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'LocalAlbumsRoute';
|
static const String name = 'LocalAlbumsRoute';
|
||||||
|
|
||||||
@ -956,14 +855,27 @@ class LocalAlbumsRoute extends PageRouteInfo<void> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [LocalMediaSummaryPage]
|
||||||
|
class LocalMediaSummaryRoute extends PageRouteInfo<void> {
|
||||||
|
const LocalMediaSummaryRoute({List<PageRouteInfo>? children})
|
||||||
|
: super(LocalMediaSummaryRoute.name, initialChildren: children);
|
||||||
|
|
||||||
|
static const String name = 'LocalMediaSummaryRoute';
|
||||||
|
|
||||||
|
static PageInfo page = PageInfo(
|
||||||
|
name,
|
||||||
|
builder: (data) {
|
||||||
|
return const LocalMediaSummaryPage();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [LockedPage]
|
/// [LockedPage]
|
||||||
class LockedRoute extends PageRouteInfo<void> {
|
class LockedRoute extends PageRouteInfo<void> {
|
||||||
const LockedRoute({List<PageRouteInfo>? children})
|
const LockedRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(LockedRoute.name, initialChildren: children);
|
||||||
LockedRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'LockedRoute';
|
static const String name = 'LockedRoute';
|
||||||
|
|
||||||
@ -979,10 +891,7 @@ class LockedRoute extends PageRouteInfo<void> {
|
|||||||
/// [LoginPage]
|
/// [LoginPage]
|
||||||
class LoginRoute extends PageRouteInfo<void> {
|
class LoginRoute extends PageRouteInfo<void> {
|
||||||
const LoginRoute({List<PageRouteInfo>? children})
|
const LoginRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(LoginRoute.name, initialChildren: children);
|
||||||
LoginRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'LoginRoute';
|
static const String name = 'LoginRoute';
|
||||||
|
|
||||||
@ -1016,7 +925,8 @@ class MapLocationPickerRoute extends PageRouteInfo<MapLocationPickerRouteArgs> {
|
|||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args = data.argsAs<MapLocationPickerRouteArgs>(
|
final args = data.argsAs<MapLocationPickerRouteArgs>(
|
||||||
orElse: () => const MapLocationPickerRouteArgs());
|
orElse: () => const MapLocationPickerRouteArgs(),
|
||||||
|
);
|
||||||
return MapLocationPickerPage(
|
return MapLocationPickerPage(
|
||||||
key: args.key,
|
key: args.key,
|
||||||
initialLatLng: args.initialLatLng,
|
initialLatLng: args.initialLatLng,
|
||||||
@ -1044,16 +954,10 @@ class MapLocationPickerRouteArgs {
|
|||||||
/// generated route for
|
/// generated route for
|
||||||
/// [MapPage]
|
/// [MapPage]
|
||||||
class MapRoute extends PageRouteInfo<MapRouteArgs> {
|
class MapRoute extends PageRouteInfo<MapRouteArgs> {
|
||||||
MapRoute({
|
MapRoute({Key? key, LatLng? initialLocation, List<PageRouteInfo>? children})
|
||||||
Key? key,
|
: super(
|
||||||
LatLng? initialLocation,
|
|
||||||
List<PageRouteInfo>? children,
|
|
||||||
}) : super(
|
|
||||||
MapRoute.name,
|
MapRoute.name,
|
||||||
args: MapRouteArgs(
|
args: MapRouteArgs(key: key, initialLocation: initialLocation),
|
||||||
key: key,
|
|
||||||
initialLocation: initialLocation,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1062,21 +966,16 @@ class MapRoute extends PageRouteInfo<MapRouteArgs> {
|
|||||||
static PageInfo page = PageInfo(
|
static PageInfo page = PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args =
|
final args = data.argsAs<MapRouteArgs>(
|
||||||
data.argsAs<MapRouteArgs>(orElse: () => const MapRouteArgs());
|
orElse: () => const MapRouteArgs(),
|
||||||
return MapPage(
|
|
||||||
key: args.key,
|
|
||||||
initialLocation: args.initialLocation,
|
|
||||||
);
|
);
|
||||||
|
return MapPage(key: args.key, initialLocation: args.initialLocation);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MapRouteArgs {
|
class MapRouteArgs {
|
||||||
const MapRouteArgs({
|
const MapRouteArgs({this.key, this.initialLocation});
|
||||||
this.key,
|
|
||||||
this.initialLocation,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
final Key? key;
|
||||||
|
|
||||||
@ -1213,10 +1112,7 @@ class PartnerDetailRoute extends PageRouteInfo<PartnerDetailRouteArgs> {
|
|||||||
List<PageRouteInfo>? children,
|
List<PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
PartnerDetailRoute.name,
|
PartnerDetailRoute.name,
|
||||||
args: PartnerDetailRouteArgs(
|
args: PartnerDetailRouteArgs(key: key, partner: partner),
|
||||||
key: key,
|
|
||||||
partner: partner,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1226,19 +1122,13 @@ class PartnerDetailRoute extends PageRouteInfo<PartnerDetailRouteArgs> {
|
|||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args = data.argsAs<PartnerDetailRouteArgs>();
|
final args = data.argsAs<PartnerDetailRouteArgs>();
|
||||||
return PartnerDetailPage(
|
return PartnerDetailPage(key: args.key, partner: args.partner);
|
||||||
key: args.key,
|
|
||||||
partner: args.partner,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class PartnerDetailRouteArgs {
|
class PartnerDetailRouteArgs {
|
||||||
const PartnerDetailRouteArgs({
|
const PartnerDetailRouteArgs({this.key, required this.partner});
|
||||||
this.key,
|
|
||||||
required this.partner,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
final Key? key;
|
||||||
|
|
||||||
@ -1254,10 +1144,7 @@ class PartnerDetailRouteArgs {
|
|||||||
/// [PartnerPage]
|
/// [PartnerPage]
|
||||||
class PartnerRoute extends PageRouteInfo<void> {
|
class PartnerRoute extends PageRouteInfo<void> {
|
||||||
const PartnerRoute({List<PageRouteInfo>? children})
|
const PartnerRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(PartnerRoute.name, initialChildren: children);
|
||||||
PartnerRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'PartnerRoute';
|
static const String name = 'PartnerRoute';
|
||||||
|
|
||||||
@ -1273,10 +1160,7 @@ class PartnerRoute extends PageRouteInfo<void> {
|
|||||||
/// [PeopleCollectionPage]
|
/// [PeopleCollectionPage]
|
||||||
class PeopleCollectionRoute extends PageRouteInfo<void> {
|
class PeopleCollectionRoute extends PageRouteInfo<void> {
|
||||||
const PeopleCollectionRoute({List<PageRouteInfo>? children})
|
const PeopleCollectionRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(PeopleCollectionRoute.name, initialChildren: children);
|
||||||
PeopleCollectionRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'PeopleCollectionRoute';
|
static const String name = 'PeopleCollectionRoute';
|
||||||
|
|
||||||
@ -1292,10 +1176,7 @@ class PeopleCollectionRoute extends PageRouteInfo<void> {
|
|||||||
/// [PermissionOnboardingPage]
|
/// [PermissionOnboardingPage]
|
||||||
class PermissionOnboardingRoute extends PageRouteInfo<void> {
|
class PermissionOnboardingRoute extends PageRouteInfo<void> {
|
||||||
const PermissionOnboardingRoute({List<PageRouteInfo>? children})
|
const PermissionOnboardingRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(PermissionOnboardingRoute.name, initialChildren: children);
|
||||||
PermissionOnboardingRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'PermissionOnboardingRoute';
|
static const String name = 'PermissionOnboardingRoute';
|
||||||
|
|
||||||
@ -1363,10 +1244,7 @@ class PersonResultRouteArgs {
|
|||||||
/// [PhotosPage]
|
/// [PhotosPage]
|
||||||
class PhotosRoute extends PageRouteInfo<void> {
|
class PhotosRoute extends PageRouteInfo<void> {
|
||||||
const PhotosRoute({List<PageRouteInfo>? children})
|
const PhotosRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(PhotosRoute.name, initialChildren: children);
|
||||||
PhotosRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'PhotosRoute';
|
static const String name = 'PhotosRoute';
|
||||||
|
|
||||||
@ -1387,10 +1265,7 @@ class PinAuthRoute extends PageRouteInfo<PinAuthRouteArgs> {
|
|||||||
List<PageRouteInfo>? children,
|
List<PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
PinAuthRoute.name,
|
PinAuthRoute.name,
|
||||||
args: PinAuthRouteArgs(
|
args: PinAuthRouteArgs(key: key, createPinCode: createPinCode),
|
||||||
key: key,
|
|
||||||
createPinCode: createPinCode,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1399,21 +1274,16 @@ class PinAuthRoute extends PageRouteInfo<PinAuthRouteArgs> {
|
|||||||
static PageInfo page = PageInfo(
|
static PageInfo page = PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args =
|
final args = data.argsAs<PinAuthRouteArgs>(
|
||||||
data.argsAs<PinAuthRouteArgs>(orElse: () => const PinAuthRouteArgs());
|
orElse: () => const PinAuthRouteArgs(),
|
||||||
return PinAuthPage(
|
|
||||||
key: args.key,
|
|
||||||
createPinCode: args.createPinCode,
|
|
||||||
);
|
);
|
||||||
|
return PinAuthPage(key: args.key, createPinCode: args.createPinCode);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class PinAuthRouteArgs {
|
class PinAuthRouteArgs {
|
||||||
const PinAuthRouteArgs({
|
const PinAuthRouteArgs({this.key, this.createPinCode = false});
|
||||||
this.key,
|
|
||||||
this.createPinCode = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
final Key? key;
|
||||||
|
|
||||||
@ -1447,7 +1317,8 @@ class PlacesCollectionRoute extends PageRouteInfo<PlacesCollectionRouteArgs> {
|
|||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args = data.argsAs<PlacesCollectionRouteArgs>(
|
final args = data.argsAs<PlacesCollectionRouteArgs>(
|
||||||
orElse: () => const PlacesCollectionRouteArgs());
|
orElse: () => const PlacesCollectionRouteArgs(),
|
||||||
|
);
|
||||||
return PlacesCollectionPage(
|
return PlacesCollectionPage(
|
||||||
key: args.key,
|
key: args.key,
|
||||||
currentLocation: args.currentLocation,
|
currentLocation: args.currentLocation,
|
||||||
@ -1457,10 +1328,7 @@ class PlacesCollectionRoute extends PageRouteInfo<PlacesCollectionRouteArgs> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class PlacesCollectionRouteArgs {
|
class PlacesCollectionRouteArgs {
|
||||||
const PlacesCollectionRouteArgs({
|
const PlacesCollectionRouteArgs({this.key, this.currentLocation});
|
||||||
this.key,
|
|
||||||
this.currentLocation,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
final Key? key;
|
||||||
|
|
||||||
@ -1476,10 +1344,7 @@ class PlacesCollectionRouteArgs {
|
|||||||
/// [RecentlyTakenPage]
|
/// [RecentlyTakenPage]
|
||||||
class RecentlyTakenRoute extends PageRouteInfo<void> {
|
class RecentlyTakenRoute extends PageRouteInfo<void> {
|
||||||
const RecentlyTakenRoute({List<PageRouteInfo>? children})
|
const RecentlyTakenRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(RecentlyTakenRoute.name, initialChildren: children);
|
||||||
RecentlyTakenRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'RecentlyTakenRoute';
|
static const String name = 'RecentlyTakenRoute';
|
||||||
|
|
||||||
@ -1500,10 +1365,7 @@ class SearchRoute extends PageRouteInfo<SearchRouteArgs> {
|
|||||||
List<PageRouteInfo>? children,
|
List<PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
SearchRoute.name,
|
SearchRoute.name,
|
||||||
args: SearchRouteArgs(
|
args: SearchRouteArgs(key: key, prefilter: prefilter),
|
||||||
key: key,
|
|
||||||
prefilter: prefilter,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1512,21 +1374,16 @@ class SearchRoute extends PageRouteInfo<SearchRouteArgs> {
|
|||||||
static PageInfo page = PageInfo(
|
static PageInfo page = PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args =
|
final args = data.argsAs<SearchRouteArgs>(
|
||||||
data.argsAs<SearchRouteArgs>(orElse: () => const SearchRouteArgs());
|
orElse: () => const SearchRouteArgs(),
|
||||||
return SearchPage(
|
|
||||||
key: args.key,
|
|
||||||
prefilter: args.prefilter,
|
|
||||||
);
|
);
|
||||||
|
return SearchPage(key: args.key, prefilter: args.prefilter);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SearchRouteArgs {
|
class SearchRouteArgs {
|
||||||
const SearchRouteArgs({
|
const SearchRouteArgs({this.key, this.prefilter});
|
||||||
this.key,
|
|
||||||
this.prefilter,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
final Key? key;
|
||||||
|
|
||||||
@ -1542,10 +1399,7 @@ class SearchRouteArgs {
|
|||||||
/// [SettingsPage]
|
/// [SettingsPage]
|
||||||
class SettingsRoute extends PageRouteInfo<void> {
|
class SettingsRoute extends PageRouteInfo<void> {
|
||||||
const SettingsRoute({List<PageRouteInfo>? children})
|
const SettingsRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(SettingsRoute.name, initialChildren: children);
|
||||||
SettingsRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'SettingsRoute';
|
static const String name = 'SettingsRoute';
|
||||||
|
|
||||||
@ -1566,10 +1420,7 @@ class SettingsSubRoute extends PageRouteInfo<SettingsSubRouteArgs> {
|
|||||||
List<PageRouteInfo>? children,
|
List<PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
SettingsSubRoute.name,
|
SettingsSubRoute.name,
|
||||||
args: SettingsSubRouteArgs(
|
args: SettingsSubRouteArgs(section: section, key: key),
|
||||||
section: section,
|
|
||||||
key: key,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1579,19 +1430,13 @@ class SettingsSubRoute extends PageRouteInfo<SettingsSubRouteArgs> {
|
|||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args = data.argsAs<SettingsSubRouteArgs>();
|
final args = data.argsAs<SettingsSubRouteArgs>();
|
||||||
return SettingsSubPage(
|
return SettingsSubPage(args.section, key: args.key);
|
||||||
args.section,
|
|
||||||
key: args.key,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsSubRouteArgs {
|
class SettingsSubRouteArgs {
|
||||||
const SettingsSubRouteArgs({
|
const SettingsSubRouteArgs({required this.section, this.key});
|
||||||
required this.section,
|
|
||||||
this.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final SettingSection section;
|
final SettingSection section;
|
||||||
|
|
||||||
@ -1612,10 +1457,7 @@ class ShareIntentRoute extends PageRouteInfo<ShareIntentRouteArgs> {
|
|||||||
List<PageRouteInfo>? children,
|
List<PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
ShareIntentRoute.name,
|
ShareIntentRoute.name,
|
||||||
args: ShareIntentRouteArgs(
|
args: ShareIntentRouteArgs(key: key, attachments: attachments),
|
||||||
key: key,
|
|
||||||
attachments: attachments,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1625,19 +1467,13 @@ class ShareIntentRoute extends PageRouteInfo<ShareIntentRouteArgs> {
|
|||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args = data.argsAs<ShareIntentRouteArgs>();
|
final args = data.argsAs<ShareIntentRouteArgs>();
|
||||||
return ShareIntentPage(
|
return ShareIntentPage(key: args.key, attachments: args.attachments);
|
||||||
key: args.key,
|
|
||||||
attachments: args.attachments,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ShareIntentRouteArgs {
|
class ShareIntentRouteArgs {
|
||||||
const ShareIntentRouteArgs({
|
const ShareIntentRouteArgs({this.key, required this.attachments});
|
||||||
this.key,
|
|
||||||
required this.attachments,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
final Key? key;
|
||||||
|
|
||||||
@ -1675,7 +1511,8 @@ class SharedLinkEditRoute extends PageRouteInfo<SharedLinkEditRouteArgs> {
|
|||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final args = data.argsAs<SharedLinkEditRouteArgs>(
|
final args = data.argsAs<SharedLinkEditRouteArgs>(
|
||||||
orElse: () => const SharedLinkEditRouteArgs());
|
orElse: () => const SharedLinkEditRouteArgs(),
|
||||||
|
);
|
||||||
return SharedLinkEditPage(
|
return SharedLinkEditPage(
|
||||||
key: args.key,
|
key: args.key,
|
||||||
existingLink: args.existingLink,
|
existingLink: args.existingLink,
|
||||||
@ -1712,10 +1549,7 @@ class SharedLinkEditRouteArgs {
|
|||||||
/// [SharedLinkPage]
|
/// [SharedLinkPage]
|
||||||
class SharedLinkRoute extends PageRouteInfo<void> {
|
class SharedLinkRoute extends PageRouteInfo<void> {
|
||||||
const SharedLinkRoute({List<PageRouteInfo>? children})
|
const SharedLinkRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(SharedLinkRoute.name, initialChildren: children);
|
||||||
SharedLinkRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'SharedLinkRoute';
|
static const String name = 'SharedLinkRoute';
|
||||||
|
|
||||||
@ -1731,10 +1565,7 @@ class SharedLinkRoute extends PageRouteInfo<void> {
|
|||||||
/// [SplashScreenPage]
|
/// [SplashScreenPage]
|
||||||
class SplashScreenRoute extends PageRouteInfo<void> {
|
class SplashScreenRoute extends PageRouteInfo<void> {
|
||||||
const SplashScreenRoute({List<PageRouteInfo>? children})
|
const SplashScreenRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(SplashScreenRoute.name, initialChildren: children);
|
||||||
SplashScreenRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'SplashScreenRoute';
|
static const String name = 'SplashScreenRoute';
|
||||||
|
|
||||||
@ -1750,10 +1581,7 @@ class SplashScreenRoute extends PageRouteInfo<void> {
|
|||||||
/// [TabControllerPage]
|
/// [TabControllerPage]
|
||||||
class TabControllerRoute extends PageRouteInfo<void> {
|
class TabControllerRoute extends PageRouteInfo<void> {
|
||||||
const TabControllerRoute({List<PageRouteInfo>? children})
|
const TabControllerRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(TabControllerRoute.name, initialChildren: children);
|
||||||
TabControllerRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'TabControllerRoute';
|
static const String name = 'TabControllerRoute';
|
||||||
|
|
||||||
@ -1769,10 +1597,7 @@ class TabControllerRoute extends PageRouteInfo<void> {
|
|||||||
/// [TrashPage]
|
/// [TrashPage]
|
||||||
class TrashRoute extends PageRouteInfo<void> {
|
class TrashRoute extends PageRouteInfo<void> {
|
||||||
const TrashRoute({List<PageRouteInfo>? children})
|
const TrashRoute({List<PageRouteInfo>? children})
|
||||||
: super(
|
: super(TrashRoute.name, initialChildren: children);
|
||||||
TrashRoute.name,
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'TrashRoute';
|
static const String name = 'TrashRoute';
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
||||||
import 'package:immich_mobile/models/server_info/server_info.model.dart';
|
import 'package:immich_mobile/models/server_info/server_info.model.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
@ -180,10 +179,10 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||||||
child: action,
|
child: action,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (kDebugMode)
|
if (kDebugMode || kProfileMode)
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => ref.read(backgroundSyncProvider).sync(),
|
icon: const Icon(Icons.science_rounded),
|
||||||
icon: const Icon(Icons.sync),
|
onPressed: () => context.pushRoute(const FeatInDevRoute()),
|
||||||
),
|
),
|
||||||
if (showUploadButton)
|
if (showUploadButton)
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
.PHONY: build watch create_app_icon create_splash build_release_android
|
.PHONY: build watch create_app_icon create_splash build_release_android pigeon
|
||||||
|
|
||||||
build:
|
build:
|
||||||
dart run build_runner build --delete-conflicting-outputs
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
|
pigeon:
|
||||||
|
dart run pigeon --input pigeon/native_sync_api.dart
|
||||||
|
|
||||||
watch:
|
watch:
|
||||||
dart run build_runner watch --delete-conflicting-outputs
|
dart run build_runner watch --delete-conflicting-outputs
|
||||||
|
|
||||||
|
83
mobile/pigeon/native_sync_api.dart
Normal file
83
mobile/pigeon/native_sync_api.dart
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import 'package:pigeon/pigeon.dart';
|
||||||
|
|
||||||
|
@ConfigurePigeon(
|
||||||
|
PigeonOptions(
|
||||||
|
dartOut: 'lib/platform/native_sync_api.g.dart',
|
||||||
|
swiftOut: 'ios/Runner/Sync/Messages.g.swift',
|
||||||
|
swiftOptions: SwiftOptions(),
|
||||||
|
kotlinOut:
|
||||||
|
'android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt',
|
||||||
|
kotlinOptions: KotlinOptions(package: 'app.alextran.immich.sync'),
|
||||||
|
dartOptions: DartOptions(),
|
||||||
|
dartPackageName: 'immich_mobile',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
class ImAsset {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
// Follows AssetType enum from base_asset.model.dart
|
||||||
|
final int type;
|
||||||
|
// Seconds since epoch
|
||||||
|
final int? createdAt;
|
||||||
|
final int? updatedAt;
|
||||||
|
final int durationInSeconds;
|
||||||
|
|
||||||
|
const ImAsset({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.type,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
this.durationInSeconds = 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImAlbum {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
// Seconds since epoch
|
||||||
|
final int? updatedAt;
|
||||||
|
final bool isCloud;
|
||||||
|
final int assetCount;
|
||||||
|
|
||||||
|
const ImAlbum({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
this.updatedAt,
|
||||||
|
this.isCloud = false,
|
||||||
|
this.assetCount = 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class SyncDelta {
|
||||||
|
final bool hasChanges;
|
||||||
|
final List<ImAsset> updates;
|
||||||
|
final List<String> deletes;
|
||||||
|
// Asset -> Album mapping
|
||||||
|
final Map<String, List<String>> assetAlbums;
|
||||||
|
|
||||||
|
const SyncDelta({
|
||||||
|
this.hasChanges = false,
|
||||||
|
this.updates = const [],
|
||||||
|
this.deletes = const [],
|
||||||
|
this.assetAlbums = const {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostApi()
|
||||||
|
abstract class NativeSyncApi {
|
||||||
|
bool shouldFullSync();
|
||||||
|
|
||||||
|
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||||
|
SyncDelta getMediaChanges();
|
||||||
|
|
||||||
|
void checkpointSync();
|
||||||
|
|
||||||
|
void clearSyncCheckpoint();
|
||||||
|
|
||||||
|
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||||
|
List<String> getAssetIdsForAlbum(String albumId);
|
||||||
|
|
||||||
|
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||||
|
List<ImAlbum> getAlbums();
|
||||||
|
}
|
@ -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: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.8"
|
version: "3.1.0"
|
||||||
dartx:
|
dartx:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -723,10 +726,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:
|
||||||
@ -971,10 +974,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
|
||||||
@ -1072,14 +1076,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:
|
||||||
@ -1121,7 +1117,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
|
||||||
@ -1352,6 +1348,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: a093af76026160bb5ff6eb98e3e678a301ffd1001ac0d90be558bc133a0c73f5
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "25.3.2"
|
||||||
pinput:
|
pinput:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1361,7 +1365,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.1"
|
version: "5.0.1"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||||
@ -1444,10 +1448,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:
|
||||||
@ -1460,18 +1464,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:
|
||||||
@ -1633,10 +1637,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:
|
||||||
|
@ -32,6 +32,7 @@ dependencies:
|
|||||||
flutter_displaymode: ^0.6.0
|
flutter_displaymode: ^0.6.0
|
||||||
flutter_hooks: ^0.21.2
|
flutter_hooks: ^0.21.2
|
||||||
flutter_local_notifications: ^17.2.1+2
|
flutter_local_notifications: ^17.2.1+2
|
||||||
|
flutter_secure_storage: ^9.2.4
|
||||||
flutter_svg: ^2.0.17
|
flutter_svg: ^2.0.17
|
||||||
flutter_udid: ^3.0.0
|
flutter_udid: ^3.0.0
|
||||||
flutter_web_auth_2: ^5.0.0-alpha.0
|
flutter_web_auth_2: ^5.0.0-alpha.0
|
||||||
@ -41,6 +42,7 @@ dependencies:
|
|||||||
http: ^1.3.0
|
http: ^1.3.0
|
||||||
image_picker: ^1.1.2
|
image_picker: ^1.1.2
|
||||||
intl: ^0.19.0
|
intl: ^0.19.0
|
||||||
|
local_auth: ^2.3.0
|
||||||
logging: ^1.3.0
|
logging: ^1.3.0
|
||||||
maplibre_gl: ^0.21.0
|
maplibre_gl: ^0.21.0
|
||||||
network_info_plus: ^6.1.3
|
network_info_plus: ^6.1.3
|
||||||
@ -52,6 +54,8 @@ dependencies:
|
|||||||
permission_handler: ^11.4.0
|
permission_handler: ^11.4.0
|
||||||
photo_manager: ^3.6.4
|
photo_manager: ^3.6.4
|
||||||
photo_manager_image_provider: ^2.2.0
|
photo_manager_image_provider: ^2.2.0
|
||||||
|
pinput: ^5.0.1
|
||||||
|
platform: ^3.1.6
|
||||||
punycode: ^1.0.0
|
punycode: ^1.0.0
|
||||||
riverpod_annotation: ^2.6.1
|
riverpod_annotation: ^2.6.1
|
||||||
scrollable_positioned_list: ^0.3.8
|
scrollable_positioned_list: ^0.3.8
|
||||||
@ -64,9 +68,6 @@ dependencies:
|
|||||||
uuid: ^4.5.1
|
uuid: ^4.5.1
|
||||||
wakelock_plus: ^1.2.10
|
wakelock_plus: ^1.2.10
|
||||||
worker_manager: ^7.2.3
|
worker_manager: ^7.2.3
|
||||||
local_auth: ^2.3.0
|
|
||||||
pinput: ^5.0.1
|
|
||||||
flutter_secure_storage: ^9.2.4
|
|
||||||
|
|
||||||
native_video_player:
|
native_video_player:
|
||||||
git:
|
git:
|
||||||
@ -84,11 +85,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
|
||||||
@ -98,11 +94,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
|
||||||
@ -112,6 +110,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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user