diff --git a/Forji/Forji/App/AppDelegate.swift b/Forji/Forji/App/AppDelegate.swift index f17b9c1..dc96d90 100644 --- a/Forji/Forji/App/AppDelegate.swift +++ b/Forji/Forji/App/AppDelegate.swift @@ -59,6 +59,8 @@ extension AppDelegate: UNUserNotificationCenterDelegate { if response.actionIdentifier == "MARK_READ" { await markNotificationAsRead(userInfo: userInfo, serverURL: serverURL, username: username) } else { + // Opening the notification clears it, the same as tapping it inside the app. + await markNotificationAsRead(userInfo: userInfo, serverURL: serverURL, username: username) await MainActor.run { NavigationState.shared.navigateToNotifications(serverURL: serverURL, username: username) } diff --git a/Forji/Forji/Views/MergedNotificationsOverviewView.swift b/Forji/Forji/Views/MergedNotificationsOverviewView.swift index 6ae7a68..99b2e51 100644 --- a/Forji/Forji/Views/MergedNotificationsOverviewView.swift +++ b/Forji/Forji/Views/MergedNotificationsOverviewView.swift @@ -113,6 +113,9 @@ struct MergedNotificationsOverviewView: View { NavigationLink { destination } label: { MergedNotificationRow(notification: notification, instanceName: tagged.instanceName) } + .simultaneousGesture(TapGesture().onEnded { + Task { await markReadOnOpen(tagged) } + }) } else { MergedNotificationRow(notification: notification, instanceName: tagged.instanceName) } @@ -193,14 +196,22 @@ struct MergedNotificationsOverviewView: View { await setNotificationRead(tagged) } - private func setNotificationRead(_ tagged: TaggedItem) async { + /// Marks a notification read when the user opens it, mirroring Mail and News. + /// The row is updated in place rather than removed so navigation into the + /// detail view is not interrupted while the list is mutating. + private func markReadOnOpen(_ tagged: TaggedItem) async { + guard tagged.item.unread else { return } + await setNotificationRead(tagged, removeFromUnread: false) + } + + private func setNotificationRead(_ tagged: TaggedItem, removeFromUnread: Bool = true) async { guard let client = tagged.authService.client else { return } let service = NotificationService(client: client) let notification = tagged.item do { try await service.markAsRead(id: notification.id) withAnimation { - if statusFilter == "unread" { + if removeFromUnread, statusFilter == "unread" { pagination.items.removeAll { $0.id == tagged.id } } else if let index = pagination.items.firstIndex(where: { $0.id == tagged.id }) { let updated = NotificationThread( diff --git a/Forji/Forji/Views/NotificationsOverviewView.swift b/Forji/Forji/Views/NotificationsOverviewView.swift index 6c475ee..128041a 100644 --- a/Forji/Forji/Views/NotificationsOverviewView.swift +++ b/Forji/Forji/Views/NotificationsOverviewView.swift @@ -117,6 +117,9 @@ struct NotificationsOverviewView: View { NavigationLink { destination } label: { NotificationRow(notification: notification) } + .simultaneousGesture(TapGesture().onEnded { + Task { await markReadOnOpen(notification) } + }) } else { NotificationRow(notification: notification) } @@ -182,12 +185,20 @@ struct NotificationsOverviewView: View { await setNotificationRead(notification) } - private func setNotificationRead(_ notification: NotificationThread) async { + /// Marks a notification read when the user opens it, mirroring Mail and News. + /// The row is updated in place rather than removed so navigation into the + /// detail view is not interrupted while the list is mutating. + private func markReadOnOpen(_ notification: NotificationThread) async { + guard notification.unread else { return } + await setNotificationRead(notification, removeFromUnread: false) + } + + private func setNotificationRead(_ notification: NotificationThread, removeFromUnread: Bool = true) async { guard let notificationService else { return } do { try await notificationService.markAsRead(id: notification.id) withAnimation { - if statusFilter == "unread" { + if removeFromUnread, statusFilter == "unread" { pagination.items.removeAll { $0.id == notification.id } } else if let index = pagination.items.firstIndex(where: { $0.id == notification.id }) { pagination.items[index] = NotificationThread(