Skip to content

Instantly share code, notes, and snippets.

@augusteo
Created February 2, 2017 05:29
Show Gist options
  • Save augusteo/687a6516c6f239be878c0432d9c1b592 to your computer and use it in GitHub Desktop.
Save augusteo/687a6516c6f239be878c0432d9c1b592 to your computer and use it in GitHub Desktop.
//
// BeaconManager.swift
// Keno
//
// Created by Victor Augusteo on 17/8/16.
// Copyright © 2016 Tabcorp. All rights reserved.
//
import Foundation
import ReactiveCocoa
import ReactiveSwift
import CoreBluetooth
import Result
class BeaconManager: NSObject {
static let instance = BeaconManager()
let isBluetoothOn = MutableProperty<Bool>(true)
fileprivate var bluetoothManager: CBPeripheralManager!
fileprivate var initState = false
fileprivate let tpState = MutableProperty<Bool>(true)
fileprivate var shouldRegisterForAppNotification = true
fileprivate var isFirstTime = true
let beaconStatus = MutableProperty<BeaconStatus>(.outside)
let isInVenue = MutableProperty<Bool>(false)
var spreoKey = SpreoKeys.mvp
private override init() {
super.init()
log.debug(IDKit.version())
IDKit.setDebugMode(false)
if UserDefaults.standard.bool(forKey: SpreoDefaultKeyIsTest) {
IDKit.setCustomerAPIKey(Constants.BeaconKeys.CustomerKeyTest)
spreoKey = .others
} else {
IDKit.setCustomerAPIKey(Constants.BeaconKeys.CustomerKeyProduction)
spreoKey = .mvp
}
bluetoothManager = CBPeripheralManager(delegate: self, queue: nil, options: nil)
isInVenue <~ beaconStatus.producer
.map { zone in
if let testInVenue = ProcessInfo.processInfo.environment["IN_VENUE_TEST"], Bool(testInVenue) == true {
return true
}
return zone == .inside
}
SignalProducer.combineLatest(tpState.producer, isBluetoothOn.producer)
.filter { !$0 || !$1 }
.startWithValues { _, _ in
self.beaconStatus.value = .outside
}
NotificationCenter.default.reactive.notifications(forName: NSNotification.Name.UIApplicationDidEnterBackground)
.observeValues { [weak self] _ in
self?.beaconStatus.value = .outside
}
let becomeActiveTrigger =
NotificationCenter.default.reactive.notifications(forName: NSNotification.Name.UIApplicationDidBecomeActive)
.map { _ in () }
let becomeActiveProducer: SignalProducer<(), NoError> = SignalProducer(becomeActiveTrigger)
.beginWith(())
let locationProducer = LocationManager.sharedInstance
.authorizationStatus
.producer
.map { $0 == .authorizedWhenInUse }
let jurisdictionProducer = AccountManager.sharedInstance.account
.producer
.filterMap { $0.jurisdiction }
.map { $0 == .NSW }
let bluetoothProducer = isBluetoothOn.producer
.skipRepeats()
SignalProducer.combineLatest(jurisdictionProducer, locationProducer, becomeActiveProducer, bluetoothProducer)
.logEvents()
.map { (isNSW, locationOn, _, bluetoothOn) in
return isNSW && locationOn && bluetoothOn
}
.skipRepeats()
.startWithValues { [weak self] shouldStart in
self?.handleZoneDetection(shouldStart: shouldStart)
}
}
func doInitialDetection() {
if (!initState && isBluetoothOn.value) {
initState = true;
IDKit.startInitialZoneDetection();
}
}
/// call just before placing a bet, returns all data needed to place a bet
func getVenueData() -> VenueData? {
if let testInVenue = ProcessInfo.processInfo.environment["IN_VENUE_TEST"] , Bool(testInVenue) == true {
return handleTestVenue()
}
if beaconStatus.value == .outside { return nil }
guard let projectID = IDKit.getCurrentProjectId() else { return nil }
let date = Date()
let signature = generateSignature(projectID: projectID, date: date)
return VenueData(venueID: projectID, date: date, signature: signature)
}
private func handleTestVenue() -> VenueData? {
let projectID = "00000_melbourne"
let date = Date()
let signature = generateSignature(projectID: projectID, date: date)
return VenueData(venueID: projectID, date: date, signature: signature)
}
}
extension BeaconManager {
func handleZoneDetection(shouldStart: Bool) {
if shouldStart {
IDKit.registerToZoneDetectionListener(withDelegate: self)
IDKit.startSearching(forCampus: nil)
log.debug("Beacon manager started searching")
} else {
IDKit.stopZoneDetection()
log.debug("Beacon manager stopped searching")
}
}
}
extension BeaconManager: IDZoneDetectionListener {
func onUpdateGate(with aZoneStatus: IDZoneStatus, detectionType aZoneDetectionType: IDZoneDetectionType) {
log.debug("zone status: \(aZoneStatus.stringValue), detectionType: \(aZoneDetectionType.stringValue)")
let isLocationOn = LocationManager.sharedInstance.authorizationStatus.value == .authorizedWhenInUse
if aZoneDetectionType == .loadingStateDetection && isBluetoothOn.value && isLocationOn {
beaconStatus.value = .loading
return
}
if (aZoneDetectionType == .initialDetection) {
initState = false;
if (aZoneStatus == .in) {
beaconStatus.value = .inside
return
}
}
if (aZoneDetectionType == .deviceConectivetyDetectionOn) {
if (isFirstTime) {
isFirstTime = false;
} else {
initState = false;
doInitialDetection()
return
}
}
if (aZoneDetectionType == .deviceConectivetyDetection) {
beaconStatus.value = aZoneStatus == .in ? .inside : .outside
return
}
//Gate Detection
if (aZoneStatus == .in && tpState.value) {
beaconStatus.value = .inside
}
if (aZoneStatus == .out) {
beaconStatus.value = .outside
}
}
func onUpdateTheftProtectionState(withMode aTPStateMode: Bool) {
log.debug("tpState: \(aTPStateMode)")
tpState.value = aTPStateMode
}
}
extension BeaconManager {
fileprivate func generateSignature(projectID: String, date: Date) -> String {
let str = "\(projectID)\(date.iso)"
let signature = DigitalSignature.signature(from: str)
return signature!
}
}
extension BeaconManager: CBPeripheralManagerDelegate {
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if let testInVenue = ProcessInfo.processInfo.environment["IN_VENUE_TEST"], Bool(testInVenue) == true {
// Pretend we have bluetooth turned on.
isBluetoothOn.value = true
} else {
isBluetoothOn.value = peripheral.state == .poweredOn
}
}
}
/// we need to pass venueID, device location, and one-time security token for placing a bet
struct VenueData {
let venueID : String
let date: Date
let signature: String
}
extension IDZoneStatus {
var stringValue: String {
switch self {
case .in : return "In"
case .out: return "Out"
}
}
}
extension IDZoneDetectionType {
var stringValue: String {
switch self {
case .initialDetection:
return "Initial"
case .unknownDetection:
return "Unkown"
case .gateDetection:
return "Gate"
case .deviceConectivetyDetection:
return "DeviceConnectivity"
case .deviceConectivetyDetectionOn:
return "DeviceConnectivityOn"
case .userDefenitionDetection:
return "User Definition"
case .loadingStateDetection:
return "Loading detection..."
case .notInRangeDetection:
return "no beacon in range"
case .bleNotAvailableDetection:
return "bluetooth is not available"
}
}
}
enum BeaconStatus {
case inside, outside, loading
}
enum SpreoKeys {
case mvp
case others
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment