@test-web/react-native-sdk 1.0.1 → 2.0.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/README.md +824 -26
- package/assets/images/Chrome-logo.svg +1 -0
- package/assets/images/Firefox-logo.png +0 -0
- package/assets/images/IDM-logo.jpg +0 -0
- package/assets/images/MRZOverlay.png +0 -0
- package/assets/images/Safari-logo.png +0 -0
- package/assets/images/aadhar.png +0 -0
- package/assets/images/camera-bg.png +0 -0
- package/assets/images/card-overlay-back.png +0 -0
- package/assets/images/card-overlay.png +0 -0
- package/assets/images/card-scan-back-icon.jpg +0 -0
- package/assets/images/card-scan-front-icon.png +0 -0
- package/assets/images/card-scan-icon-aadhaar-1.png +0 -0
- package/assets/images/card-scan-icon-aadhaar-back.png +0 -0
- package/assets/images/card-scan-icon-aadhaar-scan-qr.png +0 -0
- package/assets/images/card-scan-icon-aadhaar.png +0 -0
- package/assets/images/card-scan-icon-can-pr.png +0 -0
- package/assets/images/card-scan-icon-default-back.png +0 -0
- package/assets/images/card-scan-icon-dl.png +0 -0
- package/assets/images/card-scan-icon-greencard-back.jpg +0 -0
- package/assets/images/card-scan-icon-greencard.jpg +0 -0
- package/assets/images/card-scan-icon-hc.png +0 -0
- package/assets/images/card-scan-icon-ni-argentina-back.jpg +0 -0
- package/assets/images/card-scan-icon-ni-argentina-old.png +0 -0
- package/assets/images/card-scan-icon-ni-argentina.jpg +0 -0
- package/assets/images/card-scan-icon-ni-barcode.jpg +0 -0
- package/assets/images/card-scan-icon-ni-brazil-back.jpg +0 -0
- package/assets/images/card-scan-icon-ni-brazil.jpg +0 -0
- package/assets/images/card-scan-icon-ni-dominican-republic-back.png +0 -0
- package/assets/images/card-scan-icon-ni-dominican-republic-front.png +0 -0
- package/assets/images/card-scan-icon-ni-dominican-republic-mrz.png +0 -0
- package/assets/images/card-scan-icon-ni-dominicanaRepublic-back.jpg +0 -0
- package/assets/images/card-scan-icon-ni-france-back.png +0 -0
- package/assets/images/card-scan-icon-ni-france-front.png +0 -0
- package/assets/images/card-scan-icon-ni-france-scan-mrz.png +0 -0
- package/assets/images/card-scan-icon-ni-germany-back.jpg +0 -0
- package/assets/images/card-scan-icon-ni-germany.jpg +0 -0
- package/assets/images/card-scan-icon-ni-paraguay-back.png +0 -0
- package/assets/images/card-scan-icon-ni-paraguay-front.png +0 -0
- package/assets/images/card-scan-icon-ni-paraguay-scan-mrz.png +0 -0
- package/assets/images/card-scan-icon-ni-uae-back.png +0 -0
- package/assets/images/card-scan-icon-ni-uae-front.png +0 -0
- package/assets/images/card-scan-icon-ni-uae-scan-mrz.png +0 -0
- package/assets/images/card-scan-icon-ni-uganda-front.png +0 -0
- package/assets/images/card-scan-icon-ni-uganda-scan-mrz.png +0 -0
- package/assets/images/card-scan-icon-ni-ukrain-back.png +0 -0
- package/assets/images/card-scan-icon-ni-ukrain-front.png +0 -0
- package/assets/images/card-scan-icon-ni-ukrain-scan-mrz.png +0 -0
- package/assets/images/card-scan-icon-ni.png +0 -0
- package/assets/images/card-scan-icon-old.jpg +0 -0
- package/assets/images/card-scan-icon-pan.png +0 -0
- package/assets/images/card-scan-icon-passport-card-back.jpg +0 -0
- package/assets/images/card-scan-icon-passport-card.jpg +0 -0
- package/assets/images/card-scan-icon-passport-old.png +0 -0
- package/assets/images/card-scan-icon-passport.png +0 -0
- package/assets/images/card-scan-icon-pr.png +0 -0
- package/assets/images/card-scan-icon.jpg +0 -0
- package/assets/images/check.png +0 -0
- package/assets/images/chrome-animation-GPS-permissions-setting.gif +0 -0
- package/assets/images/chrome-animation-camera-permissions-setting.gif +0 -0
- package/assets/images/denied.png +0 -0
- package/assets/images/dl.png +0 -0
- package/assets/images/driver-license.png +0 -0
- package/assets/images/firefox-animation-permissions-setting.gif +0 -0
- package/assets/images/flashlight_on.png +0 -0
- package/assets/images/gallery.png +0 -0
- package/assets/images/greencard.png +0 -0
- package/assets/images/header.jpg +0 -0
- package/assets/images/health-card.png +0 -0
- package/assets/images/ic_camera_front_white_36px.svg +4 -0
- package/assets/images/ic_camera_rear_white_36px.svg +4 -0
- package/assets/images/ic_fullscreen_exit_white_48px.svg +4 -0
- package/assets/images/ic_fullscreen_white_48px.svg +4 -0
- package/assets/images/ic_photo_camera_white_48px.svg +5 -0
- package/assets/images/id-card.png +0 -0
- package/assets/images/idcardimg.png +0 -0
- package/assets/images/idmval-barcode.png +0 -0
- package/assets/images/information.png +0 -0
- package/assets/images/loader.gif +0 -0
- package/assets/images/loading.svg +1 -0
- package/assets/images/logo.jpg +0 -0
- package/assets/images/logo.png +0 -0
- package/assets/images/mrz-back.png +0 -0
- package/assets/images/mrz-ni.png +0 -0
- package/assets/images/mrz.png +0 -0
- package/assets/images/mrz1.png +0 -0
- package/assets/images/mrz_old.png +0 -0
- package/assets/images/mrz_small.png +0 -0
- package/assets/images/national-id.png +0 -0
- package/assets/images/nationalID.png +0 -0
- package/assets/images/no-wifi.png +0 -0
- package/assets/images/passport-card.png +0 -0
- package/assets/images/passport.png +0 -0
- package/assets/images/permit-card.png +0 -0
- package/assets/images/photo-overlay.png +0 -0
- package/assets/images/placeholder.jpg +0 -0
- package/assets/images/qr-code.png +0 -0
- package/assets/images/right-checkmark.jpg +0 -0
- package/assets/images/selfie.jpg +0 -0
- package/assets/images/showing-sec.png +0 -0
- package/assets/images/spinner.gif +0 -0
- package/assets/images/splash-icon.png +0 -0
- package/assets/images/take-selfie.jpg +0 -0
- package/assets/images/torch_off.png +0 -0
- package/assets/images/warning-icon.jpg +0 -0
- package/assets/images/warning-stick.jpg +0 -0
- package/assets/images/wrong-checkmark.jpg +0 -0
- package/package.json +40 -4
- package/src/apis/index.ts +338 -17
- package/src/components/common/Loader.tsx +16 -2
- package/src/config/apiConfig.ts +6 -0
- package/src/index.tsx +123 -7
- package/src/screens/BarcodeCapture.tsx +154 -24
- package/src/screens/DocumentCaptureBack.tsx +133 -24
- package/src/screens/DocumentCaptureFront.tsx +146 -24
- package/src/screens/MrzCapture.tsx +205 -16
- package/src/screens/SelectDocuments.tsx +37 -56
- package/src/screens/SelfieCapture.tsx +114 -18
- package/src/screens/ThankYou.tsx +34 -1
- package/src/services/getUserData.ts +111 -0
- package/src/types/IDMConf.ts +81 -7
- package/src/utils/base64.ts +25 -0
- package/src/utils/flowManager.ts +138 -0
- package/src/utils/imageProcessor.ts +96 -0
- package/src/utils/index.ts +18 -0
|
@@ -1,30 +1,172 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { View, Text, StyleSheet } from 'react-native';
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { View, Text, StyleSheet, Alert } from 'react-native';
|
|
3
3
|
import { useNavigation } from '@react-navigation/native';
|
|
4
4
|
import { useTheme } from '../context/ThemeContext';
|
|
5
5
|
import { useOrientation } from '../hooks/useOrientation';
|
|
6
|
+
import { useIDM } from '../context/IDMConfigurationContext';
|
|
6
7
|
import getStyles from '../styles/ScannerStyles';
|
|
7
8
|
import Loader from '../components/common/Loader';
|
|
9
|
+
import { captureBarcode } from '../apis';
|
|
8
10
|
|
|
9
11
|
export default function BarcodeCapture() {
|
|
10
12
|
const { theme } = useTheme();
|
|
13
|
+
const { idmConf, setIDMConf } = useIDM();
|
|
11
14
|
const orientation = useOrientation();
|
|
12
15
|
const styles = getStyles(theme, orientation);
|
|
13
16
|
const navigation = useNavigation();
|
|
14
17
|
const [loading, setLoading] = useState(false);
|
|
18
|
+
const [scannedData, setScannedData] = useState<string | null>(null);
|
|
19
|
+
const [isScanning, setIsScanning] = useState(true);
|
|
20
|
+
const scannerRef = useRef<any>(null);
|
|
15
21
|
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
// Initialize Dynamsoft Barcode Scanner
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
let isMounted = true;
|
|
25
|
+
|
|
26
|
+
const initializeScanner = async () => {
|
|
27
|
+
try {
|
|
28
|
+
// Try to import Dynamsoft Capture Vision
|
|
29
|
+
const { DCVCameraView, DBRRuntimeSettings } = require('dynamsoft-capture-vision-react-native');
|
|
30
|
+
|
|
31
|
+
// License initialization would go here
|
|
32
|
+
// await DCVBarcodeReader.initLicense('YOUR_LICENSE_KEY');
|
|
33
|
+
|
|
34
|
+
console.log('Dynamsoft Barcode Scanner initialized');
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.warn('Dynamsoft not available, using fallback:', error);
|
|
37
|
+
// Fallback to simulation if Dynamsoft not installed
|
|
38
|
+
if (isMounted) {
|
|
39
|
+
simulateBarcodeScan();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
initializeScanner();
|
|
45
|
+
|
|
46
|
+
return () => {
|
|
47
|
+
isMounted = false;
|
|
48
|
+
setIsScanning(false);
|
|
49
|
+
};
|
|
50
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
// Handle barcode scan result
|
|
54
|
+
const handleBarcodeScanned = async (barcodeText: string) => {
|
|
55
|
+
if (loading || !barcodeText || !isScanning) return;
|
|
56
|
+
|
|
57
|
+
setIsScanning(false);
|
|
58
|
+
setScannedData(barcodeText);
|
|
59
|
+
|
|
60
|
+
try {
|
|
19
61
|
setLoading(true);
|
|
20
|
-
|
|
21
|
-
|
|
62
|
+
|
|
63
|
+
const barcodeData = {
|
|
64
|
+
token: String(idmConf.verificationCode || ''),
|
|
65
|
+
text: barcodeText,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Call API to submit barcode
|
|
69
|
+
const result = await captureBarcode(idmConf, barcodeData);
|
|
70
|
+
|
|
71
|
+
if (result.status) {
|
|
72
|
+
// Update configuration with response
|
|
73
|
+
setIDMConf({
|
|
74
|
+
...idmConf,
|
|
75
|
+
requestConfiguration: { ...idmConf.requestConfiguration, ...result },
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Navigate to ThankYou
|
|
22
79
|
navigation.navigate('ThankYou' as never);
|
|
23
|
-
}
|
|
80
|
+
} else {
|
|
81
|
+
Alert.alert('Error', result.errorMessage || 'Failed to process barcode', [
|
|
82
|
+
{
|
|
83
|
+
text: 'Retry',
|
|
84
|
+
onPress: () => {
|
|
85
|
+
setIsScanning(true);
|
|
86
|
+
setScannedData(null);
|
|
87
|
+
setLoading(false);
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
]);
|
|
91
|
+
}
|
|
92
|
+
} catch (error: any) {
|
|
93
|
+
console.error('Error processing barcode:', error);
|
|
94
|
+
Alert.alert('Error', 'Failed to process barcode', [
|
|
95
|
+
{
|
|
96
|
+
text: 'Retry',
|
|
97
|
+
onPress: () => {
|
|
98
|
+
setIsScanning(true);
|
|
99
|
+
setScannedData(null);
|
|
100
|
+
setLoading(false);
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Fallback simulation if Dynamsoft not available
|
|
108
|
+
const simulateBarcodeScan = () => {
|
|
109
|
+
const timer = setTimeout(() => {
|
|
110
|
+
if (isScanning) {
|
|
111
|
+
const simulatedBarcode = 'SIMULATED_PDF417_' + Date.now();
|
|
112
|
+
handleBarcodeScanned(simulatedBarcode);
|
|
113
|
+
}
|
|
24
114
|
}, 3000);
|
|
25
115
|
|
|
26
116
|
return () => clearTimeout(timer);
|
|
27
|
-
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Render Dynamsoft Camera View
|
|
120
|
+
const renderBarcodeScanner = () => {
|
|
121
|
+
try {
|
|
122
|
+
const { DCVCameraView } = require('dynamsoft-capture-vision-react-native');
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<DCVCameraView
|
|
126
|
+
ref={scannerRef}
|
|
127
|
+
style={StyleSheet.absoluteFill}
|
|
128
|
+
scanRegionVisible={true}
|
|
129
|
+
overlayVisible={true}
|
|
130
|
+
onBarcodeScanned={(results: any) => {
|
|
131
|
+
if (results && results.length > 0 && isScanning) {
|
|
132
|
+
const barcodeText = results[0].barcodeText || results[0].text;
|
|
133
|
+
handleBarcodeScanned(barcodeText);
|
|
134
|
+
}
|
|
135
|
+
}}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.warn('Dynamsoft camera view not available:', error);
|
|
140
|
+
|
|
141
|
+
// Fallback UI
|
|
142
|
+
return (
|
|
143
|
+
<View style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}>
|
|
144
|
+
<Text
|
|
145
|
+
style={{
|
|
146
|
+
color: '#fff',
|
|
147
|
+
textAlign: 'center',
|
|
148
|
+
marginTop: 200,
|
|
149
|
+
fontSize: 16,
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
{loading ? 'Processing...' : 'Scanning for barcode...'}
|
|
153
|
+
</Text>
|
|
154
|
+
{scannedData && (
|
|
155
|
+
<Text
|
|
156
|
+
style={{
|
|
157
|
+
color: '#0f0',
|
|
158
|
+
textAlign: 'center',
|
|
159
|
+
marginTop: 20,
|
|
160
|
+
fontSize: 12,
|
|
161
|
+
}}
|
|
162
|
+
>
|
|
163
|
+
Detected: {scannedData.substring(0, 30)}...
|
|
164
|
+
</Text>
|
|
165
|
+
)}
|
|
166
|
+
</View>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
28
170
|
|
|
29
171
|
return (
|
|
30
172
|
<View style={styles.container}>
|
|
@@ -32,19 +174,7 @@ export default function BarcodeCapture() {
|
|
|
32
174
|
Fill the box with the barcode{'\n'}until it turns green
|
|
33
175
|
</Text>
|
|
34
176
|
|
|
35
|
-
{
|
|
36
|
-
<View style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}>
|
|
37
|
-
<Text
|
|
38
|
-
style={{
|
|
39
|
-
color: '#fff',
|
|
40
|
-
textAlign: 'center',
|
|
41
|
-
marginTop: 200,
|
|
42
|
-
fontSize: 16,
|
|
43
|
-
}}
|
|
44
|
-
>
|
|
45
|
-
Barcode Scanner (Static)
|
|
46
|
-
</Text>
|
|
47
|
-
</View>
|
|
177
|
+
{renderBarcodeScanner()}
|
|
48
178
|
|
|
49
179
|
{/* Barcode overlay box */}
|
|
50
180
|
<View
|
|
@@ -55,12 +185,12 @@ export default function BarcodeCapture() {
|
|
|
55
185
|
width: 350,
|
|
56
186
|
height: 100,
|
|
57
187
|
borderWidth: 2,
|
|
58
|
-
borderColor: '#00ff00',
|
|
188
|
+
borderColor: scannedData ? '#00ff00' : '#ffffff',
|
|
59
189
|
backgroundColor: 'transparent',
|
|
60
190
|
}}
|
|
61
191
|
/>
|
|
62
192
|
|
|
63
|
-
{loading && <Loader />}
|
|
193
|
+
{loading && <Loader message="Processing barcode..." />}
|
|
64
194
|
</View>
|
|
65
195
|
);
|
|
66
196
|
}
|
|
@@ -1,54 +1,163 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet, Image, Alert } from 'react-native';
|
|
3
3
|
import { useNavigation } from '@react-navigation/native';
|
|
4
4
|
import getStyles from '../styles/DocumentCaptureStyles';
|
|
5
5
|
import { useTheme } from '../context/ThemeContext';
|
|
6
6
|
import { useOrientation } from '../hooks/useOrientation';
|
|
7
|
+
import { useIDM } from '../context/IDMConfigurationContext';
|
|
7
8
|
import Loader from '../components/common/Loader';
|
|
9
|
+
import { captureDocumentBack } from '../apis';
|
|
10
|
+
import { processImageToBase64, validateImagePath } from '../utils/imageProcessor';
|
|
11
|
+
import { createFlowManager } from '../utils/flowManager';
|
|
8
12
|
|
|
9
13
|
export default function DocumentCaptureBack() {
|
|
10
14
|
const { theme } = useTheme();
|
|
15
|
+
const { idmConf, setIDMConf } = useIDM();
|
|
11
16
|
const orientation = useOrientation();
|
|
12
17
|
const styles = getStyles(theme, orientation);
|
|
13
18
|
const navigation = useNavigation();
|
|
14
19
|
const [loading, setLoading] = useState(false);
|
|
20
|
+
const [hasPermission, setHasPermission] = useState(false);
|
|
21
|
+
const [device, setDevice] = useState<any>(null);
|
|
22
|
+
const cameraRef = useRef<any>(null);
|
|
23
|
+
|
|
24
|
+
// Initialize camera
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const initializeCamera = async () => {
|
|
27
|
+
try {
|
|
28
|
+
const { Camera, useCameraDevice, useCameraPermission } = require('react-native-vision-camera');
|
|
29
|
+
|
|
30
|
+
const { hasPermission: hasPerm, requestPermission } = useCameraPermission();
|
|
31
|
+
|
|
32
|
+
if (!hasPerm) {
|
|
33
|
+
const granted = await requestPermission();
|
|
34
|
+
if (!granted) {
|
|
35
|
+
navigation.navigate('CameraPermission' as never);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setHasPermission(true);
|
|
41
|
+
|
|
42
|
+
const backDevice = useCameraDevice('back');
|
|
43
|
+
if (!backDevice) {
|
|
44
|
+
navigation.navigate('NoCameraFound' as never);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setDevice(backDevice);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.warn('Camera initialization failed:', error);
|
|
51
|
+
setHasPermission(true);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
initializeCamera();
|
|
56
|
+
}, [navigation]);
|
|
57
|
+
|
|
58
|
+
// Determine next navigation step based on metadata
|
|
59
|
+
const handleNextStep = () => {
|
|
60
|
+
const metaData = idmConf?.selectedCountryDetails?.selectedMetaData;
|
|
61
|
+
|
|
62
|
+
if (!metaData) {
|
|
63
|
+
navigation.navigate('ThankYou' as never);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const flowManager = createFlowManager(metaData);
|
|
68
|
+
const nextScreen = flowManager.getNextScreenAfterBack();
|
|
69
|
+
navigation.navigate(nextScreen as never);
|
|
70
|
+
};
|
|
15
71
|
|
|
16
72
|
const takePhoto = async () => {
|
|
73
|
+
if (!cameraRef.current) {
|
|
74
|
+
Alert.alert('Camera Not Available', 'Camera is not available in this environment.');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
17
78
|
try {
|
|
18
79
|
setLoading(true);
|
|
19
|
-
|
|
20
|
-
|
|
80
|
+
|
|
81
|
+
const photo = await cameraRef.current.takePhoto();
|
|
82
|
+
|
|
83
|
+
if (!validateImagePath(photo?.path)) {
|
|
84
|
+
Alert.alert('Error', 'Failed to capture image');
|
|
21
85
|
setLoading(false);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Process image to base64
|
|
90
|
+
const base64Image = await processImageToBase64(photo.path);
|
|
91
|
+
|
|
92
|
+
// Prepare photo data for API
|
|
93
|
+
const photoData = {
|
|
94
|
+
token: String(idmConf.verificationCode || ''),
|
|
95
|
+
file: base64Image,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Call API to capture document back
|
|
99
|
+
const result = await captureDocumentBack(idmConf, photoData);
|
|
100
|
+
|
|
101
|
+
if (result.status) {
|
|
102
|
+
// Update configuration with response
|
|
103
|
+
setIDMConf({
|
|
104
|
+
...idmConf,
|
|
105
|
+
requestConfiguration: { ...idmConf.requestConfiguration, ...result },
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Navigate to next step based on metadata
|
|
109
|
+
handleNextStep();
|
|
110
|
+
} else {
|
|
111
|
+
navigation.navigate('RetakeSelfie' as never, {
|
|
112
|
+
errorMessage: result.errorMessage || 'Failed to process document',
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
} catch (error: any) {
|
|
26
116
|
console.error('Error taking photo:', error);
|
|
117
|
+
Alert.alert('Error', 'Failed to capture or process the image.');
|
|
118
|
+
} finally {
|
|
27
119
|
setLoading(false);
|
|
28
120
|
}
|
|
29
121
|
};
|
|
30
122
|
|
|
123
|
+
const renderCamera = () => {
|
|
124
|
+
try {
|
|
125
|
+
const { Camera } = require('react-native-vision-camera');
|
|
126
|
+
|
|
127
|
+
if (hasPermission && device) {
|
|
128
|
+
return (
|
|
129
|
+
<Camera
|
|
130
|
+
ref={cameraRef}
|
|
131
|
+
style={StyleSheet.absoluteFill}
|
|
132
|
+
device={device}
|
|
133
|
+
isActive={true}
|
|
134
|
+
photo={true}
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.warn('Camera render failed:', error);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<View style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}>
|
|
144
|
+
<Text style={{ color: '#fff', textAlign: 'center', marginTop: 100 }}>
|
|
145
|
+
Camera Preview
|
|
146
|
+
</Text>
|
|
147
|
+
</View>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
|
|
31
151
|
return (
|
|
32
152
|
<View style={styles.container}>
|
|
33
153
|
<Text style={styles.topLabel}>Document Back Side</Text>
|
|
34
154
|
|
|
35
|
-
{
|
|
36
|
-
<View style={StyleSheet.absoluteFill}>
|
|
37
|
-
<View
|
|
38
|
-
style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}
|
|
39
|
-
></View>
|
|
40
|
-
</View>
|
|
155
|
+
{renderCamera()}
|
|
41
156
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
style={
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
borderWidth: 2,
|
|
48
|
-
borderColor: '#fff',
|
|
49
|
-
backgroundColor: 'transparent',
|
|
50
|
-
},
|
|
51
|
-
]}
|
|
157
|
+
<Image
|
|
158
|
+
source={require('../../assets/images/card-overlay-back.png')}
|
|
159
|
+
style={styles.cardoverlay}
|
|
160
|
+
resizeMode="cover"
|
|
52
161
|
/>
|
|
53
162
|
|
|
54
163
|
<Text style={styles.bottomLabel}>
|
|
@@ -1,54 +1,176 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet, Image, Alert } from 'react-native';
|
|
3
3
|
import { useNavigation } from '@react-navigation/native';
|
|
4
4
|
import getStyles from '../styles/DocumentCaptureStyles';
|
|
5
5
|
import { useTheme } from '../context/ThemeContext';
|
|
6
6
|
import { useOrientation } from '../hooks/useOrientation';
|
|
7
|
+
import { useIDM } from '../context/IDMConfigurationContext';
|
|
7
8
|
import Loader from '../components/common/Loader';
|
|
9
|
+
import { captureDocumentFront } from '../apis';
|
|
10
|
+
import { processImageToBase64, validateImagePath } from '../utils/imageProcessor';
|
|
11
|
+
import { createFlowManager } from '../utils/flowManager';
|
|
8
12
|
|
|
9
13
|
export default function DocumentCaptureFront() {
|
|
10
14
|
const { theme } = useTheme();
|
|
15
|
+
const { idmConf, setIDMConf } = useIDM();
|
|
11
16
|
const orientation = useOrientation();
|
|
12
17
|
const styles = getStyles(theme, orientation);
|
|
13
18
|
const navigation = useNavigation();
|
|
14
19
|
const [loading, setLoading] = useState(false);
|
|
20
|
+
const [hasPermission, setHasPermission] = useState(false);
|
|
21
|
+
const [device, setDevice] = useState<any>(null);
|
|
22
|
+
const cameraRef = useRef<any>(null);
|
|
23
|
+
|
|
24
|
+
// Initialize camera
|
|
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]);
|
|
61
|
+
|
|
62
|
+
// Determine next navigation step based on metadata
|
|
63
|
+
const handleNextStep = () => {
|
|
64
|
+
const metaData = idmConf?.selectedCountryDetails?.selectedMetaData;
|
|
65
|
+
|
|
66
|
+
if (!metaData) {
|
|
67
|
+
navigation.navigate('ThankYou' as never);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const flowManager = createFlowManager(metaData);
|
|
72
|
+
const nextScreen = flowManager.getNextScreenAfterFront();
|
|
73
|
+
navigation.navigate(nextScreen as never);
|
|
74
|
+
};
|
|
15
75
|
|
|
16
76
|
const takePhoto = async () => {
|
|
77
|
+
if (!cameraRef.current) {
|
|
78
|
+
// Fallback for testing without camera
|
|
79
|
+
Alert.alert('Camera Not Available', 'Camera is not available in this environment.');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
17
83
|
try {
|
|
18
84
|
setLoading(true);
|
|
19
|
-
|
|
20
|
-
|
|
85
|
+
|
|
86
|
+
const photo = await cameraRef.current.takePhoto();
|
|
87
|
+
|
|
88
|
+
if (!validateImagePath(photo?.path)) {
|
|
89
|
+
Alert.alert('Error', 'Failed to capture image');
|
|
21
90
|
setLoading(false);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Process image to base64
|
|
95
|
+
const base64Image = await processImageToBase64(photo.path);
|
|
96
|
+
|
|
97
|
+
// Prepare photo data for API
|
|
98
|
+
const photoData = {
|
|
99
|
+
latitude: String(idmConf.userDetails?.location?.coords?.latitude || ''),
|
|
100
|
+
longitude: String(idmConf.userDetails?.location?.coords?.longitude || ''),
|
|
101
|
+
token: String(idmConf.verificationCode || ''),
|
|
102
|
+
persistLoc: 'false',
|
|
103
|
+
metadataIndex: String(idmConf.selectedCountryDetails?.selectedMetaData?.id || ''),
|
|
104
|
+
file: base64Image,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Call API to capture document front
|
|
108
|
+
const result = await captureDocumentFront(idmConf, photoData);
|
|
109
|
+
|
|
110
|
+
if (result.status) {
|
|
111
|
+
// Update configuration with response
|
|
112
|
+
setIDMConf({
|
|
113
|
+
...idmConf,
|
|
114
|
+
requestConfiguration: result,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Navigate to next step based on metadata
|
|
118
|
+
handleNextStep();
|
|
119
|
+
} else {
|
|
120
|
+
// Handle error - navigate to retake screen
|
|
121
|
+
navigation.navigate('RetakeSelfie' as never, {
|
|
122
|
+
errorMessage: result.errorMessage || 'Failed to process document',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
} catch (error: any) {
|
|
26
126
|
console.error('Error taking photo:', error);
|
|
127
|
+
Alert.alert('Error', 'Failed to capture or process the image.');
|
|
128
|
+
} finally {
|
|
27
129
|
setLoading(false);
|
|
28
130
|
}
|
|
29
131
|
};
|
|
30
132
|
|
|
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
|
+
|
|
31
163
|
return (
|
|
32
164
|
<View style={styles.container}>
|
|
33
165
|
<Text style={styles.topLabel}>Document Front Side</Text>
|
|
34
166
|
|
|
35
|
-
{
|
|
36
|
-
<View style={StyleSheet.absoluteFill}>
|
|
37
|
-
<View
|
|
38
|
-
style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}
|
|
39
|
-
></View>
|
|
40
|
-
</View>
|
|
167
|
+
{renderCamera()}
|
|
41
168
|
|
|
42
|
-
{/* Card overlay
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
borderWidth: 2,
|
|
48
|
-
borderColor: '#fff',
|
|
49
|
-
backgroundColor: 'transparent',
|
|
50
|
-
},
|
|
51
|
-
]}
|
|
169
|
+
{/* Card overlay */}
|
|
170
|
+
<Image
|
|
171
|
+
source={require('../../assets/images/card-overlay.png')}
|
|
172
|
+
style={styles.cardoverlay}
|
|
173
|
+
resizeMode="cover"
|
|
52
174
|
/>
|
|
53
175
|
|
|
54
176
|
<Text style={styles.bottomLabel}>
|