1eafa96e0SStanisław Chmiela// Copyright 2018-present 650 Industries. All rights reserved.
2eafa96e0SStanisław Chmiela
3eafa96e0SStanisław Chmiela#import <EXBarCodeScanner/EXBarCodeScannerView.h>
4eafa96e0SStanisław Chmiela#import <EXBarCodeScanner/EXBarCodeScanner.h>
5eafa96e0SStanisław Chmiela#import <EXBarCodeScanner/EXBarCodeScannerUtils.h>
6284d4840SŁukasz Kosmaty#import <EXBarCodeScanner/EXBarCodeCameraRequester.h>
7fb3d99c5STomasz Sapeta#import <ExpoModulesCore/EXPermissionsInterface.h>
8*314e850cSTomasz Sapeta#import <ExpoModulesCore/EXAppLifecycleService.h>
9*314e850cSTomasz Sapeta#import <ExpoModulesCore/EXUtilities.h>
10eafa96e0SStanisław Chmiela
11eafa96e0SStanisław Chmiela@interface EXBarCodeScannerView ()
12eafa96e0SStanisław Chmiela
13eafa96e0SStanisław Chmiela@property (nonatomic, strong) dispatch_queue_t sessionQueue;
14eafa96e0SStanisław Chmiela@property (nonatomic, strong) AVCaptureSession *session;
15eafa96e0SStanisław Chmiela@property (nonatomic, strong) AVCaptureDeviceInput *videoCaptureDeviceInput;
16eafa96e0SStanisław Chmiela@property (nonatomic, strong) AVCaptureMetadataOutput *metadataOutput;
17eafa96e0SStanisław Chmiela@property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer;
18eafa96e0SStanisław Chmiela@property (nonatomic, strong) id runtimeErrorHandlingObserver;
19eafa96e0SStanisław Chmiela@property (nonatomic, strong) EXBarCodeScanner *barCodeScanner;
20eafa96e0SStanisław Chmiela
21*314e850cSTomasz Sapeta@property (nonatomic, weak) EXModuleRegistry *moduleRegistry;
22fb3d99c5STomasz Sapeta@property (nonatomic, weak) id<EXPermissionsInterface> permissionsManager;
23*314e850cSTomasz Sapeta@property (nonatomic, weak) id<EXAppLifecycleService> lifecycleManager;
24eafa96e0SStanisław Chmiela
25eafa96e0SStanisław Chmiela@property (nonatomic, assign, getter=isSessionPaused) BOOL paused;
26eafa96e0SStanisław Chmiela
27*314e850cSTomasz Sapeta@property (nonatomic, copy) EXDirectEventBlock onCameraReady;
28*314e850cSTomasz Sapeta@property (nonatomic, copy) EXDirectEventBlock onMountError;
29*314e850cSTomasz Sapeta@property (nonatomic, copy) EXDirectEventBlock onBarCodeScanned;
30eafa96e0SStanisław Chmiela
31eafa96e0SStanisław Chmiela@end
32eafa96e0SStanisław Chmiela
33eafa96e0SStanisław Chmiela@implementation EXBarCodeScannerView
34eafa96e0SStanisław Chmiela
35*314e850cSTomasz Sapeta- (instancetype)initWithModuleRegistry:(EXModuleRegistry *)moduleRegistry
36eafa96e0SStanisław Chmiela{
37eafa96e0SStanisław Chmiela  if ((self = [super init])) {
38eafa96e0SStanisław Chmiela    _presetCamera = AVCaptureDevicePositionBack;
39eafa96e0SStanisław Chmiela    _moduleRegistry = moduleRegistry;
40eafa96e0SStanisław Chmiela    _session = [AVCaptureSession new];
41eafa96e0SStanisław Chmiela    _sessionQueue = dispatch_queue_create("barCodeScannerQueue", DISPATCH_QUEUE_SERIAL);
42*314e850cSTomasz Sapeta    _lifecycleManager = [moduleRegistry getModuleImplementingProtocol:@protocol(EXAppLifecycleListener)];
43fb3d99c5STomasz Sapeta    _permissionsManager = [moduleRegistry getModuleImplementingProtocol:@protocol(EXPermissionsInterface)];
44eafa96e0SStanisław Chmiela    _barCodeScanner = [self createBarCodeScanner];
45eafa96e0SStanisław Chmiela
46eafa96e0SStanisław Chmiela#if !(TARGET_IPHONE_SIMULATOR)
47eafa96e0SStanisław Chmiela    _previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session];
48eafa96e0SStanisław Chmiela    _previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
49eafa96e0SStanisław Chmiela    _previewLayer.needsDisplayOnBoundsChange = YES;
50bd4c8242SŁukasz Kosmaty    [_barCodeScanner setPreviewLayer:_previewLayer];
51eafa96e0SStanisław Chmiela#endif
52eafa96e0SStanisław Chmiela    _paused = NO;
53eafa96e0SStanisław Chmiela
54eafa96e0SStanisław Chmiela    [_lifecycleManager registerAppLifecycleListener:self];
55eafa96e0SStanisław Chmiela    [[NSNotificationCenter defaultCenter] addObserver:self
56eafa96e0SStanisław Chmiela                                             selector:@selector(orientationChanged:)
57eafa96e0SStanisław Chmiela                                                 name:UIDeviceOrientationDidChangeNotification
58eafa96e0SStanisław Chmiela                                               object:nil];
59eafa96e0SStanisław Chmiela
60eafa96e0SStanisław Chmiela    [self changePreviewOrientation:[UIApplication sharedApplication].statusBarOrientation];
61eafa96e0SStanisław Chmiela    [self initializeSession];
62eafa96e0SStanisław Chmiela  }
63eafa96e0SStanisław Chmiela  return self;
64eafa96e0SStanisław Chmiela}
65eafa96e0SStanisław Chmiela
66eafa96e0SStanisław Chmiela# pragma mark - events
67eafa96e0SStanisław Chmiela
68eafa96e0SStanisław Chmiela- (void)onReady
69eafa96e0SStanisław Chmiela{
70eafa96e0SStanisław Chmiela  if (_onCameraReady) {
71eafa96e0SStanisław Chmiela    _onCameraReady(nil);
72eafa96e0SStanisław Chmiela  }
73eafa96e0SStanisław Chmiela}
74eafa96e0SStanisław Chmiela
75eafa96e0SStanisław Chmiela- (void)onMountingError:(NSDictionary *)event
76eafa96e0SStanisław Chmiela{
77eafa96e0SStanisław Chmiela  if (_onMountError) {
78eafa96e0SStanisław Chmiela    _onMountError(event);
79eafa96e0SStanisław Chmiela  }
80eafa96e0SStanisław Chmiela}
81eafa96e0SStanisław Chmiela
82eafa96e0SStanisław Chmiela- (void)onBarCodeScanned:(NSDictionary *)event
83eafa96e0SStanisław Chmiela{
84eafa96e0SStanisław Chmiela  if (_onBarCodeScanned) {
85eafa96e0SStanisław Chmiela    _onBarCodeScanned(event);
86eafa96e0SStanisław Chmiela  }
87eafa96e0SStanisław Chmiela}
88eafa96e0SStanisław Chmiela
89eafa96e0SStanisław Chmiela# pragma mark - JS properties setters
90eafa96e0SStanisław Chmiela
91eafa96e0SStanisław Chmiela- (void)setPresetCamera:(NSInteger)presetCamera
92eafa96e0SStanisław Chmiela{
93eafa96e0SStanisław Chmiela  if (_presetCamera == presetCamera) {
94eafa96e0SStanisław Chmiela    return;
95eafa96e0SStanisław Chmiela  }
96eafa96e0SStanisław Chmiela  _presetCamera = presetCamera;
97*314e850cSTomasz Sapeta  EX_WEAKIFY(self);
98eafa96e0SStanisław Chmiela  dispatch_async(_sessionQueue, ^{
99*314e850cSTomasz Sapeta    EX_ENSURE_STRONGIFY(self);
100eafa96e0SStanisław Chmiela    [self initializeSession];
101eafa96e0SStanisław Chmiela  });
102eafa96e0SStanisław Chmiela}
103eafa96e0SStanisław Chmiela
104eafa96e0SStanisław Chmiela- (void)setBarCodeTypes:(NSArray *)barCodeTypes
105eafa96e0SStanisław Chmiela{
106eafa96e0SStanisław Chmiela  _barCodeTypes = barCodeTypes;
107eafa96e0SStanisław Chmiela  [_barCodeScanner setSettings:@{
108eafa96e0SStanisław Chmiela                                 @"barCodeTypes": barCodeTypes,
109eafa96e0SStanisław Chmiela                                 }];
110eafa96e0SStanisław Chmiela}
111eafa96e0SStanisław Chmiela
112eafa96e0SStanisław Chmiela# pragma mark - lifecycle
113eafa96e0SStanisław Chmiela
114eafa96e0SStanisław Chmiela- (void)layoutSubviews
115eafa96e0SStanisław Chmiela{
116eafa96e0SStanisław Chmiela  [super layoutSubviews];
117eafa96e0SStanisław Chmiela  _previewLayer.frame = self.bounds;
118eafa96e0SStanisław Chmiela  [self setBackgroundColor:[UIColor blackColor]];
119eafa96e0SStanisław Chmiela  [self.layer insertSublayer:_previewLayer atIndex:0];
120eafa96e0SStanisław Chmiela}
121eafa96e0SStanisław Chmiela
122eafa96e0SStanisław Chmiela- (void)removeFromSuperview
123eafa96e0SStanisław Chmiela{
124eafa96e0SStanisław Chmiela  [_lifecycleManager unregisterAppLifecycleListener:self];
125eafa96e0SStanisław Chmiela  [self stopSession];
126eafa96e0SStanisław Chmiela  [super removeFromSuperview];
127eafa96e0SStanisław Chmiela  [[NSNotificationCenter defaultCenter] removeObserver:self
128eafa96e0SStanisław Chmiela                                                  name:UIDeviceOrientationDidChangeNotification
129eafa96e0SStanisław Chmiela                                                object:nil];
130eafa96e0SStanisław Chmiela}
131eafa96e0SStanisław Chmiela
132eafa96e0SStanisław Chmiela- (void)onAppForegrounded
133eafa96e0SStanisław Chmiela{
134eafa96e0SStanisław Chmiela  if (![_session isRunning] && [self isSessionPaused]) {
135eafa96e0SStanisław Chmiela    _paused = NO;
136*314e850cSTomasz Sapeta    EX_WEAKIFY(self);
137eafa96e0SStanisław Chmiela    dispatch_async(_sessionQueue, ^{
138*314e850cSTomasz Sapeta      EX_ENSURE_STRONGIFY(self);
139eafa96e0SStanisław Chmiela      [self.session startRunning];
140eafa96e0SStanisław Chmiela    });
141eafa96e0SStanisław Chmiela  }
142eafa96e0SStanisław Chmiela}
143eafa96e0SStanisław Chmiela
144eafa96e0SStanisław Chmiela- (void)onAppBackgrounded
145eafa96e0SStanisław Chmiela{
146eafa96e0SStanisław Chmiela  if ([_session isRunning] && ![self isSessionPaused]) {
147eafa96e0SStanisław Chmiela    _paused = YES;
148*314e850cSTomasz Sapeta    EX_WEAKIFY(self);
149eafa96e0SStanisław Chmiela    dispatch_async(_sessionQueue, ^{
150*314e850cSTomasz Sapeta      EX_ENSURE_STRONGIFY(self);
151eafa96e0SStanisław Chmiela      [self.session stopRunning];
152eafa96e0SStanisław Chmiela    });
153eafa96e0SStanisław Chmiela  }
154eafa96e0SStanisław Chmiela}
155eafa96e0SStanisław Chmiela
156eafa96e0SStanisław Chmiela# pragma mark - orientation
157eafa96e0SStanisław Chmiela
158eafa96e0SStanisław Chmiela- (void)orientationChanged:(NSNotification *)notification
159eafa96e0SStanisław Chmiela{
160eafa96e0SStanisław Chmiela  UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
161eafa96e0SStanisław Chmiela  [self changePreviewOrientation:orientation];
162eafa96e0SStanisław Chmiela}
163eafa96e0SStanisław Chmiela
164eafa96e0SStanisław Chmiela- (void)changePreviewOrientation:(UIInterfaceOrientation)orientation
165eafa96e0SStanisław Chmiela{
166*314e850cSTomasz Sapeta  EX_WEAKIFY(self);
167eafa96e0SStanisław Chmiela  AVCaptureVideoOrientation videoOrientation = [EXBarCodeScannerUtils videoOrientationForInterfaceOrientation:orientation];
168*314e850cSTomasz Sapeta  [EXUtilities performSynchronouslyOnMainThread:^{
169*314e850cSTomasz Sapeta    EX_ENSURE_STRONGIFY(self);
170eafa96e0SStanisław Chmiela    if (self.previewLayer.connection.isVideoOrientationSupported) {
171eafa96e0SStanisław Chmiela      [self.previewLayer.connection setVideoOrientation:videoOrientation];
172eafa96e0SStanisław Chmiela    }
173eafa96e0SStanisław Chmiela  }];
174eafa96e0SStanisław Chmiela}
175eafa96e0SStanisław Chmiela
176eafa96e0SStanisław Chmiela# pragma mark - session
177eafa96e0SStanisław Chmiela
178eafa96e0SStanisław Chmiela- (BOOL)ensurePermissionsGranted
179eafa96e0SStanisław Chmiela{
180284d4840SŁukasz Kosmaty  if (![_permissionsManager hasGrantedPermissionUsingRequesterClass:[EXBareCodeCameraRequester class]]) {
181eafa96e0SStanisław Chmiela    [self onMountingError:@{@"message": @"Camera permissions not granted - component could not be rendered."}];
182eafa96e0SStanisław Chmiela    return FALSE;
183eafa96e0SStanisław Chmiela  }
184eafa96e0SStanisław Chmiela  return TRUE;
185eafa96e0SStanisław Chmiela}
186eafa96e0SStanisław Chmiela
187eafa96e0SStanisław Chmiela- (void)initializeSession
188eafa96e0SStanisław Chmiela{
189eafa96e0SStanisław Chmiela  if (_videoCaptureDeviceInput.device.position == _presetCamera) {
190eafa96e0SStanisław Chmiela    return;
191eafa96e0SStanisław Chmiela  }
192eafa96e0SStanisław Chmiela
193eafa96e0SStanisław Chmiela  __block UIInterfaceOrientation interfaceOrientation;
194*314e850cSTomasz Sapeta  [EXUtilities performSynchronouslyOnMainThread:^{
195eafa96e0SStanisław Chmiela    interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
196eafa96e0SStanisław Chmiela  }];
197eafa96e0SStanisław Chmiela  AVCaptureVideoOrientation orientation = [EXBarCodeScannerUtils videoOrientationForInterfaceOrientation:interfaceOrientation];
198eafa96e0SStanisław Chmiela
199*314e850cSTomasz Sapeta  EX_WEAKIFY(self);
200eafa96e0SStanisław Chmiela  dispatch_async(_sessionQueue, ^{
201*314e850cSTomasz Sapeta    EX_ENSURE_STRONGIFY(self);
202eafa96e0SStanisław Chmiela
203eafa96e0SStanisław Chmiela    [self.session beginConfiguration];
204eafa96e0SStanisław Chmiela
205eafa96e0SStanisław Chmiela    NSError *error = nil;
206eafa96e0SStanisław Chmiela    AVCaptureDevice *captureDevice = [EXBarCodeScannerUtils deviceWithMediaType:AVMediaTypeVideo
207eafa96e0SStanisław Chmiela                                                             preferringPosition:self.presetCamera];
208eafa96e0SStanisław Chmiela    AVCaptureDeviceInput *captureDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
209eafa96e0SStanisław Chmiela
210eafa96e0SStanisław Chmiela    if (error || captureDeviceInput == nil) {
211eafa96e0SStanisław Chmiela      NSString *errorMessage = @"Camera could not be started - ";
212eafa96e0SStanisław Chmiela      if (error) {
213eafa96e0SStanisław Chmiela        errorMessage = [errorMessage stringByAppendingString:[error description]];
214eafa96e0SStanisław Chmiela      } else {
215eafa96e0SStanisław Chmiela        errorMessage = [errorMessage stringByAppendingString:@"there's no captureDeviceInput available"];
216eafa96e0SStanisław Chmiela      }
217eafa96e0SStanisław Chmiela      [self onMountingError:@{@"message": errorMessage}];
218eafa96e0SStanisław Chmiela      return;
219eafa96e0SStanisław Chmiela    }
220eafa96e0SStanisław Chmiela
221eafa96e0SStanisław Chmiela    [self.session removeInput:self.videoCaptureDeviceInput];
222eafa96e0SStanisław Chmiela    if ([self.session canAddInput:captureDeviceInput]) {
223eafa96e0SStanisław Chmiela      [self.session addInput:captureDeviceInput];
224eafa96e0SStanisław Chmiela
225eafa96e0SStanisław Chmiela      self.videoCaptureDeviceInput = captureDeviceInput;
226eafa96e0SStanisław Chmiela      [self.previewLayer.connection setVideoOrientation:orientation];
227eafa96e0SStanisław Chmiela    }
228eafa96e0SStanisław Chmiela
229eafa96e0SStanisław Chmiela    [self.session commitConfiguration];
230eafa96e0SStanisław Chmiela    if (!self.session.isRunning) {
231eafa96e0SStanisław Chmiela      [self startSession];
232eafa96e0SStanisław Chmiela    }
233eafa96e0SStanisław Chmiela  });
234eafa96e0SStanisław Chmiela}
235eafa96e0SStanisław Chmiela
236eafa96e0SStanisław Chmiela- (void)startSession
237eafa96e0SStanisław Chmiela{
2386a392fcaSStanisław Chmiela#pragma clang diagnostic push
2396a392fcaSStanisław Chmiela#pragma clang diagnostic ignored "-Wunreachable-code"
240eafa96e0SStanisław Chmiela#if TARGET_IPHONE_SIMULATOR
241eafa96e0SStanisław Chmiela  return;
242eafa96e0SStanisław Chmiela#endif
243eafa96e0SStanisław Chmiela  if (![self ensurePermissionsGranted]) {
244eafa96e0SStanisław Chmiela    return;
245eafa96e0SStanisław Chmiela  };
246eafa96e0SStanisław Chmiela
247*314e850cSTomasz Sapeta  EX_WEAKIFY(self);
248eafa96e0SStanisław Chmiela  dispatch_async(_sessionQueue, ^{
249*314e850cSTomasz Sapeta    EX_ENSURE_STRONGIFY(self);
250eafa96e0SStanisław Chmiela
251eafa96e0SStanisław Chmiela    if (self.presetCamera == AVCaptureDevicePositionUnspecified) {
252eafa96e0SStanisław Chmiela      return;
253eafa96e0SStanisław Chmiela    }
254eafa96e0SStanisław Chmiela
255eafa96e0SStanisław Chmiela    [self setRuntimeErrorHandlingObserver:
256eafa96e0SStanisław Chmiela     [[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureSessionRuntimeErrorNotification
257eafa96e0SStanisław Chmiela                                                       object:self.session
258eafa96e0SStanisław Chmiela                                                        queue:nil
259eafa96e0SStanisław Chmiela                                                   usingBlock:^(NSNotification *note) {
260*314e850cSTomasz Sapeta      EX_ENSURE_STRONGIFY(self);
261eafa96e0SStanisław Chmiela      dispatch_async(self.sessionQueue, ^{
262*314e850cSTomasz Sapeta        EX_ENSURE_STRONGIFY(self);
263eafa96e0SStanisław Chmiela        // Manually restarting the session since it must have been stopped due to an error.
264eafa96e0SStanisław Chmiela        [self.session startRunning];
265eafa96e0SStanisław Chmiela        [self onReady];
266eafa96e0SStanisław Chmiela      });
267eafa96e0SStanisław Chmiela    }]];
268eafa96e0SStanisław Chmiela
269eafa96e0SStanisław Chmiela    [self.barCodeScanner maybeStartBarCodeScanning];
270eafa96e0SStanisław Chmiela
271eafa96e0SStanisław Chmiela    [self.session startRunning];
272eafa96e0SStanisław Chmiela    [self onReady];
273eafa96e0SStanisław Chmiela  });
2746a392fcaSStanisław Chmiela#pragma clang diagnostic pop
275eafa96e0SStanisław Chmiela}
276eafa96e0SStanisław Chmiela
277eafa96e0SStanisław Chmiela- (void)stopSession
278eafa96e0SStanisław Chmiela{
279eafa96e0SStanisław Chmiela#if TARGET_IPHONE_SIMULATOR
280eafa96e0SStanisław Chmiela  return;
281eafa96e0SStanisław Chmiela#endif
282*314e850cSTomasz Sapeta  EX_WEAKIFY(self);
283eafa96e0SStanisław Chmiela  dispatch_async(_sessionQueue, ^{
284*314e850cSTomasz Sapeta    EX_ENSURE_STRONGIFY(self);
285eafa96e0SStanisław Chmiela
286eafa96e0SStanisław Chmiela    [self.barCodeScanner stopBarCodeScanning];
287eafa96e0SStanisław Chmiela
288eafa96e0SStanisław Chmiela    [self.previewLayer removeFromSuperlayer];
289eafa96e0SStanisław Chmiela    [self.session commitConfiguration];
290eafa96e0SStanisław Chmiela    [self.session stopRunning];
291eafa96e0SStanisław Chmiela    for (AVCaptureInput *input in self.session.inputs) {
292eafa96e0SStanisław Chmiela      [self.session removeInput:input];
293eafa96e0SStanisław Chmiela    }
294eafa96e0SStanisław Chmiela
295eafa96e0SStanisław Chmiela    for (AVCaptureOutput *output in self.session.outputs) {
296eafa96e0SStanisław Chmiela      [self.session removeOutput:output];
297eafa96e0SStanisław Chmiela    }
298eafa96e0SStanisław Chmiela  });
299eafa96e0SStanisław Chmiela}
300eafa96e0SStanisław Chmiela
301eafa96e0SStanisław Chmiela# pragma mark - BarCode scanner
302eafa96e0SStanisław Chmiela
303eafa96e0SStanisław Chmiela- (EXBarCodeScanner *)createBarCodeScanner
304eafa96e0SStanisław Chmiela{
305eafa96e0SStanisław Chmiela  EXBarCodeScanner *barCodeScanner = [EXBarCodeScanner new];
306eafa96e0SStanisław Chmiela  [barCodeScanner setSession:_session];
307eafa96e0SStanisław Chmiela  [barCodeScanner setSessionQueue:_sessionQueue];
308*314e850cSTomasz Sapeta  EX_WEAKIFY(self);
309eafa96e0SStanisław Chmiela  [barCodeScanner setOnBarCodeScanned:^(NSDictionary *body) {
310*314e850cSTomasz Sapeta    EX_ENSURE_STRONGIFY(self);
311eafa96e0SStanisław Chmiela    [self onBarCodeScanned:body];
312eafa96e0SStanisław Chmiela  }];
313eafa96e0SStanisław Chmiela  [barCodeScanner setIsEnabled:true];
314eafa96e0SStanisław Chmiela  return barCodeScanner;
315eafa96e0SStanisław Chmiela}
316eafa96e0SStanisław Chmiela
317eafa96e0SStanisław Chmiela@end
318