Clear the app badge when the inbox is read !119

merged merged by cmc on 2026-09-21 01:25 UTC · krz/gitbay-ios:badge-clear into main

3 files changed, +32 −1

Layout: unified · split

gitbay/Account/NotificationsViewModel.swift +7
@@ -124,6 +124,13 @@ final class NotificationsViewModel {
124 124
125 func markAllRead() async { 125 func markAllRead() async {
126 await perform(["notifications", "read", "--all"]) 126 await perform(["notifications", "read", "--all"])
127 // Nothing else tells iOS the count dropped: a push sets the badge
128 // on arrival and no one lowers it, so it would sit at whatever the
129 // last one said. Zero is exact here. Marking a single row read has
130 // no reliable count to work from — the inbox list is paged, so the
131 // rows in hand are not the unread total — and is left to the
132 // dashboard's resync.
133 await setAppBadge(0)
127 } 134 }
128 135
129 func loadPushSettings() async { 136 func loadPushSettings() async {
gitbay/Dashboard/DashboardViewModel.swift +6 −1
@@ -19,7 +19,12 @@ final class DashboardViewModel {
19 19
20 func load() async { 20 func load() async {
21 do { 21 do {
22 state = .loaded(try await client.read(["dashboard"], as: DashboardData.self)) 22 let data = try await client.read(["dashboard"], as: DashboardData.self)
23 state = .loaded(data)
24 // The authoritative resync. A push sets the badge on arrival
25 // and nothing lowers it afterwards, so this is what corrects
26 // it — on every app open, against the account you are in.
27 await setAppBadge(data.unread)
23 } catch { 28 } catch {
24 state = .from(error) 29 state = .from(error)
25 } 30 }
gitbay/Push/AppBadge.swift added +19
@@ -0,0 +1,19 @@
1import UserNotifications
2
3/// Sets the app icon badge to an unread count.
4///
5/// A push carries `aps.badge` and iOS applies it on arrival, so the badge
6/// is right the moment a notification lands. It is not right afterwards:
7/// nothing tells iOS the count dropped when the inbox is read in the app,
8/// and the number sits there until the next push. These calls close that.
9///
10/// The value is always the active account's unread count. The badge is
11/// per-device and unread is per-account, so with two accounts signed in
12/// the last writer wins — a push for one account, then a dashboard load
13/// on the other, disagree. Exact for a single account.
14///
15/// Failures are ignored. A badge that did not take is not worth telling
16/// anyone about, and the next writer corrects it.
17func setAppBadge(_ count: Int) async {
18 try? await UNUserNotificationCenter.current().setBadgeCount(max(0, count))
19}