react-native-biometric-verifier 0.0.65 → 0.0.67

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.
@@ -7,15 +7,18 @@ import {
7
7
  ActivityIndicator,
8
8
  Animated,
9
9
  Dimensions,
10
+ Platform,
10
11
  } from 'react-native';
11
12
  import {
12
13
  Camera,
13
- getCameraDevice,
14
- useCodeScanner,
15
- useCameraFormat,
14
+ useCameraDevices,
15
+ useCameraPermission,
16
+ usePhotoOutput,
16
17
  } from 'react-native-vision-camera';
18
+ import { useBarcodeScannerOutput } from 'react-native-vision-camera-barcode-scanner';
17
19
  import { Global } from '../utils/Global';
18
20
  import { useFaceDetectionFrameProcessor } from '../hooks/useFaceDetectionFrameProcessor';
21
+ import DeviceBrightness from '@adrianso/react-native-device-brightness';
19
22
 
20
23
  const CaptureImageWithoutEdit = React.memo(
21
24
  ({
@@ -33,6 +36,7 @@ const CaptureImageWithoutEdit = React.memo(
33
36
  const [cameraInitialized, setCameraInitialized] = useState(false);
34
37
  const [currentCameraType, setCurrentCameraType] = useState(cameraType);
35
38
  const [isInitializing, setIsInitializing] = useState(true);
39
+ const [layout, setLayout] = useState({ width: 0, height: 0 });
36
40
 
37
41
  const [faces, setFaces] = useState([]);
38
42
  const [livenessStep, setLivenessStep] = useState(0);
@@ -50,6 +54,43 @@ const CaptureImageWithoutEdit = React.memo(
50
54
  const instructionAnim = useRef(new Animated.Value(1)).current;
51
55
  const liveIndicatorAnim = useRef(new Animated.Value(0)).current;
52
56
 
57
+ const originalBrightnessRef = useRef(null);
58
+
59
+ // Camera hooks - NEW API
60
+ const devices = useCameraDevices();
61
+ const { hasPermission, requestPermission } = useCameraPermission();
62
+ const photoOutput = usePhotoOutput();
63
+
64
+ useEffect(() => {
65
+ const adjustBrightness = async () => {
66
+ try {
67
+ const currentBr = await DeviceBrightness.getBrightnessLevel();
68
+ originalBrightnessRef.current = currentBr;
69
+ } catch (e) {
70
+ console.log('Error getting brightness:', e);
71
+ originalBrightnessRef.current = 0.6;
72
+ }
73
+
74
+ try {
75
+ await DeviceBrightness.setBrightnessLevel(1.0);
76
+ } catch (e) {
77
+ console.log('Error setting brightness:', e);
78
+ }
79
+ };
80
+
81
+ adjustBrightness();
82
+
83
+ return () => {
84
+ if (originalBrightnessRef.current !== null) {
85
+ try {
86
+ DeviceBrightness.setBrightnessLevel(originalBrightnessRef.current);
87
+ } catch (e) {
88
+ console.log('Error restoring brightness in cleanup:', e);
89
+ }
90
+ }
91
+ };
92
+ }, []);
93
+
53
94
  const resetCaptureState = useCallback(() => {
54
95
  captured.current = false;
55
96
  setFaces([]);
@@ -63,18 +104,20 @@ const CaptureImageWithoutEdit = React.memo(
63
104
  setHasSingleFace(false);
64
105
  }, []);
65
106
 
66
- const codeScanner = useCodeScanner({
67
- codeTypes: ['qr', 'ean-13'],
68
- onCodeScanned: (codes) => {
69
- try {
70
- if (showCodeScanner && codes && codes[0]?.value && !isLoading) {
71
- onCapture(codes[0].value);
72
- }
73
- } catch (error) {
74
- console.error('Error processing scanned code:', error);
107
+ // CodeScanner component - NEW API
108
+ const handleBarcodeScanned = useCallback((barcodes) => {
109
+ try {
110
+ if (showCodeScanner && barcodes && barcodes[0]?.rawValue && !isLoading) {
111
+ onCapture(barcodes[0].rawValue);
75
112
  }
76
- },
77
- });
113
+ } catch (error) {
114
+ console.error('Error processing scanned code:', error);
115
+ }
116
+ }, [showCodeScanner, isLoading, onCapture]);
117
+
118
+ const handleScannerError = useCallback((error) => {
119
+ console.error('Barcode scanner error:', error);
120
+ }, []);
78
121
 
79
122
  const onStableFaceDetected = useCallback(
80
123
  async (faceRect) => {
@@ -89,18 +132,20 @@ const CaptureImageWithoutEdit = React.memo(
89
132
  throw new Error('Camera ref not available');
90
133
  }
91
134
 
92
- const photo = await cameraRef.current.takePhoto({
93
- flash: 'off',
94
- qualityPrioritization: 'quality',
95
- enableShutterSound: false,
96
- skipMetadata: true,
97
- });
135
+ // Use photoOutput.capturePhotoToFile - NEW API
136
+ const photo = await photoOutput.capturePhotoToFile(
137
+ {
138
+ flashMode: 'off',
139
+ qualityPrioritization: 'quality',
140
+ },
141
+ {}
142
+ );
98
143
 
99
- if (!photo || !photo.path) {
144
+ if (!photo || !photo.filePath) {
100
145
  throw new Error('Failed to capture photo - no path returned');
101
146
  }
102
147
 
103
- const photopath = `file://${photo.path}`;
148
+ const photopath = `file://${photo.filePath}`;
104
149
  const fileName = photopath.substr(photopath.lastIndexOf('/') + 1);
105
150
  const photoData = {
106
151
  uri: photopath,
@@ -115,7 +160,7 @@ const CaptureImageWithoutEdit = React.memo(
115
160
  resetCaptureState();
116
161
  }
117
162
  },
118
- [onCapture, resetCaptureState]
163
+ [onCapture, resetCaptureState, photoOutput]
119
164
  );
120
165
 
121
166
  const onFacesUpdate = useCallback((payload) => {
@@ -125,7 +170,6 @@ const CaptureImageWithoutEdit = React.memo(
125
170
  setFaceCount(count);
126
171
  setProgress(progress);
127
172
 
128
- // Update anti-spoof related states
129
173
  if (antiSpoofState) {
130
174
  setIsFaceLive(antiSpoofState.isLive || false);
131
175
  setAntiSpoofConfidence(antiSpoofState.confidence || 0);
@@ -164,7 +208,6 @@ const CaptureImageWithoutEdit = React.memo(
164
208
  const onAntiSpoofUpdate = useCallback((result) => {
165
209
  if (!isMounted.current) return;
166
210
  try {
167
- // Animate live indicator when face becomes live
168
211
  if (result?.isLive && !isFaceLive) {
169
212
  Animated.spring(liveIndicatorAnim, {
170
213
  toValue: 1,
@@ -189,7 +232,7 @@ const CaptureImageWithoutEdit = React.memo(
189
232
  }, [isFaceLive, liveIndicatorAnim]);
190
233
 
191
234
  const {
192
- frameProcessor,
235
+ outputs: faceOutputs,
193
236
  forceResetCaptureState,
194
237
  updateShowCodeScanner,
195
238
  updateIsActive,
@@ -204,6 +247,9 @@ const CaptureImageWithoutEdit = React.memo(
204
247
  isActive: showCamera && cameraInitialized,
205
248
  livenessLevel: livenessLevel,
206
249
  antispooflevel,
250
+ cameraFacing: currentCameraType,
251
+ windowWidth: layout.width,
252
+ windowHeight: layout.height,
207
253
  });
208
254
 
209
255
  useEffect(() => {
@@ -214,68 +260,55 @@ const CaptureImageWithoutEdit = React.memo(
214
260
  }
215
261
  }, [capturedSV?.value]);
216
262
 
217
- const getPermission = useCallback(async () => {
263
+ // Camera permission and device selection - NEW API
264
+ const initializeCamera = useCallback(async () => {
218
265
  try {
219
266
  if (!isMounted.current) return;
220
267
 
221
268
  setIsInitializing(true);
222
269
  setShowCamera(false);
223
270
 
224
- const newCameraPermission = await Camera?.requestCameraPermission();
225
- if (newCameraPermission === 'granted') {
226
- let devices = await Camera?.getAvailableCameraDevices();
271
+ const permission = await requestPermission();
272
+ console.log('Camera permission:', permission);
227
273
 
228
- // Retry once after short delay if no devices found
229
- if (!devices || devices.length === 0) {
230
- await new Promise((resolve) => setTimeout(resolve, 300));
231
- devices = await Camera?.getAvailableCameraDevices();
232
- }
274
+ if (permission === true || permission === 'granted') {
275
+ // Find device by position
276
+ const device = devices.find((d) => d.position === currentCameraType) || devices[0];
233
277
 
234
- if (!devices || devices.length === 0) {
235
- throw new Error('No camera devices available');
278
+ if (!device) {
279
+ throw new Error(`No ${currentCameraType} camera available`);
236
280
  }
237
281
 
238
- const device = getCameraDevice(devices, currentCameraType);
239
- if (!device) throw new Error(`No ${currentCameraType} camera available`);
240
-
282
+ console.log('Selected device:', device);
241
283
  setCameraDevice(device);
242
284
  setShowCamera(true);
243
285
  } else {
244
286
  console.warn('Camera permission not granted');
287
+ setShowCamera(false);
245
288
  }
246
289
  } catch (error) {
247
- console.error('Camera permission error:', error);
290
+ console.error('Camera initialization error:', error);
248
291
  setShowCamera(false);
249
292
  } finally {
250
293
  if (isMounted.current) {
251
294
  setIsInitializing(false);
252
295
  }
253
296
  }
254
- }, [currentCameraType]);
255
-
256
- const initializeCamera = useCallback(async () => {
257
- await getPermission();
258
- }, [getPermission]);
297
+ }, [currentCameraType, devices, requestPermission]);
259
298
 
260
299
  useEffect(() => {
261
300
  isMounted.current = true;
262
301
 
263
- const initOnMount = async () => {
264
- try {
265
- await initializeCamera();
266
- } catch (error) {
267
- console.error('Failed to initialize camera on mount:', error);
268
- }
269
- };
270
-
271
- initOnMount();
302
+ if (devices && devices.length > 0) {
303
+ initializeCamera();
304
+ }
272
305
 
273
306
  return () => {
274
307
  isMounted.current = false;
275
308
  setShowCamera(false);
276
309
  forceResetCaptureState();
277
310
  };
278
- }, [initializeCamera, forceResetCaptureState]);
311
+ }, [devices, initializeCamera, forceResetCaptureState]);
279
312
 
280
313
  useEffect(() => {
281
314
  updateIsActive(showCamera && cameraInitialized);
@@ -284,13 +317,11 @@ const CaptureImageWithoutEdit = React.memo(
284
317
  useEffect(() => {
285
318
  if (cameraType !== currentCameraType) {
286
319
  setCurrentCameraType(cameraType);
287
- initializeCamera();
320
+ if (devices && devices.length > 0) {
321
+ initializeCamera();
322
+ }
288
323
  }
289
- }, [cameraType, currentCameraType, initializeCamera]);
290
-
291
- const format = useCameraFormat(cameraDevice, [
292
- { fps: 30 },
293
- ]);
324
+ }, [cameraType, currentCameraType, initializeCamera, devices]);
294
325
 
295
326
  useEffect(() => {
296
327
  try {
@@ -315,11 +346,13 @@ const CaptureImageWithoutEdit = React.memo(
315
346
  setCameraInitialized(false);
316
347
  forceResetCaptureState();
317
348
  resetCaptureState();
318
- await initializeCamera();
349
+ if (devices && devices.length > 0) {
350
+ await initializeCamera();
351
+ }
319
352
  } catch (error) {
320
353
  console.error('Retry failed:', error);
321
354
  }
322
- }, [initializeCamera, resetCaptureState, forceResetCaptureState]);
355
+ }, [initializeCamera, resetCaptureState, forceResetCaptureState, devices]);
323
356
 
324
357
  const getInstruction = useCallback(() => {
325
358
  if (faceCount > 1) {
@@ -424,35 +457,95 @@ const CaptureImageWithoutEdit = React.memo(
424
457
 
425
458
  const stepConfig = getStepConfig();
426
459
 
460
+ const getOverlayHeights = () => {
461
+ const containerHeight = layout.height || 400;
462
+ const overlayHeight = containerHeight * 0.15;
463
+ return { overlayHeight };
464
+ };
465
+
466
+ const { overlayHeight } = getOverlayHeights();
467
+ const confidencePercent = Math.round(
468
+ Math.max(0, Math.min(1, Number(antiSpoofConfidence) || 0)) * 100
469
+ );
470
+ const confidenceColor = confidencePercent >= 70
471
+ ? Global.AppTheme.success
472
+ : confidencePercent >= 40
473
+ ? Global.AppTheme.warning
474
+ : Global.AppTheme.error;
475
+
476
+ const scannerOutput = useBarcodeScannerOutput({
477
+ barcodeFormats: [
478
+ 'qr-code',
479
+ 'ean-13',
480
+ 'code-128',
481
+ 'code-39',
482
+ 'code-93',
483
+ ],
484
+ onBarcodeScanned: handleBarcodeScanned,
485
+ onError: handleScannerError,
486
+ });
487
+
488
+ const cameraOutputs = showCodeScanner
489
+ ? [photoOutput, scannerOutput]
490
+ : [photoOutput, ...faceOutputs];
491
+
427
492
  return (
428
493
  <View style={styles.container}>
429
- <View style={styles.cameraContainer}>
494
+ <View
495
+ style={styles.cameraContainer}
496
+ onLayout={(event) => {
497
+ const { width, height } = event.nativeEvent.layout;
498
+ setLayout({ width, height });
499
+ }}
500
+ >
430
501
  {!isInitializing && showCamera && cameraDevice ? (
431
- <Camera
432
- ref={cameraRef}
433
- style={styles.camera}
434
- device={cameraDevice}
435
- isActive={cameraInitialized && showCamera && !isLoading}
436
- photo={true}
437
- format={cameraDevice ? format : undefined}
438
- codeScanner={showCodeScanner && cameraInitialized ? codeScanner : undefined}
439
- enableZoomGesture={false}
440
- lowLightBoost={cameraDevice?.supportsLowLightBoost}
441
- frameProcessor={
442
- !showCodeScanner && cameraInitialized ? frameProcessor : undefined
443
- }
444
- frameProcessorFps={frameProcessorFps}
445
- onInitialized={() => {
446
- setCameraInitialized(true);
447
- }}
448
- onError={(error) => {
449
- console.error('Camera error:', error);
450
- }}
451
- exposure={0}
452
- pixelFormat="yuv"
453
- preset="photo"
454
- orientation="portrait"
455
- />
502
+ <View style={StyleSheet.absoluteFill}>
503
+ <Camera
504
+ ref={cameraRef}
505
+ style={styles.camera}
506
+ device={cameraDevice}
507
+ isActive={showCamera && !isLoading}
508
+ outputs={cameraOutputs}
509
+ enableNativeZoomGesture={false}
510
+ onStarted={() => {
511
+ setCameraInitialized(true);
512
+ }}
513
+ onStopped={() => {
514
+ setCameraInitialized(false);
515
+ }}
516
+ onError={(error) => {
517
+ console.error('Camera error:', error);
518
+ }}
519
+ exposure={1.2}
520
+ />
521
+ {currentCameraType === 'front' && (
522
+ <View style={StyleSheet.absoluteFillObject} pointerEvents="none">
523
+ {/* Top Translucent White Overlay (Header) */}
524
+ <View style={{
525
+ position: 'absolute',
526
+ top: 0,
527
+ left: 0,
528
+ right: 0,
529
+ height: overlayHeight,
530
+ backgroundColor: 'rgba(255, 255, 255, 0.95)',
531
+ borderBottomWidth: 1,
532
+ borderBottomColor: 'rgba(255, 255, 255, 0.8)',
533
+ }} />
534
+
535
+ {/* Bottom Translucent White Overlay (Footer) */}
536
+ <View style={{
537
+ position: 'absolute',
538
+ bottom: 0,
539
+ left: 0,
540
+ right: 0,
541
+ height: overlayHeight,
542
+ backgroundColor: 'rgba(255, 255, 255, 0.95)',
543
+ borderTopWidth: 1,
544
+ borderTopColor: 'rgba(255, 255, 255, 0.8)',
545
+ }} />
546
+ </View>
547
+ )}
548
+ </View>
456
549
  ) : (
457
550
  <View style={styles.placeholderContainer}>
458
551
  {isInitializing && (
@@ -545,19 +638,15 @@ const CaptureImageWithoutEdit = React.memo(
545
638
  {isFaceCentered && (
546
639
  <View style={styles.confidenceContainer}>
547
640
  <Text style={styles.confidenceText}>
548
- Confidence: {Math.round(antiSpoofConfidence * 100)}%
641
+ Confidence: {confidencePercent}%
549
642
  </Text>
550
643
  <View style={styles.confidenceBar}>
551
644
  <View
552
645
  style={[
553
646
  styles.confidenceProgress,
554
647
  {
555
- width: `${antiSpoofConfidence * 100}%`,
556
- backgroundColor: antiSpoofConfidence * 100 > 40
557
- ? Global.AppTheme.success
558
- : antiSpoofConfidence * 100 > 20
559
- ? Global.AppTheme.warning
560
- : Global.AppTheme.error
648
+ width: `${confidencePercent}%`,
649
+ backgroundColor: confidenceColor,
561
650
  }
562
651
  ]}
563
652
  />
@@ -670,7 +759,6 @@ const styles = StyleSheet.create({
670
759
  color: 'white',
671
760
  fontWeight: 'bold',
672
761
  },
673
- // Live Indicator
674
762
  liveIndicator: {
675
763
  position: 'absolute',
676
764
  top: 20,
@@ -704,7 +792,6 @@ const styles = StyleSheet.create({
704
792
  fontSize: 12,
705
793
  fontWeight: 'bold',
706
794
  },
707
- // Status Overview
708
795
  statusOverview: {
709
796
  position: 'absolute',
710
797
  bottom: 20,
@@ -758,7 +845,6 @@ const styles = StyleSheet.create({
758
845
  height: '100%',
759
846
  borderRadius: 2,
760
847
  },
761
- // Existing styles
762
848
  blinkProgressContainer: {
763
849
  flexDirection: 'row',
764
850
  marginVertical: 8,