/** * CameraView Component * Advanced camera with real-time evidence overlay */ import React, { useState, useEffect, useRef } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, Animated, Dimensions, } from 'react-native'; import { Camera, useCameraDevices } from 'react-native-vision-camera'; import Icon from 'react-native-vector-icons/MaterialIcons'; const { width, height } = Dimensions.get('window'); export default function CameraView({ onCapture, mission, evidence }) { const [hasPermission, setHasPermission] = useState(false); const [isCapturing, setIsCapturing] = useState(false); const [flashMode, setFlashMode] = useState('auto'); const [zoom, setZoom] = useState(0); const camera = useRef(null); const devices = useCameraDevices(); const device = devices.back; const pulseAnim = useRef(new Animated.Value(1)).current; // Pulse animation for capture button useEffect(() => { const pulse = Animated.loop( Animated.sequence([ Animated.timing(pulseAnim, { toValue: 1.1, duration: 1000, useNativeDriver: true, }), Animated.timing(pulseAnim, { toValue: 1, duration: 1000, useNativeDriver: true, }), ]) ); pulse.start(); return () => pulse.stop(); }, []); // Check permissions useEffect(() => { checkPermission(); }, []); const checkPermission = async () => { const status = await Camera.requestCameraPermission(); setHasPermission(status === 'authorized'); }; // Capture photo const capturePhoto = async () => { if (!camera.current || isCapturing) return; setIsCapturing(true); try { const photo = await camera.current.takePhoto({ qualityPrioritization: 'quality', flash: flashMode, enableShutterSound: true, }); onCapture(photo); } catch (error) { console.error('Capture failed:', error); } finally { setIsCapturing(false); } }; // Toggle flash const toggleFlash = () => { const modes = ['off', 'auto', 'on']; const currentIndex = modes.indexOf(flashMode); setFlashMode(modes[(currentIndex + 1) % modes.length]); }; // Get flash icon const getFlashIcon = () => { switch (flashMode) { case 'on': return 'flash-on'; case 'off': return 'flash-off'; default: return 'flash-auto'; } }; if (!device) { return ( Loading camera... ); } return ( {/* Camera */} {/* Evidence Overlay */} {evidence && ( {evidence.visual_objects || 0} objects detected )} {/* Mission Overlay */} {mission && ( {mission.title} ${mission.reward_usd} )} {/* Top Controls */} setZoom(zoom === 0 ? 0.5 : 0)} style={styles.controlButton} > 0 ? 'zoom-out' : 'zoom-in'} size={24} color="#fff" /> {/* Bottom Controls */} {/* Capture Button */} {isCapturing ? ( ) : ( )} {/* Grid Overlay */} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#000', }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#000', }, loadingText: { color: '#fff', fontSize: 16, }, camera: { flex: 1, }, evidenceOverlay: { position: 'absolute', top: 100, left: 16, }, evidenceBadge: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'rgba(0,122,255,0.8)', paddingHorizontal: 12, paddingVertical: 6, borderRadius: 16, }, evidenceText: { color: '#fff', fontSize: 12, fontWeight: '600', marginLeft: 4, }, missionOverlay: { position: 'absolute', top: 100, right: 16, }, missionCard: { backgroundColor: 'rgba(52,199,89,0.9)', padding: 12, borderRadius: 12, minWidth: 120, }, missionTitle: { color: '#fff', fontSize: 12, fontWeight: '600', }, missionReward: { color: '#fff', fontSize: 16, fontWeight: '700', marginTop: 4, }, topControls: { position: 'absolute', top: 50, left: 0, right: 0, flexDirection: 'row', justifyContent: 'space-between', paddingHorizontal: 16, }, controlButton: { width: 44, height: 44, borderRadius: 22, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'center', alignItems: 'center', }, bottomControls: { position: 'absolute', bottom: 100, left: 0, right: 0, alignItems: 'center', }, captureButton: { width: 80, height: 80, borderRadius: 40, backgroundColor: 'rgba(255,255,255,0.3)', justifyContent: 'center', alignItems: 'center', }, captureInner: { width: 70, height: 70, borderRadius: 35, backgroundColor: '#fff', justifyContent: 'center', alignItems: 'center', }, captureCircle: { width: 60, height: 60, borderRadius: 30, backgroundColor: '#fff', }, gridOverlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, }, gridLine: { position: 'absolute', top: '33%', left: 0, right: 0, height: 1, backgroundColor: 'rgba(255,255,255,0.2)', }, gridLineVertical: { top: 0, left: '33%', width: 1, height: '100%', }, });