// // 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) } }