369 lines
15 KiB
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)
|
|
}
|
|
}
|