import React, { forwardRef, useEffect, useImperativeHandle, useState, type ReactElement, } from 'react'; import { ActivityIndicator, NativeModules, PermissionsAndroid, Platform, StyleSheet, Text, UIManager, View, findNodeHandle, requireNativeComponent, type NativeSyntheticEvent, type ViewProps, } from 'react-native'; export interface CardScannerResponse { cardNumber?: string; expiryYear?: string; expiryMonth?: string; holderName?: string; } export interface CardScannerHandle { toggleFlash(): void; resetResult(): void; startCamera(): void; stopCamera(): void; } interface CardScannerProps extends ViewProps { didCardScan?: (props: CardScannerResponse) => void; frameColor?: string; PermissionCheckingComponent?: ReactElement; NotAuthorizedComponent?: ReactElement; disabled?: boolean; useAppleVision?: boolean; } const ComponentName = 'CardScannerView'; const ComponentVisionName = 'CardScannerVision'; type CommandsType = { toggleFlash: number; resetResult: number; startScanCard: number; stopScanCard: number; }; const CardScanner: React.ForwardRefRenderFunction< CardScannerHandle, CardScannerProps > = ( { didCardScan, frameColor, PermissionCheckingComponent, NotAuthorizedComponent, disabled, useAppleVision, ...props }, ref ) => { const [viewId, setViewId] = useState(null); const [isPermissionChecked, setIsPermissionChecked] = useState(false); const [isAuthorized, setIsAuthorized] = useState(false); const isEnableAppleVision = useAppleVision && Platform.OS === 'ios' && parseInt(Platform.Version, 10) >= 13; useEffect(() => { checkCameraPermission().then((isGranted: boolean) => { setIsAuthorized(isGranted); setIsPermissionChecked(true); }); }, []); useImperativeHandle( ref, () => { const Commands = UIManager.getViewManagerConfig( isEnableAppleVision ? ComponentVisionName : ComponentName ).Commands as CommandsType; const toggleFlashId = getCommandId(Commands.toggleFlash); const resetResultId = getCommandId(Commands.resetResult); const startCameraId = getCommandId(Commands.startScanCard); const stopCameraId = getCommandId(Commands.stopScanCard); return { toggleFlash() { UIManager.dispatchViewManagerCommand(viewId, toggleFlashId, []); }, resetResult() { UIManager.dispatchViewManagerCommand(viewId, resetResultId, []); }, startCamera() { UIManager.dispatchViewManagerCommand(viewId, startCameraId, []); }, stopCamera() { UIManager.dispatchViewManagerCommand(viewId, stopCameraId, []); }, }; }, [viewId, isEnableAppleVision] ); const _onDidScanCard = (res: NativeSyntheticEvent) => { didCardScan && didCardScan(res.nativeEvent); }; if (!isPermissionChecked) { if (PermissionCheckingComponent) { return PermissionCheckingComponent; } return ( ); } if (!isAuthorized) { if (NotAuthorizedComponent) { return NotAuthorizedComponent; } return ( Need "Camera Permission" to scan your card ); } const CardScannerComponent = isEnableAppleVision ? CardScannerVision : CardScannerView; return ( {!disabled && ( { setViewId(findNodeHandle(r)); }} frameColor={frameColor} /> )} ); }; export default forwardRef(CardScanner); const styles = StyleSheet.create({ authorization: { flex: 1, alignItems: 'center', justifyContent: 'center' }, authorizationText: { textAlign: 'center', padding: 16 }, }); const checkCameraPermission = async () => { try { let hasCameraPermissions = false; if (Platform.OS === 'android') { const permissionStatus = await PermissionsAndroid.request( 'android.permission.CAMERA' ); hasCameraPermissions = permissionStatus === PermissionsAndroid.RESULTS.GRANTED; } else { const permissionStatus = await NativeModules.CardScanner.requestPermission(); hasCameraPermissions = permissionStatus.status === 'granted'; } return hasCameraPermissions; } catch (error) { return false; } }; const getCommandId = (commandId: number) => Platform.select({ android: `${commandId}`, default: commandId, }); interface ScannerNativeProps extends ViewProps { ref: any; onDidScanCard: (props: NativeSyntheticEvent) => void; frameColor?: string; } const LINKING_ERROR = `The package 'rn-card-scanner' doesn't seem to be linked. Make sure: \n\n` + Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo managed workflow\n'; const CardScannerView = UIManager.getViewManagerConfig(ComponentName) != null ? requireNativeComponent(ComponentName) : () => { throw new Error(LINKING_ERROR); }; const CardScannerVision = UIManager.getViewManagerConfig(ComponentVisionName) != null ? requireNativeComponent(ComponentVisionName) : () => { throw new Error(LINKING_ERROR); };