| | |
| | | @State private var newTagColorIndex = 0 |
| | | |
| | | private var sortedTagNames: [String] { |
| | | tagColors.keys |
| | | .filter { !excludedTagNames.contains($0) } |
| | | .sorted { $0.localizedStandardCompare($1) == .orderedAscending } |
| | | let order = TagEditor.orderedTagNames() |
| | | let filtered = tagColors.keys.filter { !excludedTagNames.contains($0) } |
| | | let ordered = order.filter { filtered.contains($0) } |
| | | let remaining = filtered.filter { !ordered.contains($0) }.sorted() |
| | | return ordered + remaining |
| | | } |
| | | |
| | | var body: some View { |
| | |
| | | .foregroundStyle(.secondary) |
| | | .padding(.vertical, 40) |
| | | } |
| | | ForEach(sortedTagNames, id: \.self) { tagName in |
| | | tagEditRow(tagName) |
| | | Divider().opacity(0.15).padding(.leading, 16) |
| | | } |
| | | if addingNewTag { |
| | | newTagRow() |
| | | Divider().opacity(0.15).padding(.leading, 16) |
| | | } |
| | | ForEach(sortedTagNames, id: \.self) { tagName in |
| | | tagEditRow(tagName) |
| | | Divider().opacity(0.15).padding(.leading, 16) |
| | | } |
| | | } |
| | |
| | | // SIP-protected apps where xattr write silently fails. |
| | | } |
| | | |
| | | /// Add a new tag name+color to the local registry only (no xattr write until |
| | | /// the tag is assigned to apps via "Edit App Categories"). |
| | | /// Does NOT call onRefresh — would overwrite tagColors with scan results. |
| | | /// Add a new tag name+color and persist to the database. |
| | | private func addNewTag() { |
| | | let name = newTagNameText.trimmingCharacters(in: .whitespaces) |
| | | guard !name.isEmpty else { return } |
| | | tagColors[name] = newTagColorIndex |
| | | TagEditor.createTag(name, color: newTagColorIndex) |
| | | addingNewTag = false |
| | | newTagNameText = "" |
| | | } |