@trustchex/react-native-sdk 1.464.0 → 1.475.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/build.gradle +3 -3
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +190 -12
- package/ios/Camera/TrustchexCameraView.swift +244 -46
- package/ios/Permission/PermissionModule.m +22 -0
- package/ios/Permission/PermissionModule.swift +67 -0
- package/lib/module/Screens/Debug/MRZTestScreen.js +121 -147
- 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 +9 -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 +45 -10
- package/lib/module/Shared/Libs/MRZ_KNOWN_ISSUES.md +112 -0
- package/lib/module/Shared/Libs/PERMISSIONS_MANAGER.md +268 -0
- package/lib/module/Shared/Libs/index.js +20 -0
- package/lib/module/Shared/Libs/mrz.utils.js +570 -16
- package/lib/module/Shared/Libs/mrzFrameAggregator.js +301 -0
- package/lib/module/Shared/Libs/mrzOcrIntegration.js +109 -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/Debug/MRZTestScreen.d.ts.map +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/mrz.utils.d.ts +8 -0
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrzFrameAggregator.d.ts +76 -0
- package/lib/typescript/src/Shared/Libs/mrzFrameAggregator.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/mrzOcrIntegration.d.ts +73 -0
- package/lib/typescript/src/Shared/Libs/mrzOcrIntegration.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 +14 -10
- package/src/Screens/Debug/MRZTestScreen.tsx +137 -166
- 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 +18 -9
- package/src/Shared/Components/EIDScanner.tsx +210 -160
- package/src/Shared/Components/FaceCamera.tsx +19 -16
- package/src/Shared/Components/IdentityDocumentCamera.tsx +53 -13
- package/src/Shared/Libs/MRZ_KNOWN_ISSUES.md +112 -0
- package/src/Shared/Libs/PERMISSIONS_MANAGER.md +268 -0
- package/src/Shared/Libs/index.ts +63 -0
- package/src/Shared/Libs/mrz.utils.ts +639 -11
- package/src/Shared/Libs/mrzFrameAggregator.ts +370 -0
- package/src/Shared/Libs/mrzOcrIntegration.ts +175 -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
|
@@ -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
|
};
|
|
@@ -21,7 +21,9 @@ import {
|
|
|
21
21
|
type Frame,
|
|
22
22
|
} from './TrustchexCamera';
|
|
23
23
|
import { NativeModules } from 'react-native';
|
|
24
|
+
import PermissionManager from '../Libs/permissions.utils';
|
|
24
25
|
import mrzUtils from '../Libs/mrz.utils';
|
|
26
|
+
import { MRZFrameAggregator } from '../Libs/mrzFrameAggregator';
|
|
25
27
|
import { useKeepAwake } from '../Libs/native-keep-awake.utils';
|
|
26
28
|
import { useIsFocused } from '@react-navigation/native';
|
|
27
29
|
import { useTranslation } from 'react-i18next';
|
|
@@ -143,6 +145,10 @@ const IdentityDocumentCamera = ({
|
|
|
143
145
|
const lastValidMRZText = useRef<string | null>(null);
|
|
144
146
|
const lastValidMRZFields = useRef<any>(null);
|
|
145
147
|
const validMRZConsecutiveCount = useRef(0);
|
|
148
|
+
// Multi-frame per-character voting: fuses OCR across frames so a single-frame
|
|
149
|
+
// misread (B↔8, S↔5, <↔K, dropped/inserted digit) is outvoted instead of
|
|
150
|
+
// resetting progress. Drives the same `parsedMRZData`/stability the flow uses.
|
|
151
|
+
const mrzAggregator = useRef(new MRZFrameAggregator({ stabilityTarget: 2 }));
|
|
146
152
|
|
|
147
153
|
// Document type stability tracking - require consistent detections from good quality frames
|
|
148
154
|
const lastDetectedDocType = useRef<
|
|
@@ -246,13 +252,12 @@ const IdentityDocumentCamera = ({
|
|
|
246
252
|
|
|
247
253
|
useEffect(() => {
|
|
248
254
|
const requestPermissions = async () => {
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
setHasPermission(true);
|
|
255
|
+
try {
|
|
256
|
+
const hasCameraPermission =
|
|
257
|
+
await PermissionManager.requestPermission('camera');
|
|
258
|
+
setHasPermission(hasCameraPermission);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
setHasPermission(false);
|
|
256
261
|
}
|
|
257
262
|
setPermissionsRequested(true);
|
|
258
263
|
};
|
|
@@ -277,6 +282,7 @@ const IdentityDocumentCamera = ({
|
|
|
277
282
|
lastValidMRZText.current = null;
|
|
278
283
|
lastValidMRZFields.current = null;
|
|
279
284
|
validMRZConsecutiveCount.current = 0;
|
|
285
|
+
mrzAggregator.current.reset();
|
|
280
286
|
cachedBarcode.current = null;
|
|
281
287
|
isCompletionCallbackInvoked.current = false;
|
|
282
288
|
isStepTransitionInProgress.current = false;
|
|
@@ -601,6 +607,8 @@ const IdentityDocumentCamera = ({
|
|
|
601
607
|
if (nextStepType === 'SCAN_ID_BACK') {
|
|
602
608
|
lastValidMRZText.current = null;
|
|
603
609
|
lastValidMRZFields.current = null;
|
|
610
|
+
// New MRZ expected on the back — start voting fresh.
|
|
611
|
+
mrzAggregator.current.reset();
|
|
604
612
|
}
|
|
605
613
|
validMRZConsecutiveCount.current = 0;
|
|
606
614
|
cachedBarcode.current = null; // Clear cached barcode on step change
|
|
@@ -848,14 +856,43 @@ const IdentityDocumentCamera = ({
|
|
|
848
856
|
// Prefer MRZ-only text if available (from detected MRZ blocks),
|
|
849
857
|
// otherwise fall back to all text (for backward compatibility)
|
|
850
858
|
const textForValidation = scannedText?.mrzOnlyText || text;
|
|
859
|
+
|
|
860
|
+
// Multi-frame per-character voting: feed this frame's OCR into the
|
|
861
|
+
// aggregator (weighted by the MRZ band's pixel height as a sharpness/
|
|
862
|
+
// closeness proxy — a taller band means more pixels per character, which
|
|
863
|
+
// ML Kit reads more reliably) and prefer the fused consensus. This outvotes
|
|
864
|
+
// single-frame OCR errors (B↔8, S↔5, <↔K, dropped/inserted digit) instead
|
|
865
|
+
// of letting one noisy frame reset progress.
|
|
866
|
+
const mrzBandHeight = scannedText?.blocks?.length
|
|
867
|
+
? Math.max(
|
|
868
|
+
0,
|
|
869
|
+
...scannedText.blocks.map((b) => b.blockFrame?.height ?? 0)
|
|
870
|
+
)
|
|
871
|
+
: 0;
|
|
872
|
+
const frameWeight = mrzBandHeight > 0 ? mrzBandHeight : 1;
|
|
873
|
+
const consensus = mrzAggregator.current.addFrame({
|
|
874
|
+
text: textForValidation,
|
|
875
|
+
weight: frameWeight,
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
// Use the fused consensus when it is valid; otherwise fall back to a
|
|
879
|
+
// single-frame validation so document-type detection still works on the
|
|
880
|
+
// early frames. Only run the (expensive) single-frame pass when the
|
|
881
|
+
// consensus has NOT yet validated — once it has, the per-frame validation
|
|
882
|
+
// is redundant work that would needlessly load the JS thread.
|
|
851
883
|
const mrzValidationResult =
|
|
852
|
-
|
|
884
|
+
consensus.validation?.valid && consensus.validation.fields
|
|
885
|
+
? consensus.validation
|
|
886
|
+
: mrzUtils.validateMRZWithCorrections(textForValidation);
|
|
853
887
|
const parsedMRZData = {
|
|
854
888
|
valid: mrzValidationResult.valid,
|
|
855
889
|
fields: mrzValidationResult.fields || null,
|
|
856
890
|
};
|
|
857
|
-
//
|
|
858
|
-
|
|
891
|
+
// Raw MRZ lines: prefer the fused/corrected consensus when valid, else the
|
|
892
|
+
// lightweight per-frame clean. correctedMrz is the noise-free recovered MRZ.
|
|
893
|
+
const mrzText = parsedMRZData.valid
|
|
894
|
+
? (mrzValidationResult.correctedMrz ?? mrzUtils.fixMRZ(text))
|
|
895
|
+
: null;
|
|
859
896
|
|
|
860
897
|
// CRITICAL: Only detect document type during initial scan step
|
|
861
898
|
// For SCAN_HOLOGRAM and beyond, use the locked detectedDocumentType to avoid
|
|
@@ -1006,9 +1043,12 @@ const IdentityDocumentCamera = ({
|
|
|
1006
1043
|
|
|
1007
1044
|
// Check if we have enough consistent valid reads
|
|
1008
1045
|
const mrzStableAndValid =
|
|
1009
|
-
|
|
1010
|
-
parsedMRZData?.valid === true
|
|
1011
|
-
|
|
1046
|
+
// Either: the multi-frame voted consensus has converged (valid + stable),
|
|
1047
|
+
(consensus.stable && parsedMRZData?.valid === true) ||
|
|
1048
|
+
// or the legacy N-identical-reads path is satisfied.
|
|
1049
|
+
(validMRZConsecutiveCount.current >= REQUIRED_CONSISTENT_MRZ_READS &&
|
|
1050
|
+
parsedMRZData?.valid === true &&
|
|
1051
|
+
areMRZFieldsEqual(lastValidMRZFields.current, parsedMRZData?.fields));
|
|
1012
1052
|
|
|
1013
1053
|
// ============================================================================
|
|
1014
1054
|
// SCAN_ID_BACK STEP - Validate MRZ + barcode on back of ID card
|