diff --git a/doc/ios-share-extension-improvements.md b/doc/ios-share-extension-improvements.md new file mode 100644 index 00000000..6ef6e3cb --- /dev/null +++ b/doc/ios-share-extension-improvements.md @@ -0,0 +1,283 @@ +# iOS Share Extension Improvements + +**Date:** 2025-11-24 +**Purpose:** Explore alternatives to improve user experience by eliminating interstitial UI and simplifying app launch mechanism + +## Current Implementation Issues + +1. **Interstitial UI**: Users see `SLComposeServiceViewController` with a "Post" button before the app opens +2. **Deep Link Dependency**: App relies on deep link (`timesafari://shared-photo`) to detect shared images, even though data is already in App Group + +## Improvement 1: Skip Interstitial UI + +### Current Approach +- Uses `SLComposeServiceViewController` which shows a UI with "Post" button +- User must tap "Post" to proceed + +### Alternative: Custom UIViewController (Headless Processing) + +Replace `SLComposeServiceViewController` with a custom `UIViewController` that: +- Processes the image immediately in `viewDidLoad` +- Shows no UI (or minimal loading indicator) +- Opens the app automatically + +**Implementation:** + +```swift +import UIKit +import UniformTypeIdentifiers + +class ShareViewController: UIViewController { + + private let appGroupIdentifier = "group.app.timesafari" + private let sharedPhotoBase64Key = "sharedPhotoBase64" + private let sharedPhotoFileNameKey = "sharedPhotoFileName" + + override func viewDidLoad() { + super.viewDidLoad() + + // Process image immediately without showing UI + processAndOpenApp() + } + + private func processAndOpenApp() { + guard let extensionContext = extensionContext, + let inputItems = extensionContext.inputItems as? [NSExtensionItem] else { + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) + return + } + + processSharedImage(from: inputItems) { [weak self] success in + guard let self = self else { + self?.extensionContext?.completeRequest(returningItems: [], completionHandler: nil) + return + } + + if success { + self.openMainApp() + } + + // Complete immediately - no UI shown + self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil) + } + } + + private func processSharedImage(from items: [NSExtensionItem], completion: @escaping (Bool) -> Void) { + // ... (same implementation as current) + } + + private func openMainApp() { + guard let url = URL(string: "timesafari://shared-photo") else { + return + } + + var responder: UIResponder? = self + while responder != nil { + if let application = responder as? UIApplication { + application.open(url, options: [:], completionHandler: nil) + return + } + responder = responder?.next + } + + extensionContext?.open(url, completionHandler: nil) + } +} +``` + +**Info.plist Changes:** +- Already configured correctly with `NSExtensionPrincipalClass` +- No storyboard needed (already removed) + +**Benefits:** +- ✅ No interstitial UI - app opens immediately +- ✅ Faster user experience +- ✅ More seamless integration + +**Considerations:** +- ⚠️ User has less control (can't cancel easily) +- ⚠️ No visual feedback during processing (could add minimal loading indicator) +- ⚠️ Apple guidelines: Extensions should provide value even if they don't open the app + +## Improvement 2: Direct App Launch Without Deep Link + +### Current Approach +- Share Extension stores data in App Group UserDefaults +- Share Extension opens app via deep link (`timesafari://shared-photo`) +- App receives deep link → checks App Group → processes image + +### Alternative: App Lifecycle Detection + +Instead of using deep links, the app can check for shared data when it becomes active: + +**Option A: Check on App Activation** + +```swift +// In AppDelegate.swift +func applicationDidBecomeActive(_ application: UIApplication) { + // Check for shared image from Share Extension + if let sharedData = getSharedImageData() { + // Store in temp file for JS to read + writeSharedImageToTempFile(sharedData) + + // Navigate to shared-photo route directly + // This would need to be handled in JS layer + } +} +``` + +**Option B: Use Notification (More Reliable)** + +```swift +// In ShareViewController.swift (after storing data) +private func openMainApp() { + // Store a flag that image is ready + guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else { + return + } + userDefaults.set(true, forKey: "sharedPhotoReady") + userDefaults.synchronize() + + // Open app (can use any URL scheme or even just launch the app) + guard let url = URL(string: "timesafari://") else { + return + } + + var responder: UIResponder? = self + while responder != nil { + if let application = responder as? UIApplication { + application.open(url, options: [:], completionHandler: nil) + return + } + responder = responder?.next + } +} + +// In AppDelegate.swift +func applicationDidBecomeActive(_ application: UIApplication) { + let appGroupIdentifier = "group.app.timesafari" + guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else { + return + } + + // Check if shared photo is ready + if userDefaults.bool(forKey: "sharedPhotoReady") { + userDefaults.removeObject(forKey: "sharedPhotoReady") + userDefaults.synchronize() + + // Process shared image + if let sharedData = getSharedImageData() { + writeSharedImageToTempFile(sharedData) + + // Trigger JS to check for shared image + // This could be done via Capacitor App plugin or custom event + } + } +} +``` + +**Option C: Check on App Launch (Most Direct)** + +```swift +// In AppDelegate.swift +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Check for shared image immediately on launch + checkForSharedImageOnLaunch() + + return true +} + +func applicationDidBecomeActive(_ application: UIApplication) { + // Also check when app becomes active (in case it was already running) + checkForSharedImageOnLaunch() +} + +private func checkForSharedImageOnLaunch() { + if let sharedData = getSharedImageData() { + writeSharedImageToTempFile(sharedData) + + // Post a notification or use Capacitor to notify JS + NotificationCenter.default.post(name: NSNotification.Name("SharedPhotoReady"), object: nil) + } +} +``` + +**JavaScript Integration:** + +```typescript +// In main.capacitor.ts +import { App } from '@capacitor/app'; + +// Listen for app becoming active +App.addListener('appStateChange', async ({ isActive }) => { + if (isActive) { + // Check for shared image when app becomes active + await checkAndStoreNativeSharedImage(); + } +}); + +// Also check on initial load +if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === 'ios') { + checkAndStoreNativeSharedImage().then(result => { + if (result.success) { + // Navigate to shared-photo route + router.push('/shared-photo' + (result.fileName ? `?fileName=${result.fileName}` : '')); + } + }); +} +``` + +**Benefits:** +- ✅ No deep link routing needed +- ✅ More direct data flow +- ✅ App can detect shared content even if it was already running +- ✅ Simpler URL scheme handling + +**Considerations:** +- ⚠️ Need to ensure app checks on both launch and activation +- ⚠️ May need to handle race conditions (app launching vs. share extension writing) +- ⚠️ Still need some way to open the app (minimal URL scheme still required) + +## Recommended Approach + +**Best of Both Worlds:** + +1. **Use Custom UIViewController** (Improvement 1) - Eliminates interstitial UI +2. **Use App Lifecycle Detection** (Improvement 2, Option C) - Direct data flow + +**Combined Implementation:** + +```swift +// ShareViewController.swift - Custom UIViewController +class ShareViewController: UIViewController { + // Process immediately in viewDidLoad + // Store data in App Group + // Open app with minimal URL (just "timesafari://") +} + +// AppDelegate.swift +func applicationDidBecomeActive(_ application: UIApplication) { + // Check for shared image + // If found, write to temp file and let JS handle navigation +} +``` + +**JavaScript:** +```typescript +// Check on app activation +App.addListener('appStateChange', async ({ isActive }) => { + if (isActive) { + const result = await checkAndStoreNativeSharedImage(); + if (result.success) { + router.push('/shared-photo' + (result.fileName ? `?fileName=${result.fileName}` : '')); + } + } +}); +``` + +This approach: +- ✅ No interstitial UI +- ✅ No deep link routing complexity +- ✅ Direct data flow via App Group +- ✅ Works whether app is running or launching fresh + diff --git a/ios/App/App/AppDelegate.swift b/ios/App/App/AppDelegate.swift index 33937d3f..a3b0d767 100644 --- a/ios/App/App/AppDelegate.swift +++ b/ios/App/App/AppDelegate.swift @@ -32,6 +32,56 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + + // Check for shared image from Share Extension when app becomes active + checkForSharedImageOnActivation() + } + + /** + * Check for shared image when app launches or becomes active + * This allows the app to detect shared images without requiring a deep link + */ + private func checkForSharedImageOnActivation() { + let appGroupIdentifier = "group.app.timesafari" + guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else { + return + } + + // Check if shared photo is ready + if userDefaults.bool(forKey: "sharedPhotoReady") { + // Clear the flag + userDefaults.removeObject(forKey: "sharedPhotoReady") + userDefaults.synchronize() + + // Get and process shared image data + if let sharedData = getSharedImageData() { + writeSharedImageToTempFile(sharedData) + + // Post notification for JavaScript to handle navigation + NotificationCenter.default.post(name: NSNotification.Name("SharedPhotoReady"), object: nil) + } + } + } + + /** + * Write shared image data to temp file for JavaScript to read + */ + private func writeSharedImageToTempFile(_ sharedData: [String: String]) { + let fileManager = FileManager.default + guard let documentsDir = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { + return + } + + let tempFileURL = documentsDir.appendingPathComponent("timesafari_shared_photo.json") + + let jsonData: [String: String] = [ + "base64": sharedData["base64"] ?? "", + "fileName": sharedData["fileName"] ?? "" + ] + + if let json = try? JSONSerialization.data(withJSONObject: jsonData, options: []) { + try? json.write(to: tempFileURL) + } } func applicationWillTerminate(_ application: UIApplication) { diff --git a/ios/App/TimeSafariShareExtension/ShareViewController.swift b/ios/App/TimeSafariShareExtension/ShareViewController.swift index 9090d128..316ccc99 100644 --- a/ios/App/TimeSafariShareExtension/ShareViewController.swift +++ b/ios/App/TimeSafariShareExtension/ShareViewController.swift @@ -6,10 +6,9 @@ // import UIKit -import Social import UniformTypeIdentifiers -class ShareViewController: SLComposeServiceViewController { +class ShareViewController: UIViewController { private let appGroupIdentifier = "group.app.timesafari" private let sharedPhotoBase64Key = "sharedPhotoBase64" @@ -18,64 +17,46 @@ class ShareViewController: SLComposeServiceViewController { override func viewDidLoad() { super.viewDidLoad() - // Set placeholder text (required for SLComposeServiceViewController) - self.placeholder = "Share image to TimeSafari" + // Set a minimal background (transparent or loading indicator) + view.backgroundColor = .systemBackground - // Validate content on load - self.validateContent() + // Process image immediately without showing UI + processAndOpenApp() } - override func isContentValid() -> Bool { - // Validate that we have image attachments - guard let extensionContext = extensionContext else { - return false - } - - guard let inputItems = extensionContext.inputItems as? [NSExtensionItem] else { - return false - } - - for item in inputItems { - if let attachments = item.attachments { - for attachment in attachments { - if attachment.hasItemConformingToTypeIdentifier(UTType.image.identifier) { - return true - } - } - } - } - - return false - } - - override func didSelectPost() { - // Extract and process the shared image - guard let extensionContext = extensionContext else { + private func processAndOpenApp() { + // extensionContext is automatically available on UIViewController when used as extension principal class + guard let context = extensionContext, + let inputItems = context.inputItems as? [NSExtensionItem] else { + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) return } - guard let inputItems = extensionContext.inputItems as? [NSExtensionItem] else { - extensionContext.completeRequest(returningItems: [], completionHandler: nil) - return - } - - // Process the first image found processSharedImage(from: inputItems) { [weak self] success in - guard let self = self else { - extensionContext.completeRequest(returningItems: [], completionHandler: nil) + guard let self = self, let context = self.extensionContext else { return } if success { - // Open the main app via deep link + // Set flag that shared photo is ready + self.setSharedPhotoReadyFlag() + // Open the main app (using minimal URL - app will detect shared data on activation) self.openMainApp() } - // Complete the extension context - extensionContext.completeRequest(returningItems: [], completionHandler: nil) + // Complete immediately - no UI shown + context.completeRequest(returningItems: [], completionHandler: nil) } } + private func setSharedPhotoReadyFlag() { + guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else { + return + } + userDefaults.set(true, forKey: "sharedPhotoReady") + userDefaults.synchronize() + } + private func processSharedImage(from items: [NSExtensionItem], completion: @escaping (Bool) -> Void) { // Find the first image attachment for item in items { @@ -144,8 +125,8 @@ class ShareViewController: SLComposeServiceViewController { } private func openMainApp() { - // Open the main app via deep link - guard let url = URL(string: "timesafari://shared-photo") else { + // Open the main app with minimal URL - app will detect shared data on activation + guard let url = URL(string: "timesafari://") else { return } @@ -162,9 +143,4 @@ class ShareViewController: SLComposeServiceViewController { extensionContext?.open(url, completionHandler: nil) } - override func configurationItems() -> [Any]! { - // No additional configuration options needed - return [] - } - } diff --git a/src/interfaces/deepLinks.ts b/src/interfaces/deepLinks.ts index 60c0826c..0fe5c68d 100644 --- a/src/interfaces/deepLinks.ts +++ b/src/interfaces/deepLinks.ts @@ -68,7 +68,6 @@ export const deepLinkPathSchemas = { "user-profile": z.object({ id: z.string(), }), - "shared-photo": z.object({}), }; export const deepLinkQuerySchemas = { diff --git a/src/main.capacitor.ts b/src/main.capacitor.ts index 3ef253a5..8045b048 100644 --- a/src/main.capacitor.ts +++ b/src/main.capacitor.ts @@ -55,6 +55,9 @@ window.addEventListener("unhandledrejection", (event) => { const deepLinkHandler = new DeepLinkHandler(router); +// Lock to prevent duplicate processing of shared images +let isProcessingSharedImage = false; + /** * Handles deep link routing for the application * Processes URLs in the format timesafari:/// @@ -79,11 +82,22 @@ async function checkAndStoreNativeSharedImage(): Promise<{ success: boolean; fileName?: string; }> { - if (!Capacitor.isNativePlatform() || Capacitor.getPlatform() !== "ios") { + // Prevent duplicate processing + if (isProcessingSharedImage) { + logger.debug( + "[Main] ⏸️ Shared image processing already in progress, skipping", + ); return { success: false }; } + isProcessingSharedImage = true; + try { + if (!Capacitor.isNativePlatform() || Capacitor.getPlatform() !== "ios") { + isProcessingSharedImage = false; + return { success: false }; + } + logger.debug("[Main] Checking for iOS shared image from App Group"); // Use Capacitor's native bridge to call the ShareImagePlugin @@ -92,6 +106,7 @@ async function checkAndStoreNativeSharedImage(): Promise<{ if (!capacitor || !capacitor.Plugins) { logger.debug("[Main] Capacitor plugins not available"); + isProcessingSharedImage = false; return { success: false }; } @@ -117,6 +132,19 @@ async function checkAndStoreNativeSharedImage(): Promise<{ ); const platformService = PlatformServiceFactory.getInstance(); + // Clear old image from temp DB first to ensure we get the new one + try { + await platformService.dbExec("DELETE FROM temp WHERE id = ?", [ + SHARED_PHOTO_BASE64_KEY, + ]); + logger.debug("[Main] Cleared old shared image from temp DB"); + } catch (clearError) { + logger.debug( + "[Main] No old image to clear (or error clearing):", + clearError, + ); + } + // Convert raw base64 to data URL format that base64ToBlob expects // base64ToBlob expects format: "data:image/jpeg;base64,/9j/4AAQ..." // Try to detect image type from base64 or default to jpeg @@ -133,17 +161,19 @@ async function checkAndStoreNativeSharedImage(): Promise<{ [SHARED_PHOTO_BASE64_KEY, dataUrl], ); - // Delete the temp file + // Delete the temp file immediately after reading to prevent re-reading try { await Filesystem.deleteFile({ path: tempFilePath, directory: Directory.Documents, }); + logger.debug("[Main] Deleted temp file after reading"); } catch (deleteError) { logger.error("[Main] Failed to delete temp file:", deleteError); } logger.info(`[Main] Stored shared image: ${fileName}`); + isProcessingSharedImage = false; return { success: true, fileName }; } } @@ -175,6 +205,7 @@ async function checkAndStoreNativeSharedImage(): Promise<{ result = await shareImagePlugin.getSharedImageData(); } catch (pluginError) { logger.error("[Main] Plugin call failed:", pluginError); + isProcessingSharedImage = false; return { success: false }; } } else { @@ -206,12 +237,14 @@ async function checkAndStoreNativeSharedImage(): Promise<{ } } catch (executeError) { logger.error("[Main] Execute method failed:", executeError); + isProcessingSharedImage = false; return { success: false }; } } if (!result || !result.success || !result.data) { logger.debug("[Main] No shared image data found in result"); + isProcessingSharedImage = false; return { success: false }; } @@ -219,6 +252,7 @@ async function checkAndStoreNativeSharedImage(): Promise<{ if (!base64) { logger.debug("[Main] Shared image data missing base64"); + isProcessingSharedImage = false; return { success: false }; } @@ -226,18 +260,39 @@ async function checkAndStoreNativeSharedImage(): Promise<{ // Store in temp database (similar to web flow) const platformService = PlatformServiceFactory.getInstance(); - // $insertEntity is available via PlatformServiceMixin - // eslint-disable-next-line @typescript-eslint/no-explicit-any - await (platformService as any).$insertEntity( - "temp", - { id: SHARED_PHOTO_BASE64_KEY, blobB64: base64 }, - ["id", "blobB64"], + + // Clear old image from temp DB first to ensure we get the new one + try { + await platformService.dbExec("DELETE FROM temp WHERE id = ?", [ + SHARED_PHOTO_BASE64_KEY, + ]); + logger.debug("[Main] Cleared old shared image from temp DB"); + } catch (clearError) { + logger.debug( + "[Main] No old image to clear (or error clearing):", + clearError, + ); + } + + // Convert raw base64 to data URL format that base64ToBlob expects + let mimeType = "image/jpeg"; // default + if (base64.startsWith("/9j/") || base64.startsWith("iVBORw0KGgo")) { + mimeType = base64.startsWith("/9j/") ? "image/jpeg" : "image/png"; + } + const dataUrl = `data:${mimeType};base64,${base64}`; + + // Use INSERT OR REPLACE to handle existing records + await platformService.dbExec( + "INSERT OR REPLACE INTO temp (id, blobB64) VALUES (?, ?)", + [SHARED_PHOTO_BASE64_KEY, dataUrl], ); logger.info(`[Main] Stored shared image: ${fileName || "unknown"}`); + isProcessingSharedImage = false; return { success: true, fileName: fileName || "shared-image.jpg" }; } catch (error) { logger.error("[Main] Error checking for native shared image:", error); + isProcessingSharedImage = false; return { success: false }; } } @@ -247,41 +302,74 @@ const handleDeepLink = async (data: { url: string }) => { logger.debug(`[Main] 🌐 Deeplink received from Capacitor: ${url}`); try { - // Check if this is a shared-photo deep link from native share - const isSharedPhotoLink = url.includes("timesafari://shared-photo"); + // Handle empty path URLs from share extension (timesafari://) + // These are used to open the app, and we should check for shared images + const isEmptyPathUrl = url === "timesafari://" || url === "timesafari:///"; if ( - isSharedPhotoLink && + isEmptyPathUrl && Capacitor.isNativePlatform() && Capacitor.getPlatform() === "ios" ) { logger.debug( - "[Main] 📸 Shared photo deep link detected, checking for native shared image", + "[Main] 📸 Empty path URL from share extension, checking for native shared image", ); // Try to get shared image from App Group and store in temp database + // AppDelegate writes the file when the deep link is received, so we may need to retry try { - const imageResult = await checkAndStoreNativeSharedImage(); + let imageResult = await checkAndStoreNativeSharedImage(); + + // If not found immediately, wait a bit and retry (AppDelegate may still be writing the file) + if (!imageResult.success) { + logger.debug( + "[Main] ⏳ Image not found immediately, waiting for AppDelegate to write file...", + ); + await new Promise((resolve) => setTimeout(resolve, 300)); // Wait 300ms + imageResult = await checkAndStoreNativeSharedImage(); + } if (imageResult.success) { - logger.info("[Main] ✅ Native shared image stored in temp database"); + logger.info( + "[Main] ✅ Native shared image found, navigating to shared-photo", + ); - // Add fileName to the URL as a query parameter if we have it - if (imageResult.fileName) { - const urlObj = new URL(url); - urlObj.searchParams.set("fileName", imageResult.fileName); - const modifiedUrl = urlObj.toString(); - data.url = modifiedUrl; - logger.debug(`[Main] Added fileName to URL: ${modifiedUrl}`); + // Wait for router to be ready + await router.isReady(); + + // Navigate directly to shared-photo route + // Use replace if already on shared-photo to force refresh, otherwise push + const fileName = imageResult.fileName || "shared-image.jpg"; + const isAlreadyOnSharedPhoto = + router.currentRoute.value.path === "/shared-photo"; + + if (isAlreadyOnSharedPhoto) { + // Force refresh by replacing the route + await router.replace({ + path: "/shared-photo", + query: { fileName, _refresh: Date.now().toString() }, // Add timestamp to force update + }); + } else { + await router.push({ + path: "/shared-photo", + query: { fileName }, + }); } + + logger.info( + `[Main] ✅ Navigated to /shared-photo?fileName=${fileName}`, + ); + return; // Exit early, don't process as deep link } else { logger.debug( - "[Main] ℹ️ No native shared image found (may be from web or already processed)", + "[Main] ℹ️ No native shared image found, ignoring empty path URL", ); + return; // Exit early, don't process empty path as deep link } } catch (error) { logger.error("[Main] Error processing native shared image:", error); - // Continue with normal deep link processing even if native check fails + // If check fails, don't process as deep link (empty path would fail validation anyway) + return; } } @@ -372,10 +460,76 @@ const registerDeepLinkListener = async () => { } }; +/** + * Check for shared image and navigate to shared-photo route if found + * This is called when app becomes active (from share extension or app launch) + */ +async function checkForSharedImageAndNavigate() { + if (!Capacitor.isNativePlatform() || Capacitor.getPlatform() !== "ios") { + return; + } + + try { + logger.debug("[Main] 🔍 Checking for shared image on app activation"); + const imageResult = await checkAndStoreNativeSharedImage(); + + if (imageResult.success) { + logger.info( + "[Main] ✅ Shared image found, navigating to shared-photo route", + ); + + // Wait for router to be ready + await router.isReady(); + + // Navigate to shared-photo route with fileName if available + // Use replace if already on shared-photo to force refresh, otherwise push + const fileName = imageResult.fileName || "shared-image.jpg"; + const isAlreadyOnSharedPhoto = + router.currentRoute.value.path === "/shared-photo"; + + if (isAlreadyOnSharedPhoto) { + // Force refresh by replacing the route + await router.replace({ + path: "/shared-photo", + query: { fileName, _refresh: Date.now().toString() }, // Add timestamp to force update + }); + } else { + const route = imageResult.fileName + ? `/shared-photo?fileName=${encodeURIComponent(imageResult.fileName)}` + : "/shared-photo"; + await router.push(route); + } + + logger.info(`[Main] ✅ Navigated to /shared-photo?fileName=${fileName}`); + } else { + logger.debug("[Main] ℹ️ No shared image found"); + } + } catch (error) { + logger.error("[Main] ❌ Error checking for shared image:", error); + } +} + logger.log("[Capacitor] 🚀 Mounting app"); app.mount("#app"); logger.info(`[Main] ✅ App mounted successfully`); +// Check for shared image on initial load (in case app was launched from share extension) +if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === "ios") { + setTimeout(async () => { + await checkForSharedImageAndNavigate(); + }, 1000); // Small delay to ensure router is ready +} + +// Listen for app state changes to detect when app becomes active +if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === "ios") { + CapacitorApp.addListener("appStateChange", async ({ isActive }) => { + if (isActive) { + logger.debug("[Main] 📱 App became active, checking for shared image"); + await checkForSharedImageAndNavigate(); + } + }); +} + // Register deeplink listener after app is mounted setTimeout(async () => { try { diff --git a/src/views/SharedPhotoView.vue b/src/views/SharedPhotoView.vue index db1e8453..e9a33751 100644 --- a/src/views/SharedPhotoView.vue +++ b/src/views/SharedPhotoView.vue @@ -120,7 +120,7 @@