427 lines
19 KiB
Swift
427 lines
19 KiB
Swift
//
|
|
// ServersControllerIPAD.swift
|
|
// PrivadoVPN
|
|
//
|
|
// Created by Zhandos Bolatbekov on 29.06.2021.
|
|
// Copyright © 2021 Privado LLC. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
enum ServersControllerComponentIPAD {
|
|
case serverList
|
|
case connection
|
|
case connectionStatus
|
|
case menu
|
|
case notificationsBell
|
|
case trafficCounter
|
|
case location
|
|
case message
|
|
}
|
|
|
|
enum ServersControllerModalComponentIPAD {
|
|
case notifications
|
|
case upgrade
|
|
}
|
|
|
|
protocol ServersControllerOutputIPAD {
|
|
func preferencesClosed()
|
|
func viewDidAppear()
|
|
}
|
|
|
|
final class ServersControllerIPAD: BaseViewController,
|
|
ServersControllerInput,
|
|
ViewModuleControllerType {
|
|
|
|
typealias ComponentCreationClosure = (ServersControllerComponentIPAD) -> UIView
|
|
typealias ModalCreationClosure = (ServersControllerModalComponentIPAD) -> UIViewController
|
|
|
|
// swiftlint:disable nesting
|
|
private enum Constants {
|
|
|
|
enum ImageName {
|
|
static let mapBackground = "ipad.map.background"
|
|
static let logo = "logo.main"
|
|
}
|
|
|
|
enum Geometry {
|
|
|
|
enum Portrait {
|
|
static let messageViewTop: CGFloat = 144
|
|
static let trafficCounterTop: CGFloat = 126
|
|
static let menuViewWidth: CGFloat = 319
|
|
static let connectionBottom: CGFloat = 22
|
|
static let mapImageTop: CGFloat = 81
|
|
static let logoTop: CGFloat = 62
|
|
static let menuTop: CGFloat = 66
|
|
static let bellTop: CGFloat = 61
|
|
static let bellRight: CGFloat = 24
|
|
static let serverListWidthMultiplier: CGFloat = 0.57
|
|
}
|
|
|
|
enum Landscape {
|
|
static let messageViewTop: CGFloat = 102
|
|
static let trafficCounterTop: CGFloat = 108
|
|
static let preferenceViewWidth: CGFloat = 288
|
|
static let preferenceHorizontalOffset: CGFloat = 28
|
|
static let preferenceTopOffset: CGFloat = 51
|
|
static let preferenceBottomOffset: CGFloat = 29
|
|
static let connectionBottom: CGFloat = 22
|
|
static let mapImageTop: CGFloat = -51
|
|
static let logoTop: CGFloat = 49
|
|
static let menuTop: CGFloat = 53
|
|
static let bellTop: CGFloat = 47
|
|
static let bellRight: CGFloat = 31
|
|
static let serverListWidthMultiplier: CGFloat = 0.45
|
|
}
|
|
|
|
static let connectionStatusTop: CGFloat = 12
|
|
static let menuLeading: CGFloat = 29
|
|
static let serverListHeightMultiplier: CGFloat = 0.75
|
|
}
|
|
|
|
enum Color {
|
|
static let backGradientTop = UIColor(rgb: 0x2A2F54)
|
|
static let backGradientBottom = UIColor(rgb: 0x2A2E4B)
|
|
|
|
static let backGradientTopHighlighted = UIColor(rgb: 0x5058C8)
|
|
static let backGradientBottomHighlighted = UIColor(rgb: 0x141850)
|
|
|
|
static let connectionStatusText = Colors.SystemCTA.lightPurple.color
|
|
}
|
|
|
|
enum Font {
|
|
static let connectionStatusText = UIFont.primary(size: 14, weight: .bold)
|
|
}
|
|
|
|
static let mapImageOpacity: CGFloat = 0.05
|
|
}
|
|
// swiftlint:enable nesting
|
|
private let output: ServersControllerOutputIPAD
|
|
|
|
private var backgroundGradientView: GradientView?
|
|
private var connectionStatusLabel: UILabel?
|
|
public var preferenceViewController: UIViewController? {
|
|
didSet {
|
|
self.setupPreferences()
|
|
}
|
|
}
|
|
private var notificationViewController: UIViewController?
|
|
private var preferenceBG: UIView?
|
|
private let componentCreationClosure: ComponentCreationClosure
|
|
private let modalCreationClosure: ModalCreationClosure
|
|
|
|
|
|
// MARK: - Init
|
|
init(output: ServersControllerOutputIPAD,
|
|
componentCreation: @escaping ComponentCreationClosure,
|
|
modalCreation: @escaping ModalCreationClosure) {
|
|
self.output = output
|
|
self.componentCreationClosure = componentCreation
|
|
self.modalCreationClosure = modalCreation
|
|
super.init()
|
|
}
|
|
|
|
required init?(coder: NSCoder) { nil }
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
override var preferredStatusBarStyle: UIStatusBarStyle {
|
|
return .lightContent // .default
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
self.configureUI()
|
|
}
|
|
|
|
override func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
guard let superview = self.view.superview else { return }
|
|
self.output.viewDidAppear()
|
|
}
|
|
|
|
// MARK: - ServersControllerInput
|
|
|
|
func upgradeWarning(show: Bool) { }
|
|
|
|
func changeMessageVisibility(isHidden: Bool) { }
|
|
|
|
func updateBackground(isHighlighted: Bool) {
|
|
self.backgroundGradientView?.stops = isHighlighted
|
|
|
|
? [.init(color: Constants.Color.backGradientTopHighlighted, location: 0),
|
|
.init(color: Constants.Color.backGradientBottomHighlighted, location: 1)]
|
|
|
|
: [.init(color: Constants.Color.backGradientTop, location: 0),
|
|
.init(color: Constants.Color.backGradientBottom, location: 1)]
|
|
}
|
|
|
|
func updateConnectionStatus(title: String?) {
|
|
self.connectionStatusLabel?.text = title
|
|
}
|
|
|
|
func changeMenuState() {
|
|
guard let preferences = preferenceViewController?.view else { return }
|
|
guard let notifications = self.notificationViewController?.view else { return }
|
|
if preferences.isAnimating() { return }
|
|
if notifications.isHidden == false {
|
|
notifications.fade(fadeIn: false, onCompletion: { notifications.isHidden = true })
|
|
}
|
|
if preferences.isHidden {
|
|
preferences.slide(out: false)
|
|
preferenceBG?.fade(fadeIn: true, 0.3)
|
|
} else {
|
|
preferences.slide(
|
|
out: true,
|
|
onCompletion: { [weak self] in
|
|
self?.output.preferencesClosed()
|
|
preferences.isHidden = true
|
|
}
|
|
)
|
|
preferenceBG?.fade(fadeIn: false, 0.3)
|
|
}
|
|
}
|
|
|
|
func changeNotificationsState() {
|
|
guard let notifications = self.notificationViewController?.view else { return }
|
|
if notifications.isHidden {
|
|
notifications.removeFromSuperview()
|
|
self.view.addSubview(notifications)
|
|
self.sharedConstraints.append(contentsOf: [
|
|
notifications.topAnchor.constraint(equalTo: self.view.topAnchor, constant: Constants.Geometry.Landscape.preferenceTopOffset),
|
|
notifications.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constants.Geometry.Landscape.preferenceHorizontalOffset)
|
|
])
|
|
let landscape = DeviceInfoProvider.isLandscapeOrienation && self.traitCollection.horizontalSizeClass == .regular
|
|
self.layoutEmitter.invoke(landscape)
|
|
notifications.fade(fadeIn: true)
|
|
} else {
|
|
notifications.fade(fadeIn: false, onCompletion: { notifications.isHidden = true })
|
|
}
|
|
|
|
}
|
|
|
|
func changeTutorialState(show: Bool) {
|
|
// MARK: - TODO
|
|
}
|
|
|
|
func scrollServers(direction: ServersScrollDirection) {
|
|
|
|
}
|
|
// MARK: - Private
|
|
|
|
// swiftlint:disable function_body_length
|
|
private func configureUI() {
|
|
|
|
let backgroundGradientView = self.initializeBackgroundGradient()
|
|
let mapImageView = self.initializeMapImage()
|
|
let connectionView = self.componentCreationClosure(.connection)
|
|
self.view.addSubview(connectionView)
|
|
|
|
let logoView = self.initializeLogoView()
|
|
let connectionStatusView = self.componentCreationClosure(.connectionStatus)
|
|
self.view.addSubview(connectionStatusView)
|
|
|
|
guard let serverList = self.componentCreationClosure(.serverList) as? (UIView & ServerListPullable) else {
|
|
return
|
|
}
|
|
self.view.addSubview(serverList)
|
|
|
|
let trafficView = self.componentCreationClosure(.trafficCounter)
|
|
self.view.addSubview(trafficView)
|
|
|
|
let messageView = self.componentCreationClosure(.message)
|
|
self.view.addSubview(messageView)
|
|
|
|
let location = self.componentCreationClosure(.location)
|
|
self.view.addSubview(location)
|
|
|
|
let upgradeController = self.modalCreationClosure(.upgrade)
|
|
upgradeController.view.isHidden = true
|
|
self.addChild(upgradeController)
|
|
self.view.addSubview(upgradeController.view)
|
|
|
|
let menuView = self.componentCreationClosure(.menu)
|
|
self.view.addSubview(menuView)
|
|
|
|
let notificationBell = self.componentCreationClosure(.notificationsBell)
|
|
self.view.addSubview(notificationBell)
|
|
|
|
let notification = self.modalCreationClosure(.notifications)
|
|
notification.view.isHidden = true
|
|
self.notificationViewController = notification
|
|
self.addChild(notification)
|
|
self.view.addSubview(notification.view)
|
|
|
|
self.portraitConstraints = [
|
|
|
|
trafficView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: Constants.Geometry.Portrait.trafficCounterTop),
|
|
|
|
messageView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: Constants.Geometry.Portrait.messageViewTop),
|
|
|
|
mapImageView.topAnchor.constraint(equalTo: self.view.topAnchor,
|
|
constant: Constants.Geometry.Portrait.mapImageTop),
|
|
logoView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: Constants.Geometry.Portrait.logoTop),
|
|
menuView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: Constants.Geometry.Portrait.menuTop),
|
|
|
|
serverList.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: Constants.Geometry.Portrait.serverListWidthMultiplier),
|
|
|
|
notificationBell.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constants.Geometry.Portrait.bellRight),
|
|
notificationBell.topAnchor.constraint(equalTo: self.view.topAnchor, constant: Constants.Geometry.Portrait.bellTop)
|
|
]
|
|
|
|
self.landscapeConstraints = [
|
|
|
|
trafficView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: Constants.Geometry.Landscape.trafficCounterTop),
|
|
|
|
messageView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: Constants.Geometry.Landscape.messageViewTop),
|
|
|
|
mapImageView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor,
|
|
constant: Constants.Geometry.Landscape.mapImageTop),
|
|
logoView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: Constants.Geometry.Landscape.logoTop),
|
|
menuView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: Constants.Geometry.Landscape.menuTop),
|
|
|
|
serverList.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: Constants.Geometry.Landscape.serverListWidthMultiplier),
|
|
|
|
notificationBell.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constants.Geometry.Landscape.bellRight),
|
|
notificationBell.topAnchor.constraint(equalTo: self.view.topAnchor, constant: Constants.Geometry.Landscape.bellTop)
|
|
]
|
|
|
|
let serverListHeightConstraint = serverList.heightAnchor.constraint(equalToConstant: serverList.minHeight)
|
|
serverListHeightConstraint.priority = .defaultHigh
|
|
|
|
let serverListHeightMaxConstraint = serverList.heightAnchor.constraint(lessThanOrEqualTo: self.view.heightAnchor,
|
|
multiplier: Constants.Geometry.serverListHeightMultiplier)
|
|
serverListHeightMaxConstraint.priority = .required
|
|
|
|
self.sharedConstraints = [
|
|
messageView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
|
|
messageView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
|
|
|
|
trafficView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
|
|
trafficView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
|
|
|
|
location.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
|
|
location.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
|
|
location.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
|
|
|
|
notification.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: Constants.Geometry.Landscape.preferenceTopOffset),
|
|
notification.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constants.Geometry.Landscape.preferenceHorizontalOffset),
|
|
|
|
connectionView.centerXAnchor.constraint(equalTo: mapImageView.centerXAnchor),
|
|
connectionView.centerYAnchor.constraint(equalTo: mapImageView.centerYAnchor),
|
|
|
|
mapImageView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
|
|
mapImageView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
|
|
|
|
logoView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
|
|
connectionStatusView.topAnchor.constraint(equalTo: logoView.bottomAnchor,
|
|
constant: Constants.Geometry.connectionStatusTop),
|
|
connectionStatusView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
|
|
|
|
menuView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: Constants.Geometry.menuLeading),
|
|
|
|
backgroundGradientView.topAnchor.constraint(equalTo: self.view.topAnchor),
|
|
backgroundGradientView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
|
|
backgroundGradientView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
|
|
backgroundGradientView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
|
|
|
|
serverList.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
|
|
serverListHeightConstraint,
|
|
serverListHeightMaxConstraint,
|
|
serverList.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
|
|
serverList.topAnchor.constraint(greaterThanOrEqualTo: self.view.safeAreaLayoutGuide.topAnchor),
|
|
|
|
upgradeController.view.topAnchor.constraint(equalTo: self.view.topAnchor),
|
|
upgradeController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
|
|
upgradeController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
|
|
upgradeController.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
|
|
]
|
|
serverList.heightConstraint = serverListHeightConstraint
|
|
serverList.isHidden = true
|
|
|
|
let landscape = DeviceInfoProvider.isLandscapeOrienation && self.traitCollection.horizontalSizeClass == .regular
|
|
self.layoutEmitter.invoke(landscape)
|
|
}
|
|
// swiftlint:enable function_body_length
|
|
|
|
private func setupPreferences() {
|
|
guard let preferencesViewController = self.preferenceViewController else {
|
|
return
|
|
}
|
|
|
|
preferencesViewController.view.isHidden = true
|
|
|
|
let bg = self.initializePreferenceBG()
|
|
|
|
self.addChild(preferencesViewController)
|
|
self.view.addSubview(preferencesViewController.view)
|
|
|
|
self.landscapeConstraints.append(contentsOf: [
|
|
|
|
preferencesViewController.view.widthAnchor.constraint(equalToConstant: Constants.Geometry.Landscape.preferenceViewWidth),
|
|
preferencesViewController.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: Constants.Geometry.Landscape.preferenceTopOffset),
|
|
preferencesViewController.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -Constants.Geometry.Landscape.preferenceBottomOffset),
|
|
preferencesViewController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: Constants.Geometry.Landscape.preferenceHorizontalOffset),
|
|
preferencesViewController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constants.Geometry.Landscape.preferenceHorizontalOffset)
|
|
])
|
|
self.portraitConstraints.append(contentsOf: [
|
|
preferencesViewController.view.widthAnchor.constraint(equalToConstant: Constants.Geometry.Portrait.menuViewWidth),
|
|
preferencesViewController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
|
|
preferencesViewController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
|
|
preferencesViewController.view.topAnchor.constraint(equalTo: self.view.topAnchor),
|
|
preferencesViewController.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
|
|
])
|
|
self.sharedConstraints.append(contentsOf: [
|
|
bg.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
|
|
bg.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
|
|
bg.topAnchor.constraint(equalTo: self.view.topAnchor),
|
|
bg.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
|
|
])
|
|
let landscape = DeviceInfoProvider.isLandscapeOrienation && self.traitCollection.horizontalSizeClass == .regular
|
|
self.layoutEmitter.invoke(landscape)
|
|
}
|
|
|
|
private func initializeMapImage() -> UIImageView {
|
|
let image = UIImage(imageLiteralResourceName: Constants.ImageName.mapBackground)
|
|
let imageView = ImageView(image: image)
|
|
imageView.contentMode = .scaleAspectFill
|
|
imageView.alpha = Constants.mapImageOpacity
|
|
self.view.addSubview(imageView)
|
|
return imageView
|
|
}
|
|
|
|
private func initializeBackgroundGradient() -> UIView {
|
|
|
|
let gradientView = GradientView()
|
|
gradientView.translatesAutoresizingMaskIntoConstraints = false
|
|
gradientView.horizontalMode = false
|
|
gradientView.stops = [
|
|
.init(color: Constants.Color.backGradientTop, location: 0),
|
|
.init(color: Constants.Color.backGradientBottom, location: 1)
|
|
]
|
|
self.view.addSubview(gradientView)
|
|
self.backgroundGradientView = gradientView
|
|
return gradientView
|
|
}
|
|
|
|
private func initializeLogoView() -> UIImageView {
|
|
let image = UIImage(imageLiteralResourceName: Constants.ImageName.logo)
|
|
let imageView = UIImageView(image: image)
|
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
|
self.view.addSubview(imageView)
|
|
return imageView
|
|
}
|
|
|
|
private func initializePreferenceBG() -> UIView {
|
|
let bg = UIView()
|
|
bg.translatesAutoresizingMaskIntoConstraints = false
|
|
bg.backgroundColor = Colors.Settings.bgMain.color
|
|
bg.isHidden = true
|
|
self.preferenceBG = bg
|
|
self.view.addSubview(bg)
|
|
|
|
return bg
|
|
}
|
|
}
|