Files
CmdBar/src/AboutViewController.swift

369 lines
15 KiB
Swift

//
// AboutLicenseView.swift
// CmdBar
//
// Created by Igor Kolokolnikov on 8/16/23.
//
import AppKit
// MARK: - Constants
fileprivate enum AboutLinks {
static let privacyLink = "https://cmdbar.app/#privacy-policy"
static let purchaseLink = "https://cmdbar.app/purchase"
}
enum Strings {
static let copyright = "Copyright © 2024 GarikMI. All rights reserved."
static let evaluationTitle = "License - Evaluation"
static let evaluationMessage = "You are currently using evaluation license. CmdBar will quit after 20 minutes. If you already own a license, enter it below or purchase a license."
static let activate = "Activate"
static let proTitle = "License - Activated"
static let proMessage = "Thank you for purchasing CmdBar! Enjoy!"
static let deactivate = "Deactivate"
static let activating = "Activating..."
}
// MARK: - Controller
class AboutViewController: NSViewController, NSTextFieldDelegate {
// MARK: - Views
private var appIconImage: NSImageView = {
//let image = NSImageView(image: NSApp.applicationIconImage)
let image = NSImageView()
image.image = NSWorkspace.shared.icon(forFile: Bundle.main.bundlePath)
image.imageScaling = .scaleAxesIndependently
image.translatesAutoresizingMaskIntoConstraints = false
return image
}()
private var appNameLabel: NSTextField = {
let textField = NSTextField()
textField.stringValue = (Bundle.main.infoDictionary?["CFBundleName"] as? String) ?? "NOT FOUND"
textField.isEditable = false
textField.isBezeled = false
textField.drawsBackground = false
textField.alignment = .left
textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .body).pointSize, weight: .bold)
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
private var versionLabel: NSTextField = {
let textField = NSTextField()
textField.stringValue = "Version \((Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "-.--")"
textField.isEditable = false
textField.isBezeled = false
textField.drawsBackground = false
textField.alignment = .left
textField.textColor = NSColor.systemGray
textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .subheadline).pointSize, weight: .regular)
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
private var copyrightLabel: NSTextField = {
let textField = NSTextField()
textField.stringValue = Strings.copyright
textField.maximumNumberOfLines = 4
textField.cell?.truncatesLastVisibleLine = true
textField.isEditable = false
textField.isBezeled = false
textField.drawsBackground = false
textField.alignment = .left
textField.textColor = NSColor.systemGray
textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .subheadline).pointSize, weight: .regular)
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
private var licenseLabel: NSTextField = {
let textField = NSTextField()
textField.stringValue = Strings.evaluationTitle
textField.isEditable = false
textField.isBezeled = false
textField.drawsBackground = false
textField.alignment = .left
textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .title2).pointSize, weight: .bold)
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
private var licenseInfoLabel: NSTextField = {
let textField = NSTextField()
textField.stringValue = Strings.evaluationMessage
textField.cell?.truncatesLastVisibleLine = true
textField.maximumNumberOfLines = 6
textField.isEditable = false
textField.isBezeled = false
textField.drawsBackground = false
textField.alignment = .left
textField.textColor = NSColor.systemGray
textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .subheadline).pointSize, weight: .regular)
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
private var emailInput: EditableNSTextField = {
let textField = EditableNSTextField()
textField.placeholderString = "Email"
textField.bezelStyle = .roundedBezel
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
private var licenseKeyInput: EditableNSTextField = {
let textField = EditableNSTextField()
textField.placeholderString = "License Key"
textField.bezelStyle = .roundedBezel
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
private var privacyButton: NSButton = {
let button = NSButton()
button.title = "Privacy Policy"
button.sizeToFit()
button.bezelStyle = .rounded
button.action = #selector(privacy)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
private var closeButton: NSButton = {
let button = NSButton()
button.title = "Close"
button.sizeToFit()
button.bezelStyle = .rounded
button.action = #selector(close)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
private var purchaseButton: NSButton = {
let button = NSButton()
button.title = "Purchase"
button.sizeToFit()
button.bezelStyle = .rounded
button.action = #selector(purchase)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
private var activateButton: NSButton = {
let button = NSButton()
button.isEnabled = false
button.title = "Activate"
button.keyEquivalent = "\r"
button.sizeToFit()
button.bezelStyle = .rounded
button.action = #selector(activate)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
private var dividerBox: NSBox = {
let box = NSBox()
box.boxType = .separator
box.translatesAutoresizingMaskIntoConstraints = false
return box
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Input delegates
emailInput.delegate = self
licenseKeyInput.delegate = self
// Program info
view.addSubview(appIconImage)
view.addSubview(appNameLabel)
view.addSubview(versionLabel)
view.addSubview(copyrightLabel)
view.addSubview(privacyButton)
// Divider
view.addSubview(dividerBox)
// License
view.addSubview(licenseLabel)
view.addSubview(licenseInfoLabel)
view.addSubview(emailInput)
view.addSubview(licenseKeyInput)
// Action buttons
view.addSubview(closeButton)
view.addSubview(purchaseButton)
view.addSubview(activateButton)
setupConstraints()
}
override func viewDidAppear() {
super.viewDidAppear()
self.view.window?.center()
checkLicense()
}
override func loadView() {
self.view = NSView()
}
// MARK: - Constraints
private func setupConstraints() {
// View.
NSLayoutConstraint.activate([
view.widthAnchor.constraint(equalToConstant: 600),
view.heightAnchor.constraint(lessThanOrEqualToConstant: 500),
])
// App image.
NSLayoutConstraint.activate([
appIconImage.widthAnchor.constraint(equalToConstant: 60),
appIconImage.heightAnchor.constraint(equalTo: appIconImage.widthAnchor, multiplier: 1),
appIconImage.topAnchor.constraint(equalTo: view.topAnchor, constant: ViewConstants.spacing20),
appIconImage.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: ViewConstants.spacing20),
])
// Title
NSLayoutConstraint.activate([
appNameLabel.topAnchor.constraint(equalTo: appIconImage.topAnchor),
appNameLabel.leadingAnchor.constraint(equalTo: appIconImage.trailingAnchor, constant: ViewConstants.spacing10),
appNameLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20),
versionLabel.topAnchor.constraint(equalTo: appNameLabel.bottomAnchor, constant: ViewConstants.spacing2),
versionLabel.leadingAnchor.constraint(equalTo: appNameLabel.leadingAnchor),
versionLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20),
copyrightLabel.topAnchor.constraint(equalTo: versionLabel.bottomAnchor, constant: ViewConstants.spacing2),
copyrightLabel.leadingAnchor.constraint(equalTo: appNameLabel.leadingAnchor),
copyrightLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20),
privacyButton.topAnchor.constraint(equalTo: copyrightLabel.bottomAnchor, constant: ViewConstants.spacing10),
privacyButton.leadingAnchor.constraint(equalTo: appNameLabel.leadingAnchor),
])
NSLayoutConstraint.activate([
dividerBox.topAnchor.constraint(equalTo: privacyButton.bottomAnchor, constant: ViewConstants.spacing10),
dividerBox.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: ViewConstants.spacing20),
dividerBox.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20),
])
// License labels.
NSLayoutConstraint.activate([
licenseLabel.topAnchor.constraint(equalTo: dividerBox.bottomAnchor, constant: ViewConstants.spacing10),
licenseLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: ViewConstants.spacing20),
licenseLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20),
licenseInfoLabel.topAnchor.constraint(equalTo: licenseLabel.bottomAnchor, constant: ViewConstants.spacing2),
licenseInfoLabel.leadingAnchor.constraint(equalTo: licenseLabel.leadingAnchor),
licenseInfoLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20),
])
// License inputs.
NSLayoutConstraint.activate([
emailInput.topAnchor.constraint(equalTo: licenseInfoLabel.bottomAnchor, constant: ViewConstants.spacing10),
emailInput.leadingAnchor.constraint(equalTo: licenseInfoLabel.leadingAnchor),
emailInput.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20),
licenseKeyInput.topAnchor.constraint(equalTo: emailInput.bottomAnchor, constant: ViewConstants.spacing10),
licenseKeyInput.leadingAnchor.constraint(equalTo: licenseInfoLabel.leadingAnchor),
licenseKeyInput.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20),
])
// License buttons.
NSLayoutConstraint.activate([
activateButton.topAnchor.constraint(equalTo: licenseKeyInput.bottomAnchor, constant: ViewConstants.spacing20),
activateButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20),
activateButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -ViewConstants.spacing20),
purchaseButton.trailingAnchor.constraint(equalTo: activateButton.leadingAnchor, constant: -ViewConstants.spacing10),
purchaseButton.lastBaselineAnchor.constraint(equalTo: activateButton.lastBaselineAnchor),
closeButton.trailingAnchor.constraint(equalTo: purchaseButton.leadingAnchor, constant: -ViewConstants.spacing10),
closeButton.lastBaselineAnchor.constraint(equalTo: purchaseButton.lastBaselineAnchor),
])
}
// MARK: - Button Functions
@objc private func privacy() {
NSWorkspace.shared.open(URL(string: AboutLinks.privacyLink)!)
}
@objc private func close() {
view.window?.orderOut(nil)
}
@objc private func purchase() {
guard let url = URL(string: AboutLinks.purchaseLink) else { return }
NSWorkspace.shared.open(url)
}
@objc private func activate() {
let email = emailInput.stringValue
let key = licenseKeyInput.stringValue
if email.isEmpty || key.isEmpty { return }
activateButton.isEnabled = false
activateButton.title = Strings.activating
// TODO: Notify the user if registration failed.
LicenseManager.standard.activateLicense(email: email, key: key) { [weak self] _ in
DispatchQueue.main.async { self?.checkLicense() }
}
}
@objc private func deactivate() {
LicenseManager.standard.deactivateLicense()
checkLicense()
}
private func checkLicense() {
if LicenseManager.standard.isRegistered() {
activateButton.isEnabled = true
activateButton.title = Strings.deactivate
activateButton.action = #selector(deactivate)
licenseLabel.stringValue = Strings.proTitle
licenseInfoLabel.stringValue = Strings.proMessage
emailInput.isEnabled = false
licenseKeyInput.isEnabled = false
let license = LicenseManager.standard.getLicense()
emailInput.stringValue = license.email
licenseKeyInput.stringValue =
"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXX" +
license.key.dropFirst(license.key.count - 5)
} else {
activateButton.isEnabled = false
activateButton.title = Strings.activate
activateButton.action = #selector(activate)
licenseLabel.stringValue = Strings.evaluationTitle
licenseInfoLabel.stringValue = Strings.evaluationMessage
emailInput.isEnabled = true
licenseKeyInput.isEnabled = true
emailInput.stringValue = ""
licenseKeyInput.stringValue = ""
emailInput.becomeFirstResponder()
}
#if(DEBUG)
emailInput.stringValue = "igorkolokolnikov@icloud.com"
licenseKeyInput.stringValue = "137A5010-A95A-4A8C-A037-6DF16B6CA666"
#endif
}
}
extension AboutViewController {
func controlTextDidChange(_ obj: Notification) {
activateButton.isEnabled = !(emailInput.stringValue.isEmpty || licenseKeyInput.stringValue.isEmpty)
}
}