From ac183daff8ffd64158fb5712f67daeacc68c1ba5 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Thu, 3 Sep 2026 20:46:48 +0300 Subject: [PATCH] Show message counts on room list notification badges --- .../Application/Settings/AppSettings.swift | 3 +++ .../Screens/HomeScreen/HomeScreenModels.swift | 6 ++++- .../HomeScreen/HomeScreenViewModel.swift | 4 ++++ .../HomeScreen/View/HomeScreenRoomCell.swift | 24 +++++++++++++++---- .../HomeScreen/View/HomeScreenRoomList.swift | 1 + .../DeveloperOptionsScreenModels.swift | 1 + .../View/DeveloperOptionsScreen.swift | 4 ++++ Enterprise | 2 +- ...oomCell.Notifications-State-iPad-en-GB.png | 4 ++-- ...omCell.Notifications-State-iPad-pseudo.png | 4 ++-- ...mCell.Notifications-State-iPhone-en-GB.png | 4 ++-- ...Cell.Notifications-State-iPhone-pseudo.png | 4 ++-- UnitTests/Sources/HomeScreenRoomTests.swift | 8 +++++++ 13 files changed, 55 insertions(+), 14 deletions(-) diff --git a/ElementX/Sources/Application/Settings/AppSettings.swift b/ElementX/Sources/Application/Settings/AppSettings.swift index c53f871ae..cbd955970 100644 --- a/ElementX/Sources/Application/Settings/AppSettings.swift +++ b/ElementX/Sources/Application/Settings/AppSettings.swift @@ -347,6 +347,9 @@ final nonisolated class AppSettings: @unchecked Sendable { @UserPreference(defaultValue: RoomListActivityVisibility.current) var roomListActivityVisibility: RoomListActivityVisibility + @UserPreference(defaultValue: false) + var roomListNotificationCountEnabled: Bool + // MARK: - Room Screen @UserPreference(defaultValue: AppBuildType.current == .debug) diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index bafe620cc..8c6bf9408 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -106,6 +106,8 @@ struct HomeScreenViewState: BindableState { var roomListActivityVisibility: RoomListActivityVisibility = .current + var roomListNotificationCountEnabled = false + var reportRoomEnabled = false var shouldShowSpaceFilters = false @@ -193,6 +195,7 @@ struct HomeScreenRoom: Identifiable, Equatable { let badges: Badges struct Badges: Equatable { let isDotShown: Bool + let notificationCount: UInt let isMentionShown: Bool let isMuteShown: Bool let callBadgeType: CallBadgeType @@ -237,7 +240,7 @@ struct HomeScreenRoom: Identifiable, Equatable { HomeScreenRoom(id: UUID().uuidString, roomID: nil, type: .placeholder, - badges: .init(isDotShown: false, isMentionShown: false, isMuteShown: false, callBadgeType: .none), + badges: .init(isDotShown: false, notificationCount: 0, isMentionShown: false, isMuteShown: false, callBadgeType: .none), name: "Placeholder room name", isDirect: false, isHighlighted: false, @@ -287,6 +290,7 @@ extension HomeScreenRoom { roomID: summary.id, type: type, badges: .init(isDotShown: isDotShown, + notificationCount: summary.unreadNotificationsCount, isMentionShown: isMentionShown, isMuteShown: isMuteShown, callBadgeType: callBadge), diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index d4e323aff..6e4e79855 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -120,6 +120,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol } .store(in: &cancellables) + appSettings.roomListNotificationCountEnabledPublisher + .weakAssign(to: \.state.roomListNotificationCountEnabled, on: self) + .store(in: &cancellables) + appSettings.seenInvitesPublisher .removeDuplicates() .sink { [weak self] _ in diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift index 667c7f831..62431afe3 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift @@ -16,6 +16,7 @@ struct HomeScreenRoomCell: View { let room: HomeScreenRoom var roomListActivityVisibility: RoomListActivityVisibility = .current + var roomListNotificationCountEnabled = false let isSelected: Bool let mediaProvider: MediaProviderProtocol! let action: (HomeScreenViewAction) -> Void @@ -157,9 +158,20 @@ struct HomeScreenRoomCell: View { } if room.badges.isDotShown { - Circle() - .frame(width: 12, height: 12) - .accessibilityLabel(L10n.a11yNotificationsNewMessages) + if roomListNotificationCountEnabled, room.isHighlighted, room.badges.notificationCount > 0 { + Text(formattedNotificationCount) + .font(.compound.bodySMSemibold) + .foregroundColor(.compound.textOnSolidPrimary) + .lineLimit(1) + .padding(.horizontal, 6) + .frame(minWidth: 20, minHeight: 20) + .background(.compound.iconAccentTertiary, in: .capsule) + .accessibilityLabel(L10n.a11yNotificationsNewMessages) + } else { + Circle() + .frame(width: 12, height: 12) + .accessibilityLabel(L10n.a11yNotificationsNewMessages) + } } } .foregroundColor(room.isHighlighted ? .compound.iconAccentTertiary : .compound.iconQuaternary) @@ -171,6 +183,10 @@ struct HomeScreenRoomCell: View { .accessibilityLabel(L10n.a11yNotificationsNewMentions) } + private var formattedNotificationCount: String { + room.badges.notificationCount > 99 ? "99+" : "\(room.badges.notificationCount)" + } + @ViewBuilder private var lastMessage: some View { if let displayedLastMessage = room.displayedLastMessage { @@ -239,7 +255,7 @@ struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview { VStack(spacing: 0) { ForEach(notificationsStateRooms) { room in - HomeScreenRoomCell(room: room, isSelected: false, mediaProvider: MediaProviderMock(.init())) { _ in } + HomeScreenRoomCell(room: room, roomListNotificationCountEnabled: true, isSelected: false, mediaProvider: MediaProviderMock(.init())) { _ in } } } .previewLayout(.sizeThatFits) diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomList.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomList.swift index e95e97b1b..334beb012 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomList.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomList.swift @@ -37,6 +37,7 @@ struct HomeScreenRoomList: View { HomeScreenRoomCell(room: room, roomListActivityVisibility: context.viewState.roomListActivityVisibility, + roomListNotificationCountEnabled: context.viewState.roomListNotificationCountEnabled, isSelected: isSelected, mediaProvider: context.mediaProvider, action: context.send) diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index 9e4234cc5..d68323d2d 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -59,6 +59,7 @@ protocol DeveloperOptionsProtocol: AnyObject { var automaticBackPaginationEnabled: Bool { get set } var roomListActivityVisibility: RoomListActivityVisibility { get set } + var roomListNotificationCountEnabled: Bool { get set } var elementCallBaseURLOverride: URL? { get set } var fuzzyRoomListSearchEnabled: Bool { get set } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index 3be807e53..191b8068e 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -67,6 +67,10 @@ struct DeveloperOptionsScreen: View { } } + Toggle(isOn: $context.roomListNotificationCountEnabled) { + Text("Show unread notification count") + } + Toggle(isOn: $context.fuzzyRoomListSearchEnabled) { Text("Fuzzy searching") } diff --git a/Enterprise b/Enterprise index fc45cdfbc..30d2ad29a 160000 --- a/Enterprise +++ b/Enterprise @@ -1 +1 @@ -Subproject commit fc45cdfbcfad33ea42abf9c0154126565df3ae53 +Subproject commit 30d2ad29a8b75e40a245a60e11db1183b352302a diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Notifications-State-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Notifications-State-iPad-en-GB.png index b9ad722d0..fee9853e1 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Notifications-State-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Notifications-State-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:050527f53b7afa77b75c456a54d6e98d15fe422f3f36921f09431fb1255dbcd6 -size 783268 +oid sha256:7ad7111dc38ca268615888915548c5c85cef835f9b20d2d79b71f9445f9dd918 +size 786479 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Notifications-State-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Notifications-State-iPad-pseudo.png index 974ada314..a93afa536 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Notifications-State-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Notifications-State-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f8cae65c6e8a501caaf2b3fcd07b48d2714c44cf7b9efa17fabffc108a32b71 -size 788915 +oid sha256:2f555cef6b6514ac6dda6604cf93bbf73f5b74712324e29d926d94354026fe1b +size 792039 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Notifications-State-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Notifications-State-iPhone-en-GB.png index 7c676fc2c..cb78a54d1 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Notifications-State-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Notifications-State-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f25670c5b16a8b37845fdc90f471d4586fbbc3eacd732fd71007ed4391f7b5e -size 536811 +oid sha256:2c77c9c9db4bc2547567c185aecf3d6378d4f538941ee129ab592b4d749266f8 +size 539834 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Notifications-State-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Notifications-State-iPhone-pseudo.png index 31a340341..c444adc29 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Notifications-State-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/homeScreenRoomCell.Notifications-State-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58d40c544df548edd120862e403d2eb61c8af11e213696b22a17e92feaa45c55 -size 540360 +oid sha256:32d421dbf919a73112b48a5fa876447ecd8842df65c6a4f21e91cbf2c9bb8c58 +size 543432 diff --git a/UnitTests/Sources/HomeScreenRoomTests.swift b/UnitTests/Sources/HomeScreenRoomTests.swift index 5d6117864..84cf06100 100644 --- a/UnitTests/Sources/HomeScreenRoomTests.swift +++ b/UnitTests/Sources/HomeScreenRoomTests.swift @@ -60,6 +60,7 @@ struct HomeScreenRoomTests { #expect(!room.isHighlighted) #expect(!room.badges.isDotShown) + #expect(room.badges.notificationCount == 0) #expect(room.badges.callBadgeType == .none) #expect(!room.badges.isMuteShown) #expect(!room.badges.isMentionShown) @@ -78,6 +79,7 @@ struct HomeScreenRoomTests { #expect(room.isHighlighted) #expect(room.badges.isDotShown) + #expect(room.badges.notificationCount == 5) #expect(room.badges.callBadgeType == .video) #expect(!room.badges.isMuteShown) #expect(room.badges.isMentionShown) @@ -111,6 +113,7 @@ struct HomeScreenRoomTests { #expect(!room.isHighlighted) #expect(room.badges.isDotShown) + #expect(room.badges.notificationCount == 0) #expect(room.badges.callBadgeType == .none) #expect(!room.badges.isMuteShown) #expect(!room.badges.isMentionShown) @@ -129,6 +132,7 @@ struct HomeScreenRoomTests { #expect(room.isHighlighted) #expect(room.badges.isDotShown) + #expect(room.badges.notificationCount == 5) #expect(room.badges.callBadgeType == .none) #expect(!room.badges.isMuteShown) #expect(!room.badges.isMentionShown) @@ -147,6 +151,7 @@ struct HomeScreenRoomTests { #expect(room.isHighlighted) #expect(room.badges.isDotShown) + #expect(room.badges.notificationCount == 0) #expect(room.badges.callBadgeType == .none) #expect(!room.badges.isMuteShown) #expect(room.badges.isMentionShown) @@ -221,6 +226,7 @@ struct HomeScreenRoomTests { #expect(room.isHighlighted) #expect(room.badges.isDotShown) + #expect(room.badges.notificationCount == 0) #expect(room.badges.callBadgeType == .none) #expect(!room.badges.isMuteShown) #expect(!room.badges.isMentionShown) @@ -239,6 +245,7 @@ struct HomeScreenRoomTests { #expect(room.isHighlighted) #expect(room.badges.isDotShown) + #expect(room.badges.notificationCount == 0) #expect(room.badges.callBadgeType == .none) #expect(!room.badges.isMuteShown) #expect(room.badges.isMentionShown) @@ -257,6 +264,7 @@ struct HomeScreenRoomTests { #expect(room.isHighlighted) #expect(room.badges.isDotShown) + #expect(room.badges.notificationCount == 5) #expect(room.badges.callBadgeType == .video) #expect(room.badges.isMuteShown) #expect(!room.badges.isMentionShown)