feat: Mark All as Read — bulk action with optimistic UI and rollback
APIClient: - markAllRead(storyIds💊) — POST /me/read/bulk with device ID, timestamp, source label, and pill name so backend can record context Models: - PaginatedStories gains unreadCount, readSuppressedCount, lastReadSyncAt, activePill — backend now returns these so the UI can report honest state SignalFeedView: - Header "…" menu replaces the standalone settings button; menu contains "Mark all as read" (disabled while pending or when nothing is unread) and a Settings item - markAllRead() captures all visible unread IDs, optimistically inserts ReadStory for each, shows toast ("Marked N stories as read"), then fires the bulk API; on failure deletes the optimistic ReadStory records and shows an honest error toast ("Sync failed. Stories may reappear until retry.") - Toast auto-dismisses after 3.5s, appears at the bottom of the ZStack with a slide-up + opacity animation Cross-pill suppression was already in place — backend applies unread_story_condition() to every /stories request regardless of tags, so stories marked read in All disappear from Tech, Sport, etc. on the next pill fetch. Co-Authored-By: Kutesir <tqwyy79vzn@privaterelay.appleid.com>
This commit is contained in:
@@ -111,6 +111,16 @@ actor APIClient {
|
||||
try await delete("/stories/\(id)/read")
|
||||
}
|
||||
|
||||
func markAllRead(storyIds: [String], pill: String) async throws {
|
||||
try await postVoid("/me/read/bulk", body: [
|
||||
"storyIds": storyIds,
|
||||
"readAt": ISO8601DateFormatter().string(from: Date()),
|
||||
"source": "mark_all_as_read",
|
||||
"pill": pill,
|
||||
"deviceId": deviceId,
|
||||
])
|
||||
}
|
||||
|
||||
// MARK: - Articles
|
||||
|
||||
func fetchArticle(id: String) async throws -> Article {
|
||||
|
||||
Reference in New Issue
Block a user