Implement Camera Scanner component and examples
- Added CameraScanner component for QR/Barcode scanning functionality. - Created CameraScannerExample to demonstrate usage of the CameraScanner. - Developed useCameraScanner hook for managing scanner state and events. - Introduced ScannerScreen for a drop-in ready scanner interface. - Added TypeScript types for ScanResult, ScannerError, and SDK configuration. - Implemented multiple examples showcasing various integration scenarios.
This commit is contained in:
275
src/components/CameraScanner.tsx
Normal file
275
src/components/CameraScanner.tsx
Normal file
@@ -0,0 +1,275 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
NativeModules,
|
||||
NativeEventEmitter,
|
||||
Alert,
|
||||
ActivityIndicator,
|
||||
} from 'react-native';
|
||||
|
||||
const { ICameraSDK } = NativeModules;
|
||||
|
||||
interface ScanResult {
|
||||
code: string;
|
||||
format: string;
|
||||
formatCode: number;
|
||||
}
|
||||
|
||||
interface ScannerProps {
|
||||
presignedUrl: string;
|
||||
onScanResult?: (result: ScanResult) => void;
|
||||
onError?: (error: string) => void;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export const CameraScanner: React.FC<ScannerProps> = ({
|
||||
presignedUrl,
|
||||
onScanResult,
|
||||
onError,
|
||||
onClose,
|
||||
}) => {
|
||||
const [isInitializing, setIsInitializing] = useState(true);
|
||||
const [isScanning, setIsScanning] = useState(false);
|
||||
const [lastScan, setLastScan] = useState<ScanResult | null>(null);
|
||||
const eventEmitterRef = useRef<NativeEventEmitter | null>(null);
|
||||
|
||||
// Initialize SDK and setup event listeners
|
||||
useEffect(() => {
|
||||
initializeSDK();
|
||||
|
||||
return () => {
|
||||
stopScanning();
|
||||
if (eventEmitterRef.current) {
|
||||
eventEmitterRef.current.removeAllListeners();
|
||||
}
|
||||
};
|
||||
}, [presignedUrl]);
|
||||
|
||||
const initializeSDK = async () => {
|
||||
try {
|
||||
if (!ICameraSDK) {
|
||||
throw new Error('ICameraSDK module not available');
|
||||
}
|
||||
|
||||
// Set up event emitter
|
||||
eventEmitterRef.current = new NativeEventEmitter(ICameraSDK);
|
||||
|
||||
// Check if SDK is already initialized
|
||||
const isInitialized = await ICameraSDK.isInitialized();
|
||||
|
||||
if (!isInitialized) {
|
||||
// Initialize with QR_CODE and BAR_CODE features
|
||||
const initResult = await ICameraSDK.initialize(presignedUrl, [
|
||||
'QR_CODE',
|
||||
'BAR_CODE',
|
||||
]);
|
||||
console.log('SDK initialized:', initResult);
|
||||
}
|
||||
|
||||
// Setup event listeners
|
||||
eventEmitterRef.current.addListener('onScannerResult', (result: ScanResult) => {
|
||||
console.log('Scan result:', result);
|
||||
setLastScan(result);
|
||||
onScanResult?.(result);
|
||||
});
|
||||
|
||||
eventEmitterRef.current.addListener('onScannerError', (error: any) => {
|
||||
console.error('Scanner error:', error);
|
||||
onError?.(error.error || 'Unknown error');
|
||||
Alert.alert('Scan Error', error.error || 'Failed to scan code');
|
||||
});
|
||||
|
||||
setIsInitializing(false);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.error('Failed to initialize SDK:', errorMessage);
|
||||
onError?.(errorMessage);
|
||||
Alert.alert('Initialization Error', errorMessage);
|
||||
setIsInitializing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const startScanning = async () => {
|
||||
try {
|
||||
if (!ICameraSDK) {
|
||||
throw new Error('ICameraSDK module not available');
|
||||
}
|
||||
|
||||
const result = await ICameraSDK.startScanner();
|
||||
console.log('Scanner started:', result);
|
||||
setIsScanning(true);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.error('Failed to start scanner:', errorMessage);
|
||||
onError?.(errorMessage);
|
||||
Alert.alert('Start Scanner Error', errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
const stopScanning = async () => {
|
||||
try {
|
||||
if (!ICameraSDK) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await ICameraSDK.stopScanner();
|
||||
console.log('Scanner stopped:', result);
|
||||
setIsScanning(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to stop scanner:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = async () => {
|
||||
await stopScanning();
|
||||
onClose?.();
|
||||
};
|
||||
|
||||
if (isInitializing) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ActivityIndicator size="large" color="#0000ff" />
|
||||
<Text style={styles.loadingText}>Initializing Camera SDK...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>QR/Barcode Scanner</Text>
|
||||
<TouchableOpacity style={styles.closeButton} onPress={handleClose}>
|
||||
<Text style={styles.closeButtonText}>✕</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Camera preview would go here */}
|
||||
<View style={styles.cameraPreview}>
|
||||
<Text style={styles.placeholderText}>
|
||||
Camera Preview Area
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.controlsContainer}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.button,
|
||||
isScanning ? styles.buttonActive : styles.buttonInactive,
|
||||
]}
|
||||
onPress={isScanning ? stopScanning : startScanning}
|
||||
disabled={isInitializing}
|
||||
>
|
||||
<Text style={styles.buttonText}>
|
||||
{isScanning ? '⏹ Stop Scanning' : '▶ Start Scanning'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{lastScan && (
|
||||
<View style={styles.resultContainer}>
|
||||
<Text style={styles.resultLabel}>Last Scan Result:</Text>
|
||||
<Text style={styles.resultCode}>{lastScan.code}</Text>
|
||||
<Text style={styles.resultFormat}>Format: {lastScan.format}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f5f5f5',
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
backgroundColor: '#fff',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#e0e0e0',
|
||||
},
|
||||
title: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
color: '#000',
|
||||
},
|
||||
closeButton: {
|
||||
padding: 8,
|
||||
},
|
||||
closeButtonText: {
|
||||
fontSize: 24,
|
||||
color: '#999',
|
||||
},
|
||||
cameraPreview: {
|
||||
flex: 1,
|
||||
backgroundColor: '#000',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
margin: 16,
|
||||
borderRadius: 8,
|
||||
borderWidth: 2,
|
||||
borderColor: '#ddd',
|
||||
},
|
||||
placeholderText: {
|
||||
color: '#999',
|
||||
fontSize: 16,
|
||||
},
|
||||
controlsContainer: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
button: {
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 20,
|
||||
borderRadius: 8,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
buttonActive: {
|
||||
backgroundColor: '#ff4444',
|
||||
},
|
||||
buttonInactive: {
|
||||
backgroundColor: '#4CAF50',
|
||||
},
|
||||
buttonText: {
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
resultContainer: {
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 16,
|
||||
padding: 12,
|
||||
backgroundColor: '#e8f5e9',
|
||||
borderRadius: 8,
|
||||
borderLeftWidth: 4,
|
||||
borderLeftColor: '#4CAF50',
|
||||
},
|
||||
resultLabel: {
|
||||
fontSize: 12,
|
||||
color: '#666',
|
||||
marginBottom: 4,
|
||||
},
|
||||
resultCode: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#000',
|
||||
marginBottom: 4,
|
||||
},
|
||||
resultFormat: {
|
||||
fontSize: 12,
|
||||
color: '#666',
|
||||
},
|
||||
loadingText: {
|
||||
marginTop: 12,
|
||||
fontSize: 14,
|
||||
color: '#666',
|
||||
},
|
||||
});
|
||||
340
src/components/CameraScannerExample.tsx
Normal file
340
src/components/CameraScannerExample.tsx
Normal file
@@ -0,0 +1,340 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
Alert,
|
||||
ScrollView,
|
||||
} from 'react-native';
|
||||
import { useCameraScanner, ScanResult } from '../hooks/useCameraScanner';
|
||||
|
||||
interface ScanHistoryItem {
|
||||
code: string;
|
||||
format: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export const CameraScannerExample: React.FC = () => {
|
||||
const [showScanner, setShowScanner] = useState(false);
|
||||
const [scanHistory, setScanHistory] = useState<ScanHistoryItem[]>([]);
|
||||
|
||||
// Your presigned URL - replace with actual URL from your backend
|
||||
const PRESIGNED_URL = 'https://your-presigned-url-here.com';
|
||||
|
||||
const scanner = useCameraScanner({
|
||||
presignedUrl: PRESIGNED_URL,
|
||||
features: ['QR_CODE', 'BAR_CODE'],
|
||||
onScanResult: (result: ScanResult) => {
|
||||
// Add to history
|
||||
const item: ScanHistoryItem = {
|
||||
code: result.code,
|
||||
format: result.format,
|
||||
timestamp: new Date().toLocaleTimeString(),
|
||||
};
|
||||
setScanHistory((prev) => [item, ...prev]);
|
||||
|
||||
// Show alert
|
||||
Alert.alert('Scan Successful!', `Code: ${result.code}\nFormat: ${result.format}`);
|
||||
},
|
||||
onError: (error: string) => {
|
||||
Alert.alert('Error', error);
|
||||
},
|
||||
});
|
||||
|
||||
const handleStartScanning = async () => {
|
||||
if (!scanner.isInitialized) {
|
||||
Alert.alert('Error', 'Scanner is not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setShowScanner(true);
|
||||
await scanner.startScanning();
|
||||
} catch (err) {
|
||||
Alert.alert('Error', 'Failed to start scanner');
|
||||
}
|
||||
};
|
||||
|
||||
const handleStopScanning = async () => {
|
||||
await scanner.stopScanning();
|
||||
setShowScanner(false);
|
||||
};
|
||||
|
||||
const clearHistory = () => {
|
||||
setScanHistory([]);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.headerTitle}>Camera Scanner</Text>
|
||||
<Text style={styles.headerSubtitle}>QR Code & Barcode Scanner</Text>
|
||||
</View>
|
||||
|
||||
{showScanner ? (
|
||||
<View style={styles.scannerContainer}>
|
||||
<View style={styles.cameraPlaceholder}>
|
||||
<Text style={styles.cameraPlaceholderText}>📷</Text>
|
||||
<Text style={styles.cameraStatusText}>
|
||||
{scanner.isScanning ? 'Scanning...' : 'Ready to scan'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.stopButton}
|
||||
onPress={handleStopScanning}
|
||||
>
|
||||
<Text style={styles.stopButtonText}>Close Scanner</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
) : (
|
||||
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
|
||||
{/* Status Section */}
|
||||
<View style={styles.statusSection}>
|
||||
<Text style={styles.sectionTitle}>Status</Text>
|
||||
<View style={styles.statusCard}>
|
||||
<View style={styles.statusItem}>
|
||||
<Text style={styles.statusLabel}>SDK Initialized:</Text>
|
||||
<Text style={styles.statusValue}>
|
||||
{scanner.isInitialized ? '✓ Yes' : '✗ No'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.statusItem}>
|
||||
<Text style={styles.statusLabel}>Scanning:</Text>
|
||||
<Text style={styles.statusValue}>
|
||||
{scanner.isScanning ? '✓ Active' : '✗ Inactive'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Controls Section */}
|
||||
<View style={styles.controlsSection}>
|
||||
<TouchableOpacity
|
||||
style={styles.startButton}
|
||||
onPress={handleStartScanning}
|
||||
disabled={!scanner.isInitialized}
|
||||
>
|
||||
<Text style={styles.startButtonText}>▶ Start Scanner</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* History Section */}
|
||||
<View style={styles.historySection}>
|
||||
<View style={styles.historyHeader}>
|
||||
<Text style={styles.sectionTitle}>Scan History</Text>
|
||||
{scanHistory.length > 0 && (
|
||||
<TouchableOpacity onPress={clearHistory}>
|
||||
<Text style={styles.clearButton}>Clear</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{scanHistory.length === 0 ? (
|
||||
<View style={styles.emptyState}>
|
||||
<Text style={styles.emptyStateText}>No scans yet</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View>
|
||||
{scanHistory.map((item, index) => (
|
||||
<View key={index} style={styles.historyItem}>
|
||||
<View style={styles.historyItemContent}>
|
||||
<Text style={styles.historyCode}>{item.code}</Text>
|
||||
<View style={styles.historyFooter}>
|
||||
<Text style={styles.historyFormat}>
|
||||
{item.format}
|
||||
</Text>
|
||||
<Text style={styles.historyTime}>{item.timestamp}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
)}
|
||||
|
||||
{scanner.error && (
|
||||
<View style={styles.errorBanner}>
|
||||
<Text style={styles.errorText}>⚠ {scanner.error}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f8f9fa',
|
||||
},
|
||||
header: {
|
||||
backgroundColor: '#2c3e50',
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: 20,
|
||||
paddingBottom: 16,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 24,
|
||||
fontWeight: '700',
|
||||
color: '#fff',
|
||||
},
|
||||
headerSubtitle: {
|
||||
fontSize: 14,
|
||||
color: '#bdc3c7',
|
||||
marginTop: 4,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
padding: 16,
|
||||
},
|
||||
scannerContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
cameraPlaceholder: {
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
backgroundColor: '#000',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
cameraPlaceholderText: {
|
||||
fontSize: 80,
|
||||
marginBottom: 16,
|
||||
},
|
||||
cameraStatusText: {
|
||||
fontSize: 16,
|
||||
color: '#fff',
|
||||
marginTop: 12,
|
||||
},
|
||||
stopButton: {
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 20,
|
||||
paddingVertical: 14,
|
||||
backgroundColor: '#e74c3c',
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
},
|
||||
stopButtonText: {
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
statusSection: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#2c3e50',
|
||||
marginBottom: 12,
|
||||
},
|
||||
statusCard: {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 8,
|
||||
padding: 16,
|
||||
borderLeftWidth: 4,
|
||||
borderLeftColor: '#3498db',
|
||||
},
|
||||
statusItem: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 8,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#ecf0f1',
|
||||
},
|
||||
statusLabel: {
|
||||
fontSize: 14,
|
||||
color: '#555',
|
||||
fontWeight: '500',
|
||||
},
|
||||
statusValue: {
|
||||
fontSize: 14,
|
||||
color: '#27ae60',
|
||||
fontWeight: '600',
|
||||
},
|
||||
controlsSection: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
startButton: {
|
||||
paddingVertical: 14,
|
||||
backgroundColor: '#27ae60',
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
},
|
||||
startButtonText: {
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
historySection: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
historyHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
},
|
||||
clearButton: {
|
||||
color: '#e74c3c',
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
},
|
||||
emptyState: {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 8,
|
||||
padding: 32,
|
||||
alignItems: 'center',
|
||||
},
|
||||
emptyStateText: {
|
||||
fontSize: 14,
|
||||
color: '#999',
|
||||
},
|
||||
historyItem: {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 8,
|
||||
padding: 12,
|
||||
marginBottom: 8,
|
||||
borderLeftWidth: 4,
|
||||
borderLeftColor: '#27ae60',
|
||||
},
|
||||
historyItemContent: {
|
||||
flex: 1,
|
||||
},
|
||||
historyCode: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#2c3e50',
|
||||
marginBottom: 6,
|
||||
},
|
||||
historyFooter: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
historyFormat: {
|
||||
fontSize: 12,
|
||||
color: '#666',
|
||||
fontWeight: '500',
|
||||
},
|
||||
historyTime: {
|
||||
fontSize: 12,
|
||||
color: '#999',
|
||||
},
|
||||
errorBanner: {
|
||||
backgroundColor: '#ffe74c',
|
||||
padding: 12,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#ffd700',
|
||||
},
|
||||
errorText: {
|
||||
fontSize: 12,
|
||||
color: '#856404',
|
||||
fontWeight: '600',
|
||||
},
|
||||
});
|
||||
145
src/hooks/useCameraScanner.ts
Normal file
145
src/hooks/useCameraScanner.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { NativeModules, NativeEventEmitter } from 'react-native';
|
||||
|
||||
const { ICameraSDK } = NativeModules;
|
||||
|
||||
export interface ScanResult {
|
||||
code: string;
|
||||
format: string;
|
||||
formatCode: number;
|
||||
}
|
||||
|
||||
export interface UseCameraScannerOptions {
|
||||
presignedUrl: string;
|
||||
features?: string[];
|
||||
onScanResult?: (result: ScanResult) => void;
|
||||
onError?: (error: string) => void;
|
||||
}
|
||||
|
||||
export const useCameraScanner = (options: UseCameraScannerOptions) => {
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
const [isScanning, setIsScanning] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const eventEmitterRef = useRef<NativeEventEmitter | null>(null);
|
||||
const subscriptionsRef = useRef<any[]>([]);
|
||||
|
||||
// Initialize the SDK
|
||||
useEffect(() => {
|
||||
initialize();
|
||||
|
||||
return () => {
|
||||
cleanup();
|
||||
};
|
||||
}, [options.presignedUrl]);
|
||||
|
||||
const initialize = async () => {
|
||||
try {
|
||||
if (!ICameraSDK) {
|
||||
throw new Error('ICameraSDK module not available');
|
||||
}
|
||||
|
||||
eventEmitterRef.current = new NativeEventEmitter(ICameraSDK);
|
||||
|
||||
const isInit = await ICameraSDK.isInitialized();
|
||||
|
||||
if (!isInit) {
|
||||
const features = options.features || ['QR_CODE', 'BAR_CODE'];
|
||||
await ICameraSDK.initialize(options.presignedUrl, features);
|
||||
}
|
||||
|
||||
// Setup event listeners
|
||||
const scannerResultSub = eventEmitterRef.current.addListener(
|
||||
'onScannerResult',
|
||||
(result: ScanResult) => {
|
||||
options.onScanResult?.(result);
|
||||
}
|
||||
);
|
||||
|
||||
const scannerErrorSub = eventEmitterRef.current.addListener(
|
||||
'onScannerError',
|
||||
(error: any) => {
|
||||
const errorMsg = error.error || 'Unknown error';
|
||||
setError(errorMsg);
|
||||
options.onError?.(errorMsg);
|
||||
}
|
||||
);
|
||||
|
||||
subscriptionsRef.current = [scannerResultSub, scannerErrorSub];
|
||||
setIsInitialized(true);
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||
setError(errorMsg);
|
||||
options.onError?.(errorMsg);
|
||||
}
|
||||
};
|
||||
|
||||
const startScanning = async () => {
|
||||
try {
|
||||
if (!ICameraSDK) {
|
||||
throw new Error('ICameraSDK module not available');
|
||||
}
|
||||
|
||||
await ICameraSDK.startScanner();
|
||||
setIsScanning(true);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||
setError(errorMsg);
|
||||
options.onError?.(errorMsg);
|
||||
}
|
||||
};
|
||||
|
||||
const stopScanning = async () => {
|
||||
try {
|
||||
if (!ICameraSDK) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ICameraSDK.stopScanner();
|
||||
setIsScanning(false);
|
||||
} catch (err) {
|
||||
console.error('Error stopping scanner:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const cleanup = async () => {
|
||||
await stopScanning();
|
||||
|
||||
subscriptionsRef.current.forEach((sub) => {
|
||||
sub.remove();
|
||||
});
|
||||
subscriptionsRef.current = [];
|
||||
|
||||
try {
|
||||
if (ICameraSDK) {
|
||||
await ICameraSDK.shutdown();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error shutting down SDK:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getConfiguration = async () => {
|
||||
try {
|
||||
if (!ICameraSDK) {
|
||||
throw new Error('ICameraSDK module not available');
|
||||
}
|
||||
|
||||
return await ICameraSDK.getConfiguration();
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||
setError(errorMsg);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
isInitialized,
|
||||
isScanning,
|
||||
error,
|
||||
startScanning,
|
||||
stopScanning,
|
||||
getConfiguration,
|
||||
cleanup,
|
||||
};
|
||||
};
|
||||
411
src/screens/CameraExamples.tsx
Normal file
411
src/screens/CameraExamples.tsx
Normal file
@@ -0,0 +1,411 @@
|
||||
/**
|
||||
* Complete Examples for iCameraSDK
|
||||
* Copy and adapt these examples for your use case
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
Alert,
|
||||
StyleSheet,
|
||||
NativeModules,
|
||||
NativeEventEmitter,
|
||||
} from 'react-native';
|
||||
import { useCameraScanner } from '../hooks/useCameraScanner';
|
||||
import { ScanResult } from '../types/icamera';
|
||||
|
||||
// ============================================================================
|
||||
// Example 1: Using the Hook (Recommended - Easiest)
|
||||
// ============================================================================
|
||||
|
||||
export const Example1_UseHook = () => {
|
||||
const scanner = useCameraScanner({
|
||||
presignedUrl: 'https://your-backend-presigned-url.com',
|
||||
features: ['QR_CODE', 'BAR_CODE'],
|
||||
onScanResult: (result: ScanResult) => {
|
||||
console.log('✓ Scanned:', result.code);
|
||||
Alert.alert('Success', `Code: ${result.code}`);
|
||||
},
|
||||
onError: (error: string) => {
|
||||
console.error('✗ Error:', error);
|
||||
Alert.alert('Error', error);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Initialized: {scanner.isInitialized ? '✓' : '✗'}</Text>
|
||||
<TouchableOpacity onPress={scanner.startScanning}>
|
||||
<Text style={styles.button}>Start Scan</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Example 2: Direct NativeModule Access
|
||||
// ============================================================================
|
||||
|
||||
export const Example2_DirectModule = () => {
|
||||
const [isScanning, setIsScanning] = useState(false);
|
||||
const { ICameraSDK } = NativeModules;
|
||||
|
||||
const handleStart = async () => {
|
||||
try {
|
||||
// Initialize if not already done
|
||||
const isInit = await ICameraSDK.isInitialized();
|
||||
if (!isInit) {
|
||||
await ICameraSDK.initialize(
|
||||
'https://presigned-url.com',
|
||||
['QR_CODE', 'BAR_CODE']
|
||||
);
|
||||
}
|
||||
|
||||
// Start scanner
|
||||
await ICameraSDK.startScanner();
|
||||
setIsScanning(true);
|
||||
|
||||
// Setup listeners
|
||||
const emitter = new NativeEventEmitter(ICameraSDK);
|
||||
emitter.addListener('onScannerResult', (result) => {
|
||||
console.log('Scanned:', result.code);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleStop = async () => {
|
||||
try {
|
||||
await ICameraSDK.stopScanner();
|
||||
setIsScanning(false);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<TouchableOpacity onPress={isScanning ? handleStop : handleStart}>
|
||||
<Text style={styles.button}>
|
||||
{isScanning ? 'Stop' : 'Start'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Example 3: Advanced Hook Usage with Lifecycle
|
||||
// ============================================================================
|
||||
|
||||
export const Example3_AdvancedHook = () => {
|
||||
const [scanHistory, setScanHistory] = useState<ScanResult[]>([]);
|
||||
|
||||
const scanner = useCameraScanner({
|
||||
presignedUrl: 'https://your-backend-presigned-url.com',
|
||||
features: ['QR_CODE'],
|
||||
onScanResult: (result: ScanResult) => {
|
||||
setScanHistory((prev) => [result, ...prev]);
|
||||
},
|
||||
onError: (error: string) => {
|
||||
Alert.alert('Scanner Error', error);
|
||||
},
|
||||
});
|
||||
|
||||
// Cleanup on unmount
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
scanner.cleanup();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.status}>
|
||||
<Text>SDK: {scanner.isInitialized ? '✓ Initialized' : '⏳ Loading'}</Text>
|
||||
<Text>State: {scanner.isScanning ? '▶ Scanning' : '⏸ Idle'}</Text>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={scanner.startScanning}
|
||||
disabled={scanner.isScanning}
|
||||
style={styles.button}
|
||||
>
|
||||
<Text>▶ Start Scanning</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={scanner.stopScanning}
|
||||
disabled={!scanner.isScanning}
|
||||
style={styles.button}
|
||||
>
|
||||
<Text>⏹ Stop Scanning</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.history}>
|
||||
<Text style={styles.historyTitle}>
|
||||
Scan History ({scanHistory.length})
|
||||
</Text>
|
||||
{scanHistory.map((scan, idx) => (
|
||||
<View key={idx} style={styles.historyItem}>
|
||||
<Text>{scan.code}</Text>
|
||||
<Text style={styles.historyFormat}>{scan.format}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{scanner.error && (
|
||||
<View style={styles.error}>
|
||||
<Text style={styles.errorText}>{scanner.error}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Example 4: Integration with Your App Flow
|
||||
// ============================================================================
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export const Example4_AppIntegration = () => {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [scannedData, setScannedData] = useState<ScanResult | null>(null);
|
||||
|
||||
// Your backend API to get presigned URL
|
||||
const getPresignedUrl = async (userId: string): Promise<string> => {
|
||||
const response = await fetch(`/api/camera/presigned-url?userId=${userId}`);
|
||||
const data = await response.json();
|
||||
return data.presignedUrl;
|
||||
};
|
||||
|
||||
const scanner = useCameraScanner({
|
||||
presignedUrl: user ? getPresignedUrl(user.id) : '',
|
||||
onScanResult: async (result: ScanResult) => {
|
||||
setScannedData(result);
|
||||
|
||||
// Process the scanned data
|
||||
try {
|
||||
// Send to your backend
|
||||
const response = await fetch('/api/camera/process-scan', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
userId: user?.id,
|
||||
scannedCode: result.code,
|
||||
format: result.format,
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
Alert.alert('✓ Success', 'Scan processed successfully');
|
||||
await scanner.stopScanning();
|
||||
}
|
||||
} catch (error) {
|
||||
Alert.alert('✗ Error', 'Failed to process scan');
|
||||
}
|
||||
},
|
||||
onError: (error: string) => {
|
||||
Alert.alert('Scanner Error', error);
|
||||
},
|
||||
});
|
||||
|
||||
const handleStartScanning = async () => {
|
||||
if (!user) {
|
||||
Alert.alert('Error', 'User not logged in');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!scanner.isInitialized) {
|
||||
Alert.alert('Error', 'Scanner not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
await scanner.startScanning();
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>App Integration Example</Text>
|
||||
|
||||
{user ? (
|
||||
<View>
|
||||
<Text>Logged in as: {user.email}</Text>
|
||||
<TouchableOpacity
|
||||
onPress={handleStartScanning}
|
||||
style={styles.button}
|
||||
>
|
||||
<Text>Open Scanner</Text>
|
||||
</TouchableOpacity>
|
||||
{scannedData && (
|
||||
<View style={styles.result}>
|
||||
<Text>Last scan: {scannedData.code}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<TouchableOpacity
|
||||
onPress={() => setUser({ id: '1', email: 'user@example.com' })}
|
||||
style={styles.button}
|
||||
>
|
||||
<Text>Login First</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Example 5: Error Handling & Recovery
|
||||
// ============================================================================
|
||||
|
||||
export const Example5_ErrorHandling = () => {
|
||||
const [logs, setLogs] = useState<string[]>([]);
|
||||
|
||||
const addLog = (message: string) => {
|
||||
setLogs((prev) => [`${new Date().toLocaleTimeString()} - ${message}`, ...prev]);
|
||||
};
|
||||
|
||||
const scanner = useCameraScanner({
|
||||
presignedUrl: 'https://your-presigned-url.com',
|
||||
onScanResult: (result: ScanResult) => {
|
||||
addLog(`✓ Scanned: ${result.code}`);
|
||||
},
|
||||
onError: (error: string) => {
|
||||
addLog(`✗ Error: ${error}`);
|
||||
|
||||
// Handle specific errors
|
||||
if (error.includes('permission')) {
|
||||
addLog('💡 Tip: Grant camera permissions in settings');
|
||||
} else if (error.includes('not initialized')) {
|
||||
addLog('💡 Tip: Initialize SDK first');
|
||||
} else if (error.includes('camera')) {
|
||||
addLog('💡 Tip: Ensure camera is available');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>Error Handling Example</Text>
|
||||
|
||||
<View style={styles.logs}>
|
||||
<Text style={styles.logsTitle}>Logs:</Text>
|
||||
{logs.map((log, idx) => (
|
||||
<Text key={idx} style={styles.log}>
|
||||
{log}
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={scanner.startScanning}
|
||||
style={styles.button}
|
||||
>
|
||||
<Text>Test Scanner</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Styles
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 16,
|
||||
backgroundColor: '#f5f5f5',
|
||||
},
|
||||
title: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
marginBottom: 16,
|
||||
},
|
||||
status: {
|
||||
backgroundColor: '#fff',
|
||||
padding: 12,
|
||||
borderRadius: 8,
|
||||
marginBottom: 12,
|
||||
},
|
||||
button: {
|
||||
backgroundColor: '#2196F3',
|
||||
padding: 12,
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
marginVertical: 8,
|
||||
},
|
||||
buttonText: {
|
||||
color: '#fff',
|
||||
fontWeight: '600',
|
||||
},
|
||||
history: {
|
||||
marginTop: 16,
|
||||
},
|
||||
historyTitle: {
|
||||
fontWeight: '600',
|
||||
marginBottom: 8,
|
||||
},
|
||||
historyItem: {
|
||||
backgroundColor: '#fff',
|
||||
padding: 8,
|
||||
marginBottom: 8,
|
||||
borderRadius: 4,
|
||||
},
|
||||
historyFormat: {
|
||||
fontSize: 12,
|
||||
color: '#666',
|
||||
},
|
||||
result: {
|
||||
backgroundColor: '#e8f5e9',
|
||||
padding: 12,
|
||||
borderRadius: 8,
|
||||
marginTop: 12,
|
||||
},
|
||||
error: {
|
||||
backgroundColor: '#ffe0e0',
|
||||
padding: 12,
|
||||
borderRadius: 8,
|
||||
marginTop: 12,
|
||||
},
|
||||
errorText: {
|
||||
color: '#c00',
|
||||
},
|
||||
logs: {
|
||||
backgroundColor: '#fff',
|
||||
padding: 12,
|
||||
borderRadius: 8,
|
||||
maxHeight: 200,
|
||||
marginBottom: 12,
|
||||
},
|
||||
logsTitle: {
|
||||
fontWeight: '600',
|
||||
marginBottom: 8,
|
||||
},
|
||||
log: {
|
||||
fontSize: 11,
|
||||
color: '#666',
|
||||
marginBottom: 4,
|
||||
fontFamily: 'monospace',
|
||||
},
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Export all examples
|
||||
// ============================================================================
|
||||
|
||||
export default [
|
||||
Example1_UseHook,
|
||||
Example2_DirectModule,
|
||||
Example3_AdvancedHook,
|
||||
Example4_AppIntegration,
|
||||
Example5_ErrorHandling,
|
||||
];
|
||||
227
src/screens/ScannerScreen.tsx
Normal file
227
src/screens/ScannerScreen.tsx
Normal file
@@ -0,0 +1,227 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
Alert,
|
||||
ScrollView,
|
||||
} from 'react-native';
|
||||
import { useCameraScanner, ScanResult } from '../hooks/useCameraScanner';
|
||||
|
||||
/**
|
||||
* Drop-in ready Scanner Screen
|
||||
*
|
||||
* Usage:
|
||||
* <ScannerScreen presignedUrl="YOUR_URL" onScan={handleScan} />
|
||||
*/
|
||||
|
||||
interface ScannerScreenProps {
|
||||
presignedUrl: string;
|
||||
onScan?: (result: ScanResult) => void;
|
||||
onError?: (error: string) => void;
|
||||
}
|
||||
|
||||
export const ScannerScreen: React.FC<ScannerScreenProps> = ({
|
||||
presignedUrl,
|
||||
onScan,
|
||||
onError,
|
||||
}) => {
|
||||
const [scans, setScans] = useState<ScanResult[]>([]);
|
||||
|
||||
const scanner = useCameraScanner({
|
||||
presignedUrl,
|
||||
features: ['QR_CODE', 'BAR_CODE'],
|
||||
onScanResult: (result) => {
|
||||
console.log('✓ Scan:', result);
|
||||
setScans((prev) => [result, ...prev]);
|
||||
onScan?.(result);
|
||||
|
||||
// Optional: Show alert
|
||||
Alert.alert(
|
||||
'✓ Scanned Successfully',
|
||||
`Code: ${result.code}\nFormat: ${result.format}`,
|
||||
[{ text: 'OK' }]
|
||||
);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('✗ Error:', error);
|
||||
onError?.(error);
|
||||
Alert.alert('Error', error);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>Scanner</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
{scanner.isInitialized ? '✓ Ready' : '⏳ Loading...'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Camera Area */}
|
||||
<View style={styles.cameraContainer}>
|
||||
<View style={styles.cameraPlaceholder}>
|
||||
<Text style={styles.cameraIcon}>📷</Text>
|
||||
<Text style={styles.cameraStatus}>
|
||||
{scanner.isScanning ? '🔴 Scanning...' : '⚪ Ready'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Controls */}
|
||||
<View style={styles.controls}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.button,
|
||||
scanner.isScanning ? styles.stopButton : styles.startButton,
|
||||
]}
|
||||
onPress={
|
||||
scanner.isScanning ? scanner.stopScanning : scanner.startScanning
|
||||
}
|
||||
disabled={!scanner.isInitialized}
|
||||
>
|
||||
<Text style={styles.buttonText}>
|
||||
{scanner.isScanning ? '⏹ Stop' : '▶ Start'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Results */}
|
||||
{scans.length > 0 && (
|
||||
<View style={styles.results}>
|
||||
<Text style={styles.resultsTitle}>Results ({scans.length})</Text>
|
||||
<ScrollView style={styles.resultsList}>
|
||||
{scans.map((scan, idx) => (
|
||||
<View key={idx} style={styles.resultItem}>
|
||||
<Text style={styles.resultCode}>{scan.code}</Text>
|
||||
<Text style={styles.resultFormat}>{scan.format}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Error Display */}
|
||||
{scanner.error && (
|
||||
<View style={styles.error}>
|
||||
<Text style={styles.errorText}>⚠ {scanner.error}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f5f5f5',
|
||||
},
|
||||
header: {
|
||||
backgroundColor: '#2c3e50',
|
||||
padding: 16,
|
||||
paddingTop: 20,
|
||||
},
|
||||
title: {
|
||||
fontSize: 24,
|
||||
fontWeight: '700',
|
||||
color: '#fff',
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 12,
|
||||
color: '#bdc3c7',
|
||||
marginTop: 4,
|
||||
},
|
||||
cameraContainer: {
|
||||
flex: 2,
|
||||
padding: 16,
|
||||
},
|
||||
cameraPlaceholder: {
|
||||
flex: 1,
|
||||
backgroundColor: '#1a1a1a',
|
||||
borderRadius: 12,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 2,
|
||||
borderColor: '#444',
|
||||
},
|
||||
cameraIcon: {
|
||||
fontSize: 60,
|
||||
marginBottom: 12,
|
||||
},
|
||||
cameraStatus: {
|
||||
fontSize: 14,
|
||||
color: '#999',
|
||||
},
|
||||
controls: {
|
||||
padding: 16,
|
||||
backgroundColor: '#fff',
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#eee',
|
||||
},
|
||||
button: {
|
||||
paddingVertical: 14,
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
},
|
||||
startButton: {
|
||||
backgroundColor: '#27ae60',
|
||||
},
|
||||
stopButton: {
|
||||
backgroundColor: '#e74c3c',
|
||||
},
|
||||
buttonText: {
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
results: {
|
||||
flex: 1,
|
||||
padding: 16,
|
||||
backgroundColor: '#fff',
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#eee',
|
||||
},
|
||||
resultsTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#333',
|
||||
marginBottom: 8,
|
||||
},
|
||||
resultsList: {
|
||||
flex: 1,
|
||||
},
|
||||
resultItem: {
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
marginBottom: 6,
|
||||
backgroundColor: '#f0f0f0',
|
||||
borderLeftWidth: 3,
|
||||
borderLeftColor: '#27ae60',
|
||||
borderRadius: 4,
|
||||
},
|
||||
resultCode: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
color: '#000',
|
||||
},
|
||||
resultFormat: {
|
||||
fontSize: 10,
|
||||
color: '#666',
|
||||
marginTop: 2,
|
||||
},
|
||||
error: {
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 16,
|
||||
backgroundColor: '#ffe0e0',
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#ffcccc',
|
||||
},
|
||||
errorText: {
|
||||
fontSize: 12,
|
||||
color: '#c00',
|
||||
fontWeight: '500',
|
||||
},
|
||||
});
|
||||
254
src/types/icamera.ts
Normal file
254
src/types/icamera.ts
Normal file
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* iCameraSDK TypeScript Types
|
||||
* Type definitions for the iCameraSDK React Native integration
|
||||
*/
|
||||
|
||||
export interface ScanResult {
|
||||
/**
|
||||
* The scanned code/data
|
||||
*/
|
||||
code: string;
|
||||
|
||||
/**
|
||||
* Format name of the scanned code
|
||||
* Examples: "QR_CODE", "CODE_128", "CODE_39", "EAN_13", "EAN_8"
|
||||
*/
|
||||
format: string;
|
||||
|
||||
/**
|
||||
* Numeric format code
|
||||
* QR_CODE: 32, CODE_128: 64, etc.
|
||||
*/
|
||||
formatCode: number;
|
||||
}
|
||||
|
||||
export interface ScannerError {
|
||||
/**
|
||||
* Error message
|
||||
*/
|
||||
error: string;
|
||||
|
||||
/**
|
||||
* The error class name
|
||||
*/
|
||||
errorType: string;
|
||||
}
|
||||
|
||||
export interface SDKConfig {
|
||||
/**
|
||||
* The presigned URL for uploads
|
||||
*/
|
||||
presignedUrl: string;
|
||||
|
||||
/**
|
||||
* List of enabled features
|
||||
*/
|
||||
features: CaptureFeature[];
|
||||
}
|
||||
|
||||
export type CaptureFeature = 'QR_CODE' | 'BAR_CODE' | 'CAPTURE_3D' | 'LENS';
|
||||
|
||||
export interface UseCameraScannerOptions {
|
||||
/**
|
||||
* Presigned URL for the SDK
|
||||
* Should be generated from your backend
|
||||
*/
|
||||
presignedUrl: string;
|
||||
|
||||
/**
|
||||
* Features to enable
|
||||
* @default ['QR_CODE', 'BAR_CODE']
|
||||
*/
|
||||
features?: CaptureFeature[];
|
||||
|
||||
/**
|
||||
* Callback when a code is successfully scanned
|
||||
*/
|
||||
onScanResult?: (result: ScanResult) => void;
|
||||
|
||||
/**
|
||||
* Callback when an error occurs
|
||||
*/
|
||||
onError?: (error: string) => void;
|
||||
}
|
||||
|
||||
export interface UseCameraScannerReturn {
|
||||
/**
|
||||
* Whether the SDK is initialized
|
||||
*/
|
||||
isInitialized: boolean;
|
||||
|
||||
/**
|
||||
* Whether scanning is currently active
|
||||
*/
|
||||
isScanning: boolean;
|
||||
|
||||
/**
|
||||
* Current error message, if any
|
||||
*/
|
||||
error: string | null;
|
||||
|
||||
/**
|
||||
* Start the scanner
|
||||
*/
|
||||
startScanning: () => Promise<void>;
|
||||
|
||||
/**
|
||||
* Stop the scanner
|
||||
*/
|
||||
stopScanning: () => Promise<void>;
|
||||
|
||||
/**
|
||||
* Get current SDK configuration
|
||||
*/
|
||||
getConfiguration: () => Promise<SDKConfig>;
|
||||
|
||||
/**
|
||||
* Clean up and shutdown the SDK
|
||||
*/
|
||||
cleanup: () => Promise<void>;
|
||||
}
|
||||
|
||||
export interface CameraScannerProps {
|
||||
/**
|
||||
* Presigned URL for the SDK
|
||||
*/
|
||||
presignedUrl: string;
|
||||
|
||||
/**
|
||||
* Callback when a code is scanned
|
||||
*/
|
||||
onScanResult?: (result: ScanResult) => void;
|
||||
|
||||
/**
|
||||
* Callback when an error occurs
|
||||
*/
|
||||
onError?: (error: string) => void;
|
||||
|
||||
/**
|
||||
* Callback when user closes the scanner
|
||||
*/
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export interface ScannerScreenProps {
|
||||
/**
|
||||
* Presigned URL for the SDK
|
||||
*/
|
||||
presignedUrl: string;
|
||||
|
||||
/**
|
||||
* Callback when a code is scanned
|
||||
*/
|
||||
onScan?: (result: ScanResult) => void;
|
||||
|
||||
/**
|
||||
* Callback when an error occurs
|
||||
*/
|
||||
onError?: (error: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Native module interface
|
||||
* Available as: const { ICameraSDK } = NativeModules;
|
||||
*/
|
||||
export interface ICameraSDKModule {
|
||||
/**
|
||||
* Initialize the SDK with presigned URL and features
|
||||
*/
|
||||
initialize(presignedUrl: string, features: string[]): Promise<string>;
|
||||
|
||||
/**
|
||||
* Start the scanner
|
||||
*/
|
||||
startScanner(): Promise<string>;
|
||||
|
||||
/**
|
||||
* Stop the scanner
|
||||
*/
|
||||
stopScanner(): Promise<string>;
|
||||
|
||||
/**
|
||||
* Check if SDK is initialized
|
||||
*/
|
||||
isInitialized(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Get current SDK configuration
|
||||
*/
|
||||
getConfiguration(): Promise<SDKConfig>;
|
||||
|
||||
/**
|
||||
* Shutdown the SDK
|
||||
*/
|
||||
shutdown(): Promise<string>;
|
||||
|
||||
/**
|
||||
* Add event listener (Native Module EventEmitter)
|
||||
*/
|
||||
addListener(eventType: string, listener: (...args: any[]) => void): void;
|
||||
|
||||
/**
|
||||
* Remove event listener
|
||||
*/
|
||||
removeListener(eventType: string): void;
|
||||
|
||||
/**
|
||||
* Remove all listeners
|
||||
*/
|
||||
removeAllListeners(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Barcode format constants
|
||||
*/
|
||||
export const BarcodeFormats = {
|
||||
QR_CODE: 'QR_CODE',
|
||||
CODE_128: 'CODE_128',
|
||||
CODE_39: 'CODE_39',
|
||||
CODE_93: 'CODE_93',
|
||||
CODABAR: 'CODABAR',
|
||||
DATA_MATRIX: 'DATA_MATRIX',
|
||||
EAN_13: 'EAN_13',
|
||||
EAN_8: 'EAN_8',
|
||||
ITF: 'ITF',
|
||||
PDF_417: 'PDF_417',
|
||||
UPC_A: 'UPC_A',
|
||||
UPC_E: 'UPC_E',
|
||||
UNKNOWN: 'UNKNOWN',
|
||||
} as const;
|
||||
|
||||
export type BarcodeFormat = typeof BarcodeFormats[keyof typeof BarcodeFormats];
|
||||
|
||||
/**
|
||||
* Capture features available in the SDK
|
||||
*/
|
||||
export const CaptureFeatures = {
|
||||
QR_CODE: 'QR_CODE' as CaptureFeature,
|
||||
BAR_CODE: 'BAR_CODE' as CaptureFeature,
|
||||
CAPTURE_3D: 'CAPTURE_3D' as CaptureFeature,
|
||||
LENS: 'LENS' as CaptureFeature,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Event types emitted by the scanner
|
||||
*/
|
||||
export const ScannerEvents = {
|
||||
RESULT: 'onScannerResult',
|
||||
ERROR: 'onScannerError',
|
||||
} as const;
|
||||
|
||||
export type ScannerEvent = typeof ScannerEvents[keyof typeof ScannerEvents];
|
||||
|
||||
/**
|
||||
* Error types
|
||||
*/
|
||||
export enum ScannerErrorType {
|
||||
InitializationError = 'INIT_ERROR',
|
||||
ScannerError = 'START_SCANNER_ERROR',
|
||||
PermissionError = 'PERMISSION_ERROR',
|
||||
LifecycleError = 'LIFECYCLE_ERROR',
|
||||
ConfigError = 'CONFIG_ERROR',
|
||||
NotInitializedError = 'NOT_INITIALIZED',
|
||||
ShutdownError = 'SHUTDOWN_ERROR',
|
||||
}
|
||||
Reference in New Issue
Block a user