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