// // ServersController.swift // Privado // // Created by Jura on 10/10/19. // Copyright © 2019 Omicronmedia. All rights reserved. // import Foundation import UIKit protocol ServersControllerOutput: AnyObject { func viewDidAppear() } protocol ServersControllerInput: AnyObject { func upgradeWarning(show: Bool) func changeMessageVisibility(isHidden: Bool) func updateBackground(isHighlighted: Bool) func changeMenuState() func changeNotificationsState() func changeTutorialState(show: Bool) func scrollServers(direction: ServersScrollDirection) } enum ServersControllerComponent { case location case serverList case connection case menu case notifications case message case upgradeWarning case traffic } enum ServersControllerModalComponent { case upgrade case tutorial } final class ServersController: UIViewController, ServersControllerInput { private enum Constants { static let serverCellIndex = 3 enum ImageName { static let notificationsButton = "notifications_button" static let mapBackground = "map.background" static let title = "mainTitle" } enum Font { static let title = UIFont(name: "SFProText-Bold", size: 20) ?? .boldSystemFont(ofSize: 20) } enum Geometry { static let titleImageSize = CGSize(width: 147, height: 20) static let menuLeading: CGFloat = 15 static let menuHeight: CGFloat = 50 static let statusBarHeight: CGFloat = UIApplication.shared.statusBarFrame.height static let trafficCounterIndent: CGFloat = 26 static let trafficCounterHeight: CGFloat = 70 static let messageViewIndent: CGFloat = 26 } enum Color { static let background = UIColor(red: 21, green: 25, blue: 53) static let navBar = UIColor(red: 3, green: 15, blue: 72) static let statusBar = UIColor(red: 18, green: 23, blue: 45) static let bgGradientStart = UIColor(red: 47, green: 42, blue: 96) static let bgGradientEnd = UIColor(red: 80, green: 88, blue: 200) } static let bgGradientAlpha: CGFloat = 0.45 static let connectionTop: CGFloat = 43 static let bottom: CGFloat = -60 static let between: CGFloat = 20 static let locationTop: CGFloat = 4 static let listBetweenLocation: CGFloat = 60 static let buttonBetweenList: CGFloat = 40 static let notificationsButtonRight: CGFloat = 8 static let navBarHeight: CGFloat = 51 } private let output: ServersControllerOutput private var background: UIImageView? private var upgradeView: UIView? private var upgradeWarningView: UIView? private var messageView: UIView? private var serverList: UIView? private var tutorialViewController: UIViewController? private var componentCreationClosure: ComponentCreationClosure private let modalCreationClosure: ModalCreationClosure typealias ComponentCreationClosure = (ServersControllerComponent) -> UIView typealias ModalCreationClosure = (ServersControllerModalComponent) -> UIViewController // MARK: - Init init(output: ServersControllerOutput, componentCreation: @escaping ComponentCreationClosure, modalCreation: @escaping ModalCreationClosure) { self.output = output self.componentCreationClosure = componentCreation self.modalCreationClosure = modalCreation super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - Lyfecycle override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent // .default } override func loadView() { let view = UIView(frame: .zero) view.translatesAutoresizingMaskIntoConstraints = false view.backgroundColor = Constants.Color.background self.view = view } override func viewDidLoad() { super.viewDidLoad() self.configureUI() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) guard let superview = self.view.superview else { return } NSLayoutConstraint.activate([ self.view.topAnchor.constraint(equalTo: superview.topAnchor), self.view.bottomAnchor.constraint(equalTo: superview.bottomAnchor), self.view.leadingAnchor.constraint(equalTo: superview.leadingAnchor), self.view.trailingAnchor.constraint(equalTo: superview.trailingAnchor) ]) self.output.viewDidAppear() } // MARK: - ServersControllerInput func upgradeWarning(show: Bool) { self.upgradeWarningView?.isHidden = !show } func changeMessageVisibility(isHidden: Bool) { guard let messageView = self.messageView else { return } if isHidden { messageView.removeFromSuperview() } else { self.layoutMessageView() if let serverList = self.serverList { self.view.insertSubview(messageView, belowSubview: serverList) } else { self.view.addSubview(messageView) } } } func updateBackground(isHighlighted: Bool) { } func changeMenuState() { } func changeNotificationsState() { } func changeTutorialState(show: Bool) { self.tutorialViewController?.view.fade(fadeIn: show) } func scrollServers(direction: ServersScrollDirection) { guard let serverList = self.serverList as? ServerListViewController else { return } switch direction { case .up: serverList.pullUpList() serverList.expandOrCollapse(index: Constants.serverCellIndex) case .down: serverList.pullDownList() serverList.expandOrCollapse(index: Constants.serverCellIndex) } } // MARK: - UI // swiftlint:disable function_body_length private func configureUI() { // Background gradient let gradientView = self.initializeBackgroundGradient() NSLayoutConstraint.activate([ gradientView.topAnchor.constraint(equalTo: self.view.topAnchor), gradientView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), gradientView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), gradientView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor) ]) // Background image let bgImageView = self.initializeBackgroundImageView() NSLayoutConstraint.activate([ bgImageView.topAnchor.constraint(equalTo: self.view.topAnchor), bgImageView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), bgImageView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), bgImageView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) ]) // Status bar let statusBarView = UIView() statusBarView.backgroundColor = Constants.Color.statusBar statusBarView.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(statusBarView) NSLayoutConstraint.activate([ statusBarView.heightAnchor.constraint(equalToConstant: Constants.Geometry.statusBarHeight), statusBarView.topAnchor.constraint(equalTo: self.view.topAnchor), statusBarView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), statusBarView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) ]) // Nav bar let navBarBackground = UIView() navBarBackground.backgroundColor = Constants.Color.navBar navBarBackground.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(navBarBackground) NSLayoutConstraint.activate([ navBarBackground.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor), navBarBackground.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), navBarBackground.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), navBarBackground.heightAnchor.constraint(equalToConstant: Constants.navBarHeight) ]) // left menu let menuButton = self.componentCreationClosure(.menu) menuButton.translatesAutoresizingMaskIntoConstraints = false navBarBackground.addSubview(menuButton) NSLayoutConstraint.activate([ menuButton.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: Constants.Geometry.menuLeading), menuButton.centerYAnchor.constraint(equalTo: navBarBackground.centerYAnchor), menuButton.heightAnchor.constraint(equalToConstant: Constants.Geometry.menuHeight), menuButton.widthAnchor.constraint(equalTo: menuButton.heightAnchor) ]) // title let titleImage = UIImage(imageLiteralResourceName: Constants.ImageName.title) let titleView = UIImageView(image: titleImage) titleView.translatesAutoresizingMaskIntoConstraints = false navBarBackground.addSubview(titleView) NSLayoutConstraint.activate([ titleView.centerXAnchor.constraint(equalTo: navBarBackground.centerXAnchor), titleView.centerYAnchor.constraint(equalTo: navBarBackground.centerYAnchor), titleView.heightAnchor.constraint(equalToConstant: Constants.Geometry.titleImageSize.height), titleView.widthAnchor.constraint(equalToConstant: Constants.Geometry.titleImageSize.width) ]) // notifications button let notificationsButton = self.componentCreationClosure(.notifications) notificationsButton.translatesAutoresizingMaskIntoConstraints = false navBarBackground.addSubview(notificationsButton) NSLayoutConstraint.activate([ notificationsButton.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -Constants.notificationsButtonRight), notificationsButton.centerYAnchor.constraint(equalTo: navBarBackground.centerYAnchor), notificationsButton.heightAnchor.constraint(equalToConstant: 50), notificationsButton.widthAnchor.constraint(equalTo: menuButton.heightAnchor) ]) // message let messageView = self.componentCreationClosure(.message) self.messageView = messageView // Connection status let connection = self.componentCreationClosure(.connection) self.view.addSubview(connection) NSLayoutConstraint.activate([ connection.topAnchor.constraint(equalTo: navBarBackground.bottomAnchor, constant: Constants.connectionTop), connection.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), connection.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) ]) // Location IP address let currentLocation = self.componentCreationClosure(.location) self.view.addSubview(currentLocation) NSLayoutConstraint.activate([ currentLocation.topAnchor.constraint(equalTo: connection.bottomAnchor, constant: Constants.locationTop), currentLocation.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), currentLocation.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), currentLocation.heightAnchor.constraint(greaterThanOrEqualToConstant: 30) ]) // Serverlist guard let serverlist = self.componentCreationClosure(.serverList) as? (UIView & ServerListPullable) else { return } self.serverList = serverlist serverlist.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(serverlist) let heightConstraint = serverlist.heightAnchor.constraint(equalToConstant: serverlist.minHeight) NSLayoutConstraint.activate([ serverlist.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), serverlist.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), heightConstraint, serverlist.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), serverlist.topAnchor.constraint(greaterThanOrEqualTo: self.view.safeAreaLayoutGuide.topAnchor) ]) serverlist.heightConstraint = heightConstraint serverlist.isHidden = true // Upgrade let upgradeController = self.modalCreationClosure(.upgrade) self.addChild(upgradeController) upgradeController.didMove(toParent: self) self.view.addSubview(upgradeController.view) NSLayoutConstraint.activate([ upgradeController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), upgradeController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), upgradeController.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), upgradeController.view.topAnchor.constraint(equalTo: navBarBackground.bottomAnchor) ]) // Traffic Counter let trafficCounterView = self.componentCreationClosure(.traffic) self.view.addSubview(trafficCounterView) NSLayoutConstraint.activate([ trafficCounterView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), trafficCounterView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: Constants.Geometry.trafficCounterIndent), trafficCounterView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constants.Geometry.trafficCounterIndent), trafficCounterView.heightAnchor.constraint(equalToConstant: Constants.Geometry.trafficCounterHeight) ]) // Upgrade warning to show when selected premium server from list let upgradeWarningView = self.componentCreationClosure(.upgradeWarning) upgradeWarningView.isHidden = true self.upgradeWarningView = upgradeWarningView self.view.addSubview(upgradeWarningView) NSLayoutConstraint.activate([ upgradeWarningView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), upgradeWarningView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), upgradeWarningView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), upgradeWarningView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor) ]) // App tutorial view let appTutorialViewController = self.modalCreationClosure(.tutorial) appTutorialViewController.view.isHidden = true self.tutorialViewController = appTutorialViewController self.addChild(appTutorialViewController) self.view.addSubview(appTutorialViewController.view) NSLayoutConstraint.activate([ appTutorialViewController.view.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor), appTutorialViewController.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), appTutorialViewController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), appTutorialViewController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor) ]) } // swiftlint:enable function_body_length private func layoutMessageView() { guard let messageView = self.messageView else { return } self.view.addSubview(messageView) NSLayoutConstraint.activate([ messageView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: Constants.navBarHeight), messageView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: Constants.Geometry.messageViewIndent), messageView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constants.Geometry.messageViewIndent) ]) } private func initializeBackgroundImageView() -> UIImageView { let image = UIImage(imageLiteralResourceName: Constants.ImageName.mapBackground) let imageView = ImageView(image: image) self.view.addSubview(imageView) self.background = imageView return imageView } private func initializeBackgroundGradient() -> UIView { let gradientView = GradientView() gradientView.translatesAutoresizingMaskIntoConstraints = false gradientView.horizontalMode = true gradientView.alpha = Constants.bgGradientAlpha gradientView.stops = [ .init(color: Constants.Color.bgGradientStart, location: 0), .init(color: Constants.Color.bgGradientEnd, location: 1) ] self.view.addSubview(gradientView) return gradientView } } extension ServersController: ViewModuleControllerType { }