@trustchex/react-native-sdk 1.374.0 → 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.
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +0 -9
- package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +636 -301
- package/ios/Camera/TrustchexCameraView.swift +8 -8
- package/ios/OpenCV/OpenCVHelper.h +0 -7
- package/ios/OpenCV/OpenCVHelper.mm +0 -60
- package/ios/OpenCV/OpenCVModule.h +0 -4
- package/ios/OpenCV/OpenCVModule.mm +440 -358
- package/lib/module/Shared/Components/DebugOverlay.js +541 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.constants.js +44 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +270 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +679 -1701
- package/lib/module/Shared/Components/IdentityDocumentCamera.types.js +3 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +273 -0
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Shared/Components/DebugOverlay.d.ts +30 -0
- package/lib/typescript/src/Shared/Components/DebugOverlay.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts +35 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -56
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +88 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts +116 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +93 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -0
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/Shared/Components/DebugOverlay.tsx +656 -0
- package/src/Shared/Components/IdentityDocumentCamera.constants.ts +44 -0
- package/src/Shared/Components/IdentityDocumentCamera.flows.ts +342 -0
- package/src/Shared/Components/IdentityDocumentCamera.tsx +1065 -2462
- package/src/Shared/Components/IdentityDocumentCamera.types.ts +136 -0
- package/src/Shared/Components/IdentityDocumentCamera.utils.ts +364 -0
- 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;
|
|
@@ -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
|