Files

250 lines
8.2 KiB
Swift

//
// MainController.swift
// PrivadoVPN
//
// Created by Juraldinio on 1/27/21.
// Copyright © 2021 Privado LLC. All rights reserved.
//
import Foundation
import AppKit
protocol MainControllerOutput: AnyObject {
func viewIsReady()
}
protocol MainControllerInput: AnyObject {
func loading(progress: Bool)
func activate(components: [MainControllerComponent])
}
enum MainControllerComponent: CaseIterable {
case notification
case connection
case servers
case killswitch
case remaining
}
final class MainController: NSViewController {
typealias HeaderCreationClosure = () -> NSViewController
typealias ComponentCreationClosure = (MainControllerComponent) -> NSView
private enum Constants {
enum Geometry {
// static let logoHeight: CGFloat = 87
static let progressSize = CGSize(width: 120, height: 120)
static let textHeight: CGFloat = 20
static let textTopSpace: CGFloat = 5
}
enum Color {
static let background = NSColor(red: 81, green: 88, blue: 193)
static let activityTitle = NSColor(red: 122, green: 134, blue: 190)
}
enum Font {
// swiftlint:disable nesting
enum Name {
static let activityTitle = PrivadoConstants.Font.light
}
enum Size {
static let activityTitle: CGFloat = 14
}
// swiftlint:enable nesting
}
enum Image {
static let background = "map.background"
}
}
private let output: MainControllerOutput
private var headerCreationClosure: HeaderCreationClosure
private var componentCreationClosure: ComponentCreationClosure
private lazy var activityIndicator: ITProgressIndicator = {
let indicator = ITProgressIndicator(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
indicator.color = .white
indicator.hideWhenStopped = true
indicator.lengthOfLine = 13
indicator.widthOfLine = 5
indicator.numberOfLines = 9
indicator.innerMargin = 8
indicator.translatesAutoresizingMaskIntoConstraints = false
return indicator
}()
private lazy var loader = PrivadoLoader(frame: .zero, isButton: false)
private var header: NSViewController?
private var firstLoading = true
private var backgroundImageView: ImageView
// MARK: - JSNavigationBarViewControllerProvider
var navigationController: JSNavigationController?
// MARK: - Init
init(output: MainControllerOutput, headerCreation: @escaping HeaderCreationClosure, componentCreation: @escaping ComponentCreationClosure) {
self.output = output
self.headerCreationClosure = headerCreation
self.componentCreationClosure = componentCreation
self.backgroundImageView = ImageView()
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Lyfecycle
override func loadView() {
let view = NSView(frame: .zero)
view.autoresizesSubviews = true
view.translatesAutoresizingMaskIntoConstraints = false
view.wantsLayer = true
view.layer?.backgroundColor = Constants.Color.background.cgColor
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
self.output.viewIsReady()
}
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)
])
}
self.view.makeKey()
}
// MARK: - UI
private func configureBackground() {
if let image = NSImage(named: Constants.Image.background) {
let coloredImage = image.imageWithTintColor(tintColor: NSColor.black)
self.backgroundImageView.image = coloredImage
}
self.backgroundImageView.imageScaling = .scaleNone
self.view.addSubview(self.backgroundImageView)
NSLayoutConstraint.activate([
self.backgroundImageView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: -50),
self.backgroundImageView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
self.backgroundImageView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
self.backgroundImageView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
])
}
}
// MARK: - MainControllerInput
extension MainController: MainControllerInput {
func loading(progress: Bool) {
if self.firstLoading {
guard progress else { return }
self.loader.translatesAutoresizingMaskIntoConstraints = false
self.loader.arcColor = NSColor(red: 132, green: 138, blue: 217)
self.loader.keyholeColor = NSColor.white
self.loader.arcWidth = 5
self.view.addSubview(self.loader)
NSLayoutConstraint.activate([
self.loader.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
self.loader.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
self.loader.heightAnchor.constraint(equalToConstant: Constants.Geometry.progressSize.height),
self.loader.widthAnchor.constraint(equalToConstant: Constants.Geometry.progressSize.height)
])
self.loader.startAnimation(clockwise: false)
self.firstLoading = false
} else {
if !progress {
self.loader.removeFromSuperview()
}
}
}
func delay(_ delay: Double, closure: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: closure)
}
func activate(components: [MainControllerComponent]) {
self.loader.removeFromSuperview()
self.configureBackground()
let wholeComponents = MainControllerComponent.allCases.count == components.count
let views = components.map { self.componentCreationClosure($0) }
views.forEach { self.view.addSubview($0) }
views.enumerated().forEach { index, view in
let constraints: [NSLayoutConstraint]
if index == 0 {
constraints = [
view.topAnchor.constraint(equalTo: self.view.topAnchor),
view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
]
} else if index == views.count - 1, wholeComponents {
constraints = [
view.topAnchor.constraint(equalTo: views[index - 1].bottomAnchor),
view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
]
} else {
constraints = [
view.topAnchor.constraint(equalTo: views[index - 1].bottomAnchor),
view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor)
]
}
NSLayoutConstraint.activate(constraints)
}
}
}
extension MainController: JSNavigationBarViewControllerProvider {
func navigationBarViewController() -> NSViewController {
if let header = self.header { return header }
let header = self.headerCreationClosure()
self.header = header
return header
}
}