@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.
Files changed (78) hide show
  1. package/android/build/.transforms/246c075ea944392e66db7aa639265547/results.bin +1 -0
  2. package/android/build/.transforms/246c075ea944392e66db7aa639265547/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/yourcompany/sdk/BuildConfig.dex +0 -0
  3. package/android/build/.transforms/246c075ea944392e66db7aa639265547/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/yourcompany/sdk/YourSDKModule.dex +0 -0
  4. package/android/build/.transforms/246c075ea944392e66db7aa639265547/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/yourcompany/sdk/YourSDKPackage.dex +0 -0
  5. package/android/build/.transforms/246c075ea944392e66db7aa639265547/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +0 -0
  6. package/android/build/.transforms/952af5a0e7b5b2ac3d48ad66eccefd1f/results.bin +1 -0
  7. package/android/build/.transforms/952af5a0e7b5b2ac3d48ad66eccefd1f/transformed/classes/classes_dex/classes.dex +0 -0
  8. package/android/build/generated/source/buildConfig/debug/com/yourcompany/sdk/BuildConfig.java +10 -0
  9. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +34 -0
  10. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +18 -0
  11. package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +6 -0
  12. package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +1 -0
  13. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  14. package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
  15. package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
  16. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -0
  17. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
  18. package/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +2 -0
  19. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
  20. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
  21. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/yourcompany/sdk/BuildConfig.class +0 -0
  22. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/yourcompany/sdk/YourSDKModule.class +0 -0
  23. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/yourcompany/sdk/YourSDKPackage.class +0 -0
  24. package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +2 -0
  25. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +56 -0
  26. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +34 -0
  27. package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +1 -0
  28. package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +1 -0
  29. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/yourcompany/sdk/BuildConfig.class +0 -0
  30. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/yourcompany/sdk/YourSDKModule.class +0 -0
  31. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/yourcompany/sdk/YourSDKPackage.class +0 -0
  32. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  33. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +1 -0
  34. package/android/build/outputs/logs/manifest-merger-debug-report.txt +61 -0
  35. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  36. package/package.json +1 -1
  37. package/src/apis/captureBarcode.ts +37 -0
  38. package/src/apis/captureMRZ.ts +39 -0
  39. package/src/apis/checkLiveness.ts +48 -0
  40. package/src/apis/index.ts +26 -75
  41. package/src/components/common/Footer.tsx +19 -22
  42. package/src/components/common/Header.tsx +22 -21
  43. package/src/components/common/Loader.tsx +9 -28
  44. package/src/components/ui/Button.tsx +23 -31
  45. package/src/components/ui/ThemedText.tsx +21 -32
  46. package/src/context/FailedCountContext.tsx +35 -0
  47. package/src/context/IDMConfigurationContext.tsx +12 -2
  48. package/src/context/themes.ts +20 -0
  49. package/src/index.tsx +41 -29
  50. package/src/screens/BackDocumentAdvice.tsx +13 -18
  51. package/src/screens/BarcodeAdvice.tsx +39 -19
  52. package/src/screens/BarcodeCapture.tsx +127 -158
  53. package/src/screens/DocumentCaptureBack.tsx +145 -102
  54. package/src/screens/DocumentCaptureFront.tsx +146 -113
  55. package/src/screens/FrontDocumentAdvice.tsx +13 -18
  56. package/src/screens/LocationPermission.tsx +124 -17
  57. package/src/screens/MrzAdvice.tsx +27 -13
  58. package/src/screens/MrzCapture.tsx +233 -206
  59. package/src/screens/SelectDocuments.tsx +57 -66
  60. package/src/screens/SelfieAdvice.tsx +2 -3
  61. package/src/screens/SelfieCapture.tsx +135 -103
  62. package/src/screens/ThankYou.tsx +25 -31
  63. package/src/screens/VerifyIdentity.tsx +1 -0
  64. package/src/styles/BarcodeAdviceStyles.ts +6 -6
  65. package/src/styles/DocumentCaptureBackStyle.ts +70 -0
  66. package/src/styles/DocumentCaptureFrontStyle.ts +70 -0
  67. package/src/styles/FooterStyles.ts +27 -0
  68. package/src/styles/HeaderStyles.ts +20 -0
  69. package/src/styles/LoaderStyles.ts +14 -0
  70. package/src/styles/ScannerStyles.ts +46 -9
  71. package/src/styles/ThankYouStyles.ts +8 -11
  72. package/src/types/index.ts +8 -0
  73. package/src/utils/MRZ_README.md +111 -0
  74. package/src/utils/imagesHelper.ts +264 -0
  75. package/src/utils/metadata_new.json +11373 -1
  76. package/src/utils/mrzExamples.ts +127 -0
  77. package/src/utils/mrzParser.ts +303 -0
  78. package/src/utils/mrzUtils.ts +70 -0
@@ -1,168 +1,147 @@
1
- import React, { useState, useEffect, useRef } from 'react';
2
- import { View, Text, StyleSheet, Alert } from 'react-native';
3
- import { useNavigation } from '@react-navigation/native';
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { View, Text } from 'react-native';
3
+ import getStyles from '../styles/ScannerStyles';
4
4
  import { useTheme } from '../context/ThemeContext';
5
5
  import { useOrientation } from '../hooks/useOrientation';
6
- import { useIDM } from '../context/IDMConfigurationContext';
7
- import getStyles from '../styles/ScannerStyles';
8
6
  import Loader from '../components/common/Loader';
9
7
  import { captureBarcode } from '../apis';
8
+ import { useIDM } from '../context/IDMConfigurationContext';
9
+ import { useNavigation } from '@react-navigation/native';
10
+ import CustomOverlay from '../components/common/CustomOverlay';
10
11
 
11
- export default function BarcodeCapture() {
12
+ // Note: This component requires dynamsoft-capture-vision-react-native
13
+ // Install with: npm install dynamsoft-capture-vision-react-native
14
+
15
+ const BarcodeCapture: React.FC = () => {
12
16
  const { theme } = useTheme();
13
- const { idmConf, setIDMConf } = useIDM();
14
17
  const orientation = useOrientation();
15
- const styles = getStyles(theme, orientation);
18
+ const { idmConf } = useIDM();
16
19
  const navigation = useNavigation();
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);
20
+ const styles = getStyles(theme, orientation);
21
+ const cameraViewRef = useRef<any>(null);
22
+
23
+ const [isScanningEnabled, setIsScanningEnabled] = useState(true);
24
+ const [isLoading, setIsLoading] = useState(false);
21
25
 
22
- // Initialize Dynamsoft Barcode Scanner
23
26
  useEffect(() => {
24
- let isMounted = true;
27
+ let cleanupFn: (() => void) | undefined;
25
28
 
26
- const initializeScanner = async () => {
29
+ const initialize = async () => {
27
30
  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();
31
+ // Import Dynamsoft modules
32
+ const {
33
+ CameraEnhancer,
34
+ CameraView,
35
+ CaptureVisionRouter,
36
+ EnumPresetTemplate,
37
+ EnumBarcodeFormat,
38
+ LicenseManager,
39
+ } = require('dynamsoft-capture-vision-react-native');
40
+
41
+ await LicenseManager.initLicense(
42
+ 'DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9'
43
+ );
44
+ await CameraEnhancer.requestCameraPermission();
45
+
46
+ const camera = await CameraEnhancer.getInstance();
47
+ const router = await CaptureVisionRouter.getInstance();
48
+
49
+ if (cameraViewRef.current) {
50
+ await camera.setCameraView(cameraViewRef.current);
40
51
  }
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 {
61
- setLoading(true);
62
-
63
- const barcodeData = {
64
- token: String(idmConf.verificationCode || ''),
65
- text: barcodeText,
66
- };
67
52
 
68
- // Call API to submit barcode
69
- const result = await captureBarcode(idmConf, barcodeData);
53
+ await router.setInput(camera);
70
54
 
71
- if (result.status) {
72
- // Update configuration with response
73
- setIDMConf({
74
- ...idmConf,
75
- requestConfiguration: { ...idmConf.requestConfiguration, ...result },
76
- });
77
-
78
- // Navigate to ThankYou
79
- navigation.navigate('ThankYou' as never);
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
- },
55
+ const settings = {
56
+ timeout: 1000,
57
+ barcodeSettings: {
58
+ expectedBarcodesCount: 1,
59
+ barcodeFormats: Number(EnumBarcodeFormat.BF_MICRO_PDF417),
89
60
  },
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);
61
+ };
62
+
63
+ await router.updateSettings(
64
+ settings,
65
+ EnumPresetTemplate.PT_READ_SINGLE_BARCODE
66
+ );
67
+
68
+ const receiver = router.addResultReceiver({
69
+ onDecodedBarcodesReceived: async (result: any) => {
70
+ const firstBarcode = result.items?.[0];
71
+ if (firstBarcode?.text) {
72
+ setIsScanningEnabled(false);
73
+ setIsLoading(true);
74
+
75
+ const requestData = {
76
+ text: firstBarcode.text,
77
+ token: idmConf.verificationCode ?? '',
78
+ };
79
+
80
+ try {
81
+ const barcodeScanResp = await captureBarcode(idmConf, requestData);
82
+ console.log('barcodeScanResp', barcodeScanResp);
83
+
84
+ // Check if verification was already processed (409 status)
85
+ if (barcodeScanResp.status === 409) {
86
+ console.log('Verification already processed, proceeding to ThankYou');
87
+ }
88
+
89
+ navigation.navigate('ThankYou' as never);
90
+ } catch (error) {
91
+ console.error('Barcode capture error:', error);
92
+ setIsScanningEnabled(true);
93
+ } finally {
94
+ setIsLoading(false);
95
+ }
96
+ }
101
97
  },
102
- },
103
- ]);
104
- }
105
- };
98
+ });
106
99
 
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);
100
+ await camera.open();
101
+ await router.startCapturing(EnumPresetTemplate.PT_READ_SINGLE_BARCODE);
102
+
103
+ cleanupFn = () => {
104
+ router.removeResultReceiver(receiver);
105
+ router.stopCapturing();
106
+ camera.close();
107
+ };
108
+ } catch (err) {
109
+ console.error('Dynamsoft initialization error:', err);
110
+ console.warn(
111
+ 'Barcode scanning requires dynamsoft-capture-vision-react-native library'
112
+ );
113
113
  }
114
- }, 3000);
114
+ };
115
115
 
116
- return () => clearTimeout(timer);
117
- };
116
+ initialize();
118
117
 
119
- // Render Dynamsoft Camera View
120
- const renderBarcodeScanner = () => {
121
- try {
122
- const { DCVCameraView } = require('dynamsoft-capture-vision-react-native');
118
+ return () => {
119
+ if (cleanupFn) cleanupFn();
120
+ };
121
+ }, [isScanningEnabled, idmConf, navigation]);
123
122
 
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
- );
123
+ // Render Dynamsoft CameraView or fallback
124
+ const renderCameraView = () => {
125
+ try {
126
+ const { CameraView } = require('dynamsoft-capture-vision-react-native');
127
+ return <CameraView ref={cameraViewRef} style={styles.camera} />;
138
128
  } catch (error) {
139
- console.warn('Dynamsoft camera view not available:', error);
140
-
141
- // Fallback UI
142
129
  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...'}
130
+ <View
131
+ style={[
132
+ styles.camera,
133
+ {
134
+ backgroundColor: '#000',
135
+ justifyContent: 'center',
136
+ alignItems: 'center',
137
+ },
138
+ ]}
139
+ >
140
+ <Text style={{ color: '#fff', textAlign: 'center', padding: 20 }}>
141
+ Barcode scanning requires{'\n'}
142
+ dynamsoft-capture-vision-react-native{'\n\n'}
143
+ Install with:{'\n'}npm install dynamsoft-capture-vision-react-native
153
144
  </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
145
  </View>
167
146
  );
168
147
  }
@@ -174,23 +153,13 @@ export default function BarcodeCapture() {
174
153
  Fill the box with the barcode{'\n'}until it turns green
175
154
  </Text>
176
155
 
177
- {renderBarcodeScanner()}
178
-
179
- {/* Barcode overlay box */}
180
- <View
181
- style={{
182
- position: 'absolute',
183
- top: '40%',
184
- alignSelf: 'center',
185
- width: 350,
186
- height: 100,
187
- borderWidth: 2,
188
- borderColor: scannedData ? '#00ff00' : '#ffffff',
189
- backgroundColor: 'transparent',
190
- }}
191
- />
192
-
193
- {loading && <Loader message="Processing barcode..." />}
156
+ {renderCameraView()}
157
+
158
+ <CustomOverlay holeWidth={350} holeHeight={100} />
159
+
160
+ {isLoading && <Loader />}
194
161
  </View>
195
162
  );
196
- }
163
+ };
164
+
165
+ export default BarcodeCapture;
@@ -1,158 +1,199 @@
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 getStyles from '../styles/DocumentCaptureStyles';
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/DocumentCaptureBackStyle';
5
9
  import { useTheme } from '../context/ThemeContext';
6
- import { useOrientation } from '../hooks/useOrientation';
7
10
  import { useIDM } from '../context/IDMConfigurationContext';
8
- import Loader from '../components/common/Loader';
11
+ import { useOrientation } from '../hooks/useOrientation';
9
12
  import { captureDocumentBack } from '../apis';
10
- import { processImageToBase64, validateImagePath } from '../utils/imageProcessor';
11
- import { createFlowManager } from '../utils/flowManager';
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 DocumentCaptureBack() {
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 [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
- };
30
+ const { failedCount, setFailedCount } = useFailedCount();
54
31
 
55
- initializeCamera();
56
- }, [navigation]);
32
+ const [loading, setLoading] = useState(false);
57
33
 
58
- // Determine next navigation step based on metadata
34
+ // Decide next navigation step
59
35
  const handleNextStep = () => {
36
+ setFailedCount(0); // Reset failed count on successful capture
60
37
  const metaData = idmConf?.selectedCountryDetails?.selectedMetaData;
61
-
62
38
  if (!metaData) {
63
39
  navigation.navigate('ThankYou' as never);
64
40
  return;
65
41
  }
66
42
 
67
- const flowManager = createFlowManager(metaData);
68
- const nextScreen = flowManager.getNextScreenAfterBack();
69
- navigation.navigate(nextScreen as never);
43
+ const { document_flow, barcode } = metaData;
44
+
45
+ if (!document_flow || document_flow.length === 0) {
46
+ // Check if barcode scan is needed even without document flow
47
+ if (barcode === 'PDF417 B' || barcode === 'PDF417F') {
48
+ navigation.navigate('BarcodeAdvice' as never);
49
+ } else if (['TD3', 'TD2', 'TD1'].includes(barcode)) {
50
+ navigation.navigate('MrzAdvice' as never);
51
+ } else {
52
+ navigation.navigate('ThankYou' as never);
53
+ }
54
+ return;
55
+ }
56
+
57
+ // Decide based on first element in flow
58
+ const currentStep = document_flow[0];
59
+
60
+ if (currentStep === 'B') {
61
+ if (document_flow.includes('F')) {
62
+ navigation.navigate('FrontDocumentAdvice' as never);
63
+ return;
64
+ }
65
+ }
66
+
67
+ // If no more flow, handle barcode
68
+ if (barcode === 'PDF417 B' || barcode === 'PDF417F') {
69
+ navigation.navigate('BarcodeAdvice' as never);
70
+ } else if (['TD3', 'TD2', 'TD1'].includes(barcode)) {
71
+ navigation.navigate('MrzAdvice' as never);
72
+ } else {
73
+ navigation.navigate('ThankYou' as never);
74
+ }
70
75
  };
71
76
 
77
+ // Check for device availability
78
+ useEffect(() => {
79
+ if (!device) {
80
+ navigation.navigate('NoCameraFound' as never);
81
+ }
82
+ }, [device, navigation]);
83
+
84
+ // Show permission request screen if camera permission is not granted
85
+ if (!hasPermission) {
86
+ return (
87
+ <ScrollView contentContainerStyle={permissionStyles.container}>
88
+ <Header />
89
+
90
+ <ThemedText style={permissionStyles.maintitle} type="title">
91
+ Camera Access Required
92
+ </ThemedText>
93
+
94
+ <View style={{ padding: 20 }}>
95
+ <Text style={{ color: theme.colors.text, textAlign: 'center', marginBottom: 20 }}>
96
+ This app needs access to your camera to capture documents. Please grant camera permission.
97
+ </Text>
98
+ </View>
99
+
100
+ <Button
101
+ title="Grant Permission"
102
+ style={permissionStyles.buttonplace}
103
+ textStyle={permissionStyles.button}
104
+ onPress={async () => {
105
+ const granted = await requestPermission();
106
+ if (!granted) {
107
+ console.log('Camera permission denied');
108
+ }
109
+ }}
110
+ />
111
+ </ScrollView>
112
+ );
113
+ }
114
+
115
+ if (!device) return null;
116
+
117
+ // Take photo and process
72
118
  const takePhoto = async () => {
73
- if (!cameraRef.current) {
74
- Alert.alert('Camera Not Available', 'Camera is not available in this environment.');
119
+ if (!cameraRef.current || loading) {
75
120
  return;
76
121
  }
77
122
 
78
123
  try {
79
124
  setLoading(true);
80
-
81
125
  const photo = await cameraRef.current.takePhoto();
82
126
 
83
- if (!validateImagePath(photo?.path)) {
84
- Alert.alert('Error', 'Failed to capture image');
127
+ if (!photo?.path) {
128
+ console.error('No photo path available');
85
129
  setLoading(false);
86
130
  return;
87
131
  }
88
132
 
89
- // Process image to base64
90
- const base64Image = await processImageToBase64(photo.path);
133
+ let base64Image;
134
+
135
+ try {
136
+ const resizedImage = await ImageResizer.createResizedImage(
137
+ `file://${photo.path}`,
138
+ 810,
139
+ 1080,
140
+ 'JPEG',
141
+ 70,
142
+ 0,
143
+ undefined,
144
+ false
145
+ );
146
+ base64Image = await RNFS.readFile(resizedImage.uri, 'base64');
147
+ } catch (resizeError: any) {
148
+ console.warn('Image resizing failed, using original image:', resizeError.message);
149
+ base64Image = await RNFS.readFile(photo.path, 'base64');
150
+ }
91
151
 
92
- // Prepare photo data for API
93
152
  const photoData = {
94
153
  token: String(idmConf.verificationCode || ''),
95
154
  file: base64Image,
96
155
  };
97
156
 
98
- // Call API to capture document back
99
157
  const result = await captureDocumentBack(idmConf, photoData);
100
158
 
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
159
+ if (typeof result.status === 'string') {
160
+ setIDMConf((prev) => ({
161
+ ...prev,
162
+ requestConfiguration: result,
163
+ }));
109
164
  handleNextStep();
110
165
  } else {
111
- navigation.navigate('RetakeSelfie' as never, {
112
- errorMessage: result.errorMessage || 'Failed to process document',
113
- });
166
+ // Failed document capture
167
+ setFailedCount((prev) => prev + 1);
168
+ if (failedCount >= 4) {
169
+ // After 5 failed attempts (0-4), reset and proceed to ThankYou
170
+ setFailedCount(0);
171
+ navigation.navigate('ThankYou' as never);
172
+ } else {
173
+ navigation.navigate('RetakeSelfie' as never, { errorMessage: result.errorMessage });
174
+ }
114
175
  }
115
176
  } catch (error: any) {
116
- console.error('Error taking photo:', error);
177
+ console.error('Error taking photo:', error.message);
117
178
  Alert.alert('Error', 'Failed to capture or process the image.');
118
179
  } finally {
119
180
  setLoading(false);
120
181
  }
121
182
  };
122
183
 
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
-
151
184
  return (
152
185
  <View style={styles.container}>
153
186
  <Text style={styles.topLabel}>Document Back Side</Text>
154
187
 
155
- {renderCamera()}
188
+ {device && (
189
+ <Camera
190
+ ref={cameraRef}
191
+ style={StyleSheet.absoluteFill}
192
+ device={device}
193
+ isActive={true}
194
+ photo={true}
195
+ />
196
+ )}
156
197
 
157
198
  <Image
158
199
  source={require('../../assets/images/card-overlay-back.png')}
@@ -160,12 +201,14 @@ export default function DocumentCaptureBack() {
160
201
  resizeMode="cover"
161
202
  />
162
203
 
163
- <Text style={styles.bottomLabel}>
164
- Position your document fully within the frame
165
- </Text>
204
+ <Text style={styles.bottomLabel}>Position your document fully within the frame</Text>
166
205
 
167
206
  <View style={styles.buttonplace}>
168
- <TouchableOpacity style={styles.captureButton} onPress={takePhoto} />
207
+ <TouchableOpacity
208
+ style={styles.captureButton}
209
+ onPress={takePhoto}
210
+ disabled={loading}
211
+ />
169
212
  </View>
170
213
 
171
214
  {loading && <Loader />}