1import { usePermissions } from '@use-expo/permissions'; 2import { BarCodeScanner, BarCodePoint, BarCodeEvent, BarCodeBounds } from 'expo-barcode-scanner'; 3import * as Permissions from 'expo-permissions'; 4import * as ScreenOrientation from 'expo-screen-orientation'; 5import React from 'react'; 6import { Button, Platform, StyleSheet, Text, View } from 'react-native'; 7import * as Svg from 'react-native-svg'; 8 9const BUTTON_COLOR = Platform.OS === 'ios' ? '#fff' : '#666'; 10 11type State = { 12 type: any; 13 cornerPoints?: BarCodePoint[]; 14 alerting: boolean; 15 haveDimensions: boolean; 16 canvasHeight?: number; 17 canvasWidth?: number; 18 boundingBox?: BarCodeBounds; 19 cornerPointsString?: string; 20 showBoundingBox: boolean; 21 showText: boolean; 22 data: string; 23}; 24 25const initialState: State = { 26 type: BarCodeScanner.Constants.Type.back, 27 alerting: false, 28 haveDimensions: false, 29 showBoundingBox: false, 30 data: '', 31 showText: false, 32}; 33 34function reducer(state: State, action: Partial<State>): State { 35 return { 36 ...state, 37 ...action, 38 }; 39} 40 41export default function BarcodeScannerScreen() { 42 const [isPermissionsGranted] = usePermissions(Permissions.CAMERA, { ask: true }); 43 44 if (!isPermissionsGranted) { 45 return ( 46 <View style={styles.container}> 47 <Text>You have not granted permission to use the camera on this device!</Text> 48 </View> 49 ); 50 } 51 52 return <BarcodeScannerExample />; 53} 54 55function BarcodeScannerExample() { 56 const [state, dispatch] = React.useReducer(reducer, initialState); 57 58 let canChangeOrientation = false; 59 60 const toggleAlertingAboutResult = () => { 61 dispatch({ alerting: !state.alerting }); 62 }; 63 64 const toggleScreenOrientationState = () => { 65 if (canChangeOrientation) { 66 ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP); 67 } else { 68 ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.ALL); 69 } 70 canChangeOrientation = !canChangeOrientation; 71 }; 72 73 const setCanvasDimensions = ({ nativeEvent: { layout } }: any) => { 74 dispatch({ canvasWidth: layout.width, canvasHeight: layout.height, haveDimensions: true }); 75 }; 76 77 const toggleType = () => { 78 dispatch({ 79 type: 80 state.type === BarCodeScanner.Constants.Type.back 81 ? BarCodeScanner.Constants.Type.front 82 : BarCodeScanner.Constants.Type.back, 83 }); 84 }; 85 86 const handleBarCodeScanned = (barCodeEvent: any) => { 87 if (state.alerting) { 88 requestAnimationFrame(() => { 89 alert(JSON.stringify(barCodeEvent)); 90 }); 91 } 92 dispatch({ 93 data: barCodeEvent.data, 94 cornerPoints: barCodeEvent.cornerPoints, 95 boundingBox: barCodeEvent.bounds, 96 cornerPointsString: getPointsString(barCodeEvent.cornerPoints), 97 }); 98 }; 99 100 const toggleText = () => dispatch({ showText: !state.showText }); 101 102 const toggleBoundingBox = () => dispatch({ showBoundingBox: !state.showBoundingBox }); 103 104 const getPointsString = (barCodePoints?: BarCodePoint[]): string | undefined => { 105 if (!barCodePoints) { 106 return; 107 } 108 return barCodePoints.map(({ x, y }) => `${Math.round(x)},${Math.round(y)}`).join(' '); 109 }; 110 111 const circles = []; 112 113 if (state.cornerPoints) { 114 for (const point of state.cornerPoints) { 115 circles.push( 116 <Svg.Circle cx={point.x} cy={point.y} r={2} strokeWidth={0.1} stroke="gray" fill="green" /> 117 ); 118 } 119 } 120 121 return ( 122 <View style={styles.container}> 123 <BarCodeScanner 124 onLayout={setCanvasDimensions} 125 onBarCodeScanned={handleBarCodeScanned} 126 barCodeTypes={[ 127 BarCodeScanner.Constants.BarCodeType.qr, 128 BarCodeScanner.Constants.BarCodeType.pdf417, 129 BarCodeScanner.Constants.BarCodeType.code128, 130 BarCodeScanner.Constants.BarCodeType.code39, 131 ]} 132 type={state.type} 133 style={styles.preview} 134 /> 135 136 {state.haveDimensions && ( 137 <Svg.Svg height={state.canvasHeight} width={state.canvasWidth} style={styles.svg}> 138 <Svg.Circle 139 cx={state.canvasWidth! / 2} 140 cy={state.canvasHeight! / 2} 141 r={2} 142 strokeWidth={2.5} 143 stroke="#e74c3c" 144 fill="#f1c40f" 145 /> 146 {state.showBoundingBox && state.cornerPointsString && ( 147 <Svg.Polygon 148 points={state.cornerPointsString} 149 strokeWidth={2} 150 stroke="#582E6E" 151 fill="none" 152 /> 153 )} 154 {state.showText && state.boundingBox && ( 155 <Svg.Text 156 fill="#CF4048" 157 stroke="#CF4048" 158 fontSize="14" 159 x={state.boundingBox.origin.x} 160 y={state.boundingBox.origin.y - 8}> 161 {state.data} 162 </Svg.Text> 163 )} 164 165 {circles} 166 </Svg.Svg> 167 )} 168 169 <View style={styles.toolbar}> 170 <Button color={BUTTON_COLOR} title="Direction" onPress={toggleType} /> 171 <Button color={BUTTON_COLOR} title="Orientation" onPress={toggleScreenOrientationState} /> 172 <Button color={BUTTON_COLOR} title="Bounding box" onPress={toggleBoundingBox} /> 173 <Button color={BUTTON_COLOR} title="Text" onPress={toggleText} /> 174 <Button color={BUTTON_COLOR} title="Alerting" onPress={toggleAlertingAboutResult} /> 175 </View> 176 </View> 177 ); 178} 179 180BarcodeScannerExample.navigationOptions = { 181 title: '<BarCodeScanner />', 182}; 183 184const styles = StyleSheet.create({ 185 container: { 186 flex: 1, 187 }, 188 preview: { 189 ...StyleSheet.absoluteFillObject, 190 backgroundColor: 'black', 191 }, 192 toolbar: { 193 position: 'absolute', 194 bottom: 0, 195 left: 0, 196 right: 0, 197 paddingVertical: 10, 198 paddingHorizontal: 10, 199 flexDirection: 'row', 200 justifyContent: 'space-between', 201 backgroundColor: 'rgba(255,255,255,0.2)', 202 }, 203 svg: { 204 position: 'absolute', 205 borderWidth: 2, 206 borderColor: 'red', 207 }, 208}); 209