Bluefruit-Playground/BluefruitPlayground/ViewControllers/AutoConnect/AutoConnectViewController.swift
Antonio cdf816f848 Autoconnect: cosmetic changes
Better compatibility for iPhone 4s
2020-03-12 15:26:57 +01:00

410 lines
17 KiB
Swift

//
// AutoConnectViewController.swift
// BluefruitPlayground
//
// Created by Antonio García on 28/01/2020.
// Copyright © 2020 Adafruit. All rights reserved.
//
import UIKit
class AutoConnectViewController: UIViewController {
// Constants
static let kNavigationControllerIdentifier = "AutoConnectNavigationController"
// Config
private static let kRssiRunningAverageFactor = 0.2
// UI
@IBOutlet weak var statusLabel: UILabel!
@IBOutlet weak var scanManuallyButton: UIButton!
@IBOutlet weak var problemButton: CornerShadowButton!
@IBOutlet weak var wave0ImageView: UIImageView!
@IBOutlet weak var wave1ImageView: UIImageView!
@IBOutlet weak var wave2ImageView: UIImageView!
@IBOutlet weak var detailLabel: UILabel!
@IBOutlet weak var boardContainerView: UIView!
@IBOutlet weak var boardImageView: UIImageView!
@IBOutlet weak var actionsContainerView: UIStackView!
@IBOutlet weak var detailContainerView: UIView!
// Data
private let bleManager = Config.bleManager
private var peripheralList = PeripheralList(bleManager: Config.bleManager)
private var peripheralAutoConnect = PeripheralAutoConnect()
private var selectedPeripheral: BlePeripheral? {
didSet {
if isViewLoaded {
UIView.animate(withDuration: 0.3) {
self.actionsContainerView.alpha = self.selectedPeripheral == nil ? 1:0
}
}
}
}
private let navigationButton = UIButton(type: .custom)
private var isAnimating = false
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.boardImageView.alpha = 0
// Localization
let localizationManager = LocalizationManager.shared
self.title = localizationManager.localizedString("autoconnect_title")
scanManuallyButton.setTitle(localizationManager.localizedString("autoconnect_manual_action").uppercased(), for: .normal)
problemButton.setTitle(localizationManager.localizedString("scanner_problems_action").uppercased(), for: .normal)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.backBarButtonItem = nil // Clear any custom back button
if let customNavigationBar = navigationController?.navigationBar as? NavigationBarWithScrollAwareRightButton {
customNavigationBar.setRightButton(topViewController: self, image: UIImage(named: "info"), target: self, action: #selector(troubleshooting(_:)))
}
// Disconnect if needed
let connectedPeripherals = bleManager.connectedPeripherals()
if connectedPeripherals.count == 1, let peripheral = connectedPeripherals.first {
DLog("Disconnect from previously connected peripheral")
// Disconnect from peripheral
disconnect(peripheral: peripheral)
}
// UI
updateStatusLabel()
// Animations
if !isAnimating {
startAnimating()
}
// Ble Notifications
registerNotifications(enabled: true)
// Autoconnect
peripheralAutoConnect.reset()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Update UI
updateScannedPeripherals()
// Start scannning
BlePeripheral.rssiRunningAverageFactor = AutoConnectViewController.kRssiRunningAverageFactor // Use running average for rssi
if !bleManager.isScanning {
bleManager.startScan()
}
// Remove saved peripheral for autoconnect
Settings.clearAutoconnectPeripheral()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Stop scanning
bleManager.stopScan()
BlePeripheral.rssiRunningAverageFactor = 1 // Disable using running average for rssi
// Clear peripherals
peripheralList.clear()
// Animations
stopAnimating()
// Ble Notifications
registerNotifications(enabled: false)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is ScannerViewController {
// Go to scanner screen
let backItem = UIBarButtonItem()
backItem.title = LocalizationManager.shared.localizedString("autoconnect_backbutton")
self.navigationItem.backBarButtonItem = backItem
}
}
// MARK: - BLE Notifications
private weak var didDiscoverPeripheralObserver: NSObjectProtocol?
private weak var didUnDiscoverPeripheralObserver: NSObjectProtocol?
private weak var willConnectToPeripheralObserver: NSObjectProtocol?
private weak var didConnectToPeripheralObserver: NSObjectProtocol?
private weak var didDisconnectFromPeripheralObserver: NSObjectProtocol?
private weak var peripheralDidUpdateNameObserver: NSObjectProtocol?
private weak var willDiscoverServicesObserver: NSObjectProtocol?
private func registerNotifications(enabled: Bool) {
let notificationCenter = NotificationCenter.default
if enabled {
didDiscoverPeripheralObserver = notificationCenter.addObserver(forName: .didDiscoverPeripheral, object: nil, queue: .main, using: {[weak self] _ in self?.updateScannedPeripherals()})
didUnDiscoverPeripheralObserver = notificationCenter.addObserver(forName: .didUnDiscoverPeripheral, object: nil, queue: .main, using: {[weak self] _ in self?.updateScannedPeripherals()})
willConnectToPeripheralObserver = notificationCenter.addObserver(forName: .willConnectToPeripheral, object: nil, queue: .main, using: {[weak self] notification in self?.willConnectToPeripheral(notification: notification)})
didConnectToPeripheralObserver = notificationCenter.addObserver(forName: .didConnectToPeripheral, object: nil, queue: .main, using: {[weak self] notification in self?.didConnectToPeripheral(notification: notification)})
didDisconnectFromPeripheralObserver = notificationCenter.addObserver(forName: .didDisconnectFromPeripheral, object: nil, queue: .main, using: {[weak self] notification in self?.didDisconnectFromPeripheral(notification: notification)})
peripheralDidUpdateNameObserver = notificationCenter.addObserver(forName: .peripheralDidUpdateName, object: nil, queue: .main, using: {[weak self] notification in self?.peripheralDidUpdateName(notification: notification)})
willDiscoverServicesObserver = notificationCenter.addObserver(forName: .willDiscoverServices, object: nil, queue: .main, using: {[weak self] notification in self?.willDiscoverServices(notification: notification)})
} else {
if let didDiscoverPeripheralObserver = didDiscoverPeripheralObserver {notificationCenter.removeObserver(didDiscoverPeripheralObserver)}
if let didUnDiscoverPeripheralObserver = didUnDiscoverPeripheralObserver {notificationCenter.removeObserver(didUnDiscoverPeripheralObserver)}
if let willConnectToPeripheralObserver = willConnectToPeripheralObserver {notificationCenter.removeObserver(willConnectToPeripheralObserver)}
if let didConnectToPeripheralObserver = didConnectToPeripheralObserver {notificationCenter.removeObserver(didConnectToPeripheralObserver)}
if let didDisconnectFromPeripheralObserver = didDisconnectFromPeripheralObserver {notificationCenter.removeObserver(didDisconnectFromPeripheralObserver)}
if let peripheralDidUpdateNameObserver = peripheralDidUpdateNameObserver {notificationCenter.removeObserver(peripheralDidUpdateNameObserver)}
if let willDiscoverServicesObserver = willDiscoverServicesObserver {notificationCenter.removeObserver(willDiscoverServicesObserver)}
}
}
private func willConnectToPeripheral(notification: Notification) {
guard let selectedPeripheral = selectedPeripheral, let identifier = notification.userInfo?[BleManager.NotificationUserInfoKey.uuid.rawValue] as? UUID, selectedPeripheral.identifier == identifier else {
DLog("willConnect to an unexpected peripheral")
return
}
let localizationManager = LocalizationManager.shared
updateStatusLabel()
detailLabel.text = localizationManager.localizedString("scanner_connecting")
}
private func didConnectToPeripheral(notification: Notification) {
guard let selectedPeripheral = selectedPeripheral, let identifier = notification.userInfo?[BleManager.NotificationUserInfoKey.uuid.rawValue] as? UUID, selectedPeripheral.identifier == identifier else {
DLog("didConnect to an unexpected peripheral")
return
}
// Setup peripheral
AdafruitBoardsManager.shared.startBoard(connectedBlePeripheral: selectedPeripheral) { [weak self] result in
guard let self = self else { return }
switch result {
case .success:
DLog("setupPeripheral success")
// Finished setup
self.showPeripheralDetails()
case .failure(let error):
DLog("setupPeripheral error: \(error.localizedDescription)")
let localizationManager = LocalizationManager.shared
let alertController = UIAlertController(title: localizationManager.localizedString("dialog_error"), message: localizationManager.localizedString("scanner_error_startboard"), preferredStyle: .alert)
let okAction = UIAlertAction(title: localizationManager.localizedString("dialog_ok"), style: .default, handler: nil)
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
self.disconnect(peripheral: selectedPeripheral)
}
}
}
private func willDiscoverServices(notification: Notification) {
// Change text (animated fade-out / fade-in)
UIView.animate(withDuration: 0.15, animations: {
self.detailContainerView.alpha = 0
}) { finished in
self.detailLabel.text = LocalizationManager.shared.localizedString("scanner_discoveringservices")
if finished {
UIView.animate(withDuration: 0.2) {
self.detailContainerView.alpha = 1
}
}
}
}
private func didDisconnectFromPeripheral(notification: Notification) {
let peripheral = bleManager.peripheral(from: notification)
let currentlyConnectedPeripheralsCount = bleManager.connectedPeripherals().count
guard let selectedPeripheral = selectedPeripheral, selectedPeripheral.identifier == peripheral?.identifier || currentlyConnectedPeripheralsCount == 0 else { // If selected peripheral is disconnected or if there are no peripherals connected (after a failed dfu update)
return
}
// Clear selected peripheral
self.selectedPeripheral = nil
// UI
updateStatusLabel()
}
private func peripheralDidUpdateName(notification: Notification) {
let name = notification.userInfo?[BlePeripheral.NotificationUserInfoKey.name.rawValue] as? String
DLog("centralManager peripheralDidUpdateName: \(name ?? "<unknown>")")
updateStatusLabel()
}
// MARK: - Connections
private func connect(peripheral: BlePeripheral) {
// Connect to selected peripheral
selectedPeripheral = peripheral
bleManager.connect(to: peripheral)
}
private func disconnect(peripheral: BlePeripheral) {
selectedPeripheral = nil
bleManager.disconnect(from: peripheral)
}
// MARK: - Actions
@IBAction func troubleshooting(_ sender: Any) {
guard let viewController = storyboard?.instantiateViewController(withIdentifier: AboutViewController.kIdentifier) else { return }
self.present(viewController, animated: true, completion: nil)
}
@IBAction func scanManually(_ sender: Any) {
ScreenFlowManager.goToManualScan()
}
private func showPeripheralDetails() {
// Save selected peripheral for autoconnect
if let autoconnectPeripheral = selectedPeripheral {
Settings.autoconnectPeripheral = (autoconnectPeripheral.identifier, autoconnectPeripheral.advertisement.advertisementData)
}
ScreenFlowManager.gotoCPBModules()
}
// MARK: - UI
private func refreshPeripherals() {
bleManager.refreshPeripherals()
// reloadBaseTable()
}
private func updateScannedPeripherals() {
// Update peripheralAutoconnect
if let peripheral = peripheralAutoConnect.update(peripheralList: peripheralList) {
// Connect to closest CPB
connect(peripheral: peripheral)
}
}
private func updateStatusLabel() {
let localizationManager = LocalizationManager.shared
let statusText: String
if let selectedPeripheral = selectedPeripheral {
statusText = "Device found:\n\(selectedPeripheral.name ?? localizationManager.localizedString("scanner_unnamed"))"
boardImageView.image = AdafruitBoard.assetFrontImage(model: selectedPeripheral.adafruitManufacturerData()?.boardModel)
// Animate found board
self.boardImageView.alpha = 0.8
UIView.animate(withDuration: 0.15, animations: {
self.boardContainerView.transform = CGAffineTransform(scaleX: 0.75, y: 0.75)
}) { didFinish in
if didFinish {
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: [.curveEaseOut], animations: {
self.boardContainerView.transform = .identity
}, completion: nil)
UIView.animate(withDuration: 0.3) {
self.boardImageView.alpha = 1
}
}
}
// Show details
UIView.animate(withDuration: 0.2) {
self.detailContainerView.alpha = 1
}
} else {
UIView.animate(withDuration: 0.3) {
self.boardImageView.alpha = 0
}
statusText = localizationManager.localizedString("autoconnect_searching")
detailContainerView.alpha = 0
}
statusLabel.text = statusText
}
}
// MARK: UIScrollViewDelegate
extension AutoConnectViewController {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// NavigationBar Button Custom Animation
if let customNavigationBar = navigationController?.navigationBar as? NavigationBarWithScrollAwareRightButton {
customNavigationBar.updateRightButtonPosition()
}
}
}
// MARK: - Animations
extension AutoConnectViewController {
private func stopAnimating() {
isAnimating = false
if isViewLoaded {
let waveImageViews = [wave0ImageView, wave1ImageView, wave2ImageView]
for waveImageView in waveImageViews {
if let waveImageView = waveImageView {
waveImageView.layer.removeAllAnimations()
waveImageView.alpha = 0
}
}
}
}
private func startAnimating() {
isAnimating = true
guard isViewLoaded else { return }
let waveImageViews = [wave0ImageView, wave1ImageView, wave2ImageView]
// Scanning animation
let duration: Double = 12 //8
let initialScaleFactor: CGFloat = 0// 0.60
let finalScaleFactor: CGFloat = 1.10
let initialAlphaFactor: CGFloat = 1//0.80
let finalAlphaFactor: CGFloat = 0
// - Initial position
let introMaxValueFactor: CGFloat = 0.7
for (i, waveImageView) in waveImageViews.enumerated() {
if let waveImageView = waveImageView {
//DLog("intro: \(i)")
let factor: CGFloat = CGFloat(i) / CGFloat(waveImageViews.count-1) * introMaxValueFactor
let scaleFactor = (finalScaleFactor - initialScaleFactor) * factor + initialScaleFactor
waveImageView.transform = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor)
let alphaFactor = (finalAlphaFactor - initialAlphaFactor) * factor + initialAlphaFactor
waveImageView.alpha = alphaFactor
// DLog("\(i): factor: \(factor) scale: \(scaleFactor)")
let introDuration = (1 - Double(factor)) * duration
UIView.animate(withDuration: introDuration, delay: 0, options: [.curveEaseOut], animations: {
waveImageView.transform = CGAffineTransform(scaleX: finalScaleFactor, y: finalScaleFactor)
waveImageView.alpha = finalAlphaFactor
}, completion: { _ in
// Ongoing
waveImageView.transform = CGAffineTransform(scaleX: initialScaleFactor, y: initialScaleFactor)
waveImageView.alpha = initialAlphaFactor
UIView.animate(withDuration: duration, delay: 0, options: [.repeat, .curveEaseOut], animations: {
waveImageView.transform = CGAffineTransform(scaleX: finalScaleFactor, y: finalScaleFactor)
waveImageView.alpha = finalAlphaFactor
}, completion: nil)
})
}
}
}
}