fix(mobile): iOS share sheet, empty files tab, and stuck upload status
- app.json: add CFBundleDocumentTypes to iOS infoPlist so the app appears in the iOS Share Sheet; add ACTION_SEND/SEND_MULTIPLE intentFilters for Android share intent support - src/context/ShareContext.tsx (new): React context that queues files received from the share sheet and delivers them to UploadScreen - app/_layout.tsx: wrap in ShareProvider; add Linking handler (makeUrlHandler factory + getInitialURL cold-start + addEventListener warm-start) to capture file:// and content:// URLs - src/services/api.ts: fix FileRecord interface (original_filename, nested ProcessingStatus, mime_type); fix UploadResponse interface; fix listFiles() (per_page param, unwrap data.files); add getFileStatus(fileId) for single-file status polling - src/screens/FilesScreen.tsx: use file.original_filename and file.processing_status.status; fix statusEmoji to use actual backend status values (completed/pending/duplicate) - src/screens/UploadScreen.tsx: consume ShareContext for auto-upload of shared files; add 5-second polling loop (search by filename → file_id → getFileStatus) to show real-time server processing status; uploadFile wrapped in useCallback; proper effect dependency arrays Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
+57
-3
@@ -4,13 +4,48 @@
|
||||
* Wraps the entire app in AuthProvider and SafeAreaProvider, then uses the
|
||||
* AuthGuard component to redirect between the unauthenticated (auth) route
|
||||
* group and the authenticated (tabs) route group based on session state.
|
||||
*
|
||||
* ShareProvider + Linking listener: when iOS opens the app via the share
|
||||
* sheet (CFBundleDocumentTypes) or Android via a SEND intent, the incoming
|
||||
* file:// / content:// URL is captured and forwarded to UploadScreen via
|
||||
* ShareContext.
|
||||
*/
|
||||
|
||||
import * as Linking from "expo-linking";
|
||||
import { Stack, useRouter, useSegments } from "expo-router";
|
||||
import React, { useEffect } from "react";
|
||||
import { ActivityIndicator, StyleSheet, Text, View } from "react-native";
|
||||
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||
import { AuthProvider, useAuth } from "../src/context/AuthContext";
|
||||
import { ShareProvider, useShare } from "../src/context/ShareContext";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Extract a display filename from a file:// or content:// URI. */
|
||||
function filenameFromUri(uri: string): string {
|
||||
try {
|
||||
const decoded = decodeURIComponent(uri);
|
||||
// Take the last path segment and strip any query string
|
||||
const last = decoded.split("/").pop() ?? "shared_file";
|
||||
return last.split("?")[0] || "shared_file";
|
||||
} catch {
|
||||
return "shared_file";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a Linking URL handler that forwards incoming file:// / content://
|
||||
* URLs to ShareContext. Extracted as a module-level factory so the handler
|
||||
* itself is created once and can be easily unit-tested without a React context.
|
||||
*/
|
||||
function makeUrlHandler(addPendingFile: (f: { uri: string; filename: string }) => void) {
|
||||
return ({ url }: { url: string }) => {
|
||||
if (!url.startsWith("file://") && !url.startsWith("content://")) return;
|
||||
addPendingFile({ uri: url, filename: filenameFromUri(url) });
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Auth guard – redirects to the correct route group after auth state resolves
|
||||
@@ -18,9 +53,26 @@ import { AuthProvider, useAuth } from "../src/context/AuthContext";
|
||||
|
||||
function AuthGuard() {
|
||||
const { isLoading, isAuthenticated } = useAuth();
|
||||
const { addPendingFile } = useShare();
|
||||
const segments = useSegments();
|
||||
const router = useRouter();
|
||||
|
||||
// Listen for files shared from other apps (iOS Share Sheet / Android Intent).
|
||||
// Both cold-start (app was not running) and warm-start (app in background)
|
||||
// cases are handled.
|
||||
useEffect(() => {
|
||||
const handleIncomingUrl = makeUrlHandler(addPendingFile);
|
||||
|
||||
// Cold start – app launched directly by a share action
|
||||
Linking.getInitialURL().then((url) => {
|
||||
if (url) handleIncomingUrl({ url });
|
||||
});
|
||||
|
||||
// Warm start – app was already running when the share action occurred
|
||||
const subscription = Linking.addEventListener("url", handleIncomingUrl);
|
||||
return () => subscription.remove();
|
||||
}, [addPendingFile]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) return;
|
||||
|
||||
@@ -59,9 +111,11 @@ function AuthGuard() {
|
||||
export default function RootLayout() {
|
||||
return (
|
||||
<SafeAreaProvider>
|
||||
<AuthProvider>
|
||||
<AuthGuard />
|
||||
</AuthProvider>
|
||||
<ShareProvider>
|
||||
<AuthProvider>
|
||||
<AuthGuard />
|
||||
</AuthProvider>
|
||||
</ShareProvider>
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user