Files

395 lines
18 KiB
Swift

//
// AccountPreferencesViewController.swift
// Cyberlock
//
// Created by Jura on 9/23/19.
// Copyright © 2019 Omicronmedia. All rights reserved.
//
import Foundation
import AppKit
enum AccountPreferencesHyperLinkType {
case termOfService
case privacyPolicy
}
protocol AccountPreferencesViewInput: AnyObject {
func current(record: AccountPreferenceRecord?, buttonVisible: Bool?, email: String?)
}
protocol AccountPreferencesViewOutput: AnyObject {
func isReady()
func signout()
func clickedHyper(link: AccountPreferencesHyperLinkType)
func viewButtonClicked()
func requireCopyUsername()
func logout()
}
final class AccountPreferencesViewController: NSViewController, PreferencePane, AccountPreferencesViewInput, HyperlinkTextFieldDelegate {
// swiftlint:disable nesting
struct Constants {
enum Image {
static let copy = "preference_copy-1"
}
enum Localized {
static let signout = "preference.authentication.signout"
static let upgrade = "preference.authentication.plan.upgrade"
static let termOfService = "preference.authentication.termsOfService"
static let privacyPolicy = "preference.authentication.privacyPolicy"
}
enum Geometry {
static let leading: CGFloat = 20
static let copyButtonTrailing: CGFloat = 5.0
static let fontSize: CGFloat = 14.0
static let accountTopSpace: CGFloat = 20
static let accountLeadingSpace: CGFloat = 30
static let usernameTopSpace: CGFloat = 20
static let innerSpace: CGFloat = 6
static let subscriptionTopSpace: CGFloat = 20
static let updateTopSpace: CGFloat = 40
static let updateLeadingSpace: CGFloat = 165
static let updateWidth: CGFloat = 200
static let updateHeight: CGFloat = 35
static let hyperlinkTop: CGFloat = 40
}
enum Color {
static let hyperlinkColor = NSColor(red: 90, green: 98, blue: 140)
static let valueColor = NSColor(red: 124, green: 134, blue: 186)
static let backgroundNormalColor = NSColor(red: 42, green: 216, blue: 153)
static let backgroundHighlightColor = NSColor(red: 23, green: 176, blue: 121)
}
enum Text {
static let accountDetails = "Account Details"
static let username = "Username"
static let subscription = "Subscription"
}
enum Font {
enum Size {
static let account: CGFloat = 16
static let username: CGFloat = 14
static let value: CGFloat = 12
static let updateButtonTitle: CGFloat = 12
}
}
}
// swiftlint:enable nesting
private let usernameCopyButton: NSButton
private let usernameValueField: NSTextField
private let usernameField: NSTextField
private let subscription: NSTextField
private let planField: NSTextField
private let currentPlantField: NSTextField
private let expirationDateField: NSTextField
private let expireField: NSTextField
private let accountDetailesField: NSTextField
private var updateButton: FlatButton
private var accountSeparator: View
private var subscriptionSeparator: View
private var logoutButton: StrokeButton
private var termsOfService: HyperlinkTextField
private var privacyPolicy: HyperlinkTextField
private let output: AccountPreferencesViewOutput
// MARK: - Init
init(output: AccountPreferencesViewOutput) {
self.output = output
let image = NSImage(named: Constants.Image.copy)
self.usernameCopyButton = Self.createButton(image: image)
self.usernameValueField = Self.createTextField()
self.planField = Self.createTextField()
self.expireField = Self.createTextField()
self.accountDetailesField = Self.createTextField()
self.usernameField = Self.createTextField()
self.subscription = Self.createTextField()
self.currentPlantField = Self.createTextField()
self.expirationDateField = Self.createTextField()
self.updateButton = FlatButton(frame: .zero)
self.privacyPolicy = HyperlinkTextField(frame: .zero, font: nil)
self.termsOfService = HyperlinkTextField(frame: .zero, font: nil)
self.accountSeparator = View(frame: .zero)
self.subscriptionSeparator = View(frame: .zero)
self.logoutButton = StrokeButton(frame: .zero)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Lifecycle
override func loadView() {
let view = NSView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
self.configureUI()
self.layoutUI()
self.output.isReady()
}
override func viewDidAppear() {
super.viewDidAppear()
NSLayoutConstraint.activate([
view.widthAnchor.constraint(equalToConstant: 500),
view.heightAnchor.constraint(equalToConstant: 524)
])
}
// MARK: - Private
private func configureUI() {
self.logoutButton.translatesAutoresizingMaskIntoConstraints = false
self.logoutButton.cornerRadius = 17
self.logoutButton.strokeNormalColor = .white
self.logoutButton.hoverBackgroundColor = NSColor(red: 138, green: 132, blue: 190)
self.logoutButton.backgroundPressedColor = NSColor(red: 189, green: 191, blue: 223)
self.logoutButton.title = NSLocalizedString(Constants.Localized.signout, comment: "")
self.logoutButton.textFont = NSFont(name: PrivadoConstants.Font.bold, size: 10)
self.logoutButton.titleColor = .white
self.logoutButton.target = self
self.logoutButton.action = #selector(self.logoutButtonTapped)
self.updateButton.momentary = true
self.updateButton.cornerRadius = 18
self.updateButton.backgroundNormalColor = Constants.Color.backgroundNormalColor
self.updateButton.backgroundHighlightColor = Constants.Color.backgroundHighlightColor
self.updateButton.titleNormalColor = .white
self.updateButton.font = NSFont(name: PrivadoConstants.Font.bold, size: Constants.Font.Size.updateButtonTitle)
self.updateButton.title = NSLocalizedString(Constants.Localized.upgrade, comment: "")
self.updateButton.target = self
self.updateButton.action = #selector(self.handleUpgrade)
self.accountDetailesField.stringValue = Constants.Text.accountDetails
self.accountDetailesField.textColor = .white
self.accountDetailesField.font = NSFont(name: PrivadoConstants.Font.semiBold, size: Constants.Font.Size.account)
if #available(macOS 10.14, *) {
self.usernameCopyButton.contentTintColor = Constants.Color.valueColor
}
self.usernameCopyButton.target = self
self.usernameCopyButton.action = #selector(self.handleCopyUsername)
self.usernameField.stringValue = Constants.Text.username
self.usernameField.textColor = Constants.Color.valueColor
self.usernameField.font = NSFont(name: PrivadoConstants.Font.regular, size: Constants.Font.Size.username)
self.usernameValueField.textColor = .white
self.usernameValueField.font = NSFont(name: PrivadoConstants.Font.medium, size: Constants.Font.Size.value)
self.accountSeparator.viewBackgroundColor = NSColor(red: 130, green: 136, blue: 216)
self.accountSeparator.translatesAutoresizingMaskIntoConstraints = false
self.subscription.stringValue = Constants.Text.subscription
self.subscription.textColor = .white
self.subscription.font = NSFont(name: PrivadoConstants.Font.semiBold, size: 16)
self.currentPlantField.stringValue = "Current plan:"
self.currentPlantField.textColor = Constants.Color.valueColor
self.currentPlantField.font = NSFont(name: PrivadoConstants.Font.regular, size: 14)
self.planField.textColor = .white
self.planField.font = NSFont(name: PrivadoConstants.Font.medium, size: 12)
self.expirationDateField.stringValue = "Expiration Date:"
self.expirationDateField.textColor = Constants.Color.valueColor
self.expirationDateField.font = NSFont(name: PrivadoConstants.Font.regular, size: 14)
self.expireField.textColor = .white
self.expireField.font = NSFont(name: PrivadoConstants.Font.medium, size: 12)
self.subscriptionSeparator.viewBackgroundColor = NSColor(red: 130, green: 136, blue: 216)
self.subscriptionSeparator.translatesAutoresizingMaskIntoConstraints = false
let termsOfService = self.initializeHyperlinkText(NSLocalizedString(Constants.Localized.termOfService, comment: ""), link: nil, font: NSFont(name: PrivadoConstants.Font.semiBold, size: 12))
self.termsOfService = termsOfService
self.termsOfService.hoveredColor = .white
let privacyPolicy = self.initializeHyperlinkText(NSLocalizedString(Constants.Localized.privacyPolicy, comment: ""), link: nil, font: NSFont(name: PrivadoConstants.Font.semiBold, size: 12))
self.privacyPolicy = privacyPolicy
self.privacyPolicy.hoveredColor = .white
[self.updateButton, self.accountDetailesField,
self.usernameCopyButton, self.usernameField, self.usernameValueField, self.accountSeparator,
self.expirationDateField, self.currentPlantField,
self.subscription, self.planField, self.subscriptionSeparator,
self.expireField, self.privacyPolicy, self.logoutButton]
.forEach(self.view.addSubview)
}
private func layoutUI() {
NSLayoutConstraint.activate([
self.accountDetailesField.topAnchor.constraint(equalTo: view.topAnchor, constant: Constants.Geometry.accountTopSpace),
self.accountDetailesField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
self.usernameField.topAnchor.constraint(equalTo: self.accountDetailesField.bottomAnchor, constant: Constants.Geometry.usernameTopSpace),
self.usernameField.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 30),
self.usernameValueField.topAnchor.constraint(equalTo: self.usernameField.bottomAnchor, constant: 15),
self.usernameValueField.leadingAnchor.constraint(equalTo: self.usernameField.leadingAnchor),
self.usernameCopyButton.leadingAnchor.constraint(equalTo: self.usernameValueField.trailingAnchor, constant: 13),
self.usernameCopyButton.centerYAnchor.constraint(equalTo: self.usernameValueField.centerYAnchor),
self.usernameCopyButton.widthAnchor.constraint(equalToConstant: 40),
self.usernameCopyButton.heightAnchor.constraint(equalToConstant: 40),
self.logoutButton.widthAnchor.constraint(equalToConstant: 80),
self.logoutButton.heightAnchor.constraint(equalToConstant: 30),
self.logoutButton.topAnchor.constraint(equalTo: self.accountDetailesField.bottomAnchor, constant: 15),
self.logoutButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20),
self.accountSeparator.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20),
self.accountSeparator.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20),
self.accountSeparator.topAnchor.constraint(equalTo: self.usernameValueField.bottomAnchor, constant: 15),
self.accountSeparator.heightAnchor.constraint(equalToConstant: 1),
self.subscription.topAnchor.constraint(equalTo: self.accountSeparator.bottomAnchor, constant: 15),
self.subscription.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20),
self.currentPlantField.topAnchor.constraint(equalTo: self.subscription.bottomAnchor, constant: 15),
self.currentPlantField.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 30),
self.planField.topAnchor.constraint(equalTo: self.subscription.bottomAnchor, constant: 15),
self.planField.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -50),
self.expirationDateField.topAnchor.constraint(equalTo: self.currentPlantField.bottomAnchor, constant: 15),
self.expirationDateField.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 30),
self.expireField.topAnchor.constraint(equalTo: self.currentPlantField.bottomAnchor, constant: 15),
self.expireField.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -50),
self.subscriptionSeparator.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20),
self.subscriptionSeparator.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20),
self.subscriptionSeparator.topAnchor.constraint(equalTo: self.expireField.bottomAnchor, constant: 15),
self.subscriptionSeparator.heightAnchor.constraint(equalToConstant: 1),
self.updateButton.topAnchor.constraint(equalTo: self.subscriptionSeparator.bottomAnchor, constant: 30),
self.updateButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
self.updateButton.widthAnchor.constraint(equalToConstant: Constants.Geometry.updateWidth),
self.updateButton.heightAnchor.constraint(equalToConstant: Constants.Geometry.updateHeight),
self.termsOfService.topAnchor.constraint(equalTo: self.updateButton.bottomAnchor, constant: Constants.Geometry.hyperlinkTop),
self.termsOfService.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 140),
self.privacyPolicy.topAnchor.constraint(equalTo: self.updateButton.bottomAnchor, constant: Constants.Geometry.hyperlinkTop),
self.privacyPolicy.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -140)
])
}
@objc
private func handleUpgrade() {
self.output.viewButtonClicked()
}
@objc
private func handleCopyUsername() {
self.output.requireCopyUsername()
}
@objc
private func logoutButtonTapped() {
self.output.logout()
}
private static func createButton(image: NSImage?) -> NSButton {
let button = NSButton(frame: .zero)
button.translatesAutoresizingMaskIntoConstraints = false
button.bezelStyle = .texturedSquare
button.isBordered = false
button.wantsLayer = true
button.viewBackgroundColor = .clear
button.image = image
button.imageScaling = .scaleProportionallyDown
return button
}
private static func createTextField() -> NSTextField {
let textField = NSTextField(frame: .zero)
textField.isBezeled = false
textField.drawsBackground = false
textField.isEditable = false
textField.isSelectable = false
textField.translatesAutoresizingMaskIntoConstraints = false
textField.focusRingType = .none
textField.alignment = .center
if let font = textField.font {
textField.font = NSFont(name: font.fontName, size: Constants.Geometry.fontSize)
}
textField.stringValue = ""
return textField
}
private func initializeHyperlinkText(_ text: String, link: URL?, font: NSFont?) -> HyperlinkTextField {
let hyperlink = HyperlinkTextField(frame: .zero, font: font)
hyperlink.hyperlinkDelegate = self
hyperlink.show(hyper: text, underline: true, with: link, using: Constants.Color.hyperlinkColor)
self.view.addSubview(hyperlink)
return hyperlink
}
// MARK: - HyperlinkTextFieldDelegate
func hyperlinkClicked(target: HyperlinkTextField) {
if target == self.termsOfService {
self.output.clickedHyper(link: .termOfService)
} else {
self.output.clickedHyper(link: .privacyPolicy)
}
}
// MARK: - PreferencePane
let preferencePaneIdentifier = PreferenceWindowIdentifiers.account
lazy var preferencePaneTitle = PreferenceWindowIdentifiers.title(for: self.preferencePaneIdentifier)
// MARK: - AccountPreferencesViewInput
func current(record: AccountPreferenceRecord?, buttonVisible: Bool?, email: String?) {
guard let record = record, let updateVisible = buttonVisible, let email = email else {
self.usernameValueField.stringValue = ""
self.planField.stringValue = ""
self.expireField.stringValue = ""
return
}
self.updateButton.isHidden = !updateVisible
self.usernameValueField.stringValue = email
self.usernameValueField.isHidden = false
self.planField.stringValue = record.plan
self.planField.isHidden = false
self.expireField.stringValue = record.expire
self.expireField.isHidden = false
}
}