From 41c834697fbf9625de0072827e1912e88767eaa9 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 6 Mar 2024 17:39:23 +0700 Subject: [PATCH 1/7] Fix immediately prompt for Camera permission in scan request flow --- .../step/IOURequestStepScan/index.native.js | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.js b/src/pages/iou/request/step/IOURequestStepScan/index.native.js index 338444d473c6..de50d25a2e78 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.js @@ -65,18 +65,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(); } }) @@ -121,22 +120,14 @@ function IOURequestStepScan({ }); useEffect(() => { - 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)); }; // Check initial camera permission status - refreshCameraPermissionStatus(true); + refreshCameraPermissionStatus(); // Refresh permission status when app gain focus const subscription = AppState.addEventListener('change', (appState) => { @@ -225,7 +216,7 @@ function IOURequestStepScan({ const capturePhoto = useCallback(() => { if (!camera.current && (cameraPermissionStatus === RESULTS.DENIED || cameraPermissionStatus === RESULTS.BLOCKED)) { - askForPermissions(cameraPermissionStatus !== RESULTS.DENIED); + askForPermissions(); return; } From 27cf2b1936e43ef62ae77413fceb6cb98a9dde68 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 18 Mar 2024 11:28:11 +0700 Subject: [PATCH 2/7] Fix prompt permission when component is mounted in mWeb --- .../request/step/IOURequestStepScan/index.js | 58 ++++++++++++++----- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.js b/src/pages/iou/request/step/IOURequestStepScan/index.js index 7da97c34cc2b..9bf1b34697fb 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.js @@ -1,5 +1,5 @@ import lodashGet from 'lodash/get'; -import React, {useCallback, useContext, useReducer, useRef, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useReducer, useRef, useState} from 'react'; import {ActivityIndicator, PanResponder, PixelRatio, View} from 'react-native'; import Hand from '@assets/images/hand.svg'; import ReceiptUpload from '@assets/images/receipt-upload.svg'; @@ -75,6 +75,22 @@ function IOURequestStepScan({ const [isTorchAvailable, setIsTorchAvailable] = useState(false); const cameraRef = useRef(null); + useEffect(() => { + if (!Browser.isMobile()) { + return; + } + navigator.permissions + .query({name: 'camera'}) + .then((permissionState) => { + setCameraPermissionState(permissionState.state); + }) + .catch(() => { + setCameraPermissionState('denied'); + }); + // We only want to get the camera permission status when the component is mounted + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const hideRecieptModal = () => { setIsAttachmentInvalid(false); }; @@ -163,7 +179,17 @@ function IOURequestStepScan({ }; const capturePhoto = useCallback(() => { - if (!cameraRef.current.getScreenshot) { + if (!cameraRef.current || !cameraRef.current.getScreenshot) { + if (cameraPermissionState === 'prompt') { + navigator.mediaDevices + .getUserMedia({video: true}) + .then(() => { + setCameraPermissionState('granted'); + }) + .catch(() => { + setCameraPermissionState('denied'); + }); + } return; } const imageBase64 = cameraRef.current.getScreenshot(); @@ -178,7 +204,7 @@ function IOURequestStepScan({ } navigateToConfirmationStep(); - }, [cameraRef, action, transactionID, updateScanAndNavigate, navigateToConfirmationStep]); + }, [cameraRef, action, transactionID, updateScanAndNavigate, navigateToConfirmationStep, cameraPermissionState]); const panResponder = useRef( PanResponder.create({ @@ -208,18 +234,20 @@ function IOURequestStepScan({ {translate('receipt.cameraAccess')} )} - setCameraPermissionState('granted')} - onUserMediaError={() => setCameraPermissionState('denied')} - style={{...styles.videoContainer, display: cameraPermissionState !== 'granted' ? 'none' : 'block'}} - ref={cameraRef} - screenshotFormat="image/png" - videoConstraints={{facingMode: {exact: 'environment'}}} - torchOn={isFlashLightOn} - onTorchAvailability={setIsTorchAvailable} - forceScreenshotSourceSize - cameraTabIndex={1} - /> + {cameraPermissionState === 'granted' && ( + setCameraPermissionState('granted')} + onUserMediaError={() => setCameraPermissionState('denied')} + style={{...styles.videoContainer, display: cameraPermissionState !== 'granted' ? 'none' : 'block'}} + ref={cameraRef} + screenshotFormat="image/png" + videoConstraints={{facingMode: {exact: 'environment'}}} + torchOn={isFlashLightOn} + onTorchAvailability={setIsTorchAvailable} + forceScreenshotSourceSize + cameraTabIndex={1} + /> + )} From 458535dd519d711e16706c93c3849464f4749532 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 18 Mar 2024 15:15:41 +0700 Subject: [PATCH 3/7] update constraints when calling getUserMedia function --- src/pages/iou/request/step/IOURequestStepScan/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.js b/src/pages/iou/request/step/IOURequestStepScan/index.js index 9bf1b34697fb..eeda08d1a06e 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.js @@ -182,7 +182,7 @@ function IOURequestStepScan({ if (!cameraRef.current || !cameraRef.current.getScreenshot) { if (cameraPermissionState === 'prompt') { navigator.mediaDevices - .getUserMedia({video: true}) + .getUserMedia({video: {facingMode: {exact: 'environment'}}}) .then(() => { setCameraPermissionState('granted'); }) From 213e2729b17f39570887c8bbf8e5ac44cd9c6227 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 21 Mar 2024 11:13:12 +0700 Subject: [PATCH 4/7] fix logic to show explain UI --- .../request/step/IOURequestStepScan/index.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.js b/src/pages/iou/request/step/IOURequestStepScan/index.js index b62cb243472c..8e6003c21c9f 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.js @@ -77,6 +77,7 @@ function IOURequestStepScan({ const [isTorchAvailable, setIsTorchAvailable] = useState(false); const cameraRef = useRef(null); const trackRef = useRef(null); + const isQueriedPermissionStateRef = useRef(null); const getScreenshotTimeoutRef = useRef(null); @@ -97,6 +98,7 @@ function IOURequestStepScan({ 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) { @@ -149,7 +151,8 @@ function IOURequestStepScan({ }) .catch(() => { setCameraPermissionState('denied'); - }); + }) + .finally(() => (isQueriedPermissionStateRef.current = true)); // We only want to get the camera permission status when the component is mounted // eslint-disable-next-line react-hooks/exhaustive-deps }, [isTabActive]); @@ -319,14 +322,14 @@ function IOURequestStepScan({ const mobileCameraView = () => ( <> - {(cameraPermissionState === 'prompt' || !cameraPermissionState) && ( + {((cameraPermissionState === 'prompt' && !isQueriedPermissionStateRef.current) || (cameraPermissionState === 'granted' && _.isEmpty(videoConstraints))) && ( )} - {cameraPermissionState === 'denied' && ( + {cameraPermissionState !== 'granted' && isQueriedPermissionStateRef.current && ( {translate('receipt.takePhoto')} {translate('receipt.cameraAccess')} +