diff options
| author | Christian Cleberg <[email protected]> | 2026-04-12 00:34:54 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-12 00:34:54 -0500 |
| commit | 5f6d545eb3455a9e4da5a3cb4460811e3a48d939 (patch) | |
| tree | 7b7591146da4e02bd617ddb9f26e47b1c4474ef6 /Hutch/Views/Inbox | |
| parent | cb1ea5ea4f163d87053285d3fb7999b12a3558b9 (diff) | |
| download | hutch-2.14.0.tar.gz hutch-2.14.0.tar.bz2 hutch-2.14.0.zip | |
improve home attention flows and cross-linkingv2.14.0
Diffstat (limited to 'Hutch/Views/Inbox')
| -rw-r--r-- | Hutch/Views/Inbox/InboxView.swift | 11 | ||||
| -rw-r--r-- | Hutch/Views/Inbox/InboxViewModel.swift | 25 | ||||
| -rw-r--r-- | Hutch/Views/Inbox/ThreadDetailView.swift | 51 |
3 files changed, 85 insertions, 2 deletions
diff --git a/Hutch/Views/Inbox/InboxView.swift b/Hutch/Views/Inbox/InboxView.swift index aa3765c..2447a6e 100644 --- a/Hutch/Views/Inbox/InboxView.swift +++ b/Hutch/Views/Inbox/InboxView.swift @@ -42,6 +42,17 @@ struct InboxView: View { @Bindable var vm = viewModel List { + Section { + Picker("Filter", selection: $vm.filter) { + ForEach(InboxThreadFilter.allCases, id: \.self) { filter in + Text(filter.rawValue).tag(filter) + } + } + .pickerStyle(.segmented) + .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets()) + } + ForEach(viewModel.filteredThreads) { thread in Button { selectThread(thread) diff --git a/Hutch/Views/Inbox/InboxViewModel.swift b/Hutch/Views/Inbox/InboxViewModel.swift index edb7593..a01f836 100644 --- a/Hutch/Views/Inbox/InboxViewModel.swift +++ b/Hutch/Views/Inbox/InboxViewModel.swift @@ -56,12 +56,19 @@ private struct InboxEmailPreview: Decodable, Sendable { let patch: InboxPatchPreview? } +enum InboxThreadFilter: String, CaseIterable, Sendable { + case all = "All" + case patches = "Patches" + case discussions = "Talk" +} + @Observable @MainActor final class InboxViewModel { private(set) var threads: [InboxThreadSummary] = [] private(set) var isLoading = false var error: String? + var filter: InboxThreadFilter = .all var searchText = "" private let client: SRHTClient @@ -181,9 +188,10 @@ final class InboxViewModel { } var filteredThreads: [InboxThreadSummary] { + let filteredByKind = Self.filterThreads(threads, filter: filter) let q = searchText.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() - guard !q.isEmpty else { return threads } - return threads.filter { + guard !q.isEmpty else { return filteredByKind } + return filteredByKind.filter { $0.displaySubject.lowercased().contains(q) || $0.listName.lowercased().contains(q) || $0.latestSender.canonicalName.lowercased().contains(q) @@ -389,4 +397,17 @@ final class InboxViewModel { } return nil } + + nonisolated static func filterThreads(_ threads: [InboxThreadSummary], filter: InboxThreadFilter) -> [InboxThreadSummary] { + threads.filter { thread in + switch filter { + case .all: + return true + case .patches: + return thread.containsPatch + case .discussions: + return !thread.containsPatch + } + } + } } diff --git a/Hutch/Views/Inbox/ThreadDetailView.swift b/Hutch/Views/Inbox/ThreadDetailView.swift index c595497..03aa779 100644 --- a/Hutch/Views/Inbox/ThreadDetailView.swift +++ b/Hutch/Views/Inbox/ThreadDetailView.swift @@ -18,6 +18,7 @@ struct ThreadDetailView: View { @State private var hasMarkedCurrentThreadViewed = false @State private var suppressAutoMarkViewed = false @State private var isUnread: Bool + @State private var isOpeningRepository = false init( thread: InboxThreadSummary, @@ -119,6 +120,41 @@ struct ThreadDetailView: View { .padding(.vertical, 4) } + Section("Related") { + Button { + appState.navigateToMailingList( + InboxMailingListReference( + id: thread.listID, + rid: thread.listRID, + name: thread.listName, + owner: thread.listOwner + ) + ) + } label: { + Label(thread.listDisplayName, systemImage: "list.bullet") + } + + if let repo = self.thread.repo { + Button { + openRepository(named: repo, ownerCanonicalName: thread.listOwner.canonicalName) + } label: { + HStack { + Label("\(thread.listOwner.canonicalName)/\(repo)", systemImage: "book.closed") + Spacer() + if isOpeningRepository { + ProgressView() + .controlSize(.small) + } else { + Image(systemName: "arrow.up.right") + .font(.caption) + .foregroundStyle(.tertiary) + } + } + } + .disabled(isOpeningRepository) + } + } + if let partialWarning = viewModel.partialWarning { Section { Text(partialWarning) @@ -180,6 +216,21 @@ struct ThreadDetailView: View { parts.append(thread.lastActivityAt.relativeDescription) return parts.joined(separator: " • ") } + + private func openRepository(named repositoryName: String, ownerCanonicalName: String) { + guard !isOpeningRepository else { return } + isOpeningRepository = true + Task { + defer { isOpeningRepository = false } + do { + let ownerUsername = ownerCanonicalName.hasPrefix("~") ? String(ownerCanonicalName.dropFirst()) : ownerCanonicalName + let repository = try await appState.resolveRepository(owner: ownerUsername, name: repositoryName) + appState.navigateToRepository(repository) + } catch { + appState.presentRepositoryDeepLinkError() + } + } + } } private struct InboxMessageRow: View { |
