Files

307 lines
12 KiB
Swift

//
// OptionsPreferencesViewController.swift
// Cyberlock
//
// Created by Jura on 9/12/19.
// Copyright © 2019 Omicronmedia. All rights reserved.
//
import Foundation
import AppKit
protocol OptionsPreferencesViewInput: AnyObject {
func available(options: [OptionsPreferenceRecord])
func select(type: OptionsPreferenceRecordType, selected: Bool)
func options(enable: Bool, for type: OptionsPreferenceRecordType)
}
protocol OptionsPreferencesViewOutput: AnyObject {
func isReady()
func changed(_ record: OptionsPreferenceRecord, selected: Bool)
func changeAutoConnectPreference(_ index: Int)
// func autostart(enabled: Bool)
}
final class OptionsPreferencesViewController: NSViewController, PreferencePane {
// swiftlint:disable nesting
enum Constants {
enum Localized {
static let switchOn = "preference.logging.switchOn"
static let switchOff = "preference.logging.switchOff"
}
enum Geometry {
static let buttonsTagDelta = 1000
static let leading: CGFloat = 20.0
static let topSpace: CGFloat = 15.0
static let recordsSpace: CGFloat = 20.0
static let innerSpace: CGFloat = 10.0
static let leadingDescription: CGFloat = 30.0
static let switchBorderWidth: CGFloat = 3.0
static let trailing: CGFloat = 70.0
static let switchTrailing: CGFloat = 30.0
static let separatorTrailing: CGFloat = 20.0
static let textTrailing: CGFloat = 20.0
static let separatorHeight: CGFloat = 1.0
static let switchWidth: CGFloat = 50.0
static let switchHeight: CGFloat = 25.0
static let viewTop: CGFloat = 20
}
enum Color {
static let switchBorder = NSColor(red: 122, green: 134, blue: 190)
static let switchOnTint = NSColor(red: 122, green: 134, blue: 190)
static let switchOffTint = NSColor(red: 0, green: 8, blue: 44)
static let switchOnText = NSColor(red: 0, green: 8, blue: 44)
static let switchOffText = NSColor(red: 122, green: 134, blue: 190)
static let text = NSColor(red: 124, green: 134, blue: 186)
}
enum Font {
enum Size {
static let title: CGFloat = 20
}
}
}
// swiftlint:enable nesting
private var options = [OptionsPreferenceRecord]()
private let output: OptionsPreferencesViewOutput
// MARK: - Init
init(output: OptionsPreferencesViewOutput) {
self.output = output
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Lifecycle
override func loadView() {
let view = NSView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
self.output.isReady()
}
override func viewDidAppear() {
super.viewDidAppear()
NSLayoutConstraint.activate([
view.widthAnchor.constraint(equalToConstant: 500),
view.heightAnchor.constraint(equalToConstant: 524)
])
}
// MARK: - Private
private func configureSwitch() -> Switch {
let switchButton = Switch(frame: .zero)
switchButton.translatesAutoresizingMaskIntoConstraints = false
switchButton.borderColor = Constants.Color.switchBorder
switchButton.onTintColor = Constants.Color.switchOnTint
switchButton.offTintColor = Constants.Color.switchOffTint
switchButton.onTextColor = Constants.Color.switchOnText
switchButton.offTextColor = Constants.Color.switchOffText
switchButton.onText = NSLocalizedString(Constants.Localized.switchOn, comment: "")
switchButton.offText = NSLocalizedString(Constants.Localized.switchOff, comment: "")
switchButton.borderWidth = Constants.Geometry.switchBorderWidth
switchButton.target = self
switchButton.action = #selector(self.checkButtonAction(_:))
return switchButton
}
private func createRecordRepresentation(index: Int, record: OptionsPreferenceRecord, after: NSView, constaints: inout [NSLayoutConstraint]) -> NSView {
let switchButton = self.configureSwitch()
switchButton.tag = Constants.Geometry.buttonsTagDelta + index
switchButton.isEnabled = record.enabled
switchButton.isOn = record.selected
switchButton.borderWidth = 2.0
switchButton.thumbRadiusPadding = 1.5
self.view.addSubview(switchButton)
let titleField = NSTextField(labelWithString: record.title)
titleField.isEnabled = record.enabled
titleField.usesSingleLineMode = true
titleField.translatesAutoresizingMaskIntoConstraints = false
titleField.font = NSFont(name: PrivadoConstants.Font.semiBold, size: 16)
titleField.textColor = .white
self.view.addSubview(titleField)
let textField = NSTextField(labelWithString: record.description)
textField.textColor = Constants.Color.text
textField.isEnabled = record.enabled
textField.lineBreakMode = .byWordWrapping
textField.usesSingleLineMode = false
textField.maximumNumberOfLines = 0
textField.translatesAutoresizingMaskIntoConstraints = false
textField.font = NSFont(name: PrivadoConstants.Font.regular, size: 12)
self.view.addSubview(textField)
let separatorView = NSView()
separatorView.translatesAutoresizingMaskIntoConstraints = false
separatorView.viewBackgroundColor = NSColor(red: 130, green: 136, blue: 216)
self.view.addSubview(separatorView)
let layout: [NSLayoutConstraint] = [
separatorView.topAnchor.constraint(equalTo: after.bottomAnchor, constant: Constants.Geometry.recordsSpace),
separatorView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: Constants.Geometry.leading),
separatorView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constants.Geometry.separatorTrailing),
separatorView.heightAnchor.constraint(equalToConstant: after == self.view ? 0 : Constants.Geometry.separatorHeight),
titleField.topAnchor.constraint(equalTo: (after == self.view ? after.topAnchor : separatorView.bottomAnchor), constant: (after == self.view ? Constants.Geometry.topSpace : Constants.Geometry.recordsSpace)),
titleField.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: Constants.Geometry.leading),
textField.topAnchor.constraint(equalTo: titleField.bottomAnchor, constant: Constants.Geometry.innerSpace),
textField.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: Constants.Geometry.leading),
textField.trailingAnchor.constraint(equalTo: switchButton.leadingAnchor, constant: -Constants.Geometry.textTrailing),
switchButton.topAnchor.constraint(equalTo: (after == self.view ? after.topAnchor : separatorView.bottomAnchor), constant: (after == self.view ? Constants.Geometry.viewTop : Constants.Geometry.topSpace)),
switchButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constants.Geometry.switchTrailing),
switchButton.widthAnchor.constraint(equalToConstant: Constants.Geometry.switchWidth),
switchButton.heightAnchor.constraint(equalToConstant: Constants.Geometry.switchHeight)
]
constaints.append(contentsOf: layout)
var optionsLayout = [NSLayoutConstraint]()
guard let views = self.createOptionsRepresentation(index: index, options: record.options, constaints: &optionsLayout) else {
return textField
}
views.enumerated().forEach { (index, view) in
if index == 0 {
optionsLayout.append(contentsOf: [
view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 10),
view.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 15),
view.heightAnchor.constraint(equalToConstant: 150),
view.widthAnchor.constraint(equalToConstant: 250)
])
}
}
views.forEach(self.view.addSubview)
constaints.append(contentsOf: optionsLayout)
self.optionsViews[index] = views
return textField
}
private var optionsViews = [Int: [PreferenceOptionsView]]()
private func createOptionsRepresentation(index: Int, options: [PreferenceOptions]?, constaints: inout [NSLayoutConstraint]) -> [PreferenceOptionsView]? {
guard let options = options else { return nil }
return options.map {
PreferenceOptionsView(index: index, option: $0, delegate: self)
}
}
@objc
private func checkButtonAction(_ sender: Switch?) {
guard let button = sender
, let record = self.options[safe: (button.tag - Constants.Geometry.buttonsTagDelta)] else {
return
}
self.output.changed(record, selected: button.isOn == true)
}
private func enableOptions(for record: OptionsPreferenceRecord, selected: Bool) {
self.options.enumerated().forEach { index, value in
guard value == record else { return }
guard let views = self.optionsViews[index] else { return }
views.forEach { $0.isEnabled = selected }
}
}
// MARK: - PreferencePane
var toolbarItemIcon: NSImage {
guard let img = NSImage(named: NSImage.preferencesGeneralName) else {
return NSImage(size: .zero)
}
return img
}
let preferencePaneIdentifier = PreferenceWindowIdentifiers.options
lazy var preferencePaneTitle = PreferenceWindowIdentifiers.title(for: self.preferencePaneIdentifier)
}
// MARK: - OptionsPreferencesViewInput
extension OptionsPreferencesViewController: OptionsPreferencesViewInput {
func available(options: [OptionsPreferenceRecord]) {
self.options = options
var constraints = [NSLayoutConstraint]()
var view = self.view
options.enumerated().forEach { index, record in
view = self.createRecordRepresentation(index: index, record: record, after: view, constaints: &constraints)
}
NSLayoutConstraint.activate(constraints)
}
func select(type: OptionsPreferenceRecordType, selected: Bool) {
guard let index = self.options.firstIndex(where: { $0.type == type })
, let checkButton = self.view.subviews.first(where: { $0.tag == index + Constants.Geometry.buttonsTagDelta }) as? Switch else {
return
}
guard let record = self.options.first(where: { $0.type == type }) else { return }
checkButton.isOn = selected
self.enableOptions(for: record, selected: selected)
}
func options(enable: Bool, for type: OptionsPreferenceRecordType) {
guard let index = self.options.firstIndex(where: { $0.type == type })
, let checkButton = self.view.subviews.first(where: { $0.tag == index + Constants.Geometry.buttonsTagDelta }) as? Switch else {
return
}
guard let record = self.options.first(where: { $0.type == type }) else { return }
checkButton.isEnabled = enable
if record.options != nil {
self.enableOptions(for: record, selected: checkButton.isOn == true)
}
}
}
extension OptionsPreferencesViewController: PreferenceOptionsViewDelegate {
func selected(index: Int, option: PreferenceOptions, enabled: Bool?, optionIndex: Int?) {
guard let optionIndex = optionIndex else { return }
self.output.changeAutoConnectPreference(optionIndex)
}
}