fix(mobile): handle iOS share sheet custom scheme URLs and add photo library picker

On iOS the Share Sheet / "Open In" action may deliver the file path
under the app's custom docuelevate:// scheme instead of a file:// URL,
causing an "Unmatched Route" error.  The URL handler now detects this
and rewrites the URL to file:// before processing.

Also adds a Photo Library button to the Upload screen so users can
select existing photos from their device library, not just capture
new ones with the camera.

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-16 17:25:03 +00:00
parent daf236b270
commit acfba4b58c
4 changed files with 75 additions and 14 deletions
+40 -5
View File
@@ -1,10 +1,11 @@
/**
* UploadScreen document upload via camera or file picker.
* UploadScreen document upload via camera, photo library, or file picker.
*
* Users can:
* 1. Take a photo of a document with the device camera.
* 2. Pick an existing file (PDF, image, Office document) from the Files app.
* 3. Receive files shared from other apps via the iOS Share Sheet / Android
* 2. Select an existing photo from the device's photo library.
* 3. Pick an existing file (PDF, image, Office document) from the Files app.
* 4. Receive files shared from other apps via the iOS Share Sheet / Android
* Share Intent (handled via ShareContext populated by the root layout).
*
* After a successful upload the screen polls the backend every 5 seconds to
@@ -161,6 +162,29 @@ export default function UploadScreen() {
}
}
async function handlePhotoLibrary() {
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== "granted") {
Alert.alert(
"Photo library access required",
"Please grant photo library access in Settings to select images."
);
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ["images"],
quality: 0.9,
allowsEditing: false,
});
if (!result.canceled && result.assets.length > 0) {
const asset = result.assets[0];
const filename = asset.fileName ?? `photo_${Date.now()}.jpg`;
await uploadFile(asset.uri, filename, asset.mimeType ?? "image/jpeg");
}
}
async function handleFilePicker() {
try {
const result = await DocumentPicker.getDocumentAsync({
@@ -201,6 +225,16 @@ export default function UploadScreen() {
<Text style={styles.actionLabel}>Camera</Text>
</Pressable>
<Pressable
style={[styles.actionButton, styles.photoLibraryButton]}
onPress={handlePhotoLibrary}
accessibilityRole="button"
accessibilityLabel="Select photo from library"
>
<Text style={styles.actionIcon}>🖼</Text>
<Text style={styles.actionLabel}>Photos</Text>
</Pressable>
<Pressable
style={[styles.actionButton, styles.fileButton]}
onPress={handleFilePicker}
@@ -208,7 +242,7 @@ export default function UploadScreen() {
accessibilityLabel="Pick file from device"
>
<Text style={styles.actionIcon}>📄</Text>
<Text style={styles.actionLabel}>File Picker</Text>
<Text style={styles.actionLabel}>Files</Text>
</Pressable>
</View>
@@ -218,7 +252,7 @@ export default function UploadScreen() {
<View style={styles.emptyState}>
<Text style={styles.emptyEmoji}></Text>
<Text style={styles.emptyText}>
Tap Camera or File Picker to upload a document.
Tap Camera, Photos, or Files to upload a document.
</Text>
<Text style={styles.emptyHint}>
You can also share files from other apps directly to DocuElevate.
@@ -304,6 +338,7 @@ const styles = StyleSheet.create({
minHeight: 80,
},
cameraButton: { backgroundColor: "#1e40af" },
photoLibraryButton: { backgroundColor: "#7c3aed" },
fileButton: { backgroundColor: "#059669" },
actionIcon: { fontSize: 28, marginBottom: 6 },
actionLabel: {