@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,5 +1,6 @@
1
- import React, { useState, useEffect, useCallback } from 'react';
2
- import { Alert, View, Text, Image, StyleSheet } from 'react-native';
1
+ import React, { useState, useEffect, useCallback, useRef } from 'react';
2
+ import { Alert, View, Text, Image, StyleSheet, Animated } from 'react-native';
3
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
3
4
  import NFCManager from 'react-native-nfc-manager';
4
5
  import DeviceInfo from 'react-native-device-info';
5
6
  import { MRZInfo } from '../EIDReader/lds/icao/mrzInfo';
@@ -40,6 +41,7 @@ const EIDScanner = ({
40
41
  isNFCSupported,
41
42
  }: eIDScannerProps) => {
42
43
  useKeepAwake();
44
+ const insets = useSafeAreaInsets();
43
45
  const [isScanning, setIsScanning] = React.useState(false);
44
46
  const [hasNfc, setHasNFC] = React.useState(false);
45
47
  const [isEnabled, setIsEnabled] = React.useState(false);
@@ -48,6 +50,41 @@ const EIDScanner = ({
48
50
  const [documentFaceImageMimeType, setDocumentFaceImageMimeType] =
49
51
  React.useState<string>();
50
52
  const [progress, setProgress] = React.useState(0);
53
+ const [progressStage, setProgressStage] = React.useState<string>('');
54
+
55
+ // Animation for pulse indicator
56
+ const pulseAnim = useRef(new Animated.Value(1)).current;
57
+
58
+ // Format date from YYMMDD to DD/MM/YYYY (matching Flutter SDK)
59
+ const formatDate = useCallback((dateStr?: string | null) => {
60
+ if (!dateStr) return '';
61
+
62
+ try {
63
+ // Check if it's YYMMDD format (6 digits from NFC)
64
+ if (dateStr.length === 6 && /^\d{6}$/.test(dateStr)) {
65
+ const yy = dateStr.substring(0, 2);
66
+ const mm = dateStr.substring(2, 4);
67
+ const dd = dateStr.substring(4, 6);
68
+
69
+ // Assume 19xx if YY >= 50, else 20xx
70
+ const year = parseInt(yy) >= 50 ? `19${yy}` : `20${yy}`;
71
+ return `${dd}/${mm}/${year}`;
72
+ }
73
+
74
+ // Otherwise try to parse as ISO 8601
75
+ const date = new Date(dateStr);
76
+ if (!isNaN(date.getTime())) {
77
+ const day = String(date.getDate()).padStart(2, '0');
78
+ const month = String(date.getMonth() + 1).padStart(2, '0');
79
+ const year = date.getFullYear();
80
+ return `${day}/${month}/${year}`;
81
+ }
82
+
83
+ return dateStr;
84
+ } catch {
85
+ return dateStr;
86
+ }
87
+ }, []);
51
88
  const [isScanned, setIsScanned] = React.useState(false);
52
89
  const [hasGuideShown, setHasGuideShown] = React.useState(false);
53
90
  const { t } = useTranslation();
@@ -63,6 +100,7 @@ const EIDScanner = ({
63
100
  setIsScanning(true);
64
101
  setIsScanned(false);
65
102
  setProgress(0);
103
+ setProgressStage('');
66
104
 
67
105
  // Track EID scan start using analytics helper
68
106
  const docType =
@@ -113,6 +151,16 @@ const EIDScanner = ({
113
151
  dateOfExpiry,
114
152
  (progressValue) => {
115
153
  setProgress(progressValue);
154
+ // Update stage message based on progress
155
+ if (progressValue <= 20) {
156
+ setProgressStage(t('eidScannerScreen.connecting'));
157
+ } else if (progressValue <= 30) {
158
+ setProgressStage(t('eidScannerScreen.readingMRZ'));
159
+ } else if (progressValue < 100) {
160
+ setProgressStage(t('eidScannerScreen.readingFaceImage'));
161
+ } else {
162
+ setProgressStage(t('eidScannerScreen.completing'));
163
+ }
116
164
  }
117
165
  );
118
166
  if (passportData) {
@@ -193,6 +241,7 @@ const EIDScanner = ({
193
241
  } finally {
194
242
  setIsScanning(false);
195
243
  setProgress(0);
244
+ setProgressStage('');
196
245
  setHasGuideShown(false);
197
246
  }
198
247
  }, [
@@ -282,6 +331,30 @@ const EIDScanner = ({
282
331
  appContext.currentWorkflowStep?.data?.voiceGuidanceActive,
283
332
  ]);
284
333
 
334
+ // Pulse animation effect when scanning
335
+ useEffect(() => {
336
+ if (isScanning && progress > 0) {
337
+ const animation = Animated.loop(
338
+ Animated.sequence([
339
+ Animated.timing(pulseAnim, {
340
+ toValue: 1.3,
341
+ duration: 800,
342
+ useNativeDriver: true,
343
+ }),
344
+ Animated.timing(pulseAnim, {
345
+ toValue: 1,
346
+ duration: 800,
347
+ useNativeDriver: true,
348
+ }),
349
+ ])
350
+ );
351
+ animation.start();
352
+ return () => animation.stop();
353
+ } else {
354
+ pulseAnim.setValue(1);
355
+ }
356
+ }, [isScanning, progress, pulseAnim]);
357
+
285
358
  useEffect(() => {
286
359
  if (
287
360
  voiceGuidanceMessage &&
@@ -296,6 +369,28 @@ const EIDScanner = ({
296
369
 
297
370
  return (
298
371
  <View style={styles.container}>
372
+ {/* NFC scan progress indicator and text at the top */}
373
+ {hasNfc && !isScanned && progress > 0 && (
374
+ <View style={[styles.topProgressContainer, { paddingTop: insets.top + 16 }]}>
375
+ <View style={styles.progressTextContainer}>
376
+ <Text style={styles.topScanningText}>
377
+ {progressStage || t('eidScannerScreen.readingDocument')}
378
+ </Text>
379
+ <Text style={[styles.progressPercentage, { color: appContext.branding.primaryColor }]}>
380
+ {Math.round(progress)}%
381
+ </Text>
382
+ </View>
383
+ <NativeProgressBar
384
+ progress={progress / 100}
385
+ width={null}
386
+ height={30}
387
+ borderRadius={8}
388
+ color={appContext.branding.primaryColor}
389
+ backgroundColor="#E5E5EA"
390
+ />
391
+ </View>
392
+ )}
393
+
299
394
  {!hasGuideShown && !isScanned ? (
300
395
  <View style={styles.guide}>
301
396
  <LottieView
@@ -405,7 +500,7 @@ const EIDScanner = ({
405
500
  {t('eidScannerScreen.birthDate')}:
406
501
  </Text>
407
502
  <Text style={styles.mrzInfoText}>
408
- {documentMRZInfo.getDateOfBirth()}
503
+ {formatDate(documentMRZInfo.getDateOfBirth())}
409
504
  </Text>
410
505
  </View>
411
506
  <View style={styles.mrzInfoItem}>
@@ -421,7 +516,7 @@ const EIDScanner = ({
421
516
  {t('eidScannerScreen.expirationDate')}:
422
517
  </Text>
423
518
  <Text style={styles.mrzInfoText}>
424
- {documentMRZInfo.getDateOfExpiry()}
519
+ {formatDate(documentMRZInfo.getDateOfExpiry())}
425
520
  </Text>
426
521
  </View>
427
522
  </View>
@@ -479,21 +574,6 @@ const EIDScanner = ({
479
574
  </Text>
480
575
  )))}
481
576
 
482
- {hasNfc && isEnabled && isScanning && progress > 0 && (
483
- <Text style={styles.mainText}>
484
- {t('eidScannerScreen.readingDocument')}
485
- </Text>
486
- )}
487
-
488
- {hasNfc && !isScanned && progress > 0 && (
489
- <NativeProgressBar
490
- progress={progress / 100}
491
- width={null}
492
- height={30}
493
- borderRadius={8}
494
- color={appContext.branding.primaryColor}
495
- />
496
- )}
497
577
 
498
578
  {hasNfc && isEnabled && isScanned && (
499
579
  <View style={styles.buttonsContainer}>
@@ -532,6 +612,51 @@ const styles = StyleSheet.create({
532
612
  gap: 10,
533
613
  padding: 20,
534
614
  },
615
+ topProgressContainer: {
616
+ position: 'absolute',
617
+ top: 0,
618
+ left: 0,
619
+ right: 0,
620
+ zIndex: 1000,
621
+ paddingHorizontal: 20,
622
+ paddingVertical: 16,
623
+ backgroundColor: 'rgba(255, 255, 255, 0.95)',
624
+ gap: 8,
625
+ },
626
+ progressTextContainer: {
627
+ flexDirection: 'row',
628
+ justifyContent: 'space-between',
629
+ alignItems: 'center',
630
+ width: '100%',
631
+ },
632
+ topScanningText: {
633
+ fontSize: 18,
634
+ fontWeight: 'bold',
635
+ color: '#000',
636
+ flex: 1,
637
+ },
638
+ progressPercentage: {
639
+ fontSize: 18,
640
+ fontWeight: 'bold',
641
+ marginLeft: 12,
642
+ },
643
+ scanningIndicator: {
644
+ flexDirection: 'row',
645
+ alignItems: 'center',
646
+ justifyContent: 'center',
647
+ gap: 8,
648
+ },
649
+ pulseCircle: {
650
+ width: 8,
651
+ height: 8,
652
+ borderRadius: 4,
653
+ opacity: 0.8,
654
+ },
655
+ scanningIndicatorText: {
656
+ fontSize: 14,
657
+ color: '#666',
658
+ fontWeight: '500',
659
+ },
535
660
  buttonsContainer: {
536
661
  flexDirection: 'column',
537
662
  gap: 10,
@@ -1,6 +1,6 @@
1
1
  import { useIsFocused } from '@react-navigation/native';
2
2
  import { useKeepAwake } from '../Libs/native-keep-awake.utils';
3
- import React, { useEffect, useState } from 'react';
3
+ import React, { useEffect, useState, useMemo } from 'react';
4
4
  import {
5
5
  StyleSheet,
6
6
  Text,
@@ -28,10 +28,11 @@ import {
28
28
  } from '../VisionCameraPlugins/FaceDetector';
29
29
  import { Worklets, useSharedValue } from 'react-native-worklets-core';
30
30
  import { crop } from '../VisionCameraPlugins/Cropper';
31
- import { isCircularRegionBright } from '../Libs/camera.utils';
31
+ import { isCircularRegionBright, isBlurry } from '../Libs/camera.utils';
32
32
  import { useTranslation } from 'react-i18next';
33
33
  import StyledButton from './StyledButton';
34
34
  import { useTheme } from '../Contexts/ThemeContext';
35
+ import { SafeAreaView } from 'react-native-safe-area-context';
35
36
 
36
37
  export type FaceCameraProps = {
37
38
  onFacesDetected: (
@@ -78,16 +79,22 @@ const FaceCamera = ({
78
79
  },
79
80
  ]);
80
81
  const isCameraInitialized = useSharedValue(false);
81
- const { detectFaces } = useFaceDetector({
82
- contourMode: 'none',
83
- landmarkMode: 'none',
84
- classificationMode: 'all',
85
- performanceMode: 'accurate',
86
- trackingEnabled: false,
87
- autoScale: true,
88
- windowWidth: windowWidth,
89
- windowHeight: windowHeight,
90
- });
82
+
83
+ const faceDetectorOptions = useMemo(
84
+ () => ({
85
+ contourMode: 'none' as const,
86
+ landmarkMode: 'none' as const,
87
+ classificationMode: 'all' as const,
88
+ performanceMode: 'accurate' as const,
89
+ trackingEnabled: false,
90
+ autoScale: true,
91
+ windowWidth: windowWidth,
92
+ windowHeight: windowHeight,
93
+ }),
94
+ [windowWidth, windowHeight]
95
+ );
96
+
97
+ const { detectFaces } = useFaceDetector(faceDetectorOptions);
91
98
  const { t } = useTranslation();
92
99
 
93
100
  useEffect(() => {
@@ -106,6 +113,7 @@ const FaceCamera = ({
106
113
  const handleFaces = Worklets.createRunOnJS(
107
114
  (faces: Face[], image: string, isBright: boolean) => {
108
115
  if (faces.length > 0) {
116
+ console.log('[FaceCamera] handleFaces called - faces:', faces.length, 'bright:', isBright);
109
117
  onFacesDetected(faces, image, isBright);
110
118
  }
111
119
  }
@@ -192,6 +200,12 @@ const FaceCamera = ({
192
200
  isBright = (luminanceSum / pixelCount) > 60;
193
201
  }
194
202
 
203
+ // Skip blurry frames to ensure sharp face captures
204
+ // Lower threshold (10) for better frame acceptance
205
+ if (isBlurry(frame, 10)) {
206
+ return;
207
+ }
208
+
195
209
  const image = crop(frame, {
196
210
  cropRegion: {
197
211
  top: 0,
@@ -205,10 +219,13 @@ const FaceCamera = ({
205
219
 
206
220
  if (image && image.base64) {
207
221
  const faces = detectFaces(frame);
222
+ if (faces.length > 0) {
223
+ console.log('[FaceCamera] Faces detected:', faces.length);
224
+ }
208
225
  handleFaces(faces, image.base64, isBright);
209
226
  }
210
227
  } catch (error) {
211
- console.error('Face detection error:', error);
228
+ console.error('[FaceCamera] Face detection error:', error);
212
229
  }
213
230
  };
214
231
 
@@ -237,16 +254,16 @@ const FaceCamera = ({
237
254
 
238
255
  if (!permissionsRequested) {
239
256
  return (
240
- <View style={styles.permissionContainer}>
257
+ <SafeAreaView style={styles.permissionContainer}>
241
258
  <ActivityIndicator size="large" color={theme.colors.primary} />
242
- </View>
259
+ </SafeAreaView>
243
260
  );
244
261
  }
245
262
 
246
263
  if (!cameraPermission.hasPermission) {
247
264
  // Camera permission denied by user - their choice, not actionable
248
265
  return (
249
- <View style={styles.permissionContainer}>
266
+ <SafeAreaView style={styles.permissionContainer}>
250
267
  <Text style={styles.permissionText}>
251
268
  {t('general.noCameraPermissionGiven')}
252
269
  </Text>
@@ -258,14 +275,14 @@ const FaceCamera = ({
258
275
  >
259
276
  {t('general.openSettings')}
260
277
  </StyledButton>
261
- </View>
278
+ </SafeAreaView>
262
279
  );
263
280
  }
264
281
 
265
282
  if (!microphonePermission.hasPermission) {
266
283
  // Microphone permission denied by user - their choice, not actionable
267
284
  return (
268
- <View style={styles.permissionContainer}>
285
+ <SafeAreaView style={styles.permissionContainer}>
269
286
  <Text style={styles.permissionText}>
270
287
  {t('general.noMicrophonePermissionGiven')}
271
288
  </Text>
@@ -277,18 +294,18 @@ const FaceCamera = ({
277
294
  >
278
295
  {t('general.openSettings')}
279
296
  </StyledButton>
280
- </View>
297
+ </SafeAreaView>
281
298
  );
282
299
  }
283
300
 
284
301
  if (device == null) {
285
302
  // No camera device - device limitation, not actionable
286
303
  return (
287
- <View style={styles.permissionContainer}>
304
+ <SafeAreaView style={styles.permissionContainer}>
288
305
  <Text style={styles.permissionText}>
289
306
  {t('general.noCameraDetected')}
290
307
  </Text>
291
- </View>
308
+ </SafeAreaView>
292
309
  );
293
310
  }
294
311