mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			187 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Objective-C
		
	
			
		
		
	
	
			187 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Objective-C
		
	
| //
 | |
| //  Copyright (c) 2019 Open Whisper Systems. All rights reserved.
 | |
| //
 | |
| 
 | |
| #import "OWSQRCodeScanningViewController.h"
 | |
| #import "OWSBezierPathView.h"
 | |
| #import "UIColor+OWS.h"
 | |
| #import "UIView+OWS.h"
 | |
| 
 | |
| NS_ASSUME_NONNULL_BEGIN
 | |
| 
 | |
| @interface OWSQRCodeScanningViewController ()
 | |
| 
 | |
| @property (atomic) ZXCapture *capture;
 | |
| @property (nonatomic) BOOL captureEnabled;
 | |
| @property (nonatomic) BOOL hasCompletedCaptureSetup;
 | |
| @property (nonatomic) UIView *maskingView;
 | |
| @property (nonatomic) dispatch_queue_t captureQueue;
 | |
| 
 | |
| @end
 | |
| 
 | |
| #pragma mark -
 | |
| 
 | |
| @implementation OWSQRCodeScanningViewController
 | |
| 
 | |
| - (void)dealloc
 | |
| {
 | |
|     [self.capture.layer removeFromSuperlayer];
 | |
| }
 | |
| 
 | |
| - (instancetype)init
 | |
| {
 | |
|     self = [super init];
 | |
|     if (!self) {
 | |
|         return self;
 | |
|     }
 | |
| 
 | |
|     _captureEnabled = NO;
 | |
|     _captureQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 | |
| 
 | |
|     return self;
 | |
| }
 | |
| 
 | |
| - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
 | |
| {
 | |
|     self = [super initWithCoder:aDecoder];
 | |
|     if (!self) {
 | |
|         return self;
 | |
|     }
 | |
| 
 | |
|     _captureEnabled = NO;
 | |
|     _captureQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 | |
| 
 | |
|     return self;
 | |
| }
 | |
| 
 | |
| - (void)loadView
 | |
| {
 | |
|     [super loadView];
 | |
| 
 | |
|     OWSBezierPathView *maskingView = [OWSBezierPathView new];
 | |
|     self.maskingView = maskingView;
 | |
|     [maskingView setConfigureShapeLayerBlock:^(CAShapeLayer *layer, CGRect bounds) {
 | |
|         // Add a circular mask
 | |
|         UIBezierPath *path = [UIBezierPath bezierPathWithRect:bounds];
 | |
|         CGFloat margin = ScaleFromIPhone5To7Plus(24.f, 48.f);
 | |
|         CGFloat radius = MIN(bounds.size.width, bounds.size.height) * 0.5f - margin;
 | |
| 
 | |
|         // Center the circle's bounding rectangle
 | |
|         CGRect circleRect = CGRectMake(
 | |
|             bounds.size.width * 0.5f - radius, bounds.size.height * 0.5f - radius, radius * 2.f, radius * 2.f);
 | |
|         UIBezierPath *circlePath = [UIBezierPath bezierPathWithRoundedRect:circleRect cornerRadius:16.f];
 | |
|         [path appendPath:circlePath];
 | |
|         [path setUsesEvenOddFillRule:YES];
 | |
| 
 | |
|         layer.path = path.CGPath;
 | |
|         layer.fillRule = kCAFillRuleEvenOdd;
 | |
|         layer.fillColor = UIColor.lokiDarkestGray.CGColor;
 | |
|         layer.opacity = 0.32f;
 | |
|     }];
 | |
|     [self.view addSubview:maskingView];
 | |
|     [maskingView ows_autoPinToSuperviewEdges];
 | |
| }
 | |
| 
 | |
| - (void)viewWillAppear:(BOOL)animated
 | |
| {
 | |
|     [super viewWillAppear:animated];
 | |
| 
 | |
|     if (self.captureEnabled) {
 | |
|         [self startCapture];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (void)viewWillDisappear:(BOOL)animated
 | |
| {
 | |
|     [super viewWillDisappear:animated];
 | |
|     [self stopCapture];
 | |
| }
 | |
| 
 | |
| - (void)viewWillLayoutSubviews
 | |
| {
 | |
|     [super viewWillLayoutSubviews];
 | |
|     
 | |
|     // Note: When accessing 'capture.layer' if the setup hasn't been completed it
 | |
|     // will result in a layout being triggered which creates an infinite loop, this
 | |
|     // check prevents that case
 | |
|     if (self.hasCompletedCaptureSetup) {
 | |
|         self.capture.layer.frame = self.view.bounds;
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (void)startCapture
 | |
| {
 | |
|     self.captureEnabled = YES;
 | |
|     
 | |
|     // Note: The simulator doesn't support video but if we do try to start an
 | |
|     // AVCaptureSession it seems to hang on that particular thread indefinitely
 | |
|     // this will prevent us from trying to start a session on the simulator
 | |
| #if TARGET_OS_SIMULATOR
 | |
| #else
 | |
|     if (!self.capture) {
 | |
|         dispatch_async(self.captureQueue, ^{
 | |
|             self.capture = [[ZXCapture alloc] init];
 | |
|             self.capture.camera = self.capture.back;
 | |
|             self.capture.focusMode = AVCaptureFocusModeContinuousAutoFocus;
 | |
|             self.capture.delegate = self;
 | |
|             [self.capture start];
 | |
|             
 | |
|             // Note: When accessing the 'layer' for the first time it will create
 | |
|             // an instance of 'AVCaptureVideoPreviewLayer', this can hang a little
 | |
|             // so we do this on the background thread first
 | |
|             if (self.capture.layer) {}
 | |
|             
 | |
|             dispatch_async(dispatch_get_main_queue(), ^{
 | |
|                 self.capture.layer.frame = self.view.bounds;
 | |
|                 [self.view.layer addSublayer:self.capture.layer];
 | |
|                 [self.view bringSubviewToFront:self.maskingView];
 | |
|                 self.hasCompletedCaptureSetup = YES;
 | |
|             });
 | |
|         });
 | |
|     }
 | |
|     else {
 | |
|         dispatch_async(self.captureQueue, ^{
 | |
|             [self.capture start];
 | |
|         });
 | |
|     }
 | |
| #endif
 | |
| }
 | |
| 
 | |
| - (void)stopCapture
 | |
| {
 | |
|     self.captureEnabled = NO;
 | |
|     dispatch_async(self.captureQueue, ^{
 | |
|         [self.capture stop];
 | |
|     });
 | |
| }
 | |
| 
 | |
| - (void)captureResult:(ZXCapture *)capture result:(ZXResult *)result
 | |
| {
 | |
|     if (!self.captureEnabled) {
 | |
|         return;
 | |
|     }
 | |
|     [self stopCapture];
 | |
| 
 | |
|     // Vibrate
 | |
|     AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
 | |
| 
 | |
|     if (self.scanDelegate) {
 | |
|         if ([self.scanDelegate respondsToSelector:@selector(controller:didDetectQRCodeWithData:)]) {
 | |
|             OWSLogInfo(@"Scanned Data Code.");
 | |
|             ZXByteArray *byteArray = result.resultMetadata[@(kResultMetadataTypeByteSegments)][0];
 | |
|             NSData *decodedData = [NSData dataWithBytes:byteArray.array length:byteArray.length];
 | |
| 
 | |
|             [self.scanDelegate controller:self didDetectQRCodeWithData:decodedData];
 | |
|         }
 | |
| 
 | |
|         if ([self.scanDelegate respondsToSelector:@selector(controller:didDetectQRCodeWithString:)]) {
 | |
|             OWSLogInfo(@"Scanned String Code.");
 | |
|             [self.scanDelegate controller:self didDetectQRCodeWithString:result.text];
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| NS_ASSUME_NONNULL_END
 |