257 lines
10 KiB
Swift
257 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.deleteItems(snapshot.itemIdentifiers(inSection: SwitchesViewController.switchCellsSectionIdentifier))
|
|
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
|
|
}
|
|
}
|
|
}
|