Files
Xaibatsu-Control-Panel/XIONControlPanel/Controllers/SwitchesViewController.swift
James Magahern 918818cd99 ServerMultiplex: Introduces a server mutiplexer
- Also moves all the existing Wemo* stuff over to support the multiplexer
- Moves MainViewController to be the owner of the multiplexer, and its delegate
2020-03-14 18:33:02 -07:00

256 lines
10 KiB
Swift

//
// SwitchesViewController.swift
// XIONControlPanel
//
// Created by Charles Magahern on 12/30/15.
// Copyright © 2015 XION. All rights reserved.
//
import Darwin
import Foundation
import UIKit
protocol SwitchesViewControllerDelegate: class
{
func switchesViewControllerDidToggleDevices(_ controller: SwitchesViewController, devices: [AnyDevice])
}
class SwitchesViewController: UIViewController,
UICollectionViewDelegateFlowLayout
{
weak var delegate: SwitchesViewControllerDelegate?
fileprivate var _collectionView: UICollectionView = UICollectionView(frame: CGRect.zero,
collectionViewLayout: UICollectionViewFlowLayout())
fileprivate var _collectionViewDataSource: UICollectionViewDiffableDataSource<Int, Int>!
fileprivate var _currentDevicesHash: Int = 0
static fileprivate let collectionViewDeviceSwitchCellReuseIdentifier = "DeviceSwitchReuseID"
static fileprivate let collectionViewActionCellReuseIdentifier = "ActionCellReuseID"
static fileprivate let collectionViewCellsSpacing: CGFloat = 5.0
static fileprivate let actionCellsSectionIdentifier = 0
static fileprivate let switchCellsSectionIdentifier = 1
fileprivate enum ActionCell: Int, CaseIterable
{
case allOn
case allOff
func name() -> String
{
switch self {
case .allOn:
return "All On"
case .allOff:
return "All Off"
}
}
static let count: Int = { return ActionCell.allCases.count }()
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
{
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
_collectionViewDataSource = UICollectionViewDiffableDataSource(collectionView: _collectionView, cellProvider: { (collectionView, indexPath, identifier) -> UICollectionViewCell? in
return self.collectionView(collectionView, cellForItemAt: indexPath, identifier: identifier)
})
// Initialize data source
var snapshot = _collectionViewDataSource.snapshot()
// Sections
snapshot.appendSections([
SwitchesViewController.actionCellsSectionIdentifier,
SwitchesViewController.switchCellsSectionIdentifier,
])
// Action cells
snapshot.appendItems([
SwitchesViewController.ActionCell.allOn.rawValue,
SwitchesViewController.ActionCell.allOff.rawValue
], toSection: SwitchesViewController.actionCellsSectionIdentifier)
_collectionViewDataSource.apply(snapshot, animatingDifferences: false)
}
required init?(coder: NSCoder) { fatalError("Unimplemented") }
override func viewDidLoad()
{
super.viewDidLoad()
let deviceCellReuseID = SwitchesViewController.collectionViewDeviceSwitchCellReuseIdentifier
let actionCellReuseID = SwitchesViewController.collectionViewActionCellReuseIdentifier
let layout = _collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.scrollDirection = .vertical
layout.minimumInteritemSpacing = SwitchesViewController.collectionViewCellsSpacing
layout.minimumLineSpacing = SwitchesViewController.collectionViewCellsSpacing
_collectionView.backgroundColor = UIColor.black
_collectionView.delegate = self
_collectionView.dataSource = _collectionViewDataSource
_collectionView.register(WemoDeviceCellView.self, forCellWithReuseIdentifier: deviceCellReuseID)
_collectionView.register(WemoActionCellView.self, forCellWithReuseIdentifier: actionCellReuseID)
self.view.addSubview(_collectionView)
self.devices = []
}
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
let bounds = self.view.bounds
_collectionView.frame = bounds
_collectionView.collectionViewLayout.invalidateLayout()
}
// MARK: API
var devices: [AnyDevice] = []
{
didSet
{
// sort devices by name
self.devices.sort(by: { (d1: Device, d2: Device) -> Bool in
return (d1.name.compare(d2.name, options: .caseInsensitive, range: nil, locale: nil) == .orderedAscending)
})
var snapshot = _collectionViewDataSource.snapshot()
snapshot.appendItems(self.devices.map { $0.hashValue }, toSection: SwitchesViewController.switchCellsSectionIdentifier)
_collectionViewDataSource.apply(snapshot, animatingDifferences: false)
}
}
public func devicesStateChanged(_ changedDevices: [AnyDevice])
{
var snapshot = _collectionViewDataSource.snapshot()
changedDevices.forEach { changedDevice in
if let existingDevice = (self.devices.first { $0.hashValue == changedDevice.hashValue }) {
existingDevice.state = changedDevice.state
snapshot.reloadItems([ existingDevice.hashValue ])
}
}
_collectionViewDataSource.apply(snapshot, animatingDifferences: true)
}
// MARK: UICollectionView
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, identifier: Int) -> UICollectionViewCell?
{
if (indexPath.section == SwitchesViewController.actionCellsSectionIdentifier) {
let reuseID = SwitchesViewController.collectionViewActionCellReuseIdentifier
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseID, for: indexPath) as! WemoActionCellView
cell.textLabel.text = ActionCell(rawValue: identifier)?.name().uppercased()
cell.enabled = (self.devices.count > 0)
return cell
} else if (indexPath.section == SwitchesViewController.switchCellsSectionIdentifier) {
let reuseID = SwitchesViewController.collectionViewDeviceSwitchCellReuseIdentifier
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseID, for: indexPath) as! WemoDeviceCellView
if let device = (self.devices.first { $0.hashValue == identifier }) {
cell.deviceName = device.name
cell.toggled = (device.state == .on)
cell.ordinal = indexPath.row
}
return cell
}
return nil
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize
{
let spacing = SwitchesViewController.collectionViewCellsSpacing
let bounds = collectionView.bounds
let boundsWidth = bounds.size.width
var cellsPerRow: CGFloat = 0.0
switch self.traitCollection.horizontalSizeClass {
case .regular where boundsWidth >= 500.0,
.compact where boundsWidth >= 500.0:
cellsPerRow = 4.0
case .regular where 400.0 ... 499.99 ~= boundsWidth,
.compact where 400.0 ... 499.99 ~= boundsWidth:
cellsPerRow = 3.0
case .compact:
cellsPerRow = 2.0
default:
cellsPerRow = 2.0
}
let dimensions = floor((collectionView.bounds.size.width / cellsPerRow) - ((spacing * (cellsPerRow - 1.0)) / cellsPerRow))
if (indexPath.section == SwitchesViewController.actionCellsSectionIdentifier) {
return CGSize(width: collectionView.bounds.size.width, height: rint(dimensions / 1.5))
} else {
return CGSize(width: dimensions, height: dimensions)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
{
let spacing = SwitchesViewController.collectionViewCellsSpacing
return UIEdgeInsets(top: spacing, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
if (indexPath.section == SwitchesViewController.actionCellsSectionIdentifier) {
let tappedActionCell = ActionCell(rawValue: indexPath.item)
for cell in collectionView.visibleCells {
if collectionView.indexPath(for: cell)?.section == SwitchesViewController.switchCellsSectionIdentifier {
let switchCell = cell as! WemoDeviceCellView
let animOptions = UIView.AnimationOptions([.allowUserInteraction])
let delay: TimeInterval = Double(switchCell.ordinal) * 0.05
UIView.animate(withDuration: 0.3, delay: delay, options: animOptions, animations: {
switchCell.toggled = (tappedActionCell == .allOn)
}, completion: nil)
}
}
for device in self.devices {
device.state = (tappedActionCell == .allOn ? .on : .off)
}
self.delegate?.switchesViewControllerDidToggleDevices(self, devices: self.devices)
} else if (indexPath.section == SwitchesViewController.switchCellsSectionIdentifier) {
let cell = collectionView.cellForItem(at: indexPath) as! WemoDeviceCellView
cell.toggled = !cell.toggled
let device = self.devices[indexPath.row]
device.state = (cell.toggled ? .on : .off)
self.delegate?.switchesViewControllerDidToggleDevices(self, devices: [device])
}
}
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool
{
return self.collectionView(collectionView, shouldSelectItemAt: indexPath)
}
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool
{
if (indexPath.section == SwitchesViewController.actionCellsSectionIdentifier) {
return (self.devices.count > 0)
} else {
return true
}
}
}