BlueWallet/components/CameraScreen.tsx

267 lines
8.8 KiB
TypeScript
Raw Normal View History

2025-01-06 20:05:55 -04:00
import React, { useState, useRef } from 'react';
2025-02-04 21:54:24 -04:00
import { Animated, Platform, StyleSheet, TouchableOpacity, View } from 'react-native';
2025-01-06 20:05:55 -04:00
import { Camera, CameraApi, CameraType, Orientation } from 'react-native-camera-kit';
import loc from '../loc';
import { Icon } from '@rneui/base';
import { OnOrientationChangeData, OnReadCodeData } from 'react-native-camera-kit/dist/CameraProps';
import { triggerSelectionHapticFeedback } from '../blue_modules/hapticFeedback';
2025-02-27 13:18:41 -04:00
import { isDesktop } from '../blue_modules/environment';
2025-01-08 00:12:41 -04:00
2025-01-06 20:05:55 -04:00
interface CameraScreenProps {
onCancelButtonPress: () => void;
showImagePickerButton?: boolean;
showFilePickerButton?: boolean;
onImagePickerButtonPress?: () => void;
onFilePickerButtonPress?: () => void;
onReadCode?: (event: OnReadCodeData) => void;
2025-01-06 20:05:55 -04:00
}
const CameraScreen: React.FC<CameraScreenProps> = ({
onCancelButtonPress,
showImagePickerButton,
showFilePickerButton,
onImagePickerButtonPress,
onFilePickerButtonPress,
onReadCode,
}) => {
const cameraRef = useRef<CameraApi>(null);
const [torchMode, setTorchMode] = useState(false);
const [cameraType, setCameraType] = useState(CameraType.Back);
const [zoom, setZoom] = useState<number | undefined>();
const [orientationAnim] = useState(new Animated.Value(3));
const onSwitchCameraPressed = () => {
const direction = cameraType === CameraType.Back ? CameraType.Front : CameraType.Back;
setCameraType(direction);
setZoom(1); // When changing camera type, reset to default zoom for that camera
triggerSelectionHapticFeedback();
2025-01-06 20:05:55 -04:00
};
const onSetTorch = () => {
setTorchMode(!torchMode);
triggerSelectionHapticFeedback();
2025-01-06 20:05:55 -04:00
};
// Counter-rotate the icons to indicate the actual orientation of the captured photo.
// For this example, it'll behave incorrectly since UI orientation is allowed (and already-counter rotates the entire screen)
// For real phone apps, lock your UI orientation using a library like 'react-native-orientation-locker'
const rotateUi = true;
const uiRotation = orientationAnim.interpolate({
2025-02-04 21:54:24 -04:00
inputRange: [1, 2, 3, 4],
outputRange: ['180deg', '90deg', '0deg', '-90deg'],
2025-01-06 20:05:55 -04:00
});
2025-01-08 00:12:41 -04:00
const uiRotationStyle = rotateUi ? { transform: [{ rotate: uiRotation }] } : {};
2025-01-06 20:05:55 -04:00
function rotateUiTo(rotationValue: number) {
Animated.timing(orientationAnim, {
toValue: rotationValue,
useNativeDriver: true,
duration: 200,
isInteraction: false,
}).start();
}
const handleZoom = (e: { nativeEvent: { zoom: number } }) => {
console.debug('zoom', e.nativeEvent.zoom);
setZoom(e.nativeEvent.zoom);
};
const handleOrientationChange = (e: OnOrientationChangeData) => {
switch (e.nativeEvent.orientation) {
case Orientation.PORTRAIT_UPSIDE_DOWN:
console.debug('orientationChange', 'PORTRAIT_UPSIDE_DOWN');
rotateUiTo(1);
break;
case Orientation.LANDSCAPE_LEFT:
console.debug('orientationChange', 'LANDSCAPE_LEFT');
rotateUiTo(2);
break;
case Orientation.PORTRAIT:
console.debug('orientationChange', 'PORTRAIT');
rotateUiTo(3);
break;
case Orientation.LANDSCAPE_RIGHT:
console.debug('orientationChange', 'LANDSCAPE_RIGHT');
rotateUiTo(4);
break;
default:
console.debug('orientationChange', e.nativeEvent);
break;
}
};
const handleReadCode = (event: OnReadCodeData) => {
onReadCode?.(event);
};
2025-01-06 20:05:55 -04:00
return (
<View style={styles.screen}>
2025-02-27 14:03:42 -04:00
{/* Render top buttons only if not desktop as they would not be relevant */}
{!isDesktop && (
<View style={styles.topButtons}>
2025-02-27 13:18:41 -04:00
<TouchableOpacity style={[styles.topButton, uiRotationStyle, torchMode ? styles.activeTorch : {}]} onPress={onSetTorch}>
<Animated.View style={styles.topButtonImg}>
{Platform.OS === 'ios' ? (
<Icon name={torchMode ? 'flashlight-on' : 'flashlight-off'} type="font-awesome-6" color={torchMode ? '#000' : '#fff'} />
) : (
<Icon name={torchMode ? 'flash-on' : 'flash-off'} type="ionicons" color={torchMode ? '#000' : '#fff'} />
)}
</Animated.View>
</TouchableOpacity>
2025-02-27 14:03:42 -04:00
<View style={styles.rightButtonsContainer}>
{showImagePickerButton && (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.pick_image}
style={[styles.topButton, styles.spacing, uiRotationStyle]}
onPress={onImagePickerButtonPress}
>
<Animated.View style={styles.topButtonImg}>
<Icon name="image" type="font-awesome" color="#ffffff" />
</Animated.View>
</TouchableOpacity>
)}
{showFilePickerButton && (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.pick_file}
style={[styles.topButton, styles.spacing, uiRotationStyle]}
onPress={onFilePickerButtonPress}
>
<Animated.View style={styles.topButtonImg}>
<Icon name="file-import" type="font-awesome-5" color="#ffffff" />
</Animated.View>
</TouchableOpacity>
)}
</View>
2025-01-06 20:05:55 -04:00
</View>
2025-02-27 14:03:42 -04:00
)}
2025-01-06 20:05:55 -04:00
<View style={styles.cameraContainer}>
<Camera
ref={cameraRef}
style={styles.cameraPreview}
cameraType={cameraType}
scanBarcode
resizeMode="cover"
onReadCode={handleReadCode}
2025-01-06 20:05:55 -04:00
torchMode={torchMode ? 'on' : 'off'}
2025-02-27 14:03:42 -04:00
resetFocusWhenMotionDetected
zoom={zoom}
onZoom={handleZoom}
maxZoom={10}
onOrientationChange={handleOrientationChange}
2025-01-06 20:05:55 -04:00
/>
</View>
<View style={styles.bottomButtons}>
2025-01-06 20:05:55 -04:00
<TouchableOpacity onPress={onCancelButtonPress}>
<Animated.Text style={[styles.backTextStyle, uiRotationStyle]}>{loc._.cancel}</Animated.Text>
</TouchableOpacity>
2025-02-27 14:03:42 -04:00
{isDesktop ? (
<View style={styles.rightButtonsContainer}>
{showImagePickerButton && (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.pick_image}
style={[styles.bottomButton, styles.spacing, uiRotationStyle]}
onPress={onImagePickerButtonPress}
>
<Animated.View style={styles.topButtonImg}>
<Icon name="image" type="font-awesome" color="#ffffff" />
</Animated.View>
</TouchableOpacity>
)}
{showFilePickerButton && (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.pick_file}
style={[styles.bottomButton, styles.spacing, uiRotationStyle]}
onPress={onFilePickerButtonPress}
>
<Animated.View style={styles.topButtonImg}>
<Icon name="file-import" type="font-awesome-5" color="#ffffff" />
</Animated.View>
</TouchableOpacity>
)}
</View>
) : (
2025-02-27 13:18:41 -04:00
<TouchableOpacity style={[styles.bottomButton, uiRotationStyle]} onPress={onSwitchCameraPressed}>
<Animated.View style={[styles.topButtonImg, uiRotationStyle]}>
{Platform.OS === 'ios' ? (
<Icon name="cameraswitch" type="font-awesome-6" color="#ffffff" />
) : (
<Icon name={cameraType === CameraType.Back ? 'camera-rear' : 'camera-front'} type="ionicons" color="#ffffff" />
)}
</Animated.View>
</TouchableOpacity>
)}
</View>
2025-01-06 20:05:55 -04:00
</View>
);
};
export default CameraScreen;
const styles = StyleSheet.create({
2025-02-04 21:54:24 -04:00
activeTorch: {
backgroundColor: '#fff',
},
2025-01-06 20:05:55 -04:00
screen: {
height: '100%',
backgroundColor: '#000000',
},
topButtons: {
padding: 10,
2025-01-06 20:05:55 -04:00
zIndex: 10,
flexDirection: 'row',
justifyContent: 'space-between',
},
topButton: {
backgroundColor: '#222',
width: 44,
height: 44,
borderRadius: 22,
justifyContent: 'center',
alignItems: 'center',
},
topButtonImg: {
margin: 10,
width: 24,
height: 24,
},
cameraContainer: {
justifyContent: 'center',
flex: 1,
},
cameraPreview: {
width: '100%',
height: '100%',
},
bottomButtons: {
padding: 10,
2025-01-06 20:05:55 -04:00
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
backTextStyle: {
padding: 10,
color: 'white',
fontSize: 20,
},
rightButtonsContainer: {
flexDirection: 'row',
alignItems: 'center',
},
bottomButton: {
backgroundColor: '#222',
width: 44,
height: 44,
borderRadius: 22,
justifyContent: 'center',
alignItems: 'center',
marginLeft: 10,
},
spacing: {
marginLeft: 20,
},
});