mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-06 07:48:38 +09:00
Dactyloidae iOS initial commit
This commit is contained in:
parent
daa6179d22
commit
7154a0497e
2123 changed files with 197052 additions and 0 deletions
291
mobile/ios/Client/Frontend/Browser/QRCodeViewController.swift
Normal file
291
mobile/ios/Client/Frontend/Browser/QRCodeViewController.swift
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
import SnapKit
|
||||
import Shared
|
||||
|
||||
private struct QRCodeViewControllerUX {
|
||||
static let navigationBarBackgroundColor = UIColor.black
|
||||
static let navigationBarTitleColor = UIColor.white
|
||||
static let maskViewBackgroungColor = UIColor(colorLiteralRed: 0, green: 0, blue: 0, alpha: 0.5)
|
||||
static let isLightingNavigationItemColor = UIColor(colorLiteralRed: 0.45, green: 0.67, blue: 0.84, alpha: 1)
|
||||
}
|
||||
|
||||
protocol QRCodeViewControllerDelegate {
|
||||
func didScanQRCodeWithURL(_ url: URL)
|
||||
func didScanQRCodeWithText(_ text: String)
|
||||
}
|
||||
|
||||
class QRCodeViewController: UIViewController {
|
||||
var qrCodeDelegate: QRCodeViewControllerDelegate?
|
||||
|
||||
fileprivate lazy var captureSession: AVCaptureSession = {
|
||||
let session = AVCaptureSession()
|
||||
session.sessionPreset = AVCaptureSessionPresetHigh
|
||||
return session
|
||||
}()
|
||||
|
||||
private lazy var captureDevice: AVCaptureDevice? = {
|
||||
return AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
|
||||
}()
|
||||
|
||||
private var videoPreviewLayer: AVCaptureVideoPreviewLayer?
|
||||
private let scanLine: UIImageView = UIImageView(image: UIImage(named: "qrcode-scanLine"))
|
||||
private let scanBorder: UIImageView = UIImageView(image: UIImage(named: "qrcode-scanBorder"))
|
||||
private lazy var instructionsLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = Strings.ScanQRCodeInstructionsLabel
|
||||
label.textColor = UIColor.white
|
||||
label.textAlignment = NSTextAlignment.center
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
private var maskView: UIView = UIView()
|
||||
private var isAnimationing: Bool = false
|
||||
private var isLightOn: Bool = false
|
||||
private var shapeLayer: CAShapeLayer = CAShapeLayer()
|
||||
|
||||
private var scanRange: CGRect {
|
||||
let size = UIDevice.current.userInterfaceIdiom == .pad ?
|
||||
CGSize(width: view.frame.width / 2, height: view.frame.width / 2) :
|
||||
CGSize(width: view.frame.width / 3 * 2, height: view.frame.width / 3 * 2)
|
||||
var rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
|
||||
rect.center = UIScreen.main.bounds.center
|
||||
return rect
|
||||
}
|
||||
|
||||
private var scanBorderHeight: CGFloat {
|
||||
return UIDevice.current.userInterfaceIdiom == .pad ?
|
||||
view.frame.width / 2 : view.frame.width / 3 * 2
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
guard let captureDevice = self.captureDevice else {
|
||||
dismiss(animated: false)
|
||||
return
|
||||
}
|
||||
|
||||
self.navigationItem.title = Strings.ScanQRCodeViewTitle
|
||||
|
||||
// Setup the NavigationBar
|
||||
self.navigationController?.navigationBar.barTintColor = QRCodeViewControllerUX.navigationBarBackgroundColor
|
||||
self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: QRCodeViewControllerUX.navigationBarTitleColor]
|
||||
|
||||
// Setup the NavigationItem
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(named: "qrcode-goBack"), style: UIBarButtonItemStyle.plain, target: self, action: #selector(goBack))
|
||||
self.navigationItem.leftBarButtonItem?.tintColor = UIColor.white
|
||||
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "qrcode-light"), style: .plain, target: self, action: #selector(openLight))
|
||||
if captureDevice.hasTorch {
|
||||
self.navigationItem.rightBarButtonItem?.tintColor = UIColor.white
|
||||
} else {
|
||||
self.navigationItem.rightBarButtonItem?.tintColor = UIColor.gray
|
||||
self.navigationItem.rightBarButtonItem?.isEnabled = false
|
||||
}
|
||||
|
||||
let getAuthorizationStatus = AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo)
|
||||
if getAuthorizationStatus != AVAuthorizationStatus.denied {
|
||||
setupCamera()
|
||||
} else {
|
||||
let alert = UIAlertController(title: "", message: Strings.ScanQRCodePermissionErrorMessage, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: Strings.ScanQRCodeErrorOKButton, style: .default, handler: nil))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
maskView.backgroundColor = QRCodeViewControllerUX.maskViewBackgroungColor
|
||||
self.view.addSubview(maskView)
|
||||
self.view.addSubview(scanBorder)
|
||||
self.view.addSubview(scanLine)
|
||||
self.view.addSubview(instructionsLabel)
|
||||
|
||||
setupConstraints()
|
||||
let rectPath = UIBezierPath(rect: UIScreen.main.bounds)
|
||||
rectPath.append(UIBezierPath(rect: scanRange).reversing())
|
||||
shapeLayer.path = rectPath.cgPath
|
||||
maskView.layer.mask = shapeLayer
|
||||
|
||||
isAnimationing = true
|
||||
startScanLineAnimation()
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
self.captureSession.stopRunning()
|
||||
stopScanLineAnimation()
|
||||
}
|
||||
|
||||
private func setupConstraints() {
|
||||
maskView.snp.makeConstraints { (make) in
|
||||
make.edges.equalTo(self.view)
|
||||
}
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
scanBorder.snp.makeConstraints { (make) in
|
||||
make.center.equalTo(self.view)
|
||||
make.width.height.equalTo(view.frame.width / 2)
|
||||
}
|
||||
} else {
|
||||
scanBorder.snp.makeConstraints { (make) in
|
||||
make.center.equalTo(self.view)
|
||||
make.width.height.equalTo(view.frame.width / 3 * 2)
|
||||
}
|
||||
}
|
||||
scanLine.snp.makeConstraints { (make) in
|
||||
make.left.equalTo(scanBorder.snp.left)
|
||||
make.top.equalTo(scanBorder.snp.top).offset(6)
|
||||
make.width.equalTo(scanBorder.snp.width)
|
||||
make.height.equalTo(6)
|
||||
}
|
||||
|
||||
instructionsLabel.snp.makeConstraints { (make) in
|
||||
make.left.right.equalTo(self.view.layoutMarginsGuide)
|
||||
make.top.equalTo(scanBorder.snp.bottom).offset(30)
|
||||
}
|
||||
}
|
||||
|
||||
func startScanLineAnimation() {
|
||||
if !isAnimationing {
|
||||
return
|
||||
}
|
||||
self.view.layoutIfNeeded()
|
||||
self.view.setNeedsLayout()
|
||||
UIView.animate(withDuration: 2.4, animations: {
|
||||
self.scanLine.snp.updateConstraints({ (make) in
|
||||
make.top.equalTo(self.scanBorder.snp.top).offset(self.scanBorderHeight - 6)
|
||||
})
|
||||
self.view.layoutIfNeeded()
|
||||
}) { (value: Bool) in
|
||||
self.scanLine.snp.updateConstraints({ (make) in
|
||||
make.top.equalTo(self.scanBorder.snp.top).offset(6)
|
||||
})
|
||||
self.perform(#selector(self.startScanLineAnimation), with: nil, afterDelay: 0)
|
||||
}
|
||||
}
|
||||
|
||||
func stopScanLineAnimation() {
|
||||
isAnimationing = false
|
||||
}
|
||||
|
||||
func goBack() {
|
||||
self.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func openLight() {
|
||||
guard let captureDevice = self.captureDevice else {
|
||||
return
|
||||
}
|
||||
|
||||
if isLightOn {
|
||||
do {
|
||||
try captureDevice.lockForConfiguration()
|
||||
captureDevice.torchMode = AVCaptureTorchMode.off
|
||||
captureDevice.unlockForConfiguration()
|
||||
navigationItem.rightBarButtonItem?.image = UIImage(named: "qrcode-light")
|
||||
navigationItem.rightBarButtonItem?.tintColor = UIColor.white
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
} else {
|
||||
do {
|
||||
try captureDevice.lockForConfiguration()
|
||||
captureDevice.torchMode = AVCaptureTorchMode.on
|
||||
captureDevice.unlockForConfiguration()
|
||||
navigationItem.rightBarButtonItem?.image = UIImage(named: "qrcode-isLighting")
|
||||
navigationItem.rightBarButtonItem?.tintColor = QRCodeViewControllerUX.isLightingNavigationItemColor
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
isLightOn = !isLightOn
|
||||
}
|
||||
|
||||
func setupCamera() {
|
||||
guard let captureDevice = self.captureDevice else {
|
||||
dismiss(animated: false)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let input = try AVCaptureDeviceInput(device: captureDevice)
|
||||
captureSession.addInput(input)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
let output = AVCaptureMetadataOutput()
|
||||
if captureSession.canAddOutput(output) {
|
||||
captureSession.addOutput(output)
|
||||
output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
|
||||
output.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
|
||||
}
|
||||
if let videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) {
|
||||
videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
|
||||
videoPreviewLayer.frame = UIScreen.main.bounds
|
||||
view.layer.addSublayer(videoPreviewLayer)
|
||||
self.videoPreviewLayer = videoPreviewLayer
|
||||
captureSession.startRunning()
|
||||
}
|
||||
}
|
||||
|
||||
override func willAnimateRotation(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
|
||||
shapeLayer.removeFromSuperlayer()
|
||||
let rectPath = UIBezierPath(rect: UIScreen.main.bounds)
|
||||
rectPath.append(UIBezierPath(rect: scanRange).reversing())
|
||||
shapeLayer.path = rectPath.cgPath
|
||||
maskView.layer.mask = shapeLayer
|
||||
|
||||
guard let videoPreviewLayer = self.videoPreviewLayer else {
|
||||
return
|
||||
}
|
||||
videoPreviewLayer.frame = UIScreen.main.bounds
|
||||
switch toInterfaceOrientation {
|
||||
case .portrait:
|
||||
videoPreviewLayer.connection.videoOrientation = AVCaptureVideoOrientation.portrait
|
||||
case .landscapeLeft:
|
||||
videoPreviewLayer.connection.videoOrientation = AVCaptureVideoOrientation.landscapeLeft
|
||||
case .landscapeRight:
|
||||
videoPreviewLayer.connection.videoOrientation = AVCaptureVideoOrientation.landscapeRight
|
||||
case .portraitUpsideDown:
|
||||
videoPreviewLayer.connection.videoOrientation = AVCaptureVideoOrientation.portraitUpsideDown
|
||||
default:
|
||||
videoPreviewLayer.connection.videoOrientation = AVCaptureVideoOrientation.portrait
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension QRCodeViewController: AVCaptureMetadataOutputObjectsDelegate {
|
||||
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
|
||||
if metadataObjects == nil || metadataObjects.count == 0 {
|
||||
self.captureSession.stopRunning()
|
||||
let alert = UIAlertController(title: "", message: Strings.ScanQRCodeInvalidDataErrorMessage, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: Strings.ScanQRCodeErrorOKButton, style: .default, handler: { (UIAlertAction) in
|
||||
self.captureSession.startRunning()
|
||||
}))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
} else {
|
||||
self.captureSession.stopRunning()
|
||||
stopScanLineAnimation()
|
||||
self.dismiss(animated: true, completion: {
|
||||
guard let metaData = metadataObjects.first as? AVMetadataMachineReadableCodeObject, let qrCodeDelegate = self.qrCodeDelegate, let text = metaData.stringValue else {
|
||||
Sentry.shared.sendWithStacktrace(message: "Unable to scan QR code", tag: .general)
|
||||
return
|
||||
}
|
||||
|
||||
if let url = URIFixup.getURL(text) {
|
||||
qrCodeDelegate.didScanQRCodeWithURL(url)
|
||||
} else {
|
||||
qrCodeDelegate.didScanQRCodeWithText(text)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class QRCodeNavigationController: UINavigationController {
|
||||
override open var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return .lightContent
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue