1import { UnavailabilityError } from '@unimodules/core';
2import { PermissionResponse, PermissionStatus } from 'expo-modules-core';
3import * as React from 'react';
4import { Platform, ViewProps } from 'react-native';
5
6import ExpoBarCodeScannerModule from './ExpoBarCodeScannerModule';
7import ExpoBarCodeScannerView from './ExpoBarCodeScannerView';
8
9const { BarCodeType, Type } = ExpoBarCodeScannerModule;
10
11const EVENT_THROTTLE_MS = 500;
12
13export type BarCodePoint = {
14  x: number;
15  y: number;
16};
17
18export type BarCodeSize = {
19  height: number;
20  width: number;
21};
22
23export type BarCodeBounds = {
24  origin: BarCodePoint;
25  size: BarCodeSize;
26};
27
28export type BarCodeScannerResult = {
29  type: string;
30  data: string;
31  bounds?: BarCodeBounds;
32  cornerPoints?: BarCodePoint[];
33};
34
35export type BarCodeEvent = BarCodeScannerResult & {
36  target?: number;
37};
38
39export type BarCodeEventCallbackArguments = {
40  nativeEvent: BarCodeEvent;
41};
42
43export type BarCodeScannedCallback = (params: BarCodeEvent) => void;
44
45export { PermissionResponse, PermissionStatus };
46
47export interface BarCodeScannerProps extends ViewProps {
48  type?: 'front' | 'back' | number;
49  barCodeTypes?: string[];
50  onBarCodeScanned?: BarCodeScannedCallback;
51}
52
53export class BarCodeScanner extends React.Component<BarCodeScannerProps> {
54  lastEvents: { [key: string]: any } = {};
55  lastEventsTimes: { [key: string]: any } = {};
56
57  static Constants = {
58    BarCodeType,
59    Type,
60  };
61
62  static ConversionTables = {
63    type: Type,
64  };
65
66  static defaultProps = {
67    type: Type.back,
68    barCodeTypes: Object.values(BarCodeType),
69  };
70
71  static async getPermissionsAsync(): Promise<PermissionResponse> {
72    return ExpoBarCodeScannerModule.getPermissionsAsync();
73  }
74
75  static async requestPermissionsAsync(): Promise<PermissionResponse> {
76    return ExpoBarCodeScannerModule.requestPermissionsAsync();
77  }
78
79  static async scanFromURLAsync(
80    url: string,
81    barCodeTypes: string[] = Object.values(BarCodeType)
82  ): Promise<BarCodeScannerResult[]> {
83    if (!ExpoBarCodeScannerModule.scanFromURLAsync) {
84      throw new UnavailabilityError('expo-barcode-scanner', 'scanFromURLAsync');
85    }
86    if (Array.isArray(barCodeTypes) && !barCodeTypes.length) {
87      throw new Error('No barCodeTypes specified; provide at least one barCodeType for scanner');
88    }
89
90    if (Platform.OS === 'ios') {
91      if (Array.isArray(barCodeTypes) && !barCodeTypes.includes(BarCodeType.qr)) {
92        // Only QR type is supported on iOS, fail if one tries to use other types
93        throw new Error('Only QR type is supported by scanFromURLAsync() on iOS');
94      }
95      // on iOS use only supported QR type
96      return await ExpoBarCodeScannerModule.scanFromURLAsync(url, [BarCodeType.qr]);
97    }
98
99    // On other platforms, if barCodeTypes is not provided, use all available types
100    return await ExpoBarCodeScannerModule.scanFromURLAsync(url, barCodeTypes);
101  }
102
103  render() {
104    const nativeProps = this.convertNativeProps(this.props);
105    const { onBarCodeScanned } = this.props;
106    return (
107      <ExpoBarCodeScannerView
108        {...nativeProps}
109        onBarCodeScanned={this.onObjectDetected(onBarCodeScanned)}
110      />
111    );
112  }
113
114  onObjectDetected = (callback?: BarCodeScannedCallback) => ({
115    nativeEvent,
116  }: BarCodeEventCallbackArguments) => {
117    const { type } = nativeEvent;
118    if (
119      this.lastEvents[type] &&
120      this.lastEventsTimes[type] &&
121      JSON.stringify(nativeEvent) === this.lastEvents[type] &&
122      Date.now() - this.lastEventsTimes[type] < EVENT_THROTTLE_MS
123    ) {
124      return;
125    }
126
127    if (callback) {
128      callback(nativeEvent);
129      this.lastEventsTimes[type] = new Date();
130      this.lastEvents[type] = JSON.stringify(nativeEvent);
131    }
132  };
133
134  convertNativeProps(props: BarCodeScannerProps) {
135    const nativeProps: BarCodeScannerProps = {};
136
137    for (const [key, value] of Object.entries(props)) {
138      if (typeof value === 'string' && BarCodeScanner.ConversionTables[key]) {
139        nativeProps[key] = BarCodeScanner.ConversionTables[key][value];
140      } else {
141        nativeProps[key] = value;
142      }
143    }
144
145    return nativeProps;
146  }
147}
148
149export const { Constants, getPermissionsAsync, requestPermissionsAsync } = BarCodeScanner;
150