import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { DeviceEventEmitter, // android NativeAppEventEmitter, // ios NativeModules, Platform, StyleSheet, findNodeHandle, requireNativeComponent, ViewPropTypes, PermissionsAndroid, ActivityIndicator, View, Text, UIManager, } from 'react-native'; import { requestPermissions } from './handlePermissions'; const CameraManager = NativeModules.CameraManager || NativeModules.CameraModule; function convertNativeProps(props) { const newProps = { ...props }; if (typeof props.aspect === 'string') { newProps.aspect = Camera.constants.Aspect[props.aspect]; } if (typeof props.flashMode === 'string') { newProps.flashMode = Camera.constants.FlashMode[props.flashMode]; } if (typeof props.zoom === 'string' || typeof props.zoom === 'number') { if (props.zoom >= 0 && props.zoom <= 100) { newProps.zoom = parseInt(props.zoom); } } if (typeof props.orientation === 'string') { newProps.orientation = Camera.constants.Orientation[props.orientation]; } if (typeof props.torchMode === 'string') { newProps.torchMode = Camera.constants.TorchMode[props.torchMode]; } if (typeof props.type === 'string') { newProps.type = Camera.constants.Type[props.type]; } if (typeof props.captureQuality === 'string') { newProps.captureQuality = Camera.constants.CaptureQuality[props.captureQuality]; } if (typeof props.captureMode === 'string') { newProps.captureMode = Camera.constants.CaptureMode[props.captureMode]; } if (typeof props.captureTarget === 'string') { newProps.captureTarget = Camera.constants.CaptureTarget[props.captureTarget]; } // do not register barCodeTypes if no barcode listener if (typeof props.onBarCodeRead !== 'function') { newProps.barCodeTypes = []; } newProps.barcodeScannerEnabled = typeof props.onBarCodeRead === 'function'; return newProps; } export default class Camera extends Component { static constants = { Aspect: CameraManager.Aspect, BarCodeType: CameraManager.BarCodeType, Type: CameraManager.Type, CaptureMode: CameraManager.CaptureMode, CaptureTarget: CameraManager.CaptureTarget, CaptureQuality: CameraManager.CaptureQuality, Orientation: CameraManager.Orientation, FlashMode: CameraManager.FlashMode, Zoom: CameraManager.Zoom, TorchMode: CameraManager.TorchMode, }; static propTypes = { ...ViewPropTypes, aspect: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), captureAudio: PropTypes.bool, captureMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), captureQuality: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), captureTarget: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), defaultOnFocusComponent: PropTypes.bool, flashMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), zoom: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), keepAwake: PropTypes.bool, onBarCodeRead: PropTypes.func, barcodeScannerEnabled: PropTypes.bool, cropToPreview: PropTypes.bool, clearWindowBackground: PropTypes.bool, onFocusChanged: PropTypes.func, onZoomChanged: PropTypes.func, mirrorImage: PropTypes.bool, fixOrientation: PropTypes.bool, barCodeTypes: PropTypes.array, orientation: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), playSoundOnCapture: PropTypes.bool, torchMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), type: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), permissionDialogTitle: PropTypes.string, permissionDialogMessage: PropTypes.string, notAuthorizedView: PropTypes.element, pendingAuthorizationView: PropTypes.element, }; static defaultProps = { aspect: CameraManager.Aspect.fill, type: CameraManager.Type.back, orientation: CameraManager.Orientation.auto, fixOrientation: false, captureAudio: false, captureMode: CameraManager.CaptureMode.still, captureTarget: CameraManager.CaptureTarget.cameraRoll, captureQuality: CameraManager.CaptureQuality.high, defaultOnFocusComponent: true, flashMode: CameraManager.FlashMode.off, zoom: 0, playSoundOnCapture: true, torchMode: CameraManager.TorchMode.off, mirrorImage: false, cropToPreview: false, clearWindowBackground: false, barCodeTypes: Object.values(CameraManager.BarCodeType), permissionDialogTitle: '', permissionDialogMessage: '', notAuthorizedView: ( Camera not authorized ), pendingAuthorizationView: ( ), }; static checkDeviceAuthorizationStatus = CameraManager.checkDeviceAuthorizationStatus; static checkVideoAuthorizationStatus = CameraManager.checkVideoAuthorizationStatus; static checkAudioAuthorizationStatus = CameraManager.checkAudioAuthorizationStatus; setNativeProps(props) { // eslint-disable-next-line this._cameraRef.setNativeProps(props); } constructor() { super(); this.state = { isAuthorized: false, isAuthorizationChecked: false, isRecording: false, }; this._cameraRef = null; this._cameraHandle = null; } async componentWillMount() { this._addOnBarCodeReadListener(); this._addOnFocusChanged(); this._addOnZoomChanged(); let { captureMode } = convertNativeProps({ captureMode: this.props.captureMode }); let hasVideoAndAudio = this.props.captureAudio && captureMode === Camera.constants.CaptureMode.video; const isAuthorized = await requestPermissions(hasVideoAndAudio, Camera, this.props.permissionDialogTitle, this.props.permissionDialogMessage); this.setState({ isAuthorized, isAuthorizationChecked: true }); } componentWillUnmount() { this._removeOnBarCodeReadListener(); this._removeOnFocusChanged(); this._removeOnZoomChanged(); if (this.state.isRecording) { this.stopCapture(); } } componentWillReceiveProps(newProps) { const { onBarCodeRead, onFocusChanged, onZoomChanged } = this.props if (onBarCodeRead !== newProps.onBarCodeRead) { this._addOnBarCodeReadListener(newProps); } if (onFocusChanged !== !newProps.onFocusChanged) { this._addOnFocusChanged(newProps) } if (onZoomChanged !== !newProps.onZoomChanged) { this._addOnZoomChanged(newProps) } } _addOnBarCodeReadListener(props) { const { onBarCodeRead } = props || this.props; this._removeOnBarCodeReadListener(); if (onBarCodeRead) { this.cameraBarCodeReadListener = Platform.select({ ios: NativeAppEventEmitter.addListener('CameraBarCodeRead', this._onBarCodeRead), android: DeviceEventEmitter.addListener('CameraBarCodeReadAndroid', this._onBarCodeRead), }); } } _addOnFocusChanged(props) { if (Platform.OS === 'ios') { const { onFocusChanged } = props || this.props; this.focusListener = NativeAppEventEmitter.addListener('focusChanged', onFocusChanged) } } _addOnZoomChanged(props) { if (Platform.OS === 'ios') { const { onZoomChanged } = props || this.props; this.zoomListener = NativeAppEventEmitter.addListener('zoomChanged', onZoomChanged) } } _removeOnBarCodeReadListener() { const listener = this.cameraBarCodeReadListener; if (listener) { listener.remove(); } } _removeOnFocusChanged() { const listener = this.focusListener if (listener) { listener.remove() } } _removeOnZoomChanged() { const listener = this.zoomListener if (listener) { listener.remove() } } _setReference = ref => { if (ref) { this._cameraRef = ref; this._cameraHandle = findNodeHandle(ref); } else { this._cameraRef = null; this._cameraHandle = null; } }; render() { // TODO - style is not used, figure it out why // eslint-disable-next-line const style = [styles.base, this.props.style]; const nativeProps = convertNativeProps(this.props); if (this.state.isAuthorized) { return ; } else if (!this.state.isAuthorizationChecked) { return this.props.pendingAuthorizationView; } else { return this.props.notAuthorizedView; } } _onBarCodeRead = data => { if (this.props.onBarCodeRead) { this.props.onBarCodeRead(data); } }; capture(options) { const props = convertNativeProps(this.props); options = { audio: props.captureAudio, barCodeTypes: props.barCodeTypes, mode: props.captureMode, playSoundOnCapture: props.playSoundOnCapture, target: props.captureTarget, quality: props.captureQuality, type: props.type, title: '', description: '', mirrorImage: props.mirrorImage, fixOrientation: props.fixOrientation, cropToPreview: props.cropToPreview, ...options, }; if (options.mode === Camera.constants.CaptureMode.video) { options.totalSeconds = options.totalSeconds > -1 ? options.totalSeconds : -1; options.preferredTimeScale = options.preferredTimeScale || 30; options.cropToPreview = false; this.setState({ isRecording: true }); } return CameraManager.capture(options); } startPreview() { if (Platform.OS === 'android') { UIManager.dispatchViewManagerCommand( this._cameraHandle, UIManager.RCTCamera.Commands.startPreview, [], ); } else { CameraManager.startPreview(); } } stopPreview() { if (Platform.OS === 'android') { UIManager.dispatchViewManagerCommand( this._cameraHandle, UIManager.RCTCamera.Commands.stopPreview, [], ); } else { CameraManager.stopPreview(); } } stopCapture() { if (this.state.isRecording) { this.setState({ isRecording: false }); return CameraManager.stopCapture(); } return Promise.resolve('Not Recording.'); } getFOV() { return CameraManager.getFOV(); } hasFlash() { if (Platform.OS === 'android') { const props = convertNativeProps(this.props); return CameraManager.hasFlash({ type: props.type, }); } return CameraManager.hasFlash(); } setZoom(zoom) { if (Platform.OS === 'android') { const props = convertNativeProps(this.props); return CameraManager.setZoom( { type: props.type, }, zoom, ); } return CameraManager.setZoom(zoom); } } export const constants = Camera.constants; const RCTCamera = requireNativeComponent('RCTCamera', Camera, { nativeOnly: { testID: true, renderToHardwareTextureAndroid: true, accessibilityLabel: true, importantForAccessibility: true, accessibilityLiveRegion: true, accessibilityComponentType: true, onLayout: true, }, }); const styles = StyleSheet.create({ base: {}, });