diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml
index 436d8c492d..1b8d2a97fb 100644
--- a/mobile/android/app/src/main/AndroidManifest.xml
+++ b/mobile/android/app/src/main/AndroidManifest.xml
@@ -89,6 +89,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt
index b4cd705b05..fc9ab28fa2 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt
@@ -1,6 +1,7 @@
package app.alextran.immich
import android.content.Context
+import android.content.Intent
import android.os.Build
import android.os.ext.SdkExtensions
import app.alextran.immich.background.BackgroundEngineLock
@@ -22,6 +23,7 @@ import app.alextran.immich.permission.PermissionApiImpl
import app.alextran.immich.sync.NativeSyncApi
import app.alextran.immich.sync.NativeSyncApiImpl26
import app.alextran.immich.sync.NativeSyncApiImpl30
+import app.alextran.immich.viewintent.ViewIntentPlugin
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
@@ -31,6 +33,11 @@ class MainActivity : FlutterFragmentActivity() {
registerPlugins(this, flutterEngine)
}
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ setIntent(intent)
+ }
+
companion object {
fun registerPlugins(ctx: Context, flutterEngine: FlutterEngine) {
HttpClientManager.initialize(ctx)
@@ -55,6 +62,7 @@ class MainActivity : FlutterFragmentActivity() {
BackgroundWorkerFgHostApi.setUp(messenger, BackgroundWorkerApiImpl(ctx))
ConnectivityApi.setUp(messenger, ConnectivityApiImpl(ctx))
+ flutterEngine.plugins.add(ViewIntentPlugin())
flutterEngine.plugins.add(backgroundEngineLockImpl)
flutterEngine.plugins.add(nativeSyncApiImpl)
flutterEngine.plugins.add(permissionApiImpl)
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntent.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntent.g.kt
new file mode 100644
index 0000000000..1d5af15cb4
--- /dev/null
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntent.g.kt
@@ -0,0 +1,292 @@
+// Autogenerated from Pigeon (v26.3.4), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
+
+package app.alextran.immich.viewintent
+
+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 ViewIntentPigeonUtils {
+
+ fun wrapResult(result: Any?): List {
+ return listOf(result)
+ }
+
+ fun wrapError(exception: Throwable): List {
+ return if (exception is FlutterError) {
+ listOf(
+ exception.code,
+ exception.message,
+ exception.details
+ )
+ } else {
+ listOf(
+ exception.javaClass.simpleName,
+ exception.toString(),
+ "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
+ )
+ }
+ }
+ fun doubleEquals(a: Double, b: Double): Boolean {
+ // Normalize -0.0 to 0.0 and handle NaN equality.
+ return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN())
+ }
+
+ fun floatEquals(a: Float, b: Float): Boolean {
+ // Normalize -0.0 to 0.0 and handle NaN equality.
+ return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN())
+ }
+
+ fun doubleHash(d: Double): Int {
+ // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
+ val normalized = if (d == 0.0) 0.0 else d
+ val bits = java.lang.Double.doubleToLongBits(normalized)
+ return (bits xor (bits ushr 32)).toInt()
+ }
+
+ fun floatHash(f: Float): Int {
+ // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
+ val normalized = if (f == 0.0f) 0.0f else f
+ return java.lang.Float.floatToIntBits(normalized)
+ }
+
+ fun deepEquals(a: Any?, b: Any?): Boolean {
+ if (a === b) {
+ return true
+ }
+ if (a == null || b == null) {
+ return false
+ }
+ 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) {
+ if (a.size != b.size) return false
+ for (i in a.indices) {
+ if (!doubleEquals(a[i], b[i])) return false
+ }
+ return true
+ }
+ if (a is FloatArray && b is FloatArray) {
+ if (a.size != b.size) return false
+ for (i in a.indices) {
+ if (!floatEquals(a[i], b[i])) return false
+ }
+ return true
+ }
+ if (a is Array<*> && b is Array<*>) {
+ if (a.size != b.size) return false
+ for (i in a.indices) {
+ if (!deepEquals(a[i], b[i])) return false
+ }
+ return true
+ }
+ if (a is List<*> && b is List<*>) {
+ if (a.size != b.size) return false
+ val iterA = a.iterator()
+ val iterB = b.iterator()
+ while (iterA.hasNext() && iterB.hasNext()) {
+ if (!deepEquals(iterA.next(), iterB.next())) return false
+ }
+ return true
+ }
+ if (a is Map<*, *> && b is Map<*, *>) {
+ if (a.size != b.size) return false
+ for (entry in a) {
+ val key = entry.key
+ var found = false
+ for (bEntry in b) {
+ if (deepEquals(key, bEntry.key)) {
+ if (deepEquals(entry.value, bEntry.value)) {
+ found = true
+ break
+ } else {
+ return false
+ }
+ }
+ }
+ if (!found) return false
+ }
+ return true
+ }
+ if (a is Double && b is Double) {
+ return doubleEquals(a, b)
+ }
+ if (a is Float && b is Float) {
+ return floatEquals(a, b)
+ }
+ return a == b
+ }
+
+ fun deepHash(value: Any?): Int {
+ return when (value) {
+ null -> 0
+ is ByteArray -> value.contentHashCode()
+ is IntArray -> value.contentHashCode()
+ is LongArray -> value.contentHashCode()
+ is DoubleArray -> {
+ var result = 1
+ for (item in value) {
+ result = 31 * result + doubleHash(item)
+ }
+ result
+ }
+ is FloatArray -> {
+ var result = 1
+ for (item in value) {
+ result = 31 * result + floatHash(item)
+ }
+ result
+ }
+ is Array<*> -> {
+ var result = 1
+ for (item in value) {
+ result = 31 * result + deepHash(item)
+ }
+ result
+ }
+ is List<*> -> {
+ var result = 1
+ for (item in value) {
+ result = 31 * result + deepHash(item)
+ }
+ result
+ }
+ is Map<*, *> -> {
+ var result = 0
+ for (entry in value) {
+ result += ((deepHash(entry.key) * 31) xor deepHash(entry.value))
+ }
+ result
+ }
+ is Double -> doubleHash(value)
+ is Float -> floatHash(value)
+ else -> value.hashCode()
+ }
+ }
+
+}
+
+/**
+ * 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
+) : RuntimeException()
+
+/** Generated class from Pigeon that represents data sent in messages. */
+data class ViewIntentPayload (
+ val path: String? = null,
+ val mimeType: String,
+ val localAssetId: String? = null
+)
+ {
+ companion object {
+ fun fromList(pigeonVar_list: List): ViewIntentPayload {
+ val path = pigeonVar_list[0] as String?
+ val mimeType = pigeonVar_list[1] as String
+ val localAssetId = pigeonVar_list[2] as String?
+ return ViewIntentPayload(path, mimeType, localAssetId)
+ }
+ }
+ fun toList(): List {
+ return listOf(
+ path,
+ mimeType,
+ localAssetId,
+ )
+ }
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other.javaClass != javaClass) {
+ return false
+ }
+ if (this === other) {
+ return true
+ }
+ val other = other as ViewIntentPayload
+ return ViewIntentPigeonUtils.deepEquals(this.path, other.path) && ViewIntentPigeonUtils.deepEquals(this.mimeType, other.mimeType) && ViewIntentPigeonUtils.deepEquals(this.localAssetId, other.localAssetId)
+ }
+
+ override fun hashCode(): Int {
+ var result = javaClass.hashCode()
+ result = 31 * result + ViewIntentPigeonUtils.deepHash(this.path)
+ result = 31 * result + ViewIntentPigeonUtils.deepHash(this.mimeType)
+ result = 31 * result + ViewIntentPigeonUtils.deepHash(this.localAssetId)
+ return result
+ }
+}
+private open class ViewIntentPigeonCodec : StandardMessageCodec() {
+ override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
+ return when (type) {
+ 129.toByte() -> {
+ return (readValue(buffer) as? List)?.let {
+ ViewIntentPayload.fromList(it)
+ }
+ }
+ else -> super.readValueOfType(type, buffer)
+ }
+ }
+ override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
+ when (value) {
+ is ViewIntentPayload -> {
+ stream.write(129)
+ writeValue(stream, value.toList())
+ }
+ else -> super.writeValue(stream, value)
+ }
+ }
+}
+
+
+/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
+interface ViewIntentHostApi {
+ fun consumeViewIntent(callback: (Result) -> Unit)
+
+ companion object {
+ /** The codec used by ViewIntentHostApi. */
+ val codec: MessageCodec by lazy {
+ ViewIntentPigeonCodec()
+ }
+ /** Sets up an instance of `ViewIntentHostApi` to handle messages through the `binaryMessenger`. */
+ @JvmOverloads
+ fun setUp(binaryMessenger: BinaryMessenger, api: ViewIntentHostApi?, messageChannelSuffix: String = "") {
+ val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ViewIntentHostApi.consumeViewIntent$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { _, reply ->
+ api.consumeViewIntent{ result: Result ->
+ val error = result.exceptionOrNull()
+ if (error != null) {
+ reply.reply(ViewIntentPigeonUtils.wrapError(error))
+ } else {
+ val data = result.getOrNull()
+ reply.reply(ViewIntentPigeonUtils.wrapResult(data))
+ }
+ }
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ }
+ }
+}
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt
new file mode 100644
index 0000000000..a1e1fea3dd
--- /dev/null
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt
@@ -0,0 +1,201 @@
+package app.alextran.immich.viewintent
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.provider.DocumentsContract
+import android.provider.MediaStore
+import android.provider.OpenableColumns
+import android.util.Log
+import android.webkit.MimeTypeMap
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+import io.flutter.embedding.engine.plugins.activity.ActivityAware
+import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
+import io.flutter.plugin.common.PluginRegistry
+import java.io.File
+import java.io.FileOutputStream
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+
+private const val TAG = "ViewIntentPlugin"
+
+class ViewIntentPlugin : FlutterPlugin, ActivityAware, PluginRegistry.NewIntentListener, ViewIntentHostApi {
+ private var context: Context? = null
+ private var activity: Activity? = null
+ private var unconsumedIntent: Intent? = null
+ private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
+
+ override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+ context = binding.applicationContext
+ ViewIntentHostApi.setUp(binding.binaryMessenger, this)
+ }
+
+ override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+ ViewIntentHostApi.setUp(binding.binaryMessenger, null)
+ ioScope.cancel()
+ context = null
+ }
+
+ override fun onAttachedToActivity(binding: ActivityPluginBinding) {
+ activity = binding.activity
+ unconsumedIntent = binding.activity.intent
+ binding.addOnNewIntentListener(this)
+ }
+
+ override fun onDetachedFromActivityForConfigChanges() {
+ activity = null
+ }
+
+ override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
+ onAttachedToActivity(binding)
+ }
+
+ override fun onDetachedFromActivity() {
+ activity = null
+ }
+
+ override fun onNewIntent(intent: Intent): Boolean {
+ unconsumedIntent = intent
+ return false
+ }
+
+ override fun consumeViewIntent(callback: (Result) -> Unit) {
+ val context = context ?: run {
+ callback(Result.success(null))
+ return
+ }
+ val intent = unconsumedIntent ?: activity?.intent
+
+ if (intent?.action != Intent.ACTION_VIEW) {
+ callback(Result.success(null))
+ return
+ }
+
+ val uri = intent.data
+ if (uri == null) {
+ callback(Result.success(null))
+ return
+ }
+
+ ioScope.launch {
+ try {
+ val mimeType = context.contentResolver.getType(uri) ?: intent.type
+ if (mimeType == null || (!mimeType.startsWith("image/") && !mimeType.startsWith("video/"))) {
+ callback(Result.success(null))
+ return@launch
+ }
+
+ val localAssetId = extractLocalAssetId(context, uri, mimeType)
+ val tempFilePath = if (localAssetId == null) {
+ copyUriToTempFile(context, uri, mimeType)?.absolutePath ?: run {
+ callback(Result.success(null))
+ return@launch
+ }
+ } else {
+ null
+ }
+ val payload = ViewIntentPayload(
+ path = tempFilePath,
+ mimeType = mimeType,
+ localAssetId = localAssetId,
+ )
+ consumeViewIntent(intent)
+ callback(Result.success(payload))
+ } catch (e: Exception) {
+ callback(Result.failure(e))
+ }
+ }
+ }
+
+ private fun consumeViewIntent(currentIntent: Intent) {
+ unconsumedIntent = Intent(currentIntent).apply {
+ action = null
+ data = null
+ type = null
+ }
+ activity?.intent = unconsumedIntent
+ }
+
+ private fun extractLocalAssetId(context: Context, uri: Uri, mimeType: String): String? {
+ return tryExtractDocumentLocalAssetId(context, uri)
+ ?: tryParseContentUriId(uri)
+ ?: resolveLocalIdByNameAndSize(context, uri, mimeType)
+ }
+
+ private fun tryExtractDocumentLocalAssetId(context: Context, uri: Uri): String? {
+ return try {
+ if (!DocumentsContract.isDocumentUri(context, uri)) return null
+ val docId = DocumentsContract.getDocumentId(uri)
+ if (docId.isBlank() || docId.startsWith("raw:")) return null
+ docId.substringAfter(':', docId).toLongOrNull()?.toString()
+ } catch (e: Exception) {
+ Log.w(TAG, "Failed to resolve local asset id from document URI: $uri", e)
+ null
+ }
+ }
+
+ private fun tryParseContentUriId(uri: Uri): String? {
+ val id = uri.lastPathSegment?.toLongOrNull() ?: return null
+ return if (id >= 0) id.toString() else null
+ }
+
+ private fun copyUriToTempFile(context: Context, uri: Uri, mimeType: String): File? {
+ return try {
+ val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)?.let { ".$it" }
+ val tempFile = File.createTempFile("view_intent_", extension, context.cacheDir)
+ context.contentResolver.openInputStream(uri)?.use { inputStream ->
+ FileOutputStream(tempFile).use { outputStream ->
+ inputStream.copyTo(outputStream)
+ }
+ } ?: return null
+ tempFile
+ } catch (_: Exception) {
+ null
+ }
+ }
+
+ private fun resolveLocalIdByNameAndSize(context: Context, uri: Uri, mimeType: String): String? {
+ val metaProjection = arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)
+ val (displayName, size) =
+ try {
+ context.contentResolver.query(uri, metaProjection, null, null, null)?.use { cursor ->
+ if (!cursor.moveToFirst()) return null
+ val nameIdx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+ val sizeIdx = cursor.getColumnIndex(OpenableColumns.SIZE)
+ val name = if (nameIdx >= 0) cursor.getString(nameIdx) else null
+ val bytes = if (sizeIdx >= 0) cursor.getLong(sizeIdx) else -1L
+ if (name.isNullOrBlank() || bytes < 0) return null
+ name to bytes
+ } ?: return null
+ } catch (_: Exception) {
+ return null
+ }
+
+ val tableUri = when {
+ mimeType.startsWith("image/") -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ mimeType.startsWith("video/") -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+ else -> return null
+ }
+ return try {
+ context.contentResolver
+ .query(
+ tableUri,
+ arrayOf(MediaStore.MediaColumns._ID),
+ "${MediaStore.MediaColumns.DISPLAY_NAME}=? AND ${MediaStore.MediaColumns.SIZE}=?",
+ arrayOf(displayName, size.toString()),
+ "${MediaStore.MediaColumns.DATE_MODIFIED} DESC",
+ )?.use { cursor ->
+ if (!cursor.moveToFirst()) return null
+ val idIndex = cursor.getColumnIndex(MediaStore.MediaColumns._ID)
+ if (idIndex < 0) return null
+ cursor.getLong(idIndex).toString()
+ }
+ } catch (_: Exception) {
+ null
+ }
+ }
+}
diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart
index cc5f131572..75f1c2221a 100644
--- a/mobile/lib/main.dart
+++ b/mobile/lib/main.dart
@@ -24,6 +24,7 @@ import 'package:immich_mobile/pages/common/splash_screen.page.dart';
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
+import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
@@ -128,6 +129,7 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve
case AppLifecycleState.resumed:
dPrint(() => "[APP STATE] resumed");
ref.read(appStateProvider.notifier).handleAppResume();
+ unawaited(ref.read(viewIntentHandlerProvider).onAppResumed());
break;
case AppLifecycleState.inactive:
dPrint(() => "[APP STATE] inactive");
@@ -233,6 +235,7 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve
}
});
+ ref.read(viewIntentHandlerProvider).init();
ref.read(shareIntentUploadProvider.notifier).init();
}
diff --git a/mobile/lib/models/view_intent/view_intent_payload.extension.dart b/mobile/lib/models/view_intent/view_intent_payload.extension.dart
new file mode 100644
index 0000000000..ca66e6a163
--- /dev/null
+++ b/mobile/lib/models/view_intent/view_intent_payload.extension.dart
@@ -0,0 +1,35 @@
+import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
+import 'package:immich_mobile/platform/view_intent_api.g.dart';
+import 'package:path/path.dart';
+
+extension ViewIntentPayloadX on ViewIntentPayload {
+ String get fileName {
+ final resolvedPath = path;
+ if (resolvedPath != null && resolvedPath.isNotEmpty) {
+ return basename(resolvedPath);
+ }
+ return localAssetId ?? 'view_intent_asset';
+ }
+
+ bool get isImage => mimeType.toLowerCase().startsWith('image/');
+
+ bool get isVideo => mimeType.toLowerCase().startsWith('video/');
+
+ AssetPlaybackStyle get playbackStyle {
+ if (isVideo) {
+ return AssetPlaybackStyle.video;
+ }
+
+ final normalizedMimeType = mimeType.toLowerCase();
+ if (normalizedMimeType == 'image/gif' || normalizedMimeType == 'image/webp') {
+ return AssetPlaybackStyle.imageAnimated;
+ }
+
+ final normalizedPath = path?.toLowerCase();
+ if (normalizedPath != null && (normalizedPath.endsWith('.gif') || normalizedPath.endsWith('.webp'))) {
+ return AssetPlaybackStyle.imageAnimated;
+ }
+
+ return AssetPlaybackStyle.image;
+ }
+}
diff --git a/mobile/lib/pages/common/splash_screen.page.dart b/mobile/lib/pages/common/splash_screen.page.dart
index aaa9fffc05..de6fda5773 100644
--- a/mobile/lib/pages/common/splash_screen.page.dart
+++ b/mobile/lib/pages/common/splash_screen.page.dart
@@ -17,6 +17,7 @@ import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
+import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart';
import 'package:immich_mobile/providers/websocket.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/theme/color_scheme.dart';
@@ -314,6 +315,7 @@ class SplashScreenPageState extends ConsumerState {
final wsProvider = ref.read(websocketProvider.notifier);
final backgroundManager = ref.read(backgroundSyncProvider);
final backupProvider = ref.read(driftBackupProvider.notifier);
+ final viewIntentHandler = ref.read(viewIntentHandlerProvider);
unawaited(
ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then(
@@ -328,6 +330,8 @@ class SplashScreenPageState extends ConsumerState {
backgroundManager.syncRemote().then((success) => syncSuccess = success),
]);
+ await viewIntentHandler.flushDeferredViewIntent();
+
if (syncSuccess) {
await Future.wait([
backgroundManager.hashAssets().then((_) {
diff --git a/mobile/lib/platform/view_intent_api.g.dart b/mobile/lib/platform/view_intent_api.g.dart
new file mode 100644
index 0000000000..d457c249de
--- /dev/null
+++ b/mobile/lib/platform/view_intent_api.g.dart
@@ -0,0 +1,191 @@
+// Autogenerated from Pigeon (v26.3.4), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+// ignore_for_file: unused_import, unused_shown_name
+// ignore_for_file: type=lint
+
+import 'dart:async';
+import 'dart:typed_data' show Float64List, Int32List, Int64List;
+
+import 'package:flutter/services.dart';
+import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
+
+Object? _extractReplyValueOrThrow(List