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