299 lines
11 KiB
Swift
299 lines
11 KiB
Swift
//
|
|
// ModalController.swift
|
|
// PrivadoVPN
|
|
//
|
|
// Created by Juraldinio on 4/15/21.
|
|
// Copyright © 2021 Privado LLC. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
protocol ModalControllerInput {
|
|
var modalIsVisible: Bool { get }
|
|
func modalShow()
|
|
func modalHide()
|
|
// swiftlint:disable function_parameter_count
|
|
func modalUpdate(closable: Bool, imageName: String?, title: String?, description: String?, action: String?)
|
|
// swiftlint:enable function_parameter_count
|
|
}
|
|
|
|
protocol ModalControllerOutput: AnyObject {
|
|
func modalControllerClose(_ controller: ModalControllerInput)
|
|
func modalControllerAction(_ controller: ModalControllerInput)
|
|
}
|
|
|
|
final class ModalController: NSViewController {
|
|
|
|
// swiftlint:disable nesting
|
|
private enum Constants {
|
|
|
|
enum Geometry {
|
|
|
|
static let leadingTrailing: CGFloat = 10
|
|
|
|
enum Close {
|
|
static let width: CGFloat = 23
|
|
static let height: CGFloat = 23
|
|
static let top: CGFloat = 11
|
|
static let trailing: CGFloat = 20
|
|
}
|
|
|
|
enum Title {
|
|
static let bottomTopDescription: CGFloat = 15
|
|
static let height: CGFloat = 54
|
|
}
|
|
|
|
enum Image {
|
|
static let verticalSpace: CGFloat = 10
|
|
static let width: CGFloat = 25
|
|
static let height: CGFloat = 35
|
|
}
|
|
|
|
enum Description {
|
|
static let height: CGFloat = 100
|
|
}
|
|
|
|
enum Action {
|
|
static let cornerRadius: CGFloat = 18
|
|
static let widthMultiplier: CGFloat = 0.8
|
|
static let height: CGFloat = 39
|
|
static let topBottomDescription: CGFloat = 24
|
|
}
|
|
|
|
}
|
|
|
|
enum Color {
|
|
static let background = NSColor(red: 4, green: 8, blue: 43)
|
|
|
|
// static let background = NSColor(red: 21, green: 25, blue: 53)
|
|
static let title = NSColor.white
|
|
static let description = NSColor(red: 122, green: 134, blue: 190)
|
|
static let buttonTitle = NSColor.white
|
|
static let buttonClosableBackground = NSColor(red: 42, green: 216, blue: 153)
|
|
static let buttonModalBackground = NSColor(red: 255, green: 143, blue: 0)
|
|
}
|
|
|
|
enum Font {
|
|
|
|
enum Name {
|
|
static let title = PrivadoConstants.Font.light
|
|
static let description = PrivadoConstants.Font.regular
|
|
static let action = PrivadoConstants.Font.black
|
|
}
|
|
|
|
enum Size {
|
|
static let title: CGFloat = 18
|
|
static let description: CGFloat = 13
|
|
static let action: CGFloat = 14
|
|
}
|
|
}
|
|
|
|
enum Image {
|
|
static let close = "close"
|
|
}
|
|
}
|
|
// swiftlint:enable nesting
|
|
|
|
weak var output: ModalControllerOutput?
|
|
private let stackManager: JSViewControllersStackManager?
|
|
|
|
private let closeButton: ImageView
|
|
private let uppearImageView: ImageView
|
|
private let titleField: NSTextField
|
|
private var descriptionField: DynamicTextField
|
|
private let actionButton: FlatButton
|
|
|
|
private var descriptionHeight: NSLayoutConstraint?
|
|
|
|
// MARK: - Init
|
|
|
|
init(stackManager: JSViewControllersStackManager?) {
|
|
self.stackManager = stackManager
|
|
|
|
self.closeButton = Self.createCloseButton(image: Constants.Image.close)
|
|
self.uppearImageView = Self.createImageView(image: nil)
|
|
self.titleField = Self.createTextField(color: Constants.Color.title,
|
|
font: NSFont(name: Constants.Font.Name.title, size: Constants.Font.Size.title),
|
|
numberLines: 2)
|
|
self.descriptionField = Self.createDynamicTextField(color: Constants.Color.description,
|
|
font: NSFont(name: Constants.Font.Name.description, size: Constants.Font.Size.description),
|
|
fontName: Constants.Font.Name.description)
|
|
self.actionButton = Self.createActionButton(corner: Constants.Geometry.Action.cornerRadius,
|
|
normalColor: Constants.Color.buttonClosableBackground,
|
|
highlightColor: Constants.Color.buttonClosableBackground,
|
|
titleColor: Constants.Color.buttonTitle,
|
|
font: NSFont(name: Constants.Font.Name.action, size: Constants.Font.Size.action))
|
|
|
|
super.init(nibName: nil, bundle: nil)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
override func loadView() {
|
|
|
|
let view = View(frame: .zero)
|
|
view.autoresizesSubviews = true
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
view.wantsLayer = true
|
|
view.viewBackgroundColor = Constants.Color.background
|
|
self.view = view
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
self.bind()
|
|
self.layoutUI()
|
|
}
|
|
|
|
override func viewDidAppear() {
|
|
super.viewDidAppear()
|
|
|
|
if let superview = self.view.superview {
|
|
NSLayoutConstraint.activate([
|
|
self.view.topAnchor.constraint(equalTo: superview.topAnchor),
|
|
self.view.leadingAnchor.constraint(equalTo: superview.leadingAnchor),
|
|
self.view.trailingAnchor.constraint(equalTo: superview.trailingAnchor),
|
|
self.view.bottomAnchor.constraint(equalTo: superview.bottomAnchor)
|
|
])
|
|
}
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private func bind() {
|
|
|
|
self.closeButton.target = self
|
|
self.closeButton.action = #selector(self.onCloseAction)
|
|
|
|
self.actionButton.target = self
|
|
self.actionButton.action = #selector(self.onActionButton)
|
|
|
|
[self.closeButton,
|
|
self.uppearImageView,
|
|
self.titleField,
|
|
self.descriptionField,
|
|
self.actionButton
|
|
].forEach(self.view.addSubview)
|
|
}
|
|
|
|
private func layoutUI() {
|
|
|
|
let constraint = self.descriptionField.heightAnchor.constraint(equalToConstant: Constants.Geometry.Description.height)
|
|
self.descriptionHeight = constraint
|
|
|
|
NSLayoutConstraint.activate([
|
|
self.closeButton.topAnchor.constraint(equalTo: self.view.topAnchor, constant: Constants.Geometry.Close.top),
|
|
self.closeButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constants.Geometry.Close.trailing),
|
|
self.closeButton.widthAnchor.constraint(equalToConstant: Constants.Geometry.Close.width),
|
|
self.closeButton.heightAnchor.constraint(equalToConstant: Constants.Geometry.Close.height),
|
|
//
|
|
self.descriptionField.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: Constants.Geometry.leadingTrailing),
|
|
self.descriptionField.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constants.Geometry.leadingTrailing),
|
|
self.descriptionField.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
|
|
constraint,
|
|
//
|
|
self.titleField.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: Constants.Geometry.leadingTrailing),
|
|
self.titleField.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constants.Geometry.leadingTrailing),
|
|
self.titleField.bottomAnchor.constraint(equalTo: self.descriptionField.topAnchor, constant: -Constants.Geometry.Title.bottomTopDescription),
|
|
self.titleField.heightAnchor.constraint(equalToConstant: Constants.Geometry.Title.height),
|
|
//
|
|
self.uppearImageView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
|
|
self.uppearImageView.bottomAnchor.constraint(equalTo: self.titleField.topAnchor, constant: -Constants.Geometry.Image.verticalSpace),
|
|
self.uppearImageView.widthAnchor.constraint(equalToConstant: Constants.Geometry.Image.width),
|
|
self.uppearImageView.heightAnchor.constraint(equalToConstant: Constants.Geometry.Image.height),
|
|
//
|
|
self.actionButton.heightAnchor.constraint(equalToConstant: Constants.Geometry.Action.height),
|
|
self.actionButton.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: Constants.Geometry.Action.widthMultiplier),
|
|
self.actionButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
|
|
self.actionButton.topAnchor.constraint(equalTo: self.descriptionField.bottomAnchor, constant: Constants.Geometry.Action.topBottomDescription)
|
|
])
|
|
}
|
|
|
|
// MARK: - on Handle
|
|
|
|
@objc
|
|
private func onCloseAction() {
|
|
self.output?.modalControllerClose(self)
|
|
}
|
|
|
|
@objc
|
|
private func onActionButton() {
|
|
self.output?.modalControllerAction(self)
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - ModalControllerInput
|
|
|
|
extension ModalController: ModalControllerInput {
|
|
|
|
func modalShow() {
|
|
|
|
guard let stackManager = self.stackManager
|
|
, let topController = stackManager.topViewController else {
|
|
return
|
|
}
|
|
|
|
guard !topController.children.contains(self) else { return }
|
|
|
|
topController.addChild(self)
|
|
topController.view.addSubview(self.view)
|
|
}
|
|
|
|
func modalHide() {
|
|
guard self.modalIsVisible else { return }
|
|
self.view.removeFromSuperview()
|
|
self.removeFromParent()
|
|
}
|
|
|
|
var modalIsVisible: Bool { self.parent.isExist }
|
|
|
|
// swiftlint:disable function_parameter_count
|
|
func modalUpdate(closable: Bool, imageName: String?, title: String?, description: String?, action: String?) {
|
|
|
|
self.closeButton.isHidden = !closable
|
|
|
|
if let imageName = imageName
|
|
, let image = NSImage(named: imageName) {
|
|
self.uppearImageView.image = image
|
|
self.uppearImageView.isHidden = false
|
|
self.descriptionHeight?.constant = 0
|
|
} else {
|
|
self.descriptionHeight?.constant = Constants.Geometry.Description.height
|
|
self.uppearImageView.isHidden = true
|
|
}
|
|
|
|
if let title = title {
|
|
self.titleField.stringValue = title
|
|
self.titleField.isHidden = false
|
|
} else {
|
|
self.titleField.isHidden = true
|
|
}
|
|
|
|
if let description = description {
|
|
self.descriptionField.stringValue = description
|
|
self.descriptionField.isHidden = false
|
|
} else {
|
|
self.descriptionField.isHidden = true
|
|
}
|
|
|
|
if let action = action {
|
|
self.actionButton.title = action
|
|
self.actionButton.isHidden = false
|
|
|
|
let color = closable ? Constants.Color.buttonClosableBackground : Constants.Color.buttonModalBackground
|
|
self.actionButton.backgroundNormalColor = color
|
|
self.actionButton.backgroundHighlightColor = color
|
|
} else {
|
|
self.actionButton.isHidden = true
|
|
}
|
|
|
|
}
|
|
// swiftlint:enable function_parameter_count
|
|
}
|