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