From 025b3e4195d5d2eb14723144b5c2169873b3bbf8 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Wed, 7 Feb 2024 13:00:35 +0000 Subject: [PATCH] Add a setting to hide unread messages badges. (#2412) * Add a setting to hide grey unread messages badges. * Move room badge logic to be built in the view model instead of in the view. * Move setting into DeveloperOptions. --- .../Sources/Application/AppSettings.swift | 6 +++ .../Screens/HomeScreen/HomeScreenModels.swift | 54 ++++++++++++------- .../HomeScreen/HomeScreenViewModel.swift | 29 +++------- .../HomeScreen/View/HomeScreenRoomCell.swift | 44 +++++---------- .../HomeScreen/View/HomeScreenRoomList.swift | 2 +- .../DeveloperOptionsScreenModels.swift | 1 + .../View/DeveloperOptionsScreen.swift | 4 ++ .../NotificationSettingsScreenViewModel.swift | 2 +- .../View/NotificationSettingsScreen.swift | 5 ++ .../Room/RoomSummary/RoomSummaryDetails.swift | 5 ++ .../test_homeScreenRoomCell.Generic.png | 4 +- ...homeScreenRoomCell.Notifications-State.png | 4 +- 12 files changed, 85 insertions(+), 75 deletions(-) diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift index 05cbe3a0a..9e9c4f14e 100644 --- a/ElementX/Sources/Application/AppSettings.swift +++ b/ElementX/Sources/Application/AppSettings.swift @@ -38,6 +38,7 @@ final class AppSettings { case richTextEditorEnabled case appAppearance case sendReadReceiptsEnabled + case hideUnreadMessagesBadge case elementCallBaseURL case elementCallEncryptionEnabled @@ -212,6 +213,11 @@ final class AppSettings { @UserPreference(key: UserDefaultsKeys.analyticsConsentState, defaultValue: AnalyticsConsentState.unknown, storageType: .userDefaults(store)) var analyticsConsentState + // MARK: - Home Screen + + @UserPreference(key: UserDefaultsKeys.hideUnreadMessagesBadge, defaultValue: false, storageType: .userDefaults(store)) + var hideUnreadMessagesBadge + // MARK: - Room Screen @UserPreference(key: UserDefaultsKeys.timelineStyle, defaultValue: TimelineStyle.bubbles, storageType: .userDefaults(store)) diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index 17724e6dc..a0f66d905 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -157,15 +157,15 @@ struct HomeScreenRoom: Identifiable, Equatable { var name = "" - var isMarkedUnread: Bool + var badges: Badges + struct Badges: Equatable { + let isDotShown: Bool + let isMentionShown: Bool + let isMuteShown: Bool + let isCallShown: Bool + } - var hasUnreadMessages = false - - var hasUnreadMentions = false - - var hasUnreadNotifications = false - - var hasOngoingCall = false + let isHighlighted: Bool var timestamp: String? @@ -173,28 +173,46 @@ struct HomeScreenRoom: Identifiable, Equatable { var avatarURL: URL? - var notificationMode: RoomNotificationModeProxy? - var isPlaceholder = false - var hasNewContent: Bool { - hasUnreadMessages || hasUnreadMentions || hasUnreadNotifications || isMarkedUnread - } - static func placeholder() -> HomeScreenRoom { HomeScreenRoom(id: UUID().uuidString, roomId: nil, name: "Placeholder room name", - isMarkedUnread: false, - hasUnreadMessages: false, - hasUnreadMentions: false, - hasUnreadNotifications: false, + badges: .init(isDotShown: false, isMentionShown: false, isMuteShown: false, isCallShown: false), + isHighlighted: false, timestamp: "Now", lastMessage: placeholderLastMessage, isPlaceholder: true) } } +extension HomeScreenRoom { + init(details: RoomSummaryDetails, invalidated: Bool, hideUnreadMessagesBadge: Bool) { + let identifier = invalidated ? "invalidated-" + details.id : details.id + + let hasUnreadMessages = hideUnreadMessagesBadge ? false : details.hasUnreadMessages + + let isDotShown = hasUnreadMessages || details.hasUnreadMentions || details.hasUnreadNotifications || details.isMarkedUnread + let isMentionShown = details.hasUnreadMentions && !details.isMuted + let isMuteShown = details.isMuted + let isCallShown = details.hasOngoingCall + let isHighlighted = !details.isMuted && (details.hasUnreadNotifications || details.hasUnreadMentions || details.isMarkedUnread) + + self.init(id: identifier, + roomId: details.id, + name: details.name, + badges: .init(isDotShown: isDotShown, + isMentionShown: isMentionShown, + isMuteShown: isMuteShown, + isCallShown: isCallShown), + isHighlighted: isHighlighted, + timestamp: details.lastMessageFormattedTimestamp, + lastMessage: details.lastMessage, + avatarURL: details.avatarURL) + } +} + enum RoomListFilter: Int, CaseIterable, Identifiable { var id: Int { rawValue diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 6deff19bc..7a142c456 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -90,6 +90,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol .weakAssign(to: \.state.markAsUnreadEnabled, on: self) .store(in: &cancellables) + appSettings.$hideUnreadMessagesBadge + .sink { [weak self] _ in self?.updateRooms() } + .store(in: &cancellables) + let isSearchFieldFocused = context.$viewState.map(\.bindings.isSearchFieldFocused) let searchQuery = context.$viewState.map(\.bindings.searchQuery) isSearchFieldFocused @@ -339,11 +343,11 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol switch summary { case .empty: rooms.append(HomeScreenRoom.placeholder()) - case .invalidated(let details): - let room = buildRoom(with: details, invalidated: true) - rooms.append(room) case .filled(let details): - let room = buildRoom(with: details, invalidated: false) + let room = HomeScreenRoom(details: details, invalidated: false, hideUnreadMessagesBadge: appSettings.hideUnreadMessagesBadge) + rooms.append(room) + case .invalidated(let details): + let room = HomeScreenRoom(details: details, invalidated: true, hideUnreadMessagesBadge: appSettings.hideUnreadMessagesBadge) rooms.append(room) } } @@ -353,23 +357,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol MXLog.verbose("Finished updating rooms") } - private func buildRoom(with details: RoomSummaryDetails, invalidated: Bool) -> HomeScreenRoom { - let identifier = invalidated ? "invalidated-" + details.id : details.id - - return HomeScreenRoom(id: identifier, - roomId: details.id, - name: details.name, - isMarkedUnread: details.isMarkedUnread, - hasUnreadMessages: details.unreadMessagesCount > 0, - hasUnreadMentions: details.unreadMentionsCount > 0, - hasUnreadNotifications: details.unreadNotificationsCount > 0, - hasOngoingCall: details.hasOngoingCall, - timestamp: details.lastMessageFormattedTimestamp, - lastMessage: details.lastMessage, - avatarURL: details.avatarURL, - notificationMode: details.notificationMode) - } - private func updateVisibleRange(_ range: Range) { guard !range.isEmpty else { return diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift index ecd7d790d..59dab2349 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift @@ -95,8 +95,8 @@ struct HomeScreenRoomCell: View { if let timestamp = room.timestamp { Text(timestamp) - .font(isHighlighted ? .compound.bodySMSemibold : .compound.bodySM) - .foregroundColor(isHighlighted ? .compound.textActionAccent : .compound.textSecondary) + .font(room.isHighlighted ? .compound.bodySMSemibold : .compound.bodySM) + .foregroundColor(room.isHighlighted ? .compound.textActionAccent : .compound.textSecondary) } } } @@ -117,38 +117,30 @@ struct HomeScreenRoomCell: View { Spacer() HStack(spacing: 8) { - if room.hasOngoingCall { + if room.badges.isCallShown { CompoundIcon(\.videoCallSolid, size: .xSmall, relativeTo: .compound.bodySM) - .foregroundColor(isHighlighted ? .compound.iconAccentTertiary : .compound.iconQuaternary) + .foregroundColor(room.isHighlighted ? .compound.iconAccentTertiary : .compound.iconQuaternary) } - - if room.notificationMode == .mute { + + if room.badges.isMuteShown { CompoundIcon(\.notificationsOffSolid, size: .custom(15), relativeTo: .compound.bodyMD) .accessibilityLabel(L10n.a11yNotificationsMuted) .foregroundColor(.compound.iconQuaternary) } - if room.hasUnreadMentions, room.notificationMode != .mute { + if room.badges.isMentionShown { mentionIcon .foregroundColor(.compound.iconAccentTertiary) } - if room.hasNewContent { + if room.badges.isDotShown { Circle() .frame(width: 12, height: 12) - .foregroundColor(isHighlighted ? .compound.iconAccentTertiary : .compound.iconQuaternary) + .foregroundColor(room.isHighlighted ? .compound.iconAccentTertiary : .compound.iconQuaternary) } } } } - - private var isHighlighted: Bool { - guard !room.isPlaceholder && room.notificationMode != .mute else { - return false - } - - return room.hasUnreadNotifications || room.hasUnreadMentions || room.isMarkedUnread - } private var mentionIcon: some View { CompoundIcon(\.mention, size: .custom(15), relativeTo: .compound.bodyMD) @@ -212,19 +204,11 @@ struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview { static func mockRoom(summary: RoomSummary) -> HomeScreenRoom? { switch summary { case .empty: - return nil - case .invalidated(let details), .filled(let details): - return HomeScreenRoom(id: UUID().uuidString, - roomId: details.id, - name: details.name, - isMarkedUnread: details.isMarkedUnread, - hasUnreadMessages: details.unreadMessagesCount > 0, - hasUnreadMentions: details.unreadMentionsCount > 0, - hasUnreadNotifications: details.unreadNotificationsCount > 0, - hasOngoingCall: details.hasOngoingCall, - timestamp: Date(timeIntervalSinceReferenceDate: 0).formattedMinimal(), - lastMessage: details.lastMessage, - notificationMode: details.notificationMode) + nil + case .invalidated(let details): + HomeScreenRoom(details: details, invalidated: true, hideUnreadMessagesBadge: false) + case .filled(let details): + HomeScreenRoom(details: details, invalidated: false, hideUnreadMessagesBadge: false) } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomList.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomList.swift index f4d47e2df..538d9e5d3 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomList.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomList.swift @@ -48,7 +48,7 @@ struct HomeScreenRoomList: View { HomeScreenRoomCell(room: room, context: context, isSelected: isSelected) .contextMenu { if context.viewState.markAsUnreadEnabled { - if room.hasNewContent { + if room.badges.isDotShown { Button { context.send(viewAction: .markRoomAsRead(roomIdentifier: room.id)) } label: { diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index 3664fed0f..4966ec0a8 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -51,6 +51,7 @@ protocol DeveloperOptionsProtocol: AnyObject { var mentionsBadgeEnabled: Bool { get set } var roomListFiltersEnabled: Bool { get set } var markAsUnreadEnabled: Bool { get set } + var hideUnreadMessagesBadge: Bool { get set } var elementCallBaseURL: URL { get set } } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index 91b31c25e..8521a79a5 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -59,6 +59,10 @@ struct DeveloperOptionsScreen: View { Toggle(isOn: $context.markAsUnreadEnabled) { Text("Mark as unread") } + + Toggle(isOn: $context.hideUnreadMessagesBadge) { + Text("Hide grey dots") + } } Section("Element Call") { diff --git a/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenViewModel.swift index bfbe615f3..8082ddc65 100644 --- a/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/NotificationSettingsScreenViewModel.swift @@ -39,7 +39,7 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy let bindings = NotificationSettingsScreenViewStateBindings(enableNotifications: appSettings.enableNotifications) super.init(initialViewState: NotificationSettingsScreenViewState(bindings: bindings, isModallyPresented: isModallyPresented)) - // Listen for changes to AppSettings.enableNotifications + // Listen for changes to AppSettings. appSettings.$enableNotifications .weakAssign(to: \.state.bindings.enableNotifications, on: self) .store(in: &cancellables) diff --git a/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/View/NotificationSettingsScreen.swift b/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/View/NotificationSettingsScreen.swift index c557494b8..96618914b 100644 --- a/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/View/NotificationSettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/View/NotificationSettingsScreen.swift @@ -28,15 +28,20 @@ struct NotificationSettingsScreen: View { if context.viewState.showSystemNotificationsAlert { userPermissionSection } + enableNotificationSection + if context.enableNotifications { roomsNotificationSection + if context.viewState.settings?.roomMentionsEnabled != nil { mentionsSection } + if context.viewState.showCallsSettings, context.viewState.settings?.callsEnabled != nil { callsSection } + if context.viewState.settings?.invitationsEnabled != nil { additionalSettingsSection } diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryDetails.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryDetails.swift index e023a66aa..e75e6723d 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryDetails.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryDetails.swift @@ -32,6 +32,11 @@ struct RoomSummaryDetails { let canonicalAlias: String? let inviter: RoomMemberProxyProtocol? let hasOngoingCall: Bool + + var hasUnreadMessages: Bool { unreadMessagesCount > 0 } + var hasUnreadMentions: Bool { unreadMentionsCount > 0 } + var hasUnreadNotifications: Bool { unreadNotificationsCount > 0 } + var isMuted: Bool { notificationMode == .mute } } extension RoomSummaryDetails: CustomStringConvertible { diff --git a/UnitTests/__Snapshots__/PreviewTests/test_homeScreenRoomCell.Generic.png b/UnitTests/__Snapshots__/PreviewTests/test_homeScreenRoomCell.Generic.png index fd0afbfeb..61cd6262d 100644 --- a/UnitTests/__Snapshots__/PreviewTests/test_homeScreenRoomCell.Generic.png +++ b/UnitTests/__Snapshots__/PreviewTests/test_homeScreenRoomCell.Generic.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a008adeae6b250d84187e72067a734e213b2a7557258f4bcd251b45b64c9dc3 -size 241273 +oid sha256:b4bd725468edb125b1f12f847a51e37da0e5d128bf4aff21d028f71c9c13201b +size 270374 diff --git a/UnitTests/__Snapshots__/PreviewTests/test_homeScreenRoomCell.Notifications-State.png b/UnitTests/__Snapshots__/PreviewTests/test_homeScreenRoomCell.Notifications-State.png index 4e2645469..64d380a80 100644 --- a/UnitTests/__Snapshots__/PreviewTests/test_homeScreenRoomCell.Notifications-State.png +++ b/UnitTests/__Snapshots__/PreviewTests/test_homeScreenRoomCell.Notifications-State.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15801a4cca3898a59aaa7607710b7193323ae25df590a1857d009d97afc45955 -size 804308 +oid sha256:64cea9279b782b82793e81bb6ef4de68724840fdf0e5e084c9445ea0e773d4b8 +size 799721