@trustchex/react-native-sdk 1.267.0 → 1.354.0

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.
Files changed (72) hide show
  1. package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +8 -2
  2. package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +5 -1
  3. package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +5 -1
  4. package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +29 -15
  5. package/lib/module/Screens/Static/OTPVerificationScreen.js +285 -0
  6. package/lib/module/Screens/Static/ResultScreen.js +90 -26
  7. package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +48 -134
  8. package/lib/module/Shared/Components/DebugNavigationPanel.js +252 -0
  9. package/lib/module/Shared/Components/EIDScanner.js +142 -17
  10. package/lib/module/Shared/Components/FaceCamera.js +23 -11
  11. package/lib/module/Shared/Components/IdentityDocumentCamera.js +295 -44
  12. package/lib/module/Shared/Components/NavigationManager.js +19 -3
  13. package/lib/module/Shared/Config/camera-enhancement.config.js +58 -0
  14. package/lib/module/Shared/Contexts/AppContext.js +1 -0
  15. package/lib/module/Shared/Libs/camera.utils.js +221 -1
  16. package/lib/module/Shared/Libs/frame-enhancement.utils.js +133 -0
  17. package/lib/module/Shared/Libs/mrz.utils.js +98 -1
  18. package/lib/module/Translation/Resources/en.js +30 -0
  19. package/lib/module/Translation/Resources/tr.js +30 -0
  20. package/lib/module/Trustchex.js +49 -39
  21. package/lib/module/version.js +1 -1
  22. package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
  23. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  24. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
  25. package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
  26. package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts +3 -0
  27. package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -0
  28. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  29. package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
  30. package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts +3 -0
  31. package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -0
  32. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  33. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
  34. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  35. package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
  36. package/lib/typescript/src/Shared/Config/camera-enhancement.config.d.ts +54 -0
  37. package/lib/typescript/src/Shared/Config/camera-enhancement.config.d.ts.map +1 -0
  38. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +2 -0
  39. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
  40. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +65 -1
  41. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +1 -1
  42. package/lib/typescript/src/Shared/Libs/frame-enhancement.utils.d.ts +25 -0
  43. package/lib/typescript/src/Shared/Libs/frame-enhancement.utils.d.ts.map +1 -0
  44. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
  45. package/lib/typescript/src/Translation/Resources/en.d.ts +30 -0
  46. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  47. package/lib/typescript/src/Translation/Resources/tr.d.ts +30 -0
  48. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  49. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  50. package/lib/typescript/src/version.d.ts +1 -1
  51. package/package.json +3 -3
  52. package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +6 -2
  53. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +3 -1
  54. package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +3 -1
  55. package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +27 -17
  56. package/src/Screens/Static/OTPVerificationScreen.tsx +379 -0
  57. package/src/Screens/Static/ResultScreen.tsx +160 -101
  58. package/src/Screens/Static/VerificationSessionCheckScreen.tsx +51 -196
  59. package/src/Shared/Components/DebugNavigationPanel.tsx +262 -0
  60. package/src/Shared/Components/EIDScanner.tsx +144 -19
  61. package/src/Shared/Components/FaceCamera.tsx +38 -21
  62. package/src/Shared/Components/IdentityDocumentCamera.tsx +399 -101
  63. package/src/Shared/Components/NavigationManager.tsx +19 -3
  64. package/src/Shared/Config/camera-enhancement.config.ts +46 -0
  65. package/src/Shared/Contexts/AppContext.ts +3 -0
  66. package/src/Shared/Libs/camera.utils.ts +240 -1
  67. package/src/Shared/Libs/frame-enhancement.utils.ts +217 -0
  68. package/src/Shared/Libs/mrz.utils.ts +78 -1
  69. package/src/Translation/Resources/en.ts +30 -0
  70. package/src/Translation/Resources/tr.ts +30 -0
  71. package/src/Trustchex.tsx +58 -46
  72. package/src/version.ts +1 -1
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
 
3
- import React, { useState, useEffect, useCallback } from 'react';
4
- import { Alert, View, Text, Image, StyleSheet } from 'react-native';
3
+ import React, { useState, useEffect, useCallback, useRef } from 'react';
4
+ import { Alert, View, Text, Image, StyleSheet, Animated } from 'react-native';
5
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
5
6
  import NFCManager from 'react-native-nfc-manager';
6
7
  import DeviceInfo from 'react-native-device-info';
7
8
  import { eidReader } from "../EIDReader/eidReader.js";
@@ -23,6 +24,7 @@ const EIDScanner = ({
23
24
  isNFCSupported
24
25
  }) => {
25
26
  useKeepAwake();
27
+ const insets = useSafeAreaInsets();
26
28
  const [isScanning, setIsScanning] = React.useState(false);
27
29
  const [hasNfc, setHasNFC] = React.useState(false);
28
30
  const [isEnabled, setIsEnabled] = React.useState(false);
@@ -30,6 +32,39 @@ const EIDScanner = ({
30
32
  const [documentMRZInfo, setDocumentMRZInfo] = React.useState();
31
33
  const [documentFaceImageMimeType, setDocumentFaceImageMimeType] = React.useState();
32
34
  const [progress, setProgress] = React.useState(0);
35
+ const [progressStage, setProgressStage] = React.useState('');
36
+
37
+ // Animation for pulse indicator
38
+ const pulseAnim = useRef(new Animated.Value(1)).current;
39
+
40
+ // Format date from YYMMDD to DD/MM/YYYY (matching Flutter SDK)
41
+ const formatDate = useCallback(dateStr => {
42
+ if (!dateStr) return '';
43
+ try {
44
+ // Check if it's YYMMDD format (6 digits from NFC)
45
+ if (dateStr.length === 6 && /^\d{6}$/.test(dateStr)) {
46
+ const yy = dateStr.substring(0, 2);
47
+ const mm = dateStr.substring(2, 4);
48
+ const dd = dateStr.substring(4, 6);
49
+
50
+ // Assume 19xx if YY >= 50, else 20xx
51
+ const year = parseInt(yy) >= 50 ? `19${yy}` : `20${yy}`;
52
+ return `${dd}/${mm}/${year}`;
53
+ }
54
+
55
+ // Otherwise try to parse as ISO 8601
56
+ const date = new Date(dateStr);
57
+ if (!isNaN(date.getTime())) {
58
+ const day = String(date.getDate()).padStart(2, '0');
59
+ const month = String(date.getMonth() + 1).padStart(2, '0');
60
+ const year = date.getFullYear();
61
+ return `${day}/${month}/${year}`;
62
+ }
63
+ return dateStr;
64
+ } catch {
65
+ return dateStr;
66
+ }
67
+ }, []);
33
68
  const [isScanned, setIsScanned] = React.useState(false);
34
69
  const [hasGuideShown, setHasGuideShown] = React.useState(false);
35
70
  const {
@@ -45,6 +80,7 @@ const EIDScanner = ({
45
80
  setIsScanning(true);
46
81
  setIsScanned(false);
47
82
  setProgress(0);
83
+ setProgressStage('');
48
84
 
49
85
  // Track EID scan start using analytics helper
50
86
  const docType = documentType || 'UNKNOWN';
@@ -71,6 +107,16 @@ const EIDScanner = ({
71
107
  if (documentNumber && dateOfBirth && dateOfExpiry) {
72
108
  const passportData = await eidReader(documentNumber, dateOfBirth, dateOfExpiry, progressValue => {
73
109
  setProgress(progressValue);
110
+ // Update stage message based on progress
111
+ if (progressValue <= 20) {
112
+ setProgressStage(t('eidScannerScreen.connecting'));
113
+ } else if (progressValue <= 30) {
114
+ setProgressStage(t('eidScannerScreen.readingMRZ'));
115
+ } else if (progressValue < 100) {
116
+ setProgressStage(t('eidScannerScreen.readingFaceImage'));
117
+ } else {
118
+ setProgressStage(t('eidScannerScreen.completing'));
119
+ }
74
120
  });
75
121
  if (passportData) {
76
122
  const scanDuration = Date.now() - startTime;
@@ -113,6 +159,7 @@ const EIDScanner = ({
113
159
  } finally {
114
160
  setIsScanning(false);
115
161
  setProgress(0);
162
+ setProgressStage('');
116
163
  setHasGuideShown(false);
117
164
  }
118
165
  }, [documentNumber, dateOfBirth, dateOfExpiry, documentType, hasNfc, isEnabled, attemptNumber, t]);
@@ -173,14 +220,56 @@ const EIDScanner = ({
173
220
  setVoiceGuidanceMessage(message);
174
221
  }
175
222
  }, [hasGuideShown, hasNfc, isEnabled, isScanning, progress, isScanned, documentType, t, appContext.currentWorkflowStep?.data?.voiceGuidanceActive]);
223
+
224
+ // Pulse animation effect when scanning
225
+ useEffect(() => {
226
+ if (isScanning && progress > 0) {
227
+ const animation = Animated.loop(Animated.sequence([Animated.timing(pulseAnim, {
228
+ toValue: 1.3,
229
+ duration: 800,
230
+ useNativeDriver: true
231
+ }), Animated.timing(pulseAnim, {
232
+ toValue: 1,
233
+ duration: 800,
234
+ useNativeDriver: true
235
+ })]));
236
+ animation.start();
237
+ return () => animation.stop();
238
+ } else {
239
+ pulseAnim.setValue(1);
240
+ }
241
+ }, [isScanning, progress, pulseAnim]);
176
242
  useEffect(() => {
177
243
  if (voiceGuidanceMessage && appContext.currentWorkflowStep?.data?.voiceGuidanceActive) {
178
244
  speakWithDebounce(voiceGuidanceMessage);
179
245
  }
180
246
  }, [voiceGuidanceMessage, appContext.currentWorkflowStep?.data?.voiceGuidanceActive]);
181
- return /*#__PURE__*/_jsx(View, {
247
+ return /*#__PURE__*/_jsxs(View, {
182
248
  style: styles.container,
183
- children: !hasGuideShown && !isScanned ? /*#__PURE__*/_jsxs(View, {
249
+ children: [hasNfc && !isScanned && progress > 0 && /*#__PURE__*/_jsxs(View, {
250
+ style: [styles.topProgressContainer, {
251
+ paddingTop: insets.top + 16
252
+ }],
253
+ children: [/*#__PURE__*/_jsxs(View, {
254
+ style: styles.progressTextContainer,
255
+ children: [/*#__PURE__*/_jsx(Text, {
256
+ style: styles.topScanningText,
257
+ children: progressStage || t('eidScannerScreen.readingDocument')
258
+ }), /*#__PURE__*/_jsxs(Text, {
259
+ style: [styles.progressPercentage, {
260
+ color: appContext.branding.primaryColor
261
+ }],
262
+ children: [Math.round(progress), "%"]
263
+ })]
264
+ }), /*#__PURE__*/_jsx(NativeProgressBar, {
265
+ progress: progress / 100,
266
+ width: null,
267
+ height: 30,
268
+ borderRadius: 8,
269
+ color: appContext.branding.primaryColor,
270
+ backgroundColor: "#E5E5EA"
271
+ })]
272
+ }), !hasGuideShown && !isScanned ? /*#__PURE__*/_jsxs(View, {
184
273
  style: styles.guide,
185
274
  children: [/*#__PURE__*/_jsx(LottieView, {
186
275
  source: require('../../Shared/Animations/nfc.json'),
@@ -281,7 +370,7 @@ const EIDScanner = ({
281
370
  children: [t('eidScannerScreen.birthDate'), ":"]
282
371
  }), /*#__PURE__*/_jsx(Text, {
283
372
  style: styles.mrzInfoText,
284
- children: documentMRZInfo.getDateOfBirth()
373
+ children: formatDate(documentMRZInfo.getDateOfBirth())
285
374
  })]
286
375
  }), /*#__PURE__*/_jsxs(View, {
287
376
  style: styles.mrzInfoItem,
@@ -299,7 +388,7 @@ const EIDScanner = ({
299
388
  children: [t('eidScannerScreen.expirationDate'), ":"]
300
389
  }), /*#__PURE__*/_jsx(Text, {
301
390
  style: styles.mrzInfoText,
302
- children: documentMRZInfo.getDateOfExpiry()
391
+ children: formatDate(documentMRZInfo.getDateOfExpiry())
303
392
  })]
304
393
  })]
305
394
  }), !hasNfc && /*#__PURE__*/_jsx(Text, {
@@ -332,16 +421,7 @@ const EIDScanner = ({
332
421
  }) || documentType === 'UNKNOWN' && /*#__PURE__*/_jsx(Text, {
333
422
  style: styles.mainText,
334
423
  children: t('eidScannerScreen.placeDocumentOnNFC')
335
- })), hasNfc && isEnabled && isScanning && progress > 0 && /*#__PURE__*/_jsx(Text, {
336
- style: styles.mainText,
337
- children: t('eidScannerScreen.readingDocument')
338
- }), hasNfc && !isScanned && progress > 0 && /*#__PURE__*/_jsx(NativeProgressBar, {
339
- progress: progress / 100,
340
- width: null,
341
- height: 30,
342
- borderRadius: 8,
343
- color: appContext.branding.primaryColor
344
- }), hasNfc && isEnabled && isScanned && /*#__PURE__*/_jsx(View, {
424
+ })), hasNfc && isEnabled && isScanned && /*#__PURE__*/_jsx(View, {
345
425
  style: styles.buttonsContainer,
346
426
  children: /*#__PURE__*/_jsx(StyledButton, {
347
427
  mode: "contained",
@@ -354,7 +434,7 @@ const EIDScanner = ({
354
434
  children: t('eidScannerScreen.approveAndContinue')
355
435
  })
356
436
  })]
357
- })
437
+ })]
358
438
  });
359
439
  };
360
440
  const styles = StyleSheet.create({
@@ -364,6 +444,51 @@ const styles = StyleSheet.create({
364
444
  gap: 10,
365
445
  padding: 20
366
446
  },
447
+ topProgressContainer: {
448
+ position: 'absolute',
449
+ top: 0,
450
+ left: 0,
451
+ right: 0,
452
+ zIndex: 1000,
453
+ paddingHorizontal: 20,
454
+ paddingVertical: 16,
455
+ backgroundColor: 'rgba(255, 255, 255, 0.95)',
456
+ gap: 8
457
+ },
458
+ progressTextContainer: {
459
+ flexDirection: 'row',
460
+ justifyContent: 'space-between',
461
+ alignItems: 'center',
462
+ width: '100%'
463
+ },
464
+ topScanningText: {
465
+ fontSize: 18,
466
+ fontWeight: 'bold',
467
+ color: '#000',
468
+ flex: 1
469
+ },
470
+ progressPercentage: {
471
+ fontSize: 18,
472
+ fontWeight: 'bold',
473
+ marginLeft: 12
474
+ },
475
+ scanningIndicator: {
476
+ flexDirection: 'row',
477
+ alignItems: 'center',
478
+ justifyContent: 'center',
479
+ gap: 8
480
+ },
481
+ pulseCircle: {
482
+ width: 8,
483
+ height: 8,
484
+ borderRadius: 4,
485
+ opacity: 0.8
486
+ },
487
+ scanningIndicatorText: {
488
+ fontSize: 14,
489
+ color: '#666',
490
+ fontWeight: '500'
491
+ },
367
492
  buttonsContainer: {
368
493
  flexDirection: 'column',
369
494
  gap: 10
@@ -2,17 +2,18 @@
2
2
 
3
3
  import { useIsFocused } from '@react-navigation/native';
4
4
  import { useKeepAwake } from "../Libs/native-keep-awake.utils.js";
5
- import React, { useEffect, useState } from 'react';
5
+ import React, { useEffect, useState, useMemo } from 'react';
6
6
  import { StyleSheet, Text, View, Platform, Linking, Dimensions, ActivityIndicator, NativeModules } from 'react-native';
7
7
  import { useCameraDevice, useCameraPermission, Camera, useFrameProcessor, useCameraFormat, runAtTargetFps, useMicrophonePermission } from 'react-native-vision-camera';
8
8
  import { runAsync } from "../Libs/worklet.utils.js";
9
9
  import { useFaceDetector } from "../VisionCameraPlugins/FaceDetector/index.js";
10
10
  import { Worklets, useSharedValue } from 'react-native-worklets-core';
11
11
  import { crop } from "../VisionCameraPlugins/Cropper/index.js";
12
- import { isCircularRegionBright } from "../Libs/camera.utils.js";
12
+ import { isCircularRegionBright, isBlurry } from "../Libs/camera.utils.js";
13
13
  import { useTranslation } from 'react-i18next';
14
14
  import StyledButton from "./StyledButton.js";
15
15
  import { useTheme } from "../Contexts/ThemeContext.js";
16
+ import { SafeAreaView } from 'react-native-safe-area-context';
16
17
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
17
18
  const VIDEO_WIDTH = 1280;
18
19
  const VIDEO_HEIGHT = 720;
@@ -43,9 +44,7 @@ const FaceCamera = ({
43
44
  fps: 30
44
45
  }]);
45
46
  const isCameraInitialized = useSharedValue(false);
46
- const {
47
- detectFaces
48
- } = useFaceDetector({
47
+ const faceDetectorOptions = useMemo(() => ({
49
48
  contourMode: 'none',
50
49
  landmarkMode: 'none',
51
50
  classificationMode: 'all',
@@ -54,7 +53,10 @@ const FaceCamera = ({
54
53
  autoScale: true,
55
54
  windowWidth: windowWidth,
56
55
  windowHeight: windowHeight
57
- });
56
+ }), [windowWidth, windowHeight]);
57
+ const {
58
+ detectFaces
59
+ } = useFaceDetector(faceDetectorOptions);
58
60
  const {
59
61
  t
60
62
  } = useTranslation();
@@ -72,6 +74,7 @@ const FaceCamera = ({
72
74
  }, [cameraPermission, microphonePermission]);
73
75
  const handleFaces = Worklets.createRunOnJS((faces, image, isBright) => {
74
76
  if (faces.length > 0) {
77
+ console.log('[FaceCamera] handleFaces called - faces:', faces.length, 'bright:', isBright);
75
78
  onFacesDetected(faces, image, isBright);
76
79
  }
77
80
  });
@@ -151,6 +154,12 @@ const FaceCamera = ({
151
154
  }
152
155
  isBright = luminanceSum / pixelCount > 60;
153
156
  }
157
+
158
+ // Skip blurry frames to ensure sharp face captures
159
+ // Lower threshold (10) for better frame acceptance
160
+ if (isBlurry(frame, 10)) {
161
+ return;
162
+ }
154
163
  const image = crop(frame, {
155
164
  cropRegion: {
156
165
  top: 0,
@@ -163,10 +172,13 @@ const FaceCamera = ({
163
172
  });
164
173
  if (image && image.base64) {
165
174
  const faces = detectFaces(frame);
175
+ if (faces.length > 0) {
176
+ console.log('[FaceCamera] Faces detected:', faces.length);
177
+ }
166
178
  handleFaces(faces, image.base64, isBright);
167
179
  }
168
180
  } catch (error) {
169
- console.error('Face detection error:', error);
181
+ console.error('[FaceCamera] Face detection error:', error);
170
182
  }
171
183
  };
172
184
  const frameProcessor = useFrameProcessor(frame => {
@@ -192,7 +204,7 @@ const FaceCamera = ({
192
204
  }
193
205
  }, [handleFaces, isCameraInitialized]);
194
206
  if (!permissionsRequested) {
195
- return /*#__PURE__*/_jsx(View, {
207
+ return /*#__PURE__*/_jsx(SafeAreaView, {
196
208
  style: styles.permissionContainer,
197
209
  children: /*#__PURE__*/_jsx(ActivityIndicator, {
198
210
  size: "large",
@@ -202,7 +214,7 @@ const FaceCamera = ({
202
214
  }
203
215
  if (!cameraPermission.hasPermission) {
204
216
  // Camera permission denied by user - their choice, not actionable
205
- return /*#__PURE__*/_jsxs(View, {
217
+ return /*#__PURE__*/_jsxs(SafeAreaView, {
206
218
  style: styles.permissionContainer,
207
219
  children: [/*#__PURE__*/_jsx(Text, {
208
220
  style: styles.permissionText,
@@ -218,7 +230,7 @@ const FaceCamera = ({
218
230
  }
219
231
  if (!microphonePermission.hasPermission) {
220
232
  // Microphone permission denied by user - their choice, not actionable
221
- return /*#__PURE__*/_jsxs(View, {
233
+ return /*#__PURE__*/_jsxs(SafeAreaView, {
222
234
  style: styles.permissionContainer,
223
235
  children: [/*#__PURE__*/_jsx(Text, {
224
236
  style: styles.permissionText,
@@ -234,7 +246,7 @@ const FaceCamera = ({
234
246
  }
235
247
  if (device == null) {
236
248
  // No camera device - device limitation, not actionable
237
- return /*#__PURE__*/_jsx(View, {
249
+ return /*#__PURE__*/_jsx(SafeAreaView, {
238
250
  style: styles.permissionContainer,
239
251
  children: /*#__PURE__*/_jsx(Text, {
240
252
  style: styles.permissionText,