250 lines
8.2 KiB
Swift
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
|
|
}
|
|
}
|