From 43ad806482760f7900bb21e46ba112cf01db1ce9 Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Sat, 29 Aug 2026 05:37:38 +0000 Subject: [PATCH] ui: include support chats that need attention in the chat list unread filter A group whose support chats need attention - for a member, unread replies in their own "Chat with admins"; for a moderator, members waiting on them - is flagged on its chat list row, but the unread filter did not list it. chatStats.unreadCount is filled by a query scoped to the main conversation (group_scope_tag IS NULL), so, except for a pending member's own support chat, unreadTag cannot see support scopes at all. Match on Chat.hasUnread, which adds supportUnreadCount to unreadTag, gated on the chat not being muted to "mute all" - so a support chat waiting in a fully muted group does not put it in the filter, matching what the profile's unread count does with that group. Marking such a chat unread by hand still lists it, through unreadTag's unreadChat branch. On iOS, updateChatInfo changes the chat in place, so it now publishes when hasUnread changes, for the filter to update. supportUnreadCount is the same value the row's flag shows. For a moderator it is membersRequireAttention, which counts members: pending approval or review, or with unread messages nobody has answered or unread mentions. Reading does not clear a pending member - approving (once nothing else in their support chat needs attention), rejecting, or the member leaving does - so the group stays listed until then. Mentions-only chats still count support chats, on the view that mentions-only is a statement about group chatter, and support chats (the user's own line to the admins, or members waiting on a moderator) are not chatter. --- apps/ios/Shared/Model/ChatModel.swift | 4 ++++ apps/ios/Shared/Views/ChatList/ChatListView.swift | 2 +- apps/ios/product/views/chat-list.md | 2 +- apps/ios/spec/client/chat-list.md | 2 +- .../commonMain/kotlin/chat/simplex/common/model/ChatModel.kt | 2 ++ .../kotlin/chat/simplex/common/views/chatlist/ChatListView.kt | 2 +- apps/multiplatform/product/views/chat-list.md | 2 +- apps/multiplatform/spec/client/chat-list.md | 2 +- 8 files changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index c928de85db..04c76fd9a6 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -623,12 +623,14 @@ final class ChatModel: ObservableObject { // Spec: spec/state.md#updateChatInfo func updateChatInfo(_ cInfo: ChatInfo) { if let i = getChatIndex(cInfo.id) { + let wasUnread = chats[i].hasUnread if case let .group(groupInfo, groupChatScope) = cInfo, groupChatScope != nil { chats[i].chatInfo = .group(groupInfo: groupInfo, groupChatScope: nil) } else { chats[i].chatInfo = cInfo } chats[i].created = Date.now + if chats[i].hasUnread != wasUnread { objectWillChange.send() } } } @@ -1470,6 +1472,8 @@ final class Chat: ObservableObject, Identifiable, ChatLike { } } + var hasUnread: Bool { unreadTag || (chatInfo.chatSettings?.enableNtfs != MsgFilter.none && supportUnreadCount > 0) } + public static var sampleData: Chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []) } diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 5c5021b79f..5516cc4a1d 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -673,7 +673,7 @@ struct ChatListView: View { switch chatTagsModel.activeFilter { case let .presetTag(tag): presetTagMatchesChat(tag, chat.chatInfo, chat.chatStats) case let .userTag(tag): chat.chatInfo.chatTags?.contains(tag.chatTagId) == true - case .unread: chat.unreadTag + case .unread: chat.hasUnread case .none: true } } diff --git a/apps/ios/product/views/chat-list.md b/apps/ios/product/views/chat-list.md index dee9e1d427..5a2de9b59a 100644 --- a/apps/ios/product/views/chat-list.md +++ b/apps/ios/product/views/chat-list.md @@ -41,7 +41,7 @@ Managed by `ChatTagsModel` and `TagListView`: | Filter | PresetTag | Description | |---|---|---| | All | (none) | No filter, shows all chats | -| Unread | `.unread` | Chats with unread messages | +| Unread | `.unread` | Chats with unread messages, or support chats that need attention (as flagged on the row: unread messages or mentions, or, for moderators, members pending, with unread messages nobody has answered, or with unread mentions) unless muted to "mute all" | | Favorites | `.favorites` | User-favorited chats | | Groups | `.groups` | Group conversations only | | Contacts | `.contacts` | Direct contacts only | diff --git a/apps/ios/spec/client/chat-list.md b/apps/ios/spec/client/chat-list.md index 2087d0dee3..9388a43633 100644 --- a/apps/ios/spec/client/chat-list.md +++ b/apps/ios/spec/client/chat-list.md @@ -164,7 +164,7 @@ Horizontal scrolling tab bar below the navigation bar. Tabs: | Tab | Filter | Shows | |-----|--------|-------| | All | `nil` | All conversations | -| Unread | `.unread` | Conversations with unread messages | +| Unread | `.unread` | Conversations with unread messages, or support chats that need attention (as flagged on the row: unread messages or mentions, or, for moderators, members pending, with unread messages nobody has answered, or with unread mentions) unless muted to "mute all" | | Favorites | `.presetTag(.favorites)` | Favorited conversations | | Groups | `.presetTag(.groups)` | Group conversations | | Contacts | `.presetTag(.contacts)` | Direct conversations | diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index b3c2ec3e92..4f92dba2f2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1507,6 +1507,8 @@ data class Chat( else -> 0 } + val hasUnread: Boolean get() = unreadTag || (chatInfo.chatSettings?.enableNtfs != None && supportUnreadCount > 0) + fun groupFeatureEnabled(feature: GroupFeature): Boolean = if (chatInfo is ChatInfo.Group) { chatInfo.groupInfo.groupFeatureEnabled(feature) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 52cb2b338b..b45e7454ef 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -1601,7 +1601,7 @@ private fun filtered(chat: Chat, activeFilter: ActiveFilter?): Boolean = when (activeFilter) { is ActiveFilter.PresetTag -> presetTagMatchesChat(activeFilter.tag, chat.chatInfo, chat.chatStats) is ActiveFilter.UserTag -> chat.chatInfo.chatTags?.contains(activeFilter.tag.chatTagId) ?: false - is ActiveFilter.Unread -> chat.unreadTag + is ActiveFilter.Unread -> chat.hasUnread else -> true } diff --git a/apps/multiplatform/product/views/chat-list.md b/apps/multiplatform/product/views/chat-list.md index a09994d1e3..3324906435 100644 --- a/apps/multiplatform/product/views/chat-list.md +++ b/apps/multiplatform/product/views/chat-list.md @@ -65,7 +65,7 @@ Managed by `chatModel.userTags`, `chatModel.presetTags`, and `chatModel.activeCh | Business | `BUSINESS` | Work | Business chat conversations | | Notes | `NOTES` | Folder | Notes to self | | Custom tags | `UserTag(ChatTag)` | Label/emoji | User-created tags with custom emoji and name | -| Unread | `ActiveFilter.Unread` | Filter list icon | Chats with unread messages (toggle via filter button) | +| Unread | `ActiveFilter.Unread` | Filter list icon | Chats with unread messages, or support chats that need attention (as flagged on the row: unread messages or mentions, or, for moderators, members pending, with unread messages nobody has answered, or with unread mentions) unless muted to "mute all" (toggle via filter button) | Display logic: - When collapsible preset tags exceed 3 total (with user tags), they collapse into a `CollapsedTagsFilterView` dropdown menu diff --git a/apps/multiplatform/spec/client/chat-list.md b/apps/multiplatform/spec/client/chat-list.md index 5a63ad92bc..8b0ab1c7ff 100644 --- a/apps/multiplatform/spec/client/chat-list.md +++ b/apps/multiplatform/spec/client/chat-list.md @@ -144,7 +144,7 @@ The `filteredChats` function (line ~1474) applies filters in this order: 3. **Active filter:** - `PresetTag`: Matches chat type and characteristics (e.g., `CONTACTS` filters `ChatInfo.Direct`, `GROUPS` filters `ChatInfo.Group`). - `UserTag`: Matches chats whose `chatTags` contain the tag ID. - - `Unread`: Matches chats with `unreadCount > 0` or `unreadChat == true`. + - `Unread`: Matches `Chat.hasUnread` — a chat marked unread by hand, unread messages as the chat's notification setting counts them, or support chats that need attention (as flagged on the row: unread messages or mentions, or, for moderators, members pending, with unread messages nobody has answered, or with unread mentions) unless the chat is muted to "mute all". ### Search Bar