react-native-biometric-verifier 0.0.8 → 0.0.9
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/hooks/useSafeCallback.js +24 -0
- package/src/index.js +339 -289
package/package.json
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook to safely execute a callback function with error handling
|
|
5
|
+
*/
|
|
6
|
+
export const useSafeCallback = (callback, notifyMessage) => {
|
|
7
|
+
return useCallback(
|
|
8
|
+
(response) => {
|
|
9
|
+
if (typeof callback === "function") {
|
|
10
|
+
try {
|
|
11
|
+
callback(response);
|
|
12
|
+
} catch (err) {
|
|
13
|
+
console.error("Callback execution failed:", err);
|
|
14
|
+
if (typeof notifyMessage === "function") {
|
|
15
|
+
notifyMessage("Unexpected error while processing callback.", "error");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
console.log("Biometric Verification Response:", response);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
[callback, notifyMessage]
|
|
23
|
+
);
|
|
24
|
+
};
|
package/src/index.js
CHANGED
|
@@ -1,326 +1,376 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
useState,
|
|
3
|
+
useEffect,
|
|
4
|
+
useRef,
|
|
5
|
+
useCallback,
|
|
6
|
+
useMemo,
|
|
7
|
+
} from "react";
|
|
2
8
|
import {
|
|
3
9
|
View,
|
|
4
10
|
TouchableOpacity,
|
|
5
11
|
Text,
|
|
6
12
|
Modal,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
13
|
+
InteractionManager,
|
|
14
|
+
} from "react-native";
|
|
15
|
+
import Icon from "react-native-vector-icons/MaterialIcons";
|
|
16
|
+
import { useCountdown } from "./hooks/useCountdown";
|
|
17
|
+
import { useGeolocation } from "./hooks/useGeolocation";
|
|
18
|
+
import { useImageProcessing } from "./hooks/useImageProcessing";
|
|
19
|
+
import { useNotifyMessage } from "./hooks/useNotifyMessage";
|
|
20
|
+
import { getDistanceInMeters } from "./utils/distanceCalculator";
|
|
14
21
|
import {
|
|
15
22
|
ANIMATION_STATES,
|
|
16
23
|
COLORS,
|
|
17
24
|
COUNTDOWN_DURATION,
|
|
18
25
|
MAX_DISTANCE_METERS,
|
|
19
|
-
} from
|
|
20
|
-
import Loader from
|
|
21
|
-
import { CountdownTimer } from
|
|
22
|
-
import { EmployeeCard } from
|
|
23
|
-
import { Notification } from
|
|
24
|
-
import { StateIndicator } from
|
|
25
|
-
import { styles } from
|
|
26
|
-
import { useNavigation } from
|
|
27
|
-
import networkServiceCall from
|
|
28
|
-
import { getLoaderGif } from
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const dataRef = useRef(data);
|
|
47
|
-
const mountedRef = useRef(true);
|
|
48
|
-
const responseRef = useRef(null);
|
|
49
|
-
|
|
50
|
-
const safeCallback = useCallback(
|
|
51
|
-
(response) => {
|
|
52
|
-
if (typeof callback === 'function') {
|
|
53
|
-
try {
|
|
54
|
-
callback(response);
|
|
55
|
-
} catch (err) {
|
|
56
|
-
console.error('Callback execution failed:', err);
|
|
57
|
-
notifyMessage('Unexpected error while processing callback.', 'error');
|
|
58
|
-
}
|
|
59
|
-
} else {
|
|
60
|
-
console.log('Biometric Verification Response:', response);
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
[callback, notifyMessage]
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
return () => {
|
|
68
|
-
mountedRef.current = false;
|
|
69
|
-
};
|
|
70
|
-
}, []);
|
|
71
|
-
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
dataRef.current = data;
|
|
74
|
-
}, [data]);
|
|
75
|
-
|
|
76
|
-
const updateState = useCallback((newState) => {
|
|
77
|
-
if (mountedRef.current) {
|
|
78
|
-
setState((prev) => ({ ...prev, ...newState }));
|
|
79
|
-
}
|
|
80
|
-
}, []);
|
|
81
|
-
|
|
82
|
-
const resetState = useCallback(() => {
|
|
83
|
-
updateState({
|
|
84
|
-
currentStep: 'Start',
|
|
26
|
+
} from "./utils/constants";
|
|
27
|
+
import Loader from "./components/Loader";
|
|
28
|
+
import { CountdownTimer } from "./components/CountdownTimer";
|
|
29
|
+
import { EmployeeCard } from "./components/EmployeeCard";
|
|
30
|
+
import { Notification } from "./components/Notification";
|
|
31
|
+
import { StateIndicator } from "./components/StateIndicator";
|
|
32
|
+
import { styles } from "./components/styles";
|
|
33
|
+
import { useNavigation } from "@react-navigation/native";
|
|
34
|
+
import networkServiceCall from "./utils/NetworkServiceCall";
|
|
35
|
+
import { getLoaderGif } from "./utils/getLoaderGif";
|
|
36
|
+
import { useSafeCallback } from "./hooks/useSafeCallback";
|
|
37
|
+
|
|
38
|
+
const BiometricVerificationModal = React.memo(
|
|
39
|
+
({ data, qrscan = false, callback, apiurl }) => {
|
|
40
|
+
const navigation = useNavigation();
|
|
41
|
+
|
|
42
|
+
const { countdown, startCountdown, resetCountdown } = useCountdown();
|
|
43
|
+
const { requestLocationPermission, getCurrentLocation } = useGeolocation();
|
|
44
|
+
const { convertImageToBase64 } = useImageProcessing();
|
|
45
|
+
const { notification, fadeAnim, slideAnim, notifyMessage } =
|
|
46
|
+
useNotifyMessage();
|
|
47
|
+
|
|
48
|
+
const [modalVisible, setModalVisible] = useState(false);
|
|
49
|
+
const [state, setState] = useState({
|
|
50
|
+
isLoading: false,
|
|
51
|
+
currentStep: "Start",
|
|
85
52
|
employeeData: null,
|
|
86
53
|
animationState: ANIMATION_STATES.FACE_SCAN,
|
|
87
|
-
isLoading: false,
|
|
88
54
|
});
|
|
89
|
-
resetCountdown();
|
|
90
|
-
}, [resetCountdown, updateState]);
|
|
91
|
-
|
|
92
|
-
const handleProcessError = useCallback(
|
|
93
|
-
(message, errorObj = null) => {
|
|
94
|
-
if (errorObj) console.error(message, errorObj);
|
|
95
|
-
notifyMessage(message, 'error');
|
|
96
|
-
updateState({ animationState: ANIMATION_STATES.ERROR });
|
|
97
|
-
setTimeout(resetState, 1200);
|
|
98
|
-
},
|
|
99
|
-
[notifyMessage, resetState, updateState]
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
const handleCountdownFinish = useCallback(() => {
|
|
103
|
-
handleProcessError('Time is up! Please try again.');
|
|
104
|
-
updateState({ modalVisible: false });
|
|
105
|
-
if (navigation.canGoBack()) navigation.goBack();
|
|
106
|
-
}, [handleProcessError, navigation, updateState]);
|
|
107
|
-
|
|
108
|
-
const validateApiUrl = useCallback(() => {
|
|
109
|
-
if (!apiurl || typeof apiurl !== 'string') {
|
|
110
|
-
handleProcessError('Invalid API URL configuration.');
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
return true;
|
|
114
|
-
}, [apiurl, handleProcessError]);
|
|
115
|
-
|
|
116
|
-
const uploadFaceScan = useCallback(
|
|
117
|
-
async (selfie) => {
|
|
118
|
-
if (!validateApiUrl()) return;
|
|
119
|
-
|
|
120
|
-
const currentData = dataRef.current;
|
|
121
|
-
if (!currentData) {
|
|
122
|
-
handleProcessError('Employee data not found.');
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
55
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
56
|
+
const dataRef = useRef(data);
|
|
57
|
+
const mountedRef = useRef(true);
|
|
58
|
+
const responseRef = useRef(null);
|
|
59
|
+
const processedRef = useRef(false);
|
|
60
|
+
const lastDataRef = useRef(null);
|
|
61
|
+
|
|
62
|
+
const safeCallback = useSafeCallback(callback, notifyMessage);
|
|
63
|
+
|
|
64
|
+
/** Cleanup on unmount */
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
return () => {
|
|
67
|
+
mountedRef.current = false;
|
|
68
|
+
};
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
dataRef.current = data;
|
|
73
|
+
}, [data]);
|
|
74
|
+
|
|
75
|
+
const updateState = useCallback((newState) => {
|
|
76
|
+
if (mountedRef.current) {
|
|
77
|
+
setState((prev) => {
|
|
78
|
+
const merged = { ...prev, ...newState };
|
|
79
|
+
return prev !== merged ? merged : prev;
|
|
80
|
+
});
|
|
132
81
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
animationState: ANIMATION_STATES.
|
|
82
|
+
}, []);
|
|
83
|
+
|
|
84
|
+
const resetState = useCallback(() => {
|
|
85
|
+
console.log("🔄 Resetting biometric modal...");
|
|
86
|
+
setState({
|
|
87
|
+
isLoading: false,
|
|
88
|
+
currentStep: "Start",
|
|
89
|
+
employeeData: null,
|
|
90
|
+
animationState: ANIMATION_STATES.FACE_SCAN,
|
|
142
91
|
});
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
} catch (error) {
|
|
174
|
-
handleProcessError('Connection error. Please check your network.', error);
|
|
175
|
-
} finally {
|
|
176
|
-
updateState({ isLoading: false });
|
|
92
|
+
setModalVisible(false);
|
|
93
|
+
processedRef.current = false;
|
|
94
|
+
resetCountdown();
|
|
95
|
+
}, [resetCountdown]);
|
|
96
|
+
|
|
97
|
+
const handleProcessError = useCallback(
|
|
98
|
+
(message, errorObj = null) => {
|
|
99
|
+
if (errorObj) console.error(message, errorObj);
|
|
100
|
+
notifyMessage(message, "error");
|
|
101
|
+
updateState({
|
|
102
|
+
animationState: ANIMATION_STATES.ERROR,
|
|
103
|
+
isLoading: false,
|
|
104
|
+
});
|
|
105
|
+
setTimeout(resetState, 1200);
|
|
106
|
+
},
|
|
107
|
+
[notifyMessage, resetState, updateState]
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const handleCountdownFinish = useCallback(() => {
|
|
111
|
+
handleProcessError("Time is up! Please try again.");
|
|
112
|
+
resetState();
|
|
113
|
+
if (navigation.canGoBack()) navigation.goBack();
|
|
114
|
+
}, [handleProcessError, navigation, resetState]);
|
|
115
|
+
|
|
116
|
+
const validateApiUrl = useCallback(() => {
|
|
117
|
+
if (!apiurl || typeof apiurl !== "string") {
|
|
118
|
+
handleProcessError("Invalid API URL configuration.");
|
|
119
|
+
return false;
|
|
177
120
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
121
|
+
return true;
|
|
122
|
+
}, [apiurl, handleProcessError]);
|
|
123
|
+
|
|
124
|
+
const uploadFaceScan = useCallback(
|
|
125
|
+
async (selfie) => {
|
|
126
|
+
if (!validateApiUrl()) return;
|
|
127
|
+
const currentData = dataRef.current;
|
|
128
|
+
if (!currentData) {
|
|
129
|
+
handleProcessError("Employee data not found.");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
181
132
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
133
|
+
updateState({
|
|
134
|
+
isLoading: true,
|
|
135
|
+
animationState: ANIMATION_STATES.PROCESSING,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
InteractionManager.runAfterInteractions(async () => {
|
|
139
|
+
let base64;
|
|
140
|
+
try {
|
|
141
|
+
base64 = await convertImageToBase64(selfie?.uri);
|
|
142
|
+
} catch (err) {
|
|
143
|
+
handleProcessError("Image conversion failed.", err);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
193
146
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
});
|
|
199
|
-
navigation.navigate('CCaptureImageWithoutEdit', {
|
|
200
|
-
hidebuttons: true,
|
|
201
|
-
cameratype: 'back',
|
|
202
|
-
cameramoduletype: 2,
|
|
203
|
-
onSelect: handleQRScanned,
|
|
204
|
-
});
|
|
205
|
-
}, [navigation, updateState]);
|
|
147
|
+
if (!base64) {
|
|
148
|
+
handleProcessError("Failed to process image.");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
206
151
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
152
|
+
try {
|
|
153
|
+
const body = { image: base64 };
|
|
154
|
+
const header = { faceid: currentData };
|
|
155
|
+
const buttonapi = `${apiurl}python/recognize`;
|
|
156
|
+
console.log("buttonapi", buttonapi);
|
|
157
|
+
const response = await networkServiceCall(
|
|
158
|
+
"POST",
|
|
159
|
+
buttonapi,
|
|
160
|
+
header,
|
|
161
|
+
body
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
if (response?.httpstatus === 200) {
|
|
165
|
+
responseRef.current = response;
|
|
166
|
+
updateState({
|
|
167
|
+
employeeData: response.data?.data || null,
|
|
168
|
+
animationState: ANIMATION_STATES.SUCCESS,
|
|
169
|
+
isLoading: false,
|
|
170
|
+
});
|
|
171
|
+
notifyMessage("Identity verified successfully!", "success");
|
|
172
|
+
|
|
173
|
+
if (qrscan) {
|
|
174
|
+
setTimeout(() => startQRCodeScan(), 1200);
|
|
175
|
+
} else {
|
|
176
|
+
safeCallback(responseRef.current);
|
|
177
|
+
setTimeout(() => resetState(), 1200);
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
handleProcessError(
|
|
181
|
+
response?.data?.error?.message ||
|
|
182
|
+
"Face not recognized. Please try again."
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
} catch (error) {
|
|
186
|
+
handleProcessError(
|
|
187
|
+
"Connection error. Please check your network.",
|
|
188
|
+
error
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
[
|
|
194
|
+
convertImageToBase64,
|
|
195
|
+
notifyMessage,
|
|
196
|
+
qrscan,
|
|
197
|
+
resetState,
|
|
198
|
+
updateState,
|
|
199
|
+
validateApiUrl,
|
|
200
|
+
safeCallback,
|
|
201
|
+
]
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const handleStartFaceScan = useCallback(() => {
|
|
205
|
+
updateState({
|
|
206
|
+
currentStep: "Identity Verification",
|
|
207
|
+
animationState: ANIMATION_STATES.FACE_SCAN,
|
|
208
|
+
});
|
|
209
|
+
navigation.navigate("CCaptureImageWithoutEdit", {
|
|
210
|
+
facedetection: true,
|
|
211
|
+
cameratype: "front",
|
|
212
|
+
onSelect: uploadFaceScan,
|
|
213
|
+
});
|
|
214
|
+
}, [navigation, updateState, uploadFaceScan]);
|
|
210
215
|
|
|
216
|
+
const startQRCodeScan = useCallback(() => {
|
|
211
217
|
updateState({
|
|
212
|
-
|
|
213
|
-
|
|
218
|
+
currentStep: "Location Verification",
|
|
219
|
+
animationState: ANIMATION_STATES.QR_SCAN,
|
|
220
|
+
});
|
|
221
|
+
navigation.navigate("CCaptureImageWithoutEdit", {
|
|
222
|
+
hidebuttons: true,
|
|
223
|
+
cameratype: "back",
|
|
224
|
+
cameramoduletype: 2,
|
|
225
|
+
onSelect: handleQRScanned,
|
|
214
226
|
});
|
|
227
|
+
}, [navigation, updateState]);
|
|
215
228
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (!
|
|
219
|
-
handleProcessError('Location permission not granted.');
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
229
|
+
const handleQRScanned = useCallback(
|
|
230
|
+
async (qrCodeData) => {
|
|
231
|
+
if (!validateApiUrl()) return;
|
|
222
232
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
233
|
+
updateState({
|
|
234
|
+
animationState: ANIMATION_STATES.PROCESSING,
|
|
235
|
+
isLoading: true,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const hasPermission = await requestLocationPermission();
|
|
240
|
+
if (!hasPermission) {
|
|
241
|
+
handleProcessError("Location permission not granted.");
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
228
244
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
245
|
+
const qrString =
|
|
246
|
+
typeof qrCodeData === "object" ? qrCodeData?.data : qrCodeData;
|
|
247
|
+
if (!qrString || typeof qrString !== "string") {
|
|
248
|
+
handleProcessError("Invalid QR code. Please try again.");
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
233
251
|
|
|
234
|
-
|
|
235
|
-
|
|
252
|
+
const location = await getCurrentLocation();
|
|
253
|
+
const [latStr, lngStr] = qrString.split(",");
|
|
254
|
+
const lat = parseFloat(latStr);
|
|
255
|
+
const lng = parseFloat(lngStr);
|
|
236
256
|
|
|
237
|
-
|
|
238
|
-
const
|
|
257
|
+
const validCoords = !isNaN(lat) && !isNaN(lng);
|
|
258
|
+
const validDev =
|
|
259
|
+
!isNaN(location?.latitude) && !isNaN(location?.longitude);
|
|
239
260
|
|
|
240
|
-
if (
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
261
|
+
if (validCoords && validDev) {
|
|
262
|
+
const distance = getDistanceInMeters(
|
|
263
|
+
lat,
|
|
264
|
+
lng,
|
|
265
|
+
location.latitude,
|
|
266
|
+
location.longitude
|
|
267
|
+
);
|
|
244
268
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
269
|
+
if (distance <= MAX_DISTANCE_METERS) {
|
|
270
|
+
safeCallback(responseRef.current);
|
|
271
|
+
notifyMessage("Location verified successfully!", "success");
|
|
272
|
+
updateState({
|
|
273
|
+
animationState: ANIMATION_STATES.SUCCESS,
|
|
274
|
+
isLoading: false,
|
|
275
|
+
});
|
|
276
|
+
setTimeout(() => resetState(), 1200);
|
|
277
|
+
} else {
|
|
278
|
+
handleProcessError(
|
|
279
|
+
`Location mismatch (${distance.toFixed(0)}m away).`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
249
282
|
} else {
|
|
250
|
-
handleProcessError(
|
|
283
|
+
handleProcessError("Invalid coordinates in QR code.");
|
|
251
284
|
}
|
|
252
|
-
}
|
|
253
|
-
handleProcessError(
|
|
285
|
+
} catch (error) {
|
|
286
|
+
handleProcessError(
|
|
287
|
+
"Unable to verify location. Please try again.",
|
|
288
|
+
error
|
|
289
|
+
);
|
|
254
290
|
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
291
|
+
},
|
|
292
|
+
[
|
|
293
|
+
getCurrentLocation,
|
|
294
|
+
notifyMessage,
|
|
295
|
+
requestLocationPermission,
|
|
296
|
+
resetState,
|
|
297
|
+
updateState,
|
|
298
|
+
validateApiUrl,
|
|
299
|
+
safeCallback,
|
|
300
|
+
]
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
const startProcess = useCallback(() => {
|
|
304
|
+
startCountdown(COUNTDOWN_DURATION, handleCountdownFinish);
|
|
305
|
+
handleStartFaceScan();
|
|
306
|
+
}, [handleCountdownFinish, handleStartFaceScan, startCountdown]);
|
|
307
|
+
|
|
308
|
+
useEffect(() => {
|
|
309
|
+
if (data && data !== lastDataRef.current) {
|
|
310
|
+
console.log("📥 New donor data received:", data);
|
|
311
|
+
lastDataRef.current = data;
|
|
312
|
+
setModalVisible(true);
|
|
313
|
+
startProcess();
|
|
259
314
|
}
|
|
260
|
-
},
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
315
|
+
}, [data, startProcess]);
|
|
316
|
+
|
|
317
|
+
const loaderSource = useMemo(
|
|
318
|
+
() =>
|
|
319
|
+
state.isLoading &&
|
|
320
|
+
getLoaderGif(state.animationState, state.currentStep, apiurl),
|
|
321
|
+
[state.isLoading, state.animationState, state.currentStep, apiurl]
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
return (
|
|
325
|
+
<>
|
|
326
|
+
{modalVisible && (
|
|
327
|
+
<Modal
|
|
328
|
+
visible={modalVisible}
|
|
329
|
+
animationType="slide"
|
|
330
|
+
transparent
|
|
331
|
+
onRequestClose={resetState}
|
|
332
|
+
statusBarTranslucent
|
|
333
|
+
>
|
|
334
|
+
<View style={styles.modalBg}>
|
|
335
|
+
<TouchableOpacity
|
|
336
|
+
style={styles.close}
|
|
337
|
+
onPress={resetState}
|
|
338
|
+
accessibilityLabel="Close modal"
|
|
339
|
+
hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
|
|
340
|
+
>
|
|
341
|
+
<Icon name="close" size={24} color={COLORS.light} />
|
|
342
|
+
</TouchableOpacity>
|
|
343
|
+
|
|
344
|
+
<Text style={styles.title}>Biometric Verification</Text>
|
|
345
|
+
<Text style={styles.subTitle}>{state.currentStep}</Text>
|
|
346
|
+
|
|
347
|
+
<StateIndicator state={state.animationState} size={120} />
|
|
348
|
+
|
|
349
|
+
{state.employeeData && (
|
|
350
|
+
<EmployeeCard
|
|
351
|
+
employeeData={state.employeeData}
|
|
352
|
+
apiurl={apiurl}
|
|
353
|
+
/>
|
|
354
|
+
)}
|
|
355
|
+
|
|
356
|
+
<Notification
|
|
357
|
+
notification={notification}
|
|
358
|
+
fadeAnim={fadeAnim}
|
|
359
|
+
slideAnim={slideAnim}
|
|
360
|
+
/>
|
|
361
|
+
|
|
362
|
+
<CountdownTimer
|
|
363
|
+
duration={COUNTDOWN_DURATION}
|
|
364
|
+
currentTime={countdown}
|
|
365
|
+
/>
|
|
366
|
+
|
|
367
|
+
{loaderSource && <Loader source={loaderSource} />}
|
|
368
|
+
</View>
|
|
369
|
+
</Modal>
|
|
307
370
|
)}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
slideAnim={slideAnim}
|
|
313
|
-
/>
|
|
314
|
-
|
|
315
|
-
<CountdownTimer duration={COUNTDOWN_DURATION} currentTime={countdown} />
|
|
316
|
-
|
|
317
|
-
{state.isLoading &&
|
|
318
|
-
getLoaderGif(state.animationState, state.currentStep, apiurl) && (
|
|
319
|
-
<Loader source={getLoaderGif(state.animationState, state.currentStep, apiurl)} />
|
|
320
|
-
)}
|
|
321
|
-
</View>
|
|
322
|
-
</Modal>
|
|
323
|
-
);
|
|
324
|
-
});
|
|
371
|
+
</>
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
);
|
|
325
375
|
|
|
326
376
|
export default BiometricVerificationModal;
|