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