diff options
| author | Christian Cleberg <[email protected]> | 2026-04-23 17:39:13 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-23 17:39:13 -0500 |
| commit | 66601a8091a73f3e31f4fb534c38b3eca4756e93 (patch) | |
| tree | f85cd883e63177d25dfe5993a4d545efa4812923 /Hutch | |
| parent | 380fb866d090c5f13b2ad3f88f1fe23c4ade4801 (diff) | |
| download | hutch-3.2.1.tar.gz hutch-3.2.1.tar.bz2 hutch-3.2.1.zip | |
fix: profile loading error and user-timeline prefs updatev3.2.1
Diffstat (limited to 'Hutch')
| -rw-r--r-- | Hutch/App/AppStorageKeys.swift | 1 | ||||
| -rw-r--r-- | Hutch/Models/Meta.swift | 8 | ||||
| -rw-r--r-- | Hutch/Views/Builds/BuildListView.swift | 26 | ||||
| -rw-r--r-- | Hutch/Views/Builds/BuildListViewModel.swift | 28 | ||||
| -rw-r--r-- | Hutch/Views/Home/HomeView.swift | 3 | ||||
| -rw-r--r-- | Hutch/Views/Home/HomeViewModel.swift | 4 | ||||
| -rw-r--r-- | Hutch/Views/More/ProfileView.swift | 4 | ||||
| -rw-r--r-- | Hutch/Views/Settings/SettingsViewModel.swift | 4 |
8 files changed, 70 insertions, 8 deletions
diff --git a/Hutch/App/AppStorageKeys.swift b/Hutch/App/AppStorageKeys.swift index 30cd0d0..de3ce54 100644 --- a/Hutch/App/AppStorageKeys.swift +++ b/Hutch/App/AppStorageKeys.swift @@ -18,6 +18,7 @@ enum AppStorageKeys { static let homeBuildsExpanded = "homeBuildsExpanded" static let buildsAutoRefreshInterval = "buildsAutoRefreshInterval" static let buildsRepoFilter = "buildsRepoFilter" + static let buildsLookbackDays = "buildsLookbackDays" static let ticketFilterState = "ticketFilterState" static let ticketSavedFilters = "ticketSavedFilters" static let appTheme = "appTheme" diff --git a/Hutch/Models/Meta.swift b/Hutch/Models/Meta.swift index c0434bc..91bd7e3 100644 --- a/Hutch/Models/Meta.swift +++ b/Hutch/Models/Meta.swift @@ -22,10 +22,16 @@ struct UserProfile: Codable, Sendable { struct SSHKey: Codable, Sendable, Identifiable { let id: Int - let fingerprint: String let comment: String? let created: Date let lastUsed: Date? + + var displayLabel: String { + if let comment, !comment.isEmpty { + return comment + } + return "SSH key #\(id)" + } } struct SSHKeyPage: Codable, Sendable { 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 } } """ |
