434 lines
16 KiB
Swift
434 lines
16 KiB
Swift
import UIKit
|
||
import Flutter
|
||
import DropDown
|
||
import ZXingObjC
|
||
|
||
@objc class ScannerViewController: UIViewController, ZXCaptureDelegate, UITextFieldDelegate {
|
||
|
||
enum AppLocale {
|
||
case ru
|
||
case en
|
||
}
|
||
|
||
enum ButtonState {
|
||
case card
|
||
case phone
|
||
|
||
var icon: UIImage {
|
||
|
||
switch self {
|
||
case .card: return UIImage(named: "card")!
|
||
case .phone: return UIImage(named: "phone")!
|
||
}
|
||
}
|
||
|
||
var searchType: String {
|
||
switch self {
|
||
case .card: return "card"
|
||
case .phone: return "phone"
|
||
}
|
||
}
|
||
}
|
||
|
||
var captureSizeTransform: CGAffineTransform?
|
||
var buttonState: ButtonState = .card
|
||
@objc var platformChannel: FlutterMethodChannel?
|
||
var strings = [String:String]()
|
||
|
||
let scanRectView = UIView()
|
||
let header = UIView()
|
||
let textField = UITextField()
|
||
let settingsButton = UIButton(type: .system)
|
||
let searchType = UIButton(type: .system)
|
||
let dropDown = DropDown()
|
||
|
||
required init?(coder aDecoder: NSCoder) {
|
||
super.init(coder: aDecoder)
|
||
}
|
||
|
||
@objc init(strings: [String: String]) {
|
||
super.init(nibName: nil, bundle: nil)
|
||
strings.forEach { (k,v) in self.strings[k] = v }
|
||
}
|
||
|
||
func getInputHint() -> String {
|
||
switch self.buttonState {
|
||
case .card: return strings["enter_manual"]!
|
||
case .phone: return strings["enter_phone"]!
|
||
}
|
||
}
|
||
|
||
func getErrorText() -> String {
|
||
switch self.buttonState {
|
||
case .card: return strings["user_card_not_found"]!
|
||
case .phone: return strings["user_phone_not_found"]!
|
||
}
|
||
}
|
||
|
||
func setButtonState() {
|
||
switch self.buttonState {
|
||
case .card: self.buttonState = .phone
|
||
case .phone: self.buttonState = .card
|
||
}
|
||
}
|
||
|
||
let capture: ZXCapture = ZXCapture()
|
||
|
||
override func viewDidLoad() {
|
||
super.viewDidLoad()
|
||
|
||
view.layer.addSublayer((capture.layer)!)
|
||
view.addSubview(scanRectView)
|
||
view.addSubview(header)
|
||
view.bringSubview(toFront: header)
|
||
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(ScannerViewController.hideKeyboard)))
|
||
|
||
initCamera()
|
||
initSearchTypeButton()
|
||
initTextFIeld()
|
||
initSettingsButton()
|
||
initDropDown()
|
||
initHeader()
|
||
}
|
||
|
||
private func initCamera() {
|
||
capture.camera = capture.back()
|
||
capture.focusMode = .continuousAutoFocus
|
||
}
|
||
|
||
private func initHeader() {
|
||
header.addSubview(textField)
|
||
header.addSubview(searchType)
|
||
header.addSubview(settingsButton)
|
||
}
|
||
|
||
private func initTextFIeld() {
|
||
textField.delegate = self
|
||
textField.placeholder = self.getInputHint()
|
||
}
|
||
|
||
private func initSearchTypeButton() {
|
||
searchType.setImage(self.buttonState.icon, for: .normal)
|
||
searchType.addTarget(self, action: #selector(ScannerViewController.buttonTouch), for: .touchUpInside)
|
||
}
|
||
|
||
private func initDropDown() {
|
||
dropDown.anchorView = settingsButton
|
||
dropDown.dataSource = [strings["settings"]!, strings["faq"]!]
|
||
dropDown.selectionAction = { (index: Int, item: String) in
|
||
if index == 0 {
|
||
self.platformChannel?.invokeMethod("settings", arguments: nil)
|
||
} else if index == 1 {
|
||
self.platformChannel?.invokeMethod("faq", arguments: nil)
|
||
}
|
||
|
||
self.dismiss(animated: false)
|
||
}
|
||
}
|
||
|
||
private func initSettingsButton() {
|
||
settingsButton.setImage(UIImage(named: "more")!, for: .normal)
|
||
settingsButton.addTarget(self, action: #selector(ScannerViewController.settingsTouch), for: .touchUpInside)
|
||
}
|
||
|
||
@objc func hideKeyboard() {
|
||
view.endEditing(false)
|
||
}
|
||
|
||
@objc func buttonTouch() {
|
||
setButtonState()
|
||
searchType.setImage(self.buttonState.icon, for: .normal)
|
||
textField.placeholder = self.getInputHint()
|
||
}
|
||
|
||
@objc func settingsTouch() {
|
||
dropDown.show()
|
||
}
|
||
|
||
override func viewWillAppear(_ animated: Bool) {
|
||
super.viewWillAppear(animated)
|
||
header.backgroundColor = UIColor.white
|
||
textField.borderStyle = .roundedRect
|
||
capture.delegate = self
|
||
applyOrientation()
|
||
}
|
||
|
||
// TODO: Вынести эту копипасту в методы, когда будет время
|
||
override func viewWillLayoutSubviews() {
|
||
|
||
// TODO: Надо бы уйти от констант, переписать на отступы какие-нибудь
|
||
scanRectView.frame = view.bounds
|
||
header.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: 56)
|
||
searchType.frame = CGRect(x: 8, y: 26, width: 20, height: 20)
|
||
textField.frame = CGRect(x: searchType.frame.maxX + 8, y: 21, width: view.frame.size.width - searchType.frame.maxX - 48, height: 30)
|
||
settingsButton.frame = CGRect(x: view.frame.size.width - 30, y: 26, width: 20, height: 20)
|
||
|
||
var path = UIBezierPath()
|
||
path.move(to: CGPoint(x: 32, y: view.frame.size.height / 2))
|
||
path.addLine(to: CGPoint(x: view.frame.size.width - 32, y: view.frame.size.height / 2))
|
||
|
||
var shapeLayer = CAShapeLayer()
|
||
shapeLayer.path = path.cgPath
|
||
shapeLayer.strokeColor = UIColor.red.cgColor
|
||
shapeLayer.fillColor = UIColor.clear.cgColor
|
||
shapeLayer.lineWidth = 2
|
||
|
||
view.layer.addSublayer(shapeLayer)
|
||
|
||
path = UIBezierPath()
|
||
path.move(to: CGPoint(x: 32, y: (view.frame.size.height / 2) - 32))
|
||
path.addLine(to: CGPoint(x: 32, y: (view.frame.size.height / 2) - 64))
|
||
path.addLine(to: CGPoint(x: 64, y: (view.frame.size.height / 2) - 64))
|
||
|
||
path.move(to: CGPoint(x: view.frame.size.width - 64, y: (view.frame.size.height / 2) - 64))
|
||
path.addLine(to: CGPoint(x: view.frame.size.width - 32, y: (view.frame.size.height / 2) - 64))
|
||
path.addLine(to: CGPoint(x: view.frame.size.width - 32, y: (view.frame.size.height / 2) - 32))
|
||
|
||
path.move(to: CGPoint(x: 32, y: (view.frame.size.height / 2) + 32))
|
||
path.addLine(to: CGPoint(x: 32, y: (view.frame.size.height / 2) + 64))
|
||
path.addLine(to: CGPoint(x: 64, y: (view.frame.size.height / 2) + 64))
|
||
|
||
path.move(to: CGPoint(x: view.frame.size.width - 32, y: (view.frame.size.height / 2) + 32))
|
||
path.addLine(to: CGPoint(x: view.frame.size.width - 32, y: (view.frame.size.height / 2) + 64))
|
||
path.addLine(to: CGPoint(x: view.frame.size.width - 64, y: (view.frame.size.height / 2) + 64))
|
||
|
||
shapeLayer = CAShapeLayer()
|
||
shapeLayer.path = path.cgPath
|
||
shapeLayer.strokeColor = UIColor.green.cgColor
|
||
shapeLayer.fillColor = UIColor.clear.cgColor
|
||
shapeLayer.lineWidth = 2
|
||
|
||
view.layer.addSublayer(shapeLayer)
|
||
|
||
path = UIBezierPath()
|
||
path.move(to: CGPoint(x: 0, y: 56))
|
||
path.addLine(to: CGPoint(x: view.frame.size.width, y: 56))
|
||
path.addLine(to: CGPoint(x: view.frame.size.width, y: (view.frame.size.height / 2) - 64))
|
||
path.addLine(to: CGPoint(x: 0, y: (view.frame.size.height / 2) - 64))
|
||
path.addLine(to: CGPoint(x: 0, y: 56))
|
||
path.close()
|
||
|
||
shapeLayer = CAShapeLayer()
|
||
shapeLayer.path = path.cgPath
|
||
shapeLayer.fillColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5).cgColor
|
||
|
||
view.layer.addSublayer(shapeLayer)
|
||
|
||
path = UIBezierPath()
|
||
path.move(to: CGPoint(x: 0, y: (view.frame.size.height / 2) - 64))
|
||
path.addLine(to: CGPoint(x: 32, y: (view.frame.size.height / 2) - 64))
|
||
path.addLine(to: CGPoint(x: 32, y: (view.frame.size.height / 2) + 64))
|
||
path.addLine(to: CGPoint(x: 0, y: (view.frame.size.height / 2) + 64))
|
||
path.addLine(to: CGPoint(x: 0, y: (view.frame.size.height / 2) - 64))
|
||
path.close()
|
||
|
||
shapeLayer = CAShapeLayer()
|
||
shapeLayer.path = path.cgPath
|
||
shapeLayer.fillColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5).cgColor
|
||
|
||
view.layer.addSublayer(shapeLayer)
|
||
|
||
path = UIBezierPath()
|
||
path.move(to: CGPoint(x: view.frame.size.width, y: (view.frame.size.height / 2) - 64))
|
||
path.addLine(to: CGPoint(x: view.frame.size.width - 32, y: (view.frame.size.height / 2) - 64))
|
||
path.addLine(to: CGPoint(x: view.frame.size.width - 32, y: (view.frame.size.height / 2) + 64))
|
||
path.addLine(to: CGPoint(x: view.frame.size.width, y: (view.frame.size.height / 2) + 64))
|
||
path.addLine(to: CGPoint(x: view.frame.size.width, y: (view.frame.size.height / 2) - 64))
|
||
path.close()
|
||
|
||
shapeLayer = CAShapeLayer()
|
||
shapeLayer.path = path.cgPath
|
||
shapeLayer.fillColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5).cgColor
|
||
|
||
view.layer.addSublayer(shapeLayer)
|
||
|
||
path = UIBezierPath()
|
||
path.move(to: CGPoint(x: view.frame.size.width, y: (view.frame.size.height / 2) + 64))
|
||
path.addLine(to: CGPoint(x: 0, y: (view.frame.size.height / 2) + 64))
|
||
path.addLine(to: CGPoint(x: 0, y: view.frame.size.height))
|
||
path.addLine(to: CGPoint(x: view.frame.size.width, y: view.frame.size.height))
|
||
path.addLine(to: CGPoint(x: view.frame.size.width, y: (view.frame.size.height / 2) + 64))
|
||
path.close()
|
||
|
||
shapeLayer = CAShapeLayer()
|
||
shapeLayer.path = path.cgPath
|
||
shapeLayer.fillColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5).cgColor
|
||
|
||
view.layer.addSublayer(shapeLayer)
|
||
|
||
}
|
||
|
||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||
print("User from manual input: \(textField.text!)")
|
||
sendResult(textField.text!, buttonState)
|
||
return true
|
||
}
|
||
|
||
func sendResult(_ str: String, _ buttonState: ButtonState) {
|
||
platformChannel?.invokeMethod("findUser", arguments: [str, buttonState.searchType], result: { (result: Any?) in
|
||
if result is FlutterError {
|
||
print("Result is nil (ios code)");
|
||
self.showErrorAlert(str)
|
||
} else {
|
||
print("Result is not nil (ios code)");
|
||
self.dismiss(animated: true) {
|
||
self.platformChannel?.invokeMethod("purchase", arguments: [result, str])
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
func showErrorAlert(_ str: String) {
|
||
let alertController = UIAlertController(
|
||
title: strings["error"]!,
|
||
message: String(format: self.getErrorText(), str),
|
||
preferredStyle: UIAlertControllerStyle.alert
|
||
)
|
||
alertController.addAction(UIAlertAction(title: strings["dismiss"]!, style: UIAlertActionStyle.default,handler: nil))
|
||
self.present(alertController, animated: true, completion: nil)
|
||
}
|
||
|
||
override func didReceiveMemoryWarning() {
|
||
super.didReceiveMemoryWarning()
|
||
}
|
||
|
||
deinit {
|
||
capture.layer.removeFromSuperlayer()
|
||
}
|
||
|
||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||
super.viewWillTransition(to: size, with: coordinator)
|
||
coordinator.animate(alongsideTransition: {(_ context: UIViewControllerTransitionCoordinatorContext) -> Void in
|
||
}, completion: {(_ context: UIViewControllerTransitionCoordinatorContext) -> Void in
|
||
self.applyOrientation()
|
||
})
|
||
}
|
||
|
||
func applyOrientation() {
|
||
print("APPLY ORIENTATION")
|
||
let orientation: UIInterfaceOrientation = UIApplication.shared.statusBarOrientation
|
||
var scanRectRotation: Float
|
||
var captureRotation: Float
|
||
switch orientation {
|
||
case .portrait:
|
||
captureRotation = 0
|
||
scanRectRotation = 90
|
||
case .landscapeLeft:
|
||
captureRotation = 90
|
||
scanRectRotation = 180
|
||
case .landscapeRight:
|
||
captureRotation = 270
|
||
scanRectRotation = 0
|
||
case .portraitUpsideDown:
|
||
captureRotation = 180
|
||
scanRectRotation = 270
|
||
default:
|
||
captureRotation = 0
|
||
scanRectRotation = 90
|
||
}
|
||
applyRectOfInterest(orientation)
|
||
let transform = CGAffineTransform(rotationAngle: CGFloat(captureRotation / 180 * .pi))
|
||
capture.transform = transform
|
||
capture.rotation = CGFloat(scanRectRotation)
|
||
capture.layer.frame = view.frame
|
||
}
|
||
|
||
func applyRectOfInterest(_ orientation: UIInterfaceOrientation) {
|
||
var scaleVideo: CGFloat
|
||
var scaleVideoX: CGFloat
|
||
var scaleVideoY: CGFloat
|
||
var videoSizeX: CGFloat
|
||
var videoSizeY: CGFloat
|
||
var transformedVideoRect: CGRect = scanRectView.frame
|
||
videoSizeX = 720
|
||
videoSizeY = 1280
|
||
if UIInterfaceOrientationIsPortrait(orientation) {
|
||
scaleVideoX = view.frame.size.width / videoSizeX
|
||
scaleVideoY = view.frame.size.height / videoSizeY
|
||
scaleVideo = max(scaleVideoX, scaleVideoY)
|
||
if scaleVideoX > scaleVideoY {
|
||
transformedVideoRect.origin.y += (scaleVideo * videoSizeY - view.frame.size.height) / 2
|
||
}
|
||
else {
|
||
transformedVideoRect.origin.x += (scaleVideo * videoSizeX - view.frame.size.width) / 2
|
||
}
|
||
}
|
||
else {
|
||
scaleVideoX = view.frame.size.width / videoSizeY
|
||
scaleVideoY = view.frame.size.height / videoSizeX
|
||
scaleVideo = max(scaleVideoX, scaleVideoY)
|
||
if scaleVideoX > scaleVideoY {
|
||
transformedVideoRect.origin.y += (scaleVideo * videoSizeX - view.frame.size.height) / 2
|
||
}
|
||
else {
|
||
transformedVideoRect.origin.x += (scaleVideo * videoSizeY - view.frame.size.width) / 2
|
||
}
|
||
}
|
||
captureSizeTransform = CGAffineTransform(scaleX: 1 / scaleVideo, y: 1 / scaleVideo)
|
||
capture.scanRect = transformedVideoRect.applying(captureSizeTransform!)
|
||
}
|
||
|
||
// MARK: - Private Methods
|
||
func barcodeFormat(toString format: ZXBarcodeFormat) -> String {
|
||
switch format {
|
||
case kBarcodeFormatAztec:
|
||
return "Aztec"
|
||
case kBarcodeFormatCodabar:
|
||
return "CODABAR"
|
||
case kBarcodeFormatCode39:
|
||
return "Code 39"
|
||
case kBarcodeFormatCode93:
|
||
return "Code 93"
|
||
case kBarcodeFormatCode128:
|
||
return "Code 128"
|
||
case kBarcodeFormatDataMatrix:
|
||
return "Data Matrix"
|
||
case kBarcodeFormatEan8:
|
||
return "EAN-8"
|
||
case kBarcodeFormatEan13:
|
||
return "EAN-13"
|
||
case kBarcodeFormatITF:
|
||
return "ITF"
|
||
case kBarcodeFormatPDF417:
|
||
return "PDF417"
|
||
case kBarcodeFormatQRCode:
|
||
return "QR Code"
|
||
case kBarcodeFormatRSS14:
|
||
return "RSS 14"
|
||
case kBarcodeFormatRSSExpanded:
|
||
return "RSS Expanded"
|
||
case kBarcodeFormatUPCA:
|
||
return "UPCA"
|
||
case kBarcodeFormatUPCE:
|
||
return "UPCE"
|
||
case kBarcodeFormatUPCEANExtension:
|
||
return "UPC/EAN extension"
|
||
default:
|
||
return "Unknown"
|
||
}
|
||
}
|
||
|
||
// MARK: - ZXCaptureDelegate Methods
|
||
func captureResult(_ capture: ZXCapture, result: ZXResult) {
|
||
let inverse: CGAffineTransform = captureSizeTransform!.inverted()
|
||
var points = [AnyHashable]()
|
||
var location = ""
|
||
for resultPoint in result.resultPoints {
|
||
let p = resultPoint as! ZXResultPoint
|
||
let cgPoint = CGPoint(x: CGFloat(p.x), y: CGFloat(p.y))
|
||
var transformedPoint: CGPoint = cgPoint.applying(inverse)
|
||
transformedPoint = scanRectView.convert(transformedPoint, to: scanRectView.window)
|
||
let windowPointValue = NSValue(cgPoint: transformedPoint)
|
||
location = "\(location) (\(transformedPoint.x), \(transformedPoint.y))"
|
||
points.append(windowPointValue)
|
||
}
|
||
print("User from scanner: \(result.text)")
|
||
sendResult(result.text, ButtonState.card)
|
||
print(result.text)
|
||
self.capture.stop()
|
||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(2 * Double(NSEC_PER_SEC)) / Double(NSEC_PER_SEC), execute: {() -> Void in
|
||
self.capture.start()
|
||
})
|
||
}
|
||
}
|