Notification Decoration Revised (#5312)

1. I removed the grey dots for when there is activity in rooms set to mentions or mute.
2. For all unread rooms, I made the message preview text bold for better legibility
3. For rooms with no unreads, I made the font weight regular, again for legibility

We have also mergeg the new room list activity rendering with the existing feature hide unread badge feature flag and provide 3 variations: current production behavior, no badge but bolding, and no bolding and no badge
This commit is contained in:
amshak
2026-04-21 12:12:07 +01:00
committed by GitHub
parent 5b7ee5c455
commit 2a6f94ff48
36 changed files with 141 additions and 84 deletions
@@ -84,7 +84,7 @@ final class AppSettings {
case automaticBackPaginationEnabled
// Doug's tweaks 🔧
case hideUnreadMessagesBadge
case roomListActivityVisibility
case hideQuietNotificationAlerts
case developerOptionsEnabled
@@ -358,8 +358,8 @@ final class AppSettings {
// MARK: - Home Screen
@UserPreference(key: UserDefaultsKeys.hideUnreadMessagesBadge, defaultValue: false, storageType: .userDefaults(store))
var hideUnreadMessagesBadge
@UserPreference(key: UserDefaultsKeys.roomListActivityVisibility, defaultValue: .current, storageType: .userDefaults(store))
var roomListActivityVisibility: RoomListActivityVisibility
// MARK: - Room Screen
@@ -0,0 +1,15 @@
//
// 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.
//
enum RoomListActivityVisibility: String, CaseIterable, Codable {
/// Show unread badges for all unread messages (the default).
case current
/// Don't show badges but bold unread room names and their latest message
case show
/// Don't show badges and don't bold room names and messages
case hide
}
@@ -108,6 +108,8 @@ struct HomeScreenViewState: BindableState {
var hideInviteAvatars = false
var roomListActivityVisibility: RoomListActivityVisibility = .current
var reportRoomEnabled = false
var shouldShowSpaceFilters = false
@@ -197,6 +199,8 @@ struct HomeScreenRoom: Identifiable, Equatable {
let callBadgeType: CallBadgeType
}
var hasUnreads = false
let name: String
let isDirect: Bool
@@ -247,13 +251,20 @@ struct HomeScreenRoom: Identifiable, Equatable {
}
extension HomeScreenRoom {
init(summary: RoomSummary, hideUnreadMessagesBadge: Bool, seenInvites: Set<String> = []) {
init(summary: RoomSummary,
roomListActivityVisibility: RoomListActivityVisibility = .current,
seenInvites: Set<String> = []) {
let roomID = summary.id
let hasUnreadMessages = hideUnreadMessagesBadge ? false : summary.hasUnreadMessages
let isUnseenInvite = summary.joinRequestType?.isInvite == true && !seenInvites.contains(roomID)
let isDotShown = hasUnreadMessages || summary.hasUnreadMentions || summary.hasUnreadNotifications || summary.isMarkedUnread || isUnseenInvite
let isDotShown = switch roomListActivityVisibility {
case .current:
summary.hasUnreadMessages || summary.hasUnreadMentions || summary.hasUnreadNotifications || summary.isMarkedUnread || isUnseenInvite
case .hide, .show:
(!summary.isMuted && (summary.hasUnreadNotifications || summary.hasUnreadMentions)) || summary.isMarkedUnread || isUnseenInvite
}
let isMentionShown = summary.hasUnreadMentions && !summary.isMuted
let isMuteShown = summary.isMuted
let isHighlighted = summary.isMarkedUnread || (!summary.isMuted && (summary.hasUnreadNotifications || summary.hasUnreadMentions)) || isUnseenInvite
@@ -275,6 +286,7 @@ extension HomeScreenRoom {
isMentionShown: isMentionShown,
isMuteShown: isMuteShown,
callBadgeType: callBadge),
hasUnreads: summary.hasUnreadMessages,
name: summary.name,
isDirect: summary.isDirect,
isHighlighted: isHighlighted,
@@ -114,8 +114,11 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
.weakAssign(to: \.state.selectedRoomID, on: self)
.store(in: &cancellables)
appSettings.$hideUnreadMessagesBadge
.sink { [weak self] _ in self?.updateRooms() }
appSettings.$roomListActivityVisibility
.sink { [weak self] value in
self?.state.roomListActivityVisibility = value
self?.updateRooms()
}
.store(in: &cancellables)
appSettings.$seenInvites
@@ -363,7 +366,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
for summary in roomSummaryProvider.roomListPublisher.value {
let room = HomeScreenRoom(summary: summary,
hideUnreadMessagesBadge: appSettings.hideUnreadMessagesBadge,
roomListActivityVisibility: appSettings.roomListActivityVisibility,
seenInvites: seenInvites)
rooms.append(room)
}
@@ -240,7 +240,7 @@ private extension HomeScreenRoom {
isFavourite: false,
isTombstoned: false)
return .init(summary: summary, hideUnreadMessagesBadge: false)
return .init(summary: summary)
}
static func invite(name: String = "Awesome Room",
@@ -276,6 +276,6 @@ private extension HomeScreenRoom {
isFavourite: false,
isTombstoned: false)
return .init(summary: summary, hideUnreadMessagesBadge: false)
return .init(summary: summary)
}
}
@@ -172,7 +172,7 @@ private extension HomeScreenRoom {
isFavourite: false,
isTombstoned: false)
return .init(summary: summary, hideUnreadMessagesBadge: false)
return .init(summary: summary)
}
static func roomKnocked(alias: String? = nil, avatarURL: URL? = nil) -> HomeScreenRoom {
@@ -205,6 +205,6 @@ private extension HomeScreenRoom {
isFavourite: false,
isTombstoned: false)
return .init(summary: summary, hideUnreadMessagesBadge: false)
return .init(summary: summary)
}
}
@@ -15,6 +15,7 @@ struct HomeScreenRoomCell: View {
@Environment(\.redactionReasons) private var redactionReasons
let room: HomeScreenRoom
var roomListActivityVisibility: RoomListActivityVisibility = .current
let isSelected: Bool
let mediaProvider: MediaProviderProtocol!
let action: (HomeScreenViewAction) -> Void
@@ -74,7 +75,7 @@ struct HomeScreenRoomCell: View {
private var header: some View {
HStack(alignment: .top, spacing: 16) {
Text(room.name)
.font(.compound.bodyLGSemibold)
.font(headerFont)
.foregroundColor(.compound.textPrimary)
.lineLimit(1)
.frame(maxWidth: .infinity, alignment: .leading)
@@ -87,6 +88,17 @@ struct HomeScreenRoomCell: View {
}
}
private var headerFont: Font {
switch roomListActivityVisibility {
case .current:
.compound.bodyLGSemibold
case .show:
room.hasUnreads ? .compound.bodyLGSemibold : .compound.bodyLG
case .hide:
.compound.bodyLG
}
}
private var footer: some View {
HStack(alignment: .firstTextBaseline, spacing: 0) {
ZStack(alignment: .topLeading) {
@@ -157,6 +169,7 @@ struct HomeScreenRoomCell: View {
private var lastMessage: some View {
if let displayedLastMessage = room.displayedLastMessage {
Text(displayedLastMessage)
.font(roomListActivityVisibility == .show ? (room.hasUnreads ? .compound.bodyMDSemibold : .compound.bodyMD) : .compound.bodyMD)
.lastMessageFormatting(hasFailed: room.lastMessageState == .failed)
}
}
@@ -175,8 +188,7 @@ struct HomeScreenRoomCellButtonStyle: ButtonStyle {
private extension View {
func lastMessageFormatting(hasFailed: Bool) -> some View {
font(.compound.bodyMD)
.foregroundColor(hasFailed ? .compound.textCriticalPrimary : .compound.textSecondary)
foregroundColor(hasFailed ? .compound.textCriticalPrimary : .compound.textSecondary)
.lineLimit(2)
.multilineTextAlignment(.leading)
}
@@ -224,7 +236,7 @@ struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview {
}
static func mockRoom(summary: RoomSummary) -> HomeScreenRoom? {
HomeScreenRoom(summary: summary, hideUnreadMessagesBadge: false)
HomeScreenRoom(summary: summary)
}
static func makeViewModel(roomSummaryProvider: RoomSummaryProviderProtocol) -> HomeScreenViewModel {
@@ -263,6 +275,6 @@ struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview {
isFavourite: false,
isTombstoned: false)
return .init(summary: summary, hideUnreadMessagesBadge: false)
return .init(summary: summary)
}
}
@@ -35,7 +35,11 @@ struct HomeScreenRoomList: View {
case .room:
let isSelected = context.viewState.selectedRoomID == room.id
HomeScreenRoomCell(room: room, isSelected: isSelected, mediaProvider: context.mediaProvider, action: context.send)
HomeScreenRoomCell(room: room,
roomListActivityVisibility: context.viewState.roomListActivityVisibility,
isSelected: isSelected,
mediaProvider: context.mediaProvider,
action: context.send)
.simultaneousGesture(TapGesture(count: 2).onEnded {
context.send(viewAction: .detachRoom(roomIdentifier: room.id))
})
@@ -57,7 +57,7 @@ protocol DeveloperOptionsProtocol: AnyObject {
var focusEventOnNotificationTap: Bool { get set }
var automaticBackPaginationEnabled: Bool { get set }
var hideUnreadMessagesBadge: Bool { get set }
var roomListActivityVisibility: RoomListActivityVisibility { get set }
var elementCallBaseURLOverride: URL? { get set }
var publicSearchEnabled: Bool { get set }
@@ -56,8 +56,11 @@ struct DeveloperOptionsScreen: View {
Text("Public search")
}
Toggle(isOn: $context.hideUnreadMessagesBadge) {
Text("Hide grey dots")
Picker("Room list activity visibility", selection: $context.roomListActivityVisibility) {
ForEach(RoomListActivityVisibility.allCases, id: \.self) { visibility in
Text(visibility.rawValue.capitalized)
.tag(visibility)
}
}
Toggle(isOn: $context.fuzzyRoomListSearchEnabled) {