Files

230 lines
8.7 KiB
Swift

//
// CountryServerCell.swift
// PrivadoVPN
//
// Created by Juraldinio on 3/15/21.
// Copyright © 2021 Privado LLC. All rights reserved.
//
import Foundation
final class CountryServerCell: View {
// swiftlint:disable nesting
enum Constants {
static let height: CGFloat = 41
enum Images {
static let collapsed = "countryArrowUp"
static let expanded = "countryArrowDown"
}
enum Geometry {
static let flagLeading: CGFloat = 14
static let flagSize = CGSize(width: 20, height: 20)
static let flagBetweenTitle: CGFloat = 23
static let titleHeight: CGFloat = 20
static let titleBetweenArrow: CGFloat = 23
static let arrowSize = CGSize(width: 12, height: 7)
static let arrowTrailing: CGFloat = 26
static let bottomLineHeight: CGFloat = 1.0
}
enum Font {
enum Name {
static let title = PrivadoConstants.Font.semiBold
}
enum Size {
static let title: CGFloat = 14.0
}
}
enum Color {
static let title = NSColor.white
static let bottomLine = NSColor(red: 25, green: 33, blue: 71)
}
}
var strokeLayer = CAShapeLayer()
var backgroundLayer = CAShapeLayer()
var cornerRadius: CGFloat = 6
func setupBackgroundLayer() {
let path = NSBezierPath(roundedRect: CGRect(x: self.bounds.minX + 5, y: self.bounds.minY, width: self.bounds.width - 10, height: self.bounds.height - 5), xRadius: cornerRadius, yRadius: cornerRadius)
self.backgroundLayer.path = path.CGPath
self.backgroundLayer.fillRule = .evenOdd
self.backgroundLayer.fillColor = .clear
}
func setupStrokeLayer() {
self.layer?.addSublayer(self.strokeLayer)
let path = NSBezierPath()
let rect = CGRect(x: self.bounds.minX + 5, y: self.bounds.minY, width: self.bounds.width - 10, height: self.bounds.height - 5)
let topRight = NSPoint(x: rect.maxX, y: rect.maxY)
let topLeft = NSPoint(x: rect.minX, y: rect.maxY)
let bottomLeft = NSPoint(x: rect.minX, y: rect.minY)
let bottomRight = NSPoint(x: rect.maxX, y: rect.minY)
path.move(to: bottomLeft)
path.line(to: NSPoint(x: rect.minX, y: rect.maxY - cornerRadius))
path.appendArc(from: topLeft, to: NSPoint(x: rect.minX + cornerRadius, y: rect.maxY), radius: cornerRadius)
path.line(to: NSPoint(x: rect.maxX - cornerRadius, y: rect.maxY))
path.appendArc(from: topRight, to: NSPoint(x: rect.maxX, y: rect.maxY - cornerRadius), radius: cornerRadius)
path.line(to: bottomRight)
path.line(to: bottomLeft)
self.strokeLayer.path = path.CGPath
self.strokeLayer.lineWidth = 1
self.strokeLayer.strokeColor = .clear
self.strokeLayer.fillRule = .evenOdd
self.strokeLayer.fillColor = NSColor(red: 58, green: 66, blue: 183).cgColor
}
func removeStrokeLayer() {
self.strokeLayer.removeFromSuperlayer()
}
func removeBackgroundLayer() {
self.backgroundLayer.fillColor = .clear
}
var strokeIsSetup = false
override func layout() {
super.layout()
if self.expanded {
self.removeBackgroundLayer()
self.setupStrokeLayer()
} else {
self.removeStrokeLayer()
self.setupBackgroundLayer()
}
}
// swiftlint:enable nesting
private let flagImage = ImageView()
private let titleField = NSTextField(frame: .zero)
private let arrowImage = ImageView()
private let bottomLine = NSView()
private weak var output: ServerCellOutput?
var backgroundColor: NSColor = .clear
var expanded: Bool = false {
didSet {
self.arrowImage.image = NSImage(named: expanded ? Constants.Images.expanded : Constants.Images.collapsed)
}
}
// MARK: - Initializers
init(flag: String, country: String, expanded: Bool = false, isPremium: Bool, premiumBackground: Bool, output: ServerCellOutput) {
super.init(frame: .zero)
self.output = output
self.output?.scrollEmitter.addReaction({ [weak self] isScrolling in
if isScrolling {
self?.backgroundLayer.fillColor = .clear
}
return self.isExist
})
self.backgroundColor = premiumBackground ? NSColor(red: 69, green: 77, blue: 181) : .clear
self.viewBackgroundColor = self.backgroundColor
self.isHoverEnabled = true
self.layer?.addSublayer(self.backgroundLayer)
self.configure(flag: flag, country: country, expanded: expanded)
}
// MARK: - LifeCycle
override func viewDidMoveToSuperview() {
super.viewDidMoveToSuperview()
NSLayoutConstraint.activate([
self.heightAnchor.constraint(equalToConstant: Constants.height)
])
}
override func hitTest(_ point: NSPoint) -> NSView? { nil }
override func updateTrackingAreas() {
super.updateTrackingAreas()
for trackingArea in self.trackingAreas {
self.removeTrackingArea(trackingArea)
}
let options: NSTrackingArea.Options = [.mouseEnteredAndExited, .activeAlways]
let trackingArea = NSTrackingArea(rect: self.bounds, options: options, owner: self, userInfo: nil)
self.addTrackingArea(trackingArea)
}
// MARK: - Lifecycle View
override func addSubviews() {
self.wantsLayer = true
self.addSubview(self.flagImage)
self.addSubview(self.titleField)
self.addSubview(self.arrowImage)
self.addSubview(self.bottomLine)
// titleField
self.titleField.translatesAutoresizingMaskIntoConstraints = false
self.titleField.isEditable = false
self.titleField.isSelectable = false
self.titleField.usesSingleLineMode = true
self.titleField.isBezeled = false
self.titleField.drawsBackground = false
self.titleField.focusRingType = .none
self.titleField.textColor = Constants.Color.title
self.titleField.font = NSFont(name: Constants.Font.Name.title, size: Constants.Font.Size.title)
self.arrowImage.translatesAutoresizingMaskIntoConstraints = false
self.bottomLine.translatesAutoresizingMaskIntoConstraints = false
}
override func addConstraints() {
NSLayoutConstraint.activate([
self.flagImage.widthAnchor.constraint(equalToConstant: Constants.Geometry.flagSize.width),
self.flagImage.heightAnchor.constraint(equalToConstant: Constants.Geometry.flagSize.height),
self.flagImage.centerYAnchor.constraint(equalTo: self.centerYAnchor),
self.flagImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: Constants.Geometry.flagLeading),
//
self.titleField.leadingAnchor.constraint(equalTo: self.flagImage.trailingAnchor, constant: Constants.Geometry.flagBetweenTitle),
self.titleField.centerYAnchor.constraint(equalTo: self.centerYAnchor),
self.titleField.heightAnchor.constraint(equalToConstant: Constants.Geometry.titleHeight),
//
self.arrowImage.widthAnchor.constraint(equalToConstant: Constants.Geometry.arrowSize.width),
self.arrowImage.heightAnchor.constraint(equalToConstant: Constants.Geometry.arrowSize.height),
self.arrowImage.leadingAnchor.constraint(equalTo: self.titleField.trailingAnchor, constant: Constants.Geometry.titleBetweenArrow),
self.arrowImage.centerYAnchor.constraint(equalTo: self.centerYAnchor),
self.arrowImage.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -Constants.Geometry.arrowTrailing),
//
self.bottomLine.leadingAnchor.constraint(equalTo: self.leadingAnchor),
self.bottomLine.trailingAnchor.constraint(equalTo: self.trailingAnchor),
self.bottomLine.bottomAnchor.constraint(equalTo: self.bottomAnchor),
self.bottomLine.heightAnchor.constraint(equalToConstant: Constants.Geometry.bottomLineHeight)
])
}
override func onMouseHover(_ hover: Bool) {
guard !self.expanded else { return }
self.backgroundLayer.fillColor = hover ? NSColor(red: 58, green: 66, blue: 183).cgColor : self.backgroundColor.cgColor
}
// MARK: - Private
private func configure(flag: String, country: String, expanded: Bool) {
self.flagImage.image = NSImage(named: flag)
self.titleField.stringValue = country
self.expanded = expanded
}
}