348 lines
14 KiB
Swift
348 lines
14 KiB
Swift
//
|
|
// ConnectionPanelView.swift
|
|
// PrivadoVPN
|
|
//
|
|
// Created by Juraldinio on 1/31/21.
|
|
// Copyright © 2021 Privado LLC. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import AppKit
|
|
|
|
protocol ConnectionPanelViewInput: AnyObject {
|
|
func hide()
|
|
func progress(status: String, type: ConnectionPanelState)
|
|
func interactionEnabled(_ enable: Bool)
|
|
func location(connected: Bool, ip: String, city: String, secured: Bool, status: String)
|
|
}
|
|
|
|
protocol ConnectionPanelViewOutput: AnyObject {
|
|
func viewIsReady()
|
|
func viewWillDisappear()
|
|
func viewConnectButtonClicked()
|
|
}
|
|
|
|
final class ConnectionPanelView: NSView {
|
|
|
|
private enum Constants {
|
|
|
|
enum Image {
|
|
static let buttonDisconnected = "disconnected"
|
|
static let buttonConnected = "connected"
|
|
static let buttonProgress = "connecting"
|
|
static let connectionProtected = "connection.lock"
|
|
static let connectionUnProtected = "connection.unlock"
|
|
}
|
|
|
|
enum LocalizedString {
|
|
static let connecting = "connection.status.connecting"
|
|
static let disconnecting = "connection.status.disconnecting"
|
|
}
|
|
|
|
enum Color {
|
|
static let background = NSColor(calibratedRed: 21 / 255.0, green: 25 / 255.0, blue: 53 / 255.0, alpha: 1.0)
|
|
static let statusDisconnected = NSColor(calibratedRed: 228 / 255.0, green: 59 / 255.0, blue: 105 / 255.0, alpha: 1.0)
|
|
static let statusProgress = NSColor(calibratedRed: 255 / 255.0, green: 195 / 255.0, blue: 65 / 255.0, alpha: 1.0)
|
|
static let statusConnected = NSColor(calibratedRed: 40 / 255.0, green: 215 / 255.0, blue: 153 / 255.0, alpha: 1.0)
|
|
static let address = NSColor(red: 155, green: 160, blue: 224)
|
|
static let location = NSColor(calibratedRed: 122 / 255.0, green: 134 / 255.0, blue: 190 / 255.0, alpha: 1.0)
|
|
static let protection = NSColor(calibratedRed: 122 / 255.0, green: 134 / 255.0, blue: 190 / 255.0, alpha: 1.0)
|
|
static let loaderDisconnected = NSColor(red: 211, green: 73, blue: 107)
|
|
static let loaderConnected = NSColor(red: 104, green: 212, blue: 158)
|
|
static let loaderConnecting = NSColor(red: 246, green: 197, blue: 91)
|
|
static let loaderBack = NSColor(red: 46, green: 51, blue: 80)
|
|
static let loaderArc = NSColor(red: 126, green: 133, blue: 182)
|
|
}
|
|
|
|
enum Font {
|
|
|
|
// swiftlint:disable nesting
|
|
enum Name {
|
|
static let location = PrivadoConstants.Font.light
|
|
static let address = PrivadoConstants.Font.semiBold
|
|
static let status = PrivadoConstants.Font.bold
|
|
}
|
|
|
|
enum Size {
|
|
static let status: CGFloat = 15.0
|
|
static let address: CGFloat = 13.0
|
|
static let location: CGFloat = 13.0
|
|
}
|
|
// swiftlint:enable nesting
|
|
}
|
|
|
|
enum Geometry {
|
|
static let height: CGFloat = 269
|
|
static let buttonWidth: CGFloat = 110
|
|
static let buttonHeight: CGFloat = 110
|
|
static let buttonleading: CGFloat = 30
|
|
static let buttonBetween: CGFloat = 25
|
|
static let connectionStatusBottom: CGFloat = 7
|
|
static let secureImageWidth: CGFloat = 14
|
|
static let secureImageHeight: CGFloat = 14
|
|
static let secureImageTrailing: CGFloat = 5.6
|
|
static let connectionAddress: CGFloat = 10
|
|
|
|
}
|
|
}
|
|
|
|
private let output: ConnectionPanelViewOutput
|
|
private var connectionStatus: NSTextField?
|
|
private var connectionSecureStatus: ImageView?
|
|
private var connectionIpAddress: NSTextField?
|
|
private var connectionLocation: NSTextField?
|
|
private var connectionLoader: PrivadoLoader?
|
|
private var connectionButton: ConnectionButton?
|
|
|
|
private var status = ""
|
|
private var connectStatus: Bool?
|
|
private var inprogress = false
|
|
|
|
|
|
// MARK: - Init
|
|
|
|
init(output: ConnectionPanelViewOutput) {
|
|
|
|
self.output = output
|
|
super.init(frame: .zero)
|
|
|
|
self.isHidden = true
|
|
self.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
self.createUI()
|
|
self.activateLayout()
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
// MARK: - Lyfecycle
|
|
|
|
override func viewDidMoveToSuperview() {
|
|
super.viewDidMoveToSuperview()
|
|
|
|
if self.superview.isExist {
|
|
self.output.viewIsReady()
|
|
}
|
|
}
|
|
|
|
override func removeFromSuperview() {
|
|
self.output.viewWillDisappear()
|
|
super.removeFromSuperview()
|
|
}
|
|
|
|
override func updateLayer() {
|
|
super.updateLayer()
|
|
self.connectionLoader?.animation(clockwise: false)
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private func createUI() {
|
|
|
|
let button = ConnectionButton(frame: .zero)
|
|
button.cornerRadius = 19
|
|
button.textFont = NSFont(name: PrivadoConstants.Font.bold, size: 14)
|
|
self.addSubview(button)
|
|
button.target = self
|
|
button.action = #selector(self.handleConnectionClick)
|
|
self.connectionButton = button
|
|
self.addSubview(button)
|
|
|
|
let statusFont = NSFont(name: PrivadoConstants.Font.semiBold, size: 14)
|
|
let status = self.initializeTextField(color: Constants.Color.statusDisconnected, font: statusFont, alignment: .left)
|
|
status.isHidden = true
|
|
status.textColor = .white
|
|
self.connectionStatus = status
|
|
self.addSubview(status)
|
|
|
|
let secureStatus = self.initializeImageView(resource: Constants.Image.connectionUnProtected, color: Constants.Color.protection)
|
|
self.connectionSecureStatus = secureStatus
|
|
self.addSubview(secureStatus)
|
|
|
|
let addressFont = NSFont(name: PrivadoConstants.Font.medium, size: 15)
|
|
let ipAddress = self.initializeTextField(color: Constants.Color.address, font: addressFont, alignment: .center)
|
|
self.connectionIpAddress = ipAddress
|
|
self.addSubview(ipAddress)
|
|
|
|
let locationFont = NSFont(name: PrivadoConstants.Font.semiBold, size: 16)
|
|
let location = self.initializeTextField(color: Constants.Color.address, font: locationFont, alignment: .center)
|
|
self.connectionLocation = location
|
|
self.addSubview(location)
|
|
|
|
let loader = PrivadoLoader(frame: .zero, isButton: true)
|
|
loader.translatesAutoresizingMaskIntoConstraints = false
|
|
loader.isEnabled = true
|
|
loader.target = self
|
|
loader.action = #selector(self.handleConnectionClick)
|
|
loader.backCircleColor = .clear
|
|
loader.arcColor = Constants.Color.loaderArc
|
|
// loader.keyholeColor = Constants.Color.loaderArc
|
|
loader.keyholeColor = NSColor(red: 229, green: 93, blue: 129)
|
|
loader.arcWidth = 4.5
|
|
loader.hasShadow = false
|
|
self.connectionLoader = loader
|
|
self.addSubview(loader)
|
|
}
|
|
|
|
private func activateLayout() {
|
|
|
|
guard let connectionLoader = self.connectionLoader
|
|
, let connectionStatus = self.connectionStatus
|
|
, let connectionSecureStatus = self.connectionSecureStatus
|
|
, let connectionIpAddress = self.connectionIpAddress
|
|
, let connectionLocation = self.connectionLocation
|
|
, let connectionButton = self.connectionButton else {
|
|
return
|
|
}
|
|
|
|
NSLayoutConstraint.activate([
|
|
self.heightAnchor.constraint(equalToConstant: Constants.Geometry.height),
|
|
//
|
|
connectionLoader.widthAnchor.constraint(equalToConstant: Constants.Geometry.buttonWidth),
|
|
connectionLoader.heightAnchor.constraint(equalToConstant: Constants.Geometry.buttonHeight),
|
|
connectionLoader.centerXAnchor.constraint(equalTo: self.centerXAnchor),
|
|
connectionLoader.topAnchor.constraint(equalTo: connectionIpAddress.bottomAnchor, constant: 20),
|
|
//
|
|
connectionButton.widthAnchor.constraint(equalToConstant: 200),
|
|
connectionButton.heightAnchor.constraint(equalToConstant: 40),
|
|
connectionButton.topAnchor.constraint(equalTo: connectionLoader.bottomAnchor, constant: 20),
|
|
connectionButton.centerXAnchor.constraint(equalTo: self.centerXAnchor),
|
|
//
|
|
connectionStatus.topAnchor.constraint(equalTo: self.topAnchor),
|
|
connectionStatus.centerXAnchor.constraint(equalTo: self.centerXAnchor),
|
|
//
|
|
connectionLocation.topAnchor.constraint(equalTo: self.topAnchor),
|
|
connectionLocation.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20),
|
|
connectionLocation.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20),
|
|
//
|
|
connectionIpAddress.topAnchor.constraint(equalTo: connectionLocation.bottomAnchor, constant: 10),
|
|
connectionIpAddress.centerXAnchor.constraint(equalTo: self.centerXAnchor),
|
|
//
|
|
connectionSecureStatus.trailingAnchor.constraint(equalTo: connectionIpAddress.leadingAnchor, constant: -5),
|
|
connectionSecureStatus.topAnchor.constraint(equalTo: connectionLocation.bottomAnchor, constant: 10),
|
|
connectionSecureStatus.widthAnchor.constraint(equalToConstant: Constants.Geometry.secureImageWidth),
|
|
connectionSecureStatus.heightAnchor.constraint(equalToConstant: Constants.Geometry.secureImageHeight),
|
|
])
|
|
}
|
|
|
|
// MARK: - Handlers
|
|
|
|
@objc
|
|
private func handleConnectionClick() {
|
|
self.output.viewConnectButtonClicked()
|
|
self.connectionLoader?.startAnimation(clockwise: false)
|
|
}
|
|
|
|
override func layoutSubtreeIfNeeded() {
|
|
super.layoutSubtreeIfNeeded()
|
|
self.connectionLoader?.startAnimation(clockwise: false)
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - ConnectionPanelViewInput
|
|
|
|
extension ConnectionPanelView: ConnectionPanelViewInput {
|
|
|
|
func interactionEnabled(_ enable: Bool) {
|
|
self.connectionLoader?.isEnabled = enable
|
|
}
|
|
|
|
func hide() {
|
|
self.isHidden = true
|
|
}
|
|
|
|
func progress(status: String, type: ConnectionPanelState) {
|
|
|
|
self.inprogress = true
|
|
//
|
|
if status != self.status {
|
|
self.status = status
|
|
|
|
switch type {
|
|
case .connecting:
|
|
self.connectionStatus?.isHidden = false
|
|
self.connectionStatus?.stringValue = status
|
|
self.connectionButton?.buttonIsEnabled = false
|
|
self.connectionLoader?.connecting = true
|
|
self.connectionLoader?.startConnectionAnimation {
|
|
if self.inprogress {
|
|
self.connectionLoader?.connectingAnimation()
|
|
}
|
|
}
|
|
case .disconnecting:
|
|
self.connectionStatus?.isHidden = false
|
|
self.connectionStatus?.stringValue = status
|
|
self.connectionButton?.buttonIsEnabled = false
|
|
self.connectionLoader?.connecting = false
|
|
self.connectionLoader?.disconnectAnimation()
|
|
case .reconnecting:
|
|
self.connectionStatus?.isHidden = false
|
|
self.connectionStatus?.stringValue = status
|
|
self.connectionButton?.buttonIsEnabled = false
|
|
self.connectStatus = false
|
|
self.connectionLoader?.connecting = true
|
|
self.connectionLoader?.startConnectionAnimation {
|
|
if self.inprogress {
|
|
self.connectionLoader?.connectingAnimation()
|
|
}
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
//
|
|
self.connectionIpAddress?.stringValue = ""
|
|
self.connectionLocation?.stringValue = ""
|
|
self.connectionSecureStatus?.image = nil
|
|
//
|
|
self.isHidden = false
|
|
|
|
}
|
|
|
|
// swiftlint:disable function_parameter_count
|
|
func location(connected: Bool, ip: String, city: String, secured: Bool, status: String) {
|
|
|
|
|
|
if let connectionButton = self.connectionButton {
|
|
connectionButton.buttonIsEnabled = true
|
|
connectionButton.state = connected ? .disconnect : .connect
|
|
}
|
|
|
|
if let connectionLoader = self.connectionLoader {
|
|
if connected != self.connectStatus {
|
|
self.connectStatus = connected
|
|
connectionLoader.isDisconnected = !connected
|
|
if connected {
|
|
self.inprogress = false
|
|
connectionLoader.connecting = false
|
|
connectionLoader.connectedAnimation()
|
|
}
|
|
}
|
|
}
|
|
|
|
if let connectionStatus = self.connectionStatus {
|
|
connectionStatus.isHidden = !connected
|
|
connectionStatus.stringValue = status
|
|
}
|
|
|
|
if let connectionIpAddress = self.connectionIpAddress {
|
|
connectionIpAddress.stringValue = ip
|
|
}
|
|
|
|
if let connectionLocation = self.connectionLocation {
|
|
connectionLocation.stringValue = city
|
|
connectionLocation.isHidden = connected
|
|
}
|
|
|
|
if let connectionSecureStatus = self.connectionSecureStatus {
|
|
connectionSecureStatus.image = NSImage(named: secured ? Constants.Image.connectionProtected : Constants.Image.connectionUnProtected)
|
|
}
|
|
|
|
self.isHidden = false
|
|
}
|
|
// swiftlint:enable function_parameter_count
|
|
}
|