react-native-camera
Version:
A Camera component for React Native. Also reads barcodes.
945 lines (853 loc) • 27.4 kB
JavaScript
// @flow
import React from 'react';
import PropTypes from 'prop-types';
import {
findNodeHandle,
Platform,
NativeModules,
ViewPropTypes,
requireNativeComponent,
View,
ActivityIndicator,
Text,
StyleSheet,
PermissionsAndroid,
} from 'react-native';
import type { FaceFeature } from './FaceDetector';
const Rationale = PropTypes.shape({
title: PropTypes.string.isRequired,
message: PropTypes.string.isRequired,
buttonPositive: PropTypes.string,
buttonNegative: PropTypes.string,
buttonNeutral: PropTypes.string,
});
const requestPermissions = async (
captureAudio: boolean,
CameraManager: any,
androidCameraPermissionOptions: Rationale | null,
androidRecordAudioPermissionOptions: Rationale | null,
): Promise<{ hasCameraPermissions: boolean, hasRecordAudioPermissions: boolean }> => {
let hasCameraPermissions = false;
let hasRecordAudioPermissions = false;
if (Platform.OS === 'ios') {
hasCameraPermissions = await CameraManager.checkVideoAuthorizationStatus();
} else if (Platform.OS === 'android') {
const cameraPermissionResult = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.CAMERA,
androidCameraPermissionOptions,
);
if (typeof cameraPermissionResult === 'boolean') {
hasCameraPermissions = cameraPermissionResult;
} else {
hasCameraPermissions = cameraPermissionResult === PermissionsAndroid.RESULTS.GRANTED;
}
} else if (Platform.OS === 'windows') {
hasCameraPermissions = await CameraManager.checkMediaCapturePermission();
}
if (captureAudio) {
if (Platform.OS === 'ios') {
hasRecordAudioPermissions = await CameraManager.checkRecordAudioAuthorizationStatus();
} else if (Platform.OS === 'android') {
if (await CameraManager.checkIfRecordAudioPermissionsAreDefined()) {
const audioPermissionResult = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
androidRecordAudioPermissionOptions,
);
if (typeof audioPermissionResult === 'boolean') {
hasRecordAudioPermissions = audioPermissionResult;
} else {
hasRecordAudioPermissions = audioPermissionResult === PermissionsAndroid.RESULTS.GRANTED;
}
} else if (__DEV__) {
// eslint-disable-next-line no-console
console.warn(
`The 'captureAudio' property set on RNCamera instance but 'RECORD_AUDIO' permissions not defined in the applications 'AndroidManifest.xml'. ` +
`If you want to record audio you will have to add '<uses-permission android:name="android.permission.RECORD_AUDIO"/>' to your 'AndroidManifest.xml'. ` +
`Otherwise you should set the 'captureAudio' property on the component instance to 'false'.`,
);
}
} else if (Platform.OS === 'windows') {
hasRecordAudioPermissions = await CameraManager.checkMediaCapturePermission();
}
}
return {
hasCameraPermissions,
hasRecordAudioPermissions,
};
};
const styles = StyleSheet.create({
authorizationContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
notAuthorizedText: {
textAlign: 'center',
fontSize: 16,
},
});
type Orientation = 'auto' | 'landscapeLeft' | 'landscapeRight' | 'portrait' | 'portraitUpsideDown';
type OrientationNumber = 1 | 2 | 3 | 4;
type PictureOptions = {
quality?: number,
orientation?: Orientation | OrientationNumber,
base64?: boolean,
mirrorImage?: boolean,
exif?: boolean,
writeExif?: boolean | { [name: string]: any },
width?: number,
fixOrientation?: boolean,
forceUpOrientation?: boolean,
pauseAfterCapture?: boolean,
};
type TrackedFaceFeature = FaceFeature & {
faceID?: number,
};
type TrackedTextFeature = {
type: string,
bounds: {
size: {
width: number,
height: number,
},
origin: {
x: number,
y: number,
},
},
value: string,
components: Array<TrackedTextFeature>,
};
type TrackedBarcodeFeature = {
bounds: {
size: {
width: number,
height: number,
},
origin: {
x: number,
y: number,
},
},
data: string,
dataRaw: string,
type: BarcodeType,
format?: string,
addresses?: {
addressesType?: 'UNKNOWN' | 'Work' | 'Home',
addressLines?: string[],
}[],
emails?: Email[],
phones?: Phone[],
urls: ?(string[]),
name?: {
firstName?: string,
lastName?: string,
middleName?: string,
prefix?: string,
pronounciation?: string,
suffix?: string,
formattedName?: string,
},
phone?: Phone,
organization?: string,
latitude?: number,
longitude?: number,
ssid?: string,
password?: string,
encryptionType?: string,
title?: string,
url?: string,
firstName?: string,
middleName?: string,
lastName?: string,
gender?: string,
addressCity?: string,
addressState?: string,
addressStreet?: string,
addressZip?: string,
birthDate?: string,
documentType?: string,
licenseNumber?: string,
expiryDate?: string,
issuingDate?: string,
issuingCountry?: string,
eventDescription?: string,
location?: string,
organizer?: string,
status?: string,
summary?: string,
start?: string,
end?: string,
email?: Email,
phoneNumber?: string,
message?: string,
};
type BarcodeType =
| 'EMAIL'
| 'PHONE'
| 'CALENDAR_EVENT'
| 'DRIVER_LICENSE'
| 'GEO'
| 'SMS'
| 'CONTACT_INFO'
| 'WIFI'
| 'TEXT'
| 'ISBN'
| 'PRODUCT'
| 'URL';
type Email = {
address?: string,
body?: string,
subject?: string,
emailType?: 'UNKNOWN' | 'Work' | 'Home',
};
type Phone = {
number?: string,
phoneType?: 'UNKNOWN' | 'Work' | 'Home' | 'Fax' | 'Mobile',
};
type RecordingOptions = {
maxDuration?: number,
maxFileSize?: number,
orientation?: Orientation,
quality?: number | string,
fps?: number,
codec?: string,
mute?: boolean,
path?: string,
videoBitrate?: number,
};
type EventCallbackArgumentsType = {
nativeEvent: Object,
};
type Rect = {
x: number,
y: number,
width: number,
height: number,
};
type PropsType = typeof View.props & {
zoom?: number,
useNativeZoom?: boolean,
maxZoom?: number,
ratio?: string,
focusDepth?: number,
type?: number | string,
onCameraReady?: Function,
onAudioInterrupted?: Function,
onAudioConnected?: Function,
onStatusChange?: Function,
onBarCodeRead?: Function,
onPictureTaken?: Function,
onPictureSaved?: Function,
onRecordingStart?: Function,
onRecordingEnd?: Function,
onTap?: Function,
onDoubleTap?: Function,
onGoogleVisionBarcodesDetected?: ({ barcodes: Array<TrackedBarcodeFeature> }) => void,
onSubjectAreaChanged?: ({ nativeEvent: { prevPoint: {| x: number, y: number |} } }) => void,
faceDetectionMode?: number,
trackingEnabled?: boolean,
flashMode?: number | string,
exposure?: number,
barCodeTypes?: Array<string>,
googleVisionBarcodeType?: number,
googleVisionBarcodeMode?: number,
whiteBalance?: number | string | {temperature: number, tint: number, redGainOffset?: number, greenGainOffset?: number, blueGainOffset?: number },
faceDetectionLandmarks?: number,
autoFocus?: string | boolean | number,
autoFocusPointOfInterest?: { x: number, y: number },
faceDetectionClassifications?: number,
onFacesDetected?: ({ faces: Array<TrackedFaceFeature> }) => void,
onTextRecognized?: ({ textBlocks: Array<TrackedTextFeature> }) => void,
captureAudio?: boolean,
keepAudioSession?: boolean,
useCamera2Api?: boolean,
playSoundOnCapture?: boolean,
playSoundOnRecord?: boolean,
videoStabilizationMode?: number | string,
pictureSize?: string,
rectOfInterest: Rect,
};
type StateType = {
isAuthorized: boolean,
isAuthorizationChecked: boolean,
recordAudioPermissionStatus: RecordAudioPermissionStatus,
};
export type Status = 'READY' | 'PENDING_AUTHORIZATION' | 'NOT_AUTHORIZED';
const CameraStatus: { [key: Status]: Status } = {
READY: 'READY',
PENDING_AUTHORIZATION: 'PENDING_AUTHORIZATION',
NOT_AUTHORIZED: 'NOT_AUTHORIZED',
};
export type RecordAudioPermissionStatus = 'AUTHORIZED' | 'NOT_AUTHORIZED' | 'PENDING_AUTHORIZATION';
const RecordAudioPermissionStatusEnum: {
[key: RecordAudioPermissionStatus]: RecordAudioPermissionStatus,
} = {
AUTHORIZED: 'AUTHORIZED',
PENDING_AUTHORIZATION: 'PENDING_AUTHORIZATION',
NOT_AUTHORIZED: 'NOT_AUTHORIZED',
};
const CameraManager: Object = NativeModules.RNCameraManager ||
NativeModules.RNCameraModule || {
stubbed: true,
Type: {
back: 1,
},
AutoFocus: {
on: 1,
},
FlashMode: {
off: 1,
},
WhiteBalance: {},
BarCodeType: {},
FaceDetection: {
fast: 1,
Mode: {},
Landmarks: {
none: 0,
},
Classifications: {
none: 0,
},
},
GoogleVisionBarcodeDetection: {
BarcodeType: 0,
BarcodeMode: 0,
},
};
const EventThrottleMs = 500;
const mapValues = (input, mapper) => {
const result = {};
Object.entries(input).map(([key, value]) => {
result[key] = mapper(value, key);
});
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,
ImageType: CameraManager.ImageType,
VideoCodec: CameraManager.VideoCodec,
BarCodeType: CameraManager.BarCodeType,
GoogleVisionBarcodeDetection: CameraManager.GoogleVisionBarcodeDetection,
FaceDetection: CameraManager.FaceDetection,
CameraStatus,
CaptureTarget: CameraManager.CaptureTarget,
RecordAudioPermissionStatus: RecordAudioPermissionStatusEnum,
VideoStabilization: CameraManager.VideoStabilization,
Orientation: {
auto: 'auto',
landscapeLeft: 'landscapeLeft',
landscapeRight: 'landscapeRight',
portrait: 'portrait',
portraitUpsideDown: 'portraitUpsideDown',
},
};
// 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,
googleVisionBarcodeMode: (CameraManager.GoogleVisionBarcodeDetection || {}).BarcodeMode,
videoStabilizationMode: CameraManager.VideoStabilization || {},
};
static propTypes = {
...ViewPropTypes,
zoom: PropTypes.number,
useNativeZoom: PropTypes.bool,
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,
onRecordingStart: PropTypes.func,
onRecordingEnd: PropTypes.func,
onTap: PropTypes.func,
onDoubleTap: 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,
PropTypes.shape({ temperature: PropTypes.number, tint: PropTypes.number,
redGainOffset: PropTypes.number,
greenGainOffset: PropTypes.number,
blueGainOffset: 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,
playSoundOnRecord: 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,
useNativeZoom: false,
maxZoom: 0,
ratio: '4:3',
focusDepth: 0,
type: CameraManager.Type.back,
cameraId: '',
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: null,
androidRecordAudioPermissionOptions: null,
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,
playSoundOnRecord: false,
pictureSize: 'None',
videoStabilizationMode: 0,
mirrorVideo: false,
};
_cameraRef: ?Object;
_cameraHandle: ?number;
_lastEvents: { [string]: string };
_lastEventsTimes: { [string]: Date };
_isMounted: boolean;
constructor(props: PropsType) {
super(props);
this._lastEvents = {};
this._lastEventsTimes = {};
this._isMounted = true;
this.state = {
isAuthorized: false,
isAuthorizationChecked: false,
recordAudioPermissionStatus: RecordAudioPermissionStatusEnum.PENDING_AUTHORIZATION,
};
}
async takePictureAsync(options?: PictureOptions) {
if (!options) {
options = {};
}
if (!options.quality) {
options.quality = 1;
}
if (options.orientation) {
if (typeof options.orientation !== 'number') {
const { orientation } = options;
options.orientation = CameraManager.Orientation[orientation];
if (__DEV__) {
if (typeof options.orientation !== 'number') {
// eslint-disable-next-line no-console
console.warn(`Orientation '${orientation}' is invalid.`);
}
}
}
}
if (options.pauseAfterCapture === undefined) {
options.pauseAfterCapture = false;
}
if (!this._cameraHandle) {
throw 'Camera handle cannot be null';
}
return await CameraManager.takePicture(options, this._cameraHandle);
}
async getSupportedRatiosAsync() {
if (Platform.OS === 'android') {
return await CameraManager.getSupportedRatios(this._cameraHandle);
} else {
throw new Error('Ratio is not supported on iOS');
}
}
async getCameraIdsAsync() {
if (Platform.OS === 'android') {
return await CameraManager.getCameraIds(this._cameraHandle);
} else {
return await CameraManager.getCameraIds(); // iOS does not need a camera instance
}
}
static async checkIfVideoIsValid(path) {
if (Platform.OS === 'android') {
return await CameraManager.checkIfVideoIsValid(path);
} else {
return true; // iOS: not implemented
}
}
getSupportedPreviewFpsRange = async (): Promise<[]> => {
if (Platform.OS === 'android') {
return await CameraManager.getSupportedPreviewFpsRange(this._cameraHandle);
} else {
throw new Error('getSupportedPreviewFpsRange is not supported on iOS');
}
};
getAvailablePictureSizes = async (): string[] => {
//$FlowFixMe
return await CameraManager.getAvailablePictureSizes(this.props.ratio, this._cameraHandle);
};
async recordAsync(options?: RecordingOptions) {
if (!options || typeof options !== 'object') {
options = {};
} else if (typeof options.quality === 'string') {
options.quality = Camera.Constants.VideoQuality[options.quality];
}
if (options.orientation) {
if (typeof options.orientation !== 'number') {
const { orientation } = options;
options.orientation = CameraManager.Orientation[orientation];
if (__DEV__) {
if (typeof options.orientation !== 'number') {
// eslint-disable-next-line no-console
console.warn(`Orientation '${orientation}' is invalid.`);
}
}
}
}
if (__DEV__) {
if (options.videoBitrate && typeof options.videoBitrate !== 'number') {
// eslint-disable-next-line no-console
console.warn('Video Bitrate should be a positive integer');
}
}
const { recordAudioPermissionStatus } = this.state;
const { captureAudio } = this.props;
if (
!captureAudio ||
recordAudioPermissionStatus !== RecordAudioPermissionStatusEnum.AUTHORIZED
) {
options.mute = true;
}
if (__DEV__) {
if (
(!options.mute || captureAudio) &&
recordAudioPermissionStatus !== RecordAudioPermissionStatusEnum.AUTHORIZED
) {
// eslint-disable-next-line no-console
console.warn('Recording with audio not possible. Permissions are missing.');
}
}
return await CameraManager.record(options, this._cameraHandle);
}
stopRecording() {
CameraManager.stopRecording(this._cameraHandle);
}
pauseRecording() {
CameraManager.pauseRecording(this._cameraHandle);
}
resumeRecording() {
CameraManager.resumeRecording(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);
}
};
_onCameraReady = () => {
if (this.props.onCameraReady) {
this.props.onCameraReady();
}
};
_onAudioInterrupted = () => {
if (this.props.onAudioInterrupted) {
this.props.onAudioInterrupted();
}
};
_onTouch = ({ nativeEvent }: EventCallbackArgumentsType) => {
if (this.props.onTap && !nativeEvent.isDoubleTap) {
this.props.onTap(nativeEvent.touchOrigin);
}
if (this.props.onDoubleTap && nativeEvent.isDoubleTap) {
this.props.onDoubleTap(nativeEvent.touchOrigin);
}
};
_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;
}
if (callback) {
callback(nativeEvent);
this._lastEventsTimes[type] = new Date();
this._lastEvents[type] = JSON.stringify(nativeEvent);
}
};
_onSubjectAreaChanged = e => {
if (this.props.onSubjectAreaChanged) {
this.props.onSubjectAreaChanged(e);
}
};
_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,
);
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._onObjectDetected(this._onCameraReady)}
onAudioInterrupted={this._onAudioInterrupted}
onAudioConnected={this._onAudioConnected}
onGoogleVisionBarcodesDetected={this._onObjectDetected(
this.props.onGoogleVisionBarcodesDetected,
)}
onBarCodeRead={this._onObjectDetected(this.props.onBarCodeRead)}
onTouch={this._onTouch}
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.onTap || props.onDoubleTap) {
newProps.touchDetectorEnabled = true;
}
if (props.onTextRecognized) {
newProps.textRecognizerEnabled = true;
}
if (Platform.OS === 'ios') {
delete newProps.ratio;
}
return newProps;
}
_convertProp(value: *, key: string): * {
if (typeof value === 'string' && Camera.ConversionTables[key]) {
return Camera.ConversionTables[key][value];
}
return value;
}
}
export const Constants = Camera.Constants;
export function hasTorch() {
return CameraManager.hasTorch();
}
const RNCamera = requireNativeComponent('RNCamera', Camera, {
nativeOnly: {
accessibilityComponentType: true,
accessibilityLabel: true,
accessibilityLiveRegion: true,
barCodeScannerEnabled: true,
touchDetectorEnabled: true,
googleVisionBarcodeDetectorEnabled: true,
faceDetectorEnabled: true,
textRecognizerEnabled: true,
importantForAccessibility: true,
onBarCodeRead: true,
onGoogleVisionBarcodesDetected: true,
onCameraReady: true,
onAudioInterrupted: true,
onAudioConnected: true,
onPictureSaved: true,
onFaceDetected: true,
onTouch: true,
onLayout: true,
onMountError: true,
onSubjectAreaChanged: true,
renderToHardwareTextureAndroid: true,
testID: true,
},
});