-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCamera.jsx
More file actions
136 lines (123 loc) · 5.89 KB
/
Copy pathCamera.jsx
File metadata and controls
136 lines (123 loc) · 5.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import React, { useState, useRef, useCallback } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import CameraView from '../components/camera/CameraView';
import TopBar from '../components/camera/TopBar';
import CaptureButton from '../components/camera/CaptureButton';
import FilterCarousel from '../components/camera/FilterCarousel';
import PhotoPreview from '../components/camera/PhotoPreview';
import { ALL_FILTERS } from '../components/camera/filters';
import { PREMIUM_FILTERS } from '../components/camera/premiumFilters';
const FULL_LIST = [...ALL_FILTERS, ...PREMIUM_FILTERS];
export default function Camera() {
const [activeFilter, setActiveFilter] = useState('none');
const [facingMode, setFacingMode] = useState('user');
const [flashOn, setFlashOn] = useState(false);
const [capturedPhoto, setCapturedPhoto] = useState(null);
const [showFlash, setShowFlash] = useState(false);
const [showGallery, setShowGallery] = useState(false);
const [performanceMode,setPerformanceMode] = useState(false);
const cameraRef = useRef(null);
const handleCapture = useCallback(() => {
if (!cameraRef.current) return;
setShowFlash(true);
setTimeout(() => setShowFlash(false), 350);
const photoData = cameraRef.current.capture();
if (photoData) setCapturedPhoto(photoData);
}, []);
const handleFlipCamera = useCallback(() => {
setFacingMode(p => p === 'user' ? 'environment' : 'user');
}, []);
const activeFilterObj = FULL_LIST.find(f => f.id === activeFilter);
const filterName = activeFilterObj?.name ?? '';
const filterEmoji = activeFilterObj?.emoji ?? '';
return (
<div
className="fixed inset-0 bg-black overflow-hidden select-none"
style={{ touchAction: 'none' }}
>
{/* ── Camera ─────────────────────────────────────────────────── */}
<CameraView
ref={cameraRef}
activeFilter={activeFilter}
facingMode={facingMode}
performanceMode={performanceMode}
/>
{/* ── Top bar ─────────────────────────────────────────────────── */}
<TopBar
onFlipCamera={handleFlipCamera}
flashOn={flashOn}
onToggleFlash={() => setFlashOn(p => !p)}
performanceMode={performanceMode}
onTogglePerformance={() => setPerformanceMode(p => !p)}
/>
{/* ── Active filter badge ──────────────────────────────────────── */}
<AnimatePresence>
{activeFilter !== 'none' && (
<motion.div
key={activeFilter}
initial={{ opacity: 0, scale: 0.75, y: -4 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.75 }}
transition={{ type: 'spring', damping: 18 }}
className="absolute z-20 left-1/2 -translate-x-1/2 flex items-center gap-1.5 px-3.5 py-1.5 rounded-full bg-black/45 backdrop-blur-md"
style={{ top: 'max(env(safe-area-inset-top, 0px), 12px)', marginTop: 44 }}
>
<span className="text-sm">{filterEmoji}</span>
<span className="text-white text-xs font-bold tracking-wide">{filterName}</span>
{activeFilterObj?.premium && (
<span className="text-yellow-400 text-[9px] font-black">PRO</span>
)}
</motion.div>
)}
</AnimatePresence>
{/* ── Performance mode indicator ───────────────────────────────── */}
<AnimatePresence>
{performanceMode && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute top-14 right-3 z-20 px-2 py-0.5 rounded-full bg-yellow-400/20 border border-yellow-400/40"
>
<span className="text-yellow-400 text-[9px] font-bold tracking-widest">ECO</span>
</motion.div>
)}
</AnimatePresence>
{/* ── Filter carousel ──────────────────────────────────────────── */}
<FilterCarousel
activeFilter={activeFilter}
onSelectFilter={setActiveFilter}
showGallery={showGallery}
onToggleGallery={() => setShowGallery(p => !p)}
/>
{/* ── Capture button (overlaid on carousel action row) ─────────── */}
<div
className="absolute z-30 left-1/2 -translate-x-1/2"
style={{
bottom: 'calc(max(env(safe-area-inset-bottom, 0px), 14px) + 14px)',
}}
>
<CaptureButton onCapture={handleCapture} />
</div>
{/* ── Flash overlay ────────────────────────────────────────────── */}
<AnimatePresence>
{showFlash && (
<motion.div
initial={{ opacity: 0.95 }}
animate={{ opacity: 0 }}
transition={{ duration: 0.35 }}
className="absolute inset-0 bg-white z-40 pointer-events-none"
/>
)}
</AnimatePresence>
{/* ── Photo preview ────────────────────────────────────────────── */}
{capturedPhoto && (
<PhotoPreview
photoData={capturedPhoto}
filterName={filterName}
onClose={() => setCapturedPhoto(null)}
/>
)}
</div>
);
}