@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,184 +1,217 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { View, Text, TouchableOpacity, StyleSheet, Image, Alert } from 'react-native';
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet, Image, Alert, ScrollView } from 'react-native';
|
|
3
3
|
import { useNavigation } from '@react-navigation/native';
|
|
4
|
-
import
|
|
4
|
+
import { Camera, useCameraDevice, useCameraPermission } from 'react-native-vision-camera';
|
|
5
|
+
import ImageResizer from 'react-native-image-resizer';
|
|
6
|
+
import RNFS from 'react-native-fs';
|
|
7
|
+
|
|
8
|
+
import getStyles from '../styles/DocumentCaptureFrontStyle';
|
|
5
9
|
import { useTheme } from '../context/ThemeContext';
|
|
6
|
-
import { useOrientation } from '../hooks/useOrientation';
|
|
7
10
|
import { useIDM } from '../context/IDMConfigurationContext';
|
|
8
|
-
import
|
|
11
|
+
import { useOrientation } from '../hooks/useOrientation';
|
|
9
12
|
import { captureDocumentFront } from '../apis';
|
|
10
|
-
import
|
|
11
|
-
import
|
|
13
|
+
import Loader from '../components/common/Loader';
|
|
14
|
+
import Header from '../components/common/Header';
|
|
15
|
+
import Button from '../components/ui/Button';
|
|
16
|
+
import ThemedText from '../components/ui/ThemedText';
|
|
17
|
+
import getPermissionStyles from '../styles/PermissionStyle';
|
|
18
|
+
import { useFailedCount } from '../context/FailedCountContext';
|
|
12
19
|
|
|
13
20
|
export default function DocumentCaptureFront() {
|
|
14
21
|
const { theme } = useTheme();
|
|
15
22
|
const { idmConf, setIDMConf } = useIDM();
|
|
16
23
|
const orientation = useOrientation();
|
|
17
24
|
const styles = getStyles(theme, orientation);
|
|
25
|
+
const permissionStyles = getPermissionStyles(theme, orientation);
|
|
26
|
+
const { hasPermission, requestPermission } = useCameraPermission();
|
|
27
|
+
const device = useCameraDevice('back');
|
|
28
|
+
const cameraRef = useRef<Camera>(null);
|
|
18
29
|
const navigation = useNavigation();
|
|
19
|
-
const
|
|
20
|
-
const [hasPermission, setHasPermission] = useState(false);
|
|
21
|
-
const [device, setDevice] = useState<any>(null);
|
|
22
|
-
const cameraRef = useRef<any>(null);
|
|
30
|
+
const { failedCount, setFailedCount } = useFailedCount();
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
const initializeCamera = async () => {
|
|
27
|
-
try {
|
|
28
|
-
// Try to import camera libraries
|
|
29
|
-
const { Camera, useCameraDevice, useCameraPermission } = require('react-native-vision-camera');
|
|
30
|
-
|
|
31
|
-
// Check permission
|
|
32
|
-
const { hasPermission: hasPerm, requestPermission } = useCameraPermission();
|
|
33
|
-
|
|
34
|
-
if (!hasPerm) {
|
|
35
|
-
const granted = await requestPermission();
|
|
36
|
-
if (!granted) {
|
|
37
|
-
navigation.navigate('CameraPermission' as never);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
setHasPermission(true);
|
|
43
|
-
|
|
44
|
-
// Get back camera device
|
|
45
|
-
const backDevice = useCameraDevice('back');
|
|
46
|
-
if (!backDevice) {
|
|
47
|
-
navigation.navigate('NoCameraFound' as never);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
setDevice(backDevice);
|
|
52
|
-
} catch (error) {
|
|
53
|
-
console.warn('Camera initialization failed:', error);
|
|
54
|
-
// Fallback for testing without camera
|
|
55
|
-
setHasPermission(true);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
initializeCamera();
|
|
60
|
-
}, [navigation]);
|
|
32
|
+
const [loading, setLoading] = useState(false);
|
|
61
33
|
|
|
62
|
-
//
|
|
34
|
+
// Decide next navigation step
|
|
63
35
|
const handleNextStep = () => {
|
|
36
|
+
setFailedCount(0); // Reset failed count on successful capture
|
|
64
37
|
const metaData = idmConf?.selectedCountryDetails?.selectedMetaData;
|
|
65
|
-
|
|
66
38
|
if (!metaData) {
|
|
67
39
|
navigation.navigate('ThankYou' as never);
|
|
68
40
|
return;
|
|
69
41
|
}
|
|
70
42
|
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
43
|
+
const { document_flow, barcode } = metaData;
|
|
44
|
+
|
|
45
|
+
// If no flow, just go to ThankYou
|
|
46
|
+
if (!document_flow || document_flow.length === 0) {
|
|
47
|
+
navigation.navigate('ThankYou' as never);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Decide based on first element in flow
|
|
52
|
+
const currentStep = document_flow[0];
|
|
53
|
+
|
|
54
|
+
if (currentStep === 'F') {
|
|
55
|
+
if (document_flow.includes('B')) {
|
|
56
|
+
navigation.navigate('BackDocumentAdvice' as never);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// If no more flow, handle barcode
|
|
62
|
+
if (barcode === 'PDF417 B' || barcode === 'PDF417F') {
|
|
63
|
+
navigation.navigate('BarcodeAdvice' as never);
|
|
64
|
+
} else if (['TD3', 'TD2', 'TD1'].includes(barcode)) {
|
|
65
|
+
navigation.navigate('MrzAdvice' as never);
|
|
66
|
+
} else {
|
|
67
|
+
navigation.navigate('ThankYou' as never);
|
|
68
|
+
}
|
|
74
69
|
};
|
|
75
70
|
|
|
71
|
+
// Check for device availability
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!device) {
|
|
74
|
+
navigation.navigate('NoCameraFound' as never);
|
|
75
|
+
}
|
|
76
|
+
}, [device, navigation]);
|
|
77
|
+
|
|
78
|
+
// Show permission request screen if camera permission is not granted
|
|
79
|
+
if (!hasPermission) {
|
|
80
|
+
return (
|
|
81
|
+
<ScrollView contentContainerStyle={permissionStyles.container}>
|
|
82
|
+
<Header />
|
|
83
|
+
|
|
84
|
+
<ThemedText style={permissionStyles.maintitle} type="title">
|
|
85
|
+
Camera Access Required
|
|
86
|
+
</ThemedText>
|
|
87
|
+
|
|
88
|
+
<View style={{ padding: 20 }}>
|
|
89
|
+
<Text style={{ color: theme.colors.text, textAlign: 'center', marginBottom: 20 }}>
|
|
90
|
+
This app needs access to your camera to capture documents. Please grant camera permission.
|
|
91
|
+
</Text>
|
|
92
|
+
</View>
|
|
93
|
+
|
|
94
|
+
<Button
|
|
95
|
+
title="Grant Permission"
|
|
96
|
+
style={permissionStyles.buttonplace}
|
|
97
|
+
textStyle={permissionStyles.button}
|
|
98
|
+
onPress={async () => {
|
|
99
|
+
const granted = await requestPermission();
|
|
100
|
+
if (!granted) {
|
|
101
|
+
console.log('Camera permission denied');
|
|
102
|
+
}
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
</ScrollView>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!device) return null;
|
|
110
|
+
|
|
111
|
+
// Take photo and process
|
|
76
112
|
const takePhoto = async () => {
|
|
77
|
-
if (!cameraRef.current) {
|
|
78
|
-
|
|
79
|
-
|
|
113
|
+
if (!cameraRef.current || loading) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Validate required data before taking photo
|
|
118
|
+
if (!idmConf?.selectedCountryDetails?.selectedMetaData) {
|
|
119
|
+
Alert.alert('Error', 'Please select a document type before capturing.');
|
|
80
120
|
return;
|
|
81
121
|
}
|
|
82
122
|
|
|
83
123
|
try {
|
|
84
124
|
setLoading(true);
|
|
85
|
-
|
|
86
125
|
const photo = await cameraRef.current.takePhoto();
|
|
87
126
|
|
|
88
|
-
if (!
|
|
89
|
-
|
|
90
|
-
setLoading(false);
|
|
127
|
+
if (!photo?.path) {
|
|
128
|
+
console.error('No photo path available');
|
|
91
129
|
return;
|
|
92
130
|
}
|
|
93
131
|
|
|
94
|
-
|
|
95
|
-
|
|
132
|
+
let base64Image;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const resizedImage = await ImageResizer.createResizedImage(
|
|
136
|
+
`file://${photo.path}`,
|
|
137
|
+
810,
|
|
138
|
+
1080,
|
|
139
|
+
'JPEG',
|
|
140
|
+
70,
|
|
141
|
+
0,
|
|
142
|
+
undefined,
|
|
143
|
+
false
|
|
144
|
+
);
|
|
145
|
+
base64Image = await RNFS.readFile(resizedImage.uri, 'base64');
|
|
146
|
+
} catch (resizeError: any) {
|
|
147
|
+
console.warn('Image resizing failed, using original image:', resizeError.message);
|
|
148
|
+
base64Image = await RNFS.readFile(photo.path, 'base64');
|
|
149
|
+
}
|
|
96
150
|
|
|
97
|
-
// Prepare photo data for API
|
|
98
151
|
const photoData = {
|
|
99
|
-
latitude: String(idmConf
|
|
100
|
-
longitude: String(idmConf
|
|
101
|
-
token: String(idmConf
|
|
102
|
-
persistLoc:
|
|
103
|
-
metadataIndex: String(idmConf
|
|
152
|
+
latitude: String(idmConf?.userDetails?.location?.coords?.latitude || ''),
|
|
153
|
+
longitude: String(idmConf?.userDetails?.location?.coords?.longitude || ''),
|
|
154
|
+
token: String(idmConf?.verificationCode || ''),
|
|
155
|
+
persistLoc: String(false),
|
|
156
|
+
metadataIndex: String(idmConf?.selectedCountryDetails?.selectedMetaData?.id || ''),
|
|
104
157
|
file: base64Image,
|
|
105
158
|
};
|
|
106
159
|
|
|
107
|
-
// Call API to capture document front
|
|
108
160
|
const result = await captureDocumentFront(idmConf, photoData);
|
|
109
161
|
|
|
110
|
-
if (result.status) {
|
|
111
|
-
// Update configuration with response
|
|
162
|
+
if (typeof result.status === 'string') {
|
|
112
163
|
setIDMConf({
|
|
113
164
|
...idmConf,
|
|
114
165
|
requestConfiguration: result,
|
|
115
166
|
});
|
|
116
|
-
|
|
117
|
-
// Navigate to next step based on metadata
|
|
118
167
|
handleNextStep();
|
|
119
168
|
} else {
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
169
|
+
// Failed document capture
|
|
170
|
+
setFailedCount((prev) => prev + 1);
|
|
171
|
+
if (failedCount >= 4) {
|
|
172
|
+
// After 5 failed attempts (0-4), reset and proceed to ThankYou
|
|
173
|
+
setFailedCount(0);
|
|
174
|
+
navigation.navigate('ThankYou' as never);
|
|
175
|
+
} else {
|
|
176
|
+
(navigation.navigate as any)('RetakeSelfie', { errorMessage: result.errorMessage });
|
|
177
|
+
}
|
|
124
178
|
}
|
|
125
179
|
} catch (error: any) {
|
|
126
|
-
console.error('Error taking photo:', error);
|
|
180
|
+
console.error('Error taking photo:', error.message);
|
|
127
181
|
Alert.alert('Error', 'Failed to capture or process the image.');
|
|
128
182
|
} finally {
|
|
129
183
|
setLoading(false);
|
|
130
184
|
}
|
|
131
185
|
};
|
|
132
186
|
|
|
133
|
-
// Render camera if available
|
|
134
|
-
const renderCamera = () => {
|
|
135
|
-
try {
|
|
136
|
-
const { Camera } = require('react-native-vision-camera');
|
|
137
|
-
|
|
138
|
-
if (hasPermission && device) {
|
|
139
|
-
return (
|
|
140
|
-
<Camera
|
|
141
|
-
ref={cameraRef}
|
|
142
|
-
style={StyleSheet.absoluteFill}
|
|
143
|
-
device={device}
|
|
144
|
-
isActive={true}
|
|
145
|
-
photo={true}
|
|
146
|
-
/>
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
} catch (error) {
|
|
150
|
-
console.warn('Camera render failed:', error);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Fallback placeholder
|
|
154
|
-
return (
|
|
155
|
-
<View style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}>
|
|
156
|
-
<Text style={{ color: '#fff', textAlign: 'center', marginTop: 100 }}>
|
|
157
|
-
Camera Preview
|
|
158
|
-
</Text>
|
|
159
|
-
</View>
|
|
160
|
-
);
|
|
161
|
-
};
|
|
162
|
-
|
|
163
187
|
return (
|
|
164
188
|
<View style={styles.container}>
|
|
165
189
|
<Text style={styles.topLabel}>Document Front Side</Text>
|
|
166
190
|
|
|
167
|
-
{
|
|
191
|
+
{device && (
|
|
192
|
+
<Camera
|
|
193
|
+
ref={cameraRef}
|
|
194
|
+
style={StyleSheet.absoluteFill}
|
|
195
|
+
device={device}
|
|
196
|
+
isActive={true}
|
|
197
|
+
photo={true}
|
|
198
|
+
/>
|
|
199
|
+
)}
|
|
168
200
|
|
|
169
|
-
{/* Card overlay */}
|
|
170
201
|
<Image
|
|
171
202
|
source={require('../../assets/images/card-overlay.png')}
|
|
172
203
|
style={styles.cardoverlay}
|
|
173
204
|
resizeMode="cover"
|
|
174
205
|
/>
|
|
175
206
|
|
|
176
|
-
<Text style={styles.bottomLabel}>
|
|
177
|
-
Position your document fully within the frame
|
|
178
|
-
</Text>
|
|
207
|
+
<Text style={styles.bottomLabel}>Position your document fully within the frame</Text>
|
|
179
208
|
|
|
180
209
|
<View style={styles.buttonplace}>
|
|
181
|
-
<TouchableOpacity
|
|
210
|
+
<TouchableOpacity
|
|
211
|
+
style={styles.captureButton}
|
|
212
|
+
onPress={takePhoto}
|
|
213
|
+
disabled={loading}
|
|
214
|
+
/>
|
|
182
215
|
</View>
|
|
183
216
|
|
|
184
217
|
{loading && <Loader />}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { useNavigation } from '@react-navigation/native';
|
|
2
|
-
import { Image, ScrollView
|
|
2
|
+
import { Image, ScrollView } from 'react-native';
|
|
3
3
|
import Button from '../components/ui/Button';
|
|
4
4
|
import ThemedText from '../components/ui/ThemedText';
|
|
5
5
|
import getStyles from '../styles/DocumentAdviceStyles';
|
|
6
6
|
import { useTheme } from '../context/ThemeContext';
|
|
7
7
|
import { useOrientation } from '../hooks/useOrientation';
|
|
8
|
+
import { useIDM } from '../context/IDMConfigurationContext';
|
|
8
9
|
import { useMemo } from 'react';
|
|
10
|
+
import { getCardScanImage } from '../utils/imagesHelper';
|
|
9
11
|
|
|
10
12
|
export default function FrontDocumentAdvice() {
|
|
11
13
|
const { theme } = useTheme();
|
|
14
|
+
const { idmConf } = useIDM();
|
|
12
15
|
const orientation = useOrientation();
|
|
13
16
|
const navigation = useNavigation();
|
|
14
17
|
|
|
15
|
-
const styles = useMemo(
|
|
16
|
-
() => getStyles(theme, orientation),
|
|
17
|
-
[theme, orientation]
|
|
18
|
-
);
|
|
18
|
+
const styles = useMemo(() => getStyles(theme, orientation), [theme, orientation]);
|
|
19
19
|
|
|
20
20
|
const handleRedirect = () => {
|
|
21
21
|
navigation.navigate('DocumentCaptureFront' as never);
|
|
@@ -27,19 +27,14 @@ export default function FrontDocumentAdvice() {
|
|
|
27
27
|
Capture Your ID Front
|
|
28
28
|
</ThemedText>
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
]}
|
|
40
|
-
>
|
|
41
|
-
<ThemedText>ID Front Image</ThemedText>
|
|
42
|
-
</View>
|
|
30
|
+
<Image
|
|
31
|
+
source={getCardScanImage(
|
|
32
|
+
idmConf?.selectedCountryDetails?.selectedMetaData?.type,
|
|
33
|
+
idmConf?.selectedCountryDetails?.selectedMetaData?.countryCode
|
|
34
|
+
)}
|
|
35
|
+
style={styles.idcard}
|
|
36
|
+
resizeMode="contain"
|
|
37
|
+
/>
|
|
43
38
|
|
|
44
39
|
<Button
|
|
45
40
|
title="Take a photo"
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { useNavigation } from '@react-navigation/native';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ScrollView,
|
|
5
|
+
Text,
|
|
6
|
+
View,
|
|
7
|
+
PermissionsAndroid,
|
|
8
|
+
Platform,
|
|
9
|
+
Linking,
|
|
10
|
+
Alert
|
|
11
|
+
} from 'react-native';
|
|
4
12
|
import Button from '../components/ui/Button';
|
|
5
13
|
import ThemedText from '../components/ui/ThemedText';
|
|
6
14
|
import { useTheme } from '../context/ThemeContext';
|
|
@@ -12,35 +20,133 @@ export default function LocationPermission() {
|
|
|
12
20
|
const orientation = useOrientation();
|
|
13
21
|
const styles = getStyles(theme, orientation);
|
|
14
22
|
const navigation = useNavigation();
|
|
23
|
+
const [isRequesting, setIsRequesting] = React.useState(false);
|
|
24
|
+
|
|
25
|
+
const navigateNext = () => {
|
|
26
|
+
if (navigation.canGoBack()) {
|
|
27
|
+
navigation.goBack();
|
|
28
|
+
} else {
|
|
29
|
+
navigation.navigate('VerifyIdentity' as never);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
15
32
|
|
|
16
33
|
const requestLocationPermission = async () => {
|
|
17
|
-
if (
|
|
18
|
-
|
|
34
|
+
if (isRequesting) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
setIsRequesting(true);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
if (Platform.OS === 'android') {
|
|
42
|
+
// Check if permission is already granted
|
|
43
|
+
const hasPermission = await PermissionsAndroid.check(
|
|
44
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (hasPermission) {
|
|
48
|
+
console.log('Location permission already granted');
|
|
49
|
+
navigateNext();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Request permission
|
|
19
54
|
const granted = await PermissionsAndroid.request(
|
|
20
55
|
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
|
|
21
56
|
{
|
|
22
57
|
title: 'Location Permission',
|
|
23
|
-
message: 'This app needs access to your location.',
|
|
24
|
-
buttonPositive: '
|
|
58
|
+
message: 'This app needs access to your location to verify your identity.',
|
|
59
|
+
buttonPositive: 'Allow',
|
|
60
|
+
buttonNegative: 'Deny',
|
|
25
61
|
}
|
|
26
62
|
);
|
|
63
|
+
|
|
27
64
|
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
65
|
+
console.log('Location permission granted');
|
|
66
|
+
// Try to get location using Geolocation service
|
|
67
|
+
try {
|
|
68
|
+
const Geolocation = require('react-native-geolocation-service');
|
|
69
|
+
if (Geolocation && typeof Geolocation.getCurrentPosition === 'function') {
|
|
70
|
+
Geolocation.getCurrentPosition(
|
|
71
|
+
(position: any) => {
|
|
72
|
+
console.log('Location obtained:', position);
|
|
73
|
+
navigateNext();
|
|
74
|
+
},
|
|
75
|
+
(error: any) => {
|
|
76
|
+
console.warn('Location error:', error);
|
|
77
|
+
navigateNext();
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
enableHighAccuracy: true,
|
|
81
|
+
timeout: 15000,
|
|
82
|
+
maximumAge: 10000,
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
} else {
|
|
86
|
+
navigateNext();
|
|
87
|
+
}
|
|
88
|
+
} catch (geoError) {
|
|
89
|
+
console.warn('Geolocation service error:', geoError);
|
|
90
|
+
navigateNext();
|
|
32
91
|
}
|
|
92
|
+
} else if (granted === PermissionsAndroid.RESULTS.DENIED) {
|
|
93
|
+
console.log('Location permission denied');
|
|
94
|
+
Alert.alert(
|
|
95
|
+
'Permission Denied',
|
|
96
|
+
'Location permission is required to continue. Please grant permission.',
|
|
97
|
+
[{ text: 'OK' }]
|
|
98
|
+
);
|
|
99
|
+
} else if (granted === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
|
|
100
|
+
console.log('Location permission denied permanently');
|
|
101
|
+
Alert.alert(
|
|
102
|
+
'Permission Required',
|
|
103
|
+
'Location permission is required. Please enable it in app settings.',
|
|
104
|
+
[
|
|
105
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
106
|
+
{
|
|
107
|
+
text: 'Open Settings',
|
|
108
|
+
onPress: () => Linking.openSettings(),
|
|
109
|
+
},
|
|
110
|
+
]
|
|
111
|
+
);
|
|
33
112
|
}
|
|
34
|
-
} catch (err) {
|
|
35
|
-
console.warn(err);
|
|
36
|
-
}
|
|
37
|
-
} else {
|
|
38
|
-
// For iOS, navigate directly (implement iOS permission separately)
|
|
39
|
-
if (navigation.canGoBack()) {
|
|
40
|
-
navigation.goBack();
|
|
41
113
|
} else {
|
|
42
|
-
|
|
114
|
+
// For iOS, request location permission through Geolocation
|
|
115
|
+
try {
|
|
116
|
+
const Geolocation = require('react-native-geolocation-service');
|
|
117
|
+
if (Geolocation && typeof Geolocation.requestAuthorization === 'function') {
|
|
118
|
+
const auth = await Geolocation.requestAuthorization('whenInUse');
|
|
119
|
+
if (auth === 'granted') {
|
|
120
|
+
console.log('iOS location permission granted');
|
|
121
|
+
navigateNext();
|
|
122
|
+
} else {
|
|
123
|
+
console.log('iOS location permission denied:', auth);
|
|
124
|
+
Alert.alert(
|
|
125
|
+
'Permission Required',
|
|
126
|
+
'Location permission is required. Please enable it in app settings.',
|
|
127
|
+
[
|
|
128
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
129
|
+
{
|
|
130
|
+
text: 'Open Settings',
|
|
131
|
+
onPress: () => Linking.openSettings(),
|
|
132
|
+
},
|
|
133
|
+
]
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
// Fallback for iOS
|
|
138
|
+
navigateNext();
|
|
139
|
+
}
|
|
140
|
+
} catch (err) {
|
|
141
|
+
console.warn('iOS location permission error:', err);
|
|
142
|
+
Alert.alert('Error', 'Failed to request location permission. Please try again.');
|
|
143
|
+
}
|
|
43
144
|
}
|
|
145
|
+
} catch (err: any) {
|
|
146
|
+
console.error('Permission request error:', err);
|
|
147
|
+
Alert.alert('Error', 'Failed to request location permission. Please try again.');
|
|
148
|
+
} finally {
|
|
149
|
+
setIsRequesting(false);
|
|
44
150
|
}
|
|
45
151
|
};
|
|
46
152
|
|
|
@@ -68,6 +174,7 @@ export default function LocationPermission() {
|
|
|
68
174
|
style={styles.buttonplace}
|
|
69
175
|
textStyle={styles.button}
|
|
70
176
|
onPress={requestLocationPermission}
|
|
177
|
+
disabled={isRequesting}
|
|
71
178
|
/>
|
|
72
179
|
</ScrollView>
|
|
73
180
|
);
|
|
@@ -1,22 +1,36 @@
|
|
|
1
1
|
import { useNavigation } from '@react-navigation/native';
|
|
2
|
-
import { ScrollView, View } from 'react-native';
|
|
2
|
+
import { ScrollView, View, Image } from 'react-native';
|
|
3
3
|
import Button from '../components/ui/Button';
|
|
4
4
|
import ThemedText from '../components/ui/ThemedText';
|
|
5
5
|
import getStyles from '../styles/BarcodeAdviceStyles';
|
|
6
6
|
import { useTheme } from '../context/ThemeContext';
|
|
7
7
|
import { useOrientation } from '../hooks/useOrientation';
|
|
8
|
+
import { useIDM } from '../context/IDMConfigurationContext';
|
|
9
|
+
import { getMRZImage } from '../utils/imagesHelper';
|
|
8
10
|
import { useMemo } from 'react';
|
|
9
11
|
|
|
10
12
|
export default function MrzAdvice() {
|
|
11
13
|
const { theme } = useTheme();
|
|
12
14
|
const orientation = useOrientation();
|
|
13
15
|
const navigation = useNavigation();
|
|
16
|
+
const { idmConf } = useIDM();
|
|
14
17
|
|
|
15
18
|
const styles = useMemo(
|
|
16
19
|
() => getStyles(theme, orientation),
|
|
17
20
|
[theme, orientation]
|
|
18
21
|
);
|
|
19
22
|
|
|
23
|
+
const mrzImage = useMemo(() => {
|
|
24
|
+
const metadata = idmConf?.selectedCountryDetails?.selectedMetaData;
|
|
25
|
+
if (!metadata) return null;
|
|
26
|
+
|
|
27
|
+
return getMRZImage(
|
|
28
|
+
metadata.type || '',
|
|
29
|
+
metadata.countryCode || '',
|
|
30
|
+
metadata.barcode || ''
|
|
31
|
+
);
|
|
32
|
+
}, [idmConf]);
|
|
33
|
+
|
|
20
34
|
const handleRedirect = () => {
|
|
21
35
|
navigation.navigate('MrzCapture' as never);
|
|
22
36
|
};
|
|
@@ -27,20 +41,20 @@ export default function MrzAdvice() {
|
|
|
27
41
|
Scan the MRZ of your ID
|
|
28
42
|
</ThemedText>
|
|
29
43
|
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
]}
|
|
40
|
-
>
|
|
41
|
-
<ThemedText>MRZ Zone Image</ThemedText>
|
|
44
|
+
<View style={styles.idcard}>
|
|
45
|
+
{mrzImage && (
|
|
46
|
+
<Image
|
|
47
|
+
source={mrzImage}
|
|
48
|
+
style={{ width: '100%', height: '100%' }}
|
|
49
|
+
resizeMode="contain"
|
|
50
|
+
/>
|
|
51
|
+
)}
|
|
42
52
|
</View>
|
|
43
53
|
|
|
54
|
+
<ThemedText style={[styles.maintitle, { fontSize: 14, marginTop: 20 }]}>
|
|
55
|
+
Position the MRZ zone (bottom lines) within the frame
|
|
56
|
+
</ThemedText>
|
|
57
|
+
|
|
44
58
|
<Button
|
|
45
59
|
title="Scan"
|
|
46
60
|
style={styles.buttonplace}
|