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