import type * as ImagePicker from "expo-image-picker"; import { Alert, Linking } from "react-native"; type Copy = { title: string; /** Why we need it — shown while Android will still show its own dialog. */ message: string; /** Shown once Android has stopped showing that dialog. */ blocked: string; openSettings: string; cancel: string; }; /** * Explains a refused camera or photo-library permission, and offers the only * way out when there is one. * * `granted: false` covers two states that feel completely different to a * driver. While `canAskAgain` is true the system dialog appeared and they * declined it, so repeating why we need it and letting them tap the button * again is the whole fix. Once `canAskAgain` is false Android stops showing * that dialog altogether: `requestCameraPermissionsAsync()` returns denied * without anything appearing on screen, so from the driver's side the app has * simply stopped asking, and no amount of tapping will ever change it. The * only remaining route is the system settings page for the app, so that case * gets a button that opens it rather than a message telling them to allow * something they are never going to be offered. * * Android also lands drivers in that second state through no choice of their * own: requesting a runtime permission the manifest doesn't declare is * auto-denied and flagged as permanently denied, and the flag survives an * update install. A driver who ran a build predating the CAMERA declaration in * app.config.js is stuck there until they either use this button or reinstall. */ export const alertPermissionDenied = ( permission: ImagePicker.PermissionResponse, copy: Copy, ) => { // Both branches below end in an alert and nothing else, which leaves no // trace in the logs — the reason a driver reporting "it never asks me" is // indistinguishable from one who never tapped the button. Logging the two // fields that decide the branch makes that difference readable. console.log( "[CAPTURE_PERMISSION_DENIED]: ", JSON.stringify({ status: permission.status, canAskAgain: permission.canAskAgain, }), ); if (permission.canAskAgain) { Alert.alert(copy.title, copy.message); return; } Alert.alert(copy.title, copy.blocked, [ { text: copy.cancel, style: "cancel" }, { text: copy.openSettings, onPress: () => { // Failure here is not worth a second alert on top of this one: the // driver is already reading instructions that name the settings // screen, and reaching it by hand still works. void Linking.openSettings().catch((error) => console.log("[CAPTURE_PERMISSION_SETTINGS]: ", error), ); }, }, ]); };