230 lines
8.7 KiB
Swift
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
|
|
}
|
|
}
|