UNPKG

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
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;