@test-web/react-native-sdk 2.2.0 → 2.4.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/.transforms/246c075ea944392e66db7aa639265547/results.bin +1 -0
- package/android/build/.transforms/246c075ea944392e66db7aa639265547/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/yourcompany/sdk/BuildConfig.dex +0 -0
- package/android/build/.transforms/246c075ea944392e66db7aa639265547/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/yourcompany/sdk/YourSDKModule.dex +0 -0
- package/android/build/.transforms/246c075ea944392e66db7aa639265547/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/yourcompany/sdk/YourSDKPackage.dex +0 -0
- package/android/build/.transforms/246c075ea944392e66db7aa639265547/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +0 -0
- package/android/build/.transforms/952af5a0e7b5b2ac3d48ad66eccefd1f/results.bin +1 -0
- package/android/build/.transforms/952af5a0e7b5b2ac3d48ad66eccefd1f/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/generated/source/buildConfig/debug/com/yourcompany/sdk/BuildConfig.java +10 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +34 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +18 -0
- package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +6 -0
- package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +1 -0
- package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
- package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/yourcompany/sdk/BuildConfig.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/yourcompany/sdk/YourSDKModule.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/yourcompany/sdk/YourSDKPackage.class +0 -0
- package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +2 -0
- package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +56 -0
- package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +34 -0
- package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +1 -0
- package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +1 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/yourcompany/sdk/BuildConfig.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/yourcompany/sdk/YourSDKModule.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/yourcompany/sdk/YourSDKPackage.class +0 -0
- package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +1 -0
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +61 -0
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/package.json +1 -1
- package/src/apis/captureBarcode.ts +37 -0
- package/src/apis/captureMRZ.ts +39 -0
- package/src/apis/checkLiveness.ts +48 -0
- package/src/apis/index.ts +26 -75
- package/src/components/common/Footer.tsx +19 -22
- package/src/components/common/Header.tsx +22 -21
- package/src/components/common/Loader.tsx +9 -28
- package/src/components/ui/Button.tsx +23 -31
- package/src/components/ui/ThemedText.tsx +21 -32
- package/src/context/FailedCountContext.tsx +35 -0
- package/src/context/IDMConfigurationContext.tsx +12 -2
- package/src/context/themes.ts +20 -0
- package/src/index.tsx +41 -29
- package/src/screens/BackDocumentAdvice.tsx +13 -18
- package/src/screens/BarcodeAdvice.tsx +39 -19
- package/src/screens/BarcodeCapture.tsx +127 -158
- package/src/screens/DocumentCaptureBack.tsx +145 -102
- package/src/screens/DocumentCaptureFront.tsx +146 -113
- package/src/screens/FrontDocumentAdvice.tsx +13 -18
- package/src/screens/LocationPermission.tsx +124 -17
- package/src/screens/MrzAdvice.tsx +27 -13
- package/src/screens/MrzCapture.tsx +233 -206
- package/src/screens/SelectDocuments.tsx +57 -66
- package/src/screens/SelfieAdvice.tsx +2 -3
- package/src/screens/SelfieCapture.tsx +135 -103
- package/src/screens/ThankYou.tsx +25 -31
- package/src/screens/VerifyIdentity.tsx +1 -0
- package/src/styles/BarcodeAdviceStyles.ts +6 -6
- package/src/styles/DocumentCaptureBackStyle.ts +70 -0
- package/src/styles/DocumentCaptureFrontStyle.ts +70 -0
- package/src/styles/FooterStyles.ts +27 -0
- package/src/styles/HeaderStyles.ts +20 -0
- package/src/styles/LoaderStyles.ts +14 -0
- package/src/styles/ScannerStyles.ts +46 -9
- package/src/styles/ThankYouStyles.ts +8 -11
- package/src/types/index.ts +8 -0
- package/src/utils/MRZ_README.md +111 -0
- package/src/utils/imagesHelper.ts +264 -0
- package/src/utils/metadata_new.json +11373 -1
- package/src/utils/mrzExamples.ts +127 -0
- package/src/utils/mrzParser.ts +303 -0
- package/src/utils/mrzUtils.ts +70 -0
|
@@ -1,253 +1,280 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { View, Text,
|
|
3
|
-
import {
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { View, Text, ScrollView, Image } from 'react-native';
|
|
3
|
+
import { Camera, useCameraDevice, useCameraPermission } from 'react-native-vision-camera';
|
|
4
|
+
import TextRecognition from '@react-native-ml-kit/text-recognition';
|
|
4
5
|
import { useTheme } from '../context/ThemeContext';
|
|
5
6
|
import { useOrientation } from '../hooks/useOrientation';
|
|
6
7
|
import { useIDM } from '../context/IDMConfigurationContext';
|
|
7
8
|
import getStyles from '../styles/ScannerStyles';
|
|
8
|
-
import Loader from '../components/common/Loader';
|
|
9
9
|
import { captureMRZ } from '../apis';
|
|
10
|
-
import {
|
|
10
|
+
import { useNavigation } from '@react-navigation/native';
|
|
11
|
+
import Loader from '../components/common/Loader';
|
|
12
|
+
import Header from '../components/common/Header';
|
|
13
|
+
import Button from '../components/ui/Button';
|
|
14
|
+
import ThemedText from '../components/ui/ThemedText';
|
|
15
|
+
import getPermissionStyles from '../styles/PermissionStyle';
|
|
16
|
+
const overlayImage = require('../../assets/images/MRZOverlay.png');
|
|
11
17
|
|
|
12
|
-
|
|
18
|
+
function MrzCapture() {
|
|
13
19
|
const { theme } = useTheme();
|
|
14
|
-
const { idmConf, setIDMConf } = useIDM();
|
|
15
20
|
const orientation = useOrientation();
|
|
16
21
|
const styles = getStyles(theme, orientation);
|
|
22
|
+
const permissionStyles = getPermissionStyles(theme, orientation);
|
|
17
23
|
const navigation = useNavigation();
|
|
18
|
-
const
|
|
19
|
-
const [scannedData, setScannedData] = useState<string | null>(null);
|
|
20
|
-
const [isScanning, setIsScanning] = useState(true);
|
|
21
|
-
const [hasPermission, setHasPermission] = useState(false);
|
|
22
|
-
const [device, setDevice] = useState<any>(null);
|
|
23
|
-
const cameraRef = useRef<any>(null);
|
|
24
|
-
const frameProcessorRef = useRef<any>(null);
|
|
25
|
-
|
|
26
|
-
// Get MRZ engine type from metadata
|
|
27
|
-
const getMRZEngine = () => {
|
|
28
|
-
const metadata = idmConf?.selectedCountryDetails?.selectedMetaData;
|
|
29
|
-
if (!metadata) return 'TD3';
|
|
30
|
-
|
|
31
|
-
const flowManager = createFlowManager(metadata);
|
|
32
|
-
return flowManager.getMRZEngine();
|
|
33
|
-
};
|
|
24
|
+
const { idmConf } = useIDM();
|
|
34
25
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
const { hasPermission, requestPermission } = useCameraPermission();
|
|
27
|
+
const device = useCameraDevice('back');
|
|
28
|
+
const cameraRef = useRef<Camera>(null);
|
|
38
29
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (!hasPerm) {
|
|
46
|
-
const granted = await requestPermission();
|
|
47
|
-
if (!granted) {
|
|
48
|
-
navigation.navigate('CameraPermission' as never);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (isMounted) {
|
|
54
|
-
setHasPermission(true);
|
|
55
|
-
|
|
56
|
-
const backDevice = useCameraDevice('back');
|
|
57
|
-
if (!backDevice) {
|
|
58
|
-
navigation.navigate('NoCameraFound' as never);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
setDevice(backDevice);
|
|
63
|
-
}
|
|
64
|
-
} catch (error) {
|
|
65
|
-
console.warn('Camera initialization failed:', error);
|
|
66
|
-
if (isMounted) {
|
|
67
|
-
simulateMRZScan();
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
};
|
|
30
|
+
const [isCallingAPI, setIsCallingAPI] = useState(false);
|
|
31
|
+
const [autoScan, setAutoScan] = useState(true);
|
|
32
|
+
const scanIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
|
33
|
+
const isProcessingRef = useRef(false);
|
|
34
|
+
const apiCalledRef = useRef(false);
|
|
71
35
|
|
|
72
|
-
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!device) {
|
|
38
|
+
navigation.navigate('NoCameraFound' as never);
|
|
39
|
+
}
|
|
73
40
|
|
|
74
41
|
return () => {
|
|
75
|
-
|
|
76
|
-
|
|
42
|
+
// Cleanup on unmount
|
|
43
|
+
if (scanIntervalRef.current) {
|
|
44
|
+
clearInterval(scanIntervalRef.current);
|
|
45
|
+
}
|
|
77
46
|
};
|
|
78
|
-
|
|
79
|
-
}, []);
|
|
47
|
+
}, [device, navigation]);
|
|
80
48
|
|
|
81
|
-
//
|
|
82
|
-
const
|
|
83
|
-
|
|
49
|
+
// Normalize MRZ text - same as demo-testing-sdk
|
|
50
|
+
const normalizeMrz = (text: string) => {
|
|
51
|
+
return text
|
|
52
|
+
.replace(/[«]/g, '<')
|
|
53
|
+
.replace(/0/g, 'O')
|
|
54
|
+
.replace(/[^A-Z0-9<]/gi, '');
|
|
55
|
+
};
|
|
84
56
|
|
|
57
|
+
// Extract MRZ - improved to get only MRZ lines
|
|
58
|
+
const extractMRZ = async (imagePath: string): Promise<string | null> => {
|
|
85
59
|
try {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
const result = await recognizeText(frame);
|
|
60
|
+
const result = await TextRecognition.recognize(imagePath);
|
|
89
61
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
62
|
+
// Get all normalized lines
|
|
63
|
+
const allLines = result.blocks
|
|
64
|
+
.flatMap(block => block.lines)
|
|
65
|
+
.map(line => normalizeMrz(line.text))
|
|
66
|
+
.filter(text => text.length > 0);
|
|
67
|
+
|
|
68
|
+
// Filter only MRZ lines (30-44 characters, only A-Z, 0-9, <)
|
|
69
|
+
const mrzLines = allLines.filter(text => /^[A-Z0-9<]{30,44}$/.test(text));
|
|
70
|
+
|
|
71
|
+
console.log('All lines:', allLines.length);
|
|
72
|
+
console.log('MRZ lines found:', mrzLines.length);
|
|
73
|
+
mrzLines.forEach((line, idx) => {
|
|
74
|
+
console.log(`MRZ ${idx + 1}:`, line, `(${line.length} chars)`);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (mrzLines.length === 0) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Determine document type and extract correct MRZ
|
|
82
|
+
let finalMRZ = '';
|
|
83
|
+
|
|
84
|
+
// TD1: 3 lines of 30 characters (ID cards) - Total: 90 characters
|
|
85
|
+
if (mrzLines.length >= 3) {
|
|
86
|
+
const td1Lines = mrzLines.filter(l => l.length === 30);
|
|
87
|
+
if (td1Lines.length >= 3) {
|
|
88
|
+
const combined = td1Lines.slice(0, 3).join('');
|
|
89
|
+
// Check if total matches: 30 + 30 + 30 = 90
|
|
90
|
+
if (combined.length === 90) {
|
|
91
|
+
finalMRZ = combined;
|
|
92
|
+
console.log('TD1 detected (3x30):', finalMRZ.length, 'chars - MATCH!');
|
|
93
|
+
return finalMRZ;
|
|
94
|
+
}
|
|
100
95
|
}
|
|
101
96
|
}
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
|
|
98
|
+
// TD3: 2 lines of 44 characters (Passports) - Total: 88 characters
|
|
99
|
+
if (mrzLines.length >= 2) {
|
|
100
|
+
const td3Lines = mrzLines.filter(l => l.length === 44);
|
|
101
|
+
if (td3Lines.length >= 2) {
|
|
102
|
+
const combined = td3Lines.slice(0, 2).join('');
|
|
103
|
+
// Check if total matches: 44 + 44 = 88
|
|
104
|
+
if (combined.length === 88) {
|
|
105
|
+
finalMRZ = combined;
|
|
106
|
+
console.log('TD3 detected (2x44):', finalMRZ.length, 'chars - MATCH!');
|
|
107
|
+
return finalMRZ;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// TD2: 2 lines of 36 characters - Total: 72 characters
|
|
113
|
+
if (mrzLines.length >= 2) {
|
|
114
|
+
const td2Lines = mrzLines.filter(l => l.length === 36);
|
|
115
|
+
if (td2Lines.length >= 2) {
|
|
116
|
+
const combined = td2Lines.slice(0, 2).join('');
|
|
117
|
+
// Check if total matches: 36 + 36 = 72
|
|
118
|
+
if (combined.length === 72) {
|
|
119
|
+
finalMRZ = combined;
|
|
120
|
+
console.log('TD2 detected (2x36):', finalMRZ.length, 'chars - MATCH!');
|
|
121
|
+
return finalMRZ;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// If character count doesn't match expected totals, return null to continue scanning
|
|
127
|
+
console.log('MRZ lines found but character count does not match expected format');
|
|
128
|
+
return null;
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.warn('Text recognition failed:', err);
|
|
131
|
+
return null;
|
|
104
132
|
}
|
|
105
133
|
};
|
|
106
134
|
|
|
107
|
-
//
|
|
108
|
-
const
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
setScannedData(mrzCode);
|
|
135
|
+
// Take photo - same as demo-testing-sdk
|
|
136
|
+
const takePhoto = async () => {
|
|
137
|
+
if (!cameraRef.current || isProcessingRef.current || apiCalledRef.current) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
113
140
|
|
|
114
141
|
try {
|
|
115
|
-
|
|
142
|
+
isProcessingRef.current = true;
|
|
116
143
|
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
144
|
+
const photo = await cameraRef.current.takePhoto({
|
|
145
|
+
flash: 'off',
|
|
146
|
+
enableShutterSound: false,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const mrzData = await extractMRZ('file://' + photo.path);
|
|
150
|
+
|
|
151
|
+
if (mrzData) {
|
|
152
|
+
// Stop auto-scanning immediately
|
|
153
|
+
setAutoScan(false);
|
|
154
|
+
if (scanIntervalRef.current) {
|
|
155
|
+
clearInterval(scanIntervalRef.current);
|
|
156
|
+
scanIntervalRef.current = null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Mark that API is being called
|
|
160
|
+
apiCalledRef.current = true;
|
|
161
|
+
setIsCallingAPI(true);
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const requestData = {
|
|
165
|
+
code: mrzData,
|
|
166
|
+
token: idmConf.verificationCode ?? '',
|
|
167
|
+
engine: 'idmerit',
|
|
168
|
+
};
|
|
122
169
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
170
|
+
const response = await captureMRZ(idmConf, requestData);
|
|
171
|
+
|
|
172
|
+
console.log('API Response:', response);
|
|
173
|
+
|
|
174
|
+
// Check if response has a valid status field (string)
|
|
175
|
+
if (response && typeof response.status === 'string') {
|
|
176
|
+
console.log('Valid response with status:', response.status);
|
|
177
|
+
// Navigate to ThankYou only if we have a valid response
|
|
178
|
+
navigation.navigate('ThankYou' as never);
|
|
179
|
+
} else {
|
|
180
|
+
console.log('Invalid response - no status field, retrying...');
|
|
181
|
+
// Reset and allow retry if response is invalid
|
|
182
|
+
apiCalledRef.current = false;
|
|
183
|
+
setIsCallingAPI(false);
|
|
184
|
+
setAutoScan(true);
|
|
185
|
+
}
|
|
186
|
+
} catch (apiError) {
|
|
187
|
+
console.error('API call failed:', apiError);
|
|
188
|
+
|
|
189
|
+
// Reset on error to allow retry
|
|
190
|
+
apiCalledRef.current = false;
|
|
191
|
+
setIsCallingAPI(false);
|
|
192
|
+
setAutoScan(true);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log(mrzData);
|
|
146
196
|
}
|
|
147
|
-
} catch (
|
|
148
|
-
console.error('
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
text: 'Retry',
|
|
152
|
-
onPress: () => {
|
|
153
|
-
setIsScanning(true);
|
|
154
|
-
setScannedData(null);
|
|
155
|
-
setLoading(false);
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
]);
|
|
197
|
+
} catch (err: any) {
|
|
198
|
+
console.error('MRZ scan error:', err);
|
|
199
|
+
} finally {
|
|
200
|
+
isProcessingRef.current = false;
|
|
159
201
|
}
|
|
160
202
|
};
|
|
161
203
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return () => clearTimeout(timer);
|
|
173
|
-
};
|
|
204
|
+
// Auto-scan effect
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
if (device && autoScan && !isCallingAPI) {
|
|
207
|
+
// Start auto-scanning after 1 second delay
|
|
208
|
+
const startDelay = setTimeout(() => {
|
|
209
|
+
scanIntervalRef.current = setInterval(() => {
|
|
210
|
+
takePhoto();
|
|
211
|
+
}, 1000); // Scan every 1 second
|
|
212
|
+
}, 1000);
|
|
174
213
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return (
|
|
182
|
-
<Camera
|
|
183
|
-
ref={cameraRef}
|
|
184
|
-
style={StyleSheet.absoluteFill}
|
|
185
|
-
device={device}
|
|
186
|
-
isActive={isScanning}
|
|
187
|
-
frameProcessor={(frame) => {
|
|
188
|
-
'worklet';
|
|
189
|
-
if (isScanning) {
|
|
190
|
-
processMRZWithMLKit(frame);
|
|
191
|
-
}
|
|
192
|
-
}}
|
|
193
|
-
/>
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
} catch (error) {
|
|
197
|
-
console.warn('Camera render failed:', error);
|
|
214
|
+
return () => {
|
|
215
|
+
clearTimeout(startDelay);
|
|
216
|
+
if (scanIntervalRef.current) {
|
|
217
|
+
clearInterval(scanIntervalRef.current);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
198
220
|
}
|
|
221
|
+
}, [device, autoScan, isCallingAPI]);
|
|
199
222
|
|
|
200
|
-
|
|
223
|
+
// Show permission request screen
|
|
224
|
+
if (!hasPermission) {
|
|
201
225
|
return (
|
|
202
|
-
<
|
|
203
|
-
<
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
</Text>
|
|
213
|
-
{scannedData && (
|
|
214
|
-
<Text
|
|
215
|
-
style={{
|
|
216
|
-
color: '#0f0',
|
|
217
|
-
textAlign: 'center',
|
|
218
|
-
marginTop: 20,
|
|
219
|
-
fontSize: 10,
|
|
220
|
-
paddingHorizontal: 20,
|
|
221
|
-
}}
|
|
222
|
-
>
|
|
223
|
-
Detected MRZ
|
|
226
|
+
<ScrollView contentContainerStyle={permissionStyles.container}>
|
|
227
|
+
<Header />
|
|
228
|
+
|
|
229
|
+
<ThemedText style={permissionStyles.maintitle} type="title">
|
|
230
|
+
Camera Access Required
|
|
231
|
+
</ThemedText>
|
|
232
|
+
|
|
233
|
+
<View style={{ padding: 20 }}>
|
|
234
|
+
<Text style={{ color: theme.colors.text, textAlign: 'center', marginBottom: 20 }}>
|
|
235
|
+
This app needs access to your camera to scan MRZ. Please grant camera permission.
|
|
224
236
|
</Text>
|
|
225
|
-
|
|
226
|
-
|
|
237
|
+
</View>
|
|
238
|
+
|
|
239
|
+
<Button
|
|
240
|
+
title="Grant Permission"
|
|
241
|
+
style={permissionStyles.buttonplace}
|
|
242
|
+
textStyle={permissionStyles.button}
|
|
243
|
+
onPress={async () => {
|
|
244
|
+
await requestPermission();
|
|
245
|
+
}}
|
|
246
|
+
/>
|
|
247
|
+
</ScrollView>
|
|
227
248
|
);
|
|
228
|
-
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!device) return null;
|
|
229
252
|
|
|
230
253
|
return (
|
|
231
254
|
<View style={styles.container}>
|
|
232
255
|
<Text style={styles.topLabel}>Align the MRZ code in the box</Text>
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
top: '40%',
|
|
241
|
-
alignSelf: 'center',
|
|
242
|
-
width: 380,
|
|
243
|
-
height: 78,
|
|
244
|
-
borderWidth: 2,
|
|
245
|
-
borderColor: scannedData ? '#00ff00' : '#ffffff',
|
|
246
|
-
backgroundColor: 'transparent',
|
|
247
|
-
}}
|
|
256
|
+
|
|
257
|
+
<Camera
|
|
258
|
+
ref={cameraRef}
|
|
259
|
+
style={styles.camera}
|
|
260
|
+
device={device}
|
|
261
|
+
isActive={true}
|
|
262
|
+
photo
|
|
248
263
|
/>
|
|
249
|
-
|
|
250
|
-
|
|
264
|
+
|
|
265
|
+
<Image
|
|
266
|
+
source={overlayImage}
|
|
267
|
+
style={styles.overlay}
|
|
268
|
+
resizeMode="stretch"
|
|
269
|
+
/>
|
|
270
|
+
|
|
271
|
+
<Text style={styles.bottomLabel}>
|
|
272
|
+
{isCallingAPI ? 'Processing...' : 'Scanning...'}
|
|
273
|
+
</Text>
|
|
274
|
+
|
|
275
|
+
{isCallingAPI && <Loader />}
|
|
251
276
|
</View>
|
|
252
277
|
);
|
|
253
278
|
}
|
|
279
|
+
|
|
280
|
+
export default MrzCapture;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useNavigation } from '@react-navigation/native';
|
|
2
|
-
import React, { useState,
|
|
2
|
+
import React, { useState, useEffect } from 'react';
|
|
3
3
|
import {
|
|
4
4
|
Alert,
|
|
5
5
|
Pressable,
|
|
@@ -24,105 +24,92 @@ export default function SelectDocuments() {
|
|
|
24
24
|
const orientation = useOrientation();
|
|
25
25
|
const styles = getStyles(theme, orientation);
|
|
26
26
|
const navigation = useNavigation();
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
const [filteredCountries, setFilteredCountries] = useState<any[]>([]);
|
|
29
29
|
const [search, setSearch] = useState('');
|
|
30
30
|
const [selectedCountry, setSelectedCountry] = useState<string | null>(null);
|
|
31
31
|
const [dropdownVisible, setDropdownVisible] = useState(false);
|
|
32
32
|
const [selectedIdType, setSelectedIdType] = useState<string | null>(null);
|
|
33
33
|
const [idTypeDropdownVisible, setIdTypeDropdownVisible] = useState(false);
|
|
34
|
+
const [idCardTypes, setIdCardTypes] = useState<any[]>([]);
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
if (idmConf.countryDetails) {
|
|
38
|
-
setFilteredCountries(idmConf.countryDetails);
|
|
39
|
-
}
|
|
40
|
-
}, [idmConf.countryDetails]);
|
|
41
|
-
|
|
42
|
-
// Memoize ID card types based on selected country
|
|
43
|
-
const idCardTypes = useMemo(() => {
|
|
44
|
-
if (!selectedCountry || !idmConf.countryDetails) return [];
|
|
45
|
-
|
|
46
|
-
const country = idmConf.countryDetails.find((c) => String(c.value) === String(selectedCountry));
|
|
47
|
-
return country?.metadata.map((doc) => ({
|
|
48
|
-
label: getDocumentLabel(doc.type),
|
|
49
|
-
value: doc.type,
|
|
50
|
-
metadata: doc,
|
|
51
|
-
})) || [];
|
|
52
|
-
}, [selectedCountry, idmConf.countryDetails]);
|
|
53
|
-
|
|
54
|
-
// Memoize selected country label
|
|
55
|
-
const selectedCountryLabel = useMemo(() => {
|
|
56
|
-
if (!idmConf.countryDetails) return null;
|
|
57
|
-
return idmConf.countryDetails.find((c) => String(c.value) === String(selectedCountry))?.label;
|
|
58
|
-
}, [selectedCountry, idmConf.countryDetails]);
|
|
59
|
-
|
|
60
|
-
// Memoize selected ID type label
|
|
61
|
-
const selectedIdTypeLabel = useMemo(() => {
|
|
62
|
-
return idCardTypes.find((i) => i.value === selectedIdType)?.label;
|
|
63
|
-
}, [idCardTypes, selectedIdType]);
|
|
64
|
-
|
|
65
|
-
const toggleDropdown = useCallback(() => {
|
|
36
|
+
const toggleDropdown = () => {
|
|
66
37
|
setDropdownVisible((prev) => !prev);
|
|
67
38
|
setIdTypeDropdownVisible(false);
|
|
68
|
-
}
|
|
39
|
+
};
|
|
69
40
|
|
|
70
|
-
const toggleIdTypeDropdown =
|
|
41
|
+
const toggleIdTypeDropdown = () => {
|
|
71
42
|
setIdTypeDropdownVisible((prev) => !prev);
|
|
72
43
|
setDropdownVisible(false);
|
|
73
|
-
}
|
|
44
|
+
};
|
|
74
45
|
|
|
75
|
-
const selectCountry =
|
|
46
|
+
const selectCountry = (value: string) => {
|
|
76
47
|
setSelectedCountry(value);
|
|
77
48
|
setDropdownVisible(false);
|
|
78
49
|
setSearch('');
|
|
79
|
-
setSelectedIdType(null);
|
|
80
|
-
|
|
81
|
-
if (idmConf.countryDetails) {
|
|
82
|
-
setFilteredCountries(idmConf.countryDetails);
|
|
83
|
-
}
|
|
84
50
|
|
|
85
|
-
const selectedCountryData = idmConf.countryDetails?.find((c) =>
|
|
86
|
-
updateIDMConf({
|
|
87
|
-
|
|
51
|
+
const selectedCountryData = idmConf.countryDetails?.find((c: any) => c.value === value);
|
|
52
|
+
updateIDMConf({
|
|
53
|
+
selectedCountryDetails: selectedCountryData || null,
|
|
54
|
+
});
|
|
88
55
|
|
|
89
|
-
|
|
56
|
+
if (selectedCountryData?.metadata) {
|
|
57
|
+
const types = selectedCountryData.metadata.map((doc: any) => ({
|
|
58
|
+
label: getDocumentLabel(doc.type),
|
|
59
|
+
value: doc.type,
|
|
60
|
+
}));
|
|
61
|
+
setIdCardTypes(types);
|
|
62
|
+
} else {
|
|
63
|
+
setIdCardTypes([]);
|
|
64
|
+
}
|
|
65
|
+
setSelectedIdType(null);
|
|
66
|
+
setFilteredCountries(idmConf.countryDetails || []);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const selectIdType = (value: string) => {
|
|
90
70
|
setSelectedIdType(value);
|
|
91
|
-
|
|
92
|
-
const selectedCountryData = idmConf.countryDetails?.find((c) => String(c.value) === String(selectedCountry));
|
|
93
|
-
const selectedMetaData = selectedCountryData?.metadata?.find((c) => c.type === value);
|
|
94
|
-
|
|
95
71
|
updateIDMConf({
|
|
96
72
|
selectedCountryDetails: {
|
|
97
|
-
...
|
|
98
|
-
selectedMetaData,
|
|
73
|
+
...idmConf.selectedCountryDetails,
|
|
74
|
+
selectedMetaData: idmConf.selectedCountryDetails?.metadata?.find((c: any) => c.type === value),
|
|
99
75
|
},
|
|
100
76
|
});
|
|
101
77
|
setIdTypeDropdownVisible(false);
|
|
102
|
-
}
|
|
78
|
+
};
|
|
103
79
|
|
|
104
|
-
const handleContinue =
|
|
80
|
+
const handleContinue = () => {
|
|
105
81
|
if (!selectedCountry || !selectedIdType) {
|
|
106
82
|
Alert.alert('Missing Fields', 'Please select both country and ID card type.');
|
|
107
83
|
return;
|
|
108
84
|
}
|
|
109
|
-
navigation.navigate('FrontDocumentAdvice' as never);
|
|
110
|
-
}, [selectedCountry, selectedIdType, navigation]);
|
|
111
85
|
|
|
112
|
-
|
|
86
|
+
// Check document flow to determine initial navigation
|
|
87
|
+
const metaData = idmConf?.selectedCountryDetails?.selectedMetaData;
|
|
88
|
+
const flow = metaData?.document_flow;
|
|
89
|
+
|
|
90
|
+
if (flow && flow[0] === 'B') {
|
|
91
|
+
navigation.navigate('BackDocumentAdvice' as never);
|
|
92
|
+
} else {
|
|
93
|
+
navigation.navigate('FrontDocumentAdvice' as never);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
setFilteredCountries(idmConf.countryDetails || []);
|
|
99
|
+
}, [idmConf.countryDetails]);
|
|
100
|
+
|
|
101
|
+
const handleSearch = (text: string) => {
|
|
113
102
|
setSearch(text);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const filtered = idmConf.countryDetails.filter((c) =>
|
|
103
|
+
const filtered = (idmConf.countryDetails || []).filter((c: any) =>
|
|
117
104
|
c.label.toLowerCase().includes(text.toLowerCase())
|
|
118
105
|
);
|
|
119
106
|
setFilteredCountries(filtered);
|
|
120
|
-
}
|
|
107
|
+
};
|
|
121
108
|
|
|
122
|
-
const closeDropdowns =
|
|
109
|
+
const closeDropdowns = () => {
|
|
123
110
|
setDropdownVisible(false);
|
|
124
111
|
setIdTypeDropdownVisible(false);
|
|
125
|
-
}
|
|
112
|
+
};
|
|
126
113
|
|
|
127
114
|
return (
|
|
128
115
|
<View style={{ flex: 1, backgroundColor: theme.colors.background }}>
|
|
@@ -143,7 +130,9 @@ export default function SelectDocuments() {
|
|
|
143
130
|
color: selectedCountry ? theme.colors.text : theme.colors.subtitle,
|
|
144
131
|
}}
|
|
145
132
|
>
|
|
146
|
-
{
|
|
133
|
+
{selectedCountry
|
|
134
|
+
? idmConf.countryDetails?.find((c: any) => c.value === selectedCountry)?.label
|
|
135
|
+
: 'Select a country'}
|
|
147
136
|
</Text>
|
|
148
137
|
<Text style={styles.downArrow}>▼</Text>
|
|
149
138
|
</Pressable>
|
|
@@ -160,7 +149,7 @@ export default function SelectDocuments() {
|
|
|
160
149
|
{filteredCountries.map((item) => (
|
|
161
150
|
<Pressable
|
|
162
151
|
key={item.value}
|
|
163
|
-
onPress={() => selectCountry(
|
|
152
|
+
onPress={() => selectCountry(item.value)}
|
|
164
153
|
style={styles.dropdownItem}
|
|
165
154
|
>
|
|
166
155
|
<Text style={styles.dopDownLable}>{item.label}</Text>
|
|
@@ -183,7 +172,9 @@ export default function SelectDocuments() {
|
|
|
183
172
|
color: selectedIdType ? theme.colors.text : theme.colors.subtitle,
|
|
184
173
|
}}
|
|
185
174
|
>
|
|
186
|
-
{
|
|
175
|
+
{selectedIdType
|
|
176
|
+
? idCardTypes.find((i) => i.value === selectedIdType)?.label
|
|
177
|
+
: 'Select ID Card Type'}
|
|
187
178
|
</Text>
|
|
188
179
|
<Text style={styles.downArrow}>▼</Text>
|
|
189
180
|
</Pressable>
|