Update the app badge and clear read notifications on room list updates

The sync update action is sent from the room list service state listener,
which fires before the SDK has applied the new unread counts, leaving the
app badge one sync behind. Drive both the badge and the clearing of
delivered notifications for fully read rooms from the room list publisher
instead, moving the observer from the ChatsTabFlowCoordinator into the
UserSessionFlowCoordinator.

The NotificationManager now receives the badge count from the caller so the
same value can be reused elsewhere.
This commit is contained in:
Stefan Ceriu
2026-10-08 14:30:28 +03:00
parent 61fe2a8158
commit ab39d8de06
6 changed files with 39 additions and 18 deletions
@@ -337,12 +337,6 @@ class ChatsTabFlowCoordinator: FlowCoordinatorProtocol {
switch action {
case .receivedDecryptionError(let info):
processDecryptionError(info)
case .receivedSyncUpdate:
Task {
let roomSummaries = self.userSession.clientProxy.staticRoomSummaryProvider.roomListPublisher.value
await self.flowParameters.notificationManager.removeDeliveredNotificationsForFullyReadRooms(roomSummaries)
await self.flowParameters.notificationManager.updateAppBadgeCount()
}
default:
break
}
@@ -317,6 +317,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
.store(in: &cancellables)
setupCallObservers()
setupUnreadNotificationsObserver()
searchScreenCoordinator?.actionsPublisher
.sink { [weak self] action in
@@ -530,6 +531,22 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
navigationTabCoordinator.setOverlayPresentationMode(.minimized)
}
private func setupUnreadNotificationsObserver() {
let roomListPublisher = userSession.clientProxy.staticRoomSummaryProvider.roomListPublisher
Task { [weak self] in
for await roomSummaries in roomListPublisher.values {
guard let self else { return }
let badgeCount = Int(userSession.clientProxy.totalUnreadNotifications)
await flowParameters.notificationManager.removeDeliveredNotificationsForFullyReadRooms(roomSummaries)
await flowParameters.notificationManager.updateAppBadgeCount(badgeCount)
}
}
.store(in: &cancellables)
}
// MARK: - Native calls
private func setupCallObservers() {
@@ -9039,11 +9039,25 @@ nonisolated class NotificationManagerMock: NotificationManagerProtocol, @uncheck
var updateAppBadgeCountCalled: Bool {
return updateAppBadgeCountCallsCount > 0
}
nonisolated(unsafe) var updateAppBadgeCountClosure: (() async -> Void)?
private let updateAppBadgeCountReceivedBadgeCountLock = NSLock()
private nonisolated(unsafe) var updateAppBadgeCountUnderlyingReceivedBadgeCount: Int?
var updateAppBadgeCountReceivedBadgeCount: Int? {
get { updateAppBadgeCountReceivedBadgeCountLock.withLock { updateAppBadgeCountUnderlyingReceivedBadgeCount } }
set { updateAppBadgeCountReceivedBadgeCountLock.withLock { updateAppBadgeCountUnderlyingReceivedBadgeCount = newValue } }
}
private let updateAppBadgeCountReceivedInvocationsLock = NSLock()
private nonisolated(unsafe) var updateAppBadgeCountUnderlyingReceivedInvocations: [Int] = []
var updateAppBadgeCountReceivedInvocations: [Int] {
get { updateAppBadgeCountReceivedInvocationsLock.withLock { updateAppBadgeCountUnderlyingReceivedInvocations } }
set { updateAppBadgeCountReceivedInvocationsLock.withLock { updateAppBadgeCountUnderlyingReceivedInvocations = newValue } }
}
nonisolated(unsafe) var updateAppBadgeCountClosure: ((Int) async -> Void)?
@concurrent func updateAppBadgeCount() async {
@concurrent func updateAppBadgeCount(_ badgeCount: Int) async {
updateAppBadgeCountCallsCountLock.withLock { updateAppBadgeCountUnderlyingCallsCount += 1 }
await updateAppBadgeCountClosure?()
updateAppBadgeCountReceivedBadgeCount = badgeCount
updateAppBadgeCountReceivedInvocationsLock.withLock { updateAppBadgeCountUnderlyingReceivedInvocations.append(badgeCount) }
await updateAppBadgeCountClosure?(badgeCount)
}
}
nonisolated class NotificationSettingsProxyMock: NotificationSettingsProxyProtocol, @unchecked Sendable {
@@ -161,11 +161,9 @@ final class NotificationManager: NSObject, NotificationManagerProtocol {
notificationCenter.removeDeliveredNotifications(withIdentifiers: notificationsIdentifiers)
}
func updateAppBadgeCount() async {
func updateAppBadgeCount(_ badgeCount: Int) async {
guard let userSession else { return }
let badgeCount = Int(userSession.clientProxy.totalUnreadNotifications)
appSettings.lastKnownBadgeCount = badgeCount
MXLog.debug("Updating app badge count to \(badgeCount)")
@@ -37,5 +37,5 @@ protocol NotificationManagerProtocol: AnyObject {
func removeDeliveredNotificationsForFullyReadRooms(_ rooms: [RoomSummary]) async
func updateAppBadgeCount() async
func updateAppBadgeCount(_ badgeCount: Int) async
}
@@ -262,10 +262,8 @@ final class NotificationManagerTests {
}
@Test
func updatingAppBadgeCountUsesTheClientSideCount() async {
clientProxy.totalUnreadNotifications = 7
await notificationManager.updateAppBadgeCount()
func updatingAppBadgeCount() async {
await notificationManager.updateAppBadgeCount(7)
#expect(notificationCenter.setBadgeCountReceivedNewBadgeCount == 7)
#expect(appSettings.lastKnownBadgeCount == 7)
@@ -275,7 +273,7 @@ final class NotificationManagerTests {
func updatingAppBadgeCountWithoutASessionDoesNothing() async {
notificationManager.setUserSession(nil)
await notificationManager.updateAppBadgeCount()
await notificationManager.updateAppBadgeCount(7)
#expect(!notificationCenter.setBadgeCountCalled)
}