From 9c317d89a39d0871f15f8282aec6c7f3dde7768c Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Fri, 26 Jun 2026 22:42:44 +0800
Subject: [PATCH] Polish Pro status UI and app grid layout
---
src/Apptag/ApptagApp.swift | 82 +++++++++++++++++++++++++++++++++++++++-
1 files changed, 79 insertions(+), 3 deletions(-)
diff --git a/src/Apptag/ApptagApp.swift b/src/Apptag/ApptagApp.swift
index f0df77a..bb6b32b 100644
--- a/src/Apptag/ApptagApp.swift
+++ b/src/Apptag/ApptagApp.swift
@@ -34,6 +34,7 @@
private static let statusItemButtonIdentifier = NSUserInterfaceItemIdentifier("TagLauncherStatusItemButton")
private static let statusItemAccessibilityLabel = AppIdentity.displayName
private static let showAppListMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherShowAppListMenuItem")
+ private static let proStatusMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherProStatusMenuItem")
private static let helpMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherHelpMenu")
private static let downloadHelpMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherDownloadHelpMenuItem")
private static let externalActivationNotification = Notification.Name("TagLauncherExternalActivationRequested")
@@ -193,8 +194,10 @@
func applicationDidFinishLaunching(_ notification: Notification) {
AppDefaults.register()
L10n.setup()
+ ProEntitlementCenter.shared.start()
migrateDefaultGroupName()
TagDatabase.seedDefaultTags()
+ _ = TagDatabase.relocalizeSystemTagsForCurrentLanguage()
syncChromeSettings(force: true)
observeHotkeyStatusChanges()
registerConfiguredHotkeys()
@@ -659,6 +662,9 @@
versionItem.isEnabled = false
menu.addItem(versionItem)
+ let proStatusItem = makeProStatusMenuItem()
+ menu.addItem(proStatusItem)
+
menu.addItem(.separator())
let prefsItem = NSMenuItem(
title: tr("menu.preferences"),
@@ -695,6 +701,7 @@
}
func menuWillOpen(_ menu: NSMenu) {
+ configureProStatusMenuItem(in: menu)
statusMenuScreenForNextOverlay = overlayController.screenContainingCurrentPointer()
?? statusItem?.button?.window?.screen
?? NSScreen.main
@@ -764,6 +771,53 @@
image.isTemplate = true
image.accessibilityDescription = "TagLauncher"
+ return image
+ }
+
+ private func makeProStatusMenuItem() -> NSMenuItem {
+ let item = NSMenuItem(
+ title: "",
+ action: #selector(openProStatusFromStatusMenu(_:)),
+ keyEquivalent: ""
+ )
+ item.identifier = Self.proStatusMenuItemIdentifier
+ item.target = self
+ configureProStatusMenuItem(item)
+ return item
+ }
+
+ private func configureProStatusMenuItem(in menu: NSMenu) {
+ guard let item = menu.items.first(where: { $0.identifier == Self.proStatusMenuItemIdentifier }) else {
+ return
+ }
+ configureProStatusMenuItem(item)
+ }
+
+ private func configureProStatusMenuItem(_ item: NSMenuItem) {
+ let isUnlocked = ProEntitlementPolicy.accessState().isUnlocked
+ item.title = tr(isUnlocked ? "menu.proStatus.unlocked" : "menu.proStatus.free")
+ item.action = #selector(openProStatusFromStatusMenu(_:))
+ item.target = self
+ item.isEnabled = true
+ item.image = isUnlocked ? makeProStatusMenuIcon() : nil
+ }
+
+ private func makeProStatusMenuIcon() -> NSImage? {
+ guard let symbol = NSImage(systemSymbolName: "crown.fill", accessibilityDescription: "Pro") else {
+ return nil
+ }
+ let configuration = NSImage.SymbolConfiguration(pointSize: 13, weight: .semibold)
+ let configuredSymbol = symbol.withSymbolConfiguration(configuration) ?? symbol
+ let image = NSImage(size: NSSize(width: 16, height: 16))
+ image.lockFocus()
+ NSColor(red: 0.96, green: 0.63, blue: 0.10, alpha: 1.0).set()
+ let iconRect = NSRect(x: 1.5, y: 1.5, width: 13, height: 13)
+ configuredSymbol.draw(in: iconRect, from: .zero, operation: .sourceOver, fraction: 1.0)
+ NSColor(red: 0.96, green: 0.63, blue: 0.10, alpha: 1.0).set()
+ iconRect.fill(using: .sourceAtop)
+ image.unlockFocus()
+ image.isTemplate = false
+ image.accessibilityDescription = "Pro"
return image
}
@@ -1667,9 +1721,13 @@
forName: .tagLauncherOpenPreferencesRequested,
object: nil,
queue: .main
- ) { [weak self] _ in
- self?.openPreferences()
+ ) { [weak self] notification in
+ self?.openPreferences(targetTab: Self.preferencesTabTarget(from: notification))
}
+ }
+
+ private static func preferencesTabTarget(from notification: Notification) -> String? {
+ notification.userInfo?[SettingsTabTarget.userInfoKey] as? String
}
private func observeExternalActivationRequests() {
@@ -1691,6 +1749,14 @@
}
@objc private func openPreferences(_ sender: Any? = nil) {
+ openPreferences(targetTab: nil)
+ }
+
+ @objc private func openProStatusFromStatusMenu(_ sender: NSMenuItem) {
+ openPreferences(targetTab: SettingsTabTarget.about)
+ }
+
+ private func openPreferences(targetTab: String?) {
dismissQuickSearchIfNeeded()
TagDatabase.flushPendingCategorySchemeBackupBatch()
if overlayAvoidsSpaceSwitch {
@@ -1706,6 +1772,7 @@
if let settingsWindow {
prepareSettingsWindow(settingsWindow)
+ requestPreferencesTabSelection(targetTab)
return
}
@@ -1716,12 +1783,21 @@
defer: false
)
window.title = tr("menu.preferences").replacingOccurrences(of: "…", with: "")
- window.contentView = NSHostingView(rootView: PreferencesView())
+ window.contentView = NSHostingView(rootView: PreferencesView(initialTabRawValue: targetTab))
window.isReleasedWhenClosed = false
settingsWindow = window
prepareSettingsWindow(window)
}
+ private func requestPreferencesTabSelection(_ targetTab: String?) {
+ guard let targetTab else { return }
+ NotificationCenter.default.post(
+ name: .tagLauncherPreferencesTabRequested,
+ object: nil,
+ userInfo: [SettingsTabTarget.userInfoKey: targetTab]
+ )
+ }
+
private func dismissQuickSearchIfNeeded() {
guard isQuickSearchOpen else { return }
isQuickSearchOpen = false
--
Gitblit v1.9.3