diff options
| author | Christian Cleberg <[email protected]> | 2026-04-20 16:57:40 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-20 16:57:40 -0500 |
| commit | 2e90ee4ceca73d5126118920b486149cb227c4e9 (patch) | |
| tree | 32c726ba864f3e88e018452df3571f31bb518db0 /DomainDig/WatchlistView.swift | |
| parent | 9999f876feb9a2d419a79ad601f78053c44d44f8 (diff) | |
| download | domain-dig-2e90ee4ceca73d5126118920b486149cb227c4e9.tar.gz domain-dig-2e90ee4ceca73d5126118920b486149cb227c4e9.tar.bz2 domain-dig-2e90ee4ceca73d5126118920b486149cb227c4e9.zip | |
feat(v2.1.0): add bulk workflows, CSV export, and filtering
Diffstat (limited to 'DomainDig/WatchlistView.swift')
| -rw-r--r-- | DomainDig/WatchlistView.swift | 78 |
1 files changed, 73 insertions, 5 deletions
diff --git a/DomainDig/WatchlistView.swift b/DomainDig/WatchlistView.swift index aea8707..b01b8f9 100644 --- a/DomainDig/WatchlistView.swift +++ b/DomainDig/WatchlistView.swift @@ -6,7 +6,27 @@ struct WatchlistView: View { var body: some View { List { - if viewModel.sortedTrackedDomains.isEmpty { + if viewModel.batchLookupSource == .watchlistRefresh, (!viewModel.batchResults.isEmpty || viewModel.batchLookupRunning) { + Section("Refresh Progress") { + VStack(alignment: .leading, spacing: 8) { + if viewModel.batchLookupRunning { + ProgressView(value: Double(viewModel.batchCompletedCount), total: Double(max(viewModel.batchTotalCount, 1))) + .tint(.cyan) + Text(viewModel.batchProgressLabel) + .font(.system(.caption, design: .monospaced)) + .foregroundStyle(.secondary) + } + + ForEach(viewModel.batchResults.prefix(5)) { result in + BatchResultRowView(result: result) + } + } + .padding(.vertical, 4) + } + .listRowBackground(Color(.systemGray6).opacity(0.5)) + } + + if viewModel.filteredTrackedDomains.isEmpty { Section { VStack(alignment: .leading, spacing: 8) { Text("No tracked domains yet") @@ -30,7 +50,7 @@ struct WatchlistView: View { } Section { - ForEach(viewModel.sortedTrackedDomains) { trackedDomain in + ForEach(viewModel.filteredTrackedDomains) { trackedDomain in NavigationLink { TrackedDomainDetailView(viewModel: viewModel, trackedDomain: trackedDomain) } label: { @@ -83,7 +103,7 @@ struct WatchlistView: View { } .listRowBackground(Color(.systemGray6).opacity(0.5)) } - .onDelete(perform: viewModel.deleteTrackedDomains) + .onDelete(perform: deleteFilteredTrackedDomains) } header: { Text("Tracked Domains") } @@ -92,9 +112,40 @@ struct WatchlistView: View { .scrollContentBackground(.hidden) .background(Color.black) .navigationTitle("Watchlist") + .searchable(text: $viewModel.watchlistSearchText, prompt: "Search tracked domains") .toolbar { - if !viewModel.sortedTrackedDomains.isEmpty { - EditButton() + if !viewModel.filteredTrackedDomains.isEmpty { + ToolbarItemGroup(placement: .topBarTrailing) { + Menu { + Picker("Filter", selection: $viewModel.watchlistFilter) { + ForEach(WatchlistFilterOption.allCases) { option in + Text(option.title).tag(option) + } + } + + Picker("Sort", selection: $viewModel.watchlistSortOption) { + ForEach(WatchlistSortOption.allCases) { option in + Text(option.title).tag(option) + } + } + + Button("Refresh All") { + viewModel.refreshAllTrackedDomains() + } + + Button("Export TXT") { + shareTrackedDomains(asCSV: false) + } + + Button("Export CSV") { + shareTrackedDomains(asCSV: true) + } + } label: { + Image(systemName: "line.3.horizontal.decrease.circle") + } + + EditButton() + } } } .onChange(of: viewModel.rerunNavigationToken) { _, _ in @@ -102,6 +153,23 @@ struct WatchlistView: View { } .preferredColorScheme(.dark) } + + private func deleteFilteredTrackedDomains(at offsets: IndexSet) { + let domains = offsets.map { viewModel.filteredTrackedDomains[$0] } + domains.forEach(viewModel.deleteTrackedDomain) + } + + private func shareTrackedDomains(asCSV: Bool) { + let formatter = DateFormatter() + formatter.dateFormat = "yyyyMMdd_HHmmss" + let timestamp = formatter.string(from: Date()) + let fileExtension = asCSV ? "csv" : "txt" + let filename = "\(timestamp)_domaindig_watchlist.\(fileExtension)" + let contents = asCSV + ? viewModel.exportTrackedDomainsCSV(domains: viewModel.filteredTrackedDomains) + : viewModel.exportTrackedDomainsText(domains: viewModel.filteredTrackedDomains) + ExportPresenter.share(filename: filename, contents: contents) + } } struct WatchlistRowView: View { |
