summaryrefslogtreecommitdiff
path: root/Hutch/Views
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-23 17:39:13 -0500
committerChristian Cleberg <[email protected]>2026-04-23 17:39:13 -0500
commit66601a8091a73f3e31f4fb534c38b3eca4756e93 (patch)
treef85cd883e63177d25dfe5993a4d545efa4812923 /Hutch/Views
parent380fb866d090c5f13b2ad3f88f1fe23c4ade4801 (diff)
downloadhutch-66601a8091a73f3e31f4fb534c38b3eca4756e93.tar.gz
hutch-66601a8091a73f3e31f4fb534c38b3eca4756e93.tar.bz2
hutch-66601a8091a73f3e31f4fb534c38b3eca4756e93.zip
fix: profile loading error and user-timeline prefs updatev3.2.1
Diffstat (limited to 'Hutch/Views')
-rw-r--r--Hutch/Views/Builds/BuildListView.swift26
-rw-r--r--Hutch/Views/Builds/BuildListViewModel.swift28
-rw-r--r--Hutch/Views/Home/HomeView.swift3
-rw-r--r--Hutch/Views/Home/HomeViewModel.swift4
-rw-r--r--Hutch/Views/More/ProfileView.swift4
-rw-r--r--Hutch/Views/Settings/SettingsViewModel.swift4
6 files changed, 62 insertions, 7 deletions
diff --git a/Hutch/Views/Builds/BuildListView.swift b/Hutch/Views/Builds/BuildListView.swift
index 8de2220..20414d6 100644
--- a/Hutch/Views/Builds/BuildListView.swift
+++ b/Hutch/Views/Builds/BuildListView.swift
@@ -4,6 +4,8 @@ struct BuildListView: View {
@AppStorage(AppStorageKeys.swipeActionsEnabled, store: .standard) private var swipeActionsEnabled = true
@AppStorage(AppStorageKeys.buildsAutoRefreshInterval) private var autoRefreshRawValue = 0
@AppStorage(AppStorageKeys.buildsRepoFilter) private var savedRepoFilter = ""
+ @AppStorage(AppStorageKeys.buildsLookbackDays, store: .standard)
+ private var lookbackDays = BuildListViewModel.defaultLookbackDays
@Environment(AppState.self) private var appState
@Environment(\.isAMOLEDTheme) private var isAMOLED
@State private var viewModel: BuildListViewModel?
@@ -65,6 +67,20 @@ struct BuildListView: View {
}
}
}
+ Section("Timeframe") {
+ ForEach(HomeViewModel.allowedFailedBuildLookbackDays, id: \.self) { days in
+ Button {
+ lookbackDays = days
+ viewModel.lookbackDays = days
+ } label: {
+ if lookbackDays == days {
+ Label(HomeViewModel.failedBuildLookbackLabel(days: days), systemImage: "checkmark")
+ } else {
+ Text(HomeViewModel.failedBuildLookbackLabel(days: days))
+ }
+ }
+ }
+ }
} label: {
Image(systemName: "line.3.horizontal.decrease.circle")
}
@@ -107,6 +123,7 @@ struct BuildListView: View {
if viewModel == nil {
let vm = BuildListViewModel(client: appState.client, defaults: appState.accountDefaults)
vm.repoFilter = savedRepoFilter
+ vm.lookbackDays = lookbackDays
viewModel = vm
await vm.loadJobs()
}
@@ -114,6 +131,9 @@ struct BuildListView: View {
// onDisappear stops it when navigating away.
viewModel?.startAutoRefresh(interval: autoRefreshInterval)
}
+ .onChange(of: lookbackDays) { _, newValue in
+ viewModel?.lookbackDays = newValue
+ }
.onDisappear {
viewModel?.stopAutoRefresh()
}
@@ -236,6 +256,12 @@ struct BuildListView: View {
systemImage: "magnifyingglass",
description: Text("No builds matched “\(viewModel.searchText)”.")
)
+ } else if !viewModel.jobs.isEmpty, viewModel.filteredJobs.isEmpty {
+ ContentUnavailableView(
+ "No Builds In Timeframe",
+ systemImage: "calendar.badge.clock",
+ description: Text("No builds were updated \(HomeViewModel.failedBuildLookbackLabel(days: lookbackDays)).")
+ )
} else if viewModel.jobs.isEmpty, viewModel.error == nil {
ContentUnavailableView(
"No Builds",
diff --git a/Hutch/Views/Builds/BuildListViewModel.swift b/Hutch/Views/Builds/BuildListViewModel.swift
index cf5d786..14f50fd 100644
--- a/Hutch/Views/Builds/BuildListViewModel.swift
+++ b/Hutch/Views/Builds/BuildListViewModel.swift
@@ -45,6 +45,7 @@ enum AutoRefreshInterval: Int, CaseIterable, Sendable {
@MainActor
final class BuildListViewModel {
private static let searchHistoryScopeID = "builds"
+ nonisolated static let defaultLookbackDays = HomeViewModel.defaultFailedBuildLookbackDays
private(set) var jobs: [JobSummary] = [] {
didSet { updateFilteredJobs() }
@@ -68,6 +69,9 @@ final class BuildListViewModel {
updateFilteredJobs()
}
}
+ var lookbackDays = defaultLookbackDays {
+ didSet { updateFilteredJobs() }
+ }
// Cached filtered result. Updated whenever jobs, filter, searchText, or
// repoFilter changes. Only notifies observers when the content actually
// differs, which prevents the list from re-rendering on auto-refresh when
@@ -99,7 +103,7 @@ final class BuildListViewModel {
}
private func updateFilteredJobs() {
- var result = Self.filterJobs(jobs, filter: filter)
+ var result = Self.filterJobs(jobs, filter: filter, lookbackDays: lookbackDays)
if !repoFilter.isEmpty {
result = result.filter { $0.tags.contains(repoFilter) }
}
@@ -382,8 +386,14 @@ final class BuildListViewModel {
let cancel: CancelResult
}
- nonisolated static func filterJobs(_ jobs: [JobSummary], filter: BuildListFilter) -> [JobSummary] {
- jobs.filter { job in
+ nonisolated static func filterJobs(
+ _ jobs: [JobSummary],
+ filter: BuildListFilter,
+ lookbackDays: Int,
+ now: Date = .now,
+ calendar: Calendar = .current
+ ) -> [JobSummary] {
+ let filteredByStatus = jobs.filter { job in
switch filter {
case .attention:
switch job.status {
@@ -403,6 +413,18 @@ final class BuildListViewModel {
return true
}
}
+
+ let normalizedLookbackDays = HomeViewModel.allowedFailedBuildLookbackDays.contains(lookbackDays)
+ ? lookbackDays
+ : defaultLookbackDays
+ let startOfToday = calendar.startOfDay(for: now)
+ let windowStart = calendar.date(
+ byAdding: .day,
+ value: -(normalizedLookbackDays - 1),
+ to: startOfToday
+ ) ?? startOfToday
+
+ return filteredByStatus.filter { $0.updated >= windowStart }
}
nonisolated static func searchJobs(_ jobs: [JobSummary], matching query: String) -> [JobSummary] {
diff --git a/Hutch/Views/Home/HomeView.swift b/Hutch/Views/Home/HomeView.swift
index 406e2ad..9e06a00 100644
--- a/Hutch/Views/Home/HomeView.swift
+++ b/Hutch/Views/Home/HomeView.swift
@@ -56,6 +56,9 @@ struct HomeView: View {
loadRecentActivity()
}
}
+ .onChange(of: failedBuildLookbackDays) { _, _ in
+ viewModel?.refreshNeedsAttentionSnapshot()
+ }
}
@ViewBuilder
diff --git a/Hutch/Views/Home/HomeViewModel.swift b/Hutch/Views/Home/HomeViewModel.swift
index 7ef82e9..142eee4 100644
--- a/Hutch/Views/Home/HomeViewModel.swift
+++ b/Hutch/Views/Home/HomeViewModel.swift
@@ -670,6 +670,10 @@ final class HomeViewModel {
persistNeedsAttentionSnapshot()
}
+ func refreshNeedsAttentionSnapshot() {
+ persistNeedsAttentionSnapshot()
+ }
+
private func loadProjects() async -> Result<[Project], Error> {
do {
return .success(try await projectService.fetchProjects())
diff --git a/Hutch/Views/More/ProfileView.swift b/Hutch/Views/More/ProfileView.swift
index 8f353ee..45f7d06 100644
--- a/Hutch/Views/More/ProfileView.swift
+++ b/Hutch/Views/More/ProfileView.swift
@@ -243,7 +243,7 @@ struct ProfileView: View {
Section {
ForEach(viewModel.sshKeys) { key in
VStack(alignment: .leading, spacing: 2) {
- Text(key.fingerprint)
+ Text(key.displayLabel)
.font(.caption.monospaced())
.lineLimit(1)
.truncationMode(.middle)
@@ -677,7 +677,7 @@ private enum ProfileDestructiveAction {
var message: String {
switch self {
case .deleteSSHKey(let key):
- "Remove SSH key \(key.fingerprint) from your account?"
+ "Remove \(key.displayLabel) from your account?"
case .deletePGPKey(let key):
"Remove PGP key \(key.fingerprint) from your account?"
}
diff --git a/Hutch/Views/Settings/SettingsViewModel.swift b/Hutch/Views/Settings/SettingsViewModel.swift
index 0b55e6a..edfaad4 100644
--- a/Hutch/Views/Settings/SettingsViewModel.swift
+++ b/Hutch/Views/Settings/SettingsViewModel.swift
@@ -90,7 +90,7 @@ final class SettingsViewModel {
avatar
userType
sshKeys {
- results { id fingerprint comment created lastUsed }
+ results { id comment created lastUsed }
cursor
}
pgpKeys {
@@ -114,7 +114,7 @@ final class SettingsViewModel {
private static let createSSHKeyMutation = """
mutation createSSHKey($key: String!) {
createSSHKey(key: $key) {
- id fingerprint comment created lastUsed
+ id comment created lastUsed
}
}
"""