summaryrefslogtreecommitdiff
path: root/Hutch
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-01 12:15:23 -0500
committerChristian Cleberg <[email protected]>2026-04-01 12:15:23 -0500
commit109b0ea7e859de2d8b5a4dc22bf0472fd3b08eaa (patch)
treeceb604cbf07102b3b386a60fd48a9dc2576e3684 /Hutch
parent367a9d8e9592d0c604356e74edea97e0791b32cd (diff)
downloadhutch-109b0ea7e859de2d8b5a4dc22bf0472fd3b08eaa.tar.gz
hutch-109b0ea7e859de2d8b5a4dc22bf0472fd3b08eaa.tar.bz2
hutch-109b0ea7e859de2d8b5a4dc22bf0472fd3b08eaa.zip
fix: fixed inbox's inconsistent UX and adds a Mark All Read button
Fixes: https://todo.sr.ht/~ccleberg/Hutch/10
Diffstat (limited to 'Hutch')
-rw-r--r--Hutch/Views/Home/HomeView.swift28
-rw-r--r--Hutch/Views/Inbox/InboxView.swift36
-rw-r--r--Hutch/Views/Inbox/InboxViewModel.swift16
3 files changed, 60 insertions, 20 deletions
diff --git a/Hutch/Views/Home/HomeView.swift b/Hutch/Views/Home/HomeView.swift
index d3d370a..35e8f7c 100644
--- a/Hutch/Views/Home/HomeView.swift
+++ b/Hutch/Views/Home/HomeView.swift
@@ -26,11 +26,18 @@ struct HomeView: View {
}
}
.task {
- if viewModel == nil, let currentUser = appState.currentUser {
- let vm = HomeViewModel(currentUser: currentUser, client: appState.client)
- viewModel = vm
- await vm.loadDashboard()
+ guard let currentUser = appState.currentUser else { return }
+
+ let vm: HomeViewModel
+ if let viewModel {
+ vm = viewModel
+ } else {
+ let newViewModel = HomeViewModel(currentUser: currentUser, client: appState.client)
+ viewModel = newViewModel
+ vm = newViewModel
}
+
+ await vm.loadDashboard()
}
}
@@ -214,17 +221,8 @@ private struct HomeInboxToolbarIcon: View {
let hasUnreadThreads: Bool
var body: some View {
- ZStack(alignment: .topTrailing) {
- Image(systemName: hasUnreadThreads ? "tray.fill" : "tray")
-
- if hasUnreadThreads {
- Circle()
- .fill(.blue)
- .frame(width: 9, height: 9)
- .offset(x: 4, y: -2)
- }
- }
- .accessibilityLabel(hasUnreadThreads ? "Inbox, unread messages" : "Inbox")
+ Image(systemName: hasUnreadThreads ? "tray.fill" : "tray")
+ .accessibilityLabel(hasUnreadThreads ? "Inbox, unread messages" : "Inbox")
}
}
diff --git a/Hutch/Views/Inbox/InboxView.swift b/Hutch/Views/Inbox/InboxView.swift
index 5724964..6ca66ac 100644
--- a/Hutch/Views/Inbox/InboxView.swift
+++ b/Hutch/Views/Inbox/InboxView.swift
@@ -2,6 +2,7 @@ import SwiftUI
struct InboxView: View {
@Environment(AppState.self) private var appState
+ @Environment(\.scenePhase) private var scenePhase
@State private var viewModel: InboxViewModel?
@State private var selectedThreadID: InboxThreadSummary.ID?
@State private var selectedThreadSnapshot: InboxThreadSummary?
@@ -17,10 +18,21 @@ struct InboxView: View {
}
.navigationTitle("Inbox")
.task {
- if viewModel == nil {
- let vm = InboxViewModel(client: appState.client)
- viewModel = vm
- await vm.loadThreads()
+ let vm: InboxViewModel
+ if let viewModel {
+ vm = viewModel
+ } else {
+ let newViewModel = InboxViewModel(client: appState.client)
+ viewModel = newViewModel
+ vm = newViewModel
+ }
+
+ await vm.loadThreads()
+ }
+ .onChange(of: scenePhase) { _, newPhase in
+ guard newPhase == .active, let viewModel, !isShowingThreadDetail else { return }
+ Task {
+ await viewModel.loadThreads()
}
}
}
@@ -51,6 +63,20 @@ struct InboxView: View {
prompt: "Search inbox"
)
.listStyle(.plain)
+ .toolbar {
+ ToolbarItem(placement: .topBarTrailing) {
+ if viewModel.hasUnreadThreads {
+ Button("Mark All Read") {
+ withAnimation(.easeInOut(duration: 0.2)) {
+ viewModel.markAllThreadsRead()
+ }
+ Task {
+ await viewModel.loadThreads()
+ }
+ }
+ }
+ }
+ }
.overlay {
if viewModel.isLoading, viewModel.threads.isEmpty {
SRHTLoadingStateView(message: "Loading inbox…")
@@ -66,7 +92,7 @@ struct InboxView: View {
ContentUnavailableView(
"Inbox Zero",
systemImage: "tray",
- description: Text("Unread threads will appear here.")
+ description: Text("You're up to date.")
)
}
}
diff --git a/Hutch/Views/Inbox/InboxViewModel.swift b/Hutch/Views/Inbox/InboxViewModel.swift
index 1d4cfd3..edb7593 100644
--- a/Hutch/Views/Inbox/InboxViewModel.swift
+++ b/Hutch/Views/Inbox/InboxViewModel.swift
@@ -150,6 +150,18 @@ final class InboxViewModel {
NeedsAttentionSnapshotStore.adjustUnreadInboxThreads(by: -1)
}
+ func markAllThreadsRead() {
+ guard !threads.isEmpty else { return }
+
+ let viewedAt = Date()
+ for thread in threads where thread.isUnread {
+ InboxReadStateStore.markViewed(max(viewedAt, thread.lastActivityAt), for: thread.id)
+ }
+
+ threads.removeAll { $0.isUnread }
+ NeedsAttentionSnapshotStore.update(unreadInboxThreads: threads.count)
+ }
+
func markThreadUnread(_ thread: InboxThreadSummary) {
InboxReadStateStore.markUnread(for: thread.id)
updateThread(thread, isUnread: true)
@@ -178,6 +190,10 @@ final class InboxViewModel {
}
}
+ var hasUnreadThreads: Bool {
+ threads.contains(where: \.isUnread)
+ }
+
private func fetchSubscriptions() async throws -> [InboxActivitySubscription] {
var subscriptions: [InboxActivitySubscription] = []
var cursor: String?