@trustchex/react-native-sdk 1.374.0 → 1.409.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 (137) hide show
  1. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +1 -21
  2. package/android/src/main/java/com/trustchex/reactnativesdk/mlkit/MLKitModule.kt +1 -1
  3. package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +636 -301
  4. package/ios/Camera/TrustchexCameraView.swift +9 -20
  5. package/ios/MLKit/MLKitModule.swift +1 -1
  6. package/ios/OpenCV/OpenCVHelper.h +0 -7
  7. package/ios/OpenCV/OpenCVHelper.mm +0 -60
  8. package/ios/OpenCV/OpenCVModule.h +0 -4
  9. package/ios/OpenCV/OpenCVModule.mm +440 -358
  10. package/lib/module/Screens/Debug/BarcodeTestScreen.js +308 -0
  11. package/lib/module/Screens/Debug/MRZTestScreen.js +105 -13
  12. package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +49 -29
  13. package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +5 -0
  14. package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +5 -0
  15. package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +26 -6
  16. package/lib/module/Screens/Dynamic/VideoCallScreen.js +676 -0
  17. package/lib/module/Screens/Static/OTPVerificationScreen.js +6 -0
  18. package/lib/module/Screens/Static/QrCodeScanningScreen.js +7 -1
  19. package/lib/module/Screens/Static/ResultScreen.js +27 -13
  20. package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +51 -51
  21. package/lib/module/Shared/Animations/video-call.json +1 -0
  22. package/lib/module/Shared/Components/DebugNavigationPanel.js +180 -14
  23. package/lib/module/Shared/Components/DebugOverlay.js +541 -0
  24. package/lib/module/Shared/Components/EIDScanner.js +1 -4
  25. package/lib/module/Shared/Components/IdentityDocumentCamera.constants.js +44 -0
  26. package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +270 -0
  27. package/lib/module/Shared/Components/IdentityDocumentCamera.js +702 -1703
  28. package/lib/module/Shared/Components/IdentityDocumentCamera.types.js +3 -0
  29. package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +273 -0
  30. package/lib/module/Shared/Components/NavigationManager.js +15 -3
  31. package/lib/module/Shared/Contexts/AppContext.js +1 -0
  32. package/lib/module/Shared/Libs/SignalingClient.js +128 -0
  33. package/lib/module/Shared/Libs/analytics.utils.js +4 -0
  34. package/lib/module/Shared/Libs/deeplink.utils.js +9 -1
  35. package/lib/module/Shared/Libs/http-client.js +9 -0
  36. package/lib/module/Shared/Libs/promise.utils.js +16 -2
  37. package/lib/module/Shared/Libs/status-bar.utils.js +21 -0
  38. package/lib/module/Shared/Services/DataUploadService.js +294 -0
  39. package/lib/module/Shared/Services/VideoSessionService.js +156 -0
  40. package/lib/module/Shared/Services/WebRTCService.js +510 -0
  41. package/lib/module/Shared/Types/analytics.types.js +2 -0
  42. package/lib/module/Translation/Resources/en.js +20 -0
  43. package/lib/module/Translation/Resources/tr.js +20 -0
  44. package/lib/module/Trustchex.js +10 -0
  45. package/lib/module/version.js +1 -1
  46. package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts +3 -0
  47. package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts.map +1 -0
  48. package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts.map +1 -1
  49. package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
  50. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  51. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
  52. package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
  53. package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts +3 -0
  54. package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts.map +1 -0
  55. package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -1
  56. package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
  57. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  58. package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
  59. package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
  60. package/lib/typescript/src/Shared/Components/DebugOverlay.d.ts +30 -0
  61. package/lib/typescript/src/Shared/Components/DebugOverlay.d.ts.map +1 -0
  62. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  63. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts +35 -0
  64. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts.map +1 -0
  65. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -56
  66. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  67. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +88 -0
  68. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -0
  69. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts +116 -0
  70. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts.map +1 -0
  71. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +93 -0
  72. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -0
  73. package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
  74. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +1 -0
  75. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
  76. package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts +24 -0
  77. package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts.map +1 -0
  78. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
  79. package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
  80. package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
  81. package/lib/typescript/src/Shared/Libs/promise.utils.d.ts.map +1 -1
  82. package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts +9 -0
  83. package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts.map +1 -0
  84. package/lib/typescript/src/Shared/Services/DataUploadService.d.ts +25 -0
  85. package/lib/typescript/src/Shared/Services/DataUploadService.d.ts.map +1 -0
  86. package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts +33 -0
  87. package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts.map +1 -0
  88. package/lib/typescript/src/Shared/Services/WebRTCService.d.ts +58 -0
  89. package/lib/typescript/src/Shared/Services/WebRTCService.d.ts.map +1 -0
  90. package/lib/typescript/src/Shared/Types/analytics.types.d.ts +2 -0
  91. package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -1
  92. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +4 -1
  93. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
  94. package/lib/typescript/src/Translation/Resources/en.d.ts +20 -0
  95. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  96. package/lib/typescript/src/Translation/Resources/tr.d.ts +20 -0
  97. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  98. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  99. package/lib/typescript/src/version.d.ts +1 -1
  100. package/package.json +29 -2
  101. package/src/Screens/Debug/BarcodeTestScreen.tsx +317 -0
  102. package/src/Screens/Debug/MRZTestScreen.tsx +107 -13
  103. package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +59 -33
  104. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +6 -0
  105. package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +6 -0
  106. package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +34 -6
  107. package/src/Screens/Dynamic/VideoCallScreen.tsx +764 -0
  108. package/src/Screens/Static/OTPVerificationScreen.tsx +6 -0
  109. package/src/Screens/Static/QrCodeScanningScreen.tsx +7 -1
  110. package/src/Screens/Static/ResultScreen.tsx +58 -23
  111. package/src/Screens/Static/VerificationSessionCheckScreen.tsx +58 -72
  112. package/src/Shared/Animations/video-call.json +1 -0
  113. package/src/Shared/Components/DebugNavigationPanel.tsx +185 -9
  114. package/src/Shared/Components/DebugOverlay.tsx +656 -0
  115. package/src/Shared/Components/EIDScanner.tsx +1 -5
  116. package/src/Shared/Components/IdentityDocumentCamera.constants.ts +44 -0
  117. package/src/Shared/Components/IdentityDocumentCamera.flows.ts +342 -0
  118. package/src/Shared/Components/IdentityDocumentCamera.tsx +1089 -2465
  119. package/src/Shared/Components/IdentityDocumentCamera.types.ts +136 -0
  120. package/src/Shared/Components/IdentityDocumentCamera.utils.ts +364 -0
  121. package/src/Shared/Components/NavigationManager.tsx +14 -1
  122. package/src/Shared/Contexts/AppContext.ts +2 -0
  123. package/src/Shared/Libs/SignalingClient.ts +189 -0
  124. package/src/Shared/Libs/analytics.utils.ts +4 -0
  125. package/src/Shared/Libs/deeplink.utils.ts +12 -1
  126. package/src/Shared/Libs/http-client.ts +10 -0
  127. package/src/Shared/Libs/promise.utils.ts +16 -2
  128. package/src/Shared/Libs/status-bar.utils.ts +19 -0
  129. package/src/Shared/Services/DataUploadService.ts +395 -0
  130. package/src/Shared/Services/VideoSessionService.ts +190 -0
  131. package/src/Shared/Services/WebRTCService.ts +636 -0
  132. package/src/Shared/Types/analytics.types.ts +2 -0
  133. package/src/Shared/Types/identificationInfo.ts +5 -1
  134. package/src/Translation/Resources/en.ts +25 -0
  135. package/src/Translation/Resources/tr.ts +27 -0
  136. package/src/Trustchex.tsx +12 -2
  137. package/src/version.ts +1 -1
@@ -0,0 +1,656 @@
1
+ /* eslint-disable react-native/no-inline-styles */
2
+ import React from 'react';
3
+ import { View, Text as TextView, Image, ScrollView } from 'react-native';
4
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
5
+ import type {
6
+ ScanStep,
7
+ ScanStatus,
8
+ DocumentType,
9
+ BoundsWithRotation,
10
+ BoundsWithAngle,
11
+ } from './IdentityDocumentCamera.types';
12
+ import { HOLOGRAM_IMAGE_COUNT } from './IdentityDocumentCamera.constants';
13
+ import {
14
+ angleBetweenPoints,
15
+ distanceBetweenPoints,
16
+ } from './IdentityDocumentCamera.utils';
17
+
18
+ // --- Shared overlay sub-components ---
19
+
20
+ /**
21
+ * Renders a rotated face bounds overlay with optional crop padding border.
22
+ */
23
+ function FaceBoundsOverlay({
24
+ bounds,
25
+ color,
26
+ }: {
27
+ bounds: BoundsWithRotation;
28
+ color: string;
29
+ }) {
30
+ const dashColor = color.replace(')', ', 0.5)').replace('rgb', 'rgba');
31
+ return (
32
+ <>
33
+ {!!bounds.cropPadding && (
34
+ <View
35
+ style={{
36
+ position: 'absolute',
37
+ left: bounds.x - bounds.cropPadding,
38
+ top: bounds.y - bounds.cropPadding,
39
+ width: bounds.width + 2 * bounds.cropPadding,
40
+ height: bounds.height + 2 * bounds.cropPadding,
41
+ borderWidth: 2,
42
+ borderColor: dashColor,
43
+ borderStyle: 'dashed',
44
+ borderRadius: 8,
45
+ backgroundColor: 'transparent',
46
+ transform: [{ rotate: `${-(bounds.rollAngle || 0)}deg` }],
47
+ transformOrigin: 'center',
48
+ }}
49
+ />
50
+ )}
51
+ <View
52
+ style={{
53
+ position: 'absolute',
54
+ left: bounds.x,
55
+ top: bounds.y,
56
+ width: bounds.width,
57
+ height: bounds.height,
58
+ borderWidth: 3,
59
+ borderColor: color,
60
+ borderRadius: 8,
61
+ backgroundColor: 'transparent',
62
+ transform: [{ rotate: `${-(bounds.rollAngle || 0)}deg` }],
63
+ transformOrigin: 'center',
64
+ }}
65
+ >
66
+ {!!bounds.rollAngle && Math.abs(bounds.rollAngle) > 5 && (
67
+ <TextView
68
+ style={{
69
+ position: 'absolute',
70
+ top: -20,
71
+ left: 0,
72
+ color: color,
73
+ fontSize: 10,
74
+ fontWeight: 'bold',
75
+ backgroundColor: 'rgba(0,0,0,0.7)',
76
+ paddingHorizontal: 4,
77
+ paddingVertical: 2,
78
+ borderRadius: 2,
79
+ }}
80
+ >
81
+ {bounds.rollAngle.toFixed(1)}°
82
+ </TextView>
83
+ )}
84
+ </View>
85
+ </>
86
+ );
87
+ }
88
+
89
+ /**
90
+ * Renders bounds with corner points (for barcode, MRZ, signature).
91
+ * Falls back to a rotated rectangle when corners aren't available.
92
+ */
93
+ function CornerBoundsOverlay({
94
+ bounds,
95
+ color,
96
+ }: {
97
+ bounds: BoundsWithAngle;
98
+ color: string;
99
+ }) {
100
+ if (bounds.corners && bounds.corners.length >= 2) {
101
+ return (
102
+ <>
103
+ {bounds.corners.map((corner, idx) => {
104
+ const nextCorner =
105
+ bounds.corners![(idx + 1) % bounds.corners!.length];
106
+ const length = distanceBetweenPoints(corner, nextCorner);
107
+ const angle = angleBetweenPoints(corner, nextCorner);
108
+
109
+ return (
110
+ <View
111
+ key={idx}
112
+ style={{
113
+ position: 'absolute',
114
+ left: corner.x,
115
+ top: corner.y,
116
+ width: length,
117
+ height: 3,
118
+ backgroundColor: color,
119
+ transform: [{ rotate: `${angle}deg` }],
120
+ transformOrigin: 'top left',
121
+ }}
122
+ />
123
+ );
124
+ })}
125
+ {/* Corner markers for barcode (4+ corners) */}
126
+ {bounds.corners.length >= 4 &&
127
+ bounds.corners.map((corner, idx) => (
128
+ <View
129
+ key={`corner-${idx}`}
130
+ style={{
131
+ position: 'absolute',
132
+ left: corner.x - 4,
133
+ top: corner.y - 4,
134
+ width: 8,
135
+ height: 8,
136
+ borderRadius: 4,
137
+ backgroundColor: color,
138
+ }}
139
+ />
140
+ ))}
141
+ {!!bounds.angle && Math.abs(bounds.angle) > 5 && (
142
+ <TextView
143
+ style={{
144
+ position: 'absolute',
145
+ left: bounds.x,
146
+ top: bounds.y - 20,
147
+ color: color,
148
+ fontSize: 10,
149
+ fontWeight: 'bold',
150
+ backgroundColor: 'rgba(0,0,0,0.7)',
151
+ paddingHorizontal: 4,
152
+ paddingVertical: 2,
153
+ borderRadius: 2,
154
+ }}
155
+ >
156
+ {bounds.angle.toFixed(1)}°
157
+ </TextView>
158
+ )}
159
+ </>
160
+ );
161
+ }
162
+
163
+ // Fallback: rotated rectangle
164
+ return (
165
+ <View
166
+ style={{
167
+ position: 'absolute',
168
+ left: bounds.x + bounds.width / 2,
169
+ top: bounds.y + bounds.height / 2,
170
+ width: bounds.width,
171
+ height: bounds.height,
172
+ marginLeft: -bounds.width / 2,
173
+ marginTop: -bounds.height / 2,
174
+ borderWidth: 3,
175
+ borderColor: color,
176
+ borderRadius: 8,
177
+ backgroundColor: 'transparent',
178
+ transform: [{ rotate: `${bounds.angle || 0}deg` }],
179
+ }}
180
+ >
181
+ {!!bounds.angle && Math.abs(bounds.angle) > 5 && (
182
+ <TextView
183
+ style={{
184
+ position: 'absolute',
185
+ top: -20,
186
+ left: 0,
187
+ color: color,
188
+ fontSize: 10,
189
+ fontWeight: 'bold',
190
+ backgroundColor: 'rgba(0,0,0,0.7)',
191
+ paddingHorizontal: 4,
192
+ paddingVertical: 2,
193
+ borderRadius: 2,
194
+ }}
195
+ >
196
+ {bounds.angle.toFixed(1)}°
197
+ </TextView>
198
+ )}
199
+ </View>
200
+ );
201
+ }
202
+
203
+ /**
204
+ * Debug image thumbnail with label
205
+ */
206
+ function DebugImageThumbnail({
207
+ image,
208
+ label,
209
+ activeColor,
210
+ borderColor,
211
+ }: {
212
+ image?: string;
213
+ label: string;
214
+ activeColor: string;
215
+ borderColor: string;
216
+ }) {
217
+ return (
218
+ <View style={{ alignItems: 'center' }}>
219
+ {image ? (
220
+ <Image
221
+ source={{ uri: `data:image/jpeg;base64,${image}` }}
222
+ style={{
223
+ width: 40,
224
+ height: 48,
225
+ borderRadius: 2,
226
+ borderWidth: 1,
227
+ borderColor,
228
+ }}
229
+ />
230
+ ) : (
231
+ <View
232
+ style={{
233
+ width: 40,
234
+ height: 48,
235
+ borderRadius: 2,
236
+ borderWidth: 1,
237
+ borderColor: '#666',
238
+ backgroundColor: '#222',
239
+ justifyContent: 'center',
240
+ alignItems: 'center',
241
+ }}
242
+ >
243
+ <TextView style={{ color: '#666', fontSize: 7 }}>—</TextView>
244
+ </View>
245
+ )}
246
+ <TextView
247
+ style={{
248
+ color: image ? activeColor : '#999',
249
+ fontSize: 7,
250
+ marginTop: 1,
251
+ fontWeight: 'bold',
252
+ }}
253
+ >
254
+ {`${image ? '✓' : '○'} ${label}`}
255
+ </TextView>
256
+ </View>
257
+ );
258
+ }
259
+
260
+ // --- Main DebugOverlay ---
261
+
262
+ export interface DebugOverlayProps {
263
+ nextStep: ScanStep;
264
+ status: ScanStatus;
265
+ detectedDocumentType: DocumentType;
266
+ isBrightnessLow: boolean;
267
+ isFrameBlurry: boolean;
268
+ isTorchOn: boolean;
269
+ documentPlaneBounds: BoundsWithRotation | null;
270
+ secondaryFaceBounds: BoundsWithRotation | null;
271
+ barcodeBounds: BoundsWithAngle | null;
272
+ mrzBounds: BoundsWithAngle | null;
273
+ signatureBounds: BoundsWithAngle | null;
274
+ currentFaceImage?: string;
275
+ currentSecondaryFaceImage?: string;
276
+ currentHologramImage?: string;
277
+ currentHologramMaskImage?: string;
278
+ latestHologramFaceImage?: string;
279
+ hologramImageCount: number;
280
+ allElementsDetected: boolean;
281
+ elementsOutsideScanArea: string[];
282
+ }
283
+
284
+ const DebugOverlay = React.memo(function DebugOverlay({
285
+ nextStep,
286
+ status,
287
+ detectedDocumentType,
288
+ isBrightnessLow,
289
+ isFrameBlurry,
290
+ isTorchOn,
291
+ documentPlaneBounds,
292
+ secondaryFaceBounds,
293
+ barcodeBounds,
294
+ mrzBounds,
295
+ signatureBounds,
296
+ currentFaceImage,
297
+ currentSecondaryFaceImage,
298
+ currentHologramImage,
299
+ currentHologramMaskImage,
300
+ latestHologramFaceImage,
301
+ hologramImageCount,
302
+ elementsOutsideScanArea,
303
+ }: DebugOverlayProps) {
304
+ const insets = useSafeAreaInsets();
305
+
306
+ return (
307
+ <>
308
+ {/* Bounds overlays */}
309
+ {documentPlaneBounds && nextStep !== 'COMPLETED' && (
310
+ <FaceBoundsOverlay bounds={documentPlaneBounds} color="#4CAF50" />
311
+ )}
312
+ {secondaryFaceBounds && nextStep !== 'COMPLETED' && (
313
+ <FaceBoundsOverlay bounds={secondaryFaceBounds} color="#2196F3" />
314
+ )}
315
+ {barcodeBounds && nextStep !== 'COMPLETED' && (
316
+ <CornerBoundsOverlay bounds={barcodeBounds} color="#FF9800" />
317
+ )}
318
+ {mrzBounds && nextStep !== 'COMPLETED' && (
319
+ <CornerBoundsOverlay bounds={mrzBounds} color="#9C27B0" />
320
+ )}
321
+ {signatureBounds && nextStep !== 'COMPLETED' && (
322
+ <CornerBoundsOverlay bounds={signatureBounds} color="#00BCD4" />
323
+ )}
324
+
325
+ {/* Debug info panel */}
326
+ <View
327
+ style={{
328
+ position: 'absolute',
329
+ top: 0,
330
+ left: 0,
331
+ right: 0,
332
+ paddingTop: insets.top,
333
+ alignItems: 'center',
334
+ pointerEvents: 'none',
335
+ }}
336
+ >
337
+ <View
338
+ style={{
339
+ marginTop: 8,
340
+ marginHorizontal: 8,
341
+ backgroundColor: 'rgba(0, 0, 0, 0.9)',
342
+ padding: 8,
343
+ borderRadius: 6,
344
+ borderWidth: 1,
345
+ borderColor: 'rgba(255, 82, 82, 0.5)',
346
+ minWidth: 220,
347
+ maxWidth: '92%',
348
+ }}
349
+ >
350
+ {/* Status & State Row */}
351
+ <View
352
+ style={{
353
+ flexDirection: 'row',
354
+ justifyContent: 'space-between',
355
+ marginBottom: 4,
356
+ }}
357
+ >
358
+ <TextView
359
+ style={{ color: '#88D8B0', fontSize: 10, fontWeight: 'bold' }}
360
+ >
361
+ {`Status: ${status}`}
362
+ </TextView>
363
+ <TextView
364
+ style={{ color: '#FFEB3B', fontSize: 10, fontWeight: 'bold' }}
365
+ >
366
+ {`Step: ${nextStep}`}
367
+ </TextView>
368
+ </View>
369
+
370
+ {/* Quality Badges */}
371
+ <View
372
+ style={{
373
+ flexDirection: 'row',
374
+ justifyContent: 'space-between',
375
+ marginBottom: 4,
376
+ gap: 3,
377
+ }}
378
+ >
379
+ <View
380
+ style={{
381
+ backgroundColor: isBrightnessLow ? '#f44336' : '#4CAF50',
382
+ paddingHorizontal: 6,
383
+ paddingVertical: 1,
384
+ borderRadius: 3,
385
+ }}
386
+ >
387
+ <TextView
388
+ style={{ color: '#FFFFFF', fontSize: 8, fontWeight: 'bold' }}
389
+ >
390
+ {isBrightnessLow ? 'Low Light' : 'Good Light'}
391
+ </TextView>
392
+ </View>
393
+ <View
394
+ style={{
395
+ backgroundColor: isFrameBlurry ? '#f44336' : '#4CAF50',
396
+ paddingHorizontal: 6,
397
+ paddingVertical: 1,
398
+ borderRadius: 3,
399
+ }}
400
+ >
401
+ <TextView
402
+ style={{ color: '#FFFFFF', fontSize: 8, fontWeight: 'bold' }}
403
+ >
404
+ {isFrameBlurry ? 'Blurry' : 'Sharp'}
405
+ </TextView>
406
+ </View>
407
+ </View>
408
+
409
+ {/* Elements Status */}
410
+ <TextView
411
+ style={{
412
+ color:
413
+ elementsOutsideScanArea.length === 0 ? '#4CAF50' : '#FFA500',
414
+ fontSize: 8,
415
+ marginBottom: 3,
416
+ }}
417
+ >
418
+ {elementsOutsideScanArea.length === 0
419
+ ? 'All elements in frame'
420
+ : `Outside: ${elementsOutsideScanArea.join(', ')}`}
421
+ </TextView>
422
+
423
+ {/* Detection Info */}
424
+ <TextView
425
+ style={{
426
+ color: 'rgba(255, 255, 255, 0.7)',
427
+ fontSize: 8,
428
+ marginBottom: 3,
429
+ }}
430
+ >
431
+ {`Face: ${documentPlaneBounds ? '✓' : '✗'}, MRZ: ${mrzBounds ? '✓' : '✗'}, Sig: ${signatureBounds ? '✓' : '✗'}`}
432
+ </TextView>
433
+
434
+ {/* Hologram Count */}
435
+ {nextStep === 'SCAN_HOLOGRAM' && hologramImageCount > 0 && (
436
+ <TextView
437
+ style={{
438
+ color: '#9C27B0',
439
+ fontSize: 8,
440
+ fontWeight: 'bold',
441
+ marginBottom: 3,
442
+ }}
443
+ >
444
+ {`Hologram: ${hologramImageCount}/${HOLOGRAM_IMAGE_COUNT}`}
445
+ </TextView>
446
+ )}
447
+
448
+ {/* Additional Info */}
449
+ <View
450
+ style={{
451
+ flexDirection: 'row',
452
+ gap: 8,
453
+ paddingTop: 3,
454
+ borderTopWidth: 1,
455
+ borderTopColor: 'rgba(255, 255, 255, 0.2)',
456
+ marginBottom: 4,
457
+ }}
458
+ >
459
+ <TextView
460
+ style={{
461
+ color: 'rgba(255, 255, 255, 0.7)',
462
+ fontSize: 8,
463
+ marginTop: 3,
464
+ }}
465
+ >
466
+ {`Doc: ${detectedDocumentType}`}
467
+ </TextView>
468
+ <TextView
469
+ style={{
470
+ color: 'rgba(255, 255, 255, 0.7)',
471
+ fontSize: 8,
472
+ marginTop: 3,
473
+ }}
474
+ >
475
+ {`Flash: ${isTorchOn ? '🔦' : '○'}`}
476
+ </TextView>
477
+ </View>
478
+
479
+ {/* Debug Images Grid */}
480
+ <View
481
+ style={{
482
+ flexDirection: 'row',
483
+ flexWrap: 'wrap',
484
+ gap: 4,
485
+ justifyContent: 'center',
486
+ }}
487
+ >
488
+ <DebugImageThumbnail
489
+ image={currentFaceImage}
490
+ label="Face"
491
+ activeColor="#4CAF50"
492
+ borderColor="#4CAF50"
493
+ />
494
+ <DebugImageThumbnail
495
+ image={currentSecondaryFaceImage}
496
+ label="2nd"
497
+ activeColor="#2196F3"
498
+ borderColor="#2196F3"
499
+ />
500
+
501
+ {/* Hologram thumbnail (special: shows progress) */}
502
+ <View style={{ alignItems: 'center' }}>
503
+ {currentHologramImage ? (
504
+ <Image
505
+ source={{
506
+ uri: `data:image/jpeg;base64,${currentHologramImage}`,
507
+ }}
508
+ style={{
509
+ width: 40,
510
+ height: 48,
511
+ borderRadius: 2,
512
+ borderWidth: 1,
513
+ borderColor: '#9C27B0',
514
+ }}
515
+ />
516
+ ) : latestHologramFaceImage && hologramImageCount > 0 ? (
517
+ <View style={{ position: 'relative' }}>
518
+ <Image
519
+ source={{
520
+ uri: `data:image/jpeg;base64,${latestHologramFaceImage}`,
521
+ }}
522
+ style={{
523
+ width: 40,
524
+ height: 48,
525
+ borderRadius: 2,
526
+ borderWidth: 1,
527
+ borderColor: '#FFA500',
528
+ opacity: 0.7,
529
+ }}
530
+ />
531
+ <View
532
+ style={{
533
+ position: 'absolute',
534
+ bottom: 1,
535
+ left: 1,
536
+ right: 1,
537
+ backgroundColor: 'rgba(0,0,0,0.8)',
538
+ paddingVertical: 0,
539
+ borderRadius: 1,
540
+ }}
541
+ >
542
+ <TextView
543
+ style={{
544
+ color: '#FFA500',
545
+ fontSize: 6,
546
+ textAlign: 'center',
547
+ fontWeight: 'bold',
548
+ }}
549
+ >
550
+ {hologramImageCount}/{HOLOGRAM_IMAGE_COUNT}
551
+ </TextView>
552
+ </View>
553
+ </View>
554
+ ) : (
555
+ <View
556
+ style={{
557
+ width: 40,
558
+ height: 48,
559
+ borderRadius: 2,
560
+ borderWidth: 1,
561
+ borderColor: '#666',
562
+ backgroundColor: '#222',
563
+ justifyContent: 'center',
564
+ alignItems: 'center',
565
+ }}
566
+ >
567
+ <TextView style={{ color: '#666', fontSize: 7 }}>—</TextView>
568
+ </View>
569
+ )}
570
+ <TextView
571
+ style={{
572
+ color: currentHologramImage
573
+ ? '#9C27B0'
574
+ : latestHologramFaceImage
575
+ ? '#FFA500'
576
+ : '#999',
577
+ fontSize: 7,
578
+ marginTop: 1,
579
+ fontWeight: 'bold',
580
+ }}
581
+ >
582
+ {`${currentHologramImage ? '✓' : latestHologramFaceImage ? '⏳' : '○'} Holo`}
583
+ </TextView>
584
+ </View>
585
+
586
+ <DebugImageThumbnail
587
+ image={currentHologramMaskImage}
588
+ label="Mask"
589
+ activeColor="#FF6B6B"
590
+ borderColor="#FF6B6B"
591
+ />
592
+ </View>
593
+ </View>
594
+ </View>
595
+ </>
596
+ );
597
+ });
598
+
599
+ // --- Test Mode Panel ---
600
+
601
+ export interface TestModePanelProps {
602
+ mrzText: string;
603
+ }
604
+
605
+ export const TestModePanel = React.memo(function TestModePanel({
606
+ mrzText,
607
+ }: TestModePanelProps) {
608
+ const insets = useSafeAreaInsets();
609
+
610
+ return (
611
+ <View
612
+ style={{
613
+ position: 'absolute',
614
+ bottom: 0,
615
+ left: 0,
616
+ right: 0,
617
+ maxHeight: '40%',
618
+ paddingBottom: insets.bottom,
619
+ backgroundColor: 'rgba(0, 0, 0, 0.95)',
620
+ borderTopWidth: 2,
621
+ borderTopColor: '#FFA500',
622
+ }}
623
+ >
624
+ <ScrollView style={{ flex: 1 }}>
625
+ <View style={{ padding: 10 }}>
626
+ <TextView
627
+ style={{
628
+ color: '#FFA500',
629
+ fontSize: 12,
630
+ fontWeight: 'bold',
631
+ marginBottom: 8,
632
+ textAlign: 'center',
633
+ }}
634
+ >
635
+ MRZ Text Read
636
+ </TextView>
637
+ <TextView
638
+ style={{
639
+ color: '#FFFFFF',
640
+ fontSize: 9,
641
+ fontFamily: 'monospace',
642
+ lineHeight: 16,
643
+ }}
644
+ >
645
+ {mrzText
646
+ .split('\n')
647
+ .map((line, i) => `Line ${i + 1}: ${line} (${line.length} chars)`)
648
+ .join('\n')}
649
+ </TextView>
650
+ </View>
651
+ </ScrollView>
652
+ </View>
653
+ );
654
+ });
655
+
656
+ export default DebugOverlay;
@@ -91,7 +91,6 @@ const EIDScanner = ({
91
91
  const appContext = React.useContext(AppContext);
92
92
  const [voiceGuidanceMessage, setVoiceGuidanceMessage] = useState<string>();
93
93
  const [attemptNumber, setAttemptNumber] = useState<number>(0);
94
- const lastVoiceGuidanceMessage = useRef<string>('');
95
94
 
96
95
  const readNFC = useCallback(async () => {
97
96
  const startTime = Date.now();
@@ -102,7 +101,6 @@ const EIDScanner = ({
102
101
  setIsScanned(false);
103
102
  setProgress(0);
104
103
  setProgressStage('');
105
- lastVoiceGuidanceMessage.current = '';
106
104
  resetLastMessage();
107
105
 
108
106
  // Track EID scan start using analytics helper
@@ -365,10 +363,8 @@ const EIDScanner = ({
365
363
  useEffect(() => {
366
364
  if (
367
365
  voiceGuidanceMessage &&
368
- appContext.currentWorkflowStep?.data?.voiceGuidanceActive &&
369
- voiceGuidanceMessage !== lastVoiceGuidanceMessage.current
366
+ appContext.currentWorkflowStep?.data?.voiceGuidanceActive
370
367
  ) {
371
- lastVoiceGuidanceMessage.current = voiceGuidanceMessage;
372
368
  speak(voiceGuidanceMessage, true);
373
369
  }
374
370
  }, [