react-native-biometric-verifier 0.0.12 โ 0.0.13
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 +1 -1
- package/src/components/CCard.js +136 -0
- package/src/components/CaptureImageWithoutEdit.js +457 -0
- package/src/components/CountdownTimer.js +40 -9
- package/src/components/Loader.js +159 -97
- package/src/components/Notification.js +43 -15
- package/src/components/StepIcon.js +71 -0
- package/src/components/StepIndicator.js +91 -0
- package/src/hooks/useNotifyMessage.js +28 -6
- package/src/index.js +329 -78
- package/src/utils/constants.js +15 -0
- package/src/components/EmployeeCard.js +0 -61
- package/src/components/styles.js +0 -200
package/src/index.js
CHANGED
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
Text,
|
|
12
12
|
Modal,
|
|
13
13
|
InteractionManager,
|
|
14
|
+
StyleSheet,
|
|
15
|
+
Platform,
|
|
16
|
+
Animated,
|
|
14
17
|
} from "react-native";
|
|
15
18
|
import Icon from "react-native-vector-icons/MaterialIcons";
|
|
16
19
|
import { useCountdown } from "./hooks/useCountdown";
|
|
@@ -23,67 +26,128 @@ import {
|
|
|
23
26
|
COLORS,
|
|
24
27
|
COUNTDOWN_DURATION,
|
|
25
28
|
MAX_DISTANCE_METERS,
|
|
29
|
+
LOADING_TYPES,
|
|
26
30
|
} from "./utils/constants";
|
|
27
31
|
import Loader from "./components/Loader";
|
|
28
32
|
import { CountdownTimer } from "./components/CountdownTimer";
|
|
29
|
-
import {
|
|
33
|
+
import { CCard } from "./components/CCard";
|
|
30
34
|
import { Notification } from "./components/Notification";
|
|
31
|
-
import { styles } from "./components/styles";
|
|
32
35
|
import { useNavigation } from "@react-navigation/native";
|
|
33
36
|
import networkServiceCall from "./utils/NetworkServiceCall";
|
|
34
37
|
import { getLoaderGif } from "./utils/getLoaderGif";
|
|
35
38
|
import { useSafeCallback } from "./hooks/useSafeCallback";
|
|
39
|
+
import CaptureImageWithoutEdit from "./components/CaptureImageWithoutEdit";
|
|
40
|
+
import StepIndicator from "./components/StepIndicator";
|
|
41
|
+
import StepIcon from "./components/StepIcon";
|
|
36
42
|
|
|
37
43
|
const BiometricVerificationModal = React.memo(
|
|
38
|
-
({ data, qrscan = false, callback, apiurl }) => {
|
|
44
|
+
({ data, qrscan = false, callback, apiurl, onclose }) => {
|
|
39
45
|
const navigation = useNavigation();
|
|
40
46
|
const { countdown, startCountdown, resetCountdown } = useCountdown();
|
|
41
47
|
const { requestLocationPermission, getCurrentLocation } = useGeolocation();
|
|
42
48
|
const { convertImageToBase64 } = useImageProcessing();
|
|
43
|
-
const { notification, fadeAnim, slideAnim, notifyMessage } =
|
|
49
|
+
const { notification, fadeAnim, slideAnim, notifyMessage, clearNotification } =
|
|
44
50
|
useNotifyMessage();
|
|
51
|
+
|
|
45
52
|
const [modalVisible, setModalVisible] = useState(false);
|
|
53
|
+
const [cameraType, setCameraType] = useState("front");
|
|
54
|
+
|
|
46
55
|
const [state, setState] = useState({
|
|
47
56
|
isLoading: false,
|
|
57
|
+
loadingType: LOADING_TYPES.NONE,
|
|
48
58
|
currentStep: "Start",
|
|
49
59
|
employeeData: null,
|
|
50
60
|
animationState: ANIMATION_STATES.FACE_SCAN,
|
|
51
61
|
});
|
|
52
|
-
|
|
62
|
+
|
|
53
63
|
const dataRef = useRef(data);
|
|
54
64
|
const mountedRef = useRef(true);
|
|
55
65
|
const responseRef = useRef(null);
|
|
56
66
|
const processedRef = useRef(false);
|
|
57
67
|
const safeCallback = useSafeCallback(callback, notifyMessage);
|
|
58
68
|
const resetTimeoutRef = useRef(null);
|
|
69
|
+
|
|
70
|
+
// Animation values for icons
|
|
71
|
+
const iconScaleAnim = useRef(new Animated.Value(1)).current;
|
|
72
|
+
const iconOpacityAnim = useRef(new Animated.Value(0)).current;
|
|
59
73
|
|
|
60
|
-
|
|
74
|
+
// Cleanup on unmount
|
|
61
75
|
useEffect(() => {
|
|
76
|
+
console.log("๐ง BiometricVerificationModal mounted");
|
|
77
|
+
|
|
62
78
|
return () => {
|
|
79
|
+
console.log("๐งน BiometricVerificationModal unmounting - cleaning up");
|
|
63
80
|
mountedRef.current = false;
|
|
64
81
|
if (resetTimeoutRef.current) {
|
|
65
82
|
clearTimeout(resetTimeoutRef.current);
|
|
83
|
+
console.log("โน๏ธ Cleared reset timeout");
|
|
66
84
|
}
|
|
85
|
+
clearNotification();
|
|
67
86
|
};
|
|
68
87
|
}, []);
|
|
69
88
|
|
|
70
89
|
useEffect(() => {
|
|
71
90
|
dataRef.current = data;
|
|
91
|
+
console.log(
|
|
92
|
+
"๐ Updated dataRef with new data:",
|
|
93
|
+
data ? "Available" : "Empty"
|
|
94
|
+
);
|
|
72
95
|
}, [data]);
|
|
73
96
|
|
|
97
|
+
const animateIcon = useCallback(() => {
|
|
98
|
+
// Reset animation
|
|
99
|
+
iconScaleAnim.setValue(1);
|
|
100
|
+
iconOpacityAnim.setValue(0);
|
|
101
|
+
|
|
102
|
+
// Start animation sequence
|
|
103
|
+
Animated.sequence([
|
|
104
|
+
Animated.parallel([
|
|
105
|
+
Animated.timing(iconOpacityAnim, {
|
|
106
|
+
toValue: 1,
|
|
107
|
+
duration: 300,
|
|
108
|
+
useNativeDriver: true,
|
|
109
|
+
}),
|
|
110
|
+
Animated.spring(iconScaleAnim, {
|
|
111
|
+
toValue: 1.2,
|
|
112
|
+
friction: 3,
|
|
113
|
+
useNativeDriver: true,
|
|
114
|
+
}),
|
|
115
|
+
]),
|
|
116
|
+
Animated.spring(iconScaleAnim, {
|
|
117
|
+
toValue: 1,
|
|
118
|
+
friction: 5,
|
|
119
|
+
useNativeDriver: true,
|
|
120
|
+
}),
|
|
121
|
+
]).start();
|
|
122
|
+
}, [iconScaleAnim, iconOpacityAnim]);
|
|
123
|
+
|
|
74
124
|
const updateState = useCallback((newState) => {
|
|
75
125
|
if (mountedRef.current) {
|
|
76
126
|
setState((prev) => {
|
|
77
127
|
const merged = { ...prev, ...newState };
|
|
78
|
-
|
|
128
|
+
if (JSON.stringify(prev) !== JSON.stringify(merged)) {
|
|
129
|
+
console.log("๐ State updated:", merged);
|
|
130
|
+
|
|
131
|
+
// Animate icon when step changes
|
|
132
|
+
if (newState.currentStep && newState.currentStep !== prev.currentStep) {
|
|
133
|
+
animateIcon();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return merged;
|
|
137
|
+
}
|
|
138
|
+
return prev;
|
|
79
139
|
});
|
|
140
|
+
} else {
|
|
141
|
+
console.log("๐ซ State update skipped - component unmounted");
|
|
80
142
|
}
|
|
81
|
-
}, []);
|
|
143
|
+
}, [animateIcon]);
|
|
82
144
|
|
|
83
145
|
const resetState = useCallback(() => {
|
|
84
|
-
console.log("๐ Resetting biometric modal
|
|
146
|
+
console.log("๐ Resetting biometric modal state");
|
|
147
|
+
onclose(false);
|
|
85
148
|
setState({
|
|
86
149
|
isLoading: false,
|
|
150
|
+
loadingType: LOADING_TYPES.NONE,
|
|
87
151
|
currentStep: "Start",
|
|
88
152
|
employeeData: null,
|
|
89
153
|
animationState: ANIMATION_STATES.FACE_SCAN,
|
|
@@ -91,112 +155,152 @@ const BiometricVerificationModal = React.memo(
|
|
|
91
155
|
setModalVisible(false);
|
|
92
156
|
processedRef.current = false;
|
|
93
157
|
resetCountdown();
|
|
94
|
-
|
|
158
|
+
clearNotification();
|
|
159
|
+
|
|
95
160
|
if (resetTimeoutRef.current) {
|
|
96
161
|
clearTimeout(resetTimeoutRef.current);
|
|
97
162
|
resetTimeoutRef.current = null;
|
|
163
|
+
console.log("โน๏ธ Cleared reset timeout during reset");
|
|
98
164
|
}
|
|
99
|
-
}, [resetCountdown]);
|
|
165
|
+
}, [resetCountdown, clearNotification]);
|
|
100
166
|
|
|
101
167
|
const handleProcessError = useCallback(
|
|
102
168
|
(message, errorObj = null) => {
|
|
103
|
-
|
|
169
|
+
console.error("โ Process Error:", message, errorObj || "");
|
|
170
|
+
if (errorObj) console.error("Error details:", errorObj);
|
|
171
|
+
|
|
104
172
|
notifyMessage(message, "error");
|
|
105
173
|
updateState({
|
|
106
174
|
animationState: ANIMATION_STATES.ERROR,
|
|
107
175
|
isLoading: false,
|
|
176
|
+
loadingType: LOADING_TYPES.NONE,
|
|
108
177
|
});
|
|
109
|
-
|
|
178
|
+
|
|
110
179
|
if (resetTimeoutRef.current) {
|
|
111
180
|
clearTimeout(resetTimeoutRef.current);
|
|
112
181
|
}
|
|
113
|
-
resetTimeoutRef.current = setTimeout(
|
|
182
|
+
resetTimeoutRef.current = setTimeout(() => {
|
|
183
|
+
console.log("โฐ Error timeout completed - resetting state");
|
|
184
|
+
resetState();
|
|
185
|
+
}, 1200);
|
|
114
186
|
},
|
|
115
187
|
[notifyMessage, resetState, updateState]
|
|
116
188
|
);
|
|
117
189
|
|
|
118
190
|
const handleCountdownFinish = useCallback(() => {
|
|
191
|
+
console.log("โฐ Countdown finished");
|
|
119
192
|
handleProcessError("Time is up! Please try again.");
|
|
120
|
-
if (navigation.canGoBack())
|
|
193
|
+
if (navigation.canGoBack()) {
|
|
194
|
+
console.log("โฉ๏ธ Navigating back due to timeout");
|
|
195
|
+
navigation.goBack();
|
|
196
|
+
}
|
|
121
197
|
}, [handleProcessError, navigation]);
|
|
122
198
|
|
|
123
199
|
const validateApiUrl = useCallback(() => {
|
|
124
200
|
if (!apiurl || typeof apiurl !== "string") {
|
|
201
|
+
console.error("โ Invalid API URL:", apiurl);
|
|
125
202
|
handleProcessError("Invalid API URL configuration.");
|
|
126
203
|
return false;
|
|
127
204
|
}
|
|
205
|
+
console.log("โ
API URL validated:", apiurl);
|
|
128
206
|
return true;
|
|
129
207
|
}, [apiurl, handleProcessError]);
|
|
130
208
|
|
|
131
209
|
const uploadFaceScan = useCallback(
|
|
132
210
|
async (selfie) => {
|
|
211
|
+
console.log("๐ธ Uploading face scan");
|
|
212
|
+
|
|
133
213
|
if (!validateApiUrl()) return;
|
|
134
214
|
const currentData = dataRef.current;
|
|
215
|
+
|
|
135
216
|
if (!currentData) {
|
|
217
|
+
console.error("โ No employee data available");
|
|
136
218
|
handleProcessError("Employee data not found.");
|
|
137
219
|
return;
|
|
138
220
|
}
|
|
139
|
-
|
|
221
|
+
|
|
140
222
|
updateState({
|
|
141
223
|
isLoading: true,
|
|
224
|
+
loadingType: LOADING_TYPES.FACE_RECOGNITION,
|
|
142
225
|
animationState: ANIMATION_STATES.PROCESSING,
|
|
143
226
|
});
|
|
144
|
-
|
|
227
|
+
|
|
145
228
|
InteractionManager.runAfterInteractions(async () => {
|
|
146
229
|
let base64;
|
|
147
230
|
try {
|
|
231
|
+
console.log("๐ผ๏ธ Converting image to base64");
|
|
232
|
+
updateState({
|
|
233
|
+
loadingType: LOADING_TYPES.IMAGE_PROCESSING,
|
|
234
|
+
});
|
|
235
|
+
|
|
148
236
|
base64 = await convertImageToBase64(selfie?.uri);
|
|
237
|
+
console.log("โ
Image converted successfully");
|
|
149
238
|
} catch (err) {
|
|
239
|
+
console.error("โ Image conversion failed:", err);
|
|
150
240
|
handleProcessError("Image conversion failed.", err);
|
|
151
241
|
return;
|
|
152
242
|
}
|
|
153
|
-
|
|
243
|
+
|
|
154
244
|
if (!base64) {
|
|
245
|
+
console.error("โ Empty base64 data");
|
|
155
246
|
handleProcessError("Failed to process image.");
|
|
156
247
|
return;
|
|
157
248
|
}
|
|
158
|
-
|
|
249
|
+
|
|
159
250
|
try {
|
|
160
251
|
const body = { image: base64 };
|
|
161
252
|
const header = { faceid: currentData };
|
|
162
253
|
const buttonapi = `${apiurl}python/recognize`;
|
|
163
|
-
console.log("
|
|
164
|
-
|
|
254
|
+
console.log("๐ Calling face recognition API:", buttonapi);
|
|
255
|
+
|
|
256
|
+
updateState({
|
|
257
|
+
loadingType: LOADING_TYPES.NETWORK_REQUEST,
|
|
258
|
+
});
|
|
259
|
+
|
|
165
260
|
const response = await networkServiceCall(
|
|
166
261
|
"POST",
|
|
167
262
|
buttonapi,
|
|
168
263
|
header,
|
|
169
264
|
body
|
|
170
265
|
);
|
|
171
|
-
|
|
266
|
+
|
|
267
|
+
console.log("๐จ API Response:", response);
|
|
268
|
+
|
|
172
269
|
if (response?.httpstatus === 200) {
|
|
270
|
+
console.log("โ
Face recognition successful");
|
|
173
271
|
responseRef.current = response;
|
|
174
272
|
updateState({
|
|
175
273
|
employeeData: response.data?.data || null,
|
|
176
274
|
animationState: ANIMATION_STATES.SUCCESS,
|
|
177
275
|
isLoading: false,
|
|
276
|
+
loadingType: LOADING_TYPES.NONE,
|
|
178
277
|
});
|
|
179
|
-
|
|
278
|
+
|
|
180
279
|
notifyMessage("Identity verified successfully!", "success");
|
|
181
|
-
|
|
280
|
+
|
|
182
281
|
if (qrscan) {
|
|
282
|
+
console.log("๐ Proceeding to QR code scan");
|
|
183
283
|
setTimeout(() => startQRCodeScan(), 1200);
|
|
184
284
|
} else {
|
|
285
|
+
console.log("โ
Verification complete - calling callback");
|
|
185
286
|
safeCallback(responseRef.current);
|
|
186
287
|
if (resetTimeoutRef.current) {
|
|
187
288
|
clearTimeout(resetTimeoutRef.current);
|
|
188
289
|
}
|
|
189
290
|
resetTimeoutRef.current = setTimeout(() => {
|
|
291
|
+
console.log("โฐ Success timeout completed - resetting");
|
|
190
292
|
resetState();
|
|
191
293
|
}, 1200);
|
|
192
294
|
}
|
|
193
295
|
} else {
|
|
296
|
+
console.warn("โ ๏ธ Face recognition failed:", response?.data?.error);
|
|
194
297
|
handleProcessError(
|
|
195
298
|
response?.data?.error?.message ||
|
|
196
299
|
"Face not recognized. Please try again."
|
|
197
300
|
);
|
|
198
301
|
}
|
|
199
302
|
} catch (error) {
|
|
303
|
+
console.error("โ Network request failed:", error);
|
|
200
304
|
handleProcessError(
|
|
201
305
|
"Connection error. Please check your network.",
|
|
202
306
|
error
|
|
@@ -212,99 +316,126 @@ const BiometricVerificationModal = React.memo(
|
|
|
212
316
|
updateState,
|
|
213
317
|
validateApiUrl,
|
|
214
318
|
safeCallback,
|
|
319
|
+
handleProcessError
|
|
215
320
|
]
|
|
216
321
|
);
|
|
217
322
|
|
|
218
|
-
const handleStartFaceScan = useCallback(() => {
|
|
219
|
-
updateState({
|
|
220
|
-
currentStep: "Identity Verification",
|
|
221
|
-
animationState: ANIMATION_STATES.FACE_SCAN,
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
navigation.navigate("CCaptureImageWithoutEdit", {
|
|
225
|
-
facedetection: true,
|
|
226
|
-
cameratype: "front",
|
|
227
|
-
onSelect: uploadFaceScan,
|
|
228
|
-
});
|
|
229
|
-
}, [navigation, updateState, uploadFaceScan]);
|
|
230
|
-
|
|
231
|
-
const startQRCodeScan = useCallback(() => {
|
|
232
|
-
updateState({
|
|
233
|
-
currentStep: "Location Verification",
|
|
234
|
-
animationState: ANIMATION_STATES.QR_SCAN,
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
navigation.navigate("CCaptureImageWithoutEdit", {
|
|
238
|
-
hidebuttons: true,
|
|
239
|
-
cameratype: "back",
|
|
240
|
-
cameramoduletype: 2,
|
|
241
|
-
onSelect: handleQRScanned,
|
|
242
|
-
});
|
|
243
|
-
}, [navigation, updateState, handleQRScanned]);
|
|
244
|
-
|
|
245
323
|
const handleQRScanned = useCallback(
|
|
246
324
|
async (qrCodeData) => {
|
|
325
|
+
console.log("๐ Processing scanned QR code");
|
|
326
|
+
|
|
247
327
|
if (!validateApiUrl()) return;
|
|
248
|
-
|
|
328
|
+
|
|
249
329
|
updateState({
|
|
250
330
|
animationState: ANIMATION_STATES.PROCESSING,
|
|
251
331
|
isLoading: true,
|
|
332
|
+
loadingType: LOADING_TYPES.LOCATION_VERIFICATION,
|
|
252
333
|
});
|
|
253
|
-
|
|
334
|
+
|
|
254
335
|
try {
|
|
336
|
+
console.log("๐ Requesting location permission");
|
|
337
|
+
updateState({
|
|
338
|
+
loadingType: LOADING_TYPES.LOCATION_PERMISSION,
|
|
339
|
+
});
|
|
340
|
+
|
|
255
341
|
const hasPermission = await requestLocationPermission();
|
|
342
|
+
|
|
256
343
|
if (!hasPermission) {
|
|
344
|
+
console.error("โ Location permission denied");
|
|
257
345
|
handleProcessError("Location permission not granted.");
|
|
258
346
|
return;
|
|
259
347
|
}
|
|
260
|
-
|
|
348
|
+
|
|
349
|
+
console.log("โ
Location permission granted");
|
|
350
|
+
|
|
261
351
|
const qrString =
|
|
262
352
|
typeof qrCodeData === "object" ? qrCodeData?.data : qrCodeData;
|
|
263
|
-
|
|
353
|
+
|
|
264
354
|
if (!qrString || typeof qrString !== "string") {
|
|
355
|
+
console.error("โ Invalid QR code data:", qrCodeData);
|
|
265
356
|
handleProcessError("Invalid QR code. Please try again.");
|
|
266
357
|
return;
|
|
267
358
|
}
|
|
268
|
-
|
|
359
|
+
|
|
360
|
+
console.log("๐ QR code content:", qrString);
|
|
361
|
+
|
|
362
|
+
updateState({
|
|
363
|
+
loadingType: LOADING_TYPES.GETTING_LOCATION,
|
|
364
|
+
});
|
|
365
|
+
|
|
269
366
|
const location = await getCurrentLocation();
|
|
367
|
+
console.log("๐ Device location:", location);
|
|
368
|
+
|
|
270
369
|
const [latStr, lngStr] = qrString.split(",");
|
|
271
370
|
const lat = parseFloat(latStr);
|
|
272
371
|
const lng = parseFloat(lngStr);
|
|
273
372
|
const validCoords = !isNaN(lat) && !isNaN(lng);
|
|
274
373
|
const validDev =
|
|
275
374
|
!isNaN(location?.latitude) && !isNaN(location?.longitude);
|
|
276
|
-
|
|
375
|
+
|
|
376
|
+
console.log(
|
|
377
|
+
"๐ Coordinates validation - QR:",
|
|
378
|
+
validCoords,
|
|
379
|
+
"Device:",
|
|
380
|
+
validDev
|
|
381
|
+
);
|
|
382
|
+
|
|
277
383
|
if (validCoords && validDev) {
|
|
384
|
+
updateState({
|
|
385
|
+
loadingType: LOADING_TYPES.CALCULATING_DISTANCE,
|
|
386
|
+
});
|
|
387
|
+
|
|
278
388
|
const distance = getDistanceInMeters(
|
|
279
389
|
lat,
|
|
280
390
|
lng,
|
|
281
391
|
location.latitude,
|
|
282
392
|
location.longitude
|
|
283
393
|
);
|
|
284
|
-
|
|
394
|
+
|
|
395
|
+
console.log(
|
|
396
|
+
"๐ Distance calculated:",
|
|
397
|
+
distance.toFixed(0),
|
|
398
|
+
"meters"
|
|
399
|
+
);
|
|
400
|
+
|
|
285
401
|
if (distance <= MAX_DISTANCE_METERS) {
|
|
402
|
+
console.log("โ
Location verified successfully");
|
|
286
403
|
safeCallback(responseRef.current);
|
|
287
404
|
notifyMessage("Location verified successfully!", "success");
|
|
288
405
|
updateState({
|
|
289
406
|
animationState: ANIMATION_STATES.SUCCESS,
|
|
290
407
|
isLoading: false,
|
|
408
|
+
loadingType: LOADING_TYPES.NONE,
|
|
291
409
|
});
|
|
292
|
-
|
|
410
|
+
|
|
293
411
|
if (resetTimeoutRef.current) {
|
|
294
412
|
clearTimeout(resetTimeoutRef.current);
|
|
295
413
|
}
|
|
296
414
|
resetTimeoutRef.current = setTimeout(() => {
|
|
415
|
+
console.log("โฐ Location success timeout - resetting");
|
|
297
416
|
resetState();
|
|
298
417
|
}, 1200);
|
|
299
418
|
} else {
|
|
419
|
+
console.warn(
|
|
420
|
+
"โ ๏ธ Location mismatch:",
|
|
421
|
+
distance.toFixed(0),
|
|
422
|
+
"m away"
|
|
423
|
+
);
|
|
300
424
|
handleProcessError(
|
|
301
425
|
`Location mismatch (${distance.toFixed(0)}m away).`
|
|
302
426
|
);
|
|
303
427
|
}
|
|
304
428
|
} else {
|
|
429
|
+
console.error("โ Invalid coordinates:", {
|
|
430
|
+
qrLat: lat,
|
|
431
|
+
qrLng: lng,
|
|
432
|
+
devLat: location.latitude,
|
|
433
|
+
devLng: location.longitude,
|
|
434
|
+
});
|
|
305
435
|
handleProcessError("Invalid coordinates in QR code.");
|
|
306
436
|
}
|
|
307
437
|
} catch (error) {
|
|
438
|
+
console.error("โ Location verification failed:", error);
|
|
308
439
|
handleProcessError(
|
|
309
440
|
"Unable to verify location. Please try again.",
|
|
310
441
|
error
|
|
@@ -319,17 +450,62 @@ const BiometricVerificationModal = React.memo(
|
|
|
319
450
|
updateState,
|
|
320
451
|
validateApiUrl,
|
|
321
452
|
safeCallback,
|
|
453
|
+
handleProcessError
|
|
322
454
|
]
|
|
323
455
|
);
|
|
324
456
|
|
|
457
|
+
const handleImageCapture = useCallback(
|
|
458
|
+
async (capturedData) => {
|
|
459
|
+
console.log("๐ท Image captured for step:", state.currentStep);
|
|
460
|
+
|
|
461
|
+
if (state.currentStep === "Identity Verification") {
|
|
462
|
+
console.log("๐ค Processing face verification");
|
|
463
|
+
uploadFaceScan(capturedData);
|
|
464
|
+
} else if (state.currentStep === "Location Verification") {
|
|
465
|
+
console.log("๐ Processing QR code verification");
|
|
466
|
+
handleQRScanned(capturedData);
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
[state.currentStep, uploadFaceScan, handleQRScanned]
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
const handleStartFaceScan = useCallback(() => {
|
|
473
|
+
console.log("๐ค Starting face scan");
|
|
474
|
+
updateState({
|
|
475
|
+
currentStep: "Identity Verification",
|
|
476
|
+
animationState: ANIMATION_STATES.FACE_SCAN,
|
|
477
|
+
});
|
|
478
|
+
setCameraType("front");
|
|
479
|
+
}, [updateState]);
|
|
480
|
+
|
|
481
|
+
const startQRCodeScan = useCallback(() => {
|
|
482
|
+
console.log("๐ Starting QR code scan");
|
|
483
|
+
updateState({
|
|
484
|
+
currentStep: "Location Verification",
|
|
485
|
+
animationState: ANIMATION_STATES.QR_SCAN,
|
|
486
|
+
});
|
|
487
|
+
setCameraType("back");
|
|
488
|
+
}, [updateState]);
|
|
489
|
+
|
|
490
|
+
const toggleCamera = useCallback(() => {
|
|
491
|
+
console.log("๐ Toggling camera");
|
|
492
|
+
setCameraType((prevType) => {
|
|
493
|
+
const newType = prevType === "front" ? "back" : "front";
|
|
494
|
+
console.log("๐ท Switching to camera:", newType);
|
|
495
|
+
return newType;
|
|
496
|
+
});
|
|
497
|
+
}, []);
|
|
498
|
+
|
|
325
499
|
const startProcess = useCallback(() => {
|
|
500
|
+
console.log("๐ Starting verification process");
|
|
326
501
|
startCountdown(COUNTDOWN_DURATION, handleCountdownFinish);
|
|
327
502
|
handleStartFaceScan();
|
|
328
503
|
}, [handleCountdownFinish, handleStartFaceScan, startCountdown]);
|
|
329
504
|
|
|
330
505
|
useEffect(() => {
|
|
331
|
-
if (data && !modalVisible) {
|
|
332
|
-
console.log("๐ฅ New
|
|
506
|
+
if (data && !modalVisible && !processedRef.current) {
|
|
507
|
+
console.log("๐ฅ New data received, opening modal");
|
|
508
|
+
processedRef.current = true;
|
|
333
509
|
setModalVisible(true);
|
|
334
510
|
startProcess();
|
|
335
511
|
}
|
|
@@ -342,6 +518,12 @@ const BiometricVerificationModal = React.memo(
|
|
|
342
518
|
[state.isLoading, state.animationState, state.currentStep, apiurl]
|
|
343
519
|
);
|
|
344
520
|
|
|
521
|
+
const shouldShowCamera =
|
|
522
|
+
(state.currentStep === "Identity Verification" ||
|
|
523
|
+
state.currentStep === "Location Verification") &&
|
|
524
|
+
state.animationState !== ANIMATION_STATES.SUCCESS &&
|
|
525
|
+
state.animationState !== ANIMATION_STATES.ERROR;
|
|
526
|
+
|
|
345
527
|
return (
|
|
346
528
|
<>
|
|
347
529
|
<Modal
|
|
@@ -351,41 +533,60 @@ const BiometricVerificationModal = React.memo(
|
|
|
351
533
|
onRequestClose={resetState}
|
|
352
534
|
statusBarTranslucent
|
|
353
535
|
>
|
|
354
|
-
<View style={styles.
|
|
536
|
+
<View style={styles.modalContainer}>
|
|
355
537
|
<TouchableOpacity
|
|
356
|
-
style={styles.
|
|
538
|
+
style={styles.closeButton}
|
|
357
539
|
onPress={resetState}
|
|
358
540
|
accessibilityLabel="Close modal"
|
|
359
541
|
hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
|
|
360
542
|
>
|
|
361
543
|
<Icon name="close" size={24} color={COLORS.light} />
|
|
362
544
|
</TouchableOpacity>
|
|
363
|
-
|
|
364
|
-
<
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
545
|
+
|
|
546
|
+
<View style={styles.headerContainer}>
|
|
547
|
+
<StepIcon
|
|
548
|
+
currentStep={state.currentStep}
|
|
549
|
+
animationState={state.animationState}
|
|
550
|
+
scaleAnim={iconScaleAnim}
|
|
551
|
+
opacityAnim={iconOpacityAnim}
|
|
552
|
+
/>
|
|
553
|
+
|
|
554
|
+
<View style={styles.titleContainer}>
|
|
555
|
+
<Text style={styles.title}>Biometric Verification</Text>
|
|
556
|
+
<Text style={styles.subtitle}>{state.currentStep}</Text>
|
|
557
|
+
</View>
|
|
558
|
+
</View>
|
|
559
|
+
|
|
560
|
+
<StepIndicator currentStep={state.currentStep} />
|
|
561
|
+
|
|
562
|
+
{shouldShowCamera && !state.isLoading && (
|
|
563
|
+
<CaptureImageWithoutEdit
|
|
564
|
+
cameraType={cameraType}
|
|
565
|
+
onCapture={handleImageCapture}
|
|
566
|
+
onToggleCamera={toggleCamera}
|
|
567
|
+
showCodeScanner={state.currentStep === "Location Verification"}
|
|
568
|
+
isLoading={state.isLoading}
|
|
569
|
+
currentStep={state.currentStep}
|
|
371
570
|
/>
|
|
372
571
|
)}
|
|
373
|
-
|
|
572
|
+
|
|
573
|
+
{state.employeeData && (
|
|
574
|
+
<CCard employeeData={state.employeeData} apiurl={apiurl} />
|
|
575
|
+
)}
|
|
576
|
+
|
|
374
577
|
<Notification
|
|
375
578
|
notification={notification}
|
|
376
579
|
fadeAnim={fadeAnim}
|
|
377
580
|
slideAnim={slideAnim}
|
|
378
581
|
/>
|
|
379
|
-
|
|
582
|
+
|
|
380
583
|
<CountdownTimer
|
|
381
584
|
duration={COUNTDOWN_DURATION}
|
|
382
585
|
currentTime={countdown}
|
|
383
586
|
/>
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
source={loaderSource}
|
|
388
|
-
isLoading={state.isLoading}
|
|
587
|
+
<Loader
|
|
588
|
+
gifSource={loaderSource}
|
|
589
|
+
visible={state.isLoading}
|
|
389
590
|
/>
|
|
390
591
|
</View>
|
|
391
592
|
</Modal>
|
|
@@ -394,4 +595,54 @@ const BiometricVerificationModal = React.memo(
|
|
|
394
595
|
}
|
|
395
596
|
);
|
|
396
597
|
|
|
598
|
+
const styles = StyleSheet.create({
|
|
599
|
+
modalContainer: {
|
|
600
|
+
flex: 1,
|
|
601
|
+
backgroundColor: 'rgba(0, 0, 0, 0.85)',
|
|
602
|
+
justifyContent: 'center',
|
|
603
|
+
alignItems: 'center',
|
|
604
|
+
paddingVertical: 50,
|
|
605
|
+
},
|
|
606
|
+
headerContainer: {
|
|
607
|
+
flexDirection: 'row',
|
|
608
|
+
alignItems: 'center',
|
|
609
|
+
marginBottom: 15,
|
|
610
|
+
},
|
|
611
|
+
titleContainer: {
|
|
612
|
+
flex: 1,
|
|
613
|
+
},
|
|
614
|
+
title: {
|
|
615
|
+
fontSize: 26,
|
|
616
|
+
fontWeight: '700',
|
|
617
|
+
color: COLORS.light,
|
|
618
|
+
marginBottom: 5,
|
|
619
|
+
textAlign: 'left',
|
|
620
|
+
fontFamily: Platform.OS === 'ios' ? 'Helvetica Neue' : 'sans-serif',
|
|
621
|
+
textShadowColor: 'rgba(0, 0, 0, 0.3)',
|
|
622
|
+
textShadowOffset: { width: 1, height: 1 },
|
|
623
|
+
textShadowRadius: 3,
|
|
624
|
+
},
|
|
625
|
+
subtitle: {
|
|
626
|
+
fontSize: 18,
|
|
627
|
+
color: COLORS.light,
|
|
628
|
+
marginBottom: 0,
|
|
629
|
+
fontWeight: '600',
|
|
630
|
+
textAlign: 'left',
|
|
631
|
+
opacity: 0.9,
|
|
632
|
+
fontFamily: Platform.OS === 'ios' ? 'Helvetica Neue' : 'sans-serif-medium',
|
|
633
|
+
textShadowColor: 'rgba(0, 0, 0, 0.2)',
|
|
634
|
+
textShadowOffset: { width: 1, height: 1 },
|
|
635
|
+
textShadowRadius: 2,
|
|
636
|
+
},
|
|
637
|
+
closeButton: {
|
|
638
|
+
position: 'absolute',
|
|
639
|
+
top: Platform.OS === 'ios' ? 50 : 30,
|
|
640
|
+
right: 20,
|
|
641
|
+
zIndex: 10,
|
|
642
|
+
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
|
643
|
+
borderRadius: 20,
|
|
644
|
+
padding: 8,
|
|
645
|
+
},
|
|
646
|
+
});
|
|
647
|
+
|
|
397
648
|
export default BiometricVerificationModal;
|