fix(mobile): add shared file to ShareContext directly in +not-found.tsx

When iOS delivers a file via "Open In…", expo-router strips the
docuelevate:// scheme and routes to +not-found.tsx. Previously, this
screen only redirected to the Upload tab and relied on the Linking
handler in _layout.tsx to add the file to ShareContext. This was
unreliable because expo-router may consume the URL event before the
Linking handler fires.

Now +not-found.tsx directly reconstructs the file:// URI from the
pathname and adds it to ShareContext before redirecting. ShareContext
deduplicates by URI to prevent double uploads if both mechanisms fire.

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-19 11:12:34 +00:00
parent 34ea9333fb
commit 71a7a57adc
5 changed files with 52 additions and 16 deletions
+35 -8
View File
@@ -10,10 +10,13 @@
* no such route exists, expo-router previously threw "unmatched route
* docuelevate://…" and the upload never happened.
*
* This screen detects the filesystem-path pattern and immediately redirects
* to the Upload tab. The `Linking` listener registered in `_layout.tsx`
* runs concurrently and adds the file to `ShareContext`; `UploadScreen`
* picks it up and begins uploading as soon as the redirect completes.
* This screen detects the filesystem-path pattern, adds the file directly
* to `ShareContext`, and redirects to the Upload tab. `UploadScreen`
* picks up the pending file and begins uploading automatically.
*
* The `Linking` listener in `_layout.tsx` may also fire for the same URL;
* `ShareContext.addPendingFile` deduplicates by URI so the file is only
* uploaded once.
*
* 2. **Any other unmatched in-app route** — redirect silently to the root so
* the user isn't left on a blank error page.
@@ -22,6 +25,7 @@
import { usePathname, useRouter } from "expo-router";
import React, { useEffect } from "react";
import { ActivityIndicator, StyleSheet, View } from "react-native";
import { useShare } from "../src/context/ShareContext";
// ---------------------------------------------------------------------------
// Helpers
@@ -81,6 +85,21 @@ function looksLikeFilePath(pathname: string): boolean {
return !IN_APP_ROUTE_PREFIXES.some((prefix) => stripped.startsWith(prefix));
}
/**
* Extract a display filename from a filesystem path.
* Handles URL-encoded characters and strips query strings.
*/
function filenameFromPath(pathname: string): string {
try {
const decoded = decodeURIComponent(pathname);
const segments = decoded.split("/").filter(Boolean);
const last = segments[segments.length - 1] ?? "shared_file";
return last.split("?")[0] || "shared_file";
} catch {
return "shared_file";
}
}
// ---------------------------------------------------------------------------
// Screen component
// ---------------------------------------------------------------------------
@@ -88,18 +107,26 @@ function looksLikeFilePath(pathname: string): boolean {
export default function NotFoundScreen() {
const pathname = usePathname();
const router = useRouter();
const { addPendingFile } = useShare();
useEffect(() => {
if (looksLikeFilePath(pathname)) {
// Filesystem path from iOS "Open In…" redirect to Upload tab.
// The Linking listener in _layout.tsx has already (or will shortly)
// added the file to ShareContext; UploadScreen will pick it up.
// Filesystem path from iOS "Open In…" add the file to ShareContext
// and redirect to the Upload tab. UploadScreen will pick up the
// pending file and begin uploading automatically.
//
// The pathname from expo-router is the raw filesystem path
// (e.g. "/private/var/mobile/Library/…/file.pdf"). Reconstruct a
// file:// URI so the upload logic can read the file.
const fileUri = `file://${pathname}`;
const filename = filenameFromPath(pathname);
addPendingFile({ uri: fileUri, filename });
router.replace("/(tabs)/");
} else {
// Truly unknown in-app route fall back to the root redirect.
router.replace("/");
}
}, [pathname, router]);
}, [pathname, router, addPendingFile]);
// Show a brief spinner while the redirect is in flight.
return (