mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
optimized decoding
This commit is contained in:
@@ -31,7 +31,7 @@ if (keystorePropertiesFile.exists()) {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 35
|
compileSdkVersion 35
|
||||||
ndkVersion = "28.1.13356709"
|
ndkVersion = "28.2.13676358"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_17
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import app.alextran.immich.background.BackgroundWorkerLockApi
|
|||||||
import app.alextran.immich.connectivity.ConnectivityApi
|
import app.alextran.immich.connectivity.ConnectivityApi
|
||||||
import app.alextran.immich.connectivity.ConnectivityApiImpl
|
import app.alextran.immich.connectivity.ConnectivityApiImpl
|
||||||
import app.alextran.immich.core.ImmichPlugin
|
import app.alextran.immich.core.ImmichPlugin
|
||||||
import app.alextran.immich.images.ThumbnailApi
|
import app.alextran.immich.images.LocalImageApi
|
||||||
import app.alextran.immich.images.ThumbnailsImpl
|
import app.alextran.immich.images.LocalImagesImpl
|
||||||
import app.alextran.immich.sync.NativeSyncApi
|
import app.alextran.immich.sync.NativeSyncApi
|
||||||
import app.alextran.immich.sync.NativeSyncApiImpl26
|
import app.alextran.immich.sync.NativeSyncApiImpl26
|
||||||
import app.alextran.immich.sync.NativeSyncApiImpl30
|
import app.alextran.immich.sync.NativeSyncApiImpl30
|
||||||
@@ -36,7 +36,8 @@ class MainActivity : FlutterFragmentActivity() {
|
|||||||
NativeSyncApiImpl30(ctx)
|
NativeSyncApiImpl30(ctx)
|
||||||
}
|
}
|
||||||
NativeSyncApi.setUp(messenger, nativeSyncApiImpl)
|
NativeSyncApi.setUp(messenger, nativeSyncApiImpl)
|
||||||
ThumbnailApi.setUp(messenger, ThumbnailsImpl(ctx))
|
LocalImageApi.setUp(messenger, LocalImagesImpl(ctx))
|
||||||
|
|
||||||
BackgroundWorkerFgHostApi.setUp(messenger, BackgroundWorkerApiImpl(ctx))
|
BackgroundWorkerFgHostApi.setUp(messenger, BackgroundWorkerApiImpl(ctx))
|
||||||
ConnectivityApi.setUp(messenger, ConnectivityApiImpl(ctx))
|
ConnectivityApi.setUp(messenger, ConnectivityApiImpl(ctx))
|
||||||
|
|
||||||
|
|||||||
+17
-17
@@ -13,7 +13,7 @@ import io.flutter.plugin.common.StandardMethodCodec
|
|||||||
import io.flutter.plugin.common.StandardMessageCodec
|
import io.flutter.plugin.common.StandardMessageCodec
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
private object ThumbnailsPigeonUtils {
|
private object LocalImagesPigeonUtils {
|
||||||
|
|
||||||
fun wrapResult(result: Any?): List<Any?> {
|
fun wrapResult(result: Any?): List<Any?> {
|
||||||
return listOf(result)
|
return listOf(result)
|
||||||
@@ -47,7 +47,7 @@ class FlutterError (
|
|||||||
override val message: String? = null,
|
override val message: String? = null,
|
||||||
val details: Any? = null
|
val details: Any? = null
|
||||||
) : Throwable()
|
) : Throwable()
|
||||||
private open class ThumbnailsPigeonCodec : StandardMessageCodec() {
|
private open class LocalImagesPigeonCodec : StandardMessageCodec() {
|
||||||
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
||||||
return super.readValueOfType(type, buffer)
|
return super.readValueOfType(type, buffer)
|
||||||
}
|
}
|
||||||
@@ -58,22 +58,22 @@ private open class ThumbnailsPigeonCodec : StandardMessageCodec() {
|
|||||||
|
|
||||||
|
|
||||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||||
interface ThumbnailApi {
|
interface LocalImageApi {
|
||||||
fun requestImage(assetId: String, requestId: Long, width: Long, height: Long, isVideo: Boolean, callback: (Result<Map<String, Long>>) -> Unit)
|
fun requestImage(assetId: String, requestId: Long, width: Long, height: Long, isVideo: Boolean, callback: (Result<Map<String, Long>>) -> Unit)
|
||||||
fun cancelImageRequest(requestId: Long)
|
fun cancelRequest(requestId: Long)
|
||||||
fun getThumbhash(thumbhash: String, callback: (Result<Map<String, Long>>) -> Unit)
|
fun getThumbhash(thumbhash: String, callback: (Result<Map<String, Long>>) -> Unit)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/** The codec used by ThumbnailApi. */
|
/** The codec used by LocalImageApi. */
|
||||||
val codec: MessageCodec<Any?> by lazy {
|
val codec: MessageCodec<Any?> by lazy {
|
||||||
ThumbnailsPigeonCodec()
|
LocalImagesPigeonCodec()
|
||||||
}
|
}
|
||||||
/** Sets up an instance of `ThumbnailApi` to handle messages through the `binaryMessenger`. */
|
/** Sets up an instance of `LocalImageApi` to handle messages through the `binaryMessenger`. */
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun setUp(binaryMessenger: BinaryMessenger, api: ThumbnailApi?, messageChannelSuffix: String = "") {
|
fun setUp(binaryMessenger: BinaryMessenger, api: LocalImageApi?, messageChannelSuffix: String = "") {
|
||||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||||
run {
|
run {
|
||||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ThumbnailApi.requestImage$separatedMessageChannelSuffix", codec)
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.LocalImageApi.requestImage$separatedMessageChannelSuffix", codec)
|
||||||
if (api != null) {
|
if (api != null) {
|
||||||
channel.setMessageHandler { message, reply ->
|
channel.setMessageHandler { message, reply ->
|
||||||
val args = message as List<Any?>
|
val args = message as List<Any?>
|
||||||
@@ -85,10 +85,10 @@ interface ThumbnailApi {
|
|||||||
api.requestImage(assetIdArg, requestIdArg, widthArg, heightArg, isVideoArg) { result: Result<Map<String, Long>> ->
|
api.requestImage(assetIdArg, requestIdArg, widthArg, heightArg, isVideoArg) { result: Result<Map<String, Long>> ->
|
||||||
val error = result.exceptionOrNull()
|
val error = result.exceptionOrNull()
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
reply.reply(ThumbnailsPigeonUtils.wrapError(error))
|
reply.reply(LocalImagesPigeonUtils.wrapError(error))
|
||||||
} else {
|
} else {
|
||||||
val data = result.getOrNull()
|
val data = result.getOrNull()
|
||||||
reply.reply(ThumbnailsPigeonUtils.wrapResult(data))
|
reply.reply(LocalImagesPigeonUtils.wrapResult(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,16 +97,16 @@ interface ThumbnailApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
run {
|
run {
|
||||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ThumbnailApi.cancelImageRequest$separatedMessageChannelSuffix", codec)
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.LocalImageApi.cancelRequest$separatedMessageChannelSuffix", codec)
|
||||||
if (api != null) {
|
if (api != null) {
|
||||||
channel.setMessageHandler { message, reply ->
|
channel.setMessageHandler { message, reply ->
|
||||||
val args = message as List<Any?>
|
val args = message as List<Any?>
|
||||||
val requestIdArg = args[0] as Long
|
val requestIdArg = args[0] as Long
|
||||||
val wrapped: List<Any?> = try {
|
val wrapped: List<Any?> = try {
|
||||||
api.cancelImageRequest(requestIdArg)
|
api.cancelRequest(requestIdArg)
|
||||||
listOf(null)
|
listOf(null)
|
||||||
} catch (exception: Throwable) {
|
} catch (exception: Throwable) {
|
||||||
ThumbnailsPigeonUtils.wrapError(exception)
|
LocalImagesPigeonUtils.wrapError(exception)
|
||||||
}
|
}
|
||||||
reply.reply(wrapped)
|
reply.reply(wrapped)
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ interface ThumbnailApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
run {
|
run {
|
||||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ThumbnailApi.getThumbhash$separatedMessageChannelSuffix", codec)
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.LocalImageApi.getThumbhash$separatedMessageChannelSuffix", codec)
|
||||||
if (api != null) {
|
if (api != null) {
|
||||||
channel.setMessageHandler { message, reply ->
|
channel.setMessageHandler { message, reply ->
|
||||||
val args = message as List<Any?>
|
val args = message as List<Any?>
|
||||||
@@ -123,10 +123,10 @@ interface ThumbnailApi {
|
|||||||
api.getThumbhash(thumbhashArg) { result: Result<Map<String, Long>> ->
|
api.getThumbhash(thumbhashArg) { result: Result<Map<String, Long>> ->
|
||||||
val error = result.exceptionOrNull()
|
val error = result.exceptionOrNull()
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
reply.reply(ThumbnailsPigeonUtils.wrapError(error))
|
reply.reply(LocalImagesPigeonUtils.wrapError(error))
|
||||||
} else {
|
} else {
|
||||||
val data = result.getOrNull()
|
val data = result.getOrNull()
|
||||||
reply.reply(ThumbnailsPigeonUtils.wrapResult(data))
|
reply.reply(LocalImagesPigeonUtils.wrapResult(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -29,7 +29,7 @@ data class Request(
|
|||||||
val callback: (Result<Map<String, Long>>) -> Unit
|
val callback: (Result<Map<String, Long>>) -> Unit
|
||||||
)
|
)
|
||||||
|
|
||||||
class ThumbnailsImpl(context: Context) : ThumbnailApi {
|
class LocalImagesImpl(context: Context) : LocalImageApi {
|
||||||
private val ctx: Context = context.applicationContext
|
private val ctx: Context = context.applicationContext
|
||||||
private val resolver: ContentResolver = ctx.contentResolver
|
private val resolver: ContentResolver = ctx.contentResolver
|
||||||
private val requestThread = Executors.newSingleThreadExecutor()
|
private val requestThread = Executors.newSingleThreadExecutor()
|
||||||
@@ -98,7 +98,7 @@ class ThumbnailsImpl(context: Context) : ThumbnailApi {
|
|||||||
requestMap[requestId] = request
|
requestMap[requestId] = request
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancelImageRequest(requestId: Long) {
|
override fun cancelRequest(requestId: Long) {
|
||||||
val request = requestMap.remove(requestId) ?: return
|
val request = requestMap.remove(requestId) ?: return
|
||||||
request.taskFuture.cancel(false)
|
request.taskFuture.cancel(false)
|
||||||
request.cancellationSignal.cancel()
|
request.cancellationSignal.cancel()
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
// Autogenerated from Pigeon (v26.0.2), do not edit directly.
|
||||||
|
// See also: https://pub.dev/packages/pigeon
|
||||||
|
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
|
||||||
|
|
||||||
|
package app.alextran.immich.images
|
||||||
|
|
||||||
|
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 RemoteImagesPigeonUtils {
|
||||||
|
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private open class RemoteImagesPigeonCodec : StandardMessageCodec() {
|
||||||
|
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
||||||
|
return super.readValueOfType(type, buffer)
|
||||||
|
}
|
||||||
|
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
|
||||||
|
super.writeValue(stream, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||||
|
interface RemoteImageApi {
|
||||||
|
fun requestImage(url: String, headers: Map<String, String>, requestId: Long, callback: (Result<Map<String, Long>>) -> Unit)
|
||||||
|
fun cancelRequest(requestId: Long)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** The codec used by RemoteImageApi. */
|
||||||
|
val codec: MessageCodec<Any?> by lazy {
|
||||||
|
RemoteImagesPigeonCodec()
|
||||||
|
}
|
||||||
|
/** Sets up an instance of `RemoteImageApi` to handle messages through the `binaryMessenger`. */
|
||||||
|
@JvmOverloads
|
||||||
|
fun setUp(binaryMessenger: BinaryMessenger, api: RemoteImageApi?, messageChannelSuffix: String = "") {
|
||||||
|
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.RemoteImageApi.requestImage$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val urlArg = args[0] as String
|
||||||
|
val headersArg = args[1] as Map<String, String>
|
||||||
|
val requestIdArg = args[2] as Long
|
||||||
|
api.requestImage(urlArg, headersArg, requestIdArg) { result: Result<Map<String, Long>> ->
|
||||||
|
val error = result.exceptionOrNull()
|
||||||
|
if (error != null) {
|
||||||
|
reply.reply(RemoteImagesPigeonUtils.wrapError(error))
|
||||||
|
} else {
|
||||||
|
val data = result.getOrNull()
|
||||||
|
reply.reply(RemoteImagesPigeonUtils.wrapResult(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.RemoteImageApi.cancelRequest$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val requestIdArg = args[0] as Long
|
||||||
|
val wrapped: List<Any?> = try {
|
||||||
|
api.cancelRequest(requestIdArg)
|
||||||
|
listOf(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
RemoteImagesPigeonUtils.wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,8 +56,8 @@ public final class ThumbHash {
|
|||||||
int w = Math.round(ratio > 1.0f ? 32.0f : 32.0f * ratio);
|
int w = Math.round(ratio > 1.0f ? 32.0f : 32.0f * ratio);
|
||||||
int h = Math.round(ratio > 1.0f ? 32.0f / ratio : 32.0f);
|
int h = Math.round(ratio > 1.0f ? 32.0f / ratio : 32.0f);
|
||||||
int size = w * h * 4;
|
int size = w * h * 4;
|
||||||
long pointer = ThumbnailsImpl.allocateNative(size);
|
long pointer = LocalImagesImpl.allocateNative(size);
|
||||||
ByteBuffer rgba = ThumbnailsImpl.wrapAsBuffer(pointer, size);
|
ByteBuffer rgba = LocalImagesImpl.wrapAsBuffer(pointer, size);
|
||||||
int cx_stop = Math.max(lx, hasAlpha ? 5 : 3);
|
int cx_stop = Math.max(lx, hasAlpha ? 5 : 3);
|
||||||
int cy_stop = Math.max(ly, hasAlpha ? 5 : 3);
|
int cy_stop = Math.max(ly, hasAlpha ? 5 : 3);
|
||||||
float[] fx = new float[cx_stop];
|
float[] fx = new float[cx_stop];
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 54;
|
objectVersion = 77;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -29,9 +29,11 @@
|
|||||||
FAC6F89B2D287C890078CB2F /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = FAC6F8902D287C890078CB2F /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
FAC6F89B2D287C890078CB2F /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = FAC6F8902D287C890078CB2F /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
FAC6F8B72D287F120078CB2F /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC6F8B52D287F120078CB2F /* ShareViewController.swift */; };
|
FAC6F8B72D287F120078CB2F /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC6F8B52D287F120078CB2F /* ShareViewController.swift */; };
|
||||||
FAC6F8B92D287F120078CB2F /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FAC6F8B32D287F120078CB2F /* MainInterface.storyboard */; };
|
FAC6F8B92D287F120078CB2F /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FAC6F8B32D287F120078CB2F /* MainInterface.storyboard */; };
|
||||||
|
FE5499F32F1197D8006016CB /* LocalImages.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE5499F12F1197D8006016CB /* LocalImages.g.swift */; };
|
||||||
|
FE5499F42F1197D8006016CB /* RemoteImages.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE5499F22F1197D8006016CB /* RemoteImages.g.swift */; };
|
||||||
|
FE5499F62F11980E006016CB /* LocalImagesImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE5499F52F11980E006016CB /* LocalImagesImpl.swift */; };
|
||||||
|
FE5499F82F1198E2006016CB /* RemoteImagesImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE5499F72F1198DE006016CB /* RemoteImagesImpl.swift */; };
|
||||||
FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */; };
|
FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */; };
|
||||||
FED3B1962E253E9B0030FD97 /* ThumbnailsImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = FED3B1942E253E9B0030FD97 /* ThumbnailsImpl.swift */; };
|
|
||||||
FED3B1972E253E9B0030FD97 /* Thumbnails.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = FED3B1932E253E9B0030FD97 /* Thumbnails.g.swift */; };
|
|
||||||
FEE084F82EC172460045228E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084F72EC172460045228E /* SQLiteData */; };
|
FEE084F82EC172460045228E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084F72EC172460045228E /* SQLiteData */; };
|
||||||
FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FA2EC1725A0045228E /* RawStructuredFieldValues */; };
|
FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FA2EC1725A0045228E /* RawStructuredFieldValues */; };
|
||||||
FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FC2EC1725A0045228E /* StructuredFieldValues */; };
|
FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FC2EC1725A0045228E /* StructuredFieldValues */; };
|
||||||
@@ -118,9 +120,11 @@
|
|||||||
FAC6F8B42D287F120078CB2F /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = "<group>"; };
|
FAC6F8B42D287F120078CB2F /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = "<group>"; };
|
||||||
FAC6F8B52D287F120078CB2F /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
FAC6F8B52D287F120078CB2F /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||||
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>"; };
|
||||||
|
FE5499F12F1197D8006016CB /* LocalImages.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalImages.g.swift; sourceTree = "<group>"; };
|
||||||
|
FE5499F22F1197D8006016CB /* RemoteImages.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteImages.g.swift; sourceTree = "<group>"; };
|
||||||
|
FE5499F52F11980E006016CB /* LocalImagesImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalImagesImpl.swift; sourceTree = "<group>"; };
|
||||||
|
FE5499F72F1198DE006016CB /* RemoteImagesImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteImagesImpl.swift; sourceTree = "<group>"; };
|
||||||
FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbhash.swift; sourceTree = "<group>"; };
|
FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbhash.swift; sourceTree = "<group>"; };
|
||||||
FED3B1932E253E9B0030FD97 /* Thumbnails.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbnails.g.swift; sourceTree = "<group>"; };
|
|
||||||
FED3B1942E253E9B0030FD97 /* ThumbnailsImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailsImpl.swift; sourceTree = "<group>"; };
|
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
@@ -136,15 +140,11 @@
|
|||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
B231F52D2E93A44A00BC45D1 /* Core */ = {
|
B231F52D2E93A44A00BC45D1 /* Core */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
exceptions = (
|
|
||||||
);
|
|
||||||
path = Core;
|
path = Core;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
|
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
exceptions = (
|
|
||||||
);
|
|
||||||
path = Sync;
|
path = Sync;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -158,8 +158,6 @@
|
|||||||
};
|
};
|
||||||
FEE084F22EC172080045228E /* Schemas */ = {
|
FEE084F22EC172080045228E /* Schemas */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
exceptions = (
|
|
||||||
);
|
|
||||||
path = Schemas;
|
path = Schemas;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -321,9 +319,11 @@
|
|||||||
FED3B1952E253E9B0030FD97 /* Images */ = {
|
FED3B1952E253E9B0030FD97 /* Images */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
FE5499F72F1198DE006016CB /* RemoteImagesImpl.swift */,
|
||||||
|
FE5499F52F11980E006016CB /* LocalImagesImpl.swift */,
|
||||||
|
FE5499F12F1197D8006016CB /* LocalImages.g.swift */,
|
||||||
|
FE5499F22F1197D8006016CB /* RemoteImages.g.swift */,
|
||||||
FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */,
|
FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */,
|
||||||
FED3B1932E253E9B0030FD97 /* Thumbnails.g.swift */,
|
|
||||||
FED3B1942E253E9B0030FD97 /* ThumbnailsImpl.swift */,
|
|
||||||
);
|
);
|
||||||
path = Images;
|
path = Images;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -549,10 +549,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";
|
||||||
@@ -581,10 +585,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";
|
||||||
@@ -600,12 +608,14 @@
|
|||||||
65F32F31299BD2F800CE9261 /* BackgroundServicePlugin.swift in Sources */,
|
65F32F31299BD2F800CE9261 /* BackgroundServicePlugin.swift in Sources */,
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
B21E34AC2E5B09190031FDB9 /* BackgroundWorker.swift in Sources */,
|
B21E34AC2E5B09190031FDB9 /* BackgroundWorker.swift in Sources */,
|
||||||
|
FE5499F32F1197D8006016CB /* LocalImages.g.swift in Sources */,
|
||||||
|
FE5499F62F11980E006016CB /* LocalImagesImpl.swift in Sources */,
|
||||||
|
FE5499F42F1197D8006016CB /* RemoteImages.g.swift in Sources */,
|
||||||
B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */,
|
B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */,
|
||||||
|
FE5499F82F1198E2006016CB /* RemoteImagesImpl.swift in Sources */,
|
||||||
FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */,
|
FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */,
|
||||||
B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */,
|
B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */,
|
||||||
FED3B1962E253E9B0030FD97 /* ThumbnailsImpl.swift in Sources */,
|
|
||||||
B21E34AA2E5AFD2B0031FDB9 /* BackgroundWorkerApiImpl.swift in Sources */,
|
B21E34AA2E5AFD2B0031FDB9 /* BackgroundWorkerApiImpl.swift in Sources */,
|
||||||
FED3B1972E253E9B0030FD97 /* Thumbnails.g.swift in Sources */,
|
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
B2BE315F2E5E5229006EEF88 /* BackgroundWorker.g.swift in Sources */,
|
B2BE315F2E5E5229006EEF88 /* BackgroundWorker.g.swift in Sources */,
|
||||||
65F32F33299D349D00CE9261 /* BackgroundSyncWorker.swift in Sources */,
|
65F32F33299D349D00CE9261 /* BackgroundSyncWorker.swift in Sources */,
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ import UIKit
|
|||||||
|
|
||||||
public static func registerPlugins(with engine: FlutterEngine) {
|
public static func registerPlugins(with engine: FlutterEngine) {
|
||||||
NativeSyncApiImpl.register(with: engine.registrar(forPlugin: NativeSyncApiImpl.name)!)
|
NativeSyncApiImpl.register(with: engine.registrar(forPlugin: NativeSyncApiImpl.name)!)
|
||||||
ThumbnailApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: ThumbnailApiImpl())
|
LocalImageApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: LocalImageApiImpl())
|
||||||
|
RemoteImageApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: RemoteImageApiImpl())
|
||||||
BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: BackgroundWorkerApiImpl())
|
BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: BackgroundWorkerApiImpl())
|
||||||
ConnectivityApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: ConnectivityApiImpl())
|
ConnectivityApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: ConnectivityApiImpl())
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-19
@@ -47,41 +47,41 @@ private func nilOrValue<T>(_ value: Any?) -> T? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class ThumbnailsPigeonCodecReader: FlutterStandardReader {
|
private class LocalImagesPigeonCodecReader: FlutterStandardReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ThumbnailsPigeonCodecWriter: FlutterStandardWriter {
|
private class LocalImagesPigeonCodecWriter: FlutterStandardWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ThumbnailsPigeonCodecReaderWriter: FlutterStandardReaderWriter {
|
private class LocalImagesPigeonCodecReaderWriter: FlutterStandardReaderWriter {
|
||||||
override func reader(with data: Data) -> FlutterStandardReader {
|
override func reader(with data: Data) -> FlutterStandardReader {
|
||||||
return ThumbnailsPigeonCodecReader(data: data)
|
return LocalImagesPigeonCodecReader(data: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
|
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
|
||||||
return ThumbnailsPigeonCodecWriter(data: data)
|
return LocalImagesPigeonCodecWriter(data: data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThumbnailsPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
|
class LocalImagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
|
||||||
static let shared = ThumbnailsPigeonCodec(readerWriter: ThumbnailsPigeonCodecReaderWriter())
|
static let shared = LocalImagesPigeonCodec(readerWriter: LocalImagesPigeonCodecReaderWriter())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||||
protocol ThumbnailApi {
|
protocol LocalImageApi {
|
||||||
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, completion: @escaping (Result<[String: Int64], Error>) -> Void)
|
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, completion: @escaping (Result<[String: Int64], Error>) -> Void)
|
||||||
func cancelImageRequest(requestId: Int64) throws
|
func cancelRequest(requestId: Int64) throws
|
||||||
func getThumbhash(thumbhash: String, completion: @escaping (Result<[String: Int64], Error>) -> Void)
|
func getThumbhash(thumbhash: String, completion: @escaping (Result<[String: Int64], Error>) -> Void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
||||||
class ThumbnailApiSetup {
|
class LocalImageApiSetup {
|
||||||
static var codec: FlutterStandardMessageCodec { ThumbnailsPigeonCodec.shared }
|
static var codec: FlutterStandardMessageCodec { LocalImagesPigeonCodec.shared }
|
||||||
/// Sets up an instance of `ThumbnailApi` to handle messages through the `binaryMessenger`.
|
/// Sets up an instance of `LocalImageApi` to handle messages through the `binaryMessenger`.
|
||||||
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: ThumbnailApi?, messageChannelSuffix: String = "") {
|
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: LocalImageApi?, messageChannelSuffix: String = "") {
|
||||||
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
|
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
|
||||||
let requestImageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ThumbnailApi.requestImage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
let requestImageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.LocalImageApi.requestImage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
if let api = api {
|
if let api = api {
|
||||||
requestImageChannel.setMessageHandler { message, reply in
|
requestImageChannel.setMessageHandler { message, reply in
|
||||||
let args = message as! [Any?]
|
let args = message as! [Any?]
|
||||||
@@ -102,22 +102,22 @@ class ThumbnailApiSetup {
|
|||||||
} else {
|
} else {
|
||||||
requestImageChannel.setMessageHandler(nil)
|
requestImageChannel.setMessageHandler(nil)
|
||||||
}
|
}
|
||||||
let cancelImageRequestChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ThumbnailApi.cancelImageRequest\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
let cancelRequestChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.LocalImageApi.cancelRequest\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
if let api = api {
|
if let api = api {
|
||||||
cancelImageRequestChannel.setMessageHandler { message, reply in
|
cancelRequestChannel.setMessageHandler { message, reply in
|
||||||
let args = message as! [Any?]
|
let args = message as! [Any?]
|
||||||
let requestIdArg = args[0] as! Int64
|
let requestIdArg = args[0] as! Int64
|
||||||
do {
|
do {
|
||||||
try api.cancelImageRequest(requestId: requestIdArg)
|
try api.cancelRequest(requestId: requestIdArg)
|
||||||
reply(wrapResult(nil))
|
reply(wrapResult(nil))
|
||||||
} catch {
|
} catch {
|
||||||
reply(wrapError(error))
|
reply(wrapError(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cancelImageRequestChannel.setMessageHandler(nil)
|
cancelRequestChannel.setMessageHandler(nil)
|
||||||
}
|
}
|
||||||
let getThumbhashChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ThumbnailApi.getThumbhash\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
let getThumbhashChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.LocalImageApi.getThumbhash\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
if let api = api {
|
if let api = api {
|
||||||
getThumbhashChannel.setMessageHandler { message, reply in
|
getThumbhashChannel.setMessageHandler { message, reply in
|
||||||
let args = message as! [Any?]
|
let args = message as! [Any?]
|
||||||
+40
-74
@@ -1,9 +1,9 @@
|
|||||||
import CryptoKit
|
import Accelerate
|
||||||
import Flutter
|
import Flutter
|
||||||
import MobileCoreServices
|
import MobileCoreServices
|
||||||
import Photos
|
import Photos
|
||||||
|
|
||||||
class Request {
|
class LocalImageRequest {
|
||||||
weak var workItem: DispatchWorkItem?
|
weak var workItem: DispatchWorkItem?
|
||||||
var isCancelled = false
|
var isCancelled = false
|
||||||
let callback: (Result<[String: Int64], any Error>) -> Void
|
let callback: (Result<[String: Int64], any Error>) -> Void
|
||||||
@@ -13,7 +13,7 @@ class Request {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThumbnailApiImpl: ThumbnailApi {
|
class LocalImageApiImpl: LocalImageApi {
|
||||||
private static let imageManager = PHImageManager.default()
|
private static let imageManager = PHImageManager.default()
|
||||||
private static let fetchOptions = {
|
private static let fetchOptions = {
|
||||||
let fetchOptions = PHFetchOptions()
|
let fetchOptions = PHFetchOptions()
|
||||||
@@ -36,9 +36,14 @@ class ThumbnailApiImpl: ThumbnailApi {
|
|||||||
private static let cancelQueue = DispatchQueue(label: "thumbnail.cancellation", qos: .default)
|
private static let cancelQueue = DispatchQueue(label: "thumbnail.cancellation", qos: .default)
|
||||||
private static let processingQueue = DispatchQueue(label: "thumbnail.processing", qos: .userInteractive, attributes: .concurrent)
|
private static let processingQueue = DispatchQueue(label: "thumbnail.processing", qos: .userInteractive, attributes: .concurrent)
|
||||||
|
|
||||||
private static let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
|
private static var rgbaFormat = vImage_CGImageFormat(
|
||||||
private static let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue).rawValue
|
bitsPerComponent: 8,
|
||||||
private static var requests = [Int64: Request]()
|
bitsPerPixel: 32,
|
||||||
|
colorSpace: CGColorSpaceCreateDeviceRGB(),
|
||||||
|
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue),
|
||||||
|
renderingIntent: .defaultIntent
|
||||||
|
)!
|
||||||
|
private static var requests = [Int64: LocalImageRequest]()
|
||||||
private static let cancelledResult = Result<[String: Int64], any Error>.success([:])
|
private static let cancelledResult = Result<[String: Int64], any Error>.success([:])
|
||||||
private static let concurrencySemaphore = DispatchSemaphore(value: ProcessInfo.processInfo.activeProcessorCount * 2)
|
private static let concurrencySemaphore = DispatchSemaphore(value: ProcessInfo.processInfo.activeProcessorCount * 2)
|
||||||
private static let assetCache = {
|
private static let assetCache = {
|
||||||
@@ -46,37 +51,19 @@ class ThumbnailApiImpl: ThumbnailApi {
|
|||||||
assetCache.countLimit = 10000
|
assetCache.countLimit = 10000
|
||||||
return assetCache
|
return assetCache
|
||||||
}()
|
}()
|
||||||
private static let activitySemaphore = DispatchSemaphore(value: 1)
|
|
||||||
private static let willResignActiveObserver = NotificationCenter.default.addObserver(
|
|
||||||
forName: UIApplication.willResignActiveNotification,
|
|
||||||
object: nil,
|
|
||||||
queue: .main
|
|
||||||
) { _ in
|
|
||||||
processingQueue.suspend()
|
|
||||||
activitySemaphore.wait()
|
|
||||||
}
|
|
||||||
private static let didBecomeActiveObserver = NotificationCenter.default.addObserver(
|
|
||||||
forName: UIApplication.didBecomeActiveNotification,
|
|
||||||
object: nil,
|
|
||||||
queue: .main
|
|
||||||
) { _ in
|
|
||||||
processingQueue.resume()
|
|
||||||
activitySemaphore.signal()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getThumbhash(thumbhash: String, completion: @escaping (Result<[String : Int64], any Error>) -> Void) {
|
func getThumbhash(thumbhash: String, completion: @escaping (Result<[String : Int64], any Error>) -> Void) {
|
||||||
Self.processingQueue.async {
|
Self.processingQueue.async {
|
||||||
guard let data = Data(base64Encoded: thumbhash)
|
guard let data = Data(base64Encoded: thumbhash)
|
||||||
else { return completion(.failure(PigeonError(code: "", message: "Invalid base64 string: \(thumbhash)", details: nil)))}
|
else { return completion(.failure(PigeonError(code: "", message: "Invalid base64 string: \(thumbhash)", details: nil)))}
|
||||||
|
|
||||||
let (width, height, pointer) = thumbHashToRGBA(hash: data)
|
let (width, height, pointer) = thumbHashToRGBA(hash: data)
|
||||||
self.waitForActiveState()
|
|
||||||
completion(.success(["pointer": Int64(Int(bitPattern: pointer.baseAddress)), "width": Int64(width), "height": Int64(height)]))
|
completion(.success(["pointer": Int64(Int(bitPattern: pointer.baseAddress)), "width": Int64(width), "height": Int64(height)]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, completion: @escaping (Result<[String: Int64], any Error>) -> Void) {
|
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, completion: @escaping (Result<[String: Int64], any Error>) -> Void) {
|
||||||
let request = Request(callback: completion)
|
let request = LocalImageRequest(callback: completion)
|
||||||
let item = DispatchWorkItem {
|
let item = DispatchWorkItem {
|
||||||
if request.isCancelled {
|
if request.isCancelled {
|
||||||
return completion(Self.cancelledResult)
|
return completion(Self.cancelledResult)
|
||||||
@@ -93,7 +80,7 @@ class ThumbnailApiImpl: ThumbnailApi {
|
|||||||
|
|
||||||
guard let asset = Self.requestAsset(assetId: assetId)
|
guard let asset = Self.requestAsset(assetId: assetId)
|
||||||
else {
|
else {
|
||||||
Self.removeRequest(requestId: requestId)
|
Self.remove(requestId: requestId)
|
||||||
completion(.failure(PigeonError(code: "", message: "Could not get asset data for \(assetId)", details: nil)))
|
completion(.failure(PigeonError(code: "", message: "Could not get asset data for \(assetId)", details: nil)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -119,70 +106,54 @@ class ThumbnailApiImpl: ThumbnailApi {
|
|||||||
|
|
||||||
guard let image = image,
|
guard let image = image,
|
||||||
let cgImage = image.cgImage else {
|
let cgImage = image.cgImage else {
|
||||||
Self.removeRequest(requestId: requestId)
|
Self.remove(requestId: requestId)
|
||||||
return completion(.failure(PigeonError(code: "", message: "Could not get pixel data for \(assetId)", details: nil)))
|
return completion(.failure(PigeonError(code: "", message: "Could not get pixel data for \(assetId)", details: nil)))
|
||||||
}
|
}
|
||||||
|
|
||||||
let pointer = UnsafeMutableRawPointer.allocate(
|
|
||||||
byteCount: Int(cgImage.width) * Int(cgImage.height) * 4,
|
|
||||||
alignment: MemoryLayout<UInt8>.alignment
|
|
||||||
)
|
|
||||||
|
|
||||||
if request.isCancelled {
|
if request.isCancelled {
|
||||||
pointer.deallocate()
|
|
||||||
return completion(Self.cancelledResult)
|
return completion(Self.cancelledResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let context = CGContext(
|
do {
|
||||||
data: pointer,
|
let buffer = try vImage_Buffer(cgImage: cgImage, format: Self.rgbaFormat)
|
||||||
width: cgImage.width,
|
|
||||||
height: cgImage.height,
|
if request.isCancelled {
|
||||||
bitsPerComponent: 8,
|
buffer.free()
|
||||||
bytesPerRow: cgImage.width * 4,
|
return completion(Self.cancelledResult)
|
||||||
space: Self.rgbColorSpace,
|
}
|
||||||
bitmapInfo: Self.bitmapInfo
|
|
||||||
) else {
|
request.callback(.success([
|
||||||
pointer.deallocate()
|
"pointer": Int64(Int(bitPattern: buffer.data)),
|
||||||
Self.removeRequest(requestId: requestId)
|
"width": Int64(buffer.width),
|
||||||
return completion(.failure(PigeonError(code: "", message: "Could not create context for \(assetId)", details: nil)))
|
"height": Int64(buffer.height),
|
||||||
|
"rowBytes": Int64(buffer.rowBytes)
|
||||||
|
]))
|
||||||
|
print("Successful response for \(requestId)")
|
||||||
|
Self.remove(requestId: requestId)
|
||||||
|
} catch {
|
||||||
|
Self.remove(requestId: requestId)
|
||||||
|
return completion(.failure(PigeonError(code: "", message: "Failed to convert image for \(assetId): \(error)", details: nil)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.isCancelled {
|
|
||||||
pointer.deallocate()
|
|
||||||
return completion(Self.cancelledResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
context.interpolationQuality = .none
|
|
||||||
context.draw(cgImage, in: CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height))
|
|
||||||
|
|
||||||
if request.isCancelled {
|
|
||||||
pointer.deallocate()
|
|
||||||
return completion(Self.cancelledResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.waitForActiveState()
|
|
||||||
completion(.success(["pointer": Int64(Int(bitPattern: pointer)), "width": Int64(cgImage.width), "height": Int64(cgImage.height)]))
|
|
||||||
Self.removeRequest(requestId: requestId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
request.workItem = item
|
request.workItem = item
|
||||||
Self.addRequest(requestId: requestId, request: request)
|
Self.add(requestId: requestId, request: request)
|
||||||
Self.processingQueue.async(execute: item)
|
Self.processingQueue.async(execute: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cancelImageRequest(requestId: Int64) {
|
func cancelRequest(requestId: Int64) {
|
||||||
Self.cancelRequest(requestId: requestId)
|
Self.cancel(requestId: requestId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func addRequest(requestId: Int64, request: Request) -> Void {
|
private static func add(requestId: Int64, request: LocalImageRequest) -> Void {
|
||||||
requestQueue.sync { requests[requestId] = request }
|
requestQueue.sync { requests[requestId] = request }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func removeRequest(requestId: Int64) -> Void {
|
private static func remove(requestId: Int64) -> Void {
|
||||||
requestQueue.sync { requests[requestId] = nil }
|
requestQueue.sync { requests[requestId] = nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func cancelRequest(requestId: Int64) -> Void {
|
private static func cancel(requestId: Int64) -> Void {
|
||||||
requestQueue.async {
|
requestQueue.async {
|
||||||
guard let request = requests.removeValue(forKey: requestId) else { return }
|
guard let request = requests.removeValue(forKey: requestId) else { return }
|
||||||
request.isCancelled = true
|
request.isCancelled = true
|
||||||
@@ -203,9 +174,4 @@ class ThumbnailApiImpl: ThumbnailApi {
|
|||||||
assetQueue.async { assetCache.setObject(asset, forKey: assetId as NSString) }
|
assetQueue.async { assetCache.setObject(asset, forKey: assetId as NSString) }
|
||||||
return asset
|
return asset
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForActiveState() {
|
|
||||||
Self.activitySemaphore.wait()
|
|
||||||
Self.activitySemaphore.signal()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
// Autogenerated from Pigeon (v26.0.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
|
||||||
|
|
||||||
|
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?
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class RemoteImagesPigeonCodecReader: FlutterStandardReader {
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RemoteImagesPigeonCodecWriter: FlutterStandardWriter {
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RemoteImagesPigeonCodecReaderWriter: FlutterStandardReaderWriter {
|
||||||
|
override func reader(with data: Data) -> FlutterStandardReader {
|
||||||
|
return RemoteImagesPigeonCodecReader(data: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
|
||||||
|
return RemoteImagesPigeonCodecWriter(data: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoteImagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
|
||||||
|
static let shared = RemoteImagesPigeonCodec(readerWriter: RemoteImagesPigeonCodecReaderWriter())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||||
|
protocol RemoteImageApi {
|
||||||
|
func requestImage(url: String, headers: [String: String], requestId: Int64, completion: @escaping (Result<[String: Int64], Error>) -> Void)
|
||||||
|
func cancelRequest(requestId: Int64) throws
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
||||||
|
class RemoteImageApiSetup {
|
||||||
|
static var codec: FlutterStandardMessageCodec { RemoteImagesPigeonCodec.shared }
|
||||||
|
/// Sets up an instance of `RemoteImageApi` to handle messages through the `binaryMessenger`.
|
||||||
|
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: RemoteImageApi?, messageChannelSuffix: String = "") {
|
||||||
|
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
|
||||||
|
let requestImageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.RemoteImageApi.requestImage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
|
if let api = api {
|
||||||
|
requestImageChannel.setMessageHandler { message, reply in
|
||||||
|
let args = message as! [Any?]
|
||||||
|
let urlArg = args[0] as! String
|
||||||
|
let headersArg = args[1] as! [String: String]
|
||||||
|
let requestIdArg = args[2] as! Int64
|
||||||
|
api.requestImage(url: urlArg, headers: headersArg, requestId: requestIdArg) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let res):
|
||||||
|
reply(wrapResult(res))
|
||||||
|
case .failure(let error):
|
||||||
|
reply(wrapError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
requestImageChannel.setMessageHandler(nil)
|
||||||
|
}
|
||||||
|
let cancelRequestChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.RemoteImageApi.cancelRequest\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
|
if let api = api {
|
||||||
|
cancelRequestChannel.setMessageHandler { message, reply in
|
||||||
|
let args = message as! [Any?]
|
||||||
|
let requestIdArg = args[0] as! Int64
|
||||||
|
do {
|
||||||
|
try api.cancelRequest(requestId: requestIdArg)
|
||||||
|
reply(wrapResult(nil))
|
||||||
|
} catch {
|
||||||
|
reply(wrapError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cancelRequestChannel.setMessageHandler(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
import Accelerate
|
||||||
|
import Flutter
|
||||||
|
import MobileCoreServices
|
||||||
|
import Photos
|
||||||
|
|
||||||
|
class RemoteImageRequest {
|
||||||
|
weak var task: URLSessionDataTask?
|
||||||
|
var isCancelled = false
|
||||||
|
let imageSource: CGImageSource
|
||||||
|
var data: CFMutableData?
|
||||||
|
let completion: (Result<[String: Int64], any Error>) -> Void
|
||||||
|
|
||||||
|
init(task: URLSessionDataTask, imageSource: CGImageSource, completion: @escaping (Result<[String: Int64], any Error>) -> Void) {
|
||||||
|
self.task = task
|
||||||
|
self.imageSource = imageSource
|
||||||
|
self.data = nil
|
||||||
|
self.completion = completion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoteImageApiImpl: NSObject, RemoteImageApi {
|
||||||
|
private static let delegate = RemoteImageApiDelegate()
|
||||||
|
static let session = {
|
||||||
|
let config = URLSessionConfiguration.default
|
||||||
|
let thumbnailPath = FileManager.default.temporaryDirectory.appendingPathComponent("thumbnails", isDirectory: true)
|
||||||
|
try! FileManager.default.createDirectory(at: thumbnailPath, withIntermediateDirectories: true)
|
||||||
|
config.urlCache = URLCache(
|
||||||
|
memoryCapacity: 0,
|
||||||
|
diskCapacity: 10 << 20,
|
||||||
|
directory: thumbnailPath
|
||||||
|
)
|
||||||
|
config.httpMaximumConnectionsPerHost = 16
|
||||||
|
return URLSession(configuration: config, delegate: delegate, delegateQueue: nil)
|
||||||
|
}()
|
||||||
|
|
||||||
|
func requestImage(url: String, headers: [String : String], requestId: Int64, completion: @escaping (Result<[String : Int64], any Error>) -> Void) {
|
||||||
|
print("Got image request \(requestId) for \(url)")
|
||||||
|
var urlRequest = URLRequest(url: URL(string: url)!)
|
||||||
|
for (key, value) in headers {
|
||||||
|
urlRequest.setValue(value, forHTTPHeaderField: key)
|
||||||
|
}
|
||||||
|
let task = Self.session.dataTask(with: urlRequest)
|
||||||
|
task.taskDescription = String(requestId)
|
||||||
|
|
||||||
|
let imageRequest = RemoteImageRequest(
|
||||||
|
task: task,
|
||||||
|
imageSource: CGImageSourceCreateIncremental(
|
||||||
|
[kCGImageSourceShouldCache: false] as CFDictionary),
|
||||||
|
completion: completion
|
||||||
|
)
|
||||||
|
Self.delegate.add(requestId: requestId, request: imageRequest)
|
||||||
|
|
||||||
|
task.resume()
|
||||||
|
print("Started task for \(requestId)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancelRequest(requestId: Int64) {
|
||||||
|
print("Cancelling task for \(requestId)")
|
||||||
|
Self.delegate.cancel(requestId: requestId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoteImageApiDelegate: NSObject, URLSessionDataDelegate {
|
||||||
|
private static let requestQueue = DispatchQueue(label: "thumbnail.requests", qos: .userInitiated)
|
||||||
|
private static var rgbaFormat = vImage_CGImageFormat(
|
||||||
|
bitsPerComponent: 8,
|
||||||
|
bitsPerPixel: 32,
|
||||||
|
colorSpace: CGColorSpaceCreateDeviceRGB(),
|
||||||
|
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue),
|
||||||
|
renderingIntent: .perceptual
|
||||||
|
)!
|
||||||
|
private static var requests = [Int64: RemoteImageRequest]()
|
||||||
|
private static let cancelledResult = Result<[String: Int64], any Error>.success([:])
|
||||||
|
|
||||||
|
func urlSession(
|
||||||
|
_ session: URLSession, dataTask: URLSessionDataTask,
|
||||||
|
didReceive response: URLResponse,
|
||||||
|
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void
|
||||||
|
) {
|
||||||
|
guard let taskDescription = dataTask.taskDescription,
|
||||||
|
let requestId = Int64(taskDescription),
|
||||||
|
let request = (Self.requestQueue.sync { Self.requests[requestId] })
|
||||||
|
else {
|
||||||
|
return completionHandler(.cancel)
|
||||||
|
}
|
||||||
|
|
||||||
|
let capacity = max(Int(response.expectedContentLength), 0)
|
||||||
|
request.data = CFDataCreateMutable(nil, capacity)
|
||||||
|
|
||||||
|
completionHandler(.allow)
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask,
|
||||||
|
didReceive data: Data) {
|
||||||
|
print("Got data")
|
||||||
|
guard let taskDescription = dataTask.taskDescription,
|
||||||
|
let requestId = Int64(taskDescription),
|
||||||
|
let request = get(requestId: requestId),
|
||||||
|
let accumulatedData = request.data
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
data.withUnsafeBytes { bytes in
|
||||||
|
CFDataAppendBytes(request.data, bytes.bindMemory(to: UInt8.self).baseAddress, data.count)
|
||||||
|
}
|
||||||
|
CGImageSourceUpdateData(request.imageSource, accumulatedData, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlSession(_ session: URLSession, task: URLSessionTask,
|
||||||
|
didCompleteWithError error: Error?) {
|
||||||
|
print("Task ended")
|
||||||
|
guard let taskDescription = task.taskDescription,
|
||||||
|
let requestId = Int64(taskDescription),
|
||||||
|
let request = get(requestId: requestId),
|
||||||
|
let accumulatedData = request.data
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
if let error = error {
|
||||||
|
print("Task failed for \(requestId): \(error)")
|
||||||
|
remove(requestId: requestId)
|
||||||
|
return request.completion(.failure(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
CGImageSourceUpdateData(request.imageSource, accumulatedData, true)
|
||||||
|
|
||||||
|
let options = [kCGImageSourceShouldCacheImmediately: true] as CFDictionary
|
||||||
|
guard let cgImage = CGImageSourceCreateImageAtIndex(request.imageSource, 0, nil) else {
|
||||||
|
print("No image for \(requestId)")
|
||||||
|
remove(requestId: requestId)
|
||||||
|
return request.completion(.failure(PigeonError(code: "", message: "Failed to decode image for request \(requestId)", details: nil)))
|
||||||
|
}
|
||||||
|
print("Got image for \(requestId)")
|
||||||
|
|
||||||
|
if request.isCancelled {
|
||||||
|
remove(requestId: requestId)
|
||||||
|
return request.completion(Self.cancelledResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let buffer = try vImage_Buffer(cgImage: cgImage, format: Self.rgbaFormat)
|
||||||
|
|
||||||
|
if request.isCancelled {
|
||||||
|
buffer.free()
|
||||||
|
remove(requestId: requestId)
|
||||||
|
return request.completion(Self.cancelledResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
request.completion(
|
||||||
|
.success([
|
||||||
|
"pointer": Int64(Int(bitPattern: buffer.data)),
|
||||||
|
"width": Int64(buffer.width),
|
||||||
|
"height": Int64(buffer.height),
|
||||||
|
"rowBytes": Int64(buffer.rowBytes),
|
||||||
|
]))
|
||||||
|
print("Successful response for \(requestId)")
|
||||||
|
|
||||||
|
remove(requestId: requestId)
|
||||||
|
} catch {
|
||||||
|
print("vImage conversion failed for \(requestId): \(error)")
|
||||||
|
remove(requestId: requestId)
|
||||||
|
return request.completion(.failure(PigeonError(code: "", message: "Failed to convert image for request \(requestId): \(error)", details: nil)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(requestId: Int64) -> RemoteImageRequest? {
|
||||||
|
Self.requestQueue.sync { Self.requests[requestId] }
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(requestId: Int64, request: RemoteImageRequest) -> Void {
|
||||||
|
Self.requestQueue.sync { Self.requests[requestId] = request }
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove(requestId: Int64) -> Void {
|
||||||
|
Self.requestQueue.sync { Self.requests[requestId] = nil }
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancel(requestId: Int64) -> Void {
|
||||||
|
guard let request = (Self.requestQueue.sync { Self.requests[requestId] }) else { return }
|
||||||
|
request.isCancelled = true
|
||||||
|
request.task?.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,15 +2,11 @@ import 'dart:async';
|
|||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:cronet_http/cronet_http.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:ffi/ffi.dart';
|
import 'package:ffi/ffi.dart';
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
|
||||||
|
|
||||||
part 'local_image_request.dart';
|
part 'local_image_request.dart';
|
||||||
part 'thumbhash_image_request.dart';
|
part 'thumbhash_image_request.dart';
|
||||||
@@ -52,12 +48,14 @@ abstract class ImageRequest {
|
|||||||
|
|
||||||
final int actualWidth;
|
final int actualWidth;
|
||||||
final int actualHeight;
|
final int actualHeight;
|
||||||
|
final int rowBytes;
|
||||||
final int actualSize;
|
final int actualSize;
|
||||||
final ui.ImmutableBuffer buffer;
|
final ui.ImmutableBuffer buffer;
|
||||||
try {
|
try {
|
||||||
actualWidth = info['width']!;
|
actualWidth = info['width']!;
|
||||||
actualHeight = info['height']!;
|
actualHeight = info['height']!;
|
||||||
actualSize = actualWidth * actualHeight * 4;
|
rowBytes = info['rowBytes'] ?? actualWidth * 4;
|
||||||
|
actualSize = rowBytes * actualHeight;
|
||||||
buffer = await ImmutableBuffer.fromUint8List(pointer.asTypedList(actualSize));
|
buffer = await ImmutableBuffer.fromUint8List(pointer.asTypedList(actualSize));
|
||||||
} finally {
|
} finally {
|
||||||
malloc.free(pointer);
|
malloc.free(pointer);
|
||||||
@@ -72,6 +70,7 @@ abstract class ImageRequest {
|
|||||||
buffer,
|
buffer,
|
||||||
width: actualWidth,
|
width: actualWidth,
|
||||||
height: actualHeight,
|
height: actualHeight,
|
||||||
|
rowBytes: rowBytes,
|
||||||
pixelFormat: ui.PixelFormat.rgba8888,
|
pixelFormat: ui.PixelFormat.rgba8888,
|
||||||
);
|
);
|
||||||
final codec = await descriptor.instantiateCodec();
|
final codec = await descriptor.instantiateCodec();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class LocalImageRequest extends ImageRequest {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<String, int> info = await thumbnailApi.requestImage(
|
final Map<String, int> info = await localImageApi.requestImage(
|
||||||
localId,
|
localId,
|
||||||
requestId: requestId,
|
requestId: requestId,
|
||||||
width: width,
|
width: width,
|
||||||
@@ -30,6 +30,6 @@ class LocalImageRequest extends ImageRequest {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> _onCancelled() {
|
Future<void> _onCancelled() {
|
||||||
return thumbnailApi.cancelImageRequest(requestId);
|
return localImageApi.cancelRequest(requestId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,8 @@
|
|||||||
part of 'image_request.dart';
|
part of 'image_request.dart';
|
||||||
|
|
||||||
class RemoteImageRequest extends ImageRequest {
|
class RemoteImageRequest extends ImageRequest {
|
||||||
static final _client = const NetworkRepository().getHttpClient(
|
|
||||||
'thumbnails',
|
|
||||||
diskCapacity: kThumbnailDiskCacheSize,
|
|
||||||
memoryCapacity: 0,
|
|
||||||
maxConnections: 16,
|
|
||||||
cacheMode: CacheMode.disk,
|
|
||||||
);
|
|
||||||
final String uri;
|
final String uri;
|
||||||
final Map<String, String> headers;
|
final Map<String, String> headers;
|
||||||
final abortTrigger = Completer<void>();
|
|
||||||
|
|
||||||
RemoteImageRequest({required this.uri, required this.headers});
|
RemoteImageRequest({required this.uri, required this.headers});
|
||||||
|
|
||||||
@@ -20,106 +12,14 @@ class RemoteImageRequest extends ImageRequest {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
final Map<String, int> info = await remoteImageApi.requestImage(uri, headers: headers, requestId: requestId);
|
||||||
final buffer = await _downloadImage();
|
|
||||||
if (buffer == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await _decodeBuffer(buffer, decode, scale);
|
final frame = await _fromPlatformImage(info);
|
||||||
} catch (e) {
|
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
|
||||||
if (_isCancelled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ImmutableBuffer?> _downloadImage() async {
|
|
||||||
if (_isCancelled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final req = http.AbortableRequest('GET', Uri.parse(uri), abortTrigger: abortTrigger.future);
|
|
||||||
req.headers.addAll(headers);
|
|
||||||
final res = await _client.send(req);
|
|
||||||
if (_isCancelled) {
|
|
||||||
_onCancelled();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.statusCode != 200) {
|
|
||||||
throw Exception('Failed to download $uri: ${res.statusCode}');
|
|
||||||
}
|
|
||||||
|
|
||||||
final stream = res.stream.map((chunk) {
|
|
||||||
if (_isCancelled) {
|
|
||||||
throw StateError('Cancelled request');
|
|
||||||
}
|
|
||||||
return chunk;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
final Uint8List bytes = await _downloadBytes(stream, res.contentLength ?? -1);
|
|
||||||
if (_isCancelled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return await ImmutableBuffer.fromUint8List(bytes);
|
|
||||||
} catch (e) {
|
|
||||||
if (_isCancelled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Uint8List> _downloadBytes(Stream<List<int>> stream, int length) async {
|
|
||||||
final Uint8List bytes;
|
|
||||||
int offset = 0;
|
|
||||||
if (length > 0) {
|
|
||||||
// Known content length - use pre-allocated buffer
|
|
||||||
bytes = Uint8List(length);
|
|
||||||
await stream.listen((chunk) {
|
|
||||||
bytes.setAll(offset, chunk);
|
|
||||||
offset += chunk.length;
|
|
||||||
}, cancelOnError: true).asFuture();
|
|
||||||
} else {
|
|
||||||
// Unknown content length - collect chunks dynamically
|
|
||||||
final chunks = <List<int>>[];
|
|
||||||
int totalLength = 0;
|
|
||||||
await stream.listen((chunk) {
|
|
||||||
chunks.add(chunk);
|
|
||||||
totalLength += chunk.length;
|
|
||||||
}, cancelOnError: true).asFuture();
|
|
||||||
|
|
||||||
bytes = Uint8List(totalLength);
|
|
||||||
for (final chunk in chunks) {
|
|
||||||
bytes.setAll(offset, chunk);
|
|
||||||
offset += chunk.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ImageInfo?> _decodeBuffer(ImmutableBuffer buffer, ImageDecoderCallback decode, scale) async {
|
|
||||||
if (_isCancelled) {
|
|
||||||
buffer.dispose();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final codec = await decode(buffer);
|
|
||||||
if (_isCancelled) {
|
|
||||||
buffer.dispose();
|
|
||||||
codec.dispose();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final frame = await codec.getNextFrame();
|
|
||||||
return ImageInfo(image: frame.image, scale: scale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void _onCancelled() {
|
Future<void> _onCancelled() {
|
||||||
abortTrigger.complete();
|
return localImageApi.cancelRequest(requestId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class ThumbhashImageRequest extends ImageRequest {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<String, int> info = await thumbnailApi.getThumbhash(thumbhash);
|
final Map<String, int> info = await localImageApi.getThumbhash(thumbhash);
|
||||||
final frame = await _fromPlatformImage(info);
|
final frame = await _fromPlatformImage(info);
|
||||||
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
|
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
|
||||||
}
|
}
|
||||||
|
|||||||
+142
@@ -0,0 +1,142 @@
|
|||||||
|
// Autogenerated from Pigeon (v26.0.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".',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
|
const _PigeonCodec();
|
||||||
|
@override
|
||||||
|
void writeValue(WriteBuffer buffer, Object? value) {
|
||||||
|
if (value is int) {
|
||||||
|
buffer.putUint8(4);
|
||||||
|
buffer.putInt64(value);
|
||||||
|
} else {
|
||||||
|
super.writeValue(buffer, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||||
|
switch (type) {
|
||||||
|
default:
|
||||||
|
return super.readValueOfType(type, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalImageApi {
|
||||||
|
/// Constructor for [LocalImageApi]. 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.
|
||||||
|
LocalImageApi({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<Map<String, int>> requestImage(
|
||||||
|
String assetId, {
|
||||||
|
required int requestId,
|
||||||
|
required int width,
|
||||||
|
required int height,
|
||||||
|
required bool isVideo,
|
||||||
|
}) async {
|
||||||
|
final String pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.immich_mobile.LocalImageApi.requestImage$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[
|
||||||
|
assetId,
|
||||||
|
requestId,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
isVideo,
|
||||||
|
]);
|
||||||
|
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 Map<Object?, Object?>?)!.cast<String, int>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> cancelRequest(int requestId) async {
|
||||||
|
final String pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.immich_mobile.LocalImageApi.cancelRequest$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[requestId]);
|
||||||
|
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<Map<String, int>> getThumbhash(String thumbhash) async {
|
||||||
|
final String pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.immich_mobile.LocalImageApi.getThumbhash$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[thumbhash]);
|
||||||
|
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 Map<Object?, Object?>?)!.cast<String, int>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+106
@@ -0,0 +1,106 @@
|
|||||||
|
// Autogenerated from Pigeon (v26.0.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".',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
|
const _PigeonCodec();
|
||||||
|
@override
|
||||||
|
void writeValue(WriteBuffer buffer, Object? value) {
|
||||||
|
if (value is int) {
|
||||||
|
buffer.putUint8(4);
|
||||||
|
buffer.putInt64(value);
|
||||||
|
} else {
|
||||||
|
super.writeValue(buffer, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||||
|
switch (type) {
|
||||||
|
default:
|
||||||
|
return super.readValueOfType(type, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoteImageApi {
|
||||||
|
/// Constructor for [RemoteImageApi]. 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.
|
||||||
|
RemoteImageApi({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<Map<String, int>> requestImage(
|
||||||
|
String url, {
|
||||||
|
required Map<String, String> headers,
|
||||||
|
required int requestId,
|
||||||
|
}) async {
|
||||||
|
final String pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.immich_mobile.RemoteImageApi.requestImage$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[url, headers, requestId]);
|
||||||
|
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 Map<Object?, Object?>?)!.cast<String, int>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> cancelRequest(int requestId) async {
|
||||||
|
final String pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.immich_mobile.RemoteImageApi.cancelRequest$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[requestId]);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,8 @@ import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
|||||||
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/connectivity_api.g.dart';
|
import 'package:immich_mobile/platform/connectivity_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/thumbnail_api.g.dart';
|
import 'package:immich_mobile/platform/local_image_api.g.dart';
|
||||||
|
import 'package:immich_mobile/platform/remote_image_api.g.dart';
|
||||||
|
|
||||||
final backgroundWorkerFgServiceProvider = Provider((_) => BackgroundWorkerFgService(BackgroundWorkerFgHostApi()));
|
final backgroundWorkerFgServiceProvider = Provider((_) => BackgroundWorkerFgService(BackgroundWorkerFgHostApi()));
|
||||||
|
|
||||||
@@ -16,4 +17,6 @@ final nativeSyncApiProvider = Provider<NativeSyncApi>((_) => NativeSyncApi());
|
|||||||
|
|
||||||
final connectivityApiProvider = Provider<ConnectivityApi>((_) => ConnectivityApi());
|
final connectivityApiProvider = Provider<ConnectivityApi>((_) => ConnectivityApi());
|
||||||
|
|
||||||
final thumbnailApi = ThumbnailApi();
|
final localImageApi = LocalImageApi();
|
||||||
|
|
||||||
|
final remoteImageApi = RemoteImageApi();
|
||||||
|
|||||||
+4
-2
@@ -7,12 +7,14 @@ build:
|
|||||||
|
|
||||||
pigeon:
|
pigeon:
|
||||||
dart run pigeon --input pigeon/native_sync_api.dart
|
dart run pigeon --input pigeon/native_sync_api.dart
|
||||||
dart run pigeon --input pigeon/thumbnail_api.dart
|
dart run pigeon --input pigeon/local_image_api.dart
|
||||||
|
dart run pigeon --input pigeon/remote_image_api.dart
|
||||||
dart run pigeon --input pigeon/background_worker_api.dart
|
dart run pigeon --input pigeon/background_worker_api.dart
|
||||||
dart run pigeon --input pigeon/background_worker_lock_api.dart
|
dart run pigeon --input pigeon/background_worker_lock_api.dart
|
||||||
dart run pigeon --input pigeon/connectivity_api.dart
|
dart run pigeon --input pigeon/connectivity_api.dart
|
||||||
dart format lib/platform/native_sync_api.g.dart
|
dart format lib/platform/native_sync_api.g.dart
|
||||||
dart format lib/platform/thumbnail_api.g.dart
|
dart format lib/platform/local_image_api.g.dart
|
||||||
|
dart format lib/platform/remote_image_api.g.dart
|
||||||
dart format lib/platform/background_worker_api.g.dart
|
dart format lib/platform/background_worker_api.g.dart
|
||||||
dart format lib/platform/background_worker_lock_api.g.dart
|
dart format lib/platform/background_worker_lock_api.g.dart
|
||||||
dart format lib/platform/connectivity_api.g.dart
|
dart format lib/platform/connectivity_api.g.dart
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ import 'package:pigeon/pigeon.dart';
|
|||||||
|
|
||||||
@ConfigurePigeon(
|
@ConfigurePigeon(
|
||||||
PigeonOptions(
|
PigeonOptions(
|
||||||
dartOut: 'lib/platform/thumbnail_api.g.dart',
|
dartOut: 'lib/platform/local_image_api.g.dart',
|
||||||
swiftOut: 'ios/Runner/Images/Thumbnails.g.swift',
|
swiftOut: 'ios/Runner/Images/LocalImages.g.swift',
|
||||||
swiftOptions: SwiftOptions(includeErrorClass: false),
|
swiftOptions: SwiftOptions(includeErrorClass: false),
|
||||||
kotlinOut:
|
kotlinOut:
|
||||||
'android/app/src/main/kotlin/app/alextran/immich/images/Thumbnails.g.kt',
|
'android/app/src/main/kotlin/app/alextran/immich/images/LocalImages.g.kt',
|
||||||
kotlinOptions: KotlinOptions(package: 'app.alextran.immich.images'),
|
kotlinOptions: KotlinOptions(package: 'app.alextran.immich.images'),
|
||||||
dartOptions: DartOptions(),
|
dartOptions: DartOptions(),
|
||||||
dartPackageName: 'immich_mobile',
|
dartPackageName: 'immich_mobile',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@HostApi()
|
@HostApi()
|
||||||
abstract class ThumbnailApi {
|
abstract class LocalImageApi {
|
||||||
@async
|
@async
|
||||||
Map<String, int> requestImage(
|
Map<String, int> requestImage(
|
||||||
String assetId, {
|
String assetId, {
|
||||||
@@ -23,7 +23,7 @@ abstract class ThumbnailApi {
|
|||||||
required bool isVideo,
|
required bool isVideo,
|
||||||
});
|
});
|
||||||
|
|
||||||
void cancelImageRequest(int requestId);
|
void cancelRequest(int requestId);
|
||||||
|
|
||||||
@async
|
@async
|
||||||
Map<String, int> getThumbhash(String thumbhash);
|
Map<String, int> getThumbhash(String thumbhash);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import 'package:pigeon/pigeon.dart';
|
||||||
|
|
||||||
|
@ConfigurePigeon(
|
||||||
|
PigeonOptions(
|
||||||
|
dartOut: 'lib/platform/remote_image_api.g.dart',
|
||||||
|
swiftOut: 'ios/Runner/Images/RemoteImages.g.swift',
|
||||||
|
swiftOptions: SwiftOptions(includeErrorClass: false),
|
||||||
|
kotlinOut:
|
||||||
|
'android/app/src/main/kotlin/app/alextran/immich/images/RemoteImages.g.kt',
|
||||||
|
kotlinOptions: KotlinOptions(package: 'app.alextran.immich.images', includeErrorClass: false),
|
||||||
|
dartOptions: DartOptions(),
|
||||||
|
dartPackageName: 'immich_mobile',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@HostApi()
|
||||||
|
abstract class RemoteImageApi {
|
||||||
|
@async
|
||||||
|
Map<String, int> requestImage(
|
||||||
|
String url, {
|
||||||
|
required Map<String, String> headers,
|
||||||
|
required int requestId,
|
||||||
|
});
|
||||||
|
|
||||||
|
void cancelRequest(int requestId);
|
||||||
|
}
|
||||||
+4
-4
@@ -1249,10 +1249,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.17.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1942,10 +1942,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.6"
|
version: "0.7.7"
|
||||||
thumbhash:
|
thumbhash:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
Reference in New Issue
Block a user