@trustchex/react-native-sdk 1.267.0 → 1.354.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
Image,
|
|
14
14
|
ActivityIndicator,
|
|
15
15
|
} from 'react-native';
|
|
16
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
16
17
|
import {
|
|
17
18
|
Camera,
|
|
18
19
|
runAtTargetFps,
|
|
@@ -44,10 +45,11 @@ import {
|
|
|
44
45
|
OpenCV,
|
|
45
46
|
ThresholdTypes,
|
|
46
47
|
} from 'react-native-fast-opencv';
|
|
47
|
-
import { getAverageBrightness } from '../Libs/camera.utils';
|
|
48
|
+
import { getAverageBrightness, getScanAreaCenterPoint, calculateExposureStep, isBlurry as checkBlurry } from '../Libs/camera.utils';
|
|
48
49
|
import { useTranslation } from 'react-i18next';
|
|
49
50
|
import LottieView from 'lottie-react-native';
|
|
50
51
|
import StyledButton from './StyledButton';
|
|
52
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
51
53
|
import { type Barcode, scanCodes } from '../VisionCameraPlugins/BarcodeScanner';
|
|
52
54
|
import { speakWithDebounce } from '../Libs/tts.utils';
|
|
53
55
|
import AppContext from '../Contexts/AppContext';
|
|
@@ -105,10 +107,10 @@ type ElementsData = [
|
|
|
105
107
|
export type PhotoOptions = {
|
|
106
108
|
uri: string;
|
|
107
109
|
orientation?:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
| 'landscapeRight'
|
|
111
|
+
| 'portrait'
|
|
112
|
+
| 'portraitUpsideDown'
|
|
113
|
+
| 'landscapeLeft';
|
|
112
114
|
};
|
|
113
115
|
|
|
114
116
|
export interface IdentityDocumentCameraProps {
|
|
@@ -135,6 +137,7 @@ const IdentityDocumentCamera = ({
|
|
|
135
137
|
}: IdentityDocumentCameraProps) => {
|
|
136
138
|
useKeepAwake();
|
|
137
139
|
const theme = useTheme();
|
|
140
|
+
const insets = useSafeAreaInsets();
|
|
138
141
|
const appContext = React.useContext(AppContext);
|
|
139
142
|
const cameraRef = React.useRef<Camera>(null);
|
|
140
143
|
const cameraPermission = useCameraPermission();
|
|
@@ -174,6 +177,7 @@ const IdentityDocumentCamera = ({
|
|
|
174
177
|
string | undefined
|
|
175
178
|
>(undefined);
|
|
176
179
|
const [isBrightnessLow, setIsBrightnessLow] = useState(false);
|
|
180
|
+
const [isFrameBlurry, setIsFrameBlurry] = useState(false);
|
|
177
181
|
const [hasGuideShown, setHasGuideShown] = useState(false);
|
|
178
182
|
const [status, setStatus] = useState<
|
|
179
183
|
'SEARCHING' | 'SCANNING' | 'SCANNED' | 'INCORRECT'
|
|
@@ -181,10 +185,17 @@ const IdentityDocumentCamera = ({
|
|
|
181
185
|
const [nextStep, setNextStep] = useState<
|
|
182
186
|
'SCAN_ID_FRONT_OR_PASSPORT' | 'SCAN_ID_BACK' | 'SCAN_HOLOGRAM' | 'COMPLETED'
|
|
183
187
|
>('SCAN_ID_FRONT_OR_PASSPORT');
|
|
188
|
+
const [completedStep, setCompletedStep] = useState<
|
|
189
|
+
'SCAN_ID_FRONT_OR_PASSPORT' | 'SCAN_ID_BACK' | 'SCAN_HOLOGRAM' | null
|
|
190
|
+
>(null);
|
|
191
|
+
const [detectedDocumentType, setDetectedDocumentType] = useState<
|
|
192
|
+
'ID_FRONT' | 'ID_BACK' | 'PASSPORT' | 'UNKNOWN'
|
|
193
|
+
>('UNKNOWN');
|
|
184
194
|
const hologramDetectionCurrentRetryCount = useSharedValue(0);
|
|
185
195
|
const secondaryFaceDetectionCurrentRetryCount = useSharedValue(0);
|
|
186
196
|
const mrzDetectionCurrentRetryCount = useSharedValue(0);
|
|
187
197
|
const faceDetectionErrorCount = useSharedValue(0);
|
|
198
|
+
const consecutiveBlurCount = useSharedValue(0);
|
|
188
199
|
const [faceDetectionEnabled, setFaceDetectionEnabled] = useState(true);
|
|
189
200
|
const { t } = useTranslation();
|
|
190
201
|
// const [boundingBox, setBoundingBox] = useState<Bounds>({
|
|
@@ -262,17 +273,64 @@ const IdentityDocumentCamera = ({
|
|
|
262
273
|
|
|
263
274
|
useEffect(() => {
|
|
264
275
|
if (hasGuideShown) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
+
let message = '';
|
|
277
|
+
|
|
278
|
+
// Priority: scanned > incorrect > blur during scanning > brightness > blur > step-specific
|
|
279
|
+
if (status === 'SCANNED') {
|
|
280
|
+
// Use step-specific completion messages
|
|
281
|
+
if (completedStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
|
|
282
|
+
message = detectedDocumentType === 'PASSPORT'
|
|
283
|
+
? t('identityDocumentCamera.passportScanned')
|
|
284
|
+
: t('identityDocumentCamera.frontSideScanned');
|
|
285
|
+
} else if (completedStep === 'SCAN_ID_BACK') {
|
|
286
|
+
message = t('identityDocumentCamera.backSideScanned');
|
|
287
|
+
} else if (completedStep === 'SCAN_HOLOGRAM') {
|
|
288
|
+
message = t('identityDocumentCamera.hologramVerified');
|
|
289
|
+
} else {
|
|
290
|
+
message = t('identityDocumentCamera.scanCompleted');
|
|
291
|
+
}
|
|
292
|
+
} else if (status === 'INCORRECT') {
|
|
293
|
+
// Wrong side detected - warn user
|
|
294
|
+
if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
|
|
295
|
+
message = t('identityDocumentCamera.wrongSideFront');
|
|
296
|
+
} else if (nextStep === 'SCAN_ID_BACK') {
|
|
297
|
+
message = t('identityDocumentCamera.wrongSideBack');
|
|
298
|
+
}
|
|
299
|
+
} else if (isBrightnessLow) {
|
|
300
|
+
// Brightness warning takes priority over blur
|
|
301
|
+
message = t('identityDocumentCamera.lowBrightness');
|
|
302
|
+
} else if (isFrameBlurry) {
|
|
303
|
+
// Show blur warning only when brightness is sufficient
|
|
304
|
+
message = t('identityDocumentCamera.avoidBlur');
|
|
305
|
+
} else if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
|
|
306
|
+
// Enhanced feedback based on detection status
|
|
307
|
+
if (status === 'SCANNING') {
|
|
308
|
+
if (currentFaceImage) {
|
|
309
|
+
// Document-specific detection message
|
|
310
|
+
if (detectedDocumentType === 'PASSPORT') {
|
|
311
|
+
message = t('identityDocumentCamera.passportDetected');
|
|
312
|
+
} else if (detectedDocumentType === 'ID_FRONT') {
|
|
313
|
+
message = t('identityDocumentCamera.idCardFrontDetected');
|
|
314
|
+
} else {
|
|
315
|
+
message = t('identityDocumentCamera.readingDocument');
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
message = t('identityDocumentCamera.readingDocument');
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
message = t('identityDocumentCamera.alignPhotoSide');
|
|
322
|
+
}
|
|
323
|
+
} else if (nextStep === 'SCAN_HOLOGRAM') {
|
|
324
|
+
message = t('identityDocumentCamera.alignHologram');
|
|
325
|
+
} else if (nextStep === 'SCAN_ID_BACK') {
|
|
326
|
+
if (status === 'SCANNING') {
|
|
327
|
+
message = t('identityDocumentCamera.readingDocument');
|
|
328
|
+
} else {
|
|
329
|
+
message = t('identityDocumentCamera.alignIDBackSide');
|
|
330
|
+
}
|
|
331
|
+
} else if (nextStep === 'COMPLETED') {
|
|
332
|
+
message = t('identityDocumentCamera.scanCompleted');
|
|
333
|
+
}
|
|
276
334
|
|
|
277
335
|
if (
|
|
278
336
|
appContext.currentWorkflowStep?.data?.voiceGuidanceActive &&
|
|
@@ -285,10 +343,58 @@ const IdentityDocumentCamera = ({
|
|
|
285
343
|
appContext.currentWorkflowStep?.data?.voiceGuidanceActive,
|
|
286
344
|
hasGuideShown,
|
|
287
345
|
isBrightnessLow,
|
|
346
|
+
isFrameBlurry,
|
|
288
347
|
nextStep,
|
|
348
|
+
status,
|
|
349
|
+
completedStep,
|
|
350
|
+
currentFaceImage,
|
|
351
|
+
detectedDocumentType,
|
|
289
352
|
t,
|
|
290
353
|
]);
|
|
291
354
|
|
|
355
|
+
// Auto-reset INCORRECT status after showing warning briefly
|
|
356
|
+
useEffect(() => {
|
|
357
|
+
if (status === 'INCORRECT') {
|
|
358
|
+
const timeout = setTimeout(() => {
|
|
359
|
+
setStatus('SEARCHING');
|
|
360
|
+
}, 1500); // Show warning for 1.5 seconds
|
|
361
|
+
return () => clearTimeout(timeout);
|
|
362
|
+
}
|
|
363
|
+
}, [status]);
|
|
364
|
+
|
|
365
|
+
// Periodic autofocus - refocus on scan area center every 2.5 seconds
|
|
366
|
+
useEffect(() => {
|
|
367
|
+
if (!isActive || !device || !cameraRef.current || !device.supportsFocus) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Only autofocus during searching and scanning states
|
|
372
|
+
if (status !== 'SEARCHING' && status !== 'SCANNING') {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const autofocusInterval = setInterval(async () => {
|
|
377
|
+
try {
|
|
378
|
+
// Get camera dimensions (assuming format dimensions)
|
|
379
|
+
const width = format?.videoWidth ?? 1920;
|
|
380
|
+
const height = format?.videoHeight ?? 1080;
|
|
381
|
+
|
|
382
|
+
// Calculate center point of scan area
|
|
383
|
+
const centerPoint = getScanAreaCenterPoint(width, height);
|
|
384
|
+
|
|
385
|
+
// Focus on the center of the scan area
|
|
386
|
+
await cameraRef.current?.focus({
|
|
387
|
+
x: centerPoint.x,
|
|
388
|
+
y: centerPoint.y,
|
|
389
|
+
});
|
|
390
|
+
} catch (error) {
|
|
391
|
+
// Ignore autofocus errors
|
|
392
|
+
}
|
|
393
|
+
}, 2500); // Every 2.5 seconds
|
|
394
|
+
|
|
395
|
+
return () => clearInterval(autofocusInterval);
|
|
396
|
+
}, [isActive, device, format, status]);
|
|
397
|
+
|
|
292
398
|
const detectDocumentType = (
|
|
293
399
|
faces: Face[],
|
|
294
400
|
ocrText: string,
|
|
@@ -411,6 +517,7 @@ const IdentityDocumentCamera = ({
|
|
|
411
517
|
}
|
|
412
518
|
};
|
|
413
519
|
|
|
520
|
+
|
|
414
521
|
const detectHologram = (images: string[]) => {
|
|
415
522
|
try {
|
|
416
523
|
const lowerBound = OpenCV.createObject(ObjectType.Scalar, 40, 90, 90);
|
|
@@ -587,10 +694,26 @@ const IdentityDocumentCamera = ({
|
|
|
587
694
|
| 'SCAN_ID_FRONT_OR_PASSPORT'
|
|
588
695
|
| 'SCAN_ID_BACK'
|
|
589
696
|
| 'SCAN_HOLOGRAM'
|
|
590
|
-
| 'COMPLETED'
|
|
697
|
+
| 'COMPLETED',
|
|
698
|
+
fromStep?:
|
|
699
|
+
| 'SCAN_ID_FRONT_OR_PASSPORT'
|
|
700
|
+
| 'SCAN_ID_BACK'
|
|
701
|
+
| 'SCAN_HOLOGRAM'
|
|
591
702
|
) => {
|
|
703
|
+
// Track which step was just completed for showing specific message
|
|
704
|
+
if (fromStep) {
|
|
705
|
+
setCompletedStep(fromStep);
|
|
706
|
+
}
|
|
592
707
|
setNextStep(nextStepType);
|
|
593
708
|
Vibration.vibrate(100);
|
|
709
|
+
|
|
710
|
+
// Reset status after delay to show success animation fully before next step
|
|
711
|
+
if (nextStepType !== 'COMPLETED') {
|
|
712
|
+
setTimeout(() => {
|
|
713
|
+
setStatus('SEARCHING');
|
|
714
|
+
setCompletedStep(null);
|
|
715
|
+
}, 2000); // Show success checkmark for 2 seconds before transitioning
|
|
716
|
+
}
|
|
594
717
|
};
|
|
595
718
|
|
|
596
719
|
const handleBrightness = useRunOnJS(
|
|
@@ -600,6 +723,13 @@ const IdentityDocumentCamera = ({
|
|
|
600
723
|
[setIsBrightnessLow]
|
|
601
724
|
);
|
|
602
725
|
|
|
726
|
+
const handleBlurStatus = useRunOnJS(
|
|
727
|
+
(blurry: boolean) => {
|
|
728
|
+
setIsFrameBlurry(blurry);
|
|
729
|
+
},
|
|
730
|
+
[setIsFrameBlurry]
|
|
731
|
+
);
|
|
732
|
+
|
|
603
733
|
const handleFaceAndText = useRunOnJS(
|
|
604
734
|
async (
|
|
605
735
|
text: string,
|
|
@@ -614,7 +744,7 @@ const IdentityDocumentCamera = ({
|
|
|
614
744
|
isTorchOn &&
|
|
615
745
|
(currentHologramImage ||
|
|
616
746
|
hologramDetectionCurrentRetryCount.value >=
|
|
617
|
-
|
|
747
|
+
HOLOGRAM_DETECTION_RETRY_COUNT)
|
|
618
748
|
) {
|
|
619
749
|
setIsTorchOn(false);
|
|
620
750
|
}
|
|
@@ -624,7 +754,18 @@ const IdentityDocumentCamera = ({
|
|
|
624
754
|
return;
|
|
625
755
|
}
|
|
626
756
|
|
|
757
|
+
// Early wrong side detection for SCAN_ID_BACK: if faces detected, it's the front side
|
|
758
|
+
if (nextStep === 'SCAN_ID_BACK' && faces.length > 0) {
|
|
759
|
+
console.log('[WRONG_SIDE] Back side expected but faces detected:', faces.length);
|
|
760
|
+
setStatus('INCORRECT');
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
|
|
627
764
|
if (!text || text.length < 10 || !image) {
|
|
765
|
+
// Log when searching to help debug
|
|
766
|
+
if (nextStep === 'SCAN_ID_BACK') {
|
|
767
|
+
console.log('[SCAN_ID_BACK] Searching... faces:', faces.length, 'text length:', text?.length || 0);
|
|
768
|
+
}
|
|
628
769
|
setStatus('SEARCHING');
|
|
629
770
|
return;
|
|
630
771
|
}
|
|
@@ -652,16 +793,24 @@ const IdentityDocumentCamera = ({
|
|
|
652
793
|
scannedData.faceImage = croppedFaces[0];
|
|
653
794
|
setCurrentFaceImage(croppedFaces[0]);
|
|
654
795
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
796
|
+
// Track detected document type for UI feedback
|
|
797
|
+
if (documentType !== 'UNKNOWN') {
|
|
798
|
+
setDetectedDocumentType(documentType);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Detect wrong side based on document type or face presence (works for both normal and eID scan)
|
|
802
|
+
// For ID_BACK step: if faces are detected, it's likely the front side (wrong)
|
|
803
|
+
// For FRONT step: if ID_BACK is detected, it's the wrong side
|
|
804
|
+
const isWrongSide =
|
|
805
|
+
(nextStep === 'SCAN_ID_FRONT_OR_PASSPORT' && documentType === 'ID_BACK') ||
|
|
806
|
+
(nextStep === 'SCAN_ID_BACK' && (documentType === 'ID_FRONT' || documentType === 'PASSPORT' || croppedFaces.length > 0));
|
|
807
|
+
|
|
808
|
+
if (isWrongSide) {
|
|
809
|
+
setStatus('INCORRECT');
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
664
812
|
|
|
813
|
+
if (!onlyMRZScan) {
|
|
665
814
|
if (croppedFaces.length > 0 && croppedFaces[0]) {
|
|
666
815
|
if (currentFaceImage) {
|
|
667
816
|
scannedData.faceImage = currentFaceImage;
|
|
@@ -711,22 +860,22 @@ const IdentityDocumentCamera = ({
|
|
|
711
860
|
if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
|
|
712
861
|
setStatus('SCANNED');
|
|
713
862
|
if (onlyMRZScan) {
|
|
714
|
-
setNextStepAndVibrate('SCAN_ID_BACK');
|
|
863
|
+
setNextStepAndVibrate('SCAN_ID_BACK', 'SCAN_ID_FRONT_OR_PASSPORT');
|
|
715
864
|
onIdentityDocumentScanned(scannedData);
|
|
716
865
|
} else {
|
|
717
|
-
setNextStepAndVibrate('SCAN_HOLOGRAM');
|
|
866
|
+
setNextStepAndVibrate('SCAN_HOLOGRAM', 'SCAN_ID_FRONT_OR_PASSPORT');
|
|
718
867
|
}
|
|
719
868
|
} else if (
|
|
720
869
|
nextStep === 'SCAN_HOLOGRAM' &&
|
|
721
870
|
(!!scannedData.hologramImage ||
|
|
722
871
|
hologramDetectionCurrentRetryCount.value >=
|
|
723
|
-
|
|
872
|
+
HOLOGRAM_DETECTION_RETRY_COUNT) &&
|
|
724
873
|
(!!scannedData.secondaryFaceImage ||
|
|
725
874
|
secondaryFaceDetectionCurrentRetryCount.value >=
|
|
726
|
-
|
|
875
|
+
SECOND_FACE_DETECTION_RETRY_COUNT)
|
|
727
876
|
) {
|
|
728
877
|
setStatus('SCANNED');
|
|
729
|
-
setNextStepAndVibrate('SCAN_ID_BACK');
|
|
878
|
+
setNextStepAndVibrate('SCAN_ID_BACK', 'SCAN_HOLOGRAM');
|
|
730
879
|
onIdentityDocumentScanned(scannedData);
|
|
731
880
|
}
|
|
732
881
|
} else if (documentType === 'PASSPORT') {
|
|
@@ -734,28 +883,41 @@ const IdentityDocumentCamera = ({
|
|
|
734
883
|
nextStep === 'SCAN_ID_FRONT_OR_PASSPORT' &&
|
|
735
884
|
!scannedData.hologramImage
|
|
736
885
|
) {
|
|
737
|
-
|
|
886
|
+
// For passport, require valid MRZ before proceeding
|
|
738
887
|
if (onlyMRZScan) {
|
|
739
|
-
|
|
740
|
-
|
|
888
|
+
// eID scan: require valid MRZ
|
|
889
|
+
if (
|
|
890
|
+
!!scannedData.mrzText &&
|
|
891
|
+
(parsedMRZData?.valid ||
|
|
892
|
+
mrzDetectionCurrentRetryCount.value >= MRZ_VALIDATION_RETRY_COUNT)
|
|
893
|
+
) {
|
|
894
|
+
setStatus('SCANNED');
|
|
895
|
+
setNextStepAndVibrate('COMPLETED', 'SCAN_ID_FRONT_OR_PASSPORT');
|
|
896
|
+
onIdentityDocumentScanned(scannedData);
|
|
897
|
+
} else if (!parsedMRZData?.valid) {
|
|
898
|
+
mrzDetectionCurrentRetryCount.value++;
|
|
899
|
+
setStatus('SCANNING');
|
|
900
|
+
}
|
|
741
901
|
} else {
|
|
742
|
-
|
|
902
|
+
// Normal scan: proceed to hologram check (MRZ validated later)
|
|
903
|
+
setStatus('SCANNED');
|
|
904
|
+
setNextStepAndVibrate('SCAN_HOLOGRAM', 'SCAN_ID_FRONT_OR_PASSPORT');
|
|
743
905
|
}
|
|
744
906
|
} else if (
|
|
745
907
|
((nextStep === 'SCAN_HOLOGRAM' &&
|
|
746
908
|
(!!scannedData.hologramImage ||
|
|
747
909
|
hologramDetectionCurrentRetryCount.value >=
|
|
748
|
-
|
|
910
|
+
HOLOGRAM_DETECTION_RETRY_COUNT) &&
|
|
749
911
|
(!!scannedData.secondaryFaceImage ||
|
|
750
912
|
secondaryFaceDetectionCurrentRetryCount.value >=
|
|
751
|
-
|
|
913
|
+
SECOND_FACE_DETECTION_RETRY_COUNT)) ||
|
|
752
914
|
onlyMRZScan) &&
|
|
753
915
|
!!scannedData.mrzText &&
|
|
754
916
|
(parsedMRZData?.valid ||
|
|
755
917
|
mrzDetectionCurrentRetryCount.value >= MRZ_VALIDATION_RETRY_COUNT)
|
|
756
918
|
) {
|
|
757
919
|
setStatus('SCANNED');
|
|
758
|
-
setNextStepAndVibrate('COMPLETED');
|
|
920
|
+
setNextStepAndVibrate('COMPLETED', 'SCAN_HOLOGRAM');
|
|
759
921
|
onIdentityDocumentScanned(scannedData);
|
|
760
922
|
} else if (!parsedMRZData?.valid) {
|
|
761
923
|
mrzDetectionCurrentRetryCount.value++;
|
|
@@ -764,7 +926,7 @@ const IdentityDocumentCamera = ({
|
|
|
764
926
|
if (
|
|
765
927
|
((parsedMRZData?.fields?.issuingState === 'TUR' &&
|
|
766
928
|
barcode?.value?.trim() ===
|
|
767
|
-
|
|
929
|
+
parsedMRZData?.fields?.optional1?.trim()) ||
|
|
768
930
|
parsedMRZData?.fields?.issuingState !== 'TUR' ||
|
|
769
931
|
onlyMRZScan) &&
|
|
770
932
|
nextStep === 'SCAN_ID_BACK' &&
|
|
@@ -774,7 +936,7 @@ const IdentityDocumentCamera = ({
|
|
|
774
936
|
) {
|
|
775
937
|
scannedData.barcodeValue = barcode?.value ?? undefined;
|
|
776
938
|
setStatus('SCANNED');
|
|
777
|
-
setNextStepAndVibrate('COMPLETED');
|
|
939
|
+
setNextStepAndVibrate('COMPLETED', 'SCAN_ID_BACK');
|
|
778
940
|
onIdentityDocumentScanned(scannedData);
|
|
779
941
|
} else if (!parsedMRZData?.valid) {
|
|
780
942
|
mrzDetectionCurrentRetryCount.value++;
|
|
@@ -808,25 +970,63 @@ const IdentityDocumentCamera = ({
|
|
|
808
970
|
[exposure]
|
|
809
971
|
);
|
|
810
972
|
|
|
973
|
+
// Focus trigger for when blur is detected (called from worklet)
|
|
974
|
+
const triggerFocus = useRunOnJS(
|
|
975
|
+
async () => {
|
|
976
|
+
if (!cameraRef.current || !device?.supportsFocus) {
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
try {
|
|
980
|
+
const width = format?.videoWidth ?? 1920;
|
|
981
|
+
const height = format?.videoHeight ?? 1080;
|
|
982
|
+
const centerPoint = getScanAreaCenterPoint(width, height);
|
|
983
|
+
await cameraRef.current.focus({
|
|
984
|
+
x: centerPoint.x,
|
|
985
|
+
y: centerPoint.y,
|
|
986
|
+
});
|
|
987
|
+
} catch (error) {
|
|
988
|
+
// Ignore focus errors
|
|
989
|
+
}
|
|
990
|
+
},
|
|
991
|
+
[device, format]
|
|
992
|
+
);
|
|
993
|
+
|
|
811
994
|
const handleExposureAndBrightness = (frame: Frame) => {
|
|
812
995
|
'worklet';
|
|
813
996
|
const averageBrightness = getAverageBrightness(frame);
|
|
814
997
|
const minExposure = device?.minExposure ?? 0;
|
|
815
998
|
const maxExposure = device?.maxExposure ?? 0;
|
|
816
|
-
|
|
817
|
-
|
|
999
|
+
|
|
1000
|
+
// Dynamic thresholds based on scanning state using config values
|
|
1001
|
+
// Face detection requires higher minimum brightness for reliable detection
|
|
1002
|
+
const isFrontOrPassport = nextStep === 'SCAN_ID_FRONT_OR_PASSPORT';
|
|
1003
|
+
const isBack = nextStep === 'SCAN_ID_BACK';
|
|
1004
|
+
|
|
1005
|
+
// Using config values: faceDetection { low: 50, high: 110, target: 85 }, mrzScanning { low: 45, high: 130, target: 80 }
|
|
1006
|
+
const lowerBrightnessBound = isFrontOrPassport ? 50 : 40;
|
|
1007
|
+
const upperBrightnessBound = isBack ? 130 : 120;
|
|
1008
|
+
const targetBrightness = isFrontOrPassport ? 85 : 80;
|
|
1009
|
+
|
|
1010
|
+
// Smooth exposure adjustment with hysteresis to prevent oscillation
|
|
1011
|
+
// Only adjust if brightness is significantly outside the acceptable range
|
|
1012
|
+
const hysteresis = 5; // Dead zone to prevent jitter
|
|
818
1013
|
|
|
819
1014
|
if (
|
|
820
|
-
averageBrightness < lowerBrightnessBound &&
|
|
1015
|
+
averageBrightness < (lowerBrightnessBound - hysteresis) &&
|
|
821
1016
|
exposureValue.value < maxExposure
|
|
822
1017
|
) {
|
|
823
|
-
|
|
1018
|
+
// Increase exposure smoothly when too dark
|
|
1019
|
+
const step = calculateExposureStep(averageBrightness, targetBrightness);
|
|
1020
|
+
exposureValue.value = Math.min(maxExposure, exposureValue.value + step);
|
|
824
1021
|
} else if (
|
|
825
|
-
averageBrightness > upperBrightnessBound &&
|
|
1022
|
+
averageBrightness > (upperBrightnessBound + hysteresis) &&
|
|
826
1023
|
exposureValue.value > minExposure
|
|
827
1024
|
) {
|
|
828
|
-
|
|
1025
|
+
// Decrease exposure smoothly when too bright
|
|
1026
|
+
const step = calculateExposureStep(averageBrightness, targetBrightness);
|
|
1027
|
+
exposureValue.value = Math.max(minExposure, exposureValue.value - step);
|
|
829
1028
|
}
|
|
1029
|
+
// When within acceptable range (with hysteresis), don't adjust - prevents oscillation
|
|
830
1030
|
|
|
831
1031
|
const isBright = averageBrightness > lowerBrightnessBound;
|
|
832
1032
|
handleExposureValue(exposureValue.value);
|
|
@@ -843,6 +1043,28 @@ const IdentityDocumentCamera = ({
|
|
|
843
1043
|
return;
|
|
844
1044
|
}
|
|
845
1045
|
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
// Check for blur before processing - skip blurry frames
|
|
1049
|
+
// Use different thresholds: 25 for front (face detection), 30 for back (MRZ/text)
|
|
1050
|
+
// Higher thresholds with improved Laplacian algorithm using H+V gradients
|
|
1051
|
+
const isFront = nextStep === 'SCAN_ID_FRONT_OR_PASSPORT';
|
|
1052
|
+
const blurThreshold = isFront ? 25 : 30;
|
|
1053
|
+
const blurry = checkBlurry(frame, blurThreshold);
|
|
1054
|
+
handleBlurStatus(blurry);
|
|
1055
|
+
if (blurry) {
|
|
1056
|
+
consecutiveBlurCount.value++;
|
|
1057
|
+
// Only trigger focus after 2 consecutive blurry frames (matching Flutter)
|
|
1058
|
+
if (consecutiveBlurCount.value >= 2) {
|
|
1059
|
+
triggerFocus();
|
|
1060
|
+
consecutiveBlurCount.value = 0;
|
|
1061
|
+
}
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
// Reset blur count on sharp frame
|
|
1065
|
+
consecutiveBlurCount.value = 0;
|
|
1066
|
+
|
|
1067
|
+
|
|
846
1068
|
// Validate frame dimensions before processing
|
|
847
1069
|
if (
|
|
848
1070
|
!frame.width ||
|
|
@@ -901,6 +1123,8 @@ const IdentityDocumentCamera = ({
|
|
|
901
1123
|
}
|
|
902
1124
|
|
|
903
1125
|
// Text recognition with error handling
|
|
1126
|
+
// Note: CLAHE enhancement is applied to captured images, not live frames
|
|
1127
|
+
// ML Kit plugins work directly on Frame objects and don't support Mat input
|
|
904
1128
|
let scannedText: BlockText;
|
|
905
1129
|
try {
|
|
906
1130
|
scannedText = scanText(frame) as any as BlockText;
|
|
@@ -972,15 +1196,15 @@ const IdentityDocumentCamera = ({
|
|
|
972
1196
|
|
|
973
1197
|
if (!permissionsRequested) {
|
|
974
1198
|
return (
|
|
975
|
-
<
|
|
1199
|
+
<SafeAreaView style={styles.permissionContainer}>
|
|
976
1200
|
<ActivityIndicator size="large" color={theme.colors.primary} />
|
|
977
|
-
</
|
|
1201
|
+
</SafeAreaView>
|
|
978
1202
|
);
|
|
979
1203
|
}
|
|
980
1204
|
|
|
981
1205
|
if (!cameraPermission.hasPermission) {
|
|
982
1206
|
return (
|
|
983
|
-
<
|
|
1207
|
+
<SafeAreaView style={styles.permissionContainer}>
|
|
984
1208
|
<Text style={styles.permissionText}>
|
|
985
1209
|
{t('general.noCameraPermissionGiven')}
|
|
986
1210
|
</Text>
|
|
@@ -992,17 +1216,17 @@ const IdentityDocumentCamera = ({
|
|
|
992
1216
|
>
|
|
993
1217
|
{t('general.openSettings')}
|
|
994
1218
|
</StyledButton>
|
|
995
|
-
</
|
|
1219
|
+
</SafeAreaView>
|
|
996
1220
|
);
|
|
997
1221
|
}
|
|
998
1222
|
|
|
999
1223
|
if (device == null) {
|
|
1000
1224
|
return (
|
|
1001
|
-
<
|
|
1225
|
+
<SafeAreaView style={styles.permissionContainer}>
|
|
1002
1226
|
<TextView style={styles.permissionText}>
|
|
1003
1227
|
{t('general.noCameraDetected')}
|
|
1004
1228
|
</TextView>
|
|
1005
|
-
</
|
|
1229
|
+
</SafeAreaView>
|
|
1006
1230
|
);
|
|
1007
1231
|
}
|
|
1008
1232
|
|
|
@@ -1023,7 +1247,7 @@ const IdentityDocumentCamera = ({
|
|
|
1023
1247
|
return (
|
|
1024
1248
|
<View style={StyleSheet.absoluteFill}>
|
|
1025
1249
|
{!hasGuideShown ? (
|
|
1026
|
-
<
|
|
1250
|
+
<SafeAreaView style={styles.guide}>
|
|
1027
1251
|
<LottieView
|
|
1028
1252
|
source={require('../../Shared/Animations/id-or-passport.json')}
|
|
1029
1253
|
style={styles.guideAnimation}
|
|
@@ -1055,7 +1279,7 @@ const IdentityDocumentCamera = ({
|
|
|
1055
1279
|
>
|
|
1056
1280
|
{t('general.letsGo')}
|
|
1057
1281
|
</StyledButton>
|
|
1058
|
-
</
|
|
1282
|
+
</SafeAreaView>
|
|
1059
1283
|
) : (
|
|
1060
1284
|
<>
|
|
1061
1285
|
<Camera
|
|
@@ -1075,19 +1299,74 @@ const IdentityDocumentCamera = ({
|
|
|
1075
1299
|
isCameraInitialized.value = true;
|
|
1076
1300
|
}}
|
|
1077
1301
|
/>
|
|
1078
|
-
<View style={styles.topZone}>
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
? t('identityDocumentCamera.
|
|
1302
|
+
<View style={[styles.topZone, { paddingTop: insets.top }]}>
|
|
1303
|
+
{/* Step Progress Indicator - show only after document type detected and not completed/scanned */}
|
|
1304
|
+
{nextStep !== 'COMPLETED' && status !== 'SCANNED' && detectedDocumentType !== 'UNKNOWN' && (
|
|
1305
|
+
<TextView style={styles.stepIndicator}>
|
|
1306
|
+
{nextStep === 'SCAN_ID_FRONT_OR_PASSPORT'
|
|
1307
|
+
? `${t('identityDocumentCamera.frontSide')} • ${t('identityDocumentCamera.stepProgress', {
|
|
1308
|
+
current: 1,
|
|
1309
|
+
total: onlyMRZScan
|
|
1310
|
+
? (detectedDocumentType === 'PASSPORT' ? 1 : 2)
|
|
1311
|
+
: (detectedDocumentType === 'PASSPORT' ? 2 : 3)
|
|
1312
|
+
})}`
|
|
1084
1313
|
: nextStep === 'SCAN_HOLOGRAM'
|
|
1085
|
-
? t('identityDocumentCamera.
|
|
1314
|
+
? `${t('identityDocumentCamera.hologramCheck')} • ${t('identityDocumentCamera.stepProgress', {
|
|
1315
|
+
current: 2,
|
|
1316
|
+
total: detectedDocumentType === 'PASSPORT' ? 2 : 3
|
|
1317
|
+
})}`
|
|
1086
1318
|
: nextStep === 'SCAN_ID_BACK'
|
|
1087
|
-
? t('identityDocumentCamera.
|
|
1088
|
-
:
|
|
1089
|
-
|
|
1090
|
-
|
|
1319
|
+
? `${t('identityDocumentCamera.backSide')} • ${t('identityDocumentCamera.stepProgress', { current: 3, total: 3 })}`
|
|
1320
|
+
: ''}
|
|
1321
|
+
</TextView>
|
|
1322
|
+
)}
|
|
1323
|
+
{/* Status-based guidance text */}
|
|
1324
|
+
<TextView style={[
|
|
1325
|
+
styles.topZoneText,
|
|
1326
|
+
status === 'SCANNING' && styles.topZoneTextScanning,
|
|
1327
|
+
status === 'SCANNED' && styles.topZoneTextSuccess,
|
|
1328
|
+
status === 'INCORRECT' && styles.topZoneTextError,
|
|
1329
|
+
(isBrightnessLow || isFrameBlurry) && styles.topZoneTextWarning,
|
|
1330
|
+
]}>
|
|
1331
|
+
{status === 'SCANNED'
|
|
1332
|
+
? completedStep === 'SCAN_ID_FRONT_OR_PASSPORT'
|
|
1333
|
+
? detectedDocumentType === 'PASSPORT'
|
|
1334
|
+
? t('identityDocumentCamera.passportScanned')
|
|
1335
|
+
: t('identityDocumentCamera.frontSideScanned')
|
|
1336
|
+
: completedStep === 'SCAN_ID_BACK'
|
|
1337
|
+
? t('identityDocumentCamera.backSideScanned')
|
|
1338
|
+
: completedStep === 'SCAN_HOLOGRAM'
|
|
1339
|
+
? t('identityDocumentCamera.hologramVerified')
|
|
1340
|
+
: t('identityDocumentCamera.scanCompleted')
|
|
1341
|
+
: status === 'INCORRECT'
|
|
1342
|
+
? nextStep === 'SCAN_ID_FRONT_OR_PASSPORT'
|
|
1343
|
+
? t('identityDocumentCamera.wrongSideFront')
|
|
1344
|
+
: nextStep === 'SCAN_ID_BACK'
|
|
1345
|
+
? t('identityDocumentCamera.wrongSideBack')
|
|
1346
|
+
: t('identityDocumentCamera.alignPhotoSide')
|
|
1347
|
+
: isBrightnessLow
|
|
1348
|
+
? t('identityDocumentCamera.lowBrightness')
|
|
1349
|
+
: isFrameBlurry
|
|
1350
|
+
? t('identityDocumentCamera.avoidBlur')
|
|
1351
|
+
: nextStep === 'SCAN_ID_FRONT_OR_PASSPORT'
|
|
1352
|
+
? status === 'SCANNING'
|
|
1353
|
+
? currentFaceImage
|
|
1354
|
+
? detectedDocumentType === 'PASSPORT'
|
|
1355
|
+
? t('identityDocumentCamera.passportDetected')
|
|
1356
|
+
: detectedDocumentType === 'ID_FRONT'
|
|
1357
|
+
? t('identityDocumentCamera.idCardFrontDetected')
|
|
1358
|
+
: t('identityDocumentCamera.readingDocument')
|
|
1359
|
+
: t('identityDocumentCamera.readingDocument')
|
|
1360
|
+
: t('identityDocumentCamera.alignPhotoSide')
|
|
1361
|
+
: nextStep === 'SCAN_HOLOGRAM'
|
|
1362
|
+
? t('identityDocumentCamera.alignHologram')
|
|
1363
|
+
: nextStep === 'SCAN_ID_BACK'
|
|
1364
|
+
? status === 'SCANNING'
|
|
1365
|
+
? t('identityDocumentCamera.readingDocument')
|
|
1366
|
+
: t('identityDocumentCamera.alignIDBackSide')
|
|
1367
|
+
: nextStep === 'COMPLETED'
|
|
1368
|
+
? t('identityDocumentCamera.scanCompleted')
|
|
1369
|
+
: ''}
|
|
1091
1370
|
</TextView>
|
|
1092
1371
|
</View>
|
|
1093
1372
|
<View style={styles.leftZone} />
|
|
@@ -1154,19 +1433,16 @@ const IdentityDocumentCamera = ({
|
|
|
1154
1433
|
Status: {status}
|
|
1155
1434
|
</TextView>
|
|
1156
1435
|
<TextView style={styles.debugInfoText}>
|
|
1157
|
-
{`Face: ${currentFaceImage ? '✓' : '✗'} ${
|
|
1158
|
-
|
|
1159
|
-
}`}
|
|
1436
|
+
{`Face: ${currentFaceImage ? '✓' : '✗'} ${!faceDetectionEnabled ? '(DISABLED)' : ''
|
|
1437
|
+
}`}
|
|
1160
1438
|
</TextView>
|
|
1161
1439
|
<TextView style={styles.debugInfoText}>
|
|
1162
|
-
{`Hologram: ${currentHologramImage ? '✓' : '✗'} (${
|
|
1163
|
-
|
|
1164
|
-
}/${HOLOGRAM_DETECTION_RETRY_COUNT})`}
|
|
1440
|
+
{`Hologram: ${currentHologramImage ? '✓' : '✗'} (${hologramDetectionCurrentRetryCount.value
|
|
1441
|
+
}/${HOLOGRAM_DETECTION_RETRY_COUNT})`}
|
|
1165
1442
|
</TextView>
|
|
1166
1443
|
<TextView style={styles.debugInfoText}>
|
|
1167
|
-
{`2nd Face: ${currentSecondaryFaceImage ? '✓' : '✗'} (${
|
|
1168
|
-
|
|
1169
|
-
}/${SECOND_FACE_DETECTION_RETRY_COUNT})`}
|
|
1444
|
+
{`2nd Face: ${currentSecondaryFaceImage ? '✓' : '✗'} (${secondaryFaceDetectionCurrentRetryCount.value
|
|
1445
|
+
}/${SECOND_FACE_DETECTION_RETRY_COUNT})`}
|
|
1170
1446
|
</TextView>
|
|
1171
1447
|
</View>
|
|
1172
1448
|
)}
|
|
@@ -1177,51 +1453,54 @@ const IdentityDocumentCamera = ({
|
|
|
1177
1453
|
{
|
|
1178
1454
|
borderColor:
|
|
1179
1455
|
status === 'SCANNED' || nextStep === 'COMPLETED'
|
|
1180
|
-
? '
|
|
1456
|
+
? '#4CAF50' // Green - success
|
|
1181
1457
|
: status === 'INCORRECT'
|
|
1182
|
-
? '
|
|
1183
|
-
: '
|
|
1458
|
+
? '#f44336' // Red - error
|
|
1459
|
+
: status === 'SCANNING'
|
|
1460
|
+
? '#2196F3' // Blue - processing
|
|
1461
|
+
: isBrightnessLow || isFrameBlurry
|
|
1462
|
+
? '#FFC107' // Yellow - warning
|
|
1463
|
+
: 'white',
|
|
1464
|
+
borderWidth: status === 'SCANNING' ? 3 : 2,
|
|
1184
1465
|
},
|
|
1185
1466
|
]}
|
|
1186
1467
|
>
|
|
1187
|
-
{
|
|
1468
|
+
{/* Only show ONE animation at a time - priority order: completed/scanned > brightness > hologram > scanning */}
|
|
1469
|
+
{nextStep === 'COMPLETED' || status === 'SCANNED' ? (
|
|
1188
1470
|
<LottieView
|
|
1189
|
-
source={require('../../Shared/Animations/
|
|
1471
|
+
source={require('../../Shared/Animations/success.json')}
|
|
1190
1472
|
style={styles.animation}
|
|
1191
|
-
loop={
|
|
1473
|
+
loop={false}
|
|
1192
1474
|
autoPlay
|
|
1193
1475
|
/>
|
|
1194
|
-
)
|
|
1195
|
-
{nextStep !== 'COMPLETED' && isBrightnessLow && (
|
|
1476
|
+
) : isBrightnessLow ? (
|
|
1196
1477
|
<LottieView
|
|
1197
1478
|
source={require('../../Shared/Animations/light.json')}
|
|
1198
1479
|
style={styles.animation}
|
|
1199
1480
|
loop={true}
|
|
1200
1481
|
autoPlay
|
|
1201
1482
|
/>
|
|
1202
|
-
)
|
|
1203
|
-
{nextStep === 'COMPLETED' && (
|
|
1483
|
+
) : nextStep === 'SCAN_HOLOGRAM' && isTorchOn ? (
|
|
1204
1484
|
<LottieView
|
|
1205
|
-
source={require('../../Shared/Animations/
|
|
1485
|
+
source={require('../../Shared/Animations/hologram-scan.json')}
|
|
1206
1486
|
style={styles.animation}
|
|
1207
|
-
loop={
|
|
1487
|
+
loop={true}
|
|
1208
1488
|
autoPlay
|
|
1209
1489
|
/>
|
|
1210
|
-
)
|
|
1490
|
+
) : status === 'SCANNING' ? (
|
|
1491
|
+
<LottieView
|
|
1492
|
+
source={require('../../Shared/Animations/scanning.json')}
|
|
1493
|
+
style={styles.animation}
|
|
1494
|
+
loop={true}
|
|
1495
|
+
autoPlay
|
|
1496
|
+
/>
|
|
1497
|
+
) : null}
|
|
1211
1498
|
</View>
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
top: boundingBox.y,
|
|
1218
|
-
left: boundingBox.x,
|
|
1219
|
-
width: boundingBox.width,
|
|
1220
|
-
height: boundingBox.height,
|
|
1221
|
-
zIndex: 3,
|
|
1222
|
-
}}
|
|
1223
|
-
/> */}
|
|
1224
|
-
<TouchableOpacity onPress={handleFocus} style={styles.focusArea} />
|
|
1499
|
+
<TouchableOpacity
|
|
1500
|
+
onPress={handleFocus}
|
|
1501
|
+
style={styles.focusArea}
|
|
1502
|
+
activeOpacity={1}
|
|
1503
|
+
/>
|
|
1225
1504
|
</>
|
|
1226
1505
|
)}
|
|
1227
1506
|
</View>
|
|
@@ -1281,6 +1560,13 @@ const styles = StyleSheet.create({
|
|
|
1281
1560
|
justifyContent: 'flex-end',
|
|
1282
1561
|
alignItems: 'center',
|
|
1283
1562
|
},
|
|
1563
|
+
stepIndicator: {
|
|
1564
|
+
color: '#aaaaaa',
|
|
1565
|
+
fontSize: 14,
|
|
1566
|
+
textAlign: 'center',
|
|
1567
|
+
fontWeight: '500',
|
|
1568
|
+
marginBottom: 4,
|
|
1569
|
+
},
|
|
1284
1570
|
topZoneText: {
|
|
1285
1571
|
color: 'white',
|
|
1286
1572
|
fontSize: 20,
|
|
@@ -1288,6 +1574,18 @@ const styles = StyleSheet.create({
|
|
|
1288
1574
|
fontWeight: 'bold',
|
|
1289
1575
|
padding: 20,
|
|
1290
1576
|
},
|
|
1577
|
+
topZoneTextScanning: {
|
|
1578
|
+
color: '#2196F3', // Blue when scanning
|
|
1579
|
+
},
|
|
1580
|
+
topZoneTextSuccess: {
|
|
1581
|
+
color: '#4CAF50', // Green for success
|
|
1582
|
+
},
|
|
1583
|
+
topZoneTextWarning: {
|
|
1584
|
+
color: '#FFC107', // Yellow for warnings
|
|
1585
|
+
},
|
|
1586
|
+
topZoneTextError: {
|
|
1587
|
+
color: '#f44336', // Red for errors
|
|
1588
|
+
},
|
|
1291
1589
|
leftZone: {
|
|
1292
1590
|
position: 'absolute',
|
|
1293
1591
|
top: '36%',
|