wireguard-apple/Sources/WireGuardApp/UI/iOS/ViewController/QRScanViewController.swift

156 lines
6.4 KiB
Swift

// SPDX-License-Identifier: MIT
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import AVFoundation
import UIKit
protocol QRScanViewControllerDelegate: class {
func addScannedQRCode(tunnelConfiguration: TunnelConfiguration, qrScanViewController: QRScanViewController, completionHandler: (() -> Void)?)
}
class QRScanViewController: UIViewController {
weak var delegate: QRScanViewControllerDelegate?
var captureSession: AVCaptureSession? = AVCaptureSession()
let metadataOutput = AVCaptureMetadataOutput()
var previewLayer: AVCaptureVideoPreviewLayer?
override func viewDidLoad() {
super.viewDidLoad()
title = tr("scanQRCodeViewTitle")
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelTapped))
let tipLabel = UILabel()
tipLabel.text = tr("scanQRCodeTipText")
tipLabel.adjustsFontSizeToFitWidth = true
tipLabel.textColor = .lightGray
tipLabel.textAlignment = .center
view.addSubview(tipLabel)
tipLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tipLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
tipLabel.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
tipLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -32)
])
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video),
let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice),
let captureSession = captureSession,
captureSession.canAddInput(videoInput),
captureSession.canAddOutput(metadataOutput) else {
scanDidEncounterError(title: tr("alertScanQRCodeCameraUnsupportedTitle"), message: tr("alertScanQRCodeCameraUnsupportedMessage"))
return
}
captureSession.addInput(videoInput)
captureSession.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(self, queue: .main)
metadataOutput.metadataObjectTypes = [.qr]
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.insertSublayer(previewLayer, at: 0)
self.previewLayer = previewLayer
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if captureSession?.isRunning == false {
captureSession?.startRunning()
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if captureSession?.isRunning == true {
captureSession?.stopRunning()
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let connection = previewLayer?.connection {
let currentDevice = UIDevice.current
let orientation = currentDevice.orientation
let previewLayerConnection = connection
if previewLayerConnection.isVideoOrientationSupported {
switch orientation {
case .portrait:
previewLayerConnection.videoOrientation = .portrait
case .landscapeRight:
previewLayerConnection.videoOrientation = .landscapeLeft
case .landscapeLeft:
previewLayerConnection.videoOrientation = .landscapeRight
case .portraitUpsideDown:
previewLayerConnection.videoOrientation = .portraitUpsideDown
default:
previewLayerConnection.videoOrientation = .portrait
}
}
}
previewLayer?.frame = view.bounds
}
func scanDidComplete(withCode code: String) {
let scannedTunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: code, called: "Scanned")
guard let tunnelConfiguration = scannedTunnelConfiguration else {
scanDidEncounterError(title: tr("alertScanQRCodeInvalidQRCodeTitle"), message: tr("alertScanQRCodeInvalidQRCodeMessage"))
return
}
let alert = UIAlertController(title: tr("alertScanQRCodeNamePromptTitle"), message: nil, preferredStyle: .alert)
alert.addTextField(configurationHandler: nil)
alert.addAction(UIAlertAction(title: tr("actionCancel"), style: .cancel) { [weak self] _ in
self?.dismiss(animated: true, completion: nil)
})
alert.addAction(UIAlertAction(title: tr("actionSave"), style: .default) { [weak self] _ in
guard let title = alert.textFields?[0].text?.trimmingCharacters(in: .whitespacesAndNewlines), !title.isEmpty else { return }
tunnelConfiguration.name = title
if let self = self {
self.delegate?.addScannedQRCode(tunnelConfiguration: tunnelConfiguration, qrScanViewController: self) {
self.dismiss(animated: true, completion: nil)
}
}
})
present(alert, animated: true)
}
func scanDidEncounterError(title: String, message: String) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: tr("actionOK"), style: .default) { [weak self] _ in
self?.dismiss(animated: true, completion: nil)
})
present(alertController, animated: true)
captureSession = nil
}
@objc func cancelTapped() {
dismiss(animated: true, completion: nil)
}
}
extension QRScanViewController: AVCaptureMetadataOutputObjectsDelegate {
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
captureSession?.stopRunning()
guard let metadataObject = metadataObjects.first,
let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
let stringValue = readableObject.stringValue else {
scanDidEncounterError(title: tr("alertScanQRCodeUnreadableQRCodeTitle"), message: tr("alertScanQRCodeUnreadableQRCodeMessage"))
return
}
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
scanDidComplete(withCode: stringValue)
}
}