399 lines
17 KiB
Swift
399 lines
17 KiB
Swift
//
|
|
// 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 { }
|