From ccd9d3df81acdfd623b42a9aeb172d5c1a4b209f Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Thu, 20 Aug 2026 19:30:54 -0400 Subject: [PATCH] fix(threads): clear unread badge when opening thread Pass the active threadId through chat content mark-read handling so thread views call the thread-aware status-go mark-read path instead of the parent chat mark-all-read path. Carry threadId through the async message service read event and use it to route read acknowledgements to the matching thread controller. When a thread is marked read, clear the synthetic thread row badge while leaving the parent channel unread state untouched. This fixes unread thread badges staying visible after entering a thread. --- .../main/chat_section/chat_content/controller.nim | 6 ++++-- .../chat_section/chat_content/messages/controller.nim | 2 ++ .../modules/main/chat_section/chat_content/module.nim | 2 +- src/app/modules/main/chat_section/controller.nim | 5 +++-- src/app/modules/main/chat_section/io_interface.nim | 2 +- src/app/modules/main/chat_section/module.nim | 9 ++++++++- src/app_service/service/message/async_tasks.nim | 9 ++++++++- src/app_service/service/message/service.nim | 11 ++++++++--- src/backend/messages.nim | 4 ++++ ui/i18n/qml_fr.ts | 4 ++-- 10 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/app/modules/main/chat_section/chat_content/controller.nim b/src/app/modules/main/chat_section/chat_content/controller.nim index 8b0df5a64e..695e05d3c3 100644 --- a/src/app/modules/main/chat_section/chat_content/controller.nim +++ b/src/app/modules/main/chat_section/chat_content/controller.nim @@ -20,6 +20,7 @@ type events: UniqueUUIDEventEmitter sectionId: string chatId: string + threadId: string belongsToCommunity: bool isUsersListAvailable: bool #users list is not available for 1:1 chat nodeConfigurationService: node_configuration_service.Service @@ -37,12 +38,13 @@ proc newController*(delegate: io_interface.AccessInterface, events: EventEmitter belongsToCommunity: bool, isUsersListAvailable: bool, settingsService: settings_service.Service, nodeConfigurationService: node_configuration_service.Service, contactService: contact_service.Service, chatService: chat_service.Service, communityService: community_service.Service, - messageService: message_service.Service): Controller = + messageService: message_service.Service, threadId: string = ""): Controller = result = Controller() result.delegate = delegate result.events = initUniqueUUIDEventEmitter(events) result.sectionId = sectionId result.chatId = chatId + result.threadId = threadId result.belongsToCommunity = belongsToCommunity result.isUsersListAvailable = isUsersListAvailable result.settingsService = settingsService @@ -238,7 +240,7 @@ proc unblockChat*(self: Controller) = self.contactService.unblockContact(self.chatId) proc markAllMessagesRead*(self: Controller) = - self.messageService.markAllMessagesRead(self.chatId) + self.messageService.markAllMessagesRead(self.chatId, self.threadId) proc markMessageRead*(self: Controller, msgID: string) = self.messageService.markCertainMessagesRead(self.chatId, @[msgID]) diff --git a/src/app/modules/main/chat_section/chat_content/messages/controller.nim b/src/app/modules/main/chat_section/chat_content/messages/controller.nim index 582f478885..22fe83dd06 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/controller.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/controller.nim @@ -150,6 +150,8 @@ proc init*(self: Controller) = let args = MessagesMarkedAsReadArgs(e) if self.chatId != args.chatId: return + if self.threadId != args.threadId: + return if args.allMessagesMarked: self.delegate.markAllMessagesRead() else: diff --git a/src/app/modules/main/chat_section/chat_content/module.nim b/src/app/modules/main/chat_section/chat_content/module.nim index 1f42c9f37e..03e1dce39e 100644 --- a/src/app/modules/main/chat_section/chat_content/module.nim +++ b/src/app/modules/main/chat_section/chat_content/module.nim @@ -54,7 +54,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface, events: EventEmitt result.viewVariant = newQVariant(result.view) result.controller = controller.newController(result, events, sectionId, chatId, belongsToCommunity, isUsersListAvailable, settingsService, nodeConfigurationService, contactService, chatService, communityService, - messageService) + messageService, threadId = threadId) result.moduleLoaded = false result.inputAreaModule = input_area_module.newModule(result, events, sectionId, chatId, belongsToCommunity, diff --git a/src/app/modules/main/chat_section/controller.nim b/src/app/modules/main/chat_section/controller.nim index 774ae4222f..80d604609d 100644 --- a/src/app/modules/main/chat_section/controller.nim +++ b/src/app/modules/main/chat_section/controller.nim @@ -124,8 +124,9 @@ proc init*(self: Controller) = if ((self.isCommunitySection and chat.communityId != self.sectionId) or (not self.isCommunitySection and chat.communityId != "")): return - self.chatService.updateUnreadMessagesAndMentions(args.chatId, args.allMessagesMarked, args.messagesCount, args.messagesWithMentionsCount) - self.delegate.onMarkAllMessagesRead(chat) + if args.threadId.len == 0: + self.chatService.updateUnreadMessagesAndMentions(args.chatId, args.allMessagesMarked, args.messagesCount, args.messagesWithMentionsCount) + self.delegate.onMarkAllMessagesRead(chat, args.threadId) self.events.on(message_service.SIGNAL_MESSAGE_MARKED_AS_UNREAD) do(e:Args): let args = message_service.MessageMarkMessageAsUnreadArgs(e) diff --git a/src/app/modules/main/chat_section/io_interface.nim b/src/app/modules/main/chat_section/io_interface.nim index ea62d0ef24..3f80c43588 100644 --- a/src/app/modules/main/chat_section/io_interface.nim +++ b/src/app/modules/main/chat_section/io_interface.nim @@ -102,7 +102,7 @@ method onNewMessagesReceived*(self: AccessInterface, sectionIdMsgBelongsTo: stri method changeMutedOnChat*(self: AccessInterface, chatId: string, muted: bool) {.base.} = raise newException(ValueError, "No implementation available") -method onMarkAllMessagesRead*(self: AccessInterface, chat: ChatDto) {.base.} = +method onMarkAllMessagesRead*(self: AccessInterface, chat: ChatDto, threadId: string = "") {.base.} = raise newException(ValueError, "No implementation available") method onMarkMessageAsUnread*(self: AccessInterface, chat: ChatDto) {.base.} = diff --git a/src/app/modules/main/chat_section/module.nim b/src/app/modules/main/chat_section/module.nim index bf5453dd4d..d37bf7e616 100644 --- a/src/app/modules/main/chat_section/module.nim +++ b/src/app/modules/main/chat_section/module.nim @@ -1249,7 +1249,14 @@ method onJoinedCommunity*(self: Module) = self.view.setWaitingOnNewCommunityOwnerToConfirmRequestToRejoin(false) self.view.setRequestToJoinState(RequestToJoinState.None) -method onMarkAllMessagesRead*(self: Module, chat: ChatDto) = +method onMarkAllMessagesRead*(self: Module, chat: ChatDto, threadId: string = "") = + if threadId.len > 0: + self.view.chatsModel().updateNotificationsForItemById(threadId, hasUnreadMessages=false, notificationsCount=0) + if self.chatContentModules.contains(threadId): + self.chatContentModules[threadId].onNotificationsUpdated(hasUnreadMessages=false, notificationCount=0) + self.updateParentBadgeNotifications() + return + self.updateBadgeNotifications(chat, hasUnreadMessages=false, unviewedMentionsCount=0) method onMarkMessageAsUnread*(self: Module, chat: ChatDto) = diff --git a/src/app_service/service/message/async_tasks.nim b/src/app_service/service/message/async_tasks.nim index 8e22b4917b..5aed9906d9 100644 --- a/src/app_service/service/message/async_tasks.nim +++ b/src/app_service/service/message/async_tasks.nim @@ -357,18 +357,24 @@ proc asyncSearchMessagesInChatsAndCommunitiesTask(argEncoded: string) {.gcsafe, type AsyncMarkAllMessagesReadTaskArg = ref object of QObjectTaskArg chatId: string + threadId: string proc asyncMarkAllMessagesReadTask(argEncoded: string) {.gcsafe, nimcall.} = let arg = decode[AsyncMarkAllMessagesReadTaskArg](argEncoded) try: - let rpcResponse = status_go.markAllMessagesFromChatWithIdAsRead(arg.chatId) + let rpcResponse = + if arg.threadId.len > 0: + status_go.markAllMessagesFromThreadWithIdAsRead(arg.chatId, arg.threadId) + else: + status_go.markAllMessagesFromChatWithIdAsRead(arg.chatId) var activityCenterNotifications: JsonNode = newJObject() discard rpcResponse.result.getProp("activityCenterNotifications", activityCenterNotifications) arg.finish(%*{ "chatId": arg.chatId, + "threadId": arg.threadId, "activityCenterNotifications": activityCenterNotifications, "error": rpcResponse.error, }) @@ -376,6 +382,7 @@ proc asyncMarkAllMessagesReadTask(argEncoded: string) {.gcsafe, nimcall.} = except Exception as e: arg.finish(%* { "chatId": arg.chatId, + "threadId": arg.threadId, "error": e.msg, }) diff --git a/src/app_service/service/message/service.nim b/src/app_service/service/message/service.nim index fb6217f01d..442d8b3fe4 100644 --- a/src/app_service/service/message/service.nim +++ b/src/app_service/service/message/service.nim @@ -123,6 +123,7 @@ type MessagesMarkedAsReadArgs* = ref object of Args chatId*: string + threadId*: string allMessagesMarked*: bool messagesIds*: seq[string] messagesCount*: int @@ -1310,13 +1311,16 @@ QtObject: var chatId: string discard responseObj.getProp("chatId", chatId) - let data = MessagesMarkedAsReadArgs(chatId: chatId, allMessagesMarked: true) + var threadId: string + discard responseObj.getProp("threadId", threadId) + + let data = MessagesMarkedAsReadArgs(chatId: chatId, threadId: threadId, allMessagesMarked: true) self.events.emit(SIGNAL_MESSAGES_MARKED_AS_READ, data) checkAndEmitACNotificationsFromResponse(self.events, responseObj{"activityCenterNotifications"}) except Exception as e: error "error: ", procName="onMarkAllMessagesRead", errDesription = e.msg - proc markAllMessagesRead*(self: Service, chatId: string) = + proc markAllMessagesRead*(self: Service, chatId: string, threadId: string = "") = if (chatId.len == 0): error "empty chat id", procName="markAllMessagesRead" return @@ -1325,7 +1329,8 @@ QtObject: tptr: asyncMarkAllMessagesReadTask, vptr: cast[uint](self.vptr), slot: "onMarkAllMessagesRead", - chatId: chatId + chatId: chatId, + threadId: threadId ) self.threadpool.start(arg) diff --git a/src/backend/messages.nim b/src/backend/messages.nim index 28f75a6d59..043f964f53 100644 --- a/src/backend/messages.nim +++ b/src/backend/messages.nim @@ -58,6 +58,10 @@ proc markAllMessagesFromChatWithIdAsRead*(chatId: string): RpcResponse[JsonNode] let payload = %* [chatId] result = callPrivateRPC("markAllRead".prefix, payload) +proc markAllMessagesFromThreadWithIdAsRead*(chatId: string, threadId: string): RpcResponse[JsonNode] = + let payload = %* [chatId, threadId] + result = callPrivateRPC("markThreadRead".prefix, payload) + proc markCertainMessagesFromChatWithIdAsRead*(chatId: string, messageIds: seq[string]): RpcResponse[JsonNode] = let payload = %* [chatId, messageIds] diff --git a/ui/i18n/qml_fr.ts b/ui/i18n/qml_fr.ts index b48bd94e69..e41a27e0d4 100644 --- a/ui/i18n/qml_fr.ts +++ b/ui/i18n/qml_fr.ts @@ -17165,7 +17165,7 @@ avec un retour à la ligne Fastest - + Le plus rapide Shortest execution time @@ -17173,7 +17173,7 @@ avec un retour à la ligne Lowest fee - + Frais le plus bas Lowest network cost