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