@trustchex/react-native-sdk 1.362.6 → 1.381.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 (58) hide show
  1. package/TrustchexSDK.podspec +3 -3
  2. package/android/build.gradle +3 -3
  3. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +64 -19
  4. package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +636 -301
  5. package/ios/Camera/TrustchexCameraView.swift +166 -119
  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/Shared/Components/DebugOverlay.js +541 -0
  11. package/lib/module/Shared/Components/FaceCamera.js +1 -0
  12. package/lib/module/Shared/Components/IdentityDocumentCamera.constants.js +44 -0
  13. package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +270 -0
  14. package/lib/module/Shared/Components/IdentityDocumentCamera.js +708 -1593
  15. package/lib/module/Shared/Components/IdentityDocumentCamera.types.js +3 -0
  16. package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +273 -0
  17. package/lib/module/Shared/Components/QrCodeScannerCamera.js +1 -8
  18. package/lib/module/Shared/Libs/mrz.utils.js +202 -9
  19. package/lib/module/Translation/Resources/en.js +0 -4
  20. package/lib/module/Translation/Resources/tr.js +0 -4
  21. package/lib/module/version.js +1 -1
  22. package/lib/typescript/src/Shared/Components/DebugOverlay.d.ts +30 -0
  23. package/lib/typescript/src/Shared/Components/DebugOverlay.d.ts.map +1 -0
  24. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
  25. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts +35 -0
  26. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts.map +1 -0
  27. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -56
  28. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  29. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +88 -0
  30. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -0
  31. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts +116 -0
  32. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts.map +1 -0
  33. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +93 -0
  34. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -0
  35. package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
  36. package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +1 -0
  37. package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -1
  38. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts +8 -0
  39. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
  40. package/lib/typescript/src/Translation/Resources/en.d.ts +0 -4
  41. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  42. package/lib/typescript/src/Translation/Resources/tr.d.ts +0 -4
  43. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  44. package/lib/typescript/src/version.d.ts +1 -1
  45. package/package.json +1 -1
  46. package/src/Shared/Components/DebugOverlay.tsx +656 -0
  47. package/src/Shared/Components/FaceCamera.tsx +1 -0
  48. package/src/Shared/Components/IdentityDocumentCamera.constants.ts +44 -0
  49. package/src/Shared/Components/IdentityDocumentCamera.flows.ts +342 -0
  50. package/src/Shared/Components/IdentityDocumentCamera.tsx +1105 -2324
  51. package/src/Shared/Components/IdentityDocumentCamera.types.ts +136 -0
  52. package/src/Shared/Components/IdentityDocumentCamera.utils.ts +364 -0
  53. package/src/Shared/Components/QrCodeScannerCamera.tsx +1 -9
  54. package/src/Shared/Components/TrustchexCamera.tsx +1 -0
  55. package/src/Shared/Libs/mrz.utils.ts +238 -26
  56. package/src/Translation/Resources/en.ts +0 -4
  57. package/src/Translation/Resources/tr.ts +0 -4
  58. 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;
@@ -366,6 +366,7 @@ const FaceCamera = ({
366
366
  enableFaceDetection={isActive}
367
367
  includeBase64={true} // Enabled to capture photos for liveness steps
368
368
  targetFps={5}
369
+ resolution="hd"
369
370
  onFrameAvailable={handleFrame}
370
371
  onCameraReady={handleCameraReady}
371
372
  onCameraError={handleCameraError}
@@ -0,0 +1,44 @@
1
+ export const HOLOGRAM_IMAGE_COUNT = 16;
2
+ export const HOLOGRAM_DETECTION_THRESHOLD = 1000;
3
+ export const HOLOGRAM_DETECTION_RETRY_COUNT = 3;
4
+ export const HOLOGRAM_CAPTURE_INTERVAL = 250;
5
+ export const HOLOGRAM_MAX_FRAMES_WITHOUT_FACE = 30;
6
+
7
+ export const MIN_BRIGHTNESS_THRESHOLD = 45;
8
+ export const MAX_BRIGHTNESS_THRESHOLD = 210; // Glare detection threshold
9
+ export const MAX_CONSECUTIVE_QUALITY_FAILURES = 30;
10
+ export const FACE_EDGE_MARGIN_PERCENT = 0.08; // Minimum 8% margin from edges
11
+ export const MIN_CARD_FACE_SIZE_PERCENT = 0.05; // Main face: min 5% of frame width
12
+ export const MIN_MLI_FACE_SIZE_PERCENT = 0.01; // MLI (Multi Layer Image): min 1% of frame width - lowered for better detection
13
+
14
+ export const REQUIRED_CONSISTENT_MRZ_READS = 2;
15
+ export const REQUIRED_CONSISTENT_DOCTYPE_DETECTIONS = 3;
16
+
17
+ export const SIGNATURE_REGEX = /s[gi]g?n[au]?t[u]?r|imz[a]s?/i;
18
+ export const SIGNATURE_TEXT_REGEX = /signature|imza|İmza/i;
19
+ export const MRZ_BLOCK_PATTERN = /[A-Z0-9<]{8,}.*</i;
20
+ export const PASSPORT_MRZ_PATTERN = /P<[A-Z]{3}/;
21
+
22
+ // BDDK Madde 7 - Security Feature Validation
23
+ // When NFC chip reading unavailable, must verify at least 4 visual security features
24
+ export const MIN_SECURITY_FEATURES_REQUIRED = 4;
25
+ export const SECURITY_FEATURE_NAMES = {
26
+ icao_photo: 'ICAO Uyumlu Fotoğraf (ICAO Compliant Photo)',
27
+ barcode: 'Barkod (Barcode)',
28
+ hologram: 'DOVID/OVD (Hologram)',
29
+ wet_signature: 'Islak İmza (Wet Signature)',
30
+ guilloche: 'Giyoş (Guilloche Pattern)',
31
+ rainbow_print: 'Gökkuşağı Baskı (Rainbow Print)',
32
+ ovi: 'Optik Değişken Mürekkep (OVI)',
33
+ latent_image: 'Gizli Görüntü (Latent Image)',
34
+ microtext: 'Mikro Yazı (Microtext)',
35
+ } as const;
36
+
37
+ // BDDK Madde 8 - Device-side Face Shape & Visual Similarity Thresholds
38
+ // Backend does full FaceNet biometric matching
39
+ // Relaxed thresholds for device-side pre-filtering only
40
+ export const FACE_SHAPE_MATCH_THRESHOLD = 0.5; // Facial geometry similarity (0-1) - very lenient
41
+ export const FACE_VISUAL_SIMILARITY_THRESHOLD = 0.5; // Pixel-level similarity (0-1) - very lenient
42
+ export const FACE_ASPECT_RATIO_TOLERANCE = 0.15; // 15% tolerance for width/height ratio
43
+ export const FACE_SIZE_RATIO_MIN = 0.7; // Minimum 70% of primary face area
44
+ export const FACE_SIZE_RATIO_MAX = 1.3; // Maximum 130% of primary face area