Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
QRScannerView.swift 10.68 KiB
//
//  QRScannerView.swift
//  enzevalos_iphone
//
//  Created by Joscha on 01.08.17.
//  Copyright © 2018 fu-berlin.
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
//  https://www.appcoda.com/barcode-reader-swift/

import AVFoundation
import AudioToolbox // necessary only to vibrate on versions before iOS 10

class QRScannerView: ViewControllerPannable, AVCaptureMetadataOutputObjectsDelegate {
    @IBOutlet weak var topBar: UIView!
    @IBOutlet weak var bottomBar: UIView!
    @IBOutlet weak var topLabel: UILabel!
    @IBOutlet weak var bottomLabel: UILabel!

    var captureSession: AVCaptureSession?
    var videoPreviewLayer: AVCaptureVideoPreviewLayer?
    var qrCodeFrameView: CAShapeLayer?
    var qrCodeFrameColor: UIColor? {
        set {
            if let frame = qrCodeFrameView {
                frame.strokeColor = newValue?.cgColor
                frame.fillColor = newValue?.withAlphaComponent(0.4).cgColor
            }
        }
        get { return nil }
    }

    var fingerprint: String?
    var keyId: String? //used for logging
    var callback: (() -> ())?

    override func viewDidLoad() {
        super.viewDidLoad()

        topLabel.text = NSLocalizedString("verifyContact", comment: "")
        bottomLabel.text = NSLocalizedString("scanQRCode", comment: "")

        let topBlurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
        let bottomBlurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
        topBlurView.frame = topBar.bounds
        bottomBlurView.frame = bottomBar.bounds
        topBlurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        bottomBlurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        topBar.addSubview(topBlurView)
        topBar.sendSubviewToBack(topBlurView)
        bottomBar.addSubview(bottomBlurView)
        bottomBar.sendSubviewToBack(bottomBlurView)

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            self.topBar.translatesAutoresizingMaskIntoConstraints = true
            self.bottomBar.translatesAutoresizingMaskIntoConstraints = true
            self.bottomLabel.translatesAutoresizingMaskIntoConstraints = true
            self.topLabel.translatesAutoresizingMaskIntoConstraints = true
        }

        // Get an instance of the AVCaptureDevice class to initialize a device object and provide the video as the media type parameter.
        let captureDevice = AVCaptureDevice.default(for: AVMediaType.video)

        do {
            // Get an instance of the AVCaptureDeviceInput class using the previous device object.
            let input = try AVCaptureDeviceInput(device: captureDevice!)

            // Initialize the captureSession object.
            captureSession = AVCaptureSession()

            // Set the input device on the capture session.
            captureSession?.addInput(input)

            // Initialize a AVCaptureMetadataOutput object and set it as the output device to the capture session.
            let captureMetadataOutput = AVCaptureMetadataOutput()
            captureSession?.addOutput(captureMetadataOutput)

            // Set delegate and use the default dispatch queue to execute the call back
            captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
            captureMetadataOutput.metadataObjectTypes = [AVMetadataObject.ObjectType.qr]

            // Initialize the video preview layer and add it as a sublayer to the viewPreview view's layer.
            videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
            videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
            videoPreviewLayer?.frame = view.layer.bounds
            view.layer.addSublayer(videoPreviewLayer!)

            // Initialize QR Code Frame to highlight the QR code
            qrCodeFrameView = CAShapeLayer()

            if let qrCodeFrameView = qrCodeFrameView {
                qrCodeFrameColor = UIColor.orange
                qrCodeFrameView.lineWidth = 2
                qrCodeFrameView.lineJoin = CAShapeLayerLineJoin.miter
                view.layer.addSublayer(qrCodeFrameView)
            }

            view.bringSubviewToFront(topBar)
            view.bringSubviewToFront(bottomBar)

            // Start video capture.
            captureSession?.startRunning()
        } catch {
            // If any error occurs, simply print it out and don't continue any more.
            print(error)
            return
        }
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        AppUtility.lockOrientation(.allButUpsideDown)
    }

    @IBAction func close(_ sender: Any) {
//        Logger.queue.async(flags: .barrier) {
        Logger.log(verify: self.keyId ?? "noKeyID", open: false, success: false)
//        }
        dismiss(animated: true, completion: nil)
    }

    func metadataOutput(_ captureOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {

        // Check if the metadataObjects array is not nil and it contains at least one object.
        if metadataObjects.count == 0 {
            qrCodeFrameView?.path = nil
            bottomLabel.text = NSLocalizedString("scanQRCode", comment: "")
            return
        }

        if let metadataObj = metadataObjects[0] as? AVMetadataMachineReadableCodeObject, metadataObj.type == AVMetadataObject.ObjectType.qr {

            let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
            if let barCodeObject = barCodeObject as? AVMetadataMachineReadableCodeObject {
                let barcodeOverlayPath = barcodeOverlayPathWithCorners(barCodeObject.corners as! [CFDictionary])
                qrCodeFrameView?.path = barcodeOverlayPath
            }

            if let string = metadataObj.stringValue {
                let seperated = string.components(separatedBy: ":")
                if let fingerprint = fingerprint, seperated[0].caseInsensitiveCompare("OPENPGP4FPR") == ComparisonResult.orderedSame {
                    if seperated[1].caseInsensitiveCompare(fingerprint) == ComparisonResult.orderedSame {
                        qrCodeFrameColor = UIColor.green
                        captureSession?.stopRunning()
                        bottomLabel.text = NSLocalizedString("verifySuccess", comment: "Fingerprint was successfully verified")
                        if #available(iOS 10.0, *) {
                            let feedbackGenerator = UINotificationFeedbackGenerator()
                            feedbackGenerator.notificationOccurred(.success)
                        } else {
                            AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
                        }
                        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.5) {
                            self.dismiss(animated: true, completion: self.callback)
                        }
                    } else {
                        qrCodeFrameColor = UIColor.red
                        bottomLabel.text = NSLocalizedString("fingerprintMissmatch", comment: "Found fingerprint does not match")
                        captureSession?.stopRunning()
                        if #available(iOS 10.0, *) {
                            let feedbackGenerator = UINotificationFeedbackGenerator()
                            feedbackGenerator.notificationOccurred(.error)
                        } else {
                            AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
                        }
                        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {
                            let alert = UIAlertController(title: NSLocalizedString("fingerprintMissmatchShort", comment: "Found fingerprint does not match"), message: NSLocalizedString("fingerprintMissmatchText", comment: "Found fingerprint does not match"), preferredStyle: .alert)
                            alert.addAction(UIAlertAction(title: NSLocalizedString("MoreInformation", comment: "More Information"), style: .default, handler: {
                                    (action: UIAlertAction!) -> Void in
                                UIApplication.shared.open(URL(string: "https://letterbox-app.org")!, options: [:], completionHandler: nil)
                                    self.dismiss(animated: false, completion: nil)
                                }))
                            alert.addAction(UIAlertAction(title: NSLocalizedString("scanDifferentCode", comment: ""), style: .default, handler: {
                                    (action: UIAlertAction!) -> Void in
                                    self.qrCodeFrameView?.path = nil
                                    self.captureSession?.startRunning()
                                }))
                            alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "Cancel"), style: .cancel, handler: { (action: UIAlertAction!) -> Void in self.dismiss(animated: true, completion: nil) }))
                            self.present(alert, animated: true, completion: nil)
                        }
                    }
                } else {
                    qrCodeFrameColor = UIColor.orange
                    bottomLabel.text = NSLocalizedString("wrongQRCode", comment: "The found QR Code is not compatible")
                }
            }
        }
    }

    // from: https://developer.apple.com/library/content/samplecode/AVCamBarcode/Listings/AVCamBarcode_CameraViewController_swift.html#//apple_ref/doc/uid/TP40017312-AVCamBarcode_CameraViewController_swift-DontLinkElementID_4
    private func barcodeOverlayPathWithCorners(_ corners: [CFDictionary]) -> CGMutablePath {
        let path = CGMutablePath()

        if !corners.isEmpty {
            guard let corner = CGPoint(dictionaryRepresentation: corners[0]) else { return path }
            path.move(to: corner, transform: .identity)

            for cornerDictionary in corners {
                guard let corner = CGPoint(dictionaryRepresentation: cornerDictionary) else { return path }
                path.addLine(to: corner)
            }

            path.closeSubpath()
        }

        return path
    }
}