From 70a1cc2e63d2c49a135bd89cec3ffb1d427d90ee Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Tue, 30 Jun 2026 16:21:47 +0800
Subject: [PATCH] Freeze prep TagLauncher 8.2.9 Pro status UI

---
 src/Apptag/TagEditorView.swift |   97 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 95 insertions(+), 2 deletions(-)

diff --git a/src/Apptag/TagEditorView.swift b/src/Apptag/TagEditorView.swift
index 775170a..1d2963b 100644
--- a/src/Apptag/TagEditorView.swift
+++ b/src/Apptag/TagEditorView.swift
@@ -1,3 +1,4 @@
+import AppKit
 import SwiftUI
 
 // MARK: - Reusable Tag Editor (overlay edit mode + Preferences)
@@ -6,7 +7,11 @@
 /// All changes (rename, delete, setColor) are saved to the local tag database.
 struct TagEditorView: View {
     @Binding var tagColors: [String: Int]
+    @Binding var tagCustomColors: [String: TagCustomColor]
     let excludedTagNames: Set<String>
+    let isCustomColorUnlocked: Bool
+    let onLockedCustomColor: () -> Void
+    var topLeadingAccessory: AnyView? = nil
     var onRefresh: (() -> Void)?
 
     @State private var editingTagName: String? = nil
@@ -30,7 +35,13 @@
         VStack(spacing: 0) {
             // "New Tag" button at top
             HStack {
-                Spacer()
+                if let topLeadingAccessory {
+                    topLeadingAccessory
+                        .frame(maxWidth: .infinity, alignment: .leading)
+                } else {
+                    Spacer()
+                }
+
                 Button {
                     addingNewTag = true
                     newTagNameText = ""
@@ -85,16 +96,20 @@
 
     private func tagEditRow(_ tagName: String) -> some View {
         let colorIndex = tagColors[tagName] ?? 0
+        let hasCustomColor = tagCustomColors[tagName] != nil
         return HStack(spacing: 10) {
             HStack(spacing: 4) {
                 ForEach(TagColor.allIndices, id: \.self) { idx in
-                    ColorSwatch(index: idx, isSelected: idx == colorIndex) {
+                    ColorSwatch(index: idx, isSelected: !hasCustomColor && idx == colorIndex) {
                         TagEditor.setColor(idx, for: tagName)
                         tagColors[tagName] = idx
+                        tagCustomColors.removeValue(forKey: tagName)
                         onRefresh?()
                     }
                 }
             }
+
+            customColorControl(tagName: tagName, colorIndex: colorIndex)
 
             if editingTagName == tagName {
                 MacTextField(
@@ -167,6 +182,8 @@
         TagEditor.renameTag(from: oldName, to: newName)
         tagColors[newName] = tagColors[oldName]
         tagColors.removeValue(forKey: oldName)
+        tagCustomColors[newName] = tagCustomColors[oldName]
+        tagCustomColors.removeValue(forKey: oldName)
         editingTagName = nil
         onRefresh?()
     }
@@ -184,6 +201,82 @@
     private func deleteTag(_ name: String) {
         TagEditor.deleteTagCompletely(name)
         tagColors.removeValue(forKey: name)
+        tagCustomColors.removeValue(forKey: name)
         onRefresh?()
     }
+
+    @ViewBuilder
+    private func customColorControl(tagName: String, colorIndex: Int) -> some View {
+        let isSelected = tagCustomColors[tagName] != nil
+        if isCustomColorUnlocked {
+            ColorPicker(
+                "",
+                selection: customColorBinding(tagName: tagName, colorIndex: colorIndex),
+                supportsOpacity: false
+            )
+            .labelsHidden()
+            .opacity(0.02)
+            .frame(width: 64, height: 24)
+            .overlay(
+                customColorSwatch(tagName: tagName, colorIndex: colorIndex, isSelected: isSelected)
+                    .allowsHitTesting(false)
+            )
+            .frame(width: 72, alignment: .leading)
+            .help(tr("tag.customColor"))
+        } else {
+            Button(action: onLockedCustomColor) {
+                customColorSwatch(tagName: tagName, colorIndex: colorIndex, isSelected: isSelected)
+            }
+            .buttonStyle(.plain)
+            .frame(width: 72, alignment: .leading)
+            .help(tr("pro.prompt.customTagColors"))
+        }
+    }
+
+    private func customColorSwatch(tagName: String, colorIndex: Int, isSelected: Bool) -> some View {
+        let fill = Color(nsColor: TagColor.nsColor(for: colorIndex, customColor: tagCustomColors[tagName]))
+        return ZStack {
+            RoundedRectangle(cornerRadius: 10, style: .continuous)
+                .fill(fill)
+                .frame(width: 58, height: 18)
+                .overlay(
+                    RoundedRectangle(cornerRadius: 10, style: .continuous)
+                        .stroke(Color.secondary.opacity(0.18), lineWidth: 1)
+                )
+                .padding(3)
+                .overlay(
+                    RoundedRectangle(cornerRadius: 13, style: .continuous)
+                        .stroke(isSelected ? Color.white : Color.clear, lineWidth: 2)
+                )
+                .shadow(color: .black.opacity(0.15), radius: 2, y: 1)
+
+            Image(systemName: "crown")
+                .font(.system(size: 10, weight: .semibold))
+                .symbolRenderingMode(.monochrome)
+                .foregroundStyle(Color.white)
+                .shadow(color: .black.opacity(0.28), radius: 1, y: 0.5)
+        }
+    }
+
+    private func customColorBinding(tagName: String, colorIndex: Int) -> Binding<Color> {
+        Binding(
+            get: {
+                Color(nsColor: TagColor.nsColor(for: colorIndex, customColor: tagCustomColors[tagName]))
+            },
+            set: { color in
+                guard isCustomColorUnlocked else {
+                    onLockedCustomColor()
+                    return
+                }
+                let nsColor = NSColor(color)
+                let customColor = TagCustomColor(nsColor: nsColor)
+                guard TagEditor.setCustomColor(customColor, for: tagName) else {
+                    onLockedCustomColor()
+                    return
+                }
+                tagCustomColors[tagName] = customColor
+                onRefresh?()
+            }
+        )
+    }
 }

--
Gitblit v1.9.3