mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
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.
This commit is contained in:
@@ -38,6 +38,7 @@ final class AppSettings {
|
|||||||
case richTextEditorEnabled
|
case richTextEditorEnabled
|
||||||
case appAppearance
|
case appAppearance
|
||||||
case sendReadReceiptsEnabled
|
case sendReadReceiptsEnabled
|
||||||
|
case hideUnreadMessagesBadge
|
||||||
|
|
||||||
case elementCallBaseURL
|
case elementCallBaseURL
|
||||||
case elementCallEncryptionEnabled
|
case elementCallEncryptionEnabled
|
||||||
@@ -212,6 +213,11 @@ final class AppSettings {
|
|||||||
@UserPreference(key: UserDefaultsKeys.analyticsConsentState, defaultValue: AnalyticsConsentState.unknown, storageType: .userDefaults(store))
|
@UserPreference(key: UserDefaultsKeys.analyticsConsentState, defaultValue: AnalyticsConsentState.unknown, storageType: .userDefaults(store))
|
||||||
var analyticsConsentState
|
var analyticsConsentState
|
||||||
|
|
||||||
|
// MARK: - Home Screen
|
||||||
|
|
||||||
|
@UserPreference(key: UserDefaultsKeys.hideUnreadMessagesBadge, defaultValue: false, storageType: .userDefaults(store))
|
||||||
|
var hideUnreadMessagesBadge
|
||||||
|
|
||||||
// MARK: - Room Screen
|
// MARK: - Room Screen
|
||||||
|
|
||||||
@UserPreference(key: UserDefaultsKeys.timelineStyle, defaultValue: TimelineStyle.bubbles, storageType: .userDefaults(store))
|
@UserPreference(key: UserDefaultsKeys.timelineStyle, defaultValue: TimelineStyle.bubbles, storageType: .userDefaults(store))
|
||||||
|
|||||||
@@ -157,15 +157,15 @@ struct HomeScreenRoom: Identifiable, Equatable {
|
|||||||
|
|
||||||
var name = ""
|
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
|
let isHighlighted: Bool
|
||||||
|
|
||||||
var hasUnreadMentions = false
|
|
||||||
|
|
||||||
var hasUnreadNotifications = false
|
|
||||||
|
|
||||||
var hasOngoingCall = false
|
|
||||||
|
|
||||||
var timestamp: String?
|
var timestamp: String?
|
||||||
|
|
||||||
@@ -173,28 +173,46 @@ struct HomeScreenRoom: Identifiable, Equatable {
|
|||||||
|
|
||||||
var avatarURL: URL?
|
var avatarURL: URL?
|
||||||
|
|
||||||
var notificationMode: RoomNotificationModeProxy?
|
|
||||||
|
|
||||||
var isPlaceholder = false
|
var isPlaceholder = false
|
||||||
|
|
||||||
var hasNewContent: Bool {
|
|
||||||
hasUnreadMessages || hasUnreadMentions || hasUnreadNotifications || isMarkedUnread
|
|
||||||
}
|
|
||||||
|
|
||||||
static func placeholder() -> HomeScreenRoom {
|
static func placeholder() -> HomeScreenRoom {
|
||||||
HomeScreenRoom(id: UUID().uuidString,
|
HomeScreenRoom(id: UUID().uuidString,
|
||||||
roomId: nil,
|
roomId: nil,
|
||||||
name: "Placeholder room name",
|
name: "Placeholder room name",
|
||||||
isMarkedUnread: false,
|
badges: .init(isDotShown: false, isMentionShown: false, isMuteShown: false, isCallShown: false),
|
||||||
hasUnreadMessages: false,
|
isHighlighted: false,
|
||||||
hasUnreadMentions: false,
|
|
||||||
hasUnreadNotifications: false,
|
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
lastMessage: placeholderLastMessage,
|
lastMessage: placeholderLastMessage,
|
||||||
isPlaceholder: true)
|
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 {
|
enum RoomListFilter: Int, CaseIterable, Identifiable {
|
||||||
var id: Int {
|
var id: Int {
|
||||||
rawValue
|
rawValue
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
.weakAssign(to: \.state.markAsUnreadEnabled, on: self)
|
.weakAssign(to: \.state.markAsUnreadEnabled, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
appSettings.$hideUnreadMessagesBadge
|
||||||
|
.sink { [weak self] _ in self?.updateRooms() }
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
let isSearchFieldFocused = context.$viewState.map(\.bindings.isSearchFieldFocused)
|
let isSearchFieldFocused = context.$viewState.map(\.bindings.isSearchFieldFocused)
|
||||||
let searchQuery = context.$viewState.map(\.bindings.searchQuery)
|
let searchQuery = context.$viewState.map(\.bindings.searchQuery)
|
||||||
isSearchFieldFocused
|
isSearchFieldFocused
|
||||||
@@ -339,11 +343,11 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
switch summary {
|
switch summary {
|
||||||
case .empty:
|
case .empty:
|
||||||
rooms.append(HomeScreenRoom.placeholder())
|
rooms.append(HomeScreenRoom.placeholder())
|
||||||
case .invalidated(let details):
|
|
||||||
let room = buildRoom(with: details, invalidated: true)
|
|
||||||
rooms.append(room)
|
|
||||||
case .filled(let details):
|
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)
|
rooms.append(room)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,23 +357,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
MXLog.verbose("Finished updating rooms")
|
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<Int>) {
|
private func updateVisibleRange(_ range: Range<Int>) {
|
||||||
guard !range.isEmpty else {
|
guard !range.isEmpty else {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ struct HomeScreenRoomCell: View {
|
|||||||
|
|
||||||
if let timestamp = room.timestamp {
|
if let timestamp = room.timestamp {
|
||||||
Text(timestamp)
|
Text(timestamp)
|
||||||
.font(isHighlighted ? .compound.bodySMSemibold : .compound.bodySM)
|
.font(room.isHighlighted ? .compound.bodySMSemibold : .compound.bodySM)
|
||||||
.foregroundColor(isHighlighted ? .compound.textActionAccent : .compound.textSecondary)
|
.foregroundColor(room.isHighlighted ? .compound.textActionAccent : .compound.textSecondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,38 +117,30 @@ struct HomeScreenRoomCell: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
if room.hasOngoingCall {
|
if room.badges.isCallShown {
|
||||||
CompoundIcon(\.videoCallSolid, size: .xSmall, relativeTo: .compound.bodySM)
|
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)
|
CompoundIcon(\.notificationsOffSolid, size: .custom(15), relativeTo: .compound.bodyMD)
|
||||||
.accessibilityLabel(L10n.a11yNotificationsMuted)
|
.accessibilityLabel(L10n.a11yNotificationsMuted)
|
||||||
.foregroundColor(.compound.iconQuaternary)
|
.foregroundColor(.compound.iconQuaternary)
|
||||||
}
|
}
|
||||||
|
|
||||||
if room.hasUnreadMentions, room.notificationMode != .mute {
|
if room.badges.isMentionShown {
|
||||||
mentionIcon
|
mentionIcon
|
||||||
.foregroundColor(.compound.iconAccentTertiary)
|
.foregroundColor(.compound.iconAccentTertiary)
|
||||||
}
|
}
|
||||||
|
|
||||||
if room.hasNewContent {
|
if room.badges.isDotShown {
|
||||||
Circle()
|
Circle()
|
||||||
.frame(width: 12, height: 12)
|
.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 {
|
private var mentionIcon: some View {
|
||||||
CompoundIcon(\.mention, size: .custom(15), relativeTo: .compound.bodyMD)
|
CompoundIcon(\.mention, size: .custom(15), relativeTo: .compound.bodyMD)
|
||||||
@@ -212,19 +204,11 @@ struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview {
|
|||||||
static func mockRoom(summary: RoomSummary) -> HomeScreenRoom? {
|
static func mockRoom(summary: RoomSummary) -> HomeScreenRoom? {
|
||||||
switch summary {
|
switch summary {
|
||||||
case .empty:
|
case .empty:
|
||||||
return nil
|
nil
|
||||||
case .invalidated(let details), .filled(let details):
|
case .invalidated(let details):
|
||||||
return HomeScreenRoom(id: UUID().uuidString,
|
HomeScreenRoom(details: details, invalidated: true, hideUnreadMessagesBadge: false)
|
||||||
roomId: details.id,
|
case .filled(let details):
|
||||||
name: details.name,
|
HomeScreenRoom(details: details, invalidated: false, hideUnreadMessagesBadge: false)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ struct HomeScreenRoomList: View {
|
|||||||
HomeScreenRoomCell(room: room, context: context, isSelected: isSelected)
|
HomeScreenRoomCell(room: room, context: context, isSelected: isSelected)
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
if context.viewState.markAsUnreadEnabled {
|
if context.viewState.markAsUnreadEnabled {
|
||||||
if room.hasNewContent {
|
if room.badges.isDotShown {
|
||||||
Button {
|
Button {
|
||||||
context.send(viewAction: .markRoomAsRead(roomIdentifier: room.id))
|
context.send(viewAction: .markRoomAsRead(roomIdentifier: room.id))
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
+1
@@ -51,6 +51,7 @@ protocol DeveloperOptionsProtocol: AnyObject {
|
|||||||
var mentionsBadgeEnabled: Bool { get set }
|
var mentionsBadgeEnabled: Bool { get set }
|
||||||
var roomListFiltersEnabled: Bool { get set }
|
var roomListFiltersEnabled: Bool { get set }
|
||||||
var markAsUnreadEnabled: Bool { get set }
|
var markAsUnreadEnabled: Bool { get set }
|
||||||
|
var hideUnreadMessagesBadge: Bool { get set }
|
||||||
|
|
||||||
var elementCallBaseURL: URL { get set }
|
var elementCallBaseURL: URL { get set }
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -59,6 +59,10 @@ struct DeveloperOptionsScreen: View {
|
|||||||
Toggle(isOn: $context.markAsUnreadEnabled) {
|
Toggle(isOn: $context.markAsUnreadEnabled) {
|
||||||
Text("Mark as unread")
|
Text("Mark as unread")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Toggle(isOn: $context.hideUnreadMessagesBadge) {
|
||||||
|
Text("Hide grey dots")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("Element Call") {
|
Section("Element Call") {
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
|||||||
let bindings = NotificationSettingsScreenViewStateBindings(enableNotifications: appSettings.enableNotifications)
|
let bindings = NotificationSettingsScreenViewStateBindings(enableNotifications: appSettings.enableNotifications)
|
||||||
super.init(initialViewState: NotificationSettingsScreenViewState(bindings: bindings, isModallyPresented: isModallyPresented))
|
super.init(initialViewState: NotificationSettingsScreenViewState(bindings: bindings, isModallyPresented: isModallyPresented))
|
||||||
|
|
||||||
// Listen for changes to AppSettings.enableNotifications
|
// Listen for changes to AppSettings.
|
||||||
appSettings.$enableNotifications
|
appSettings.$enableNotifications
|
||||||
.weakAssign(to: \.state.bindings.enableNotifications, on: self)
|
.weakAssign(to: \.state.bindings.enableNotifications, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
+5
@@ -28,15 +28,20 @@ struct NotificationSettingsScreen: View {
|
|||||||
if context.viewState.showSystemNotificationsAlert {
|
if context.viewState.showSystemNotificationsAlert {
|
||||||
userPermissionSection
|
userPermissionSection
|
||||||
}
|
}
|
||||||
|
|
||||||
enableNotificationSection
|
enableNotificationSection
|
||||||
|
|
||||||
if context.enableNotifications {
|
if context.enableNotifications {
|
||||||
roomsNotificationSection
|
roomsNotificationSection
|
||||||
|
|
||||||
if context.viewState.settings?.roomMentionsEnabled != nil {
|
if context.viewState.settings?.roomMentionsEnabled != nil {
|
||||||
mentionsSection
|
mentionsSection
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.viewState.showCallsSettings, context.viewState.settings?.callsEnabled != nil {
|
if context.viewState.showCallsSettings, context.viewState.settings?.callsEnabled != nil {
|
||||||
callsSection
|
callsSection
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.viewState.settings?.invitationsEnabled != nil {
|
if context.viewState.settings?.invitationsEnabled != nil {
|
||||||
additionalSettingsSection
|
additionalSettingsSection
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ struct RoomSummaryDetails {
|
|||||||
let canonicalAlias: String?
|
let canonicalAlias: String?
|
||||||
let inviter: RoomMemberProxyProtocol?
|
let inviter: RoomMemberProxyProtocol?
|
||||||
let hasOngoingCall: Bool
|
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 {
|
extension RoomSummaryDetails: CustomStringConvertible {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:0a008adeae6b250d84187e72067a734e213b2a7557258f4bcd251b45b64c9dc3
|
oid sha256:b4bd725468edb125b1f12f847a51e37da0e5d128bf4aff21d028f71c9c13201b
|
||||||
size 241273
|
size 270374
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:15801a4cca3898a59aaa7607710b7193323ae25df590a1857d009d97afc45955
|
oid sha256:64cea9279b782b82793e81bb6ef4de68724840fdf0e5e084c9445ea0e773d4b8
|
||||||
size 804308
|
size 799721
|
||||||
|
|||||||
Reference in New Issue
Block a user