-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #28411 from lukemorawski/new-web-scan-flow
Make Mobile Web and Native App Scanning Consistent
- Loading branch information
Showing
8 changed files
with
394 additions
and
102 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,60 +1,75 @@ | ||
import React, {useEffect, useState} from 'react'; | ||
import {Camera} from 'react-native-vision-camera'; | ||
import {useNavigation} from '@react-navigation/native'; | ||
import React, {useEffect, useRef} from 'react'; | ||
import Webcam from 'react-webcam'; | ||
import {useIsFocused} from '@react-navigation/native'; | ||
import PropTypes from 'prop-types'; | ||
import refPropTypes from '../../../components/refPropTypes'; | ||
import {View} from 'react-native'; | ||
|
||
const propTypes = { | ||
/* The index of the tab that contains this camera */ | ||
cameraTabIndex: PropTypes.number.isRequired, | ||
/* Flag to turn on/off the torch/flashlight - if available */ | ||
torchOn: PropTypes.bool, | ||
|
||
/* Forwarded ref */ | ||
forwardedRef: refPropTypes.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. | ||
function NavigationAwareCamera({cameraTabIndex, forwardedRef, ...props}) { | ||
// Get navigation to get initial isFocused value (only needed once during init!) | ||
const navigation = useNavigation(); | ||
const [isCameraActive, setIsCameraActive] = useState(navigation.isFocused()); | ||
|
||
// Note: The useEffect can be removed once VisionCamera V3 is used. | ||
// Its only needed for android, because there is a native cameraX android bug. With out this flow would break the camera: | ||
// 1. Open camera tab | ||
// 2. Take a picture | ||
// 3. Go back from the opened screen | ||
// 4. The camera is not working anymore | ||
function NavigationAwareCamera({torchOn, onTorchAvailability, ...props}, ref) { | ||
const trackRef = useRef(null); | ||
const isCameraActive = useIsFocused(); | ||
|
||
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(() => { | ||
const removeBlurListener = navigation.addListener('blur', () => { | ||
setIsCameraActive(false); | ||
}); | ||
const removeFocusListener = navigation.addListener('focus', () => { | ||
setIsCameraActive(true); | ||
}); | ||
if (!trackRef.current) { | ||
return; | ||
} | ||
|
||
return () => { | ||
removeBlurListener(); | ||
removeFocusListener(); | ||
}; | ||
}, [navigation]); | ||
trackRef.current.applyConstraints({ | ||
advanced: [{torch: torchOn}], | ||
}); | ||
}, [torchOn]); | ||
|
||
if (!isCameraActive) { | ||
return null; | ||
} | ||
return ( | ||
<Camera | ||
ref={forwardedRef} | ||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
{...props} | ||
isActive={isCameraActive} | ||
/> | ||
<View> | ||
<Webcam | ||
audio={false} | ||
screenshotFormat="image/png" | ||
// 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 React.forwardRef((props, ref) => ( | ||
<NavigationAwareCamera | ||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
{...props} | ||
forwardedRef={ref} | ||
/> | ||
)); | ||
export default React.forwardRef(NavigationAwareCamera); |
76 changes: 76 additions & 0 deletions
76
src/pages/iou/ReceiptSelector/NavigationAwareCamera.native.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import React, {useEffect, useState} from 'react'; | ||
import {Camera} from 'react-native-vision-camera'; | ||
import {useTabAnimation} from '@react-navigation/material-top-tabs'; | ||
import {useNavigation} from '@react-navigation/native'; | ||
import PropTypes from 'prop-types'; | ||
import refPropTypes from '../../../components/refPropTypes'; | ||
|
||
const propTypes = { | ||
/* The index of the tab that contains this camera */ | ||
cameraTabIndex: PropTypes.number.isRequired, | ||
|
||
/* Forwarded ref */ | ||
forwardedRef: refPropTypes.isRequired, | ||
}; | ||
|
||
// Wraps a camera that will only be active when the tab is focused or as soon as it starts to become focused. | ||
function NavigationAwareCamera({cameraTabIndex, forwardedRef, ...props}) { | ||
// Get navigation to get initial isFocused value (only needed once during init!) | ||
const navigation = useNavigation(); | ||
const [isCameraActive, setIsCameraActive] = useState(navigation.isFocused()); | ||
|
||
// Retrieve the animation value from the tab navigator, which ranges from 0 to the total number of pages displayed. | ||
// Even a minimal scroll towards the camera page (e.g., a value of 0.001 at start) should activate the camera for immediate responsiveness. | ||
const tabPositionAnimation = useTabAnimation(); | ||
|
||
useEffect(() => { | ||
const listenerId = tabPositionAnimation.addListener(({value}) => { | ||
// Activate camera as soon the index is animating towards the `cameraTabIndex` | ||
setIsCameraActive(value > cameraTabIndex - 1 && value < cameraTabIndex + 1); | ||
}); | ||
|
||
return () => { | ||
tabPositionAnimation.removeListener(listenerId); | ||
}; | ||
}, [cameraTabIndex, tabPositionAnimation]); | ||
|
||
// Note: The useEffect can be removed once VisionCamera V3 is used. | ||
// Its only needed for android, because there is a native cameraX android bug. With out this flow would break the camera: | ||
// 1. Open camera tab | ||
// 2. Take a picture | ||
// 3. Go back from the opened screen | ||
// 4. The camera is not working anymore | ||
useEffect(() => { | ||
const removeBlurListener = navigation.addListener('blur', () => { | ||
setIsCameraActive(false); | ||
}); | ||
const removeFocusListener = navigation.addListener('focus', () => { | ||
setIsCameraActive(true); | ||
}); | ||
|
||
return () => { | ||
removeBlurListener(); | ||
removeFocusListener(); | ||
}; | ||
}, [navigation]); | ||
|
||
return ( | ||
<Camera | ||
ref={forwardedRef} | ||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
{...props} | ||
isActive={isCameraActive} | ||
/> | ||
); | ||
} | ||
|
||
NavigationAwareCamera.propTypes = propTypes; | ||
NavigationAwareCamera.displayName = 'NavigationAwareCamera'; | ||
|
||
export default React.forwardRef((props, ref) => ( | ||
<NavigationAwareCamera | ||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
{...props} | ||
forwardedRef={ref} | ||
/> | ||
)); |
Oops, something went wrong.