Files
2021-11-15 13:50:43 +03:00

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 { }