import Foundation from '@expo/vector-icons/build/Foundation'; import Ionicons from '@expo/vector-icons/build/Ionicons'; import MaterialCommunityIcons from '@expo/vector-icons/build/MaterialCommunityIcons'; import MaterialIcons from '@expo/vector-icons/build/MaterialIcons'; import Octicons from '@expo/vector-icons/build/Octicons'; import { BarCodeScanner } from 'expo-barcode-scanner'; import { BarCodeScanningResult, Camera, PermissionStatus } from 'expo-camera'; import { AutoFocus, CameraType, FlashMode, WhiteBalance } from 'expo-camera/build/Camera.types'; import Constants from 'expo-constants'; import * as FileSystem from 'expo-file-system'; import React from 'react'; import { Alert, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { isIphoneX } from 'react-native-iphone-x-helper'; import { face, landmarks } from '../../components/Face'; import GalleryScreen from './GalleryScreen'; interface Picture { width: number; height: number; uri: string; base64?: string; exif?: any; } type FlashModeString = keyof typeof FlashMode; type AutoFocusString = keyof typeof AutoFocus; type WhiteBalanceString = keyof typeof WhiteBalance; const flashModeOrder: { [key: string]: FlashModeString } = { off: 'on', on: 'auto', auto: 'torch', torch: 'off', }; const flashIcons: { [key: string]: string } = { off: 'flash-off', on: 'flash', auto: 'flash-outline', torch: 'flashlight', }; const wbOrder: { [key: string]: WhiteBalanceString } = { auto: 'sunny', sunny: 'cloudy', cloudy: 'shadow', shadow: 'fluorescent', fluorescent: 'incandescent', incandescent: 'auto', }; const wbIcons: { [key: string]: string } = { auto: 'wb-auto', sunny: 'wb-sunny', cloudy: 'wb-cloudy', shadow: 'beach-access', fluorescent: 'wb-iridescent', incandescent: 'wb-incandescent', }; const photos: Picture[] = []; interface State { flash: FlashModeString; zoom: number; autoFocus: AutoFocusString; type: CameraType; depth: number; whiteBalance: WhiteBalanceString; ratio: string; ratios: any[]; barcodeScanning: boolean; faceDetecting: boolean; faces: any[]; newPhotos: boolean; permissionsGranted: boolean; permission?: PermissionStatus; pictureSize?: any; pictureSizes: any[]; pictureSizeId: number; showGallery: boolean; showMoreOptions: boolean; } // See: https://github.com/expo/expo/pull/10229#discussion_r490961694 // eslint-disable-next-line @typescript-eslint/ban-types export default class CameraScreen extends React.Component<{}, State> { readonly state: State = { flash: 'off', zoom: 0, autoFocus: 'on', type: CameraType.back, depth: 0, whiteBalance: 'auto', ratio: '16:9', ratios: [], barcodeScanning: false, faceDetecting: false, faces: [], newPhotos: false, permissionsGranted: false, pictureSizes: [], pictureSizeId: 0, showGallery: false, showMoreOptions: false, }; camera?: Camera; componentDidMount() { if (Platform.OS !== 'web') { this.ensureDirectoryExistsAsync(); } Camera.requestPermissionsAsync().then(({ status }) => { this.setState({ permission: status, permissionsGranted: status === 'granted' }); }); } async ensureDirectoryExistsAsync() { try { await FileSystem.makeDirectoryAsync(FileSystem.documentDirectory + 'photos'); } catch (error) { // tslint:disable-next-line no-console console.log(error, 'Directory exists'); } } getRatios = async () => this.camera!.getSupportedRatiosAsync(); toggleView = () => this.setState(state => ({ showGallery: !state.showGallery, newPhotos: false })); toggleMoreOptions = () => this.setState(state => ({ showMoreOptions: !state.showMoreOptions })); toggleFacing = () => this.setState(state => ({ type: state.type === CameraType.back ? CameraType.front : CameraType.back, })); toggleFlash = () => this.setState(state => ({ flash: flashModeOrder[state.flash] })); setRatio = (ratio: string) => this.setState({ ratio }); toggleWB = () => this.setState(state => ({ whiteBalance: wbOrder[state.whiteBalance] })); toggleFocus = () => this.setState(state => ({ autoFocus: state.autoFocus === 'on' ? 'off' : 'on' })); zoomOut = () => this.setState(state => ({ zoom: state.zoom - 0.1 < 0 ? 0 : state.zoom - 0.1 })); zoomIn = () => this.setState(state => ({ zoom: state.zoom + 0.1 > 1 ? 1 : state.zoom + 0.1 })); setFocusDepth = (depth: number) => this.setState({ depth }); toggleBarcodeScanning = () => this.setState(state => ({ barcodeScanning: !state.barcodeScanning })); toggleFaceDetection = () => this.setState(state => ({ faceDetecting: !state.faceDetecting })); takePicture = () => { if (this.camera) { this.camera.takePictureAsync({ onPictureSaved: this.onPictureSaved }); } }; // tslint:disable-next-line no-console handleMountError = ({ message }: { message: string }) => console.error(message); onPictureSaved = async (photo: Picture) => { if (Platform.OS === 'web') { photos.push(photo); } else { await FileSystem.moveAsync({ from: photo.uri, to: `${FileSystem.documentDirectory}photos/${Date.now()}.jpg`, }); } this.setState({ newPhotos: true }); }; onBarCodeScanned = (code: BarCodeScanningResult) => { console.log('Found: ', code); this.setState( state => ({ barcodeScanning: !state.barcodeScanning }), () => Alert.alert(`Barcode found: ${code.data}`) ); }; onFacesDetected = ({ faces }: { faces: any }) => this.setState({ faces }); collectPictureSizes = async () => { if (this.camera) { const { ratio } = this.state; const pictureSizes = await this.camera.getAvailablePictureSizesAsync(ratio); let pictureSizeId = 0; if (Platform.OS === 'ios') { pictureSizeId = pictureSizes.indexOf('High'); } else { // returned array is sorted in ascending order - default size is the largest one pictureSizeId = pictureSizes.length - 1; } this.setState({ pictureSizes, pictureSizeId, pictureSize: pictureSizes[pictureSizeId] }); } }; previousPictureSize = () => this.changePictureSize(1); nextPictureSize = () => this.changePictureSize(-1); changePictureSize = (direction: number) => { this.setState(state => { let newId = state.pictureSizeId + direction; const length = state.pictureSizes.length; if (newId >= length) { newId = 0; } else if (newId < 0) { newId = length - 1; } return { pictureSize: state.pictureSizes[newId], pictureSizeId: newId, }; }); }; renderGallery() { const localPhotos = photos.map(photo => photo.uri); return ; } renderFaces = () => ( {this.state.faces.map(face)} ); renderLandmarks = () => ( {this.state.faces.map(landmarks)} ); renderNoPermissions = () => ( {this.state.permission && ( Permission {this.state.permission.toLowerCase()}! You'll need to enable the camera permission to continue. )} ); renderTopBar = () => ( AF ); renderBottomBar = () => ( {this.state.newPhotos && } ); renderMoreOptions = () => ( Picture quality {this.state.pictureSize} ); renderCamera = () => ( (this.camera = ref!)} style={styles.camera} onCameraReady={this.collectPictureSizes} type={this.state.type} flashMode={this.state.flash} autoFocus={this.state.autoFocus} zoom={this.state.zoom} whiteBalance={this.state.whiteBalance} ratio={this.state.ratio} pictureSize={this.state.pictureSize} onMountError={this.handleMountError} onFacesDetected={this.state.faceDetecting ? this.onFacesDetected : undefined} faceDetectorSettings={{ tracking: true, }} barCodeScannerSettings={{ barCodeTypes: [ BarCodeScanner.Constants.BarCodeType.qr, BarCodeScanner.Constants.BarCodeType.pdf417, ], }} onBarCodeScanned={this.state.barcodeScanning ? this.onBarCodeScanned : undefined}> {this.renderTopBar()} {this.renderBottomBar()} {this.state.faceDetecting && this.renderFaces()} {this.state.faceDetecting && this.renderLandmarks()} {this.state.showMoreOptions && this.renderMoreOptions()} ); render() { const cameraScreenContent = this.state.permissionsGranted ? this.renderCamera() : this.renderNoPermissions(); const content = this.state.showGallery ? this.renderGallery() : cameraScreenContent; return {content}; } } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#000', }, camera: { flex: 1, justifyContent: 'space-between', }, topBar: { flex: 0.2, backgroundColor: 'transparent', flexDirection: 'row', justifyContent: 'space-around', paddingTop: Constants.statusBarHeight / 2, }, bottomBar: { paddingBottom: isIphoneX() ? 25 : 5, backgroundColor: 'transparent', justifyContent: 'space-between', flexDirection: 'row', }, noPermissions: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 10, backgroundColor: '#f8fdff', }, gallery: { flex: 1, flexDirection: 'row', flexWrap: 'wrap', }, toggleButton: { flex: 0.25, height: 40, marginHorizontal: 2, marginBottom: 10, marginTop: 20, padding: 5, alignItems: 'center', justifyContent: 'center', }, autoFocusLabel: { fontSize: 20, fontWeight: 'bold', }, bottomButton: { flex: 0.3, height: 58, justifyContent: 'center', alignItems: 'center', }, newPhotosDot: { position: 'absolute', top: 0, right: -5, width: 8, height: 8, borderRadius: 4, backgroundColor: '#4630EB', }, options: { position: 'absolute', bottom: 80, left: 30, width: 200, height: 160, backgroundColor: '#000000BA', borderRadius: 4, padding: 10, }, detectors: { flex: 0.5, justifyContent: 'space-around', alignItems: 'center', flexDirection: 'row', }, pictureQualityLabel: { fontSize: 10, marginVertical: 3, color: 'white', }, pictureSizeContainer: { flex: 0.5, alignItems: 'center', paddingTop: 10, }, pictureSizeChooser: { alignItems: 'center', justifyContent: 'space-between', flexDirection: 'row', }, pictureSizeLabel: { flex: 1, alignItems: 'center', justifyContent: 'center', }, facesContainer: { position: 'absolute', bottom: 0, right: 0, left: 0, top: 0, }, row: { flexDirection: 'row', }, });