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