summaryrefslogtreecommitdiff
path: root/Hutch/Views/Builds/BuildListViewModel.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-12 18:59:34 -0500
committerChristian Cleberg <[email protected]>2026-04-12 18:59:34 -0500
commitb3d3e68e742650cb8a448a1f51fea7fb7783c36f (patch)
tree3508d8e3dcd31568fca293de9655a7f5957d40ea /Hutch/Views/Builds/BuildListViewModel.swift
parentb526cddd8ec421eba00feecbdffae3978cb61b34 (diff)
downloadhutch-b3d3e68e742650cb8a448a1f51fea7fb7783c36f.tar.gz
hutch-b3d3e68e742650cb8a448a1f51fea7fb7783c36f.tar.bz2
hutch-b3d3e68e742650cb8a448a1f51fea7fb7783c36f.zip
feat: add builds auto-refresh and repo filtering
implements: https://todo.sr.ht/~ccleberg/hutch/17 implements: https://todo.sr.ht/~ccleberg/hutch/18
Diffstat (limited to 'Hutch/Views/Builds/BuildListViewModel.swift')
-rw-r--r--Hutch/Views/Builds/BuildListViewModel.swift66
1 files changed, 63 insertions, 3 deletions
diff --git a/Hutch/Views/Builds/BuildListViewModel.swift b/Hutch/Views/Builds/BuildListViewModel.swift
index 8468cff..da29ca5 100644
--- a/Hutch/Views/Builds/BuildListViewModel.swift
+++ b/Hutch/Views/Builds/BuildListViewModel.swift
@@ -25,6 +25,20 @@ enum BuildListFilter: String, CaseIterable, Sendable {
case all = "All"
}
+enum AutoRefreshInterval: Int, CaseIterable, Sendable {
+ case off = 0
+ case fiveSeconds = 5
+ case tenSeconds = 10
+
+ var label: String {
+ switch self {
+ case .off: "Off"
+ case .fiveSeconds: "5s"
+ case .tenSeconds: "10s"
+ }
+ }
+}
+
// MARK: - View Model
@Observable
@@ -39,10 +53,15 @@ final class BuildListViewModel {
var error: String?
var filter: BuildListFilter = .attention
var searchText = ""
+ var repoFilter: String = "" {
+ didSet { if repoFilter != oldValue { repoFilterDidChange() } }
+ }
private var cursor: String?
private var hasMore = true
private let client: SRHTClient
+ private var refreshTask: Task<Void, Never>?
+ private var isAutoRefreshing = false
private static let cacheKey = "builds.jobs"
@@ -50,11 +69,22 @@ final class BuildListViewModel {
self.client = client
}
+ /// Unique tags across all loaded jobs, sorted alphabetically.
+ var availableTags: [String] {
+ let allTags = Set(jobs.flatMap(\.tags))
+ return allTags.sorted { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending }
+ }
+
var filteredJobs: [JobSummary] {
- let statusFiltered = Self.filterJobs(jobs, filter: filter)
+ var result = Self.filterJobs(jobs, filter: filter)
+
+ if !repoFilter.isEmpty {
+ result = result.filter { $0.tags.contains(repoFilter) }
+ }
+
let q = searchText.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
- guard !q.isEmpty else { return statusFiltered }
- return statusFiltered.filter {
+ guard !q.isEmpty else { return result }
+ return result.filter {
String($0.id).contains(q) ||
$0.tags.contains { $0.lowercased().contains(q) } ||
($0.note?.lowercased().contains(q) == true) ||
@@ -62,6 +92,36 @@ final class BuildListViewModel {
}
}
+ // MARK: - Auto-Refresh
+
+ func startAutoRefresh(interval: AutoRefreshInterval) {
+ stopAutoRefresh()
+ guard interval != .off else { return }
+ let seconds = interval.rawValue
+ refreshTask = Task { [weak self] in
+ while !Task.isCancelled {
+ try? await Task.sleep(for: .seconds(seconds))
+ guard !Task.isCancelled, let self else { return }
+ guard !self.isAutoRefreshing, !self.isLoading, !self.isRefreshing else { continue }
+ self.isAutoRefreshing = true
+ await self.loadJobs()
+ self.isAutoRefreshing = false
+ }
+ }
+ }
+
+ func stopAutoRefresh() {
+ refreshTask?.cancel()
+ refreshTask = nil
+ }
+
+ private func repoFilterDidChange() {
+ // Reset to empty if the selected tag no longer exists
+ if !repoFilter.isEmpty, !availableTags.contains(repoFilter) {
+ repoFilter = ""
+ }
+ }
+
// MARK: - Query
private static let query = """