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