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 = (state.cornerPoints || []).map((point, index) => ( 112 <Svg.Circle 113 cx={point.x} 114 cy={point.y} 115 r={3} 116 strokeWidth={0.5} 117 stroke="#CF4048" 118 fill="#CF4048" 119 key={index} 120 /> 121 )); 122 123 return ( 124 <View style={styles.container}> 125 <BarCodeScanner 126 onLayout={setCanvasDimensions} 127 onBarCodeScanned={handleBarCodeScanned} 128 barCodeTypes={[ 129 BarCodeScanner.Constants.BarCodeType.qr, 130 BarCodeScanner.Constants.BarCodeType.pdf417, 131 BarCodeScanner.Constants.BarCodeType.code128, 132 BarCodeScanner.Constants.BarCodeType.code39, 133 ]} 134 type={state.type} 135 style={styles.preview} 136 /> 137 138 {state.haveDimensions && ( 139 <Svg.Svg height={state.canvasHeight} width={state.canvasWidth} style={styles.svg}> 140 <Svg.Circle 141 cx={state.canvasWidth! / 2} 142 cy={state.canvasHeight! / 2} 143 r={2} 144 strokeWidth={2.5} 145 stroke="#e74c3c" 146 fill="#f1c40f" 147 /> 148 {state.showBoundingBox && state.cornerPointsString && ( 149 <Svg.Polygon 150 points={state.cornerPointsString} 151 strokeWidth={2} 152 stroke="#582E6E" 153 fill="none" 154 /> 155 )} 156 {state.showText && state.boundingBox && ( 157 <Svg.Text 158 fill="#CF4048" 159 stroke="#CF4048" 160 fontSize="14" 161 x={state.boundingBox.origin.x} 162 y={state.boundingBox.origin.y - 8}> 163 {state.data} 164 </Svg.Text> 165 )} 166 167 {circles} 168 </Svg.Svg> 169 )} 170 171 <View style={styles.toolbar}> 172 <Button color={BUTTON_COLOR} title="Direction" onPress={toggleType} /> 173 <Button color={BUTTON_COLOR} title="Orientation" onPress={toggleScreenOrientationState} /> 174 <Button color={BUTTON_COLOR} title="Bounding box" onPress={toggleBoundingBox} /> 175 <Button color={BUTTON_COLOR} title="Text" onPress={toggleText} /> 176 <Button color={BUTTON_COLOR} title="Alerting" onPress={toggleAlertingAboutResult} /> 177 </View> 178 </View> 179 ); 180} 181 182BarcodeScannerExample.navigationOptions = { 183 title: '<BarCodeScanner />', 184}; 185 186const styles = StyleSheet.create({ 187 container: { 188 flex: 1, 189 }, 190 preview: { 191 ...StyleSheet.absoluteFillObject, 192 backgroundColor: 'black', 193 }, 194 toolbar: { 195 position: 'absolute', 196 bottom: 0, 197 left: 0, 198 right: 0, 199 paddingVertical: 10, 200 paddingHorizontal: 10, 201 flexDirection: 'row', 202 justifyContent: 'space-between', 203 backgroundColor: 'rgba(255,255,255,0.2)', 204 }, 205 svg: { 206 position: 'absolute', 207 borderWidth: 2, 208 borderColor: 'red', 209 }, 210}); 211