Compare commits

...

2 Commits

Author SHA1 Message Date
simistern
55ff9809ac Fixed hooks sample to be in line with @mauriciords implementation 2020-02-12 18:58:17 -05:00
simistern
e5b2f4e7d5 converted basic sample to hooks 2019-12-24 14:25:26 -05:00
4 changed files with 604 additions and 586 deletions

View File

@ -1,5 +1,5 @@
/* eslint-disable no-console */
import React from 'react';
import React, {useState, useReducer, useRef} from 'react';
import {
StyleSheet,
Text,
@ -30,61 +30,87 @@ const wbOrder = {
const landmarkSize = 2;
export default class CameraScreen extends React.Component {
state = {
flash: 'off',
zoom: 0,
autoFocus: 'on',
autoFocusPoint: {
normalized: { x: 0.5, y: 0.5 }, // normalized values required for autoFocusPointOfInterest
drawRectPosition: {
x: Dimensions.get('window').width * 0.5 - 32,
y: Dimensions.get('window').height * 0.5 - 32,
},
const stateReducer = (state, action) => {
switch (action.type) {
case 'toggleFlash':
return { state.flash === flashModeOrder[ state.flash] }
break;
case 'toggleZoom':
return { state.flash === flashModeOrder[ state.flash] }
break;
default:
break;
}
}
const initialState = {
flash: 'off',
zoom: 0,
autoFocus: 'on',
depth: 0,
type: 'back',
whiteBalance: 'auto',
ratio: '16:9',
isRecording: false,
canDetectFaces: false,
canDetectText: false,
canDetectBarcode: false,
faces: [],
textBlocks: [],
barcodes: [],
recordOptions: {
mute: false,
maxDuration: 5,
quality: RNCamera.Constants.VideoQuality['288p'],
},
autoFocusPoint: {
normalized: { x: 0.5, y: 0.5 }, // normalized values required for autoFocusPointOfInterest
drawRectPosition: {
x: Dimensions.get('window').width * 0.5 - 32,
y: Dimensions.get('window').height * 0.5 - 32,
},
depth: 0,
type: 'back',
whiteBalance: 'auto',
ratio: '16:9',
recordOptions: {
mute: false,
maxDuration: 5,
quality: RNCamera.Constants.VideoQuality['288p'],
},
isRecording: false,
canDetectFaces: false,
canDetectText: false,
canDetectBarcode: false,
faces: [],
textBlocks: [],
barcodes: [],
};
},
toggleFacing() {
this.setState({
type: this.state.type === 'back' ? 'front' : 'back',
});
};
const CameraScreen = () => {
const [state, setState] = useState(initialState);
const [recordOptions, togglerecordOptions] = useReducer(reducer, initialState.recordOptions);
const [autoFocusPoint, touchToFocus] = useReducer(reducer, initialState.autoFocusPoint);
const cameraRef = useRef(null);
toggleFacing =() =>{
setState({
...state,
type: type === 'back' ? 'front' : 'back',
})
}
toggleFlash() {
this.setState({
flash: flashModeOrder[this.state.flash],
});
toggleFlash =() => {
setState({
...state,
flash: flashModeOrder[flash]
})
}
toggleWB() {
this.setState({
whiteBalance: wbOrder[this.state.whiteBalance],
});
toggleWB =() => {
setState({
...state,
whiteBalance: wbOrder[whiteBalance]
})
}
toggleFocus() {
this.setState({
autoFocus: this.state.autoFocus === 'on' ? 'off' : 'on',
});
toggleFocus =() =>{
setState({
...state,
autoFocus: autoFocus === 'on' ? 'off' : 'on'
})
}
touchToFocus(event) {
touchToFocus =(event) => {
const { pageX, pageY } = event.nativeEvent;
const screenWidth = Dimensions.get('window').width;
const screenHeight = Dimensions.get('window').height;
@ -97,49 +123,56 @@ export default class CameraScreen extends React.Component {
x = pageY / screenHeight;
y = -(pageX / screenWidth) + 1;
}
this.setState({
return {
autoFocusPoint: {
normalized: { x, y },
drawRectPosition: { x: pageX, y: pageY },
},
};
}
zoomOut =() =>{
setState({
...state,
zoom: state.zoom - 0.1 < 0 ? 0 : state.zoom - 0.1,
})
}
zoomIn =() => {
setState({
...state,
zoom: state.zoom + 0.1 > 1 ? 1 : state.zoom + 0.1,
});
}
zoomOut() {
this.setState({
zoom: this.state.zoom - 0.1 < 0 ? 0 : this.state.zoom - 0.1,
});
}
zoomIn() {
this.setState({
zoom: this.state.zoom + 0.1 > 1 ? 1 : this.state.zoom + 0.1,
});
}
setFocusDepth(depth) {
this.setState({
depth,
setFocusDepth = (depth) => {
setState({
...state,
depth: depth
});
}
takePicture = async function() {
if (this.camera) {
const data = await this.camera.takePictureAsync();
if (cameraRef) {
const data = await cameraRef.takePictureAsync();
console.warn('takePicture ', data);
}
};
takeVideo = async function() {
if (this.camera) {
if (cameraRef) {
try {
const promise = this.camera.recordAsync(this.state.recordOptions);
const promise = cameraRef.recordAsync( state.recordOptions);
if (promise) {
this.setState({ isRecording: true });
setState({
...state,
isRecording: true
});
const data = await promise;
this.setState({ isRecording: false });
setState({
...state,
isRecording: false
});
console.warn('takeVideo', data);
}
} catch (e) {
@ -148,9 +181,19 @@ export default class CameraScreen extends React.Component {
}
};
toggle = value => () => this.setState(prevState => ({ [value]: !prevState[value] }));
toggle = value => () => {
setState({
...state,
[value]: !prevState[value]
})
};
facesDetected = ({ faces }) => this.setState({ faces });
facesDetected = ({ faces }) => {
setState({
...state,
faces: faces
})
};
renderFace = ({ bounds, faceID, rollAngle, yawAngle }) => (
<View
@ -175,7 +218,7 @@ export default class CameraScreen extends React.Component {
</View>
);
renderLandmarksOfFace(face) {
renderLandmarksOfFace =(face) => {
const renderLandmark = position =>
position && (
<View
@ -207,19 +250,19 @@ export default class CameraScreen extends React.Component {
renderFaces = () => (
<View style={styles.facesContainer} pointerEvents="none">
{this.state.faces.map(this.renderFace)}
{ state.faces.map(() => renderFace())}
</View>
);
renderLandmarks = () => (
<View style={styles.facesContainer} pointerEvents="none">
{this.state.faces.map(this.renderLandmarksOfFace)}
{ state.faces.map(() => renderLandmarksOfFace())}
</View>
);
renderTextBlocks = () => (
<View style={styles.facesContainer} pointerEvents="none">
{this.state.textBlocks.map(this.renderTextBlock)}
{ state.textBlocks.map(() => renderTextBlock())}
</View>
);
@ -243,14 +286,22 @@ export default class CameraScreen extends React.Component {
textRecognized = object => {
const { textBlocks } = object;
this.setState({ textBlocks });
setState({
...state,
textBlocks: textBlocks
});
};
barcodeRecognized = ({ barcodes }) => this.setState({ barcodes });
barcodeRecognized = ({ barcodes }) => {
setState({
...stat,
barcodes
})
};
renderBarcodes = () => (
<View style={styles.facesContainer} pointerEvents="none">
{this.state.barcodes.map(this.renderBarcode)}
{ state.barcodes.map( renderBarcode)}
</View>
);
@ -271,30 +322,30 @@ export default class CameraScreen extends React.Component {
</React.Fragment>
);
renderCamera() {
const { canDetectFaces, canDetectText, canDetectBarcode } = this.state;
renderCamera = () => {
const { canDetectFaces, canDetectText, canDetectBarcode } = state;
const drawFocusRingPosition = {
top: this.state.autoFocusPoint.drawRectPosition.y - 32,
left: this.state.autoFocusPoint.drawRectPosition.x - 32,
top: state.autoFocusPoint.drawRectPosition.y - 32,
left: state.autoFocusPoint.drawRectPosition.x - 32,
};
return (
<RNCamera
ref={ref => {
this.camera = ref;
cameraRef = ref;
}}
style={{
flex: 1,
justifyContent: 'space-between',
}}
type={this.state.type}
flashMode={this.state.flash}
autoFocus={this.state.autoFocus}
autoFocusPointOfInterest={this.state.autoFocusPoint.normalized}
zoom={this.state.zoom}
whiteBalance={this.state.whiteBalance}
ratio={this.state.ratio}
focusDepth={this.state.depth}
type={ state.type}
flashMode={ state.flash}
autoFocus={ state.autoFocus}
autoFocusPointOfInterest={ state.autoFocusPoint.normalized}
zoom={ state.zoom}
whiteBalance={ state.whiteBalance}
ratio={ state.ratio}
focusDepth={ state.depth}
androidCameraPermissionOptions={{
title: 'Permission to use camera',
message: 'We need your permission to use your camera',
@ -306,13 +357,13 @@ export default class CameraScreen extends React.Component {
? RNCamera.Constants.FaceDetection.Landmarks.all
: undefined
}
onFacesDetected={canDetectFaces ? this.facesDetected : null}
onTextRecognized={canDetectText ? this.textRecognized : null}
onGoogleVisionBarcodesDetected={canDetectBarcode ? this.barcodeRecognized : null}
onFacesDetected={canDetectFaces ? facesDetected() : null}
onTextRecognized={canDetectText ? textRecognized() : null}
onGoogleVisionBarcodesDetected={canDetectBarcode ? barcodeRecognized() : null}
>
<View style={StyleSheet.absoluteFill}>
<View style={[styles.autoFocusBox, drawFocusRingPosition]} />
<TouchableWithoutFeedback onPress={this.touchToFocus.bind(this)}>
<TouchableWithoutFeedback onPress={() => touchToFocus()}>
<View style={{ flex: 1 }} />
</TouchableWithoutFeedback>
</View>
@ -332,14 +383,14 @@ export default class CameraScreen extends React.Component {
justifyContent: 'space-around',
}}
>
<TouchableOpacity style={styles.flipButton} onPress={this.toggleFacing.bind(this)}>
<TouchableOpacity style={styles.flipButton} onPress={() => toggleFacing()}>
<Text style={styles.flipText}> FLIP </Text>
</TouchableOpacity>
<TouchableOpacity style={styles.flipButton} onPress={this.toggleFlash.bind(this)}>
<Text style={styles.flipText}> FLASH: {this.state.flash} </Text>
<TouchableOpacity style={styles.flipButton} onPress={() => toggleFlash()}>
<Text style={styles.flipText}> FLASH: { state.flash} </Text>
</TouchableOpacity>
<TouchableOpacity style={styles.flipButton} onPress={this.toggleWB.bind(this)}>
<Text style={styles.flipText}> WB: {this.state.whiteBalance} </Text>
<TouchableOpacity style={styles.flipButton} onPress={() => toggleWB()}>
<Text style={styles.flipText}> WB: { state.whiteBalance} </Text>
</TouchableOpacity>
</View>
<View
@ -349,17 +400,17 @@ export default class CameraScreen extends React.Component {
justifyContent: 'space-around',
}}
>
<TouchableOpacity onPress={this.toggle('canDetectFaces')} style={styles.flipButton}>
<TouchableOpacity onPress={()=> toggle('canDetectFaces')} style={styles.flipButton}>
<Text style={styles.flipText}>
{!canDetectFaces ? 'Detect Faces' : 'Detecting Faces'}
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.toggle('canDetectText')} style={styles.flipButton}>
<TouchableOpacity onPress={()=> toggle('canDetectText')} style={styles.flipButton}>
<Text style={styles.flipText}>
{!canDetectText ? 'Detect Text' : 'Detecting Text'}
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.toggle('canDetectBarcode')} style={styles.flipButton}>
<TouchableOpacity onPress={()=> toggle('canDetectBarcode')} style={styles.flipButton}>
<Text style={styles.flipText}>
{!canDetectBarcode ? 'Detect Barcode' : 'Detecting Barcode'}
</Text>
@ -377,9 +428,9 @@ export default class CameraScreen extends React.Component {
>
<Slider
style={{ width: 150, marginTop: 15, alignSelf: 'flex-end' }}
onValueChange={this.setFocusDepth.bind(this)}
onValueChange={() => setFocusDepth()}
step={0.1}
disabled={this.state.autoFocus === 'on'}
disabled={ state.autoFocus === 'on'}
/>
</View>
<View
@ -396,20 +447,20 @@ export default class CameraScreen extends React.Component {
{
flex: 0.3,
alignSelf: 'flex-end',
backgroundColor: this.state.isRecording ? 'white' : 'darkred',
backgroundColor: state.isRecording ? 'white' : 'darkred',
},
]}
onPress={this.state.isRecording ? () => {} : this.takeVideo.bind(this)}
onPress={ state.isRecording ? () => {} : () => takeVideo()}
>
{this.state.isRecording ? (
{ state.isRecording ? (
<Text style={styles.flipText}> </Text>
) : (
<Text style={styles.flipText}> REC </Text>
)}
</TouchableOpacity>
</View>
{this.state.zoom !== 0 && (
<Text style={[styles.flipText, styles.zoomText]}>Zoom: {this.state.zoom}</Text>
{ state.zoom !== 0 && (
<Text style={[styles.flipText, styles.zoomText]}>Zoom: { state.zoom}</Text>
)}
<View
style={{
@ -421,43 +472,44 @@ export default class CameraScreen extends React.Component {
>
<TouchableOpacity
style={[styles.flipButton, { flex: 0.1, alignSelf: 'flex-end' }]}
onPress={this.zoomIn.bind(this)}
onPress={() => zoomIn()}
>
<Text style={styles.flipText}> + </Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.flipButton, { flex: 0.1, alignSelf: 'flex-end' }]}
onPress={this.zoomOut.bind(this)}
onPress={() => zoomOut()}
>
<Text style={styles.flipText}> - </Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.flipButton, { flex: 0.25, alignSelf: 'flex-end' }]}
onPress={this.toggleFocus.bind(this)}
onPress={() => toggleFocus()}
>
<Text style={styles.flipText}> AF : {this.state.autoFocus} </Text>
<Text style={styles.flipText}> AF : { state.autoFocus} </Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.flipButton, styles.picButton, { flex: 0.3, alignSelf: 'flex-end' }]}
onPress={this.takePicture.bind(this)}
onPress={() => takePicture()}
>
<Text style={styles.flipText}> SNAP </Text>
</TouchableOpacity>
</View>
</View>
{!!canDetectFaces && this.renderFaces()}
{!!canDetectFaces && this.renderLandmarks()}
{!!canDetectText && this.renderTextBlocks()}
{!!canDetectBarcode && this.renderBarcodes()}
{!!canDetectFaces && renderFaces()}
{!!canDetectFaces && renderLandmarks()}
{!!canDetectText && renderTextBlocks()}
{!!canDetectBarcode && renderBarcodes()}
</RNCamera>
);
}
render() {
return <View style={styles.container}>{this.renderCamera()}</View>;
}
return (
<View style={styles.container}>{() => renderCamera()}</View>
);
}
export default CameraScreen;
const styles = StyleSheet.create({
container: {
flex: 1,

View File

@ -1,5 +1,5 @@
// @flow
import React from 'react';
import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import {
findNodeHandle,
@ -342,159 +342,258 @@ const mapValues = (input, mapper) => {
return result;
};
export default class Camera extends React.Component<PropsType, StateType> {
static Constants = {
Type: CameraManager.Type,
FlashMode: CameraManager.FlashMode,
AutoFocus: CameraManager.AutoFocus,
WhiteBalance: CameraManager.WhiteBalance,
VideoQuality: CameraManager.VideoQuality,
VideoCodec: CameraManager.VideoCodec,
BarCodeType: CameraManager.BarCodeType,
GoogleVisionBarcodeDetection: CameraManager.GoogleVisionBarcodeDetection,
FaceDetection: CameraManager.FaceDetection,
CameraStatus,
RecordAudioPermissionStatus: RecordAudioPermissionStatusEnum,
VideoStabilization: CameraManager.VideoStabilization,
Orientation: {
auto: 'auto',
landscapeLeft: 'landscapeLeft',
landscapeRight: 'landscapeRight',
portrait: 'portrait',
portraitUpsideDown: 'portraitUpsideDown',
export const Constants = {
Type: CameraManager.Type,
FlashMode: CameraManager.FlashMode,
AutoFocus: CameraManager.AutoFocus,
WhiteBalance: CameraManager.WhiteBalance,
VideoQuality: CameraManager.VideoQuality,
VideoCodec: CameraManager.VideoCodec,
BarCodeType: CameraManager.BarCodeType,
GoogleVisionBarcodeDetection: CameraManager.GoogleVisionBarcodeDetection,
FaceDetection: CameraManager.FaceDetection,
CameraStatus,
RecordAudioPermissionStatus: RecordAudioPermissionStatusEnum,
VideoStabilization: CameraManager.VideoStabilization,
Orientation: {
auto: 'auto',
landscapeLeft: 'landscapeLeft',
landscapeRight: 'landscapeRight',
portrait: 'portrait',
portraitUpsideDown: 'portraitUpsideDown',
},
};
export const ConversionTables = {
type: CameraManager.Type,
flashMode: CameraManager.FlashMode,
exposure: CameraManager.Exposure,
autoFocus: CameraManager.AutoFocus,
whiteBalance: CameraManager.WhiteBalance,
faceDetectionMode: (CameraManager.FaceDetection || {}).Mode,
faceDetectionLandmarks: (CameraManager.FaceDetection || {}).Landmarks,
faceDetectionClassifications: (CameraManager.FaceDetection || {}).Classifications,
googleVisionBarcodeType: (CameraManager.GoogleVisionBarcodeDetection || {}).BarcodeType,
videoStabilizationMode: CameraManager.VideoStabilization || {},
};
export default function Camera(props) {
let _cameraRef = useRef((ref) => setReference(ref));
let _cameraHandle: ?number;
let _lastEvents: { [string]: string };
let _lastEventsTimes: { [string]: Date };
let _isMounted: boolean;
const [isAuthorized, setIsAuthorized] = useState<boolean>(false);
const [isAuthorizationChecked, setIsAuthorizationChecked] = useState<boolean>(false);
const [recordAudioPermissionStatus, setRecordAudioPermissionStatus] = useState(
RecordAudioPermissionStatusEnum.PENDING_AUTHORIZATION,
);
const convertProp = useMemo((value: *, key: string) => {
if (typeof value === 'string' && Camera.ConversionTables[key]) {
return Camera.ConversionTables[key][value];
}
return value;
}, []);
const convertNativeProps = useCallback(
(nativeProps: PropsType) => () => {
const { children, ...props } = nativeProps;
const newProps = mapValues(props, convertProp);
if (props.onBarCodeRead) {
newProps.barCodeScannerEnabled = true;
}
if (props.onGoogleVisionBarcodesDetected) {
newProps.googleVisionBarcodeDetectorEnabled = true;
}
if (props.onFacesDetected) {
newProps.faceDetectorEnabled = true;
}
if (props.onTextRecognized) {
newProps.textRecognizerEnabled = true;
}
if (Platform.OS === 'ios') {
delete newProps.googleVisionBarcodeMode;
delete newProps.ratio;
}
return newProps;
},
};
// Values under keys from this object will be transformed to native options
static ConversionTables = {
type: CameraManager.Type,
flashMode: CameraManager.FlashMode,
exposure: CameraManager.Exposure,
autoFocus: CameraManager.AutoFocus,
whiteBalance: CameraManager.WhiteBalance,
faceDetectionMode: (CameraManager.FaceDetection || {}).Mode,
faceDetectionLandmarks: (CameraManager.FaceDetection || {}).Landmarks,
faceDetectionClassifications: (CameraManager.FaceDetection || {}).Classifications,
googleVisionBarcodeType: (CameraManager.GoogleVisionBarcodeDetection || {}).BarcodeType,
videoStabilizationMode: CameraManager.VideoStabilization || {},
};
static propTypes = {
...ViewPropTypes,
zoom: PropTypes.number,
maxZoom: PropTypes.number,
ratio: PropTypes.string,
focusDepth: PropTypes.number,
onMountError: PropTypes.func,
onCameraReady: PropTypes.func,
onAudioInterrupted: PropTypes.func,
onAudioConnected: PropTypes.func,
onStatusChange: PropTypes.func,
onBarCodeRead: PropTypes.func,
onPictureTaken: PropTypes.func,
onPictureSaved: PropTypes.func,
onGoogleVisionBarcodesDetected: PropTypes.func,
onFacesDetected: PropTypes.func,
onTextRecognized: PropTypes.func,
onSubjectAreaChanged: PropTypes.func,
trackingEnabled: PropTypes.bool,
faceDetectionMode: PropTypes.number,
faceDetectionLandmarks: PropTypes.number,
faceDetectionClassifications: PropTypes.number,
barCodeTypes: PropTypes.arrayOf(PropTypes.string),
googleVisionBarcodeType: PropTypes.number,
googleVisionBarcodeMode: PropTypes.number,
type: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
cameraId: PropTypes.string,
flashMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
exposure: PropTypes.number,
whiteBalance: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
autoFocus: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
autoFocusPointOfInterest: PropTypes.shape({ x: PropTypes.number, y: PropTypes.number }),
permissionDialogTitle: PropTypes.string,
permissionDialogMessage: PropTypes.string,
androidCameraPermissionOptions: Rationale,
androidRecordAudioPermissionOptions: Rationale,
notAuthorizedView: PropTypes.element,
pendingAuthorizationView: PropTypes.element,
captureAudio: PropTypes.bool,
keepAudioSession: PropTypes.bool,
useCamera2Api: PropTypes.bool,
playSoundOnCapture: PropTypes.bool,
videoStabilizationMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
pictureSize: PropTypes.string,
mirrorVideo: PropTypes.bool,
rectOfInterest: PropTypes.any,
defaultVideoQuality: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};
static defaultProps: Object = {
zoom: 0,
maxZoom: 0,
ratio: '4:3',
focusDepth: 0,
type: CameraManager.Type.back,
cameraId: null,
autoFocus: CameraManager.AutoFocus.on,
flashMode: CameraManager.FlashMode.off,
exposure: -1,
whiteBalance: CameraManager.WhiteBalance.auto,
faceDetectionMode: (CameraManager.FaceDetection || {}).fast,
barCodeTypes: Object.values(CameraManager.BarCodeType),
googleVisionBarcodeType: ((CameraManager.GoogleVisionBarcodeDetection || {}).BarcodeType || {})
.None,
googleVisionBarcodeMode: ((CameraManager.GoogleVisionBarcodeDetection || {}).BarcodeMode || {})
.NORMAL,
faceDetectionLandmarks: ((CameraManager.FaceDetection || {}).Landmarks || {}).none,
faceDetectionClassifications: ((CameraManager.FaceDetection || {}).Classifications || {}).none,
permissionDialogTitle: '',
permissionDialogMessage: '',
androidCameraPermissionOptions: {
title: '',
message: '',
[],
);
const hasFaCC = useMemo(() => typeof props.children === 'function', [props.children]);
const setReference = useCallback(
(ref) => {
if (ref) {
_cameraHandle = findNodeHandle(ref);
} else {
_cameraRef = null;
_cameraHandle = null;
}
},
androidRecordAudioPermissionOptions: {
title: '',
message: '',
[_cameraRef, _cameraHandle],
);
const _onMountError = useCallback(
({ nativeEvent }: EventCallbackArgumentsType) => {
if (props.onMountError) {
props.onMountError(nativeEvent);
}
},
notAuthorizedView: (
<View style={styles.authorizationContainer}>
<Text style={styles.notAuthorizedText}>Camera not authorized</Text>
</View>
),
pendingAuthorizationView: (
<View style={styles.authorizationContainer}>
<ActivityIndicator size="small" />
</View>
),
captureAudio: true,
keepAudioSession: false,
useCamera2Api: false,
playSoundOnCapture: false,
pictureSize: 'None',
videoStabilizationMode: 0,
mirrorVideo: false,
[props.onMountError],
);
const _onCameraReady = useCallback(() => {
if (props.onCameraReady) {
props.onCameraReady();
}
}, [props.onCameraReady]);
const _onAudioInterrupted = useCallback(() => {
if (props.onAudioInterrupted) {
props.onAudioInterrupted();
}
}, [props.onAudioInterrupted]);
const _onAudioConnected = useCallback(() => {
if (props.onAudioConnected) {
props.onAudioConnected();
}
}, [props.onAudioConnected]);
const _onObjectDetected = useCallback(
(callback: ?Function) => ({ nativeEvent }: EventCallbackArgumentsType) => {
const { type } = nativeEvent;
if (
_lastEvents[type] &&
_lastEventsTimes[type] &&
JSON.stringify(nativeEvent) === _lastEvents[type] &&
new Date() - _lastEventsTimes[type] < EventThrottleMs
) {
return;
}
if (callback) {
callback(nativeEvent);
_lastEventsTimes[type] = new Date();
_lastEvents[type] = JSON.stringify(nativeEvent);
}
},
[],
);
const _onPictureSaved = useCallback(
({ nativeEvent }: EventCallbackArgumentsType) => {
if (props.onPictureSaved) {
props.onPictureSaved(nativeEvent);
}
},
[props.onPictureSaved],
);
const _onSubjectAreaChanged = useCallback(
e => () => {
if (props.onSubjectAreaChanged) {
props.onSubjectAreaChanged(e);
}
},
[props.onSubjectAreaChanged],
);
const getStatus = useMemo(() => {
if (isAuthorizationChecked === false) {
return CameraStatus.PENDING_AUTHORIZATION;
}
return isAuthorized ? CameraStatus.READY : CameraStatus.NOT_AUTHORIZED;
}, [isAuthorizationChecked, isAuthorized]);
const renderChildren = useMemo(() => {
if (hasFaCC) {
return props.children({
camera: this,
status: getStatus,
recordAudioPermissionStatus: recordAudioPermissionStatus,
});
}
return props.children;
}, []);
const { style, ...nativeProps } = convertNativeProps(props);
const arePermissionsGranted = async () => {
const {
permissionDialogTitle,
permissionDialogMessage,
androidCameraPermissionOptions,
androidRecordAudioPermissionOptions,
} = props;
let cameraPermissions = androidCameraPermissionOptions;
let audioPermissions = androidRecordAudioPermissionOptions;
if (permissionDialogTitle || permissionDialogMessage) {
// eslint-disable-next-line no-console
console.warn(
'permissionDialogTitle and permissionDialogMessage are deprecated. Please use androidCameraPermissionOptions instead.',
);
cameraPermissions = {
...cameraPermissions,
title: permissionDialogTitle,
message: permissionDialogMessage,
};
audioPermissions = {
...audioPermissions,
title: permissionDialogTitle,
message: permissionDialogMessage,
};
}
const { hasCameraPermissions, hasRecordAudioPermissions } = await requestPermissions(
props.captureAudio,
CameraManager,
cameraPermissions,
audioPermissions,
);
const recordAudioPermissionStatus = hasRecordAudioPermissions
? RecordAudioPermissionStatusEnum.AUTHORIZED
: RecordAudioPermissionStatusEnum.NOT_AUTHORIZED;
return { hasCameraPermissions, recordAudioPermissionStatus };
};
_cameraRef: ?Object;
_cameraHandle: ?number;
_lastEvents: { [string]: string };
_lastEventsTimes: { [string]: Date };
_isMounted: boolean;
const refreshAuthorizationStatus = async () => {
const { hasCameraPermissions, recordAudioPermissionStatus } = await arePermissionsGranted();
if (_isMounted === false) {
return;
}
constructor(props: PropsType) {
super(props);
this._lastEvents = {};
this._lastEventsTimes = {};
this._isMounted = true;
this.state = {
isAuthorized: false,
isAuthorizationChecked: false,
recordAudioPermissionStatus: RecordAudioPermissionStatusEnum.PENDING_AUTHORIZATION,
};
}
setIsAuthorized(hasCameraPermissions);
setIsAuthorizationChecked(true);
setRecordAudioPermissionStatus(recordAudioPermissionStatus);
};
async takePictureAsync(options?: PictureOptions) {
const _onStatusChange = useCallback(() => {
if (props.onStatusChange) {
props.onStatusChange({
cameraStatus: getStatus(),
recordAudioPermissionStatus,
});
}
}, [props.onStatusChange]);
const stopRecording = useCallback(() => {
CameraManager.stopRecording(_cameraHandle);
}, [_cameraHandle]);
const pausePreview = useCallback(() => {
CameraManager.pausePreview(_cameraHandle);
}, [_cameraHandle]);
const isRecording = useCallback(() => {
return CameraManager.isRecording(_cameraHandle);
}, [_cameraHandle]);
const resumePreview = useCallback(() => {
CameraManager.resumePreview(_cameraHandle);
}, [_cameraHandle]);
const takePicture = async (options?: PictureOptions) => {
if (!options) {
options = {};
}
@ -519,35 +618,35 @@ export default class Camera extends React.Component<PropsType, StateType> {
options.pauseAfterCapture = false;
}
if (!this._cameraHandle) {
if (!_cameraHandle) {
throw 'Camera handle cannot be null';
}
return await CameraManager.takePicture(options, this._cameraHandle);
}
return await CameraManager.takePicture(options, _cameraHandle);
};
async getSupportedRatiosAsync() {
const getSupportedRatiosAsync = async () => {
if (Platform.OS === 'android') {
return await CameraManager.getSupportedRatios(this._cameraHandle);
return await CameraManager.getSupportedRatios(_cameraHandle);
} else {
throw new Error('Ratio is not supported on iOS');
}
}
};
async getCameraIdsAsync() {
const getCameraIdsAsync = async () => {
if (Platform.OS === 'android') {
return await CameraManager.getCameraIds(this._cameraHandle);
return await CameraManager.getCameraIds(_cameraHandle);
} else {
return await CameraManager.getCameraIds(); // iOS does not need a camera instance
}
}
getAvailablePictureSizes = async (): string[] => {
//$FlowFixMe
return await CameraManager.getAvailablePictureSizes(this.props.ratio, this._cameraHandle);
};
async recordAsync(options?: RecordingOptions) {
const getAvailablePictureSizes = async (): string[] => {
//$FlowFixMe
return await CameraManager.getAvailablePictureSizes(props.ratio, _cameraHandle);
};
const recordAsync = async (options?: RecordingOptions) => {
if (!options || typeof options !== 'object') {
options = {};
} else if (typeof options.quality === 'string') {
@ -573,8 +672,7 @@ export default class Camera extends React.Component<PropsType, StateType> {
}
}
const { recordAudioPermissionStatus } = this.state;
const { captureAudio } = this.props;
const { captureAudio } = props;
if (
!captureAudio ||
@ -593,269 +691,153 @@ export default class Camera extends React.Component<PropsType, StateType> {
}
}
return await CameraManager.record(options, this._cameraHandle);
}
stopRecording() {
CameraManager.stopRecording(this._cameraHandle);
}
pausePreview() {
CameraManager.pausePreview(this._cameraHandle);
}
isRecording() {
return CameraManager.isRecording(this._cameraHandle);
}
resumePreview() {
CameraManager.resumePreview(this._cameraHandle);
}
_onMountError = ({ nativeEvent }: EventCallbackArgumentsType) => {
if (this.props.onMountError) {
this.props.onMountError(nativeEvent);
}
return await CameraManager.record(options, _cameraHandle);
};
_onCameraReady = () => {
if (this.props.onCameraReady) {
this.props.onCameraReady();
}
};
useEffect(() => {
async function getPermissionsAndSetState() {
const { hasCameraPermissions, recordAudioPermissionStatus } = await arePermissionsGranted();
if (_isMounted === false) {
return;
}
_onAudioInterrupted = () => {
if (this.props.onAudioInterrupted) {
this.props.onAudioInterrupted();
}
};
_onAudioConnected = () => {
if (this.props.onAudioConnected) {
this.props.onAudioConnected();
}
};
_onStatusChange = () => {
if (this.props.onStatusChange) {
this.props.onStatusChange({
cameraStatus: this.getStatus(),
recordAudioPermissionStatus: this.state.recordAudioPermissionStatus,
});
}
};
_onPictureSaved = ({ nativeEvent }: EventCallbackArgumentsType) => {
if (this.props.onPictureSaved) {
this.props.onPictureSaved(nativeEvent);
}
};
_onObjectDetected = (callback: ?Function) => ({ nativeEvent }: EventCallbackArgumentsType) => {
const { type } = nativeEvent;
if (
this._lastEvents[type] &&
this._lastEventsTimes[type] &&
JSON.stringify(nativeEvent) === this._lastEvents[type] &&
new Date() - this._lastEventsTimes[type] < EventThrottleMs
) {
return;
setIsAuthorized(hasCameraPermissions);
setIsAuthorizationChecked(true);
setRecordAudioPermissionStatus(recordAudioPermissionStatus);
_onStatusChange();
}
if (callback) {
callback(nativeEvent);
this._lastEventsTimes[type] = new Date();
this._lastEvents[type] = JSON.stringify(nativeEvent);
}
};
getPermissionsAndSetState();
_onSubjectAreaChanged = e => {
if (this.props.onSubjectAreaChanged) {
this.props.onSubjectAreaChanged(e);
}
};
return () => {
_isMounted = false;
};
}, []);
_setReference = (ref: ?Object) => {
if (ref) {
this._cameraRef = ref;
this._cameraHandle = findNodeHandle(ref);
} else {
this._cameraRef = null;
this._cameraHandle = null;
}
};
componentWillUnmount() {
this._isMounted = false;
}
async arePermissionsGranted() {
const {
permissionDialogTitle,
permissionDialogMessage,
androidCameraPermissionOptions,
androidRecordAudioPermissionOptions,
} = this.props;
let cameraPermissions = androidCameraPermissionOptions;
let audioPermissions = androidRecordAudioPermissionOptions;
if (permissionDialogTitle || permissionDialogMessage) {
// eslint-disable-next-line no-console
console.warn(
'permissionDialogTitle and permissionDialogMessage are deprecated. Please use androidCameraPermissionOptions instead.',
);
cameraPermissions = {
...cameraPermissions,
title: permissionDialogTitle,
message: permissionDialogMessage,
};
audioPermissions = {
...audioPermissions,
title: permissionDialogTitle,
message: permissionDialogMessage,
};
}
const { hasCameraPermissions, hasRecordAudioPermissions } = await requestPermissions(
this.props.captureAudio,
CameraManager,
cameraPermissions,
audioPermissions,
if (isAuthorized || hasFaCC) {
return (
<View style={style}>
<RNCamera
{...nativeProps}
style={StyleSheet.absoluteFill}
ref={cameraRef}
onMountError={_onMountError}
onCameraReady={_onCameraReady}
onAudioInterrupted={_onAudioInterrupted}
onAudioConnected={_onAudioConnected}
onGoogleVisionBarcodesDetected={_onObjectDetected(props.onGoogleVisionBarcodesDetected)}
onBarCodeRead={_onObjectDetected(props.onBarCodeRead)}
onFacesDetected={_onObjectDetected(props.onFacesDetected)}
onTextRecognized={_onObjectDetected(props.onTextRecognized)}
onPictureSaved={_onPictureSaved}
onSubjectAreaChanged={_onSubjectAreaChanged}
/>
{renderChildren}
</View>
);
const recordAudioPermissionStatus = hasRecordAudioPermissions
? RecordAudioPermissionStatusEnum.AUTHORIZED
: RecordAudioPermissionStatusEnum.NOT_AUTHORIZED;
return { hasCameraPermissions, recordAudioPermissionStatus };
}
async refreshAuthorizationStatus() {
const {
hasCameraPermissions,
recordAudioPermissionStatus,
} = await this.arePermissionsGranted();
if (this._isMounted === false) {
return;
}
this.setState({
isAuthorized: hasCameraPermissions,
isAuthorizationChecked: true,
recordAudioPermissionStatus,
});
}
async componentDidMount() {
const {
hasCameraPermissions,
recordAudioPermissionStatus,
} = await this.arePermissionsGranted();
if (this._isMounted === false) {
return;
}
this.setState(
{
isAuthorized: hasCameraPermissions,
isAuthorizationChecked: true,
recordAudioPermissionStatus,
},
this._onStatusChange,
);
}
getStatus = (): Status => {
const { isAuthorized, isAuthorizationChecked } = this.state;
if (isAuthorizationChecked === false) {
return CameraStatus.PENDING_AUTHORIZATION;
}
return isAuthorized ? CameraStatus.READY : CameraStatus.NOT_AUTHORIZED;
};
// FaCC = Function as Child Component;
hasFaCC = (): * => typeof this.props.children === 'function';
renderChildren = (): * => {
if (this.hasFaCC()) {
return this.props.children({
camera: this,
status: this.getStatus(),
recordAudioPermissionStatus: this.state.recordAudioPermissionStatus,
});
}
return this.props.children;
};
render() {
const { style, ...nativeProps } = this._convertNativeProps(this.props);
if (this.state.isAuthorized || this.hasFaCC()) {
return (
<View style={style}>
<RNCamera
{...nativeProps}
style={StyleSheet.absoluteFill}
ref={this._setReference}
onMountError={this._onMountError}
onCameraReady={this._onCameraReady}
onAudioInterrupted={this._onAudioInterrupted}
onAudioConnected={this._onAudioConnected}
onGoogleVisionBarcodesDetected={this._onObjectDetected(
this.props.onGoogleVisionBarcodesDetected,
)}
onBarCodeRead={this._onObjectDetected(this.props.onBarCodeRead)}
onFacesDetected={this._onObjectDetected(this.props.onFacesDetected)}
onTextRecognized={this._onObjectDetected(this.props.onTextRecognized)}
onPictureSaved={this._onPictureSaved}
onSubjectAreaChanged={this._onSubjectAreaChanged}
/>
{this.renderChildren()}
</View>
);
} else if (!this.state.isAuthorizationChecked) {
return this.props.pendingAuthorizationView;
} else {
return this.props.notAuthorizedView;
}
}
_convertNativeProps({ children, ...props }: PropsType) {
const newProps = mapValues(props, this._convertProp);
if (props.onBarCodeRead) {
newProps.barCodeScannerEnabled = true;
}
if (props.onGoogleVisionBarcodesDetected) {
newProps.googleVisionBarcodeDetectorEnabled = true;
}
if (props.onFacesDetected) {
newProps.faceDetectorEnabled = true;
}
if (props.onTextRecognized) {
newProps.textRecognizerEnabled = true;
}
if (Platform.OS === 'ios') {
delete newProps.googleVisionBarcodeMode;
delete newProps.ratio;
}
return newProps;
}
_convertProp(value: *, key: string): * {
if (typeof value === 'string' && Camera.ConversionTables[key]) {
return Camera.ConversionTables[key][value];
}
return value;
} else if (!isAuthorizationChecked) {
return props.pendingAuthorizationView;
} else {
return props.notAuthorizedView;
}
}
export const Constants = Camera.Constants;
Camera.propTypes = {
...ViewPropTypes,
zoom: PropTypes.number,
maxZoom: PropTypes.number,
ratio: PropTypes.string,
focusDepth: PropTypes.number,
onMountError: PropTypes.func,
onCameraReady: PropTypes.func,
onAudioInterrupted: PropTypes.func,
onAudioConnected: PropTypes.func,
onStatusChange: PropTypes.func,
onBarCodeRead: PropTypes.func,
onPictureTaken: PropTypes.func,
onPictureSaved: PropTypes.func,
onGoogleVisionBarcodesDetected: PropTypes.func,
onFacesDetected: PropTypes.func,
onTextRecognized: PropTypes.func,
onSubjectAreaChanged: PropTypes.func,
trackingEnabled: PropTypes.bool,
faceDetectionMode: PropTypes.number,
faceDetectionLandmarks: PropTypes.number,
faceDetectionClassifications: PropTypes.number,
barCodeTypes: PropTypes.arrayOf(PropTypes.string),
googleVisionBarcodeType: PropTypes.number,
googleVisionBarcodeMode: PropTypes.number,
type: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
cameraId: PropTypes.string,
flashMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
exposure: PropTypes.number,
whiteBalance: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
autoFocus: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
autoFocusPointOfInterest: PropTypes.shape({ x: PropTypes.number, y: PropTypes.number }),
permissionDialogTitle: PropTypes.string,
permissionDialogMessage: PropTypes.string,
androidCameraPermissionOptions: Rationale,
androidRecordAudioPermissionOptions: Rationale,
notAuthorizedView: PropTypes.element,
pendingAuthorizationView: PropTypes.element,
captureAudio: PropTypes.bool,
keepAudioSession: PropTypes.bool,
useCamera2Api: PropTypes.bool,
playSoundOnCapture: PropTypes.bool,
videoStabilizationMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
pictureSize: PropTypes.string,
mirrorVideo: PropTypes.bool,
rectOfInterest: PropTypes.any,
defaultVideoQuality: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};
Camera.defaultProps = {
zoom: 0,
maxZoom: 0,
ratio: '4:3',
focusDepth: 0,
type: CameraManager.Type.back,
cameraId: null,
autoFocus: CameraManager.AutoFocus.on,
flashMode: CameraManager.FlashMode.off,
exposure: -1,
whiteBalance: CameraManager.WhiteBalance.auto,
faceDetectionMode: (CameraManager.FaceDetection || {}).fast,
barCodeTypes: Object.values(CameraManager.BarCodeType),
googleVisionBarcodeType: ((CameraManager.GoogleVisionBarcodeDetection || {}).BarcodeType || {})
.None,
googleVisionBarcodeMode: ((CameraManager.GoogleVisionBarcodeDetection || {}).BarcodeMode || {})
.NORMAL,
faceDetectionLandmarks: ((CameraManager.FaceDetection || {}).Landmarks || {}).none,
faceDetectionClassifications: ((CameraManager.FaceDetection || {}).Classifications || {}).none,
permissionDialogTitle: '',
permissionDialogMessage: '',
androidCameraPermissionOptions: {
title: '',
message: '',
},
androidRecordAudioPermissionOptions: {
title: '',
message: '',
},
notAuthorizedView: (
<View style={styles.authorizationContainer}>
<Text style={styles.notAuthorizedText}>Camera not authorized</Text>
</View>
),
pendingAuthorizationView: (
<View style={styles.authorizationContainer}>
<ActivityIndicator size="small" />
</View>
),
captureAudio: true,
keepAudioSession: false,
useCamera2Api: false,
playSoundOnCapture: false,
pictureSize: 'None',
videoStabilizationMode: 0,
mirrorVideo: false,
};
const RNCamera = requireNativeComponent('RNCamera', Camera, {
nativeOnly: {
@ -880,4 +862,4 @@ const RNCamera = requireNativeComponent('RNCamera', Camera, {
renderToHardwareTextureAndroid: true,
testID: true,
},
});
});

View File

@ -1,6 +1,10 @@
// @flow
import RNCamera, { type Status as _CameraStatus } from './RNCamera';
import RNCamera, {
type Status as _CameraStatus,
Constants as RNCConstants,
ConversionTables,
} from './RNCamera';
import FaceDetector from './FaceDetector';
export type CameraStatus = _CameraStatus;
export { RNCamera, FaceDetector };
export { RNCamera, FaceDetector, RNCConstants, ConversionTables };

70
types/index.d.ts vendored
View File

@ -94,13 +94,11 @@ type RecordAudioPermissionStatus = Readonly<
NOT_AUTHORIZED: 'NOT_AUTHORIZED';
}>
>;
type FaCC = (
params: {
type FaCC = (params: {
camera: RNCamera;
status: keyof CameraStatus;
recordAudioPermissionStatus: keyof RecordAudioPermissionStatus;
},
) => JSX.Element;
}) => JSX.Element;
export interface Constants {
CameraStatus: CameraStatus;
@ -135,7 +133,7 @@ export interface RNCameraProps {
autoFocus?: keyof AutoFocus;
autoFocusPointOfInterest?: Point;
/* iOS only */
onSubjectAreaChanged?: (event: { nativeEvent: { prevPoint: { x: number; y: number; } } }) => void;
onSubjectAreaChanged?: (event: { nativeEvent: { prevPoint: { x: number; y: number } } }) => void;
type?: keyof CameraType;
flashMode?: keyof FlashMode;
notAuthorizedView?: JSX.Element;
@ -178,12 +176,12 @@ export interface RNCameraProps {
* @description For Android use `{ width: number, height: number, origin: Array<Point<string>> }`
* @description For iOS use `{ origin: Point<string>, size: Size<string> }`
*/
bounds: { width: number, height: number, origin: Array<Point<string>> } | { origin: Point<string>; size: Size<string> };
bounds:
| { width: number; height: number; origin: Array<Point<string>> }
| { origin: Point<string>; size: Size<string> };
}): void;
onGoogleVisionBarcodesDetected?(event: {
barcodes: Barcode[];
}): void;
onGoogleVisionBarcodesDetected?(event: { barcodes: Barcode[] }): void;
// -- FACE DETECTION PROPS
@ -257,9 +255,9 @@ export interface Barcode {
firstName?: string;
lastName?: string;
middleName?: string;
prefix?:string;
pronounciation?:string;
suffix?:string;
prefix?: string;
pronounciation?: string;
suffix?: string;
formattedName?: string;
};
phone?: Phone;
@ -298,29 +296,29 @@ export interface Barcode {
}
export type BarcodeType =
|"EMAIL"
|"PHONE"
|"CALENDAR_EVENT"
|"DRIVER_LICENSE"
|"GEO"
|"SMS"
|"CONTACT_INFO"
|"WIFI"
|"TEXT"
|"ISBN"
|"PRODUCT"
|"URL"
|'EMAIL'
|'PHONE'
|'CALENDAR_EVENT'
|'DRIVER_LICENSE'
|'GEO'
|'SMS'
|'CONTACT_INFO'
|'WIFI'
|'TEXT'
|'ISBN'
|'PRODUCT'
|'URL'
export interface Email {
address?: string;
body?: string;
subject?: string;
emailType?: "UNKNOWN" | "Work" | "Home";
emailType?: 'UNKNOWN' | 'Work' | 'Home';
}
export interface Phone {
number?: string;
phoneType?: "UNKNOWN" | "Work" | "Home" | "Fax" | "Mobile";
phoneType?: 'UNKNOWN' | 'Work' | 'Home' | 'Fax' | 'Mobile';
}
export interface Face {
@ -409,26 +407,8 @@ export interface RecordResponse {
codec: VideoCodec[keyof VideoCodec];
}
export class RNCamera extends Component<RNCameraProps & ViewProperties> {
static Constants: Constants;
export function RNCamera(props: RNCameraProps & ViewProperties): Component;
_cameraRef: null | NativeMethodsMixinStatic;
_cameraHandle: ReturnType<typeof findNodeHandle>;
takePictureAsync(options?: TakePictureOptions): Promise<TakePictureResponse>;
recordAsync(options?: RecordOptions): Promise<RecordResponse>;
refreshAuthorizationStatus(): Promise<void>;
stopRecording(): void;
pausePreview(): void;
resumePreview(): void;
getAvailablePictureSizes(): Promise<string[]>;
/** Android only */
getSupportedRatiosAsync(): Promise<string[]>;
/** iOS only */
isRecording(): Promise<boolean>;
}
interface DetectionOptions {
mode?: keyof FaceDetectionMode;