Skip to content

Instantly share code, notes, and snippets.

@yaakovgamliel
Last active August 19, 2019 10:41
Show Gist options
  • Save yaakovgamliel/199c0cec3f0ccbca376016b725c82b7d to your computer and use it in GitHub Desktop.
Save yaakovgamliel/199c0cec3f0ccbca376016b725c82b7d to your computer and use it in GitHub Desktop.

This is the code called when the user presses a button to start scanning.

func presentScanner() {
    let t = viewModel.transaction
    guard let recognizerRunnerController = RecognizerProvider().cardSccannerController(transaction: t) else { return }

    self.navigationController?.pushViewController(recognizerRunnerController, animated: true)
 }

This is the reconizer and overlay controller factory

//
//  RecognizerProvider.swift
//  pos
//
//  Copyright © 2019 Sunbit. All rights reserved.
//

import Foundation
import MicroBlink
import Core

class RecognizerProvider {
    //Maybe we don't need to keep this one here
    var pdf417Recognizer : MBPdf417Recognizer?
    var licenseRecognizer: MBUsdlRecognizer?
    var cardRecognizer: MBBlinkCardRecognizer?

    func controller(transaction: OngoingTransaction) -> UIViewController? {
        /*
         guard let license = VaultStore().storedVault(key: ApiKeys.microblink_v4_9) else {
         Loggly.client.send(events: ["event": "failed_to_load_mb_sdk"])
         return nil
         }

         Loggly.client.send(events: ["event": "starting_mb_sdk"])

         MBMicroblinkSDK.sharedInstance().setLicenseKey(license)
         */
        let license = posMicroblinkLicense

        MBMicroblinkSDK.sharedInstance().setLicenseKey(license)

        licenseRecognizer = MBUsdlRecognizer()
        pdf417Recognizer = MBPdf417Recognizer()

        let recognizerList = [licenseRecognizer!, pdf417Recognizer!]
        let recognizerCollection = MBRecognizerCollection(recognizers: recognizerList)

        /** Create your overlay view controller */
        let customOverlayViewController = OverlayViewController.instantiate(transaction: transaction)

        /** This has to be called for custom controller */
        customOverlayViewController.reconfigureRecognizers(recognizerCollection)

        /** Create recognizer view controller with wanted overlay view controller */
        return MBViewControllerFactory.recognizerRunnerViewController(
            withOverlayViewController: customOverlayViewController)
    }

    func cardSccannerController(transaction: OngoingTransaction) -> UIViewController? {
        let license = posMicroblinkLicense
        MBMicroblinkSDK.sharedInstance().setLicenseKey(license)

        cardRecognizer = MBBlinkCardRecognizer()
        cardRecognizer?.extractCvv = false
        cardRecognizer?.returnFullDocumentImage = true

        guard let recognizer = cardRecognizer else {
            return nil
        }

        let recognizerList = [recognizer]
        let recognizerCollection = MBRecognizerCollection(recognizers: recognizerList)

        let customOverlayViewController = OverlayViewController.instantiate(transaction: transaction)

        customOverlayViewController.reconfigureRecognizers(recognizerCollection)

    return MBViewControllerFactory.recognizerRunnerViewController(
            withOverlayViewController: customOverlayViewController)
    }
}

The custome overlay controller looks like this

//
//  OverlayViewController.swift
//  pos
//
//  Copyright © 2017 Sunbit. All rights reserved.
//

import UIKit
import Core
import MicroBlink
import DropDown

class OverlayViewModel {
    func userMock() -> Bool {
        return AppEnvironment.current.mock
    }

    func patchCustomerState(customer: Customer, state: State) -> Customer {
        var patched = Customer.create().update(from: customer)
        patched.addressDetails = nil
        patched.drivingLicenseState = Defaults.nonApplicableState

        return patched
    }

    func needsStatePatching(customer: Customer) -> Bool {
        let address = customer.addressDetails
        if address?.state?.rawValue.isEmpty == true || address?.state == nil {
            return true
        }

        return false
    }
}

class OverlayViewController: MBCustomOverlayViewController {
    let viewModel = OverlayViewModel()
    @IBOutlet weak var scannerMockButton: UIButton!
    @IBOutlet var cameraOverlayView: ScannerCameraOverlayView!
    @IBOutlet weak var resumeScanningLabel: UILabel!
    @IBOutlet weak var labelTitle: UILabel!
    @IBOutlet weak var hintLabel: UILabel!
    @IBOutlet weak var hintButton: HintActionButton!
    @IBOutlet weak var cameraHelpImageView: UIImageView!
    @IBOutlet weak var manualDetailsButton: UIButton!
    @IBOutlet weak var resumeScannningButton: UIButton!

    fileprivate lazy var phoneVerificationController = CustomerPhoneVerificationViewController.instantiate()
    fileprivate var transaction: OngoingTransaction?
    fileprivate var idleTimer: Timer = Timer()
    fileprivate var dlDetected: Bool = false

    deinit {
        print("[*] Unloading ............. \(self.description)")
    }

    internal static func instantiate(transaction: OngoingTransaction) -> OverlayViewController {
        let vc = Storyboard.scanner.instantiate(OverlayViewController.self)
        vc.transaction = transaction

        return vc
    }

    internal static func instantiate() -> OverlayViewController {
        return Storyboard.scanner.instantiate(OverlayViewController.self)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        CrashReporter.shared.breadcrumbs(message: "\(self.description) init")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        super.scanningRecognizerRunnerViewControllerDelegate = self

        setupUI()

        manualEntrySetup()

        setupIdleTimer()

        //Handle here manual entry button timer
        phoneVerificationController.customerVerificationDelegate = self

        let resumeBtn = UIButton.init(frame: resumeScanningLabel.bounds)
        resumeBtn.sizeToFit()

        resumeBtn.addTarget(self, action: #selector(awakeScanner), for: .touchUpInside)
        cameraHelpImageView.addSubview(resumeBtn)
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        #if SELFAPPLY
        SelfApplicationHelper.shared.screenMark.isHidden = false
        #endif
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillAppear(animated)
        if idleTimer.isValid { idleTimer.invalidate() }
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        scanningRecognizerRunnerViewControllerDelegate = nil
        recognizerRunnerViewControllerDelegate = nil
        recognizerRunnerViewController = nil
    }

    private func setupIdleTimer() {
        idleTimer = Timer.scheduledTimer(
            timeInterval: 60.0,
            target: self,
            selector: #selector(idleActiveCamera),
            userInfo: nil,
            repeats: false
        )
    }

    fileprivate func restartScannerIfNeed() {
        if recognizerRunnerViewController?.isCameraPaused() == true {
            recognizerRunnerViewController?.resumeCamera()
        }

        if recognizerRunnerViewController?.isScanningPaused() == true {
            recognizerRunnerViewController?.resumeScanningAndResetState(true)
            resumeScanningLabel.isHidden = true
        }
    }

    func setupUI() {
        navigationController?.setColorMode(mode: .blue)
        self.navigationController?.setClearBackground()
        self.resumeScanningLabel.isHidden = true

        cameraOverlayView.alpha = 0.4
        cameraOverlayView.backgroundColor = .black

        if viewModel.userMock() {
            scannerMockButton.isHidden = false
        }

        #if SELFAPPLY
        SelfApplicationHelper.shared.screenMark.isHidden = false
        #endif
    }

    @objc func idleActiveCamera() {
        recognizerRunnerViewController?.pauseScanning()
        self.resumeScanningLabel.isHidden = false
    }

    func handleLicenseExpiration(presenter: UIViewController) {
        guard let state = transaction?.retailer?.addressDetails?.state else {
            return
        }

        self.failureAlert(title: Messages.expirationTitle(),
                          message: Messages.driverLicenseExpiration(state: state.rawValue))?
            .present(with: presenter)
    }

    func failureAlert(title: String, message: String) -> GeneralAlert? {
        return GeneralAlert.with(title: title,
                                 body: message,
                                 cancelAction: { self.navigateBack() })
    }
}

extension OverlayViewController: MBScanningRecognizerRunnerViewControllerDelegate {

    func recognizerRunnerViewController(_ recognizerRunnerViewController: UIViewController & MBRecognizerRunnerViewController,
                                        didFinishScanningWith state: MBRecognizerResultState) {
        SunbitUIEventNotifier().resetTimerNotification()

        if dlDetected { return }

        if state == MBRecognizerResultState.valid {
            DispatchQueue.main.sync { [weak self] in
                guard let s = self else { return }
                s.recognizerRunnerViewController?.pauseScanning()
                s.recognizerRunnerViewController?.pauseCamera()
                for recognizer in s.recognizerCollection.recognizerList where
                    recognizer.baseResult?.resultState == MBRecognizerResultState.valid && recognizer is MBPdf417Recognizer {
                        let pdf417Recognizer = recognizer as? MBPdf417Recognizer
                        if let rawData = pdf417Recognizer?.result.stringData {
                            s.dlDetected = true
                            s.talkToService(rawData: rawData)
                        }
                }
            }
        }
    }

    func talkToService(rawData: String) {
        print("Here we are talking to our server")
    }
}

extension OverlayViewController {

    @IBAction func hintButtonTouched(sender: Any?) {
        if hintLabel.alpha == 0.0 {
            fadeInHint()
        } else {
            fadeOutHint()
        }
    }

    @IBAction func resumeScanningTouched(_ sender: Any) {
        awakeScanner()
        setupIdleTimer()
    }

    private func fadeOutHint() {
        UIView.animate(withDuration: 0.3, animations: {
            self.hintButton.setTitle("How do I scan?", for: .normal)
            self.hintButton.setImage(#imageLiteral(resourceName: "hintScanIcon"), for: .normal)
            self.hintLabel.alpha = 0.0
            self.cameraHelpImageView.alpha = 0.0
        })
    }

    private func fadeInHint() {
        UIView.animate(withDuration: 0.3, animations: {
            self.hintButton.setTitle("Cancel", for: .normal)
            self.hintButton.setImage(#imageLiteral(resourceName: "selfApplyCancelBtn"), for: .normal)
            self.hintLabel.alpha = 1.0
            self.cameraHelpImageView.alpha = 1.0
        })
    }
}

extension OverlayViewController: UserMockDelegate {

    @IBAction func userMockTouched(_ sender: UIButton) {
        let mockVC = UsersMockController.init(style: .plain)
        mockVC.mockControllerDelegate = self
        mockVC.modalPresentationStyle = .popover
        present(mockVC, animated: true, completion: nil)

        let popover = mockVC.popoverPresentationController
        popover?.sourceView = self.view
    }

    func didSelect(customer: Customer?, controller: UITableViewController) {
        guard let c = customer else { return }
        guard self.transaction != nil else { return }

        var mockCustomer = c
        mockCustomer.isTest = true

        self.transaction?.customer = mockCustomer

        controller.dismiss(animated: true, completion: nil)

        guard let customer = transaction?.customer else { return }

        if viewModel.needsStatePatching(customer: customer) {
            guard let retailerState = transaction?.retailer?.addressDetails?.state else { return }
            transaction?.customer = viewModel.patchCustomerState(customer: customer, state: retailerState)
            navigate()
        } else {
            navigate()
        }
    }

    func navigate() {
        guard  let t = transaction else { return }

        let vc = FunnelFactory().funnelController(transaction: t)
        self.navigationController?.pushViewController(vc, animated: true)
    }

    func navigateBack() {
        self.navigationController?.popViewController(animated: true)
    }

    func navigateToManualFunnel() {
        guard  let t = transaction else { return }

        let vc = CustomerFunnelViewController.instantiate(transaction: t)

        self.navigationController?.pushViewController(vc, animated: true)
    }

    @objc func awakeScanner() {
        restartScannerIfNeed()
    }
}

struct ManualFunnelStates: Codable {
    let states: [String]
}

extension OverlayViewController {

    @IBAction func manualEntryButtonTouched(_ sender: UIButton) {
        guard let transaction = self.transaction else { return }
        phoneVerificationController.viewModel.update(transaction: transaction)
        present(phoneVerificationController, animated: true) {

        }
    }

    func manualEntrySetup() {
        if transaction?.retailer?.originator != .tab { return }

        var displayAfter: Double

        if let t = VaultStore.init().storedVault(key: RemoteParameters.manualFunnelShowtime) {
            displayAfter = Double(t) ?? Defaults.manualEntryOptionDisplayDelay
        } else {
            displayAfter = Defaults.manualEntryOptionDisplayDelay
        }

        DispatchQueue
            .main
            .asyncAfter(deadline: .secondsFromNow(displayAfter)) { [weak self] in
                self?.manualDetailsButton.isEnabled = true
                self?.manualDetailsButton.isHidden = false
        }
    }
}

extension OverlayViewController: CustomerPhoneVerificationDelegate {

    func shouldPopPresenterController() {
        navigationController?.popViewController(animated: true)
    }

    func customerPhoneVerificationSucced(transaction: OngoingTransaction) {
        self.transaction?.phoneVerification = transaction.phoneVerification
        self.transaction?.customer = transaction.purchase?.customer
        self.transaction?.purchase = transaction.purchase
        navigateToManualFunnel()
    }

    func customerPhoneVerificationFailed() {

    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment