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.
This commit is contained in:
Jonathan Rainville
2026-08-26 11:55:44 -04:00
parent 8bc2887d59
commit ccd9d3df81
10 changed files with 41 additions and 13 deletions
@@ -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])
@@ -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:
@@ -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,
@@ -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)
@@ -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.} =
+8 -1
View File
@@ -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) =
@@ -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,
})
+8 -3
View File
@@ -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)
+4
View File
@@ -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]
+2 -2
View File
@@ -17165,7 +17165,7 @@ avec un retour à la ligne</translation>
</message>
<message>
<source>Fastest</source>
<translation type="unfinished"></translation>
<translation type="unfinished">Le plus rapide</translation>
</message>
<message>
<source>Shortest execution time</source>
@@ -17173,7 +17173,7 @@ avec un retour à la ligne</translation>
</message>
<message>
<source>Lowest fee</source>
<translation type="unfinished"></translation>
<translation type="unfinished">Frais le plus bas</translation>
</message>
<message>
<source>Lowest network cost</source>