Add a shared NotificationCountBadge view

Extract the unread notification count capsule from the room list cell into
its own view so it can be reused elsewhere. It caps the displayed value at
99+ and comes in a medium size (used by the room list) and a small size.
This commit is contained in:
Stefan Ceriu
2026-10-08 14:30:20 +03:00
parent 2182446684
commit 61fe2a8158
10 changed files with 77 additions and 11 deletions
@@ -407,6 +407,10 @@ extension AccessibilityTests {
try await performAccessibilityAudit(named: "NoticeRoomTimelineView_Previews")
}
func testNotificationCountBadge() async throws {
try await performAccessibilityAudit(named: "NotificationCountBadge_Previews")
}
func testNotificationPermissionsScreen() async throws {
try await performAccessibilityAudit(named: "NotificationPermissionsScreen_Previews")
}
+4
View File
@@ -266,6 +266,7 @@
288408E6151D7BD3EBAA073A /* RoomScreenHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = B343C5255FB408DDE853CFDF /* RoomScreenHook.swift */; };
28E8C44DD6E39BEB2A1599C8 /* AccessibilityTestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CABD320DE5566D133890B24 /* AccessibilityTestsAppCoordinator.swift */; };
291A3E0EECD496D0FCC2ADAA /* TimelineScrollButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4E14E3688FF3532456A656 /* TimelineScrollButton.swift */; };
293179E5CE24DDF1FE82A110 /* NotificationCountBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = E124365DC2141340F6F81A72 /* NotificationCountBadge.swift */; };
29491EE7AE37E239E839C5A3 /* LocationSharingScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BEBF0E59F25E842EDB6FD11 /* LocationSharingScreenModels.swift */; };
2955F4C160CFD7794D819C64 /* EffectsScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024F7398C5FC12586FB10E9D /* EffectsScene.swift */; };
298F9EC30E918F12AB7F1EE8 /* TypingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81F0325E252B057FAEEE1B2D /* TypingIndicatorView.swift */; };
@@ -3135,6 +3136,7 @@
E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = "<group>"; };
E0FF9CB3EFA753277291F609 /* EncryptionResetScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetScreenCoordinator.swift; sourceTree = "<group>"; };
E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileListRow.swift; sourceTree = "<group>"; };
E124365DC2141340F6F81A72 /* NotificationCountBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationCountBadge.swift; sourceTree = "<group>"; };
E157152B11E347F735C3FD6E /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = tr; path = tr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
E1573D28C8A9FB6399D0EEFB /* SecureBackupLogoutConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenCoordinator.swift; sourceTree = "<group>"; };
E1714DD78BFE27ADD40A6F89 /* LinkNewDeviceScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreenViewModelTests.swift; sourceTree = "<group>"; };
@@ -4191,6 +4193,7 @@
C352359663A0E52BA20761EE /* LoadableImage.swift */,
FFECCE59967018204876D0A5 /* LocationMarkerView.swift */,
50E31AB0E77BB70E2BC77463 /* MatrixUserShareLink.swift */,
E124365DC2141340F6F81A72 /* NotificationCountBadge.swift */,
648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */,
C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */,
95547A6C69606B5282C765D3 /* RemoveItemButton.swift */,
@@ -9245,6 +9248,7 @@
7F825CBD857D65DC986087BA /* NoticeRoomTimelineView.swift in Sources */,
3F70E237CE4C3FAB02FC227F /* NotificationConstants.swift in Sources */,
5500904C40D67D131E7E9A26 /* NotificationContentBuilder.swift in Sources */,
293179E5CE24DDF1FE82A110 /* NotificationCountBadge.swift in Sources */,
28426806399E3659BEBC5918 /* NotificationHandler.swift in Sources */,
E6A2F6E4795C1054025AACFF /* NotificationItemProxy.swift in Sources */,
9312F5A17AE59A9E910C51D6 /* NotificationItemProxyMock.swift in Sources */,
@@ -0,0 +1,48 @@
//
// Copyright 2026 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import Compound
import SwiftUI
/// A capsule showing an unread notification count, capped at 99+.
struct NotificationCountBadge: View {
enum Size {
case small
case medium
}
let count: Int
var size: Size = .medium
var body: some View {
Text(count > 99 ? "99+" : "\(count)")
.font(size == .small ? .compound.bodyXSSemibold : .compound.bodySMSemibold)
.foregroundStyle(.compound.textOnSolidPrimary)
.lineLimit(1)
.padding(.horizontal, size == .small ? 4 : 6)
.frame(minWidth: size == .small ? 16 : 20, minHeight: size == .small ? 16 : 20)
.background(.compound.iconAccentPrimary, in: .capsule)
}
}
// MARK: - Previews
struct NotificationCountBadge_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
VStack(spacing: 8) {
ForEach([Size.small, .medium], id: \.self) { size in
HStack(spacing: 8) {
NotificationCountBadge(count: 1, size: size)
NotificationCountBadge(count: 42, size: size)
NotificationCountBadge(count: 100, size: size)
}
}
}
}
private typealias Size = NotificationCountBadge.Size
}
@@ -106,6 +106,7 @@ enum TestablePreviewsDictionary {
"ModerationAndSafetySettingsScreen_Previews" : ModerationAndSafetySettingsScreen_Previews.self,
"MultiAccountAnnouncementView_Previews" : MultiAccountAnnouncementView_Previews.self,
"NoticeRoomTimelineView_Previews" : NoticeRoomTimelineView_Previews.self,
"NotificationCountBadge_Previews" : NotificationCountBadge_Previews.self,
"NotificationPermissionsScreen_Previews" : NotificationPermissionsScreen_Previews.self,
"NotificationSettingsEditScreenRoomCell_Previews" : NotificationSettingsEditScreenRoomCell_Previews.self,
"NotificationSettingsEditScreen_Previews" : NotificationSettingsEditScreen_Previews.self,
@@ -146,13 +146,7 @@ struct HomeScreenRoomCell: View {
if room.badges.isDotShown {
if 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.iconAccentPrimary, in: .capsule)
NotificationCountBadge(count: Int(room.badges.notificationCount))
.accessibilityLabel(L10n.a11yNotificationsNewMessages)
} else {
Circle()
@@ -170,10 +164,6 @@ 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 {
@@ -711,6 +711,13 @@ extension PreviewTests {
}
}
@Test
func notificationCountBadge() async throws {
for (index, preview) in NotificationCountBadge_Previews._allPreviews.enumerated() {
try await assertSnapshots(matching: preview, step: index)
}
}
@Test
func notificationPermissionsScreen() async throws {
for (index, preview) in NotificationPermissionsScreen_Previews._allPreviews.enumerated() {
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e1b9fdaf8ac1653153e78faa59162c930af5e5f6d647293ea907840f436f97b8
size 73171
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cdd5c07929cc8ac34ba855e4143f2ab1c23bde0c0780acfdf885e50c486733dc
size 74808
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:09a997528760f172b9b41994487e309e8958e7dd2404585bc7535a9f0f0b160a
size 33306
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c058780bc218c210e7b1b12efd2838f332091103246cdebfb7a2f717dbfb2d9c
size 34183