react-native-ocr-package
Version:
A React component for scanning National Identity cards using a device's camera. This package integrates OCR technology to extract the text data from scanned cards by making API calls.
470 lines (421 loc) • 12.6 kB
JavaScript
import React, { useState, useEffect } from "react";
import {
View,
StyleSheet,
Button,
ActivityIndicator,
Alert,
Dimensions,
Text,
TouchableOpacity,
Platform,
Image
} from "react-native";
import DocumentScanner from "react-native-document-scanner-plugin";
import axios from "axios";
import { ResponseType } from "react-native-document-scanner-plugin";
// Comment out all Vision Camera related imports
/*
let Camera = null;
let useCameraDevices = null;
let CameraDevices = null;
try {
const VisionCamera = require("react-native-vision-camera");
Camera = VisionCamera.Camera;
useCameraDevices = VisionCamera.useCameraDevices;
// Safe check for CameraDevices
try {
if (Camera && Camera.getConstants) {
CameraDevices = Camera.getConstants().CameraDevices;
console.log("CameraDevices loaded successfully");
} else {
console.warn("Camera.getConstants() is unavailable");
}
} catch (constantsError) {
console.error("Error accessing Camera.getConstants():", constantsError);
}
} catch (error) {
console.error("Failed to load Vision Camera:", error);
}
// Safer fallback for camera devices hook if not available
if (!useCameraDevices) {
useCameraDevices = () => ({ devices: null });
}
*/
const { width, height } = Dimensions.get("window");
// Mock colors object if not available
const colors = {
oneAppTheme: "#3498db", // Example primary color
whiteColor: "#ffffff",
blackColor: "#000000",
};
/*
// Improve the camera availability check to handle getConstants specifically
const isCameraAvailable = () => {
try {
if (!Camera) return false;
// Check if critical methods exist
const hasPermissionMethod = typeof Camera.getCameraPermissionStatus === 'function';
// Also check if getConstants works - this is what's failing
let constantsWork = false;
try {
if (Camera.getConstants) {
const constants = Camera.getConstants();
constantsWork = !!constants;
}
} catch (e) {
console.warn("Camera.getConstants() failed:", e);
constantsWork = false;
}
return hasPermissionMethod && constantsWork;
} catch (error) {
console.error('Camera API check error:', error);
return false;
}
};
*/
const CameraComponent = ({ selfie, ocrApiUrl, onScanComplete }) => {
// Main screen state controller
const [screenState, setScreenState] = useState("intro"); // "intro", "document", "result"
// Captured data states
const [selfieImage, setSelfieImage] = useState(null);
const [documentImages, setDocumentImages] = useState(null);
const [apiResult, setApiResult] = useState(null);
// Processing state
const [isProcessing, setIsProcessing] = useState(false);
// Initialization effect
useEffect(() => {
console.log("Component mounted");
return () => {
console.log("Component unmounting");
};
}, []);
// Reset all states for a new verification process
const resetStates = () => {
setSelfieImage(null);
setDocumentImages(null);
setApiResult(null);
};
// Skip directly to document scanning - this is now the main flow
const startDocumentScanning = () => {
console.log("Proceeding to document scanning");
setScreenState("document");
};
// Handle document scanning
const handleDocumentCapture = async () => {
if (isProcessing) return;
try {
setIsProcessing(true);
// Use the document scanner to scan documents
// For CNIC front/back and selfie if needed
const { scannedImages } = await DocumentScanner.scanDocument({
maxNumDocuments: 3, // Always scan 3 documents: front, back, and selfie
responseType: ResponseType.Base64,
});
console.log("Documents scanned:", scannedImages?.length);
if (!scannedImages || scannedImages.length < 3) {
throw new Error("Please scan 3 documents: front and back of CNIC, and take a selfie");
}
// Store scanned images
setDocumentImages(scannedImages);
// Use the third document as selfie
const selfieData = scannedImages[2];
// Prepare API request
const reqBody = {
CNIC_Front: scannedImages[0],
CNIC_Back: scannedImages[1],
Selfie: selfieData,
Channel: "0011",
};
console.log("Sending API request with document data");
const response = await axios.post(ocrApiUrl, reqBody);
// Create an enhanced result that includes the base64 images
const enhancedResult = {
...response.data,
DocumentImages: {
frontImageBase64: scannedImages[0],
backImageBase64: scannedImages[1],
selfieImageBase64: scannedImages[2]
}
};
// Store and pass the enhanced result
setApiResult(enhancedResult);
onScanComplete(enhancedResult);
// Move to result screen
setScreenState("result");
} catch (error) {
console.error("Error during document scanning:", error);
if (error.message === "User canceled document scan") {
// User canceled, stay on document screen
console.log("User canceled document scan");
} else {
// Show error and notify parent component
Alert.alert(
"Error",
error.message || "Failed to process documents. Please try again."
);
onScanComplete(error?.response?.data || error?.response || { error: error.message });
}
} finally {
setIsProcessing(false);
}
};
// Render intro screen - simplified without camera options
const renderIntroScreen = () => (
<View style={styles.introContainer}>
<Text style={styles.headerText}>Identity Verification</Text>
<Text style={styles.descriptionText}>
We need to verify your identity by scanning your CNIC and taking a selfie photo.
</Text>
<View style={styles.infoContainer}>
<Text style={styles.infoTitle}>How It Works:</Text>
<Text style={styles.infoText}>
1. You'll scan the front of your CNIC
</Text>
<Text style={styles.infoText}>
2. Then scan the back of your CNIC
</Text>
<Text style={styles.infoText}>
3. Finally, take a selfie photo
</Text>
</View>
<Button
title="Start Document Scanning"
onPress={startDocumentScanning}
/>
</View>
);
// Render document scanning screen
const renderDocumentScreen = () => (
<View style={styles.documentContainer}>
<Text style={styles.headerText}>Document Scanning</Text>
<Text style={styles.descriptionText}>
Let's scan your documents. Please prepare:
</Text>
<View style={styles.instructionsContainer}>
<Text style={styles.instructionItem}>• Front side of your CNIC</Text>
<Text style={styles.instructionItem}>• Back side of your CNIC</Text>
<Text style={styles.instructionItem}>• Be ready to take a selfie</Text>
</View>
<Button
title="Scan Documents"
onPress={handleDocumentCapture}
disabled={isProcessing}
/>
<TouchableOpacity
style={styles.backButton}
onPress={() => setScreenState("intro")}
disabled={isProcessing}
>
<Text style={styles.backButtonText}>Go Back</Text>
</TouchableOpacity>
</View>
);
// Render results screen
const renderResultScreen = () => (
<View style={styles.resultContainer}>
<Text style={styles.headerText}>Verification Complete</Text>
<Text style={styles.resultText}>
Your identity verification has been processed.
</Text>
{apiResult && apiResult.DocumentImages && (
<View style={styles.imagesContainer}>
<Text style={styles.imagesTitle}>Captured Images:</Text>
<View style={styles.thumbnailRow}>
{apiResult.DocumentImages.frontImageBase64 && (
<View style={styles.thumbnailContainer}>
<Image
source={{uri: `data:image/jpeg;base64,${apiResult.DocumentImages.frontImageBase64}`}}
style={styles.thumbnailImage}
/>
<Text style={styles.thumbnailLabel}>Front</Text>
</View>
)}
{apiResult.DocumentImages.backImageBase64 && (
<View style={styles.thumbnailContainer}>
<Image
source={{uri: `data:image/jpeg;base64,${apiResult.DocumentImages.backImageBase64}`}}
style={styles.thumbnailImage}
/>
<Text style={styles.thumbnailLabel}>Back</Text>
</View>
)}
{apiResult.DocumentImages.selfieImageBase64 && (
<View style={styles.thumbnailContainer}>
<Image
source={{uri: `data:image/jpeg;base64,${apiResult.DocumentImages.selfieImageBase64}`}}
style={styles.thumbnailImage}
/>
<Text style={styles.thumbnailLabel}>Selfie</Text>
</View>
)}
</View>
</View>
)}
{apiResult && (
<View style={styles.resultDataContainer}>
<Text style={styles.resultDataText}>
{JSON.stringify(apiResult, null, 2)}
</Text>
</View>
)}
<Button
title="Start Over"
onPress={() => {
resetStates();
setScreenState("intro");
}}
/>
</View>
);
// Render loading overlay
const renderLoadingOverlay = () => (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#0000ff" />
<Text style={styles.loadingText}>
Processing documents...
</Text>
</View>
);
// Main render method
return (
<View style={styles.container}>
{isProcessing ? (
renderLoadingOverlay()
) : (
<>
{screenState === "intro" && renderIntroScreen()}
{screenState === "document" && renderDocumentScreen()}
{screenState === "result" && renderResultScreen()}
</>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
},
introContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
padding: 20,
},
documentContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
padding: 20,
},
resultContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
padding: 20,
},
loadingContainer: {
...StyleSheet.absoluteFillObject,
justifyContent: "center",
alignItems: "center",
backgroundColor: "rgba(0,0,0,0.7)",
zIndex: 1000,
},
headerText: {
fontSize: 24,
fontWeight: "bold",
marginBottom: 20,
textAlign: "center",
},
descriptionText: {
fontSize: 16,
marginBottom: 30,
textAlign: "center",
},
loadingText: {
color: "#fff",
marginTop: 10,
fontSize: 16,
},
backButton: {
marginTop: 20,
},
backButtonText: {
color: "#0066CC",
fontSize: 16,
},
resultDataContainer: {
marginVertical: 20,
padding: 10,
backgroundColor: "#f5f5f5",
borderRadius: 5,
width: "100%",
},
resultDataText: {
fontSize: 14,
},
resultText: {
fontSize: 16,
marginBottom: 15,
textAlign: "center",
},
infoContainer: {
width: '90%',
backgroundColor: '#e8f4f8',
padding: 15,
borderRadius: 8,
marginBottom: 25,
},
infoTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 10,
color: '#333',
},
infoText: {
fontSize: 14,
color: '#444',
marginBottom: 5,
},
instructionsContainer: {
width: '90%',
backgroundColor: '#f9f9f9',
padding: 15,
borderRadius: 8,
marginBottom: 25,
},
instructionItem: {
fontSize: 15,
marginBottom: 8,
color: '#333',
},
imagesContainer: {
marginBottom: 20,
},
imagesTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 10,
color: '#333',
},
thumbnailRow: {
flexDirection: 'row',
justifyContent: 'space-between',
},
thumbnailContainer: {
flex: 1,
alignItems: 'center',
},
thumbnailImage: {
width: '100%',
height: 100,
borderRadius: 5,
},
thumbnailLabel: {
marginTop: 5,
fontSize: 14,
color: '#333',
},
});
export default CameraComponent;