Skip to content

Commit

Permalink
Merge pull request Expensify#34782 from tienifr/fix/34457
Browse files Browse the repository at this point in the history
  • Loading branch information
luacmartins authored Mar 20, 2024
2 parents 3311062 + 3701bf3 commit 0fa4d0f
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -1,61 +1,20 @@
import PropTypes from 'prop-types';
import React, {useEffect, useRef} from 'react';
import React from 'react';
import {View} from 'react-native';
import Webcam from 'react-webcam';
import useTabNavigatorFocus from '@hooks/useTabNavigatorFocus';

const propTypes = {
/** Flag to turn on/off the torch/flashlight - if available */
torchOn: PropTypes.bool,

/** The index of the tab that contains this camera */
cameraTabIndex: PropTypes.number.isRequired,

/** Callback function when media stream becomes available - user granted camera permissions and camera starts to work */
onUserMedia: PropTypes.func,

/** Callback function passing torch/flashlight capability as bool param of the browser */
onTorchAvailability: PropTypes.func,
};

const defaultProps = {
onUserMedia: undefined,
onTorchAvailability: undefined,
torchOn: false,
};

// Wraps a camera that will only be active when the tab is focused or as soon as it starts to become focused.
const NavigationAwareCamera = React.forwardRef(({torchOn, onTorchAvailability, cameraTabIndex, ...props}, ref) => {
const trackRef = useRef(null);
const NavigationAwareCamera = React.forwardRef(({cameraTabIndex, ...props}, ref) => {
const shouldShowCamera = useTabNavigatorFocus({
tabIndex: cameraTabIndex,
});

const handleOnUserMedia = (stream) => {
if (props.onUserMedia) {
props.onUserMedia(stream);
}

const [track] = stream.getVideoTracks();
const capabilities = track.getCapabilities();
if (capabilities.torch) {
trackRef.current = track;
}
if (onTorchAvailability) {
onTorchAvailability(!!capabilities.torch);
}
};

useEffect(() => {
if (!trackRef.current) {
return;
}

trackRef.current.applyConstraints({
advanced: [{torch: torchOn}],
});
}, [torchOn]);

if (!shouldShowCamera) {
return null;
}
Expand All @@ -67,14 +26,12 @@ const NavigationAwareCamera = React.forwardRef(({torchOn, onTorchAvailability, c
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
ref={ref}
onUserMedia={handleOnUserMedia}
/>
</View>
);
});

NavigationAwareCamera.propTypes = propTypes;
NavigationAwareCamera.displayName = 'NavigationAwareCamera';
NavigationAwareCamera.defaultProps = defaultProps;

export default NavigationAwareCamera;
63 changes: 57 additions & 6 deletions src/pages/iou/request/step/IOURequestStepScan/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ function IOURequestStepScan({
const [isFlashLightOn, toggleFlashlight] = useReducer((state) => !state, false);
const [isTorchAvailable, setIsTorchAvailable] = useState(false);
const cameraRef = useRef(null);
const trackRef = useRef(null);

const getScreenshotTimeoutRef = useRef(null);

const [videoConstraints, setVideoConstraints] = useState(null);
const tabIndex = 1;
Expand Down Expand Up @@ -212,11 +215,24 @@ function IOURequestStepScan({
navigateToConfirmationStep();
};

const capturePhoto = useCallback(() => {
if (!cameraRef.current.getScreenshot) {
const setupCameraPermissionsAndCapabilities = (stream) => {
setCameraPermissionState('granted');

const [track] = stream.getVideoTracks();
const capabilities = track.getCapabilities();
if (capabilities.torch) {
trackRef.current = track;
}
setIsTorchAvailable(!!capabilities.torch);
};

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

const imageBase64 = cameraRef.current.getScreenshot();

const filename = `receipt_${Date.now()}.png`;
const file = FileUtils.base64ToFile(imageBase64, filename);
const source = URL.createObjectURL(file);
Expand All @@ -228,14 +244,51 @@ function IOURequestStepScan({
}

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

const clearTorchConstraints = useCallback(() => {
if (!trackRef.current) {
return;
}
trackRef.current.applyConstraints({
advanced: [{torch: false}],
});
}, []);

const capturePhoto = useCallback(() => {
if (trackRef.current && isFlashLightOn) {
trackRef.current
.applyConstraints({
advanced: [{torch: true}],
})
.then(() => {
getScreenshotTimeoutRef.current = setTimeout(() => {
getScreenshot();
clearTorchConstraints();
}, 2000);
});
return;
}

getScreenshot();
}, [isFlashLightOn, getScreenshot, clearTorchConstraints]);

const panResponder = useRef(
PanResponder.create({
onPanResponderTerminationRequest: () => false,
}),
).current;

useEffect(
() => () => {
if (!getScreenshotTimeoutRef.current) {
return;
}
clearTimeout(getScreenshotTimeoutRef.current);
},
[],
);

const mobileCameraView = () => (
<>
<View style={[styles.cameraView]}>
Expand All @@ -260,14 +313,12 @@ function IOURequestStepScan({
)}
{!_.isEmpty(videoConstraints) && (
<NavigationAwareCamera
onUserMedia={() => setCameraPermissionState('granted')}
onUserMedia={setupCameraPermissionsAndCapabilities}
onUserMediaError={() => setCameraPermissionState('denied')}
style={{...styles.videoContainer, display: cameraPermissionState !== 'granted' ? 'none' : 'block'}}
ref={cameraRef}
screenshotFormat="image/png"
videoConstraints={videoConstraints}
torchOn={isFlashLightOn}
onTorchAvailability={setIsTorchAvailable}
forceScreenshotSourceSize
cameraTabIndex={tabIndex}
/>
Expand Down

0 comments on commit 0fa4d0f

Please sign in to comment.