269 lines
7.7 KiB
TypeScript
269 lines
7.7 KiB
TypeScript
import React, { useState, useRef } from 'react';
|
|
import { StyleSheet, Text, View, TouchableOpacity, Image, SafeAreaView } from 'react-native';
|
|
import Camera from '../../src/Camera';
|
|
import { CameraApi, CameraType, CaptureData } from '../../src/types';
|
|
import { Orientation } from '../../src';
|
|
|
|
const flashImages = {
|
|
on: require('../images/flashOn.png'),
|
|
off: require('../images/flashOff.png'),
|
|
auto: require('../images/flashAuto.png'),
|
|
};
|
|
|
|
const flashArray = [
|
|
{
|
|
mode: 'auto',
|
|
image: flashImages.auto,
|
|
},
|
|
{
|
|
mode: 'on',
|
|
image: flashImages.on,
|
|
},
|
|
{
|
|
mode: 'off',
|
|
image: flashImages.off,
|
|
},
|
|
] as const;
|
|
|
|
const CameraExample = ({ onBack }: { onBack: () => void }) => {
|
|
const cameraRef = useRef<CameraApi>(null);
|
|
const [currentFlashArrayPosition, setCurrentFlashArrayPosition] = useState(0);
|
|
const [captureImages, setCaptureImages] = useState<CaptureData[]>([]);
|
|
const [flashData, setFlashData] = useState(flashArray[currentFlashArrayPosition]);
|
|
const [torchMode, setTorchMode] = useState(false);
|
|
const [captured, setCaptured] = useState(false);
|
|
const [cameraType, setCameraType] = useState(CameraType.Back);
|
|
const [showImageUri, setShowImageUri] = useState<string>('');
|
|
const [zoom, setZoom] = useState(0);
|
|
|
|
// iOS will error out if capturing too fast,
|
|
// so block capturing until the current capture is done
|
|
// This also minimizes issues of delayed capturing
|
|
const isCapturing = useRef(false);
|
|
|
|
const numberOfImagesTaken = () => {
|
|
const numberTook = captureImages.length;
|
|
if (numberTook >= 2) {
|
|
return numberTook;
|
|
} else if (captured) {
|
|
return '1';
|
|
} else {
|
|
return '';
|
|
}
|
|
};
|
|
|
|
const onSwitchCameraPressed = () => {
|
|
const direction = cameraType === CameraType.Back ? CameraType.Front : CameraType.Back;
|
|
setCameraType(direction);
|
|
setZoom(0); // When changing camera type, reset to default zoom for that camera
|
|
};
|
|
|
|
const onSetFlash = () => {
|
|
const newPosition = (currentFlashArrayPosition + 1) % 3;
|
|
setCurrentFlashArrayPosition(newPosition);
|
|
setFlashData(flashArray[newPosition]);
|
|
};
|
|
|
|
const onSetTorch = () => {
|
|
setTorchMode(!torchMode);
|
|
};
|
|
|
|
const onCaptureImagePressed = async () => {
|
|
if (showImageUri) {
|
|
setShowImageUri('');
|
|
return;
|
|
}
|
|
if (!cameraRef.current || isCapturing.current) return;
|
|
let image: CaptureData | undefined;
|
|
try {
|
|
isCapturing.current = true;
|
|
image = await cameraRef.current.capture();
|
|
} catch (e) {
|
|
console.log('error', e);
|
|
} finally {
|
|
isCapturing.current = false;
|
|
}
|
|
if (!image) return;
|
|
|
|
setCaptured(true);
|
|
setCaptureImages([...captureImages, image]);
|
|
console.log('image', image);
|
|
};
|
|
|
|
return (
|
|
<View style={styles.screen}>
|
|
<SafeAreaView style={styles.topButtons}>
|
|
{flashData.image && (
|
|
<TouchableOpacity style={styles.topButton} onPress={onSetFlash}>
|
|
<Image source={flashData.image} resizeMode="contain" />
|
|
</TouchableOpacity>
|
|
)}
|
|
|
|
<TouchableOpacity style={styles.topButton} onPress={onSwitchCameraPressed}>
|
|
<Image source={require('../images/cameraFlipIcon.png')} resizeMode="contain" />
|
|
</TouchableOpacity>
|
|
|
|
<TouchableOpacity style={styles.zoom} onPress={() => setZoom(0)}>
|
|
<Text style={styles.zoomFactor}>{Number(zoom).toFixed(1)}x</Text>
|
|
</TouchableOpacity>
|
|
|
|
<TouchableOpacity style={styles.topButton} onPress={onSetTorch}>
|
|
<Image
|
|
source={torchMode ? require('../images/torchOn.png') : require('../images/torchOff.png')}
|
|
resizeMode="contain"
|
|
/>
|
|
</TouchableOpacity>
|
|
</SafeAreaView>
|
|
|
|
<View style={styles.cameraContainer}>
|
|
{showImageUri ? (
|
|
<Image source={{ uri: showImageUri }} style={styles.cameraPreview} resizeMode="contain" />
|
|
) : (
|
|
<Camera
|
|
ref={cameraRef}
|
|
style={styles.cameraPreview}
|
|
cameraType={cameraType}
|
|
flashMode={flashData?.mode}
|
|
zoomMode="on"
|
|
focusMode="on"
|
|
zoom={zoom}
|
|
maxZoom={15}
|
|
torchMode={torchMode ? 'on' : 'off'}
|
|
onZoom={(e) => {
|
|
console.log('zoom', e.nativeEvent.zoom);
|
|
setZoom(e.nativeEvent.zoom);
|
|
}}
|
|
onOrientationChange={(e) => {
|
|
// We recommend locking the camera UI to portrait (using a different library)
|
|
// and rotating the UI elements counter to the orientation
|
|
// However, we include onOrientationChange so you can match your UI to what the camera does
|
|
switch (e.nativeEvent.orientation) {
|
|
case Orientation.LANDSCAPE_LEFT:
|
|
console.log('orientationChange', 'LANDSCAPE_LEFT');
|
|
break;
|
|
case Orientation.LANDSCAPE_RIGHT:
|
|
console.log('orientationChange', 'LANDSCAPE_RIGHT');
|
|
break;
|
|
case Orientation.PORTRAIT:
|
|
console.log('orientationChange', 'PORTRAIT');
|
|
break;
|
|
case Orientation.PORTRAIT_UPSIDE_DOWN:
|
|
console.log('orientationChange', 'PORTRAIT_UPSIDE_DOWN');
|
|
break;
|
|
default:
|
|
console.log('orientationChange', e.nativeEvent);
|
|
break;
|
|
}
|
|
}}
|
|
/>
|
|
)}
|
|
</View>
|
|
|
|
<SafeAreaView style={styles.bottomButtons}>
|
|
<View style={styles.backBtnContainer}>
|
|
<TouchableOpacity onPress={onBack}>
|
|
<Text style={styles.backTextStyle}>Back</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
<View style={styles.captureButtonContainer}>
|
|
<TouchableOpacity onPress={onCaptureImagePressed}>
|
|
<Image source={require('../images/cameraButton.png')} />
|
|
<View style={styles.textNumberContainer}>
|
|
<Text>{numberOfImagesTaken()}</Text>
|
|
</View>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
<View style={styles.thumbnailContainer}>
|
|
{captureImages.length > 0 && (
|
|
<TouchableOpacity
|
|
onPress={() => {
|
|
if (showImageUri) {
|
|
setShowImageUri('');
|
|
} else {
|
|
setShowImageUri(captureImages[captureImages.length - 1].uri);
|
|
}
|
|
}}
|
|
>
|
|
<Image source={{ uri: captureImages[captureImages.length - 1].uri }} style={styles.thumbnail} />
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
</SafeAreaView>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
export default CameraExample;
|
|
|
|
const styles = StyleSheet.create({
|
|
screen: {
|
|
height: '100%',
|
|
backgroundColor: 'black',
|
|
},
|
|
topButtons: {
|
|
margin: 10,
|
|
zIndex: 10,
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
},
|
|
topButton: {
|
|
padding: 10,
|
|
},
|
|
cameraContainer: {
|
|
justifyContent: 'center',
|
|
flex: 1,
|
|
},
|
|
cameraPreview: {
|
|
aspectRatio: 3 / 4,
|
|
width: '100%',
|
|
},
|
|
bottomButtons: {
|
|
margin: 10,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
},
|
|
backBtnContainer: {
|
|
flex: 1,
|
|
alignItems: 'flex-start',
|
|
},
|
|
backTextStyle: {
|
|
padding: 10,
|
|
color: 'white',
|
|
fontSize: 20,
|
|
},
|
|
captureButtonContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
textNumberContainer: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
bottom: 0,
|
|
right: 0,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
zoom: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
zoomFactor: {
|
|
color: '#ffffff',
|
|
},
|
|
thumbnailContainer: {
|
|
flex: 1,
|
|
alignItems: 'flex-end',
|
|
justifyContent: 'center',
|
|
},
|
|
thumbnail: {
|
|
width: 48,
|
|
height: 48,
|
|
borderRadius: 4,
|
|
marginEnd: 10,
|
|
},
|
|
});
|