@trustchex/react-native-sdk 1.464.0 → 1.472.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 +43 -3
- package/ios/Camera/TrustchexCameraView.swift +152 -27
- package/ios/Permission/PermissionModule.m +22 -0
- package/ios/Permission/PermissionModule.swift +67 -0
- package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +24 -3
- package/lib/module/Screens/Dynamic/VerbalConsentScreen.js +1 -1
- package/lib/module/Screens/Dynamic/VideoCallScreen.js +7 -12
- package/lib/module/Screens/Static/ResultScreen.js +4 -1
- package/lib/module/Shared/Components/EIDScanner.js +166 -163
- package/lib/module/Shared/Components/FaceCamera.js +14 -8
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +7 -6
- package/lib/module/Shared/Libs/PERMISSIONS_MANAGER.md +268 -0
- package/lib/module/Shared/Libs/index.js +20 -0
- package/lib/module/Shared/Libs/permissions.utils.js +199 -0
- package/lib/module/Translation/Resources/en.js +1 -0
- package/lib/module/Translation/Resources/tr.js +1 -0
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- 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/Libs/index.d.ts +20 -0
- package/lib/typescript/src/Shared/Libs/index.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/permissions.utils.d.ts +58 -0
- package/lib/typescript/src/Shared/Libs/permissions.utils.d.ts.map +1 -0
- package/lib/typescript/src/Translation/Resources/en.d.ts +1 -0
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +1 -0
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +45 -5
- package/src/Screens/Dynamic/VerbalConsentScreen.tsx +1 -1
- package/src/Screens/Dynamic/VideoCallScreen.tsx +8 -19
- package/src/Screens/Static/ResultScreen.tsx +13 -9
- package/src/Shared/Components/EIDScanner.tsx +210 -160
- package/src/Shared/Components/FaceCamera.tsx +19 -16
- package/src/Shared/Components/IdentityDocumentCamera.tsx +7 -7
- package/src/Shared/Libs/PERMISSIONS_MANAGER.md +268 -0
- package/src/Shared/Libs/index.ts +63 -0
- package/src/Shared/Libs/permissions.utils.ts +251 -0
- package/src/Translation/Resources/en.ts +1 -0
- package/src/Translation/Resources/tr.ts +1 -0
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -181,7 +181,6 @@ const LivenessDetectionScreen = () => {
|
|
|
181
181
|
},
|
|
182
182
|
FINISH: { instruction: t('livenessDetectionScreen.finish') },
|
|
183
183
|
});
|
|
184
|
-
const [instructionList, setInstructionList] = useState<string[]>([]);
|
|
185
184
|
const [hasGuideShown, setHasGuideShown] = useState(false);
|
|
186
185
|
const stoppingRecordingRef = useRef(false); // Track if we're already stopping to prevent multiple calls
|
|
187
186
|
|
|
@@ -220,7 +219,6 @@ const LivenessDetectionScreen = () => {
|
|
|
220
219
|
.sort(() => Math.random() - 0.5);
|
|
221
220
|
il.unshift('START');
|
|
222
221
|
il.push('FINISH');
|
|
223
|
-
setInstructionList(il);
|
|
224
222
|
setInitialState({
|
|
225
223
|
brightnessLow: false,
|
|
226
224
|
faceDetected: false,
|
|
@@ -598,9 +596,51 @@ const LivenessDetectionScreen = () => {
|
|
|
598
596
|
const circleR = previewSizeInFrame / 2;
|
|
599
597
|
const faceDx = faceCenterX - circleCX;
|
|
600
598
|
const faceDy = faceCenterY - circleCY;
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
599
|
+
|
|
600
|
+
const yawAbs = Math.abs(face.yawAngle || 0);
|
|
601
|
+
const yawFactor = Math.min(1, yawAbs / TURN_ANGLE_LIMIT);
|
|
602
|
+
|
|
603
|
+
// General bound tolerance (instruction-agnostic): adapt to head pose and
|
|
604
|
+
// face box deformation so containment remains stable across all actions.
|
|
605
|
+
const faceWidthGrowth = Math.max(
|
|
606
|
+
0,
|
|
607
|
+
face.bounds.width - face.bounds.height
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
// On iOS, close faces tend to appear larger in-frame and lateral head
|
|
611
|
+
// turns shift center more aggressively. Apply an iOS-only proximity boost
|
|
612
|
+
// to horizontal tolerance to avoid false outside-circle resets.
|
|
613
|
+
const faceRadiusRatio = Math.min(
|
|
614
|
+
1,
|
|
615
|
+
faceCoreRadius / Math.max(1, circleR)
|
|
616
|
+
);
|
|
617
|
+
const iosProximityBoost =
|
|
618
|
+
Platform.OS === 'ios' ? Math.max(0, faceRadiusRatio - 0.5) : 0;
|
|
619
|
+
|
|
620
|
+
const baseToleranceX = previewSizeInFrame * 0.05;
|
|
621
|
+
const yawToleranceX = face.bounds.width * 0.12 * yawFactor;
|
|
622
|
+
const shapeToleranceX = faceWidthGrowth * 0.1;
|
|
623
|
+
const iosToleranceX = face.bounds.width * 0.2 * iosProximityBoost;
|
|
624
|
+
const toleranceX = Math.max(
|
|
625
|
+
0,
|
|
626
|
+
baseToleranceX + yawToleranceX + shapeToleranceX + iosToleranceX,
|
|
627
|
+
face.bounds.width * 0.05
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
const pitchAbs = Math.abs(face.pitchAngle || 0);
|
|
631
|
+
const pitchFactor = Math.min(1, pitchAbs / TURN_ANGLE_LIMIT);
|
|
632
|
+
const toleranceY = Math.max(
|
|
633
|
+
0,
|
|
634
|
+
previewSizeInFrame * 0.03 + face.bounds.height * 0.03 * pitchFactor,
|
|
635
|
+
face.bounds.height * 0.04
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
const allowedRadiusX = Math.max(1, circleR - faceCoreRadius + toleranceX);
|
|
639
|
+
const allowedRadiusY = Math.max(1, circleR - faceCoreRadius + toleranceY);
|
|
640
|
+
const normalizedDistance =
|
|
641
|
+
(faceDx * faceDx) / (allowedRadiusX * allowedRadiusX) +
|
|
642
|
+
(faceDy * faceDy) / (allowedRadiusY * allowedRadiusY);
|
|
643
|
+
const previewContainsFace = normalizedDistance <= 1;
|
|
604
644
|
const multipleFacesDetected = faces.length > 1;
|
|
605
645
|
|
|
606
646
|
if (!isImageBright) {
|
|
@@ -801,7 +801,7 @@ const VerbalConsentScreen = () => {
|
|
|
801
801
|
if (!currentInstructionKey) {
|
|
802
802
|
return;
|
|
803
803
|
}
|
|
804
|
-
speak(t(currentInstructionKey));
|
|
804
|
+
speak(t(currentInstructionKey)).catch(() => {});
|
|
805
805
|
}, [
|
|
806
806
|
appContext.currentWorkflowStep?.data?.voiceGuidanceActive,
|
|
807
807
|
currentInstructionKey,
|
|
@@ -7,13 +7,13 @@ import {
|
|
|
7
7
|
TouchableOpacity,
|
|
8
8
|
View,
|
|
9
9
|
ActivityIndicator,
|
|
10
|
-
PermissionsAndroid,
|
|
11
10
|
StatusBar,
|
|
12
11
|
} from 'react-native';
|
|
13
12
|
import { RTCView, MediaStream } from 'react-native-webrtc';
|
|
14
13
|
import InCallManager from 'react-native-incall-manager';
|
|
15
14
|
import LottieView from 'lottie-react-native';
|
|
16
15
|
import { useTranslation } from 'react-i18next';
|
|
16
|
+
import PermissionManager from '../../Shared/Libs/permissions.utils';
|
|
17
17
|
import { useStatusBarWhiteBackground } from '../../Shared/Libs/status-bar.utils';
|
|
18
18
|
import { WebRTCService } from '../../Shared/Services/WebRTCService';
|
|
19
19
|
import { VideoSessionService } from '../../Shared/Services/VideoSessionService';
|
|
@@ -373,25 +373,14 @@ const VideoCallScreen = ({ navigation }: any) => {
|
|
|
373
373
|
});
|
|
374
374
|
serviceRef.current = service;
|
|
375
375
|
|
|
376
|
-
// Request Permissions
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
granted['android.permission.CAMERA'] !==
|
|
384
|
-
PermissionsAndroid.RESULTS.GRANTED ||
|
|
385
|
-
granted['android.permission.RECORD_AUDIO'] !==
|
|
386
|
-
PermissionsAndroid.RESULTS.GRANTED
|
|
387
|
-
) {
|
|
388
|
-
if (mounted) setConnectionState('permissions_denied');
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
376
|
+
// Request Camera and Microphone Permissions
|
|
377
|
+
const hasBothPermissions =
|
|
378
|
+
await PermissionManager.requestCameraAndMicrophone();
|
|
379
|
+
|
|
380
|
+
if (!hasBothPermissions) {
|
|
381
|
+
if (mounted) setConnectionState('permissions_denied');
|
|
382
|
+
return;
|
|
391
383
|
}
|
|
392
|
-
// For iOS, react-native-webrtc or VisionCamera usually triggers it,
|
|
393
|
-
// but explicit request via library is better if we had one.
|
|
394
|
-
// Assuming automatic or previously granted for now.
|
|
395
384
|
|
|
396
385
|
try {
|
|
397
386
|
const stream = await service.initialize();
|
|
@@ -1055,15 +1055,19 @@ const ResultScreen = () => {
|
|
|
1055
1055
|
<Text style={styles.sectionText}>
|
|
1056
1056
|
{t('resultScreen.demoVideo')}
|
|
1057
1057
|
</Text>
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1058
|
+
{consentVideo.videoPath ? (
|
|
1059
|
+
<Video
|
|
1060
|
+
source={{
|
|
1061
|
+
uri: consentVideo.videoPath,
|
|
1062
|
+
}}
|
|
1063
|
+
resizeMode="contain"
|
|
1064
|
+
style={styles.video}
|
|
1065
|
+
controls={true}
|
|
1066
|
+
muted={false}
|
|
1067
|
+
/>
|
|
1068
|
+
) : (
|
|
1069
|
+
<Text style={styles.sectionText}>N/A</Text>
|
|
1070
|
+
)}
|
|
1067
1071
|
</View>
|
|
1068
1072
|
)
|
|
1069
1073
|
)}
|
|
@@ -3,6 +3,8 @@ import { Alert, View, Text, Image, StyleSheet, Animated } from 'react-native';
|
|
|
3
3
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
4
4
|
import NFCManager from 'react-native-nfc-manager';
|
|
5
5
|
import DeviceInfo from 'react-native-device-info';
|
|
6
|
+
import PermissionManager from '../Libs/permissions.utils';
|
|
7
|
+
import { debugError } from '../Libs/debug.utils';
|
|
6
8
|
import { MRZInfo } from '../EIDReader/lds/icao/mrzInfo';
|
|
7
9
|
import { eidReader } from '../EIDReader/eidReader';
|
|
8
10
|
import NativeProgressBar from './NativeProgressBar';
|
|
@@ -142,6 +144,21 @@ const EIDScanner = ({
|
|
|
142
144
|
setProgressStage('');
|
|
143
145
|
resetLastMessage();
|
|
144
146
|
|
|
147
|
+
// Check NFC permissions before attempting to scan
|
|
148
|
+
const hasNFCPermission = await PermissionManager.requestPermission('nfc');
|
|
149
|
+
if (!hasNFCPermission) {
|
|
150
|
+
debugError('EIDScanner', 'NFC permission denied');
|
|
151
|
+
setIsScanning(false);
|
|
152
|
+
Alert.alert(
|
|
153
|
+
t('eidScannerScreen.error'),
|
|
154
|
+
t(
|
|
155
|
+
'eidScannerScreen.nfcPermissionDenied',
|
|
156
|
+
'NFC permission is required to scan your document'
|
|
157
|
+
)
|
|
158
|
+
);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
145
162
|
// Track EID scan start using analytics helper
|
|
146
163
|
const docType =
|
|
147
164
|
(documentType as 'ID' | 'PASSPORT' | 'UNKNOWN') || 'UNKNOWN';
|
|
@@ -473,116 +490,124 @@ const EIDScanner = ({
|
|
|
473
490
|
</View>
|
|
474
491
|
) : (
|
|
475
492
|
<>
|
|
476
|
-
{isScanned && (
|
|
477
|
-
<View style={styles.
|
|
478
|
-
<View style={styles.
|
|
479
|
-
{
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
493
|
+
{isScanned && documentMRZInfo && (
|
|
494
|
+
<View style={styles.idCardWrapper}>
|
|
495
|
+
<View style={styles.idCard}>
|
|
496
|
+
{/* ── Card header strip: doc type + issuing state ── */}
|
|
497
|
+
<View
|
|
498
|
+
style={[
|
|
499
|
+
styles.idCardStrip,
|
|
500
|
+
{ backgroundColor: appContext.branding.primaryColor },
|
|
501
|
+
]}
|
|
502
|
+
>
|
|
503
|
+
<Text style={styles.idCardStripDocType}>
|
|
504
|
+
{formatDocumentType(documentMRZInfo.getDocumentCode())}
|
|
505
|
+
</Text>
|
|
506
|
+
</View>
|
|
507
|
+
|
|
508
|
+
{/* ── Photo (left) + fields (right) ── */}
|
|
509
|
+
<View style={styles.idCardBody}>
|
|
510
|
+
<View style={styles.idCardPhotoContainer}>
|
|
511
|
+
<View style={styles.idCardPhotoFrame}>
|
|
512
|
+
{documentFaceImage &&
|
|
513
|
+
(documentFaceImageMimeType === 'image/jpeg' ||
|
|
514
|
+
documentFaceImageMimeType === 'image/png') ? (
|
|
515
|
+
<Image
|
|
516
|
+
source={{
|
|
517
|
+
uri: `data:${documentFaceImageMimeType};base64,${documentFaceImage}`,
|
|
518
|
+
}}
|
|
519
|
+
style={styles.idCardPhoto}
|
|
520
|
+
resizeMode="cover"
|
|
521
|
+
/>
|
|
522
|
+
) : (
|
|
523
|
+
<View style={styles.idCardPhotoPlaceholder}>
|
|
524
|
+
<Text style={styles.idCardPhotoPlaceholderText}>
|
|
525
|
+
?
|
|
526
|
+
</Text>
|
|
527
|
+
</View>
|
|
528
|
+
)}
|
|
529
|
+
</View>
|
|
492
530
|
</View>
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
<View style={styles.
|
|
496
|
-
<
|
|
497
|
-
{
|
|
498
|
-
{documentMRZInfo.getPrimaryIdentifier()}
|
|
499
|
-
|
|
500
|
-
<
|
|
501
|
-
{
|
|
502
|
-
{
|
|
503
|
-
|
|
504
|
-
<
|
|
505
|
-
{
|
|
506
|
-
{
|
|
507
|
-
|
|
531
|
+
|
|
532
|
+
{/* Right column: all fields as grid */}
|
|
533
|
+
<View style={styles.idCardFields}>
|
|
534
|
+
<IDCardField
|
|
535
|
+
label={t('eidScannerScreen.surname')}
|
|
536
|
+
value={documentMRZInfo.getPrimaryIdentifier()}
|
|
537
|
+
/>
|
|
538
|
+
<IDCardField
|
|
539
|
+
label={t('eidScannerScreen.name')}
|
|
540
|
+
value={documentMRZInfo.getSecondaryIdentifier()}
|
|
541
|
+
/>
|
|
542
|
+
<IDCardField
|
|
543
|
+
label={t('eidScannerScreen.documentNumber')}
|
|
544
|
+
value={documentMRZInfo.getDocumentNumber()}
|
|
545
|
+
/>
|
|
546
|
+
<IDCardField
|
|
547
|
+
label={t('eidScannerScreen.birthDate')}
|
|
548
|
+
value={formatDate(documentMRZInfo.getDateOfBirth())}
|
|
549
|
+
/>
|
|
550
|
+
<IDCardField
|
|
551
|
+
label={t('eidScannerScreen.expirationDate')}
|
|
552
|
+
value={formatDate(documentMRZInfo.getDateOfExpiry())}
|
|
553
|
+
/>
|
|
554
|
+
<IDCardField
|
|
555
|
+
label={t('eidScannerScreen.gender')}
|
|
556
|
+
value={formatGender(
|
|
557
|
+
documentMRZInfo.getGender().getStrCode()
|
|
558
|
+
)}
|
|
559
|
+
/>
|
|
560
|
+
<IDCardField
|
|
561
|
+
label={t('eidScannerScreen.nationality')}
|
|
562
|
+
value={formatNationality(
|
|
563
|
+
documentMRZInfo.getNationality()
|
|
564
|
+
)}
|
|
565
|
+
/>
|
|
566
|
+
<IDCardField
|
|
567
|
+
label={t('eidScannerScreen.issuingState')}
|
|
568
|
+
value={formatNationality(
|
|
569
|
+
documentMRZInfo.getIssuingState()
|
|
570
|
+
)}
|
|
571
|
+
/>
|
|
572
|
+
{!!(
|
|
573
|
+
documentMRZInfo.getPersonalNumber() ||
|
|
574
|
+
documentMRZInfo.getOptionalData1()
|
|
575
|
+
) && (
|
|
576
|
+
<IDCardField
|
|
577
|
+
label={t('eidScannerScreen.personalNumber')}
|
|
578
|
+
value={
|
|
579
|
+
documentMRZInfo.getPersonalNumber() ||
|
|
580
|
+
documentMRZInfo.getOptionalData1() ||
|
|
581
|
+
''
|
|
582
|
+
}
|
|
583
|
+
/>
|
|
584
|
+
)}
|
|
508
585
|
</View>
|
|
509
|
-
|
|
586
|
+
</View>
|
|
510
587
|
</View>
|
|
511
588
|
|
|
512
|
-
{
|
|
513
|
-
<View style={styles.
|
|
514
|
-
<
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
<Text style={styles.mrzInfoText}>
|
|
535
|
-
{formatDocumentType(documentMRZInfo.getDocumentCode())}
|
|
536
|
-
</Text>
|
|
537
|
-
</View>
|
|
538
|
-
<View style={styles.mrzInfoItem}>
|
|
539
|
-
<Text style={styles.mrzInfoLabel}>
|
|
540
|
-
{t('eidScannerScreen.documentNumber')}
|
|
541
|
-
</Text>
|
|
542
|
-
<Text style={styles.mrzInfoText}>
|
|
543
|
-
{documentMRZInfo.getDocumentNumber()}
|
|
544
|
-
</Text>
|
|
545
|
-
</View>
|
|
546
|
-
<View style={styles.mrzInfoItem}>
|
|
547
|
-
<Text style={styles.mrzInfoLabel}>
|
|
548
|
-
{t('eidScannerScreen.personalNumber')}
|
|
549
|
-
</Text>
|
|
550
|
-
<Text style={styles.mrzInfoText}>
|
|
551
|
-
{documentMRZInfo.getOptionalData1()}
|
|
552
|
-
</Text>
|
|
553
|
-
</View>
|
|
554
|
-
<View style={styles.mrzInfoItem}>
|
|
555
|
-
<Text style={styles.mrzInfoLabel}>
|
|
556
|
-
{t('eidScannerScreen.nationality')}
|
|
557
|
-
</Text>
|
|
558
|
-
<Text style={styles.mrzInfoText}>
|
|
559
|
-
{formatNationality(documentMRZInfo.getNationality())}
|
|
560
|
-
</Text>
|
|
561
|
-
</View>
|
|
562
|
-
<View style={styles.mrzInfoItem}>
|
|
563
|
-
<Text style={styles.mrzInfoLabel}>
|
|
564
|
-
{t('eidScannerScreen.gender')}
|
|
565
|
-
</Text>
|
|
566
|
-
<Text style={styles.mrzInfoText}>
|
|
567
|
-
{formatGender(documentMRZInfo.getGender().getStrCode())}
|
|
568
|
-
</Text>
|
|
569
|
-
</View>
|
|
570
|
-
<View style={styles.mrzInfoItem}>
|
|
571
|
-
<Text style={styles.mrzInfoLabel}>
|
|
572
|
-
{t('eidScannerScreen.birthDate')}
|
|
573
|
-
</Text>
|
|
574
|
-
<Text style={styles.mrzInfoText}>
|
|
575
|
-
{formatDate(documentMRZInfo.getDateOfBirth())}
|
|
576
|
-
</Text>
|
|
577
|
-
</View>
|
|
578
|
-
<View style={styles.mrzInfoItem}>
|
|
579
|
-
<Text style={styles.mrzInfoLabel}>
|
|
580
|
-
{t('eidScannerScreen.expirationDate')}
|
|
581
|
-
</Text>
|
|
582
|
-
<Text style={styles.mrzInfoText}>
|
|
583
|
-
{formatDate(documentMRZInfo.getDateOfExpiry())}
|
|
584
|
-
</Text>
|
|
585
|
-
</View>
|
|
589
|
+
{hasNfc && isEnabled && isScanned && (
|
|
590
|
+
<View style={styles.buttonsContainer}>
|
|
591
|
+
<StyledButton
|
|
592
|
+
mode="contained"
|
|
593
|
+
disabled={!documentFaceImage || !documentMRZInfo}
|
|
594
|
+
onPress={() => {
|
|
595
|
+
if (
|
|
596
|
+
onScanSuccess &&
|
|
597
|
+
documentMRZInfo &&
|
|
598
|
+
documentFaceImage &&
|
|
599
|
+
documentFaceImageMimeType
|
|
600
|
+
) {
|
|
601
|
+
onScanSuccess(
|
|
602
|
+
getFieldsFromMRZ(documentMRZInfo),
|
|
603
|
+
documentFaceImage,
|
|
604
|
+
documentFaceImageMimeType
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
}}
|
|
608
|
+
>
|
|
609
|
+
{t('eidScannerScreen.approveAndContinue')}
|
|
610
|
+
</StyledButton>
|
|
586
611
|
</View>
|
|
587
612
|
)}
|
|
588
613
|
</View>
|
|
@@ -648,7 +673,7 @@ const EIDScanner = ({
|
|
|
648
673
|
</View>
|
|
649
674
|
)}
|
|
650
675
|
|
|
651
|
-
{hasNfc && isEnabled && isScanned && (
|
|
676
|
+
{hasNfc && isEnabled && isScanned && !documentMRZInfo && (
|
|
652
677
|
<View style={styles.buttonsContainer}>
|
|
653
678
|
<StyledButton
|
|
654
679
|
mode="contained"
|
|
@@ -747,83 +772,99 @@ const styles = StyleSheet.create({
|
|
|
747
772
|
color: 'black',
|
|
748
773
|
textAlign: 'center',
|
|
749
774
|
},
|
|
750
|
-
|
|
775
|
+
// ── ID Card result layout ──────────────────────────────────────────────
|
|
776
|
+
idCardWrapper: {
|
|
777
|
+
gap: 10,
|
|
778
|
+
},
|
|
779
|
+
idCard: {
|
|
751
780
|
backgroundColor: '#fff',
|
|
752
|
-
borderRadius:
|
|
753
|
-
|
|
754
|
-
gap: 16,
|
|
781
|
+
borderRadius: 14,
|
|
782
|
+
overflow: 'hidden',
|
|
755
783
|
shadowColor: '#000',
|
|
756
|
-
shadowOffset: { width: 0, height:
|
|
757
|
-
shadowOpacity: 0.
|
|
758
|
-
shadowRadius:
|
|
759
|
-
elevation:
|
|
784
|
+
shadowOffset: { width: 0, height: 3 },
|
|
785
|
+
shadowOpacity: 0.12,
|
|
786
|
+
shadowRadius: 10,
|
|
787
|
+
elevation: 4,
|
|
760
788
|
borderWidth: 1,
|
|
761
|
-
borderColor: '#
|
|
789
|
+
borderColor: '#e8e8e8',
|
|
762
790
|
},
|
|
763
|
-
|
|
791
|
+
idCardStrip: {
|
|
764
792
|
flexDirection: 'row',
|
|
765
|
-
|
|
766
|
-
alignItems: '
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
flex: 1,
|
|
770
|
-
gap: 4,
|
|
771
|
-
justifyContent: 'center',
|
|
793
|
+
justifyContent: 'space-between',
|
|
794
|
+
alignItems: 'center',
|
|
795
|
+
paddingHorizontal: 12,
|
|
796
|
+
paddingVertical: 6,
|
|
772
797
|
},
|
|
773
|
-
|
|
774
|
-
|
|
798
|
+
idCardStripDocType: {
|
|
799
|
+
color: '#fff',
|
|
800
|
+
fontSize: 12,
|
|
775
801
|
fontWeight: '700',
|
|
776
|
-
|
|
777
|
-
|
|
802
|
+
letterSpacing: 0.5,
|
|
803
|
+
textTransform: 'uppercase',
|
|
778
804
|
},
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
fontWeight: '
|
|
805
|
+
idCardStripIssuer: {
|
|
806
|
+
color: 'rgba(255,255,255,0.85)',
|
|
807
|
+
fontSize: 12,
|
|
808
|
+
fontWeight: '600',
|
|
809
|
+
letterSpacing: 0.3,
|
|
810
|
+
},
|
|
811
|
+
idCardBody: {
|
|
812
|
+
flexDirection: 'row',
|
|
813
|
+
alignItems: 'stretch',
|
|
783
814
|
},
|
|
784
|
-
|
|
785
|
-
width:
|
|
786
|
-
|
|
815
|
+
idCardPhotoContainer: {
|
|
816
|
+
width: 110,
|
|
817
|
+
padding: 8,
|
|
818
|
+
alignSelf: 'stretch',
|
|
819
|
+
},
|
|
820
|
+
idCardPhotoFrame: {
|
|
821
|
+
flex: 1,
|
|
787
822
|
borderRadius: 10,
|
|
788
823
|
overflow: 'hidden',
|
|
824
|
+
borderWidth: 1,
|
|
825
|
+
borderColor: 'rgba(0,0,0,0.12)',
|
|
789
826
|
},
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
827
|
+
idCardPhoto: {
|
|
828
|
+
flex: 1,
|
|
829
|
+
},
|
|
830
|
+
idCardPhotoPlaceholder: {
|
|
831
|
+
flex: 1,
|
|
793
832
|
borderRadius: 10,
|
|
794
833
|
backgroundColor: '#f5f5f5',
|
|
795
834
|
alignItems: 'center',
|
|
796
835
|
justifyContent: 'center',
|
|
797
836
|
},
|
|
798
|
-
|
|
799
|
-
fontSize:
|
|
837
|
+
idCardPhotoPlaceholderText: {
|
|
838
|
+
fontSize: 30,
|
|
800
839
|
color: '#ccc',
|
|
801
840
|
},
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
width: '100%',
|
|
805
|
-
gap: 2,
|
|
806
|
-
},
|
|
807
|
-
mrzInfoItem: {
|
|
841
|
+
idCardFields: {
|
|
842
|
+
flex: 1,
|
|
808
843
|
flexDirection: 'row',
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
borderBottomWidth: 1,
|
|
844
|
+
flexWrap: 'wrap',
|
|
845
|
+
gap: 6,
|
|
846
|
+
alignContent: 'flex-start',
|
|
847
|
+
padding: 10,
|
|
814
848
|
},
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
849
|
+
idCardFieldItem: {
|
|
850
|
+
flexGrow: 1,
|
|
851
|
+
flexShrink: 1,
|
|
852
|
+
flexBasis: '45%',
|
|
853
|
+
minWidth: 0,
|
|
819
854
|
},
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
855
|
+
idCardFieldLabel: {
|
|
856
|
+
fontSize: 8,
|
|
857
|
+
fontWeight: '600',
|
|
858
|
+
color: '#999',
|
|
859
|
+
letterSpacing: 0.3,
|
|
860
|
+
marginBottom: 1,
|
|
861
|
+
},
|
|
862
|
+
idCardFieldValue: {
|
|
863
|
+
fontSize: 11,
|
|
823
864
|
fontWeight: '600',
|
|
824
|
-
|
|
865
|
+
color: '#111',
|
|
825
866
|
flexShrink: 1,
|
|
826
|
-
|
|
867
|
+
flexWrap: 'wrap',
|
|
827
868
|
},
|
|
828
869
|
guide: {
|
|
829
870
|
flex: 1,
|
|
@@ -855,4 +896,13 @@ const styles = StyleSheet.create({
|
|
|
855
896
|
},
|
|
856
897
|
});
|
|
857
898
|
|
|
899
|
+
const IDCardField = ({ label, value }: { label: string; value: string }) => (
|
|
900
|
+
<View style={styles.idCardFieldItem}>
|
|
901
|
+
<Text style={styles.idCardFieldLabel} numberOfLines={1}>
|
|
902
|
+
{label}
|
|
903
|
+
</Text>
|
|
904
|
+
<Text style={styles.idCardFieldValue}>{value || '—'}</Text>
|
|
905
|
+
</View>
|
|
906
|
+
);
|
|
907
|
+
|
|
858
908
|
export default EIDScanner;
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { useIsFocused } from '@react-navigation/native';
|
|
2
2
|
import { useKeepAwake } from '../Libs/native-keep-awake.utils';
|
|
3
3
|
import { debugLog, debugWarn, debugError, logError } from '../Libs/debug.utils';
|
|
4
|
+
import PermissionManager from '../Libs/permissions.utils';
|
|
4
5
|
import React, { useEffect, useState, useRef, useCallback } from 'react';
|
|
5
6
|
import {
|
|
6
7
|
StyleSheet,
|
|
7
8
|
Text,
|
|
8
9
|
View,
|
|
9
|
-
Platform,
|
|
10
10
|
Linking,
|
|
11
11
|
ActivityIndicator,
|
|
12
12
|
NativeModules,
|
|
13
|
-
PermissionsAndroid,
|
|
14
13
|
type NativeSyntheticEvent,
|
|
15
14
|
} from 'react-native';
|
|
16
15
|
import {
|
|
@@ -81,20 +80,24 @@ const FaceCamera = ({
|
|
|
81
80
|
|
|
82
81
|
useEffect(() => {
|
|
83
82
|
const requestPermissions = async () => {
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
83
|
+
try {
|
|
84
|
+
const permissions = await PermissionManager.requestPermissions([
|
|
85
|
+
'camera',
|
|
86
|
+
'microphone',
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
const hasCamera = permissions.get('camera') ?? false;
|
|
90
|
+
const hasMicrophone = permissions.get('microphone') ?? false;
|
|
91
|
+
|
|
92
|
+
debugLog('FaceCamera', 'Permissions requested:', {
|
|
93
|
+
camera: hasCamera,
|
|
94
|
+
microphone: hasMicrophone,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
setHasPermission(hasCamera && hasMicrophone);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
debugError('FaceCamera', 'Permission request failed:', error);
|
|
100
|
+
setHasPermission(false);
|
|
98
101
|
}
|
|
99
102
|
setPermissionsRequested(true);
|
|
100
103
|
};
|