Files
Xaibatsu-Control-Panel/XIONControlPanel/Controllers/SwitchesViewController.swift

300 lines
12 KiB
Swift
Raw Normal View History

2015-12-31 15:41:32 -08:00
//
// SwitchesViewController.swift
// XIONControlPanel
//
// Created by Charles Magahern on 12/30/15.
// Copyright © 2015 XION. All rights reserved.
//
import Darwin
import Foundation
import UIKit
2016-07-24 00:14:04 -07:00
protocol SwitchesViewControllerDelegate: class
{
func switchesViewControllerDidToggleDevices(_ controller: SwitchesViewController, devices: [AnyDevice])
}
2015-12-31 15:41:32 -08:00
class SwitchesViewController: UIViewController,
UICollectionViewDelegateFlowLayout
{
weak var delegate: SwitchesViewControllerDelegate?
2019-10-13 15:40:13 -07:00
2025-06-19 19:25:53 -07:00
public var cellHeightScale: Double = 1.0
public var actionCellLayout: ActionCellLayout = .vertical
public var labelText: String = "switches" {
didSet { _label.text = labelText }
}
fileprivate var _collectionView: UICollectionView = UICollectionView(frame: CGRect.zero,
collectionViewLayout: UICollectionViewFlowLayout())
fileprivate var _collectionViewDataSource: UICollectionViewDiffableDataSource<Int, Int>!
fileprivate var _currentDevicesHash: Int = 0
2015-12-31 15:41:32 -08:00
2025-06-19 19:25:53 -07:00
fileprivate let _label = UILabel(frame: .zero)
2017-05-13 00:40:03 -07:00
static fileprivate let collectionViewDeviceSwitchCellReuseIdentifier = "DeviceSwitchReuseID"
static fileprivate let collectionViewActionCellReuseIdentifier = "ActionCellReuseID"
static fileprivate let collectionViewCellsSpacing: CGFloat = 5.0
2015-12-31 15:41:32 -08:00
static fileprivate let actionCellsSectionIdentifier = 0
static fileprivate let switchCellsSectionIdentifier = 1
fileprivate enum ActionCell: Int, CaseIterable
2016-07-24 00:14:04 -07:00
{
2017-05-13 00:40:03 -07:00
case allOn
case allOff
2015-12-31 18:20:50 -08:00
func name() -> String
{
switch self {
2017-05-13 00:40:03 -07:00
case .allOn:
2015-12-31 18:20:50 -08:00
return "All On"
2017-05-13 00:40:03 -07:00
case .allOff:
2015-12-31 18:20:50 -08:00
return "All Off"
}
}
static let count: Int = { return ActionCell.allCases.count }()
}
2025-06-19 19:25:53 -07:00
public enum ActionCellLayout
{
case horizontal
case vertical
}
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)
2015-12-31 18:20:50 -08:00
}
required init?(coder: NSCoder) { fatalError("Unimplemented") }
2015-12-31 15:41:32 -08:00
override func viewDidLoad()
{
super.viewDidLoad()
2025-06-19 19:25:53 -07:00
_label.text = labelText
_label.font = UIFont(name: "Orbitron-Medium", size: 14.0)
_label.textColor = .white.withAlphaComponent(0.8)
self.view.addSubview(_label)
2015-12-31 18:20:50 -08:00
let deviceCellReuseID = SwitchesViewController.collectionViewDeviceSwitchCellReuseIdentifier
let actionCellReuseID = SwitchesViewController.collectionViewActionCellReuseIdentifier
2015-12-31 15:41:32 -08:00
let layout = _collectionView.collectionViewLayout as! UICollectionViewFlowLayout
2017-05-13 00:40:03 -07:00
layout.scrollDirection = .vertical
2015-12-31 15:41:32 -08:00
layout.minimumInteritemSpacing = SwitchesViewController.collectionViewCellsSpacing
2016-01-01 18:38:35 -08:00
layout.minimumLineSpacing = SwitchesViewController.collectionViewCellsSpacing
2015-12-31 15:41:32 -08:00
2017-05-13 00:40:03 -07:00
_collectionView.backgroundColor = UIColor.black
2015-12-31 15:41:32 -08:00
_collectionView.delegate = self
_collectionView.dataSource = _collectionViewDataSource
2017-05-13 00:40:03 -07:00
_collectionView.register(WemoDeviceCellView.self, forCellWithReuseIdentifier: deviceCellReuseID)
_collectionView.register(WemoActionCellView.self, forCellWithReuseIdentifier: actionCellReuseID)
2015-12-31 15:41:32 -08:00
self.view.addSubview(_collectionView)
self.devices = []
2015-12-31 15:41:32 -08:00
}
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
let bounds = self.view.bounds
2025-06-19 19:25:53 -07:00
let labelSpacing = 8.0
let labelSize = _label.sizeThatFits(bounds.size)
_label.frame = CGRect(
x: 0.0,
y: 0.0,
width: labelSize.width,
height: labelSize.height
)
_collectionView.frame = CGRect(
x: 0.0,
y: _label.frame.maxY + labelSpacing,
width: bounds.width,
height: bounds.height - _label.frame.height - labelSpacing
)
2019-10-13 15:40:13 -07:00
_collectionView.collectionViewLayout.invalidateLayout()
2015-12-31 15:41:32 -08:00
}
// MARK: API
var devices: [AnyDevice] = []
{
didSet
{
2016-01-01 15:48:34 -08:00
// 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)
2016-01-01 15:48:34 -08:00
})
var snapshot = _collectionViewDataSource.snapshot()
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: SwitchesViewController.switchCellsSectionIdentifier))
snapshot.appendItems(self.devices.map { $0.hashValue }, toSection: SwitchesViewController.switchCellsSectionIdentifier)
snapshot.reloadSections([Self.actionCellsSectionIdentifier])
_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)
}
2015-12-31 15:41:32 -08:00
// MARK: UICollectionView
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, identifier: Int) -> UICollectionViewCell?
2015-12-31 15:41:32 -08:00
{
if (indexPath.section == SwitchesViewController.actionCellsSectionIdentifier) {
2015-12-31 18:20:50 -08:00
let reuseID = SwitchesViewController.collectionViewActionCellReuseIdentifier
2017-05-13 00:40:03 -07:00
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseID, for: indexPath) as! WemoActionCellView
cell.textLabel.text = ActionCell(rawValue: identifier)?.name().uppercased()
cell.enabled = (self.devices.count > 0)
2015-12-31 18:20:50 -08:00
return cell
} else if (indexPath.section == SwitchesViewController.switchCellsSectionIdentifier) {
2015-12-31 18:20:50 -08:00
let reuseID = SwitchesViewController.collectionViewDeviceSwitchCellReuseIdentifier
2017-05-13 00:40:03 -07:00
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseID, for: indexPath) as! WemoDeviceCellView
2015-12-31 18:20:50 -08:00
if let device = (self.devices.first { $0.hashValue == identifier }) {
cell.deviceName = device.name
cell.toggled = (device.state == .on)
cell.ordinal = indexPath.row
}
2015-12-31 18:20:50 -08:00
return cell
}
return nil
2015-12-31 15:41:32 -08:00
}
2017-05-13 00:40:03 -07:00
func collectionView(_ collectionView: UICollectionView,
2015-12-31 15:41:32 -08:00
layout collectionViewLayout: UICollectionViewLayout,
2017-05-13 00:40:03 -07:00
sizeForItemAt indexPath: IndexPath) -> CGSize
2015-12-31 15:41:32 -08:00
{
let spacing = SwitchesViewController.collectionViewCellsSpacing
2016-07-24 00:14:04 -07:00
let bounds = collectionView.bounds
2019-10-13 15:40:13 -07:00
let boundsWidth = bounds.size.width
2016-07-24 00:14:04 -07:00
var cellsPerRow: CGFloat = 0.0
2015-12-31 18:20:50 -08:00
2019-10-13 15:40:13 -07:00
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:
2016-07-24 00:14:04 -07:00
cellsPerRow = 3.0
2019-10-13 15:40:13 -07:00
2017-05-13 00:40:03 -07:00
case .compact:
2016-07-24 00:14:04 -07:00
cellsPerRow = 2.0
2019-10-13 15:40:13 -07:00
2016-07-24 00:14:04 -07:00
default:
cellsPerRow = 2.0
}
let dimensions = floor((collectionView.bounds.size.width / cellsPerRow) - ((spacing * (cellsPerRow - 1.0)) / cellsPerRow))
if (indexPath.section == SwitchesViewController.actionCellsSectionIdentifier) {
2025-06-19 19:25:53 -07:00
let bounds = collectionView.bounds
let height = rint(dimensions / 1.5) * cellHeightScale
switch actionCellLayout {
case .horizontal:
return CGSize(width: (bounds.width - spacing) / 2.0, height: height)
case .vertical:
return CGSize(width: bounds.size.width, height: height)
}
2015-12-31 18:20:50 -08:00
} else {
2025-06-19 19:25:53 -07:00
return CGSize(width: dimensions, height: dimensions * cellHeightScale)
2015-12-31 18:20:50 -08:00
}
2015-12-31 15:41:32 -08:00
}
2015-12-31 16:57:57 -08:00
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)
}
2017-05-13 00:40:03 -07:00
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
2015-12-31 16:57:57 -08:00
{
if (indexPath.section == SwitchesViewController.actionCellsSectionIdentifier) {
2015-12-31 18:20:50 -08:00
let tappedActionCell = ActionCell(rawValue: indexPath.item)
for cell in collectionView.visibleCells {
if collectionView.indexPath(for: cell)?.section == SwitchesViewController.switchCellsSectionIdentifier {
let switchCell = cell as! WemoDeviceCellView
2019-09-24 22:17:16 -07:00
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)
}
2015-12-31 18:20:50 -08:00
}
for device in self.devices {
2017-05-13 00:40:03 -07:00
device.state = (tappedActionCell == .allOn ? .on : .off)
2015-12-31 18:20:50 -08:00
}
self.delegate?.switchesViewControllerDidToggleDevices(self, devices: self.devices)
} else if (indexPath.section == SwitchesViewController.switchCellsSectionIdentifier) {
2017-05-13 00:40:03 -07:00
let cell = collectionView.cellForItem(at: indexPath) as! WemoDeviceCellView
2015-12-31 18:20:50 -08:00
cell.toggled = !cell.toggled
let device = self.devices[indexPath.row]
2017-05-13 00:40:03 -07:00
device.state = (cell.toggled ? .on : .off)
2015-12-31 18:20:50 -08:00
self.delegate?.switchesViewControllerDidToggleDevices(self, devices: [device])
}
2015-12-31 16:57:57 -08:00
}
2017-05-13 00:40:03 -07:00
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool
{
return self.collectionView(collectionView, shouldSelectItemAt: indexPath)
}
2017-05-13 00:40:03 -07:00
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool
{
if (indexPath.section == SwitchesViewController.actionCellsSectionIdentifier) {
return (self.devices.count > 0)
} else {
return true
}
}
2015-12-31 15:41:32 -08:00
}