From fbf2d9bf2cf50eb681afe18deac06a96c04004e6 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Fri, 17 Jul 2026 10:45:42 -0500 Subject: v4.7.0: Add watchlist tags and saved filter views - TrackedDomain.tags: [String] (backward-compatible custom decode), with updateTags(_:for:) and a normalized/deduplicated write path. - Tag editing in TrackedDomainDetailView (comma-separated field, chip display). - Tag filter chips in WatchlistView (TagFilterChipRowView), integrated into filteredTrackedDomains alongside the existing filter/search. - Saved views: name + snapshot the current tag/filter/sort as a WatchlistSavedView preset (UserDefaults-backed, not part of backup/restore), with a management sheet to apply or delete presets. --- DomainDig/DomainDigUI.swift | 54 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) (limited to 'DomainDig/DomainDigUI.swift') diff --git a/DomainDig/DomainDigUI.swift b/DomainDig/DomainDigUI.swift index 592d7a7..272c678 100644 --- a/DomainDig/DomainDigUI.swift +++ b/DomainDig/DomainDigUI.swift @@ -332,3 +332,57 @@ struct CollapsibleSectionView: View { } } } + +/// A horizontally scrolling row of read-only tag chips, e.g. for a tracked +/// domain's detail view. +struct TagChipRowView: View { + let tags: [String] + + var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 8) { + ForEach(tags, id: \.self) { tag in + Text(tag) + .font(.caption) + .padding(.horizontal, 10) + .padding(.vertical, 5) + .background(Color(.systemGray5).opacity(0.6), in: Capsule()) + } + } + } + } +} + +/// A horizontally scrolling row of selectable tag chips used to filter a list, +/// with an "All" chip to clear the selection. +struct TagFilterChipRowView: View { + let tags: [String] + @Binding var selection: String? + + var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 8) { + filterChip(title: "All", isSelected: selection == nil) { + selection = nil + } + ForEach(tags, id: \.self) { tag in + filterChip(title: tag, isSelected: selection == tag) { + selection = (selection == tag) ? nil : tag + } + } + } + } + } + + private func filterChip(title: String, isSelected: Bool, action: @escaping () -> Void) -> some View { + Button(action: action) { + Text(title) + .font(.caption) + .padding(.horizontal, 10) + .padding(.vertical, 5) + .background(isSelected ? Color.cyan.opacity(0.3) : Color(.systemGray5).opacity(0.6), in: Capsule()) + .foregroundStyle(isSelected ? Color.cyan : Color.primary) + } + .buttonStyle(.plain) + } +} -- cgit v1.2.3