react-native-biometric-verifier 0.0.21 → 0.0.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-biometric-verifier",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "description": "A React Native module for biometric verification with face recognition and QR code scanning",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -5,8 +5,6 @@ import {
5
5
  Text,
6
6
  StyleSheet,
7
7
  ActivityIndicator,
8
- AppState,
9
- BackHandler,
10
8
  Animated,
11
9
  } from 'react-native';
12
10
  import Icon from 'react-native-vector-icons/MaterialIcons';
@@ -15,7 +13,6 @@ import {
15
13
  getCameraDevice,
16
14
  useCodeScanner,
17
15
  useCameraFormat,
18
- CameraRuntimeError,
19
16
  } from 'react-native-vision-camera';
20
17
  import { Global } from '../utils/Global';
21
18
  import { useFaceDetectionFrameProcessor } from '../hooks/useFaceDetectionFrameProcessor';
@@ -27,7 +24,7 @@ const CaptureImageWithoutEdit = React.memo(
27
24
  showCodeScanner = false,
28
25
  isLoading = false,
29
26
  frameProcessorFps = 1,
30
- livenessLevel = 1, // 0=no liveness, 1=blink only, 2=face turn only, 3=both
27
+ livenessLevel = 1,
31
28
  }) => {
32
29
  const cameraRef = useRef(null);
33
30
  const [cameraDevice, setCameraDevice] = useState(null);
@@ -35,6 +32,7 @@ const CaptureImageWithoutEdit = React.memo(
35
32
  const [showCamera, setShowCamera] = useState(false);
36
33
  const [cameraInitialized, setCameraInitialized] = useState(false);
37
34
  const [currentCameraType, setCurrentCameraType] = useState(cameraType);
35
+ const [isInitializing, setIsInitializing] = useState(true);
38
36
 
39
37
  const [faces, setFaces] = useState([]);
40
38
  const [cameraError, setCameraError] = useState(null);
@@ -44,13 +42,10 @@ const CaptureImageWithoutEdit = React.memo(
44
42
  const [faceCount, setFaceCount] = useState(0);
45
43
 
46
44
  const captured = useRef(false);
47
- const appState = useRef(AppState.currentState);
48
45
  const isMounted = useRef(true);
49
46
 
50
- // Animation values
51
47
  const instructionAnim = useRef(new Animated.Value(1)).current;
52
48
 
53
- // Reset capture state
54
49
  const resetCaptureState = useCallback(() => {
55
50
  captured.current = false;
56
51
  setFaces([]);
@@ -60,7 +55,6 @@ const CaptureImageWithoutEdit = React.memo(
60
55
  setFaceCount(0);
61
56
  }, []);
62
57
 
63
- // Code scanner
64
58
  const codeScanner = useCodeScanner({
65
59
  codeTypes: ['qr', 'ean-13'],
66
60
  onCodeScanned: (codes) => {
@@ -76,46 +70,48 @@ const CaptureImageWithoutEdit = React.memo(
76
70
  },
77
71
  });
78
72
 
79
- // Callbacks for face detection events
80
- const onStableFaceDetected = useCallback(async (faceRect) => {
81
- if (!isMounted.current) return;
82
- if (captured.current) return;
73
+ const onStableFaceDetected = useCallback(
74
+ async (faceRect) => {
75
+ if (!isMounted.current) return;
76
+ if (captured.current) return;
83
77
 
84
- captured.current = true;
85
- setFaces([faceRect]);
78
+ captured.current = true;
79
+ setFaces([faceRect]);
86
80
 
87
- try {
88
- if (!cameraRef.current) {
89
- throw new Error('Camera ref not available');
90
- }
81
+ try {
82
+ if (!cameraRef.current) {
83
+ throw new Error('Camera ref not available');
84
+ }
85
+
86
+ const photo = await cameraRef.current.takePhoto({
87
+ flash: 'off',
88
+ qualityPrioritization: 'quality',
89
+ enableShutterSound: false,
90
+ skipMetadata: true,
91
+ });
91
92
 
92
- const photo = await cameraRef.current.takePhoto({
93
- flash: 'off',
94
- qualityPrioritization: 'quality',
95
- enableShutterSound: false,
96
- skipMetadata: true,
97
- });
93
+ if (!photo || !photo.path) {
94
+ throw new Error('Failed to capture photo - no path returned');
95
+ }
98
96
 
99
- if (!photo || !photo.path) {
100
- throw new Error('Failed to capture photo - no path returned');
97
+ const photopath = `file://${photo.path}`;
98
+ const fileName = photopath.substr(photopath.lastIndexOf('/') + 1);
99
+ const photoData = {
100
+ uri: photopath,
101
+ filename: fileName,
102
+ filetype: 'image/jpeg',
103
+ };
104
+
105
+ onCapture(photoData, faceRect);
106
+ } catch (e) {
107
+ console.error('Capture error:', e);
108
+ captured.current = false;
109
+ resetCaptureState();
110
+ setCameraError('Failed to capture image. Please try again.');
101
111
  }
102
-
103
- const photopath = `file://${photo.path}`;
104
- const fileName = photopath.substr(photopath.lastIndexOf('/') + 1);
105
- const photoData = {
106
- uri: photopath,
107
- filename: fileName,
108
- filetype: 'image/jpeg',
109
- };
110
-
111
- onCapture(photoData, faceRect);
112
- } catch (e) {
113
- console.error('Capture error:', e);
114
- captured.current = false;
115
- resetCaptureState();
116
- setCameraError('Failed to capture image. Please try again.');
117
- }
118
- }, [onCapture, resetCaptureState]);
112
+ },
113
+ [onCapture, resetCaptureState]
114
+ );
119
115
 
120
116
  const onFacesUpdate = useCallback((payload) => {
121
117
  if (!isMounted.current) return;
@@ -125,7 +121,7 @@ const CaptureImageWithoutEdit = React.memo(
125
121
  setProgress(progress);
126
122
 
127
123
  if (count === 1) {
128
- setFaces(prev => {
124
+ setFaces((prev) => {
129
125
  if (prev.length === 1) return prev;
130
126
  return [{ x: 0, y: 0, width: 0, height: 0 }];
131
127
  });
@@ -137,20 +133,21 @@ const CaptureImageWithoutEdit = React.memo(
137
133
  }
138
134
  }, []);
139
135
 
140
- const onLivenessUpdate = useCallback((step, extra) => {
141
- setLivenessStep(step);
142
- if (extra?.blinkCount !== undefined) setBlinkCount(extra.blinkCount);
143
-
144
- // Animate instruction change
145
- instructionAnim.setValue(0);
146
- Animated.timing(instructionAnim, {
147
- toValue: 1,
148
- duration: 300,
149
- useNativeDriver: true,
150
- }).start();
151
- }, [instructionAnim]);
136
+ const onLivenessUpdate = useCallback(
137
+ (step, extra) => {
138
+ setLivenessStep(step);
139
+ if (extra?.blinkCount !== undefined) setBlinkCount(extra.blinkCount);
140
+
141
+ instructionAnim.setValue(0);
142
+ Animated.timing(instructionAnim, {
143
+ toValue: 1,
144
+ duration: 300,
145
+ useNativeDriver: true,
146
+ }).start();
147
+ },
148
+ [instructionAnim]
149
+ );
152
150
 
153
- // Use the face detection frame processor hook
154
151
  const {
155
152
  frameProcessor,
156
153
  forceResetCaptureState,
@@ -164,10 +161,9 @@ const CaptureImageWithoutEdit = React.memo(
164
161
  showCodeScanner,
165
162
  isLoading,
166
163
  isActive: showCamera && cameraInitialized,
167
- livenessLevel, // Pass liveness level to hook
164
+ livenessLevel,
168
165
  });
169
166
 
170
- // Sync captured state with the shared value
171
167
  useEffect(() => {
172
168
  if (capturedSV?.value && !captured.current) {
173
169
  captured.current = true;
@@ -176,43 +172,63 @@ const CaptureImageWithoutEdit = React.memo(
176
172
  }
177
173
  }, [capturedSV?.value]);
178
174
 
179
- // Get Camera Permission - from original code
180
175
  const getPermission = useCallback(async () => {
181
176
  try {
182
177
  if (!isMounted.current) return;
183
178
 
179
+ setIsInitializing(true);
184
180
  setCameraError(null);
181
+ setShowCamera(false);
182
+
185
183
  const newCameraPermission = await Camera.requestCameraPermission();
186
184
  setCameraPermission(newCameraPermission);
187
185
 
188
186
  if (newCameraPermission === 'granted') {
189
187
  const devices = await Camera.getAvailableCameraDevices();
190
- setCameraDevice(getCameraDevice(devices, currentCameraType));
188
+
189
+ if (!devices || devices.length === 0) {
190
+ throw new Error('No camera devices available');
191
+ }
192
+
193
+ const device = getCameraDevice(devices, currentCameraType);
194
+
195
+ if (!device) {
196
+ throw new Error(`No ${currentCameraType} camera available`);
197
+ }
198
+
199
+ setCameraDevice(device);
191
200
  setShowCamera(true);
201
+ console.log('Camera device set successfully');
192
202
  } else {
193
203
  setCameraError('Camera permission denied');
194
204
  }
195
205
  } catch (error) {
196
206
  console.error('Camera permission error:', error);
197
- setCameraError('Failed to get camera permission');
207
+ setCameraError(error.message || 'Failed to get camera permission');
208
+ setShowCamera(false);
209
+ } finally {
210
+ if (isMounted.current) {
211
+ setIsInitializing(false);
212
+ }
198
213
  }
199
214
  }, [currentCameraType]);
200
215
 
201
- // Simplified camera initialization
202
216
  const initializeCamera = useCallback(async () => {
203
- try {
204
- if (!isMounted.current) return;
205
- await getPermission();
206
- } catch (error) {
207
- console.error('Camera initialization failed:', error);
208
- setCameraError('Failed to initialize camera');
209
- }
217
+ await getPermission();
210
218
  }, [getPermission]);
211
219
 
212
- // Effects: lifecycle, camera init, app state, cleanup
213
220
  useEffect(() => {
214
221
  isMounted.current = true;
215
- initializeCamera();
222
+
223
+ const initOnMount = async () => {
224
+ try {
225
+ await initializeCamera();
226
+ } catch (error) {
227
+ console.error('Failed to initialize camera on mount:', error);
228
+ }
229
+ };
230
+
231
+ initOnMount();
216
232
 
217
233
  return () => {
218
234
  isMounted.current = false;
@@ -221,105 +237,22 @@ const CaptureImageWithoutEdit = React.memo(
221
237
  };
222
238
  }, [initializeCamera, forceResetCaptureState]);
223
239
 
224
- // Update frame processor active state when camera state changes
225
240
  useEffect(() => {
226
241
  updateIsActive(showCamera && cameraInitialized);
227
242
  }, [showCamera, cameraInitialized, updateIsActive]);
228
243
 
229
- // Handle camera type changes from parent
230
244
  useEffect(() => {
231
245
  if (cameraType !== currentCameraType) {
232
246
  setCurrentCameraType(cameraType);
233
- // Reinitialize camera with new type
234
247
  initializeCamera();
235
248
  }
236
249
  }, [cameraType, currentCameraType, initializeCamera]);
237
250
 
238
- // app state change
239
- useEffect(() => {
240
- const subscription = AppState.addEventListener('change', nextAppState => {
241
- try {
242
- if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
243
- if (cameraPermission === 'granted') {
244
- setShowCamera(true);
245
- if (captured.current) {
246
- forceResetCaptureState();
247
- resetCaptureState();
248
- }
249
- }
250
- } else if (nextAppState.match(/inactive|background/)) {
251
- setShowCamera(false);
252
- }
253
- appState.current = nextAppState;
254
- } catch (error) {
255
- console.error('Error handling app state change:', error);
256
- }
257
- });
258
-
259
- return () => {
260
- try {
261
- subscription.remove();
262
- } catch (error) {
263
- console.error('Error removing app state listener:', error);
264
- }
265
- };
266
- }, [cameraPermission, forceResetCaptureState, resetCaptureState]);
267
-
268
- // android back handler
269
- useEffect(() => {
270
- const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
271
- return true;
272
- });
273
-
274
- return () => {
275
- try {
276
- backHandler.remove();
277
- } catch (error) {
278
- console.error('Error removing back handler:', error);
279
- }
280
- };
281
- }, []);
282
-
283
- // handle camera errors
284
- const handleCameraError = useCallback((error) => {
285
- console.error('Camera error:', error);
286
- let errorMessage = 'Camera error occurred';
287
-
288
- if (error instanceof CameraRuntimeError) {
289
- switch (error.code) {
290
- case 'session/configuration-failed':
291
- errorMessage = 'Camera configuration failed. Please try again.';
292
- break;
293
- case 'device/not-available':
294
- errorMessage = 'Camera not available';
295
- break;
296
- case 'permission/microphone-not-granted':
297
- errorMessage = 'Camera permission required';
298
- break;
299
- default:
300
- errorMessage = `Camera error: ${error.message}`;
301
- }
302
- }
303
-
304
- setCameraError(errorMessage);
305
- setShowCamera(false);
306
- forceResetCaptureState();
307
- resetCaptureState();
308
- }, [forceResetCaptureState, resetCaptureState]);
309
-
310
- const handleCameraInitialized = useCallback(() => {
311
- setCameraInitialized(true);
312
- setCameraError(null);
313
- updateIsActive(true);
314
- }, [updateIsActive]);
315
-
316
- // format selection
317
251
  const format = useCameraFormat(cameraDevice, [
318
252
  { videoResolution: { width: 640, height: 640 } },
319
253
  { fps: 30 },
320
254
  ]);
321
255
 
322
- // keep worklet's showCodeScanner flag in sync
323
256
  useEffect(() => {
324
257
  try {
325
258
  updateShowCodeScanner(!!showCodeScanner);
@@ -330,24 +263,27 @@ const CaptureImageWithoutEdit = React.memo(
330
263
  } catch (error) {
331
264
  console.error('Error updating code scanner:', error);
332
265
  }
333
- }, [showCodeScanner, updateShowCodeScanner, forceResetCaptureState, resetCaptureState]);
334
-
335
- // Retry camera initialization
336
- const handleRetry = useCallback(() => {
337
- setCameraError(null);
338
- setShowCamera(false);
339
- forceResetCaptureState();
340
- resetCaptureState();
341
- setTimeout(() => {
342
- if (isMounted.current) {
343
- initializeCamera();
344
- }
345
- }, 500);
346
- }, [initializeCamera, resetCaptureState, forceResetCaptureState]);
266
+ }, [
267
+ showCodeScanner,
268
+ updateShowCodeScanner,
269
+ forceResetCaptureState,
270
+ resetCaptureState,
271
+ ]);
347
272
 
348
-
273
+ const handleRetry = useCallback(async () => {
274
+ try {
275
+ setCameraError(null);
276
+ setShowCamera(false);
277
+ setCameraInitialized(false);
278
+ forceResetCaptureState();
279
+ resetCaptureState();
280
+ await initializeCamera();
281
+ } catch (error) {
282
+ console.error('Retry failed:', error);
283
+ setCameraError('Failed to retry camera initialization');
284
+ }
285
+ }, [initializeCamera, resetCaptureState, forceResetCaptureState]);
349
286
 
350
- // Helper text for user guidance based on liveness level
351
287
  const getInstruction = useCallback(() => {
352
288
  if (faceCount > 1) {
353
289
  return 'Multiple faces detected';
@@ -405,19 +341,24 @@ const CaptureImageWithoutEdit = React.memo(
405
341
  }
406
342
 
407
343
  return 'Align your face in frame';
408
- }, [livenessLevel, livenessStep, blinkCount, faces.length, progress, faceCount]);
409
-
344
+ }, [
345
+ livenessLevel,
346
+ livenessStep,
347
+ blinkCount,
348
+ faces.length,
349
+ progress,
350
+ faceCount,
351
+ ]);
410
352
 
411
- // Get step configuration based on liveness level
412
353
  const getStepConfig = useCallback(() => {
413
354
  switch (livenessLevel) {
414
- case 0: // No liveness
355
+ case 0:
415
356
  return { totalSteps: 0, showSteps: false };
416
- case 1: // Blink only
357
+ case 1:
417
358
  return { totalSteps: 1, showSteps: true };
418
- case 2: // Face turn only
359
+ case 2:
419
360
  return { totalSteps: 2, showSteps: true };
420
- case 3: // Both
361
+ case 3:
421
362
  return { totalSteps: 3, showSteps: true };
422
363
  default:
423
364
  return { totalSteps: 0, showSteps: false };
@@ -426,7 +367,6 @@ const CaptureImageWithoutEdit = React.memo(
426
367
 
427
368
  const stepConfig = getStepConfig();
428
369
 
429
- // Get instruction text style based on content
430
370
  const getInstructionStyle = useCallback(() => {
431
371
  if (faceCount > 1) {
432
372
  return [styles.instructionText, { color: Global.AppTheme.error }];
@@ -434,84 +374,38 @@ const CaptureImageWithoutEdit = React.memo(
434
374
  return styles.instructionText;
435
375
  }, [faceCount]);
436
376
 
437
- // camera placeholder / UI
438
- const renderCameraPlaceholder = () => (
439
- <View style={styles.cameraContainer}>
440
- {cameraError ? (
441
- <View style={styles.errorContainer}>
442
- <Icon name="error-outline" size={40} color={Global.AppTheme.error} />
443
- <Text style={styles.errorText}>{cameraError}</Text>
444
- <TouchableOpacity
445
- style={styles.retryButton}
446
- onPress={handleRetry}
447
- accessibilityLabel="Retry camera initialization"
448
- >
449
- <Text style={styles.retryButtonText}>Retry</Text>
450
- </TouchableOpacity>
451
- </View>
452
- ) : cameraPermission === 'denied' || cameraPermission === 'restricted' ? (
453
- <View style={styles.placeholderContainer}>
454
- <Icon name="camera-off" size={40} color={Global.AppTheme.light} />
455
- <Text style={styles.placeholderText}>
456
- Camera permission required. Please enable in settings.
457
- </Text>
458
- <TouchableOpacity
459
- style={styles.retryButton}
460
- onPress={handleRetry}
461
- accessibilityLabel="Request camera permission again"
462
- >
463
- <Text style={styles.retryButtonText}>Request Again</Text>
464
- </TouchableOpacity>
465
- </View>
466
- ) : cameraPermission === 'not-determined' ? (
467
- <View style={styles.placeholderContainer}>
468
- <ActivityIndicator size="large" color={Global.AppTheme.primary} />
469
- <Text style={styles.placeholderText}>
470
- Requesting camera access...
471
- </Text>
472
- </View>
473
- ) : !cameraDevice ? (
474
- <View style={styles.placeholderContainer}>
475
- <Icon name="camera-alt" size={40} color={Global.AppTheme.light} />
476
- <Text style={styles.placeholderText}>Camera not available</Text>
477
- <TouchableOpacity
478
- style={styles.retryButton}
479
- onPress={handleRetry}
480
- accessibilityLabel="Retry camera initialization"
481
- >
482
- <Text style={styles.retryButtonText}>Retry</Text>
483
- </TouchableOpacity>
484
- </View>
485
- ) : (
486
- <View style={styles.placeholderContainer}>
487
- <ActivityIndicator size="large" color={Global.AppTheme.primary} />
488
- <Text style={styles.placeholderText}>Initializing camera...</Text>
489
- </View>
490
- )}
491
- </View>
492
- );
493
-
494
- const shouldRenderCamera = showCamera &&
495
- cameraPermission === 'granted' &&
496
- cameraDevice &&
497
- !cameraError;
498
-
499
377
  return (
500
378
  <View style={styles.container}>
501
379
  <View style={styles.cameraContainer}>
502
- {shouldRenderCamera ? (
380
+ {isInitializing && (
381
+ <View style={styles.loadingContainer}>
382
+ <ActivityIndicator size="large" color={Global.AppTheme.primary} />
383
+ <Text style={styles.placeholderText}>Initializing camera...</Text>
384
+ </View>
385
+ )}
386
+
387
+ {!isInitializing && showCamera && cameraDevice ? (
503
388
  <Camera
504
389
  ref={cameraRef}
505
390
  style={styles.camera}
506
391
  device={cameraDevice}
507
- isActive={showCamera && !isLoading && appState.current === 'active'}
392
+ isActive={showCamera && !isLoading}
508
393
  photo={true}
509
394
  format={format}
510
395
  codeScanner={showCodeScanner ? codeScanner : undefined}
511
- frameProcessor={!showCodeScanner && cameraInitialized ? frameProcessor : undefined}
396
+ frameProcessor={
397
+ !showCodeScanner && cameraInitialized ? frameProcessor : undefined
398
+ }
512
399
  frameProcessorFps={frameProcessorFps}
513
- onInitialized={handleCameraInitialized}
514
- onError={handleCameraError}
400
+ onInitialized={() => {
401
+ console.log('Camera initialized successfully');
402
+ setCameraInitialized(true);
403
+ }}
404
+ onError={(error) => {
405
+ console.log('Camera error:', error);
406
+ setCameraError('Camera error occurred');
407
+ setCameraInitialized(false);
408
+ }}
515
409
  enableZoomGesture={false}
516
410
  exposure={0}
517
411
  pixelFormat="yuv"
@@ -519,13 +413,23 @@ const CaptureImageWithoutEdit = React.memo(
519
413
  orientation="portrait"
520
414
  />
521
415
  ) : (
522
- renderCameraPlaceholder()
416
+ <View style={styles.placeholderContainer}>
417
+ <Icon name="camera-alt" size={40} color={Global.AppTheme.light} />
418
+ <Text style={styles.placeholderText}>
419
+ {cameraError || 'Camera not available'}
420
+ </Text>
421
+ <TouchableOpacity
422
+ style={styles.retryButton}
423
+ onPress={handleRetry}
424
+ accessibilityLabel="Retry camera initialization"
425
+ >
426
+ <Text style={styles.retryButtonText}>Retry</Text>
427
+ </TouchableOpacity>
428
+ </View>
523
429
  )}
524
430
 
525
- {/* Liveness Detection UI */}
526
- {!showCodeScanner && shouldRenderCamera && livenessLevel > 0 && (
431
+ {!showCodeScanner && showCamera && cameraDevice && livenessLevel > 0 && (
527
432
  <View style={styles.livenessContainer}>
528
- {/* Instructions with animation */}
529
433
  <Animated.View
530
434
  style={[
531
435
  styles.instructionContainer,
@@ -545,45 +449,54 @@ const CaptureImageWithoutEdit = React.memo(
545
449
  <Text style={getInstructionStyle()}>{getInstruction()}</Text>
546
450
  </Animated.View>
547
451
 
548
- {/* Blink Progress for blink steps */}
549
- {(livenessLevel === 1 || livenessLevel === 3) && livenessStep === (livenessLevel === 1 ? 1 : 3) && (
550
- <View style={styles.blinkProgressContainer}>
551
- {[1, 2, 3].map((i) => (
552
- <View
553
- key={i}
554
- style={[
555
- styles.blinkDot,
556
- blinkCount >= i && styles.blinkDotActive,
557
- ]}
558
- />
559
- ))}
560
- </View>
561
- )}
452
+ {(livenessLevel === 1 || livenessLevel === 3) &&
453
+ livenessStep === (livenessLevel === 1 ? 1 : 3) && (
454
+ <View style={styles.blinkProgressContainer}>
455
+ {[1, 2, 3].map((i) => (
456
+ <View
457
+ key={i}
458
+ style={[
459
+ styles.blinkDot,
460
+ blinkCount >= i && styles.blinkDotActive,
461
+ ]}
462
+ />
463
+ ))}
464
+ </View>
465
+ )}
562
466
 
563
- {/* Step Indicators - only show for liveness levels 1, 2, 3 */}
564
467
  {stepConfig.showSteps && faceCount <= 1 && (
565
468
  <>
566
469
  <View style={styles.stepsContainer}>
567
- {Array.from({ length: stepConfig.totalSteps + 1 }).map((_, step) => (
568
- <React.Fragment key={step}>
569
- <View style={[
570
- styles.stepIndicator,
571
- livenessStep > step ? styles.stepCompleted :
572
- livenessStep === step ? styles.stepCurrent : styles.stepPending
573
- ]}>
574
- <Text style={styles.stepText}>{step + 1}</Text>
575
- </View>
576
- {step < stepConfig.totalSteps && (
577
- <View style={[
578
- styles.stepConnector,
579
- livenessStep > step ? styles.connectorCompleted : {}
580
- ]} />
581
- )}
582
- </React.Fragment>
583
- ))}
470
+ {Array.from({ length: stepConfig.totalSteps + 1 }).map(
471
+ (_, step) => (
472
+ <React.Fragment key={step}>
473
+ <View
474
+ style={[
475
+ styles.stepIndicator,
476
+ livenessStep > step
477
+ ? styles.stepCompleted
478
+ : livenessStep === step
479
+ ? styles.stepCurrent
480
+ : styles.stepPending,
481
+ ]}
482
+ >
483
+ <Text style={styles.stepText}>{step + 1}</Text>
484
+ </View>
485
+ {step < stepConfig.totalSteps && (
486
+ <View
487
+ style={[
488
+ styles.stepConnector,
489
+ livenessStep > step
490
+ ? styles.connectorCompleted
491
+ : {},
492
+ ]}
493
+ />
494
+ )}
495
+ </React.Fragment>
496
+ )
497
+ )}
584
498
  </View>
585
499
 
586
- {/* Step Labels */}
587
500
  <View style={styles.stepLabelsContainer}>
588
501
  {livenessLevel === 1 && (
589
502
  <>
@@ -612,8 +525,7 @@ const CaptureImageWithoutEdit = React.memo(
612
525
  </View>
613
526
  )}
614
527
 
615
- {/* Simple face detection UI for liveness level 0 */}
616
- {!showCodeScanner && shouldRenderCamera && livenessLevel === 0 && (
528
+ {!showCodeScanner && showCamera && cameraDevice && livenessLevel === 0 && (
617
529
  <View style={styles.livenessContainer}>
618
530
  <Animated.View
619
531
  style={[
@@ -626,14 +538,13 @@ const CaptureImageWithoutEdit = React.memo(
626
538
  <Text style={getInstructionStyle()}>{getInstruction()}</Text>
627
539
  </Animated.View>
628
540
 
629
- {/* Progress bar for stability - only show when single face detected */}
630
541
  {faceCount === 1 && (
631
542
  <View style={styles.stabilityContainer}>
632
543
  <View style={styles.stabilityBar}>
633
544
  <View
634
545
  style={[
635
546
  styles.stabilityProgress,
636
- { width: `${progress}%` }
547
+ { width: `${progress}%` },
637
548
  ]}
638
549
  />
639
550
  </View>
@@ -666,41 +577,23 @@ const styles = StyleSheet.create({
666
577
  flex: 1,
667
578
  width: '100%',
668
579
  },
669
- controlsContainer: {
670
- position: 'absolute',
671
- bottom: 20,
672
- right: 20,
673
- alignItems: 'center',
674
- },
675
- cameraSwitchButton: {
676
- backgroundColor: 'rgba(0,0,0,0.7)',
677
- padding: 12,
678
- borderRadius: 30,
679
- alignItems: 'center',
680
- justifyContent: 'center',
681
- },
682
- controlText: {
683
- color: 'white',
684
- fontSize: 10,
685
- marginTop: 4,
686
- },
687
- placeholderContainer: {
580
+ loadingContainer: {
688
581
  flex: 1,
689
582
  justifyContent: 'center',
690
583
  alignItems: 'center',
691
584
  padding: 20,
692
585
  },
693
- errorContainer: {
586
+ placeholderContainer: {
694
587
  flex: 1,
695
588
  justifyContent: 'center',
696
589
  alignItems: 'center',
697
590
  padding: 20,
698
591
  },
699
- errorText: {
700
- color: Global.AppTheme.error,
592
+ placeholderText: {
593
+ color: Global.AppTheme.light,
701
594
  fontSize: 16,
702
595
  textAlign: 'center',
703
- marginVertical: 16,
596
+ marginTop: 16,
704
597
  },
705
598
  retryButton: {
706
599
  backgroundColor: Global.AppTheme.primary,
@@ -713,13 +606,6 @@ const styles = StyleSheet.create({
713
606
  color: Global.AppTheme.light,
714
607
  fontWeight: 'bold',
715
608
  },
716
- placeholderText: {
717
- color: Global.AppTheme.light,
718
- fontSize: 16,
719
- textAlign: 'center',
720
- marginTop: 16,
721
- },
722
- // Liveness detection styles
723
609
  livenessContainer: {
724
610
  position: 'absolute',
725
611
  top: 40,
@@ -808,7 +694,6 @@ const styles = StyleSheet.create({
808
694
  textAlign: 'center',
809
695
  flex: 1,
810
696
  },
811
- // Stability bar for level 0
812
697
  stabilityContainer: {
813
698
  alignItems: 'center',
814
699
  width: '100%',
@@ -4,7 +4,7 @@ import Icon from 'react-native-vector-icons/MaterialIcons';
4
4
  import PropTypes from 'prop-types';
5
5
  import { Global } from '../utils/Global';
6
6
 
7
- export const Card = ({ employeeData, apiurl, imageurl = 'file/filedownload/photo/' }) => {
7
+ export const Card = ({ employeeData, apiurl, fileurl = 'file/filedownload/photo/' }) => {
8
8
  const [imageError, setImageError] = useState(false);
9
9
 
10
10
  if (!employeeData || typeof employeeData !== 'object') {
@@ -18,7 +18,7 @@ export const Card = ({ employeeData, apiurl, imageurl = 'file/filedownload/photo
18
18
  const employeeId = faceid || 'N/A';
19
19
 
20
20
  const imageSource = !imageError && img
21
- ? { uri: `${apiurl}${imageurl}${img}` }
21
+ ? { uri: `${apiurl}${fileurl}${img}` }
22
22
  : null;
23
23
 
24
24
  return (
@@ -65,7 +65,7 @@ Card.propTypes = {
65
65
  img: PropTypes.string,
66
66
  }),
67
67
  apiurl: PropTypes.string,
68
- imageurl: PropTypes.string,
68
+ fileurl: PropTypes.string,
69
69
  };
70
70
 
71
71
  const styles = StyleSheet.create({
@@ -16,7 +16,7 @@ export default function Loader({
16
16
  overlayColor = 'rgba(0,0,0,0.4)',
17
17
  loaderColor = 'lightblue',
18
18
  size = 50,
19
- gifSource = {uri: `http://emr.amalaims.org:9393/file/getCommonFile/image/heartpulse.gif`},
19
+ gifSource,
20
20
  message = '',
21
21
  messageStyle = {},
22
22
  animationType = 'fade',
@@ -6,7 +6,7 @@ import { useFaceDetector } from 'react-native-vision-camera-face-detector';
6
6
  // Tuned constants for liveness detection / stability
7
7
  const FACE_STABILITY_THRESHOLD = 3;
8
8
  const FACE_MOVEMENT_THRESHOLD = 15;
9
- const FRAME_PROCESSOR_MIN_INTERVAL_MS = 1000; // increased to reduce load
9
+ const FRAME_PROCESSOR_MIN_INTERVAL_MS = 800; // increased to reduce load
10
10
  const MIN_FACE_SIZE = 0.2;
11
11
 
12
12
  // Liveness detection constants
package/src/index.js CHANGED
@@ -40,7 +40,7 @@ import CaptureImageWithoutEdit from "./components/CaptureImageWithoutEdit";
40
40
  import StepIndicator from "./components/StepIndicator";
41
41
 
42
42
  const BiometricModal = React.memo(
43
- ({ data, qrscan = false, callback, apiurl, onclose, frameProcessorFps, livenessLevel, imageurl }) => {
43
+ ({ data, qrscan = false, callback, apiurl, onclose, frameProcessorFps, livenessLevel, fileurl, imageurl }) => {
44
44
  const navigation = useNavigation();
45
45
 
46
46
  // Custom hooks
@@ -547,8 +547,8 @@ const BiometricModal = React.memo(
547
547
  const loaderSource = useMemo(
548
548
  () =>
549
549
  state.isLoading &&
550
- getLoaderGif(state.animationState, state.currentStep, apiurl),
551
- [state.isLoading, state.animationState, state.currentStep, apiurl]
550
+ getLoaderGif(state.animationState, state.currentStep, apiurl, imageurl),
551
+ [state.isLoading, state.animationState, state.currentStep, apiurl, imageurl]
552
552
  );
553
553
 
554
554
  // Determine if camera should be shown
@@ -608,7 +608,7 @@ const BiometricModal = React.memo(
608
608
 
609
609
  {state.employeeData && (
610
610
  <View style={styles.cardContainer}>
611
- <Card employeeData={state.employeeData} apiurl={apiurl} imageurl={imageurl} />
611
+ <Card employeeData={state.employeeData} apiurl={apiurl} fileurl={fileurl} />
612
612
  </View>
613
613
  )}
614
614
 
@@ -6,11 +6,11 @@ import { Global } from "./Global";
6
6
  * @param {string} currentStep - Current step of verification
7
7
  * @returns {any} - Gif image source or null
8
8
  */
9
- export const getLoaderGif = (animationState, currentStep,APIURL) => {
9
+ export const getLoaderGif = (animationState, currentStep,APIURL,imageurl ='file/getCommonFile/image/') => {
10
10
  const FaceGifUrl =
11
- `${APIURL}file/getCommonFile/image/Face.gif`;
11
+ `${APIURL}${imageurl}Face.gif`;
12
12
  const LocationGifUrl =
13
- `${APIURL}file/getCommonFile/image/Location.gif`;
13
+ `${APIURL}${imageurl}Location.gif`;
14
14
 
15
15
  if (
16
16
  animationState === Global.AnimationStates.faceScan ||