Skip to content

Commit

Permalink
Merge pull request #37817 from dukenv0307/fix/37737
Browse files Browse the repository at this point in the history
Fix immediately prompt for Camera permission in scan request flow
  • Loading branch information
Julesssss authored Mar 27, 2024
2 parents e10d4f7 + 9baccff commit 5d554e3
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 22 deletions.
50 changes: 43 additions & 7 deletions src/pages/iou/request/step/IOURequestStepScan/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ function IOURequestStepScan({
const [isTorchAvailable, setIsTorchAvailable] = useState(false);
const cameraRef = useRef(null);
const trackRef = useRef(null);
const [isQueriedPermissionState, setIsQueriedPermissionState] = useState(false);

const getScreenshotTimeoutRef = useRef(null);

Expand All @@ -88,15 +89,16 @@ function IOURequestStepScan({
* On phones that have ultra-wide lens, react-webcam uses ultra-wide by default.
* The last deviceId is of regular len camera.
*/
useEffect(() => {
if (!_.isEmpty(videoConstraints) || !isTabActive || !Browser.isMobile()) {
const requestCameraPermission = useCallback(() => {
if (!_.isEmpty(videoConstraints) || !Browser.isMobile()) {
return;
}

const defaultConstraints = {facingMode: {exact: 'environment'}};
navigator.mediaDevices
.getUserMedia({video: {facingMode: {exact: 'environment'}, zoom: {ideal: 1}}})
.then((stream) => {
setCameraPermissionState('granted');
_.forEach(stream.getTracks(), (track) => track.stop());
// Only Safari 17+ supports zoom constraint
if (Browser.isMobileSafari() && stream.getTracks().length > 0) {
Expand Down Expand Up @@ -128,7 +130,32 @@ function IOURequestStepScan({
setVideoConstraints({deviceId: lastBackDeviceId});
});
})
.catch(() => setVideoConstraints(defaultConstraints));
.catch(() => {
setVideoConstraints(defaultConstraints);
setCameraPermissionState('denied');
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
if (!Browser.isMobile() || !isTabActive) {
return;
}
navigator.permissions
.query({name: 'camera'})
.then((permissionState) => {
setCameraPermissionState(permissionState.state);
if (permissionState.state === 'granted') {
requestCameraPermission();
}
})
.catch(() => {
setCameraPermissionState('denied');
})
.finally(() => {
setIsQueriedPermissionState(true);
});
// We only want to get the camera permission status when the component is mounted
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isTabActive]);

Expand Down Expand Up @@ -232,6 +259,7 @@ function IOURequestStepScan({

const getScreenshot = useCallback(() => {
if (!cameraRef.current) {
requestCameraPermission();
return;
}

Expand All @@ -248,7 +276,7 @@ function IOURequestStepScan({
}

navigateToConfirmationStep();
}, [action, transactionID, updateScanAndNavigate, navigateToConfirmationStep]);
}, [action, transactionID, updateScanAndNavigate, navigateToConfirmationStep, requestCameraPermission]);

const clearTorchConstraints = useCallback(() => {
if (!trackRef.current) {
Expand Down Expand Up @@ -296,14 +324,14 @@ function IOURequestStepScan({
const mobileCameraView = () => (
<>
<View style={[styles.cameraView]}>
{(cameraPermissionState === 'prompt' || !cameraPermissionState) && (
{((cameraPermissionState === 'prompt' && !isQueriedPermissionState) || (cameraPermissionState === 'granted' && _.isEmpty(videoConstraints))) && (
<ActivityIndicator
size={CONST.ACTIVITY_INDICATOR_SIZE.LARGE}
style={[styles.flex1]}
color={theme.textSupporting}
/>
)}
{cameraPermissionState === 'denied' && (
{cameraPermissionState !== 'granted' && isQueriedPermissionState && (
<View style={[styles.flex1, styles.permissionView, styles.userSelectNone]}>
<Icon
src={Hand}
Expand All @@ -313,9 +341,17 @@ function IOURequestStepScan({
/>
<Text style={[styles.textReceiptUpload]}>{translate('receipt.takePhoto')}</Text>
<Text style={[styles.subTextReceiptUpload]}>{translate('receipt.cameraAccess')}</Text>
<Button
medium
success
text={translate('common.continue')}
accessibilityLabel={translate('common.continue')}
style={[styles.p9, styles.pt5]}
onPress={capturePhoto}
/>
</View>
)}
{!_.isEmpty(videoConstraints) && (
{cameraPermissionState === 'granted' && !_.isEmpty(videoConstraints) && (
<NavigationAwareCamera
onUserMedia={setupCameraPermissionsAndCapabilities}
onUserMediaError={() => setCameraPermissionState('denied')}
Expand Down
21 changes: 6 additions & 15 deletions src/pages/iou/request/step/IOURequestStepScan/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,17 @@ function IOURequestStepScan({
const camera = useRef(null);
const [flash, setFlash] = useState(false);
const [cameraPermissionStatus, setCameraPermissionStatus] = useState(undefined);
const askedForPermission = useRef(false);

const {translate} = useLocalize();

const askForPermissions = (showPermissionsAlert = true) => {
const askForPermissions = () => {
// There's no way we can check for the BLOCKED status without requesting the permission first
// https://github.com/zoontek/react-native-permissions/blob/a836e114ce3a180b2b23916292c79841a267d828/README.md?plain=1#L670
CameraPermission.requestCameraPermission()
.then((status) => {
setCameraPermissionStatus(status);

if (status === RESULTS.BLOCKED && showPermissionsAlert) {
if (status === RESULTS.BLOCKED) {
FileUtils.showCameraPermissionsAlert();
}
})
Expand Down Expand Up @@ -123,23 +122,15 @@ function IOURequestStepScan({

useFocusEffect(
useCallback(() => {
const refreshCameraPermissionStatus = (shouldAskForPermission = false) => {
const refreshCameraPermissionStatus = () => {
CameraPermission.getCameraPermissionStatus()
.then((res) => {
// In android device app data, the status is not set to blocked until denied twice,
// due to that the app will ask for permission twice whenever users opens uses the scan tab
setCameraPermissionStatus(res);
if (shouldAskForPermission && !askedForPermission.current) {
askedForPermission.current = true;
askForPermissions(false);
}
})
.then(setCameraPermissionStatus)
.catch(() => setCameraPermissionStatus(RESULTS.UNAVAILABLE));
};

InteractionManager.runAfterInteractions(() => {
// Check initial camera permission status
refreshCameraPermissionStatus(true);
refreshCameraPermissionStatus();
});

// Refresh permission status when app gain focus
Expand Down Expand Up @@ -230,7 +221,7 @@ function IOURequestStepScan({

const capturePhoto = useCallback(() => {
if (!camera.current && (cameraPermissionStatus === RESULTS.DENIED || cameraPermissionStatus === RESULTS.BLOCKED)) {
askForPermissions(cameraPermissionStatus !== RESULTS.DENIED);
askForPermissions();
return;
}

Expand Down

0 comments on commit 5d554e3

Please sign in to comment.