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/Models.swift | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) (limited to 'DomainDig/Models.swift') diff --git a/DomainDig/Models.swift b/DomainDig/Models.swift index d14ff4a..b86fc07 100644 --- a/DomainDig/Models.swift +++ b/DomainDig/Models.swift @@ -882,7 +882,7 @@ struct TimelineSection: Identifiable, Equatable { let entries: [SnapshotSummary] } -enum WatchlistFilterOption: String, CaseIterable, Identifiable { +enum WatchlistFilterOption: String, CaseIterable, Identifiable, Codable { case all case pinnedOnly case changedOnly @@ -901,7 +901,7 @@ enum WatchlistFilterOption: String, CaseIterable, Identifiable { } } -enum WatchlistSortOption: String, CaseIterable, Identifiable { +enum WatchlistSortOption: String, CaseIterable, Identifiable, Codable { case pinned case recentlyUpdated case alphabetical @@ -920,6 +920,30 @@ enum WatchlistSortOption: String, CaseIterable, Identifiable { } } +/// A named watchlist filter preset (tag, filter, sort) the user can save and +/// reapply. Stored locally in UserDefaults; not included in backup/restore. +struct WatchlistSavedView: Codable, Identifiable, Equatable { + let id: UUID + var name: String + var tag: String? + var filter: WatchlistFilterOption + var sort: WatchlistSortOption + + init( + id: UUID = UUID(), + name: String, + tag: String?, + filter: WatchlistFilterOption, + sort: WatchlistSortOption + ) { + self.id = id + self.name = name + self.tag = tag + self.filter = filter + self.sort = sort + } +} + enum PortfolioFilterOption: String, CaseIterable, Identifiable { case all case healthy @@ -1258,6 +1282,7 @@ struct TrackedDomain: Codable, Identifiable, Equatable { var monitoringState: MonitoringState var pendingMonitoringAlerts: [MonitoringPendingAlert] var collaboration: CollaborationMetadata? + var tags: [String] init( id: UUID = UUID(), @@ -1277,7 +1302,8 @@ struct TrackedDomain: Codable, Identifiable, Equatable { lastAlertAt: Date? = nil, monitoringState: MonitoringState = MonitoringState(), pendingMonitoringAlerts: [MonitoringPendingAlert] = [], - collaboration: CollaborationMetadata? = nil + collaboration: CollaborationMetadata? = nil, + tags: [String] = [] ) { self.id = id self.domain = domain @@ -1297,6 +1323,7 @@ struct TrackedDomain: Codable, Identifiable, Equatable { self.monitoringState = monitoringState self.pendingMonitoringAlerts = pendingMonitoringAlerts self.collaboration = collaboration + self.tags = tags } init(from decoder: Decoder) throws { @@ -1319,6 +1346,7 @@ struct TrackedDomain: Codable, Identifiable, Equatable { monitoringState = try container.decodeIfPresent(MonitoringState.self, forKey: .monitoringState) ?? MonitoringState() pendingMonitoringAlerts = try container.decodeIfPresent([MonitoringPendingAlert].self, forKey: .pendingMonitoringAlerts) ?? [] collaboration = try container.decodeIfPresent(CollaborationMetadata.self, forKey: .collaboration) + tags = try container.decodeIfPresent([String].self, forKey: .tags) ?? [] } } -- cgit v1.2.3