react-native-biometric-verifier 0.0.15 → 0.0.16

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.15",
3
+ "version": "0.0.16",
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": {
@@ -1,4 +1,3 @@
1
- // CaptureImageWithoutEdit.js
2
1
  import React, { useRef, useEffect, useState, useCallback, useMemo } from 'react';
3
2
  import {
4
3
  View,
@@ -16,19 +15,10 @@ import {
16
15
  getCameraDevice,
17
16
  useCodeScanner,
18
17
  useCameraFormat,
19
- useFrameProcessor,
20
18
  CameraRuntimeError,
21
19
  } from 'react-native-vision-camera';
22
- import { Worklets } from 'react-native-worklets-core';
23
- import { useFaceDetector } from 'react-native-vision-camera-face-detector';
24
20
  import { COLORS } from "../utils/constants";
25
-
26
- // Constants for configuration
27
- const FACE_STABILITY_THRESHOLD = 4; // how many stable samples required
28
- const FACE_MOVEMENT_THRESHOLD = 18; // pixel threshold (adjust as needed)
29
- const FRAME_PROCESSOR_MIN_INTERVAL_MS = 700; // throttle; process at most every ~700ms
30
- const FRAME_PROCESSOR_FPS = 1;
31
- const MIN_FACE_SIZE = 0.2;
21
+ import { useFaceDetectionFrameProcessor } from '../hooks/useFaceDetectionFrameProcessor';
32
22
 
33
23
  const CaptureImageWithoutEdit = React.memo(
34
24
  ({
@@ -38,6 +28,7 @@ const CaptureImageWithoutEdit = React.memo(
38
28
  showCodeScanner = false,
39
29
  isLoading = false,
40
30
  currentStep = '',
31
+ frameProcessorFps = 1,
41
32
  }) => {
42
33
  const cameraRef = useRef(null);
43
34
  const [cameraDevice, setCameraDevice] = useState(null);
@@ -45,65 +36,48 @@ const CaptureImageWithoutEdit = React.memo(
45
36
  const [showCamera, setShowCamera] = useState(false);
46
37
  const [cameraInitialized, setCameraInitialized] = useState(false);
47
38
 
48
- const [faces, setFaces] = useState([]); // minimal rectangles for UI
39
+ const [faces, setFaces] = useState([]);
49
40
  const [singleFaceDetected, setSingleFaceDetected] = useState(false);
50
41
  const [cameraError, setCameraError] = useState(null);
51
42
 
52
- const captured = useRef(false); // JS-side guard to avoid double-capture
43
+ const captured = useRef(false);
53
44
  const appState = useRef(AppState.currentState);
54
45
  const isMounted = useRef(true);
46
+ const initializationAttempts = useRef(0);
47
+ const maxInitializationAttempts = 3;
48
+
49
+ // Reset capture state
50
+ const resetCaptureState = useCallback(() => {
51
+ captured.current = false;
52
+ setSingleFaceDetected(false);
53
+ setFaces([]);
54
+ }, []);
55
55
 
56
- // Face detector options
57
- const faceDetectionOptions = {
58
- performanceMode: 'fast',
59
- landmarkMode: 'none',
60
- contourMode: 'none',
61
- classificationMode: 'none',
62
- minFaceSize: MIN_FACE_SIZE,
63
- };
64
- const { detectFaces } = useFaceDetector(faceDetectionOptions);
65
-
66
- // Code scanner (unchanged)
56
+ // Code scanner
67
57
  const codeScanner = useCodeScanner({
68
58
  codeTypes: ['qr', 'ean-13'],
69
59
  onCodeScanned: (codes) => {
70
- if (showCodeScanner && codes && codes[0]?.value && !isLoading) {
71
- console.log('QR Code scanned:', codes[0].value);
72
- onCapture(codes[0].value);
60
+ try {
61
+ if (showCodeScanner && codes && codes[0]?.value && !isLoading) {
62
+ console.log('QR Code scanned:', codes[0].value);
63
+ onCapture(codes[0].value);
64
+ }
65
+ } catch (error) {
66
+ console.error('Error processing scanned code:', error);
67
+ setCameraError('Failed to process QR code');
73
68
  }
74
69
  },
75
70
  });
76
71
 
77
- //
78
- // --- Worklet-safe shared state (create once) ---
79
- //
80
- // Use useMemo to create shared values only once per component lifetime.
81
- const lastProcessedTime = useMemo(() => Worklets.createSharedValue(0), []);
82
- const lastBounds = useMemo(() => Worklets.createSharedValue(null), []);
83
- const stableCount = useMemo(() => Worklets.createSharedValue(0), []);
84
- const capturedSV = useMemo(() => Worklets.createSharedValue(false), []);
85
- const showCodeScannerSV = useMemo(() => Worklets.createSharedValue(false), []);
86
-
87
- //
88
- // --- RunOnJS callbacks (small payloads only) ---
89
- //
72
+ // Callbacks for face detection events
90
73
  const onStableFaceDetected = useCallback((faceRect) => {
91
74
  if (!isMounted.current) return;
92
75
  if (captured.current) return;
93
76
 
94
- // keep JS-side guard
95
77
  captured.current = true;
96
- // keep UI minimal
97
78
  setSingleFaceDetected(true);
98
79
  setFaces([faceRect]);
99
80
 
100
- // Ensure the worklet shared flag is set (worklet already sets it but keep JS consistent)
101
- try {
102
- capturedSV.value = true;
103
- } catch (e) {
104
- // ignore if not accessible for some reason
105
- }
106
-
107
81
  (async () => {
108
82
  try {
109
83
  if (!cameraRef.current) {
@@ -117,6 +91,10 @@ const CaptureImageWithoutEdit = React.memo(
117
91
  skipMetadata: true,
118
92
  });
119
93
 
94
+ if (!photo || !photo.path) {
95
+ throw new Error('Failed to capture photo - no path returned');
96
+ }
97
+
120
98
  const photopath = `file://${photo.path}`;
121
99
  const fileName = photopath.substr(photopath.lastIndexOf('/') + 1);
122
100
  const photoData = {
@@ -128,169 +106,146 @@ const CaptureImageWithoutEdit = React.memo(
128
106
  onCapture(photoData);
129
107
  } catch (e) {
130
108
  console.error('Capture error:', e);
131
- // reset both JS and worklet flags so user can retry
132
109
  captured.current = false;
133
- try {
134
- capturedSV.value = false;
135
- stableCount.value = 0;
136
- lastBounds.value = null;
137
- } catch (err) {
138
- // swallow
139
- }
110
+ resetCaptureState();
140
111
  setCameraError('Failed to capture image. Please try again.');
141
112
  }
142
113
  })();
143
- }, [onCapture, capturedSV, stableCount, lastBounds]);
114
+ }, [onCapture, resetCaptureState]);
144
115
 
145
116
  const onFacesUpdate = useCallback((payload) => {
146
117
  if (!isMounted.current) return;
147
- const { count } = payload;
148
- if (count === 1) {
149
- setSingleFaceDetected(true);
150
- setFaces(prev => {
151
- if (prev.length === 1) return prev;
152
- return [{ x: 0, y: 0, width: 0, height: 0 }];
153
- });
154
- } else {
155
- setSingleFaceDetected(false);
156
- setFaces([]);
118
+ try {
119
+ const { count } = payload;
120
+ if (count === 1) {
121
+ setSingleFaceDetected(true);
122
+ setFaces(prev => {
123
+ if (prev.length === 1) return prev;
124
+ return [{ x: 0, y: 0, width: 0, height: 0 }];
125
+ });
126
+ } else {
127
+ setSingleFaceDetected(false);
128
+ setFaces([]);
129
+ }
130
+ } catch (error) {
131
+ console.error('Error updating faces:', error);
157
132
  }
158
- // we don't store 'progress' in state to keep UI lightweight
159
133
  }, []);
160
134
 
161
- const runOnStable = Worklets.createRunOnJS(onStableFaceDetected);
162
- const runOnFaces = Worklets.createRunOnJS(onFacesUpdate);
163
-
164
- //
165
- // --- Frame processor (worklet): uses shared values above ---
166
- //
167
- const frameProcessor = useFrameProcessor((frame) => {
168
- 'worklet';
169
- // use shared values created via Worklets.createSharedValue
170
- if (showCodeScannerSV.value || capturedSV.value) return;
171
-
172
- const now = performance.now();
173
- if (!lastProcessedTime.value) lastProcessedTime.value = 0;
174
- if (now - lastProcessedTime.value < FRAME_PROCESSOR_MIN_INTERVAL_MS) return;
175
- lastProcessedTime.value = now;
135
+ // Use the face detection frame processor hook
136
+ const {
137
+ frameProcessor,
138
+ resetCaptureState: resetFrameProcessor,
139
+ updateShowCodeScanner,
140
+ capturedSV,
141
+ } = useFaceDetectionFrameProcessor({
142
+ onStableFaceDetected,
143
+ onFacesUpdate,
144
+ showCodeScanner,
145
+ isLoading,
146
+ });
176
147
 
148
+ // Initialize camera
149
+ const initializeCamera = useCallback(async () => {
177
150
  try {
178
- const detected = detectFaces(frame);
179
- const len = detected.length;
151
+ if (!isMounted.current) return;
152
+ if (initializationAttempts.current >= maxInitializationAttempts) {
153
+ setCameraError('Failed to initialize camera after multiple attempts');
154
+ return;
155
+ }
180
156
 
181
- if (len === 1 && !capturedSV.value) {
182
- const f = detected[0];
183
- const bounds = f.bounds; // { x, y, width, height }
157
+ initializationAttempts.current += 1;
158
+ setCameraError(null);
184
159
 
185
- if (!lastBounds.value) {
186
- lastBounds.value = bounds;
187
- stableCount.value = 1;
188
- } else {
189
- const dx = Math.abs(bounds.x - lastBounds.value.x);
190
- const dy = Math.abs(bounds.y - lastBounds.value.y);
160
+ const permission = await Camera.requestCameraPermission();
161
+ setCameraPermission(permission);
191
162
 
192
- if (dx < FACE_MOVEMENT_THRESHOLD && dy < FACE_MOVEMENT_THRESHOLD) {
193
- stableCount.value = (stableCount.value || 0) + 1;
194
- } else {
195
- stableCount.value = 0;
196
- }
197
- lastBounds.value = bounds;
163
+ if (permission === 'granted') {
164
+ const devices = await Camera.getAvailableCameraDevices();
165
+
166
+ if (!devices || devices.length === 0) {
167
+ throw new Error('No camera devices found');
198
168
  }
169
+
170
+ const device = getCameraDevice(devices, cameraType);
199
171
 
200
- if ((stableCount.value || 0) >= FACE_STABILITY_THRESHOLD) {
201
- // mark captured on worklet side to stop further processing immediately
202
- capturedSV.value = true;
203
-
204
- // send only minimal rectangle data to JS to avoid expensive bridging
205
- const faceRect = {
206
- x: Math.round(lastBounds.value.x),
207
- y: Math.round(lastBounds.value.y),
208
- width: Math.round(lastBounds.value.width),
209
- height: Math.round(lastBounds.value.height),
210
- };
211
-
212
- runOnStable(faceRect);
172
+ if (device) {
173
+ setCameraDevice(device);
174
+ setTimeout(() => setShowCamera(true), 100);
175
+ initializationAttempts.current = 0; // Reset attempts on success
213
176
  } else {
214
- // lightweight UI update
215
- runOnFaces({ count: 1, progress: stableCount.value || 0 });
177
+ throw new Error(`No camera device found for type: ${cameraType}`);
216
178
  }
217
179
  } else {
218
- // no face or multiple faces -> reset worklet-side stability trackers
219
- lastBounds.value = null;
220
- stableCount.value = 0;
221
- capturedSV.value = false;
222
- runOnFaces({ count: len });
180
+ throw new Error(`Camera permission ${permission}`);
181
+ }
182
+ } catch (error) {
183
+ console.error('Camera initialization failed:', error);
184
+ if (isMounted.current) {
185
+ let errorMessage = 'Failed to initialize camera';
186
+
187
+ if (error.message.includes('permission')) {
188
+ errorMessage = 'Camera permission denied';
189
+ } else if (error.message.includes('No camera devices')) {
190
+ errorMessage = 'No camera available on this device';
191
+ }
192
+
193
+ setCameraError(errorMessage);
223
194
  }
224
- } catch (e) {
225
- // swallow errors in worklet; don't do heavy logging here
226
195
  }
227
- }, [detectFaces]);
196
+ }, [cameraType]);
228
197
 
229
- //
230
- // --- Effects: lifecycle, camera init, app state, cleanup ---
231
- //
198
+ // Effects: lifecycle, camera init, app state, cleanup
232
199
  useEffect(() => {
233
200
  isMounted.current = true;
234
-
235
- const initializeCamera = async () => {
236
- try {
237
- if (!isMounted.current) return;
238
-
239
- const permission = await Camera.requestCameraPermission();
240
- setCameraPermission(permission);
241
-
242
- if (permission === 'granted') {
243
- const devices = await Camera.getAvailableCameraDevices();
244
- const device = getCameraDevice(devices, cameraType);
245
-
246
- if (device) {
247
- setCameraDevice(device);
248
- // short delay so camera UI transitions cleanly
249
- setTimeout(() => setShowCamera(true), 100);
250
- } else {
251
- console.error('No camera device found for type:', cameraType);
252
- setCameraError('Camera not available on this device');
253
- }
254
- } else {
255
- setCameraError('Camera permission denied');
256
- }
257
- } catch (error) {
258
- console.error('Camera init failed:', error);
259
- if (isMounted.current) {
260
- setCameraError('Failed to initialize camera');
261
- }
262
- }
263
- };
264
-
265
201
  initializeCamera();
266
202
 
267
203
  return () => {
268
204
  isMounted.current = false;
269
205
  setShowCamera(false);
270
206
  };
271
- }, [cameraType]);
207
+ }, [initializeCamera]);
272
208
 
273
209
  // app state change
274
210
  useEffect(() => {
275
211
  const subscription = AppState.addEventListener('change', nextAppState => {
276
- if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
277
- // App came to the foreground
278
- if (cameraPermission === 'granted') {
279
- setShowCamera(true);
212
+ try {
213
+ if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
214
+ if (cameraPermission === 'granted') {
215
+ setShowCamera(true);
216
+ }
217
+ } else if (nextAppState.match(/inactive|background/)) {
218
+ setShowCamera(false);
280
219
  }
281
- } else if (nextAppState.match(/inactive|background/)) {
282
- setShowCamera(false);
220
+ appState.current = nextAppState;
221
+ } catch (error) {
222
+ console.error('Error handling app state change:', error);
283
223
  }
284
- appState.current = nextAppState;
285
224
  });
286
225
 
287
- return () => subscription.remove();
226
+ return () => {
227
+ try {
228
+ subscription.remove();
229
+ } catch (error) {
230
+ console.error('Error removing app state listener:', error);
231
+ }
232
+ };
288
233
  }, [cameraPermission]);
289
234
 
290
- // android back handler (same as before)
235
+ // android back handler
291
236
  useEffect(() => {
292
- const backHandler = BackHandler.addEventListener('hardwareBackPress', () => false);
293
- return () => backHandler.remove();
237
+ const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
238
+ // Don't allow back press in camera screen
239
+ return true;
240
+ });
241
+
242
+ return () => {
243
+ try {
244
+ backHandler.remove();
245
+ } catch (error) {
246
+ console.error('Error removing back handler:', error);
247
+ }
248
+ };
294
249
  }, []);
295
250
 
296
251
  // handle camera errors
@@ -323,13 +278,34 @@ const CaptureImageWithoutEdit = React.memo(
323
278
  setCameraError(null);
324
279
  }, []);
325
280
 
326
- // format selection (unchanged)
281
+ // format selection
327
282
  const format = useCameraFormat(cameraDevice, [
328
283
  { videoResolution: { width: 640, height: 640 } },
329
284
  { fps: 30 },
330
285
  ]);
331
286
 
332
- // camera placeholder / UI (kept the same)
287
+ // keep worklet's showCodeScanner flag in sync
288
+ useEffect(() => {
289
+ try {
290
+ updateShowCodeScanner(!!showCodeScanner);
291
+ } catch (error) {
292
+ console.error('Error updating code scanner:', error);
293
+ }
294
+ }, [showCodeScanner, updateShowCodeScanner]);
295
+
296
+ // Retry camera initialization
297
+ const handleRetry = useCallback(() => {
298
+ setCameraError(null);
299
+ setShowCamera(false);
300
+ resetCaptureState();
301
+ setTimeout(() => {
302
+ if (isMounted.current) {
303
+ initializeCamera();
304
+ }
305
+ }, 500);
306
+ }, [initializeCamera, resetCaptureState]);
307
+
308
+ // camera placeholder / UI
333
309
  const renderCameraPlaceholder = () => (
334
310
  <View style={styles.cameraContainer}>
335
311
  {cameraError ? (
@@ -338,25 +314,25 @@ const CaptureImageWithoutEdit = React.memo(
338
314
  <Text style={styles.errorText}>{cameraError}</Text>
339
315
  <TouchableOpacity
340
316
  style={styles.retryButton}
341
- onPress={() => {
342
- setCameraError(null);
343
- setShowCamera(false);
344
- setTimeout(() => {
345
- if (isMounted.current) {
346
- setShowCamera(true);
347
- }
348
- }, 500);
349
- }}
317
+ onPress={handleRetry}
318
+ accessibilityLabel="Retry camera initialization"
350
319
  >
351
320
  <Text style={styles.retryButtonText}>Retry</Text>
352
321
  </TouchableOpacity>
353
322
  </View>
354
- ) : cameraPermission === 'denied' ? (
323
+ ) : cameraPermission === 'denied' || cameraPermission === 'restricted' ? (
355
324
  <View style={styles.placeholderContainer}>
356
325
  <Icon name="camera-off" size={40} color={COLORS.light} />
357
326
  <Text style={styles.placeholderText}>
358
327
  Camera permission required. Please enable in settings.
359
328
  </Text>
329
+ <TouchableOpacity
330
+ style={styles.retryButton}
331
+ onPress={handleRetry}
332
+ accessibilityLabel="Request camera permission again"
333
+ >
334
+ <Text style={styles.retryButtonText}>Request Again</Text>
335
+ </TouchableOpacity>
360
336
  </View>
361
337
  ) : cameraPermission === 'not-determined' ? (
362
338
  <View style={styles.placeholderContainer}>
@@ -369,6 +345,13 @@ const CaptureImageWithoutEdit = React.memo(
369
345
  <View style={styles.placeholderContainer}>
370
346
  <Icon name="camera-alt" size={40} color={COLORS.light} />
371
347
  <Text style={styles.placeholderText}>Camera not available</Text>
348
+ <TouchableOpacity
349
+ style={styles.retryButton}
350
+ onPress={handleRetry}
351
+ accessibilityLabel="Retry camera initialization"
352
+ >
353
+ <Text style={styles.retryButtonText}>Retry</Text>
354
+ </TouchableOpacity>
372
355
  </View>
373
356
  ) : (
374
357
  <View style={styles.placeholderContainer}>
@@ -384,33 +367,6 @@ const CaptureImageWithoutEdit = React.memo(
384
367
  cameraDevice &&
385
368
  !cameraError;
386
369
 
387
- // keep worklet's showCodeScanner flag in sync
388
- useEffect(() => {
389
- try {
390
- showCodeScannerSV.value = !!showCodeScanner;
391
- } catch (e) {
392
- // ignore
393
- }
394
- return () => {
395
- try { showCodeScannerSV.value = false; } catch (e) {}
396
- };
397
- }, [showCodeScanner, showCodeScannerSV]);
398
-
399
- // cleanup shared values when component unmounts
400
- useEffect(() => {
401
- return () => {
402
- try {
403
- lastProcessedTime.value = 0;
404
- lastBounds.value = null;
405
- stableCount.value = 0;
406
- capturedSV.value = false;
407
- showCodeScannerSV.value = false;
408
- } catch (e) {
409
- // ignore
410
- }
411
- };
412
- }, [lastProcessedTime, lastBounds, stableCount, capturedSV, showCodeScannerSV]);
413
-
414
370
  return (
415
371
  <View style={styles.cameraContainer}>
416
372
  {shouldRenderCamera ? (
@@ -423,7 +379,7 @@ const CaptureImageWithoutEdit = React.memo(
423
379
  format={format}
424
380
  codeScanner={showCodeScanner ? codeScanner : undefined}
425
381
  frameProcessor={!showCodeScanner && cameraInitialized ? frameProcessor : undefined}
426
- frameProcessorFps={FRAME_PROCESSOR_FPS}
382
+ frameProcessorFps={frameProcessorFps}
427
383
  onInitialized={handleCameraInitialized}
428
384
  onError={handleCameraError}
429
385
  enableZoomGesture={false}
@@ -455,9 +411,12 @@ const CaptureImageWithoutEdit = React.memo(
455
411
  </Text>
456
412
  )}
457
413
  {captured.current && (
458
- <Text style={[styles.faceDetectionText, styles.capturingText]}>
459
- Capturing...
460
- </Text>
414
+ <View style={styles.capturingContainer}>
415
+ <ActivityIndicator size="small" color={COLORS.light} />
416
+ <Text style={[styles.faceDetectionText, styles.capturingText]}>
417
+ Capturing...
418
+ </Text>
419
+ </View>
461
420
  )}
462
421
  </View>
463
422
  )}
@@ -521,6 +480,7 @@ const styles = StyleSheet.create({
521
480
  paddingHorizontal: 20,
522
481
  paddingVertical: 10,
523
482
  borderRadius: 8,
483
+ marginTop: 10,
524
484
  },
525
485
  retryButtonText: {
526
486
  color: COLORS.light,
@@ -587,6 +547,14 @@ const styles = StyleSheet.create({
587
547
  capturingText: {
588
548
  backgroundColor: 'rgba(0,100,255,0.7)',
589
549
  },
550
+ capturingContainer: {
551
+ flexDirection: 'row',
552
+ alignItems: 'center',
553
+ backgroundColor: 'rgba(0,100,255,0.7)',
554
+ paddingHorizontal: 16,
555
+ paddingVertical: 8,
556
+ borderRadius: 8,
557
+ },
590
558
  });
591
559
 
592
- export default CaptureImageWithoutEdit;
560
+ export default CaptureImageWithoutEdit;
@@ -4,11 +4,11 @@ import Icon from 'react-native-vector-icons/MaterialIcons';
4
4
  import PropTypes from 'prop-types';
5
5
  import { COLORS } from '../utils/constants';
6
6
 
7
- export const CCard = ({ employeeData, apiurl }) => {
7
+ export const Card = ({ employeeData, apiurl }) => {
8
8
  const [imageError, setImageError] = useState(false);
9
9
 
10
10
  if (!employeeData || typeof employeeData !== 'object') {
11
- console.warn('CCard: Invalid or missing employeeData');
11
+ console.warn('Card: Invalid or missing employeeData');
12
12
  return null;
13
13
  }
14
14
 
@@ -52,7 +52,7 @@ export const CCard = ({ employeeData, apiurl }) => {
52
52
  );
53
53
  };
54
54
 
55
- CCard.propTypes = {
55
+ Card.propTypes = {
56
56
  employeeData: PropTypes.shape({
57
57
  facename: PropTypes.string,
58
58
  faceid: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
@@ -133,4 +133,4 @@ const styles = StyleSheet.create({
133
133
  },
134
134
  });
135
135
 
136
- export default CCard;
136
+ export default Card;
@@ -0,0 +1,167 @@
1
+ import { useCallback, useMemo } from 'react';
2
+ import { Worklets } from 'react-native-worklets-core';
3
+ import { useFrameProcessor } from 'react-native-vision-camera';
4
+ import { useFaceDetector } from 'react-native-vision-camera-face-detector';
5
+
6
+ // Tuned constants
7
+ const FACE_STABILITY_THRESHOLD = 3;
8
+ const FACE_MOVEMENT_THRESHOLD = 15;
9
+ const FRAME_PROCESSOR_MIN_INTERVAL_MS = 1000;
10
+ const MIN_FACE_SIZE = 0.2;
11
+
12
+ export const useFaceDetectionFrameProcessor = ({
13
+ onStableFaceDetected = () => {},
14
+ onFacesUpdate = () => {},
15
+ showCodeScanner = false,
16
+ isLoading = false,
17
+ }) => {
18
+ // Face detector (fast mode only)
19
+ const { detectFaces } = useFaceDetector({
20
+ performanceMode: 'fast',
21
+ landmarkMode: 'none',
22
+ contourMode: 'none',
23
+ classificationMode: 'none',
24
+ minFaceSize: MIN_FACE_SIZE,
25
+ });
26
+
27
+ // Shared state
28
+ const lastProcessedTime = useMemo(() => Worklets.createSharedValue(0), []);
29
+ const lastX = useMemo(() => Worklets.createSharedValue(0), []);
30
+ const lastY = useMemo(() => Worklets.createSharedValue(0), []);
31
+ const lastW = useMemo(() => Worklets.createSharedValue(0), []);
32
+ const lastH = useMemo(() => Worklets.createSharedValue(0), []);
33
+ const stableCount = useMemo(() => Worklets.createSharedValue(0), []);
34
+ const capturedSV = useMemo(() => Worklets.createSharedValue(false), []);
35
+ const showCodeScannerSV = useMemo(
36
+ () => Worklets.createSharedValue(showCodeScanner),
37
+ [showCodeScanner]
38
+ );
39
+
40
+ // Safe JS callbacks
41
+ const runOnStable = useMemo(
42
+ () =>
43
+ Worklets.createRunOnJS((x, y, width, height) => {
44
+ try {
45
+ onStableFaceDetected?.({
46
+ x: Math.max(0, Math.round(x)),
47
+ y: Math.max(0, Math.round(y)),
48
+ width: Math.max(0, Math.round(width)),
49
+ height: Math.max(0, Math.round(height)),
50
+ });
51
+ } catch {}
52
+ }),
53
+ [onStableFaceDetected]
54
+ );
55
+
56
+ const runOnFaces = useMemo(
57
+ () =>
58
+ Worklets.createRunOnJS((count, progress) => {
59
+ try {
60
+ onFacesUpdate?.({ count, progress });
61
+ } catch {}
62
+ }),
63
+ [onFacesUpdate]
64
+ );
65
+
66
+ // Frame processor
67
+ const frameProcessor = useFrameProcessor((frame) => {
68
+ 'worklet';
69
+
70
+ // Kill switches
71
+ if (showCodeScannerSV.value || capturedSV.value || isLoading) return;
72
+
73
+ const now = frame?.timestamp ? frame.timestamp / 1e6 : Date.now();
74
+ if (now - lastProcessedTime.value < FRAME_PROCESSOR_MIN_INTERVAL_MS) return;
75
+ lastProcessedTime.value = now;
76
+
77
+ let detected;
78
+ try {
79
+ detected = detectFaces?.(frame);
80
+ } catch {
81
+ return; // skip invalid frame
82
+ }
83
+
84
+ if (!detected || detected.length === 0) {
85
+ // No faces → reset
86
+ stableCount.value = 0;
87
+ capturedSV.value = false;
88
+ runOnFaces(0, 0);
89
+ return;
90
+ }
91
+
92
+ if (detected.length === 1 && !capturedSV.value) {
93
+ const f = detected[0];
94
+ if (!f?.bounds) {
95
+ runOnFaces(0, 0);
96
+ return;
97
+ }
98
+
99
+ // Clamp invalid values
100
+ const x = Math.max(0, f.bounds.x ?? 0);
101
+ const y = Math.max(0, f.bounds.y ?? 0);
102
+ const width = Math.max(0, f.bounds.width ?? 0);
103
+ const height = Math.max(0, f.bounds.height ?? 0);
104
+
105
+ if (lastX.value === 0 && lastY.value === 0) {
106
+ // First detection
107
+ lastX.value = x;
108
+ lastY.value = y;
109
+ lastW.value = width;
110
+ lastH.value = height;
111
+ stableCount.value = 1;
112
+ } else {
113
+ const dx = Math.abs(x - lastX.value);
114
+ const dy = Math.abs(y - lastY.value);
115
+
116
+ if (dx < FACE_MOVEMENT_THRESHOLD && dy < FACE_MOVEMENT_THRESHOLD) {
117
+ stableCount.value += 1;
118
+ } else {
119
+ stableCount.value = 1; // restart stability
120
+ }
121
+
122
+ lastX.value = x;
123
+ lastY.value = y;
124
+ lastW.value = width;
125
+ lastH.value = height;
126
+ }
127
+
128
+ if (stableCount.value >= FACE_STABILITY_THRESHOLD) {
129
+ capturedSV.value = true;
130
+ runOnStable(lastX.value, lastY.value, lastW.value, lastH.value);
131
+ } else {
132
+ runOnFaces(1, stableCount.value);
133
+ }
134
+ } else {
135
+ // Multiple faces → not allowed
136
+ stableCount.value = 0;
137
+ capturedSV.value = false;
138
+ runOnFaces(detected.length, 0);
139
+ }
140
+ }, [detectFaces, isLoading]);
141
+
142
+ // Reset everything safely
143
+ const resetCaptureState = useCallback(() => {
144
+ capturedSV.value = false;
145
+ stableCount.value = 0;
146
+ lastX.value = 0;
147
+ lastY.value = 0;
148
+ lastW.value = 0;
149
+ lastH.value = 0;
150
+ lastProcessedTime.value = 0;
151
+ }, []);
152
+
153
+ // Update QR/Scanner toggle
154
+ const updateShowCodeScanner = useCallback(
155
+ (value) => {
156
+ showCodeScannerSV.value = !!value;
157
+ },
158
+ [showCodeScannerSV]
159
+ );
160
+
161
+ return {
162
+ frameProcessor,
163
+ resetCaptureState,
164
+ updateShowCodeScanner,
165
+ capturedSV,
166
+ };
167
+ };
package/src/index.js CHANGED
@@ -16,10 +16,16 @@ import {
16
16
  Animated,
17
17
  } from "react-native";
18
18
  import Icon from "react-native-vector-icons/MaterialIcons";
19
+ import { useNavigation } from "@react-navigation/native";
20
+
21
+ // Custom hooks
19
22
  import { useCountdown } from "./hooks/useCountdown";
20
23
  import { useGeolocation } from "./hooks/useGeolocation";
21
24
  import { useImageProcessing } from "./hooks/useImageProcessing";
22
25
  import { useNotifyMessage } from "./hooks/useNotifyMessage";
26
+ import { useSafeCallback } from "./hooks/useSafeCallback";
27
+
28
+ // Utils
23
29
  import { getDistanceInMeters } from "./utils/distanceCalculator";
24
30
  import {
25
31
  ANIMATION_STATES,
@@ -28,29 +34,31 @@ import {
28
34
  MAX_DISTANCE_METERS,
29
35
  LOADING_TYPES,
30
36
  } from "./utils/constants";
37
+ import networkServiceCall from "./utils/NetworkServiceCall";
38
+ import { getLoaderGif } from "./utils/getLoaderGif";
39
+
40
+ // Components
31
41
  import Loader from "./components/Loader";
32
42
  import { CountdownTimer } from "./components/CountdownTimer";
33
- import { CCard } from "./components/CCard";
43
+ import { Card } from "./components/Card";
34
44
  import { Notification } from "./components/Notification";
35
- import { useNavigation } from "@react-navigation/native";
36
- import networkServiceCall from "./utils/NetworkServiceCall";
37
- import { getLoaderGif } from "./utils/getLoaderGif";
38
- import { useSafeCallback } from "./hooks/useSafeCallback";
39
45
  import CaptureImageWithoutEdit from "./components/CaptureImageWithoutEdit";
40
46
  import StepIndicator from "./components/StepIndicator";
41
47
 
42
- const BiometricVerificationModal = React.memo(
43
- ({ data, qrscan = false, callback, apiurl, onclose }) => {
48
+ const BiometricModal = React.memo(
49
+ ({ data, qrscan = false, callback, apiurl, onclose, frameProcessorFps }) => {
44
50
  const navigation = useNavigation();
51
+
52
+ // Custom hooks
45
53
  const { countdown, startCountdown, resetCountdown, pauseCountdown, resumeCountdown } = useCountdown();
46
54
  const { requestLocationPermission, getCurrentLocation } = useGeolocation();
47
55
  const { convertImageToBase64 } = useImageProcessing();
48
- const { notification, fadeAnim, slideAnim, notifyMessage, clearNotification } =
49
- useNotifyMessage();
56
+ const { notification, fadeAnim, slideAnim, notifyMessage, clearNotification } = useNotifyMessage();
57
+ const safeCallback = useSafeCallback(callback, notifyMessage);
50
58
 
59
+ // State
51
60
  const [modalVisible, setModalVisible] = useState(false);
52
61
  const [cameraType, setCameraType] = useState("front");
53
-
54
62
  const [state, setState] = useState({
55
63
  isLoading: false,
56
64
  loadingType: LOADING_TYPES.NONE,
@@ -59,32 +67,35 @@ const BiometricVerificationModal = React.memo(
59
67
  animationState: ANIMATION_STATES.FACE_SCAN,
60
68
  });
61
69
 
70
+ // Refs
62
71
  const dataRef = useRef(data);
63
72
  const mountedRef = useRef(true);
64
73
  const responseRef = useRef(null);
65
74
  const processedRef = useRef(false);
66
- const safeCallback = useSafeCallback(callback, notifyMessage);
67
75
  const resetTimeoutRef = useRef(null);
68
76
 
69
- // Animation values for icons
77
+ // Animation values
70
78
  const iconScaleAnim = useRef(new Animated.Value(1)).current;
71
79
  const iconOpacityAnim = useRef(new Animated.Value(0)).current;
72
80
 
73
81
  // Cleanup on unmount
74
82
  useEffect(() => {
75
- console.log("🔧 BiometricVerificationModal mounted");
83
+ console.log("🔧 BiometricModal mounted");
76
84
 
77
85
  return () => {
78
- console.log("🧹 BiometricVerificationModal unmounting - cleaning up");
86
+ console.log("🧹 BiometricModal unmounting - cleaning up");
79
87
  mountedRef.current = false;
88
+
80
89
  if (resetTimeoutRef.current) {
81
90
  clearTimeout(resetTimeoutRef.current);
82
91
  console.log("⏹️ Cleared reset timeout");
83
92
  }
93
+
84
94
  clearNotification();
85
95
  };
86
96
  }, []);
87
97
 
98
+ // Update dataRef when data changes
88
99
  useEffect(() => {
89
100
  dataRef.current = data;
90
101
  console.log(
@@ -93,11 +104,12 @@ const BiometricVerificationModal = React.memo(
93
104
  );
94
105
  }, [data]);
95
106
 
107
+ // Animation helper
96
108
  const animateIcon = useCallback(() => {
97
109
  // Reset animation
98
110
  iconScaleAnim.setValue(1);
99
111
  iconOpacityAnim.setValue(0);
100
-
112
+
101
113
  // Start animation sequence
102
114
  Animated.sequence([
103
115
  Animated.parallel([
@@ -120,13 +132,15 @@ const BiometricVerificationModal = React.memo(
120
132
  ]).start();
121
133
  }, [iconScaleAnim, iconOpacityAnim]);
122
134
 
135
+ // State update helper
123
136
  const updateState = useCallback((newState) => {
124
137
  if (mountedRef.current) {
125
138
  setState((prev) => {
126
139
  const merged = { ...prev, ...newState };
140
+
127
141
  if (JSON.stringify(prev) !== JSON.stringify(merged)) {
128
142
  console.log("🔄 State updated:", merged);
129
-
143
+
130
144
  // Pause/resume countdown based on loading state
131
145
  if (newState.isLoading !== undefined) {
132
146
  if (newState.isLoading) {
@@ -137,14 +151,15 @@ const BiometricVerificationModal = React.memo(
137
151
  resumeCountdown();
138
152
  }
139
153
  }
140
-
154
+
141
155
  // Animate icon when step changes
142
156
  if (newState.currentStep && newState.currentStep !== prev.currentStep) {
143
157
  animateIcon();
144
158
  }
145
-
159
+
146
160
  return merged;
147
161
  }
162
+
148
163
  return prev;
149
164
  });
150
165
  } else {
@@ -152,9 +167,11 @@ const BiometricVerificationModal = React.memo(
152
167
  }
153
168
  }, [animateIcon, pauseCountdown, resumeCountdown]);
154
169
 
170
+ // Reset state helper
155
171
  const resetState = useCallback(() => {
156
172
  console.log("🔄 Resetting biometric modal state");
157
173
  onclose(false);
174
+
158
175
  setState({
159
176
  isLoading: false,
160
177
  loadingType: LOADING_TYPES.NONE,
@@ -162,6 +179,7 @@ const BiometricVerificationModal = React.memo(
162
179
  employeeData: null,
163
180
  animationState: ANIMATION_STATES.FACE_SCAN,
164
181
  });
182
+
165
183
  setModalVisible(false);
166
184
  processedRef.current = false;
167
185
  resetCountdown();
@@ -174,6 +192,7 @@ const BiometricVerificationModal = React.memo(
174
192
  }
175
193
  }, [resetCountdown, clearNotification]);
176
194
 
195
+ // Error handler
177
196
  const handleProcessError = useCallback(
178
197
  (message, errorObj = null) => {
179
198
  console.error("❌ Process Error:", message, errorObj || "");
@@ -189,6 +208,7 @@ const BiometricVerificationModal = React.memo(
189
208
  if (resetTimeoutRef.current) {
190
209
  clearTimeout(resetTimeoutRef.current);
191
210
  }
211
+
192
212
  resetTimeoutRef.current = setTimeout(() => {
193
213
  console.log("⏰ Error timeout completed - resetting state");
194
214
  resetState();
@@ -197,25 +217,30 @@ const BiometricVerificationModal = React.memo(
197
217
  [notifyMessage, resetState, updateState]
198
218
  );
199
219
 
220
+ // Countdown finish handler
200
221
  const handleCountdownFinish = useCallback(() => {
201
222
  console.log("⏰ Countdown finished");
202
223
  handleProcessError("Time is up! Please try again.");
224
+
203
225
  if (navigation.canGoBack()) {
204
226
  console.log("↩️ Navigating back due to timeout");
205
227
  navigation.goBack();
206
228
  }
207
229
  }, [handleProcessError, navigation]);
208
230
 
231
+ // API URL validation
209
232
  const validateApiUrl = useCallback(() => {
210
233
  if (!apiurl || typeof apiurl !== "string") {
211
234
  console.error("❌ Invalid API URL:", apiurl);
212
235
  handleProcessError("Invalid API URL configuration.");
213
236
  return false;
214
237
  }
238
+
215
239
  console.log("✅ API URL validated:", apiurl);
216
240
  return true;
217
241
  }, [apiurl, handleProcessError]);
218
242
 
243
+ // Face scan upload
219
244
  const uploadFaceScan = useCallback(
220
245
  async (selfie) => {
221
246
  console.log("📸 Uploading face scan");
@@ -237,6 +262,7 @@ const BiometricVerificationModal = React.memo(
237
262
 
238
263
  InteractionManager.runAfterInteractions(async () => {
239
264
  let base64;
265
+
240
266
  try {
241
267
  console.log("🖼️ Converting image to base64");
242
268
  updateState({
@@ -279,6 +305,7 @@ const BiometricVerificationModal = React.memo(
279
305
  if (response?.httpstatus === 200) {
280
306
  console.log("✅ Face recognition successful");
281
307
  responseRef.current = response;
308
+
282
309
  updateState({
283
310
  employeeData: response.data?.data || null,
284
311
  animationState: ANIMATION_STATES.SUCCESS,
@@ -294,9 +321,11 @@ const BiometricVerificationModal = React.memo(
294
321
  } else {
295
322
  console.log("✅ Verification complete - calling callback");
296
323
  safeCallback(responseRef.current);
324
+
297
325
  if (resetTimeoutRef.current) {
298
326
  clearTimeout(resetTimeoutRef.current);
299
327
  }
328
+
300
329
  resetTimeoutRef.current = setTimeout(() => {
301
330
  console.log("⏰ Success timeout completed - resetting");
302
331
  resetState();
@@ -306,7 +335,7 @@ const BiometricVerificationModal = React.memo(
306
335
  console.warn("⚠️ Face recognition failed:", response?.data?.error);
307
336
  handleProcessError(
308
337
  response?.data?.error?.message ||
309
- "Face not recognized. Please try again."
338
+ "Face not recognized. Please try again."
310
339
  );
311
340
  }
312
341
  } catch (error) {
@@ -330,6 +359,7 @@ const BiometricVerificationModal = React.memo(
330
359
  ]
331
360
  );
332
361
 
362
+ // QR code processing
333
363
  const handleQRScanned = useCallback(
334
364
  async (qrCodeData) => {
335
365
  console.log("🔍 Processing scanned QR code");
@@ -412,6 +442,7 @@ const BiometricVerificationModal = React.memo(
412
442
  console.log("✅ Location verified successfully");
413
443
  safeCallback(responseRef.current);
414
444
  notifyMessage("Location verified successfully!", "success");
445
+
415
446
  updateState({
416
447
  animationState: ANIMATION_STATES.SUCCESS,
417
448
  isLoading: false,
@@ -421,6 +452,7 @@ const BiometricVerificationModal = React.memo(
421
452
  if (resetTimeoutRef.current) {
422
453
  clearTimeout(resetTimeoutRef.current);
423
454
  }
455
+
424
456
  resetTimeoutRef.current = setTimeout(() => {
425
457
  console.log("⏰ Location success timeout - resetting");
426
458
  resetState();
@@ -464,6 +496,7 @@ const BiometricVerificationModal = React.memo(
464
496
  ]
465
497
  );
466
498
 
499
+ // Image capture handler
467
500
  const handleImageCapture = useCallback(
468
501
  async (capturedData) => {
469
502
  console.log("📷 Image captured for step:", state.currentStep);
@@ -479,6 +512,7 @@ const BiometricVerificationModal = React.memo(
479
512
  [state.currentStep, uploadFaceScan, handleQRScanned]
480
513
  );
481
514
 
515
+ // Start face scan
482
516
  const handleStartFaceScan = useCallback(() => {
483
517
  console.log("👤 Starting face scan");
484
518
  updateState({
@@ -488,6 +522,7 @@ const BiometricVerificationModal = React.memo(
488
522
  setCameraType("front");
489
523
  }, [updateState]);
490
524
 
525
+ // Start QR code scan
491
526
  const startQRCodeScan = useCallback(() => {
492
527
  console.log("📍 Starting QR code scan");
493
528
  updateState({
@@ -497,6 +532,7 @@ const BiometricVerificationModal = React.memo(
497
532
  setCameraType("back");
498
533
  }, [updateState]);
499
534
 
535
+ // Toggle camera
500
536
  const toggleCamera = useCallback(() => {
501
537
  console.log("🔄 Toggling camera");
502
538
  setCameraType((prevType) => {
@@ -506,12 +542,14 @@ const BiometricVerificationModal = React.memo(
506
542
  });
507
543
  }, []);
508
544
 
545
+ // Start the verification process
509
546
  const startProcess = useCallback(() => {
510
547
  console.log("🚀 Starting verification process");
511
548
  startCountdown(handleCountdownFinish);
512
549
  handleStartFaceScan();
513
550
  }, [handleCountdownFinish, handleStartFaceScan, startCountdown]);
514
551
 
552
+ // Open modal when data is received
515
553
  useEffect(() => {
516
554
  if (data && !modalVisible && !processedRef.current) {
517
555
  console.log("📥 New data received, opening modal");
@@ -521,6 +559,7 @@ const BiometricVerificationModal = React.memo(
521
559
  }
522
560
  }, [data, modalVisible, startProcess]);
523
561
 
562
+ // Loader source memoization
524
563
  const loaderSource = useMemo(
525
564
  () =>
526
565
  state.isLoading &&
@@ -528,6 +567,7 @@ const BiometricVerificationModal = React.memo(
528
567
  [state.isLoading, state.animationState, state.currentStep, apiurl]
529
568
  );
530
569
 
570
+ // Determine if camera should be shown
531
571
  const shouldShowCamera =
532
572
  (state.currentStep === "Identity Verification" ||
533
573
  state.currentStep === "Location Verification") &&
@@ -535,35 +575,17 @@ const BiometricVerificationModal = React.memo(
535
575
  state.animationState !== ANIMATION_STATES.ERROR;
536
576
 
537
577
  return (
538
- <>
539
- <Modal
540
- visible={modalVisible}
541
- animationType="slide"
542
- transparent
543
- onRequestClose={resetState}
544
- statusBarTranslucent
545
- >
546
- <View style={styles.modalContainer}>
547
- <TouchableOpacity
548
- style={styles.closeButton}
549
- onPress={resetState}
550
- accessibilityLabel="Close modal"
551
- hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
552
- >
553
- <Icon name="close" size={24} color={COLORS.light} />
554
- </TouchableOpacity>
555
-
556
- <View style={styles.headerContainer}>
557
-
558
- <View style={styles.titleContainer}>
559
- <Text style={styles.title}>Biometric Verification</Text>
560
- <Text style={styles.subtitle}>{state.currentStep}</Text>
561
- </View>
562
- </View>
563
-
564
- <StepIndicator currentStep={state.currentStep} qrscan={qrscan} />
565
-
566
- {shouldShowCamera && !state.isLoading && (
578
+ <Modal
579
+ visible={modalVisible}
580
+ animationType="slide"
581
+ transparent
582
+ onRequestClose={resetState}
583
+ statusBarTranslucent
584
+ >
585
+ <View style={styles.modalContainer}>
586
+ {/* Camera component - full screen */}
587
+ {shouldShowCamera && !state.isLoading && (
588
+ <View style={styles.cameraContainer}>
567
589
  <CaptureImageWithoutEdit
568
590
  cameraType={cameraType}
569
591
  onCapture={handleImageCapture}
@@ -571,30 +593,63 @@ const BiometricVerificationModal = React.memo(
571
593
  showCodeScanner={state.currentStep === "Location Verification"}
572
594
  isLoading={state.isLoading}
573
595
  currentStep={state.currentStep}
596
+ frameProcessorFps={frameProcessorFps}
574
597
  />
598
+ </View>
599
+ )}
600
+
601
+ {/* UI elements positioned absolutely on top of camera */}
602
+ <TouchableOpacity
603
+ style={styles.closeButton}
604
+ onPress={resetState}
605
+ accessibilityLabel="Close modal"
606
+ hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
607
+ >
608
+ <Icon name="close" size={24} color={COLORS.light} />
609
+ </TouchableOpacity>
610
+
611
+ <View style={styles.topContainer}>
612
+ {!shouldShowCamera && (
613
+ <View style={styles.headerContainer}>
614
+ <View style={styles.titleContainer}>
615
+ <Text style={styles.title}>Biometric Verification</Text>
616
+ <Text style={styles.subtitle}>{state.currentStep}</Text>
617
+ </View>
618
+ </View>
575
619
  )}
620
+ </View>
621
+
622
+ <View style={styles.topContainerstep}>
623
+ <StepIndicator currentStep={state.currentStep} qrscan={qrscan} />
624
+ </View>
576
625
 
577
- {state.employeeData && (
578
- <CCard employeeData={state.employeeData} apiurl={apiurl} />
579
- )}
626
+ {state.employeeData && (
627
+ <View style={styles.cardContainer}>
628
+ <Card employeeData={state.employeeData} apiurl={apiurl} />
629
+ </View>
630
+ )}
580
631
 
632
+ <View style={styles.notificationContainer}>
581
633
  <Notification
582
634
  notification={notification}
583
635
  fadeAnim={fadeAnim}
584
636
  slideAnim={slideAnim}
585
637
  />
638
+ </View>
586
639
 
640
+ <View style={styles.timerContainer}>
587
641
  <CountdownTimer
588
642
  duration={COUNTDOWN_DURATION}
589
643
  currentTime={countdown}
590
644
  />
591
- <Loader
592
- gifSource={loaderSource}
593
- visible={state.isLoading}
594
- />
595
645
  </View>
596
- </Modal>
597
- </>
646
+
647
+ <Loader
648
+ gifSource={loaderSource}
649
+ visible={state.isLoading}
650
+ />
651
+ </View>
652
+ </Modal>
598
653
  );
599
654
  }
600
655
  );
@@ -603,18 +658,38 @@ const styles = StyleSheet.create({
603
658
  modalContainer: {
604
659
  flex: 1,
605
660
  backgroundColor: COLORS.modalBackground || 'rgba(0, 0, 0, 0.85)',
606
- justifyContent: 'center',
607
- alignItems: 'center',
608
- paddingVertical: 50,
661
+ },
662
+ cameraContainer: {
663
+ position: 'absolute',
664
+ top: 0,
665
+ left: 0,
666
+ right: 0,
667
+ bottom: 0,
668
+ zIndex: 0,
669
+ },
670
+ topContainer: {
671
+ position: 'absolute',
672
+ top: Platform.OS === 'ios' ? 50 : 30,
673
+ left: 0,
674
+ right: 0,
675
+ zIndex: 10,
676
+ paddingHorizontal: 20,
677
+ },
678
+ topContainerstep: {
679
+ position: 'absolute',
680
+ bottom: Platform.OS === 'ios' ? 50 : 30,
681
+ left: 0,
682
+ zIndex: 10,
609
683
  },
610
684
  headerContainer: {
611
685
  flexDirection: 'row',
612
686
  alignItems: 'center',
613
687
  marginBottom: 15,
688
+ marginTop: 10,
614
689
  },
615
690
  titleContainer: {
616
691
  flex: 1,
617
- marginLeft:10
692
+ marginLeft: 10
618
693
  },
619
694
  title: {
620
695
  fontSize: 26,
@@ -641,13 +716,36 @@ const styles = StyleSheet.create({
641
716
  },
642
717
  closeButton: {
643
718
  position: 'absolute',
644
- top: Platform.OS === 'ios' ? 50 : 30,
719
+ top: Platform.OS === 'ios' ? 40 : 20,
645
720
  right: 20,
646
- zIndex: 10,
721
+ zIndex: 20,
647
722
  backgroundColor: 'rgba(255, 255, 255, 0.15)',
648
723
  borderRadius: 20,
649
724
  padding: 8,
650
725
  },
726
+ cardContainer: {
727
+ position: 'absolute',
728
+ bottom: 100,
729
+ left: 0,
730
+ right: 0,
731
+ zIndex: 10,
732
+ paddingHorizontal: 20,
733
+ },
734
+ notificationContainer: {
735
+ position: 'absolute',
736
+ top: Platform.OS === 'ios' ? 120 : 100,
737
+ left: 0,
738
+ right: 0,
739
+ zIndex: 10,
740
+ paddingHorizontal: 20,
741
+ },
742
+ timerContainer: {
743
+ position: 'absolute',
744
+ bottom: Platform.OS === 'ios' ? 40 : 20,
745
+ left: 0,
746
+ right: 0,
747
+ zIndex: 10,
748
+ },
651
749
  });
652
750
 
653
- export default BiometricVerificationModal;
751
+ export default BiometricModal;
@@ -20,7 +20,7 @@ const networkServiceCall = async (method, url, extraHeaders = {}, body = {}) =>
20
20
 
21
21
  const response = await fetch(url, dataset);
22
22
 
23
- console.log("🌐 Response Status:", response);
23
+ console.log("🌐 Response Status:", response.status);
24
24
 
25
25
  const result = await response.json();
26
26
  console.log("✅ API Success:", result);
@@ -32,4 +32,14 @@ const networkServiceCall = async (method, url, extraHeaders = {}, body = {}) =>
32
32
  }
33
33
  };
34
34
 
35
+ // ✅ GET API Call helper
36
+ export const getApiCall = (url, extraHeaders = {}) => {
37
+ return networkServiceCall('GET', url, extraHeaders);
38
+ };
39
+
40
+ // ✅ POST API Call helper
41
+ export const postApiCall = (url, body = {}, extraHeaders = {}) => {
42
+ return networkServiceCall('POST', url, extraHeaders, body);
43
+ };
44
+
35
45
  export default networkServiceCall;
@@ -1,7 +0,0 @@
1
- export const createLogger = (moduleName) => {
2
- return (message, data = null) => {
3
- if (__DEV__) {
4
- console.log(`[${moduleName}] ${message}`, JSON.stringify(data));
5
- }
6
- };
7
- };