@trustchex/react-native-sdk 1.334.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.
- package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +8 -2
- package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +5 -1
- package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +5 -1
- package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +29 -15
- package/lib/module/Screens/Static/OTPVerificationScreen.js +285 -0
- package/lib/module/Screens/Static/ResultScreen.js +90 -26
- package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +48 -134
- package/lib/module/Shared/Components/DebugNavigationPanel.js +252 -0
- package/lib/module/Shared/Components/EIDScanner.js +142 -17
- package/lib/module/Shared/Components/FaceCamera.js +23 -11
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +295 -44
- package/lib/module/Shared/Components/NavigationManager.js +19 -3
- package/lib/module/Shared/Config/camera-enhancement.config.js +58 -0
- package/lib/module/Shared/Contexts/AppContext.js +1 -0
- package/lib/module/Shared/Libs/camera.utils.js +221 -1
- package/lib/module/Shared/Libs/frame-enhancement.utils.js +133 -0
- package/lib/module/Shared/Libs/mrz.utils.js +98 -1
- package/lib/module/Translation/Resources/en.js +30 -0
- package/lib/module/Translation/Resources/tr.js +30 -0
- package/lib/module/Trustchex.js +49 -39
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -0
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts +3 -0
- package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Config/camera-enhancement.config.d.ts +54 -0
- package/lib/typescript/src/Shared/Config/camera-enhancement.config.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +2 -0
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +65 -1
- package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/frame-enhancement.utils.d.ts +25 -0
- package/lib/typescript/src/Shared/Libs/frame-enhancement.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts +30 -0
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +30 -0
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +3 -3
- package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +6 -2
- package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +3 -1
- package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +3 -1
- package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +27 -17
- package/src/Screens/Static/OTPVerificationScreen.tsx +379 -0
- package/src/Screens/Static/ResultScreen.tsx +160 -101
- package/src/Screens/Static/VerificationSessionCheckScreen.tsx +51 -196
- package/src/Shared/Components/DebugNavigationPanel.tsx +262 -0
- package/src/Shared/Components/EIDScanner.tsx +144 -19
- package/src/Shared/Components/FaceCamera.tsx +38 -21
- package/src/Shared/Components/IdentityDocumentCamera.tsx +399 -101
- package/src/Shared/Components/NavigationManager.tsx +19 -3
- package/src/Shared/Config/camera-enhancement.config.ts +46 -0
- package/src/Shared/Contexts/AppContext.ts +3 -0
- package/src/Shared/Libs/camera.utils.ts +240 -1
- package/src/Shared/Libs/frame-enhancement.utils.ts +217 -0
- package/src/Shared/Libs/mrz.utils.ts +78 -1
- package/src/Translation/Resources/en.ts +30 -0
- package/src/Translation/Resources/tr.ts +30 -0
- package/src/Trustchex.tsx +58 -46
- package/src/version.ts +1 -1
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
/* eslint-disable react-native/no-inline-styles */
|
|
4
4
|
import React, { useEffect, useState } from 'react';
|
|
5
5
|
import { View, StyleSheet, Text as TextView, Platform, Vibration, TouchableOpacity, Text, Linking, Image, ActivityIndicator } from 'react-native';
|
|
6
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
6
7
|
import { Camera, runAtTargetFps, useCameraDevice, useCameraFormat, useCameraPermission, useFrameProcessor } from 'react-native-vision-camera';
|
|
7
8
|
import { runAsync } from "../Libs/worklet.utils.js";
|
|
8
9
|
import { useRunOnJS, useSharedValue } from 'react-native-worklets-core';
|
|
@@ -14,10 +15,11 @@ import { useKeepAwake } from "../Libs/native-keep-awake.utils.js";
|
|
|
14
15
|
import ImageEditor from '@react-native-community/image-editor';
|
|
15
16
|
import { useIsFocused } from '@react-navigation/native';
|
|
16
17
|
import { AdaptiveThresholdTypes, ColorConversionCodes, DataTypes, ObjectType, OpenCV, ThresholdTypes } from 'react-native-fast-opencv';
|
|
17
|
-
import { getAverageBrightness } from "../Libs/camera.utils.js";
|
|
18
|
+
import { getAverageBrightness, getScanAreaCenterPoint, calculateExposureStep, isBlurry as checkBlurry } from "../Libs/camera.utils.js";
|
|
18
19
|
import { useTranslation } from 'react-i18next';
|
|
19
20
|
import LottieView from 'lottie-react-native';
|
|
20
21
|
import StyledButton from "./StyledButton.js";
|
|
22
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
21
23
|
import { scanCodes } from "../VisionCameraPlugins/BarcodeScanner/index.js";
|
|
22
24
|
import { speakWithDebounce } from "../Libs/tts.utils.js";
|
|
23
25
|
import AppContext from "../Contexts/AppContext.js";
|
|
@@ -39,6 +41,7 @@ const IdentityDocumentCamera = ({
|
|
|
39
41
|
}) => {
|
|
40
42
|
useKeepAwake();
|
|
41
43
|
const theme = useTheme();
|
|
44
|
+
const insets = useSafeAreaInsets();
|
|
42
45
|
const appContext = React.useContext(AppContext);
|
|
43
46
|
const cameraRef = React.useRef(null);
|
|
44
47
|
const cameraPermission = useCameraPermission();
|
|
@@ -68,13 +71,17 @@ const IdentityDocumentCamera = ({
|
|
|
68
71
|
const [currentHologramImage, setCurrentHologramImage] = useState(undefined);
|
|
69
72
|
const [currentSecondaryFaceImage, setCurrentSecondaryFaceImage] = useState(undefined);
|
|
70
73
|
const [isBrightnessLow, setIsBrightnessLow] = useState(false);
|
|
74
|
+
const [isFrameBlurry, setIsFrameBlurry] = useState(false);
|
|
71
75
|
const [hasGuideShown, setHasGuideShown] = useState(false);
|
|
72
76
|
const [status, setStatus] = useState('SEARCHING');
|
|
73
77
|
const [nextStep, setNextStep] = useState('SCAN_ID_FRONT_OR_PASSPORT');
|
|
78
|
+
const [completedStep, setCompletedStep] = useState(null);
|
|
79
|
+
const [detectedDocumentType, setDetectedDocumentType] = useState('UNKNOWN');
|
|
74
80
|
const hologramDetectionCurrentRetryCount = useSharedValue(0);
|
|
75
81
|
const secondaryFaceDetectionCurrentRetryCount = useSharedValue(0);
|
|
76
82
|
const mrzDetectionCurrentRetryCount = useSharedValue(0);
|
|
77
83
|
const faceDetectionErrorCount = useSharedValue(0);
|
|
84
|
+
const consecutiveBlurCount = useSharedValue(0);
|
|
78
85
|
const [faceDetectionEnabled, setFaceDetectionEnabled] = useState(true);
|
|
79
86
|
const {
|
|
80
87
|
t
|
|
@@ -146,12 +153,109 @@ const IdentityDocumentCamera = ({
|
|
|
146
153
|
}, [device, format, isFocused, hologramDetectionCurrentRetryCount, secondaryFaceDetectionCurrentRetryCount, mrzDetectionCurrentRetryCount, faceDetectionErrorCount]);
|
|
147
154
|
useEffect(() => {
|
|
148
155
|
if (hasGuideShown) {
|
|
149
|
-
|
|
156
|
+
let message = '';
|
|
157
|
+
|
|
158
|
+
// Priority: scanned > incorrect > blur during scanning > brightness > blur > step-specific
|
|
159
|
+
if (status === 'SCANNED') {
|
|
160
|
+
// Use step-specific completion messages
|
|
161
|
+
if (completedStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
|
|
162
|
+
message = detectedDocumentType === 'PASSPORT' ? t('identityDocumentCamera.passportScanned') : t('identityDocumentCamera.frontSideScanned');
|
|
163
|
+
} else if (completedStep === 'SCAN_ID_BACK') {
|
|
164
|
+
message = t('identityDocumentCamera.backSideScanned');
|
|
165
|
+
} else if (completedStep === 'SCAN_HOLOGRAM') {
|
|
166
|
+
message = t('identityDocumentCamera.hologramVerified');
|
|
167
|
+
} else {
|
|
168
|
+
message = t('identityDocumentCamera.scanCompleted');
|
|
169
|
+
}
|
|
170
|
+
} else if (status === 'INCORRECT') {
|
|
171
|
+
// Wrong side detected - warn user
|
|
172
|
+
if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
|
|
173
|
+
message = t('identityDocumentCamera.wrongSideFront');
|
|
174
|
+
} else if (nextStep === 'SCAN_ID_BACK') {
|
|
175
|
+
message = t('identityDocumentCamera.wrongSideBack');
|
|
176
|
+
}
|
|
177
|
+
} else if (isBrightnessLow) {
|
|
178
|
+
// Brightness warning takes priority over blur
|
|
179
|
+
message = t('identityDocumentCamera.lowBrightness');
|
|
180
|
+
} else if (isFrameBlurry) {
|
|
181
|
+
// Show blur warning only when brightness is sufficient
|
|
182
|
+
message = t('identityDocumentCamera.avoidBlur');
|
|
183
|
+
} else if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
|
|
184
|
+
// Enhanced feedback based on detection status
|
|
185
|
+
if (status === 'SCANNING') {
|
|
186
|
+
if (currentFaceImage) {
|
|
187
|
+
// Document-specific detection message
|
|
188
|
+
if (detectedDocumentType === 'PASSPORT') {
|
|
189
|
+
message = t('identityDocumentCamera.passportDetected');
|
|
190
|
+
} else if (detectedDocumentType === 'ID_FRONT') {
|
|
191
|
+
message = t('identityDocumentCamera.idCardFrontDetected');
|
|
192
|
+
} else {
|
|
193
|
+
message = t('identityDocumentCamera.readingDocument');
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
message = t('identityDocumentCamera.readingDocument');
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
message = t('identityDocumentCamera.alignPhotoSide');
|
|
200
|
+
}
|
|
201
|
+
} else if (nextStep === 'SCAN_HOLOGRAM') {
|
|
202
|
+
message = t('identityDocumentCamera.alignHologram');
|
|
203
|
+
} else if (nextStep === 'SCAN_ID_BACK') {
|
|
204
|
+
if (status === 'SCANNING') {
|
|
205
|
+
message = t('identityDocumentCamera.readingDocument');
|
|
206
|
+
} else {
|
|
207
|
+
message = t('identityDocumentCamera.alignIDBackSide');
|
|
208
|
+
}
|
|
209
|
+
} else if (nextStep === 'COMPLETED') {
|
|
210
|
+
message = t('identityDocumentCamera.scanCompleted');
|
|
211
|
+
}
|
|
150
212
|
if (appContext.currentWorkflowStep?.data?.voiceGuidanceActive && message) {
|
|
151
213
|
speakWithDebounce(message);
|
|
152
214
|
}
|
|
153
215
|
}
|
|
154
|
-
}, [appContext.currentWorkflowStep?.data?.voiceGuidanceActive, hasGuideShown, isBrightnessLow, nextStep, t]);
|
|
216
|
+
}, [appContext.currentWorkflowStep?.data?.voiceGuidanceActive, hasGuideShown, isBrightnessLow, isFrameBlurry, nextStep, status, completedStep, currentFaceImage, detectedDocumentType, t]);
|
|
217
|
+
|
|
218
|
+
// Auto-reset INCORRECT status after showing warning briefly
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
if (status === 'INCORRECT') {
|
|
221
|
+
const timeout = setTimeout(() => {
|
|
222
|
+
setStatus('SEARCHING');
|
|
223
|
+
}, 1500); // Show warning for 1.5 seconds
|
|
224
|
+
return () => clearTimeout(timeout);
|
|
225
|
+
}
|
|
226
|
+
}, [status]);
|
|
227
|
+
|
|
228
|
+
// Periodic autofocus - refocus on scan area center every 2.5 seconds
|
|
229
|
+
useEffect(() => {
|
|
230
|
+
if (!isActive || !device || !cameraRef.current || !device.supportsFocus) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Only autofocus during searching and scanning states
|
|
235
|
+
if (status !== 'SEARCHING' && status !== 'SCANNING') {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const autofocusInterval = setInterval(async () => {
|
|
239
|
+
try {
|
|
240
|
+
// Get camera dimensions (assuming format dimensions)
|
|
241
|
+
const width = format?.videoWidth ?? 1920;
|
|
242
|
+
const height = format?.videoHeight ?? 1080;
|
|
243
|
+
|
|
244
|
+
// Calculate center point of scan area
|
|
245
|
+
const centerPoint = getScanAreaCenterPoint(width, height);
|
|
246
|
+
|
|
247
|
+
// Focus on the center of the scan area
|
|
248
|
+
await cameraRef.current?.focus({
|
|
249
|
+
x: centerPoint.x,
|
|
250
|
+
y: centerPoint.y
|
|
251
|
+
});
|
|
252
|
+
} catch (error) {
|
|
253
|
+
// Ignore autofocus errors
|
|
254
|
+
}
|
|
255
|
+
}, 2500); // Every 2.5 seconds
|
|
256
|
+
|
|
257
|
+
return () => clearInterval(autofocusInterval);
|
|
258
|
+
}, [isActive, device, format, status]);
|
|
155
259
|
const detectDocumentType = (faces, ocrText, mrzFields) => {
|
|
156
260
|
if (faces.length > 0 && !mrzFields && ocrText?.includes('Signature')
|
|
157
261
|
// ocrText?.includes('Surname') &&
|
|
@@ -357,13 +461,28 @@ const IdentityDocumentCamera = ({
|
|
|
357
461
|
}
|
|
358
462
|
return croppedFaces;
|
|
359
463
|
};
|
|
360
|
-
const setNextStepAndVibrate = nextStepType => {
|
|
464
|
+
const setNextStepAndVibrate = (nextStepType, fromStep) => {
|
|
465
|
+
// Track which step was just completed for showing specific message
|
|
466
|
+
if (fromStep) {
|
|
467
|
+
setCompletedStep(fromStep);
|
|
468
|
+
}
|
|
361
469
|
setNextStep(nextStepType);
|
|
362
470
|
Vibration.vibrate(100);
|
|
471
|
+
|
|
472
|
+
// Reset status after delay to show success animation fully before next step
|
|
473
|
+
if (nextStepType !== 'COMPLETED') {
|
|
474
|
+
setTimeout(() => {
|
|
475
|
+
setStatus('SEARCHING');
|
|
476
|
+
setCompletedStep(null);
|
|
477
|
+
}, 2000); // Show success checkmark for 2 seconds before transitioning
|
|
478
|
+
}
|
|
363
479
|
};
|
|
364
480
|
const handleBrightness = useRunOnJS(isBright => {
|
|
365
481
|
setIsBrightnessLow(!isBright);
|
|
366
482
|
}, [setIsBrightnessLow]);
|
|
483
|
+
const handleBlurStatus = useRunOnJS(blurry => {
|
|
484
|
+
setIsFrameBlurry(blurry);
|
|
485
|
+
}, [setIsFrameBlurry]);
|
|
367
486
|
const handleFaceAndText = useRunOnJS(async (text, faces, frameWidth, frameHeight, barcode, image) => {
|
|
368
487
|
if (device?.hasTorch && isTorchOn && (currentHologramImage || hologramDetectionCurrentRetryCount.value >= HOLOGRAM_DETECTION_RETRY_COUNT)) {
|
|
369
488
|
setIsTorchOn(false);
|
|
@@ -372,7 +491,18 @@ const IdentityDocumentCamera = ({
|
|
|
372
491
|
setStatus('SCANNED');
|
|
373
492
|
return;
|
|
374
493
|
}
|
|
494
|
+
|
|
495
|
+
// Early wrong side detection for SCAN_ID_BACK: if faces detected, it's the front side
|
|
496
|
+
if (nextStep === 'SCAN_ID_BACK' && faces.length > 0) {
|
|
497
|
+
console.log('[WRONG_SIDE] Back side expected but faces detected:', faces.length);
|
|
498
|
+
setStatus('INCORRECT');
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
375
501
|
if (!text || text.length < 10 || !image) {
|
|
502
|
+
// Log when searching to help debug
|
|
503
|
+
if (nextStep === 'SCAN_ID_BACK') {
|
|
504
|
+
console.log('[SCAN_ID_BACK] Searching... faces:', faces.length, 'text length:', text?.length || 0);
|
|
505
|
+
}
|
|
376
506
|
setStatus('SEARCHING');
|
|
377
507
|
return;
|
|
378
508
|
}
|
|
@@ -390,11 +520,21 @@ const IdentityDocumentCamera = ({
|
|
|
390
520
|
};
|
|
391
521
|
scannedData.faceImage = croppedFaces[0];
|
|
392
522
|
setCurrentFaceImage(croppedFaces[0]);
|
|
523
|
+
|
|
524
|
+
// Track detected document type for UI feedback
|
|
525
|
+
if (documentType !== 'UNKNOWN') {
|
|
526
|
+
setDetectedDocumentType(documentType);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Detect wrong side based on document type or face presence (works for both normal and eID scan)
|
|
530
|
+
// For ID_BACK step: if faces are detected, it's likely the front side (wrong)
|
|
531
|
+
// For FRONT step: if ID_BACK is detected, it's the wrong side
|
|
532
|
+
const isWrongSide = nextStep === 'SCAN_ID_FRONT_OR_PASSPORT' && documentType === 'ID_BACK' || nextStep === 'SCAN_ID_BACK' && (documentType === 'ID_FRONT' || documentType === 'PASSPORT' || croppedFaces.length > 0);
|
|
533
|
+
if (isWrongSide) {
|
|
534
|
+
setStatus('INCORRECT');
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
393
537
|
if (!onlyMRZScan) {
|
|
394
|
-
if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT' && !(documentType === 'ID_FRONT' || documentType === 'PASSPORT') || nextStep === 'SCAN_ID_BACK' && documentType !== 'ID_BACK') {
|
|
395
|
-
setStatus('INCORRECT');
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
538
|
if (croppedFaces.length > 0 && croppedFaces[0]) {
|
|
399
539
|
if (currentFaceImage) {
|
|
400
540
|
scannedData.faceImage = currentFaceImage;
|
|
@@ -433,28 +573,37 @@ const IdentityDocumentCamera = ({
|
|
|
433
573
|
if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
|
|
434
574
|
setStatus('SCANNED');
|
|
435
575
|
if (onlyMRZScan) {
|
|
436
|
-
setNextStepAndVibrate('SCAN_ID_BACK');
|
|
576
|
+
setNextStepAndVibrate('SCAN_ID_BACK', 'SCAN_ID_FRONT_OR_PASSPORT');
|
|
437
577
|
onIdentityDocumentScanned(scannedData);
|
|
438
578
|
} else {
|
|
439
|
-
setNextStepAndVibrate('SCAN_HOLOGRAM');
|
|
579
|
+
setNextStepAndVibrate('SCAN_HOLOGRAM', 'SCAN_ID_FRONT_OR_PASSPORT');
|
|
440
580
|
}
|
|
441
581
|
} else if (nextStep === 'SCAN_HOLOGRAM' && (!!scannedData.hologramImage || hologramDetectionCurrentRetryCount.value >= HOLOGRAM_DETECTION_RETRY_COUNT) && (!!scannedData.secondaryFaceImage || secondaryFaceDetectionCurrentRetryCount.value >= SECOND_FACE_DETECTION_RETRY_COUNT)) {
|
|
442
582
|
setStatus('SCANNED');
|
|
443
|
-
setNextStepAndVibrate('SCAN_ID_BACK');
|
|
583
|
+
setNextStepAndVibrate('SCAN_ID_BACK', 'SCAN_HOLOGRAM');
|
|
444
584
|
onIdentityDocumentScanned(scannedData);
|
|
445
585
|
}
|
|
446
586
|
} else if (documentType === 'PASSPORT') {
|
|
447
587
|
if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT' && !scannedData.hologramImage) {
|
|
448
|
-
|
|
588
|
+
// For passport, require valid MRZ before proceeding
|
|
449
589
|
if (onlyMRZScan) {
|
|
450
|
-
|
|
451
|
-
|
|
590
|
+
// eID scan: require valid MRZ
|
|
591
|
+
if (!!scannedData.mrzText && (parsedMRZData?.valid || mrzDetectionCurrentRetryCount.value >= MRZ_VALIDATION_RETRY_COUNT)) {
|
|
592
|
+
setStatus('SCANNED');
|
|
593
|
+
setNextStepAndVibrate('COMPLETED', 'SCAN_ID_FRONT_OR_PASSPORT');
|
|
594
|
+
onIdentityDocumentScanned(scannedData);
|
|
595
|
+
} else if (!parsedMRZData?.valid) {
|
|
596
|
+
mrzDetectionCurrentRetryCount.value++;
|
|
597
|
+
setStatus('SCANNING');
|
|
598
|
+
}
|
|
452
599
|
} else {
|
|
453
|
-
|
|
600
|
+
// Normal scan: proceed to hologram check (MRZ validated later)
|
|
601
|
+
setStatus('SCANNED');
|
|
602
|
+
setNextStepAndVibrate('SCAN_HOLOGRAM', 'SCAN_ID_FRONT_OR_PASSPORT');
|
|
454
603
|
}
|
|
455
604
|
} else if ((nextStep === 'SCAN_HOLOGRAM' && (!!scannedData.hologramImage || hologramDetectionCurrentRetryCount.value >= HOLOGRAM_DETECTION_RETRY_COUNT) && (!!scannedData.secondaryFaceImage || secondaryFaceDetectionCurrentRetryCount.value >= SECOND_FACE_DETECTION_RETRY_COUNT) || onlyMRZScan) && !!scannedData.mrzText && (parsedMRZData?.valid || mrzDetectionCurrentRetryCount.value >= MRZ_VALIDATION_RETRY_COUNT)) {
|
|
456
605
|
setStatus('SCANNED');
|
|
457
|
-
setNextStepAndVibrate('COMPLETED');
|
|
606
|
+
setNextStepAndVibrate('COMPLETED', 'SCAN_HOLOGRAM');
|
|
458
607
|
onIdentityDocumentScanned(scannedData);
|
|
459
608
|
} else if (!parsedMRZData?.valid) {
|
|
460
609
|
mrzDetectionCurrentRetryCount.value++;
|
|
@@ -463,7 +612,7 @@ const IdentityDocumentCamera = ({
|
|
|
463
612
|
if ((parsedMRZData?.fields?.issuingState === 'TUR' && barcode?.value?.trim() === parsedMRZData?.fields?.optional1?.trim() || parsedMRZData?.fields?.issuingState !== 'TUR' || onlyMRZScan) && nextStep === 'SCAN_ID_BACK' && !!scannedData.mrzText && (parsedMRZData?.valid || mrzDetectionCurrentRetryCount.value >= MRZ_VALIDATION_RETRY_COUNT)) {
|
|
464
613
|
scannedData.barcodeValue = barcode?.value ?? undefined;
|
|
465
614
|
setStatus('SCANNED');
|
|
466
|
-
setNextStepAndVibrate('COMPLETED');
|
|
615
|
+
setNextStepAndVibrate('COMPLETED', 'SCAN_ID_BACK');
|
|
467
616
|
onIdentityDocumentScanned(scannedData);
|
|
468
617
|
} else if (!parsedMRZData?.valid) {
|
|
469
618
|
mrzDetectionCurrentRetryCount.value++;
|
|
@@ -483,19 +632,56 @@ const IdentityDocumentCamera = ({
|
|
|
483
632
|
const handleExposureValue = useRunOnJS(value => {
|
|
484
633
|
setExposure(value);
|
|
485
634
|
}, [exposure]);
|
|
635
|
+
|
|
636
|
+
// Focus trigger for when blur is detected (called from worklet)
|
|
637
|
+
const triggerFocus = useRunOnJS(async () => {
|
|
638
|
+
if (!cameraRef.current || !device?.supportsFocus) {
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
try {
|
|
642
|
+
const width = format?.videoWidth ?? 1920;
|
|
643
|
+
const height = format?.videoHeight ?? 1080;
|
|
644
|
+
const centerPoint = getScanAreaCenterPoint(width, height);
|
|
645
|
+
await cameraRef.current.focus({
|
|
646
|
+
x: centerPoint.x,
|
|
647
|
+
y: centerPoint.y
|
|
648
|
+
});
|
|
649
|
+
} catch (error) {
|
|
650
|
+
// Ignore focus errors
|
|
651
|
+
}
|
|
652
|
+
}, [device, format]);
|
|
486
653
|
const handleExposureAndBrightness = frame => {
|
|
487
654
|
'worklet';
|
|
488
655
|
|
|
489
656
|
const averageBrightness = getAverageBrightness(frame);
|
|
490
657
|
const minExposure = device?.minExposure ?? 0;
|
|
491
658
|
const maxExposure = device?.maxExposure ?? 0;
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
659
|
+
|
|
660
|
+
// Dynamic thresholds based on scanning state using config values
|
|
661
|
+
// Face detection requires higher minimum brightness for reliable detection
|
|
662
|
+
const isFrontOrPassport = nextStep === 'SCAN_ID_FRONT_OR_PASSPORT';
|
|
663
|
+
const isBack = nextStep === 'SCAN_ID_BACK';
|
|
664
|
+
|
|
665
|
+
// Using config values: faceDetection { low: 50, high: 110, target: 85 }, mrzScanning { low: 45, high: 130, target: 80 }
|
|
666
|
+
const lowerBrightnessBound = isFrontOrPassport ? 50 : 40;
|
|
667
|
+
const upperBrightnessBound = isBack ? 130 : 120;
|
|
668
|
+
const targetBrightness = isFrontOrPassport ? 85 : 80;
|
|
669
|
+
|
|
670
|
+
// Smooth exposure adjustment with hysteresis to prevent oscillation
|
|
671
|
+
// Only adjust if brightness is significantly outside the acceptable range
|
|
672
|
+
const hysteresis = 5; // Dead zone to prevent jitter
|
|
673
|
+
|
|
674
|
+
if (averageBrightness < lowerBrightnessBound - hysteresis && exposureValue.value < maxExposure) {
|
|
675
|
+
// Increase exposure smoothly when too dark
|
|
676
|
+
const step = calculateExposureStep(averageBrightness, targetBrightness);
|
|
677
|
+
exposureValue.value = Math.min(maxExposure, exposureValue.value + step);
|
|
678
|
+
} else if (averageBrightness > upperBrightnessBound + hysteresis && exposureValue.value > minExposure) {
|
|
679
|
+
// Decrease exposure smoothly when too bright
|
|
680
|
+
const step = calculateExposureStep(averageBrightness, targetBrightness);
|
|
681
|
+
exposureValue.value = Math.max(minExposure, exposureValue.value - step);
|
|
498
682
|
}
|
|
683
|
+
// When within acceptable range (with hysteresis), don't adjust - prevents oscillation
|
|
684
|
+
|
|
499
685
|
const isBright = averageBrightness > lowerBrightnessBound;
|
|
500
686
|
handleExposureValue(exposureValue.value);
|
|
501
687
|
handleBrightness(isBright);
|
|
@@ -510,6 +696,25 @@ const IdentityDocumentCamera = ({
|
|
|
510
696
|
return;
|
|
511
697
|
}
|
|
512
698
|
|
|
699
|
+
// Check for blur before processing - skip blurry frames
|
|
700
|
+
// Use different thresholds: 25 for front (face detection), 30 for back (MRZ/text)
|
|
701
|
+
// Higher thresholds with improved Laplacian algorithm using H+V gradients
|
|
702
|
+
const isFront = nextStep === 'SCAN_ID_FRONT_OR_PASSPORT';
|
|
703
|
+
const blurThreshold = isFront ? 25 : 30;
|
|
704
|
+
const blurry = checkBlurry(frame, blurThreshold);
|
|
705
|
+
handleBlurStatus(blurry);
|
|
706
|
+
if (blurry) {
|
|
707
|
+
consecutiveBlurCount.value++;
|
|
708
|
+
// Only trigger focus after 2 consecutive blurry frames (matching Flutter)
|
|
709
|
+
if (consecutiveBlurCount.value >= 2) {
|
|
710
|
+
triggerFocus();
|
|
711
|
+
consecutiveBlurCount.value = 0;
|
|
712
|
+
}
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
// Reset blur count on sharp frame
|
|
716
|
+
consecutiveBlurCount.value = 0;
|
|
717
|
+
|
|
513
718
|
// Validate frame dimensions before processing
|
|
514
719
|
if (!frame.width || !frame.height || frame.width <= 0 || frame.height <= 0) {
|
|
515
720
|
console.warn('Invalid frame dimensions:', {
|
|
@@ -562,6 +767,8 @@ const IdentityDocumentCamera = ({
|
|
|
562
767
|
}
|
|
563
768
|
|
|
564
769
|
// Text recognition with error handling
|
|
770
|
+
// Note: CLAHE enhancement is applied to captured images, not live frames
|
|
771
|
+
// ML Kit plugins work directly on Frame objects and don't support Mat input
|
|
565
772
|
let scannedText;
|
|
566
773
|
try {
|
|
567
774
|
scannedText = scanText(frame);
|
|
@@ -623,7 +830,7 @@ const IdentityDocumentCamera = ({
|
|
|
623
830
|
}
|
|
624
831
|
}, [handleFaceAndText, isCameraInitialized]);
|
|
625
832
|
if (!permissionsRequested) {
|
|
626
|
-
return /*#__PURE__*/_jsx(
|
|
833
|
+
return /*#__PURE__*/_jsx(SafeAreaView, {
|
|
627
834
|
style: styles.permissionContainer,
|
|
628
835
|
children: /*#__PURE__*/_jsx(ActivityIndicator, {
|
|
629
836
|
size: "large",
|
|
@@ -632,7 +839,7 @@ const IdentityDocumentCamera = ({
|
|
|
632
839
|
});
|
|
633
840
|
}
|
|
634
841
|
if (!cameraPermission.hasPermission) {
|
|
635
|
-
return /*#__PURE__*/_jsxs(
|
|
842
|
+
return /*#__PURE__*/_jsxs(SafeAreaView, {
|
|
636
843
|
style: styles.permissionContainer,
|
|
637
844
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
638
845
|
style: styles.permissionText,
|
|
@@ -647,7 +854,7 @@ const IdentityDocumentCamera = ({
|
|
|
647
854
|
});
|
|
648
855
|
}
|
|
649
856
|
if (device == null) {
|
|
650
|
-
return /*#__PURE__*/_jsx(
|
|
857
|
+
return /*#__PURE__*/_jsx(SafeAreaView, {
|
|
651
858
|
style: styles.permissionContainer,
|
|
652
859
|
children: /*#__PURE__*/_jsx(TextView, {
|
|
653
860
|
style: styles.permissionText,
|
|
@@ -673,7 +880,7 @@ const IdentityDocumentCamera = ({
|
|
|
673
880
|
};
|
|
674
881
|
return /*#__PURE__*/_jsx(View, {
|
|
675
882
|
style: StyleSheet.absoluteFill,
|
|
676
|
-
children: !hasGuideShown ? /*#__PURE__*/_jsxs(
|
|
883
|
+
children: !hasGuideShown ? /*#__PURE__*/_jsxs(SafeAreaView, {
|
|
677
884
|
style: styles.guide,
|
|
678
885
|
children: [/*#__PURE__*/_jsx(LottieView, {
|
|
679
886
|
source: require('../../Shared/Animations/id-or-passport.json'),
|
|
@@ -722,12 +929,26 @@ const IdentityDocumentCamera = ({
|
|
|
722
929
|
onInitialized: () => {
|
|
723
930
|
isCameraInitialized.value = true;
|
|
724
931
|
}
|
|
725
|
-
}), /*#__PURE__*/
|
|
726
|
-
style: styles.topZone,
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
932
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
933
|
+
style: [styles.topZone, {
|
|
934
|
+
paddingTop: insets.top
|
|
935
|
+
}],
|
|
936
|
+
children: [nextStep !== 'COMPLETED' && status !== 'SCANNED' && detectedDocumentType !== 'UNKNOWN' && /*#__PURE__*/_jsx(TextView, {
|
|
937
|
+
style: styles.stepIndicator,
|
|
938
|
+
children: nextStep === 'SCAN_ID_FRONT_OR_PASSPORT' ? `${t('identityDocumentCamera.frontSide')} • ${t('identityDocumentCamera.stepProgress', {
|
|
939
|
+
current: 1,
|
|
940
|
+
total: onlyMRZScan ? detectedDocumentType === 'PASSPORT' ? 1 : 2 : detectedDocumentType === 'PASSPORT' ? 2 : 3
|
|
941
|
+
})}` : nextStep === 'SCAN_HOLOGRAM' ? `${t('identityDocumentCamera.hologramCheck')} • ${t('identityDocumentCamera.stepProgress', {
|
|
942
|
+
current: 2,
|
|
943
|
+
total: detectedDocumentType === 'PASSPORT' ? 2 : 3
|
|
944
|
+
})}` : nextStep === 'SCAN_ID_BACK' ? `${t('identityDocumentCamera.backSide')} • ${t('identityDocumentCamera.stepProgress', {
|
|
945
|
+
current: 3,
|
|
946
|
+
total: 3
|
|
947
|
+
})}` : ''
|
|
948
|
+
}), /*#__PURE__*/_jsx(TextView, {
|
|
949
|
+
style: [styles.topZoneText, status === 'SCANNING' && styles.topZoneTextScanning, status === 'SCANNED' && styles.topZoneTextSuccess, status === 'INCORRECT' && styles.topZoneTextError, (isBrightnessLow || isFrameBlurry) && styles.topZoneTextWarning],
|
|
950
|
+
children: status === 'SCANNED' ? completedStep === 'SCAN_ID_FRONT_OR_PASSPORT' ? detectedDocumentType === 'PASSPORT' ? t('identityDocumentCamera.passportScanned') : t('identityDocumentCamera.frontSideScanned') : completedStep === 'SCAN_ID_BACK' ? t('identityDocumentCamera.backSideScanned') : completedStep === 'SCAN_HOLOGRAM' ? t('identityDocumentCamera.hologramVerified') : t('identityDocumentCamera.scanCompleted') : status === 'INCORRECT' ? nextStep === 'SCAN_ID_FRONT_OR_PASSPORT' ? t('identityDocumentCamera.wrongSideFront') : nextStep === 'SCAN_ID_BACK' ? t('identityDocumentCamera.wrongSideBack') : t('identityDocumentCamera.alignPhotoSide') : isBrightnessLow ? t('identityDocumentCamera.lowBrightness') : isFrameBlurry ? t('identityDocumentCamera.avoidBlur') : nextStep === 'SCAN_ID_FRONT_OR_PASSPORT' ? status === 'SCANNING' ? currentFaceImage ? detectedDocumentType === 'PASSPORT' ? t('identityDocumentCamera.passportDetected') : detectedDocumentType === 'ID_FRONT' ? t('identityDocumentCamera.idCardFrontDetected') : t('identityDocumentCamera.readingDocument') : t('identityDocumentCamera.readingDocument') : t('identityDocumentCamera.alignPhotoSide') : nextStep === 'SCAN_HOLOGRAM' ? t('identityDocumentCamera.alignHologram') : nextStep === 'SCAN_ID_BACK' ? status === 'SCANNING' ? t('identityDocumentCamera.readingDocument') : t('identityDocumentCamera.alignIDBackSide') : nextStep === 'COMPLETED' ? t('identityDocumentCamera.scanCompleted') : ''
|
|
951
|
+
})]
|
|
731
952
|
}), /*#__PURE__*/_jsx(View, {
|
|
732
953
|
style: styles.leftZone
|
|
733
954
|
}), /*#__PURE__*/_jsx(View, {
|
|
@@ -797,29 +1018,40 @@ const IdentityDocumentCamera = ({
|
|
|
797
1018
|
children: `2nd Face: ${currentSecondaryFaceImage ? '✓' : '✗'} (${secondaryFaceDetectionCurrentRetryCount.value}/${SECOND_FACE_DETECTION_RETRY_COUNT})`
|
|
798
1019
|
})]
|
|
799
1020
|
})]
|
|
800
|
-
}), /*#__PURE__*/
|
|
1021
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
801
1022
|
style: [styles.scanArea, {
|
|
802
|
-
borderColor: status === 'SCANNED' || nextStep === 'COMPLETED' ? '
|
|
1023
|
+
borderColor: status === 'SCANNED' || nextStep === 'COMPLETED' ? '#4CAF50' // Green - success
|
|
1024
|
+
: status === 'INCORRECT' ? '#f44336' // Red - error
|
|
1025
|
+
: status === 'SCANNING' ? '#2196F3' // Blue - processing
|
|
1026
|
+
: isBrightnessLow || isFrameBlurry ? '#FFC107' // Yellow - warning
|
|
1027
|
+
: 'white',
|
|
1028
|
+
borderWidth: status === 'SCANNING' ? 3 : 2
|
|
803
1029
|
}],
|
|
804
|
-
children:
|
|
805
|
-
source: require('../../Shared/Animations/
|
|
1030
|
+
children: nextStep === 'COMPLETED' || status === 'SCANNED' ? /*#__PURE__*/_jsx(LottieView, {
|
|
1031
|
+
source: require('../../Shared/Animations/success.json'),
|
|
806
1032
|
style: styles.animation,
|
|
807
|
-
loop:
|
|
1033
|
+
loop: false,
|
|
808
1034
|
autoPlay: true
|
|
809
|
-
})
|
|
1035
|
+
}) : isBrightnessLow ? /*#__PURE__*/_jsx(LottieView, {
|
|
810
1036
|
source: require('../../Shared/Animations/light.json'),
|
|
811
1037
|
style: styles.animation,
|
|
812
1038
|
loop: true,
|
|
813
1039
|
autoPlay: true
|
|
814
|
-
})
|
|
815
|
-
source: require('../../Shared/Animations/
|
|
1040
|
+
}) : nextStep === 'SCAN_HOLOGRAM' && isTorchOn ? /*#__PURE__*/_jsx(LottieView, {
|
|
1041
|
+
source: require('../../Shared/Animations/hologram-scan.json'),
|
|
816
1042
|
style: styles.animation,
|
|
817
|
-
loop:
|
|
1043
|
+
loop: true,
|
|
818
1044
|
autoPlay: true
|
|
819
|
-
})
|
|
1045
|
+
}) : status === 'SCANNING' ? /*#__PURE__*/_jsx(LottieView, {
|
|
1046
|
+
source: require('../../Shared/Animations/scanning.json'),
|
|
1047
|
+
style: styles.animation,
|
|
1048
|
+
loop: true,
|
|
1049
|
+
autoPlay: true
|
|
1050
|
+
}) : null
|
|
820
1051
|
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
821
1052
|
onPress: handleFocus,
|
|
822
|
-
style: styles.focusArea
|
|
1053
|
+
style: styles.focusArea,
|
|
1054
|
+
activeOpacity: 1
|
|
823
1055
|
})]
|
|
824
1056
|
})
|
|
825
1057
|
});
|
|
@@ -877,6 +1109,13 @@ const styles = StyleSheet.create({
|
|
|
877
1109
|
justifyContent: 'flex-end',
|
|
878
1110
|
alignItems: 'center'
|
|
879
1111
|
},
|
|
1112
|
+
stepIndicator: {
|
|
1113
|
+
color: '#aaaaaa',
|
|
1114
|
+
fontSize: 14,
|
|
1115
|
+
textAlign: 'center',
|
|
1116
|
+
fontWeight: '500',
|
|
1117
|
+
marginBottom: 4
|
|
1118
|
+
},
|
|
880
1119
|
topZoneText: {
|
|
881
1120
|
color: 'white',
|
|
882
1121
|
fontSize: 20,
|
|
@@ -884,6 +1123,18 @@ const styles = StyleSheet.create({
|
|
|
884
1123
|
fontWeight: 'bold',
|
|
885
1124
|
padding: 20
|
|
886
1125
|
},
|
|
1126
|
+
topZoneTextScanning: {
|
|
1127
|
+
color: '#2196F3' // Blue when scanning
|
|
1128
|
+
},
|
|
1129
|
+
topZoneTextSuccess: {
|
|
1130
|
+
color: '#4CAF50' // Green for success
|
|
1131
|
+
},
|
|
1132
|
+
topZoneTextWarning: {
|
|
1133
|
+
color: '#FFC107' // Yellow for warnings
|
|
1134
|
+
},
|
|
1135
|
+
topZoneTextError: {
|
|
1136
|
+
color: '#f44336' // Red for errors
|
|
1137
|
+
},
|
|
887
1138
|
leftZone: {
|
|
888
1139
|
position: 'absolute',
|
|
889
1140
|
top: '36%',
|
|
@@ -5,6 +5,7 @@ import AppContext from "../Contexts/AppContext.js";
|
|
|
5
5
|
import { CommonActions, useNavigation, usePreventRemove } from '@react-navigation/native';
|
|
6
6
|
import { View, StyleSheet, Alert } from 'react-native';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
8
9
|
import i18n from "../../Translation/index.js";
|
|
9
10
|
import StyledButton from "./StyledButton.js";
|
|
10
11
|
import { analyticsService } from "../Services/AnalyticsService.js";
|
|
@@ -21,6 +22,7 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
21
22
|
const {
|
|
22
23
|
t
|
|
23
24
|
} = useTranslation();
|
|
25
|
+
const insets = useSafeAreaInsets();
|
|
24
26
|
const routes = {
|
|
25
27
|
VERIFICATION_SESSION_CHECK: 'VerificationSessionCheckScreen',
|
|
26
28
|
DYNAMIC_ROUTES: {
|
|
@@ -42,6 +44,12 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
42
44
|
};
|
|
43
45
|
}, [navigation]);
|
|
44
46
|
const getNextRoute = useCallback((workflowSteps, currentWorkFlowStep) => {
|
|
47
|
+
// If this was a debug navigation, go directly to result screen
|
|
48
|
+
if (appContext.isDebugNavigated) {
|
|
49
|
+
appContext.isDebugNavigated = false;
|
|
50
|
+
appContext.currentWorkflowStep = undefined;
|
|
51
|
+
return routes.RESULT;
|
|
52
|
+
}
|
|
45
53
|
const currentStepIndex = workflowSteps?.findIndex(step => step.id ? step.id === currentWorkFlowStep?.id : step.type === currentWorkFlowStep?.type) ?? -1;
|
|
46
54
|
const nextStep = workflowSteps && currentStepIndex < workflowSteps.length ? workflowSteps[currentStepIndex + 1] : null;
|
|
47
55
|
if (!nextStep) {
|
|
@@ -105,10 +113,10 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
105
113
|
}
|
|
106
114
|
isNavigating = true;
|
|
107
115
|
try {
|
|
116
|
+
// Preserve demo session state when resetting
|
|
117
|
+
const wasDemoSession = appContext.isDemoSession;
|
|
108
118
|
appContext.currentWorkflowStep = undefined;
|
|
109
119
|
appContext.workflowSteps = undefined;
|
|
110
|
-
appContext.isDemoSession = false;
|
|
111
|
-
analyticsService.setDemoSession(false);
|
|
112
120
|
appContext.identificationInfo = {
|
|
113
121
|
sessionId: '',
|
|
114
122
|
identificationId: '',
|
|
@@ -124,6 +132,12 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
124
132
|
secondaryColor: appContext.branding?.secondaryColor || '#CCCCCC',
|
|
125
133
|
tertiaryColor: appContext.branding?.tertiaryColor || '#FF0000'
|
|
126
134
|
};
|
|
135
|
+
|
|
136
|
+
// Only reset demo mode if it wasn't a demo session
|
|
137
|
+
if (!wasDemoSession) {
|
|
138
|
+
appContext.setIsDemoSession?.(false);
|
|
139
|
+
analyticsService.setDemoSession(false);
|
|
140
|
+
}
|
|
127
141
|
navigation.dispatch(CommonActions.reset({
|
|
128
142
|
index: 0,
|
|
129
143
|
routes: [{
|
|
@@ -151,7 +165,9 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
151
165
|
allowSkipStep: appContext.currentWorkflowStep?.required
|
|
152
166
|
}));
|
|
153
167
|
return appContext.currentWorkflowStep && (!appContext.currentWorkflowStep?.required || canSkipStep) && /*#__PURE__*/_jsx(View, {
|
|
154
|
-
style: styles.container,
|
|
168
|
+
style: [styles.container, {
|
|
169
|
+
paddingBottom: insets.bottom
|
|
170
|
+
}],
|
|
155
171
|
children: /*#__PURE__*/_jsx(StyledButton, {
|
|
156
172
|
mode: "text",
|
|
157
173
|
onPress: goToNextRouteWithAlert,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Centralized configuration for camera enhancements
|
|
5
|
+
* Including autofocus, brightness/exposure, and contrast enhancement
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const ENHANCEMENT_CONFIG = {
|
|
9
|
+
autofocus: {
|
|
10
|
+
enabled: true,
|
|
11
|
+
intervalMs: 2500,
|
|
12
|
+
suspendOnDetection: true
|
|
13
|
+
},
|
|
14
|
+
brightness: {
|
|
15
|
+
thresholds: {
|
|
16
|
+
general: {
|
|
17
|
+
low: 40,
|
|
18
|
+
high: 120,
|
|
19
|
+
target: 80
|
|
20
|
+
},
|
|
21
|
+
faceDetection: {
|
|
22
|
+
low: 50,
|
|
23
|
+
high: 110,
|
|
24
|
+
target: 85
|
|
25
|
+
},
|
|
26
|
+
mrzScanning: {
|
|
27
|
+
low: 45,
|
|
28
|
+
high: 130,
|
|
29
|
+
target: 80
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
adaptiveStep: true,
|
|
33
|
+
maxStepSize: 2,
|
|
34
|
+
// Reduced from 3 for smoother transitions
|
|
35
|
+
hysteresis: 5 // Dead zone to prevent oscillation
|
|
36
|
+
},
|
|
37
|
+
contrast: {
|
|
38
|
+
enabled: true,
|
|
39
|
+
clahe: {
|
|
40
|
+
clipLimit: 2.0,
|
|
41
|
+
tileGridSize: [8, 8]
|
|
42
|
+
},
|
|
43
|
+
applyWhen: {
|
|
44
|
+
mrzFailing: true,
|
|
45
|
+
faceFailing: true,
|
|
46
|
+
documentBackSide: true,
|
|
47
|
+
retryThreshold: 2
|
|
48
|
+
},
|
|
49
|
+
performanceMode: 'adaptive'
|
|
50
|
+
},
|
|
51
|
+
performance: {
|
|
52
|
+
maxFrameProcessingTime: 180,
|
|
53
|
+
// ms
|
|
54
|
+
autoDisableThreshold: 200,
|
|
55
|
+
// ms
|
|
56
|
+
cachingEnabled: true
|
|
57
|
+
}
|
|
58
|
+
};
|