Documentation Index
Fetch the complete documentation index at: https://docs.launchtoday.dev/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The upload screen is located at apps/mobile/app/file-uploads/s3.tsx and provides:
- Photo picker with recent photos
- Document picker for PDFs/videos
- Multi-file selection
- Progress tracking
- Automatic upload strategy selection
File Pickers
Photos
Uses expo-image-picker and expo-media-library:
import * as ImagePicker from "expo-image-picker";
import * as MediaLibrary from "expo-media-library";
// Get recent photos
const { assets } = await MediaLibrary.getAssetsAsync({
first: 20,
mediaType: "photo",
sortBy: [MediaLibrary.SortBy.creationTime],
});
// Open full picker
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ["images", "videos"],
allowsMultipleSelection: true,
quality: 0.8,
videoExportPreset: ImagePicker.VideoExportPreset.HighestQuality,
});
Documents
Uses expo-document-picker:
import * as DocumentPicker from "expo-document-picker";
const result = await DocumentPicker.getDocumentAsync({
type: ["application/pdf", "image/*", "video/mp4", "video/quicktime"],
copyToCacheDirectory: true,
multiple: true,
});
Upload Strategies
The app automatically selects the best strategy:
// Thresholds
const MAX_SIMPLE_FILE_SIZE = 10 * 1024 * 1024; // 10MB
const NATIVE_UPLOAD_THRESHOLD = 40 * 1024 * 1024; // 40MB
const MAX_MULTIPART_FILE_SIZE = 5 * 1024 * 1024 * 1024; // 5GB
// Selection logic
if (fileSize > NATIVE_UPLOAD_THRESHOLD) {
// Use native background upload (iOS/Android)
await startNativeUpload(file);
} else if (fileSize > MAX_SIMPLE_FILE_SIZE) {
// Use multipart upload (chunked, resumable)
await uploadMultipart(file);
} else {
// Use simple presigned URL upload
await uploadSimple(file);
}
Upload Queue
Files are tracked in an upload queue with individual status:
interface QueuedUpload {
id: string;
file: SelectedFile;
status: "pending" | "uploading" | "completed" | "error";
progress: number;
error?: string;
}
The queue:
- Persists to AsyncStorage for navigation resilience
- Shows per-file progress
- Supports parallel uploads (for native uploads)
- Shows summary alert when all complete
Progress UI
The upload button dynamically shows status:
const getUploadButtonText = () => {
if (hasActiveUploads) {
return `Uploading ${completedCount + 1}/${totalFiles} (${avgProgress}%)`;
}
if (selectedFiles.length === 0) {
return "Select files to upload";
}
return `Upload ${count} files (${formatFileSize(totalSize)})`;
};
Permissions
Required permissions for iOS (Info.plist):
<key>NSPhotoLibraryUsageDescription</key>
<string>We need access to your photos to upload them.</string>
<key>NSCameraUsageDescription</key>
<string>We need access to your camera to take photos.</string>
For Android (AndroidManifest.xml):
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
Customization
Change Upload Thresholds
Adjust the constants at the top of s3.tsx:
const MAX_SIMPLE_FILE_SIZE = 10 * 1024 * 1024; // When to use multipart
const NATIVE_UPLOAD_THRESHOLD = 40 * 1024 * 1024; // When to use native
Allowed File Types
Modify the document picker types:
const result = await DocumentPicker.getDocumentAsync({
type: [
"application/pdf",
"image/*",
"video/mp4",
"video/quicktime",
// Add more types here
],
});
Photo Grid Size
const PHOTO_SIZE = 84; // Thumbnail size in pixels
Test Checklist
- Select a photo and upload successfully
- Large file triggers multipart or native upload
- Upload progress updates in UI
Troubleshooting
If uploads fail, verify permissions and EXPO_PUBLIC_API_URL in
apps/mobile/.env (from apps/mobile/example.env).
Remove / Disable
To disable uploads while you configure S3, set:
apps/mobile/features/feature-registry.tsx → featureFlags.fileUploads = false
For production removal guidance, see Removing Features.