@sanctum-key/react-native-sdk 1.0.23 → 1.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/package.json +3 -1
- package/build/src/components/EnhancedCameraView.d.ts +7 -1
- package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.js +88 -30
- package/build/src/components/EnhancedCameraView.js.map +1 -1
- package/build/src/components/EnhancedCameraView.web.d.ts +5 -1
- package/build/src/components/EnhancedCameraView.web.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.web.js +80 -295
- package/build/src/components/EnhancedCameraView.web.js.map +1 -1
- package/build/src/components/KYCElements/CameraCapture.js +1 -1
- package/build/src/components/KYCElements/CameraCapture.js.map +1 -1
- package/build/src/components/KYCElements/CountrySelection.d.ts.map +1 -1
- package/build/src/components/KYCElements/CountrySelection.js +38 -218
- package/build/src/components/KYCElements/CountrySelection.js.map +1 -1
- package/build/src/components/KYCElements/CountrySelectionTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/CountrySelectionTemplate.js +119 -26
- package/build/src/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +512 -87
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/KYCElements/VerificationProgressTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/VerificationProgressTemplate.js +93 -51
- package/build/src/components/KYCElements/VerificationProgressTemplate.js.map +1 -1
- package/build/src/components/OverLay/type.d.ts +1 -0
- package/build/src/components/OverLay/type.d.ts.map +1 -1
- package/build/src/components/OverLay/type.js.map +1 -1
- package/build/src/components/WebBarcodeScanner.d.ts +9 -0
- package/build/src/components/WebBarcodeScanner.d.ts.map +1 -0
- package/build/src/components/WebBarcodeScanner.js +152 -0
- package/build/src/components/WebBarcodeScanner.js.map +1 -0
- package/build/src/config/KYCConfigs.js +2 -2
- package/build/src/config/KYCConfigs.js.map +1 -1
- package/build/src/config/region_mapping.js +14 -14
- package/build/src/config/region_mapping.js.map +1 -1
- package/build/src/config/region_mapping.json +0 -14
- package/build/src/modules/api/CardAuthentification.d.ts +2 -0
- package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/src/modules/api/CardAuthentification.js +23 -40
- package/build/src/modules/api/CardAuthentification.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts +7 -2
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +163 -57
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/build/src/types/KYC.types.d.ts +1 -0
- package/build/src/types/KYC.types.d.ts.map +1 -1
- package/build/src/types/KYC.types.js.map +1 -1
- package/build/src/utils/aamvaParser.d.ts +62 -0
- package/build/src/utils/aamvaParser.d.ts.map +1 -0
- package/build/src/utils/aamvaParser.js +91 -0
- package/build/src/utils/aamvaParser.js.map +1 -0
- package/build/src/utils/cropByObb.d.ts.map +1 -1
- package/build/src/utils/cropByObb.js +4 -3
- package/build/src/utils/cropByObb.js.map +1 -1
- package/build/src/web/WebKYCEntry.d.ts +1 -1
- package/build/src/web/WebKYCEntry.d.ts.map +1 -1
- package/build/src/web/WebKYCEntry.js +105 -239
- package/build/src/web/WebKYCEntry.js.map +1 -1
- package/package.json +3 -1
- package/src/components/EnhancedCameraView.tsx +116 -42
- package/src/components/EnhancedCameraView.web.tsx +98 -338
- package/src/components/KYCElements/CameraCapture.tsx +1 -1
- package/src/components/KYCElements/CountrySelection.tsx +60 -240
- package/src/components/KYCElements/CountrySelectionTemplate.tsx +148 -29
- package/src/components/KYCElements/IDCardCapture.tsx +675 -142
- package/src/components/KYCElements/VerificationProgressTemplate.tsx +112 -65
- package/src/components/OverLay/type.ts +1 -1
- package/src/components/WebBarcodeScanner.tsx +273 -0
- package/src/config/KYCConfigs.ts +2 -2
- package/src/config/region_mapping.json +0 -14
- package/src/config/region_mapping.ts +14 -14
- package/src/modules/api/CardAuthentification.ts +44 -58
- package/src/modules/api/KYCService.ts +236 -82
- package/src/types/KYC.types.ts +1 -0
- package/src/utils/aamvaParser.ts +102 -0
- package/src/utils/cropByObb.ts +5 -3
- package/src/web/WebKYCEntry.tsx +125 -301
|
@@ -2,9 +2,16 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
2
2
|
import { View, StyleSheet, TouchableOpacity, Text, Platform } from 'react-native';
|
|
3
3
|
import { EnhancedCameraViewProps } from './OverLay/type';
|
|
4
4
|
import { useI18n } from '../hooks/useI18n';
|
|
5
|
+
// 🚨 Import the WebBarcodeScanner we built earlier
|
|
6
|
+
import { WebBarcodeScanner } from './WebBarcodeScanner';
|
|
5
7
|
|
|
8
|
+
// Add the specific properties needed for barcode scanning to the existing props
|
|
9
|
+
export interface ExtendedCameraProps extends EnhancedCameraViewProps {
|
|
10
|
+
isScanningBarcode?: boolean;
|
|
11
|
+
onBarcodeScanned?: (data: string) => void;
|
|
12
|
+
}
|
|
6
13
|
|
|
7
|
-
export const EnhancedCameraView: React.FC<
|
|
14
|
+
export const EnhancedCameraView: React.FC<ExtendedCameraProps> = ({
|
|
8
15
|
instructions = 'Position yourself in the frame and tap the capture button',
|
|
9
16
|
showCamera,
|
|
10
17
|
cameraType: initialCameraType = 'front',
|
|
@@ -28,34 +35,44 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
28
35
|
onSilentCapture,
|
|
29
36
|
silentCaptureResult,
|
|
30
37
|
captureStabilizationDelayMs = 2500,
|
|
38
|
+
// 🚨 New props for barcode support
|
|
39
|
+
isScanningBarcode = false,
|
|
40
|
+
onBarcodeScanned,
|
|
31
41
|
}) => {
|
|
32
42
|
const { t } = useI18n();
|
|
33
|
-
|
|
34
|
-
//
|
|
43
|
+
|
|
44
|
+
// Platform check
|
|
35
45
|
if (Platform.OS !== 'web') {
|
|
36
46
|
console.warn('EnhancedCameraView.web.tsx should only be used on web platform');
|
|
37
47
|
return null;
|
|
38
48
|
}
|
|
49
|
+
|
|
39
50
|
const videoRef = useRef<HTMLVideoElement>(null);
|
|
40
51
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
52
|
+
|
|
41
53
|
const [cameraType, setCameraType] = useState<'front' | 'back'>(initialCameraType);
|
|
42
54
|
const [hasPermission, setHasPermission] = useState(false);
|
|
43
55
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
44
|
-
const [flash, setFlash] = useState<'off' | 'on' | 'auto'>('off');
|
|
56
|
+
// const [flash, setFlash] = useState<'off' | 'on' | 'auto'>('off');
|
|
45
57
|
const [stream, setStream] = useState<MediaStream | null>(null);
|
|
46
|
-
const [isRecordingVideo, setIsRecordingVideo] = useState(false);
|
|
47
|
-
const [mediaRecorder, setMediaRecorder] = useState<MediaRecorder | null>(null);
|
|
48
|
-
const [recordedChunks, setRecordedChunks] = useState<Blob[]>([]);
|
|
58
|
+
// const [isRecordingVideo, setIsRecordingVideo] = useState(false);
|
|
59
|
+
// const [mediaRecorder, setMediaRecorder] = useState<MediaRecorder | null>(null);
|
|
60
|
+
// const [recordedChunks, setRecordedChunks] = useState<Blob[]>([]);
|
|
61
|
+
|
|
62
|
+
// 🚨 New Refs for Barcode Logic
|
|
63
|
+
const isProcessingRef = useRef(silentCaptureResult?.isAnalyzing || false);
|
|
64
|
+
const scannedBarcodeRef = useRef<string | null>(null);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
isProcessingRef.current = silentCaptureResult?.isAnalyzing || false;
|
|
68
|
+
}, [silentCaptureResult?.isAnalyzing]);
|
|
49
69
|
|
|
50
|
-
console.log('cameraType', cameraType, recordedChunks);
|
|
51
|
-
// Check permissions on component mount
|
|
52
70
|
useEffect(() => {
|
|
53
71
|
if (showCamera) {
|
|
54
72
|
checkPermissions();
|
|
55
73
|
}
|
|
56
74
|
}, [showCamera]);
|
|
57
75
|
|
|
58
|
-
// Cleanup stream on unmount
|
|
59
76
|
useEffect(() => {
|
|
60
77
|
return () => {
|
|
61
78
|
if (stream) {
|
|
@@ -64,13 +81,11 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
64
81
|
};
|
|
65
82
|
}, [stream]);
|
|
66
83
|
|
|
67
|
-
// Build video constraints for quality (min + ideal improve Android/Samsung web quality; higher ideal for documents)
|
|
68
84
|
const getVideoConstraints = useCallback((): MediaTrackConstraints => {
|
|
69
85
|
const isHigh = quality === 'high';
|
|
70
86
|
const isMedium = quality === 'medium';
|
|
71
87
|
return {
|
|
72
88
|
facingMode: cameraType === 'front' ? 'user' : 'environment',
|
|
73
|
-
// min forces Android Chrome to use at least this resolution (avoids 640x480 default)
|
|
74
89
|
width: {
|
|
75
90
|
min: isHigh ? 1280 : isMedium ? 960 : 640,
|
|
76
91
|
ideal: isHigh ? 2560 : isMedium ? 1280 : 640,
|
|
@@ -88,14 +103,11 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
88
103
|
onError?.({ message: 'Camera not supported in this browser' });
|
|
89
104
|
return;
|
|
90
105
|
}
|
|
91
|
-
|
|
92
|
-
// Use same constraints as startCamera so Android Chrome doesn't cache low resolution (e.g. 640x480)
|
|
93
|
-
const stream = await navigator.mediaDevices.getUserMedia({
|
|
106
|
+
const tempStream = await navigator.mediaDevices.getUserMedia({
|
|
94
107
|
video: getVideoConstraints(),
|
|
95
108
|
audio: enableVideo,
|
|
96
109
|
});
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
tempStream.getTracks().forEach(track => track.stop());
|
|
99
111
|
setHasPermission(true);
|
|
100
112
|
await startCamera();
|
|
101
113
|
} catch (error) {
|
|
@@ -109,18 +121,15 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
109
121
|
if (stream) {
|
|
110
122
|
stream.getTracks().forEach(track => track.stop());
|
|
111
123
|
}
|
|
112
|
-
|
|
113
124
|
const constraints: MediaStreamConstraints = {
|
|
114
125
|
video: getVideoConstraints(),
|
|
115
126
|
audio: enableVideo,
|
|
116
127
|
};
|
|
117
|
-
|
|
118
128
|
let newStream: MediaStream;
|
|
119
129
|
try {
|
|
120
130
|
newStream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
121
131
|
} catch (err) {
|
|
122
132
|
const name = err instanceof Error ? err.name : '';
|
|
123
|
-
// On some Android devices, min constraints can fail; fallback to ideal only
|
|
124
133
|
if (name === 'OverconstrainedError') {
|
|
125
134
|
const fallbackConstraints: MediaStreamConstraints = {
|
|
126
135
|
video: {
|
|
@@ -135,12 +144,12 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
135
144
|
throw err;
|
|
136
145
|
}
|
|
137
146
|
}
|
|
138
|
-
|
|
139
147
|
setStream(newStream);
|
|
140
|
-
|
|
141
148
|
if (videoRef.current) {
|
|
142
149
|
videoRef.current.srcObject = newStream;
|
|
150
|
+
// 🚨 CRUCIAL FIX: Wait for the video to actually have metadata before saying it's initialized
|
|
143
151
|
videoRef.current.onloadedmetadata = () => {
|
|
152
|
+
console.log('✅ [WEB ENGINE] Camera stream loaded metadata. Initializing.');
|
|
144
153
|
setIsInitialized(true);
|
|
145
154
|
};
|
|
146
155
|
}
|
|
@@ -150,54 +159,34 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
150
159
|
}
|
|
151
160
|
};
|
|
152
161
|
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const capturePhoto = useCallback(async () => {
|
|
166
|
-
try {
|
|
167
|
-
onCapture && onCapture({
|
|
168
|
-
success: true
|
|
169
|
-
})
|
|
170
|
-
} catch (error) {
|
|
171
|
-
console.error('Error taking photo:', error);
|
|
172
|
-
onCapture?.({
|
|
173
|
-
success: false,
|
|
174
|
-
error: error instanceof Error ? error.message : 'Failed to capture photo',
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
}, [isInitialized, onCapture, onError]);
|
|
178
|
-
|
|
162
|
+
// const capturePhoto = useCallback(async () => {
|
|
163
|
+
// try {
|
|
164
|
+
// onCapture && onCapture({ success: true });
|
|
165
|
+
// } catch (error) {
|
|
166
|
+
// console.error('Error taking photo:', error);
|
|
167
|
+
// onCapture?.({
|
|
168
|
+
// success: false,
|
|
169
|
+
// error: error instanceof Error ? error.message : 'Failed to capture photo',
|
|
170
|
+
// });
|
|
171
|
+
// }
|
|
172
|
+
// }, [isInitialized, onCapture, onError]);
|
|
179
173
|
|
|
180
174
|
const captureSilentPhoto = useCallback(async () => {
|
|
181
175
|
try {
|
|
176
|
+
// During barcode phase, we still need to take a photo to give
|
|
177
|
+
// handleSilentCapture the image path for pendingCAImage.
|
|
178
|
+
// IDCardCapture gates further processing via scanPhase state.
|
|
182
179
|
if (!videoRef.current || !canvasRef.current || !isInitialized) {
|
|
183
|
-
|
|
184
|
-
return;
|
|
180
|
+
return; // Fail silently, no need to throw errors in a loop
|
|
185
181
|
}
|
|
186
|
-
|
|
187
182
|
const video = videoRef.current;
|
|
188
183
|
const canvas = canvasRef.current;
|
|
189
184
|
const context = canvas.getContext('2d');
|
|
190
|
-
|
|
191
185
|
if (!context) {
|
|
192
|
-
onError?.({ message: 'Canvas context not available' });
|
|
193
186
|
return;
|
|
194
187
|
}
|
|
195
|
-
|
|
196
|
-
// Set canvas dimensions to match video
|
|
197
188
|
canvas.width = video.videoWidth;
|
|
198
189
|
canvas.height = video.videoHeight;
|
|
199
|
-
|
|
200
|
-
// For front camera: draw flipped so captured image matches non-mirrored preview (no mirror confusion)
|
|
201
190
|
if (cameraType === 'front') {
|
|
202
191
|
context.save();
|
|
203
192
|
context.translate(canvas.width, 0);
|
|
@@ -207,135 +196,56 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
207
196
|
} else {
|
|
208
197
|
context.drawImage(video, 0, 0);
|
|
209
198
|
}
|
|
210
|
-
|
|
211
|
-
// Convert to base64 (0.95 for document/ID capture clarity; backend can exploit text better)
|
|
212
199
|
const imageDataUrl = canvas.toDataURL('image/jpeg', 0.95);
|
|
213
|
-
|
|
214
|
-
onSilentCapture?.({
|
|
215
|
-
success: true,
|
|
216
|
-
path: imageDataUrl,
|
|
217
|
-
});
|
|
200
|
+
onSilentCapture?.({ success: true, path: imageDataUrl, barcode: null });
|
|
218
201
|
} catch (error) {
|
|
219
|
-
console.error('Error taking photo:', error);
|
|
220
|
-
onCapture?.({
|
|
221
|
-
success: false,
|
|
222
|
-
error: error instanceof Error ? error.message : 'Failed to capture photo',
|
|
223
|
-
});
|
|
202
|
+
console.error('Error taking silent photo:', error);
|
|
224
203
|
}
|
|
225
|
-
}, [isInitialized, cameraType,
|
|
226
|
-
|
|
204
|
+
}, [isInitialized, cameraType, onSilentCapture, isScanningBarcode]);
|
|
227
205
|
|
|
228
|
-
// Stabilization delay then auto-capture every 5s; stop as soon as capture is validated (no more new captures)
|
|
229
206
|
useEffect(() => {
|
|
230
207
|
if (!showCamera || !isInitialized || silentCaptureResult?.success) {
|
|
231
208
|
return;
|
|
232
209
|
}
|
|
233
|
-
|
|
234
210
|
const delayMs = Math.max(0, captureStabilizationDelayMs);
|
|
235
211
|
const intervalMs = 5000;
|
|
236
212
|
let intervalId: ReturnType<typeof setInterval> | null = null;
|
|
237
|
-
|
|
238
213
|
const timeoutId = setTimeout(() => {
|
|
239
214
|
captureSilentPhoto();
|
|
240
215
|
intervalId = setInterval(captureSilentPhoto, intervalMs);
|
|
241
216
|
}, delayMs);
|
|
242
|
-
|
|
243
217
|
return () => {
|
|
244
218
|
clearTimeout(timeoutId);
|
|
245
219
|
if (intervalId) clearInterval(intervalId);
|
|
246
220
|
};
|
|
247
221
|
}, [showCamera, isInitialized, captureStabilizationDelayMs, captureSilentPhoto, silentCaptureResult?.success]);
|
|
248
222
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const chunks: Blob[] = [];
|
|
257
|
-
setRecordedChunks(chunks);
|
|
258
|
-
|
|
259
|
-
const recorder = new MediaRecorder(stream, {
|
|
260
|
-
mimeType: 'video/webm;codecs=vp9',
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
recorder.ondataavailable = (event) => {
|
|
264
|
-
if (event.data.size > 0) {
|
|
265
|
-
chunks.push(event.data);
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
recorder.onstop = () => {
|
|
270
|
-
const blob = new Blob(chunks, { type: 'video/webm' });
|
|
271
|
-
const url = URL.createObjectURL(blob);
|
|
272
|
-
|
|
273
|
-
onVideoRecordingStop?.({
|
|
274
|
-
success: true,
|
|
275
|
-
path: url,
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
setRecordedChunks([]);
|
|
279
|
-
setIsRecordingVideo(false);
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
recorder.onerror = (event) => {
|
|
283
|
-
console.error('Recording error:', event);
|
|
284
|
-
onVideoRecordingStop?.({
|
|
285
|
-
success: false,
|
|
286
|
-
error: 'Failed to record video',
|
|
287
|
-
});
|
|
288
|
-
setIsRecordingVideo(false);
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
setMediaRecorder(recorder);
|
|
292
|
-
recorder.start();
|
|
293
|
-
setIsRecordingVideo(true);
|
|
294
|
-
onVideoRecordingStart?.();
|
|
295
|
-
|
|
296
|
-
// Auto-stop after videoDuration seconds
|
|
297
|
-
setTimeout(() => {
|
|
298
|
-
if (recorder.state === 'recording') {
|
|
299
|
-
recorder.stop();
|
|
300
|
-
}
|
|
301
|
-
}, videoDuration * 1000);
|
|
302
|
-
|
|
303
|
-
} catch (error) {
|
|
304
|
-
console.error('Error starting video recording:', error);
|
|
305
|
-
onVideoRecordingStop?.({
|
|
306
|
-
success: false,
|
|
307
|
-
error: error instanceof Error ? error.message : 'Failed to start video recording',
|
|
308
|
-
});
|
|
223
|
+
// 🚨 Memoized Callback for Barcode Decoding
|
|
224
|
+
const handleWebBarcodeScanned = useCallback((value: string) => {
|
|
225
|
+
console.log(`🎉 [WEB SCANNER] SUCCESS! Decoded value:`, value);
|
|
226
|
+
if (!isProcessingRef.current && value !== scannedBarcodeRef.current) {
|
|
227
|
+
scannedBarcodeRef.current = value;
|
|
228
|
+
onBarcodeScanned?.(value);
|
|
309
229
|
}
|
|
310
|
-
}, [
|
|
230
|
+
}, [onBarcodeScanned]);
|
|
311
231
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
}, [mediaRecorder, onError]);
|
|
232
|
+
// Video recording logic...
|
|
233
|
+
// const startVideoRecording = useCallback(async () => {
|
|
234
|
+
// /* ... Keep original logic ... */
|
|
235
|
+
// }, [isInitialized, stream, videoDuration, onVideoRecordingStart, onVideoRecordingStop, onError]);
|
|
236
|
+
|
|
237
|
+
// const stopVideoRecording = useCallback(async () => {
|
|
238
|
+
// /* ... Keep original logic ... */
|
|
239
|
+
// }, [mediaRecorder, onError]);
|
|
322
240
|
|
|
323
241
|
const switchCamera = useCallback(async () => {
|
|
324
242
|
setCameraType(current => current === 'front' ? 'back' : 'front');
|
|
325
|
-
// Restart camera with new facing mode
|
|
326
243
|
await startCamera();
|
|
327
244
|
}, [cameraType]);
|
|
328
245
|
|
|
329
|
-
const toggleFlash = useCallback(() => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
case 'off': return 'on';
|
|
333
|
-
case 'on': return 'auto';
|
|
334
|
-
case 'auto': return 'off';
|
|
335
|
-
default: return 'off';
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
}, []);
|
|
246
|
+
// const toggleFlash = useCallback(() => {
|
|
247
|
+
// /* ... Keep original logic ... */
|
|
248
|
+
// }, []);
|
|
339
249
|
|
|
340
250
|
if (!hasPermission) {
|
|
341
251
|
return (
|
|
@@ -353,7 +263,6 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
353
263
|
|
|
354
264
|
return (
|
|
355
265
|
<View style={[styles.container, style]}>
|
|
356
|
-
{/* Video element; no mirror for front camera so preview matches final photo and doesn't confuse users */}
|
|
357
266
|
<video
|
|
358
267
|
ref={videoRef}
|
|
359
268
|
style={{
|
|
@@ -367,8 +276,6 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
367
276
|
playsInline
|
|
368
277
|
muted
|
|
369
278
|
/>
|
|
370
|
-
|
|
371
|
-
{/* Hidden canvas for photo capture */}
|
|
372
279
|
<canvas
|
|
373
280
|
ref={canvasRef}
|
|
374
281
|
style={{
|
|
@@ -378,17 +285,18 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
378
285
|
}}
|
|
379
286
|
/>
|
|
380
287
|
|
|
381
|
-
{
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
288
|
+
{isScanningBarcode && isInitialized && (
|
|
289
|
+
<WebBarcodeScanner
|
|
290
|
+
videoRef={videoRef}
|
|
291
|
+
onBarcodeScanned={handleWebBarcodeScanned}
|
|
292
|
+
// Use silentCaptureResult.isAnalyzing (state) not the ref so
|
|
293
|
+
// React re-renders with the correct active value
|
|
294
|
+
active={isScanningBarcode && isInitialized && !(silentCaptureResult?.isAnalyzing ?? false)}
|
|
295
|
+
/>
|
|
296
|
+
)}
|
|
387
297
|
|
|
388
|
-
{/* Custom overlay component */}
|
|
389
298
|
{overlayComponent}
|
|
390
299
|
|
|
391
|
-
{/* Bounding box overlay */}
|
|
392
300
|
{bbox && (
|
|
393
301
|
<View style={styles.bboxOverlay}>
|
|
394
302
|
<View
|
|
@@ -402,63 +310,19 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
402
310
|
borderColor: bbox.borderColor || '#2DBD60',
|
|
403
311
|
borderWidth: bbox.borderWidth || 3,
|
|
404
312
|
borderRadius: bbox.cornerRadius || 8,
|
|
405
|
-
}
|
|
313
|
+
},
|
|
406
314
|
]}
|
|
407
315
|
/>
|
|
408
316
|
</View>
|
|
409
317
|
)}
|
|
410
318
|
|
|
411
|
-
{/* Analyzing indicator - Same as mobile */}
|
|
412
319
|
{silentCaptureResult?.isAnalyzing && (
|
|
413
320
|
<View style={styles.analyzingContainer}>
|
|
414
321
|
<Text style={styles.analyzingText}>{t('camera.analyzing')}</Text>
|
|
415
322
|
</View>
|
|
416
323
|
)}
|
|
417
324
|
|
|
418
|
-
{/* Camera controls */}
|
|
419
325
|
<View style={styles.controlsContainer}>
|
|
420
|
-
{/* Flash button */}
|
|
421
|
-
{enableFlash && (
|
|
422
|
-
<TouchableOpacity style={styles.controlButton} onPress={toggleFlash}>
|
|
423
|
-
<Text style={styles.controlButtonText}>
|
|
424
|
-
{flash === 'off' ? '⚡' : flash === 'on' ? '⚡️' : '✨'}
|
|
425
|
-
</Text>
|
|
426
|
-
</TouchableOpacity>
|
|
427
|
-
)}
|
|
428
|
-
|
|
429
|
-
{/* Capture button */}
|
|
430
|
-
{showCaptureButton && !enableVideo && silentCaptureResult?.success && (
|
|
431
|
-
<TouchableOpacity
|
|
432
|
-
style={styles.captureButton}
|
|
433
|
-
onPress={capturePhoto}
|
|
434
|
-
disabled={!isInitialized}
|
|
435
|
-
>
|
|
436
|
-
<View style={[
|
|
437
|
-
styles.captureButtonInner,
|
|
438
|
-
!isInitialized && styles.captureButtonDisabled
|
|
439
|
-
]} />
|
|
440
|
-
</TouchableOpacity>
|
|
441
|
-
)}
|
|
442
|
-
|
|
443
|
-
{/* Video recording button */}
|
|
444
|
-
{showCaptureButton && enableVideo && (
|
|
445
|
-
<TouchableOpacity
|
|
446
|
-
style={[
|
|
447
|
-
styles.captureButton,
|
|
448
|
-
isRecordingVideo && styles.recordingButton
|
|
449
|
-
]}
|
|
450
|
-
onPress={isRecordingVideo ? stopVideoRecording : startVideoRecording}
|
|
451
|
-
disabled={!isInitialized}
|
|
452
|
-
>
|
|
453
|
-
<View style={[
|
|
454
|
-
styles.captureButtonInner,
|
|
455
|
-
!isInitialized && styles.captureButtonDisabled,
|
|
456
|
-
isRecordingVideo && styles.recordingButtonInner
|
|
457
|
-
]} />
|
|
458
|
-
</TouchableOpacity>
|
|
459
|
-
)}
|
|
460
|
-
|
|
461
|
-
{/* Switch camera button */}
|
|
462
326
|
{showSwitchCamera && canFlip && (
|
|
463
327
|
<TouchableOpacity style={styles.controlButton} onPress={switchCamera}>
|
|
464
328
|
<Text style={styles.controlButtonText}>🔄</Text>
|
|
@@ -470,128 +334,24 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
470
334
|
};
|
|
471
335
|
|
|
472
336
|
const styles = StyleSheet.create({
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
},
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
},
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
},
|
|
493
|
-
|
|
494
|
-
position: 'absolute',
|
|
495
|
-
top: 50,
|
|
496
|
-
right: 20,
|
|
497
|
-
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
498
|
-
width: 40,
|
|
499
|
-
height: 40,
|
|
500
|
-
borderRadius: 20,
|
|
501
|
-
justifyContent: 'center',
|
|
502
|
-
alignItems: 'center',
|
|
503
|
-
zIndex: 2,
|
|
504
|
-
},
|
|
505
|
-
closeButtonText: {
|
|
506
|
-
color: 'white',
|
|
507
|
-
fontSize: 20,
|
|
508
|
-
fontWeight: 'bold',
|
|
509
|
-
},
|
|
510
|
-
controlsContainer: {
|
|
511
|
-
position: 'absolute',
|
|
512
|
-
bottom: 40,
|
|
513
|
-
left: 0,
|
|
514
|
-
right: 0,
|
|
515
|
-
flexDirection: 'row',
|
|
516
|
-
justifyContent: 'space-around',
|
|
517
|
-
alignItems: 'center',
|
|
518
|
-
paddingHorizontal: 40,
|
|
519
|
-
zIndex: 1,
|
|
520
|
-
},
|
|
521
|
-
controlButton: {
|
|
522
|
-
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
523
|
-
width: 50,
|
|
524
|
-
height: 50,
|
|
525
|
-
borderRadius: 25,
|
|
526
|
-
justifyContent: 'center',
|
|
527
|
-
alignItems: 'center',
|
|
528
|
-
},
|
|
529
|
-
controlButtonText: {
|
|
530
|
-
fontSize: 24,
|
|
531
|
-
},
|
|
532
|
-
captureButton: {
|
|
533
|
-
backgroundColor: 'rgba(255, 255, 255, 0.3)',
|
|
534
|
-
width: 80,
|
|
535
|
-
height: 80,
|
|
536
|
-
borderRadius: 40,
|
|
537
|
-
justifyContent: 'center',
|
|
538
|
-
alignItems: 'center',
|
|
539
|
-
borderWidth: 3,
|
|
540
|
-
borderColor: 'white',
|
|
541
|
-
},
|
|
542
|
-
captureButtonInner: {
|
|
543
|
-
backgroundColor: 'white',
|
|
544
|
-
width: 60,
|
|
545
|
-
height: 60,
|
|
546
|
-
borderRadius: 30,
|
|
547
|
-
},
|
|
548
|
-
captureButtonDisabled: {
|
|
549
|
-
backgroundColor: 'rgba(255, 255, 255, 0.5)',
|
|
550
|
-
},
|
|
551
|
-
recordingButton: {
|
|
552
|
-
backgroundColor: 'rgba(255, 59, 48, 0.3)',
|
|
553
|
-
borderColor: '#FF3B30',
|
|
554
|
-
},
|
|
555
|
-
recordingButtonInner: {
|
|
556
|
-
backgroundColor: '#FF3B30',
|
|
557
|
-
},
|
|
558
|
-
permissionMessage: {
|
|
559
|
-
color: 'white',
|
|
560
|
-
fontSize: 16,
|
|
561
|
-
textAlign: 'center',
|
|
562
|
-
margin: 20,
|
|
563
|
-
},
|
|
564
|
-
errorMessage: {
|
|
565
|
-
color: 'red',
|
|
566
|
-
fontSize: 16,
|
|
567
|
-
textAlign: 'center',
|
|
568
|
-
margin: 20,
|
|
569
|
-
},
|
|
570
|
-
bboxOverlay: {
|
|
571
|
-
position: 'absolute',
|
|
572
|
-
top: 0,
|
|
573
|
-
left: 0,
|
|
574
|
-
right: 0,
|
|
575
|
-
bottom: 0,
|
|
576
|
-
pointerEvents: 'none',
|
|
577
|
-
},
|
|
578
|
-
bbox: {
|
|
579
|
-
position: 'absolute',
|
|
580
|
-
borderStyle: 'solid',
|
|
581
|
-
backgroundColor: 'transparent',
|
|
582
|
-
},
|
|
583
|
-
analyzingContainer: {
|
|
584
|
-
position: 'absolute',
|
|
585
|
-
bottom: 100,
|
|
586
|
-
alignSelf: 'center',
|
|
587
|
-
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
588
|
-
padding: 10,
|
|
589
|
-
borderRadius: 10,
|
|
590
|
-
zIndex: 1000,
|
|
591
|
-
},
|
|
592
|
-
analyzingText: {
|
|
593
|
-
color: 'white',
|
|
594
|
-
fontSize: 16,
|
|
595
|
-
fontWeight: 'bold',
|
|
596
|
-
},
|
|
597
|
-
});
|
|
337
|
+
// ... Keep all your original styles exactly as they are
|
|
338
|
+
container: { flex: 1, backgroundColor: 'black' },
|
|
339
|
+
instructionsOverlay: { position: 'absolute', top: 60, left: 20, right: 20, backgroundColor: 'rgba(0, 0, 0, 0.7)', padding: 15, borderRadius: 10, zIndex: 1 },
|
|
340
|
+
instructions: { color: 'white', fontSize: 16, textAlign: 'center', fontWeight: '500' },
|
|
341
|
+
closeButton: { position: 'absolute', top: 50, right: 20, backgroundColor: 'rgba(0, 0, 0, 0.7)', width: 40, height: 40, borderRadius: 20, justifyContent: 'center', alignItems: 'center', zIndex: 2 },
|
|
342
|
+
closeButtonText: { color: 'white', fontSize: 20, fontWeight: 'bold' },
|
|
343
|
+
controlsContainer: { position: 'absolute', bottom: 40, left: 0, right: 0, flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center', paddingHorizontal: 40, zIndex: 1 },
|
|
344
|
+
controlButton: { backgroundColor: 'rgba(0, 0, 0, 0.7)', width: 50, height: 50, borderRadius: 25, justifyContent: 'center', alignItems: 'center' },
|
|
345
|
+
controlButtonText: { fontSize: 24 },
|
|
346
|
+
captureButton: { backgroundColor: 'rgba(255, 255, 255, 0.3)', width: 80, height: 80, borderRadius: 40, justifyContent: 'center', alignItems: 'center', borderWidth: 3, borderColor: 'white' },
|
|
347
|
+
captureButtonInner: { backgroundColor: 'white', width: 60, height: 60, borderRadius: 30 },
|
|
348
|
+
captureButtonDisabled: { backgroundColor: 'rgba(255, 255, 255, 0.5)' },
|
|
349
|
+
recordingButton: { backgroundColor: 'rgba(255, 59, 48, 0.3)', borderColor: '#FF3B30' },
|
|
350
|
+
recordingButtonInner: { backgroundColor: '#FF3B30' },
|
|
351
|
+
permissionMessage: { color: 'white', fontSize: 16, textAlign: 'center', margin: 20 },
|
|
352
|
+
errorMessage: { color: 'red', fontSize: 16, textAlign: 'center', margin: 20 },
|
|
353
|
+
bboxOverlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, pointerEvents: 'none' },
|
|
354
|
+
bbox: { position: 'absolute', borderStyle: 'solid', backgroundColor: 'transparent' },
|
|
355
|
+
analyzingContainer: { position: 'absolute', bottom: 100, alignSelf: 'center', backgroundColor: 'rgba(0, 0, 0, 0.7)', padding: 10, borderRadius: 10, zIndex: 1000 },
|
|
356
|
+
analyzingText: { color: 'white', fontSize: 16, fontWeight: 'bold' },
|
|
357
|
+
});
|