From 7a5e691c900ad91b57797d1d13c583959b9e8c14 Mon Sep 17 00:00:00 2001 From: Godfrain Jacques Date: Mon, 11 Dec 2023 20:16:06 -0600 Subject: [PATCH] feature(@desktop/chat) Enhance message context menu with mark as unread (#12879) * chore: bump status-go * feature(@desktop/chat) Enhance message context menu with mark as unread fixes #10329 linked with PR #12879 - Adds capacity to mark a message as unread - Adds capacity to mark a message with mention as unread - Adds persistence to the marking of the message (change can be seen at after reboot) - Adds marking in right click contextual menu --- .../chat_content/messages/controller.nim | 9 ++ .../chat_content/messages/io_interface.nim | 6 ++ .../chat_content/messages/module.nim | 6 ++ .../chat_content/messages/view.nim | 21 +++++ .../modules/main/chat_section/controller.nim | 6 ++ .../main/chat_section/io_interface.nim | 3 + src/app/modules/main/chat_section/module.nim | 3 + .../modules/shared_models/message_model.nim | 18 ++++ src/app_service/service/chat/service.nim | 9 ++ .../service/message/async_tasks.nim | 56 +++++++++++-- src/app_service/service/message/service.nim | 82 ++++++++++++++++++- src/backend/messages.nim | 4 + test/nim/message_model_test.nim | 63 ++++++++++++++ .../AppLayouts/Chat/stores/MessageStore.qml | 16 ++++ .../Chat/views/ChatMessagesView.qml | 8 +- .../views/chat/MessageContextMenuView.qml | 12 +++ ui/imports/shared/views/chat/MessageView.qml | 19 +++++ vendor/status-go | 2 +- 18 files changed, 331 insertions(+), 12 deletions(-) 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 ef6318edbe..144fbedbd3 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 @@ -111,6 +111,12 @@ proc init*(self: Controller) = return self.delegate.onUnpinMessage(args.messageId) + self.events.on(SIGNAL_MESSAGE_MARKED_AS_UNREAD) do(e:Args): + let args = MessageMarkMessageAsUnreadArgs(e) + if (self.chatId != args.chatId): + return + self.delegate.onMarkMessageAsUnread(args.messageId) + self.events.on(SIGNAL_MESSAGE_REACTION_ADDED) do(e:Args): let args = MessageAddRemoveReactionArgs(e) if(self.chatId != args.chatId): @@ -267,6 +273,9 @@ proc removeReaction*(self: Controller, messageId: string, emojiId: int, reaction proc pinUnpinMessage*(self: Controller, messageId: string, pin: bool) = self.messageService.pinUnpinMessage(self.chatId, messageId, pin) +proc markMessageAsUnread*(self: Controller, messageId: string) = + self.messageService.asyncMarkMessageAsUnread(self.chatId, messageId) + proc getContactById*(self: Controller, contactId: string): ContactsDto = return self.contactService.getContactById(contactId) diff --git a/src/app/modules/main/chat_section/chat_content/messages/io_interface.nim b/src/app/modules/main/chat_section/chat_content/messages/io_interface.nim index 7eef9b8902..012c329bfe 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/io_interface.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/io_interface.nim @@ -48,6 +48,12 @@ method onPinMessage*(self: AccessInterface, messageId: string, actionInitiatedBy method onUnpinMessage*(self: AccessInterface, messageId: string) {.base.} = raise newException(ValueError, "No implementation available") +method markMessageAsUnread*(self: AccessInterface, messageId: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onMarkMessageAsUnread*(self: AccessInterface, messageId: string) {.base.} = + raise newException(ValueError, "No implementation available") + method messagesAdded*(self: AccessInterface, messages: seq[MessageDto]) {.base.} = raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/chat_section/chat_content/messages/module.nim b/src/app/modules/main/chat_section/chat_content/messages/module.nim index 72814886ee..cf71884c63 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/module.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/module.nim @@ -510,12 +510,18 @@ method toggleReactionFromOthers*(self: Module, messageId: string, emojiId: int, method pinUnpinMessage*(self: Module, messageId: string, pin: bool) = self.controller.pinUnpinMessage(messageId, pin) +method markMessageAsUnread*(self: Module, messageId: string) = + self.controller.markMessageAsUnread(messageId) + method onPinMessage*(self: Module, messageId: string, actionInitiatedBy: string) = self.view.model().pinUnpinMessage(messageId, true, actionInitiatedBy) method onUnpinMessage*(self: Module, messageId: string) = self.view.model().pinUnpinMessage(messageId, false, "") +method onMarkMessageAsUnread*(self: Module, messageId: string) = + self.view.model().markMessageAsUnread(messageId) + method getSectionId*(self: Module): string = return self.controller.getMySectionId() diff --git a/src/app/modules/main/chat_section/chat_content/messages/view.nim b/src/app/modules/main/chat_section/chat_content/messages/view.nim index ea3e83cfee..a7825dce8d 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/view.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/view.nim @@ -17,6 +17,7 @@ QtObject: chatIcon: string chatType: int loading: bool + keepUnread: bool proc delete*(self: View) = self.model.delete @@ -36,6 +37,7 @@ QtObject: result.chatIcon = "" result.chatType = ChatType.Unknown.int result.loading = false + result.keepUnread = false proc load*(self: View) = self.delegate.viewDidLoad() @@ -57,6 +59,25 @@ QtObject: proc unpinMessage*(self: View, messageId: string) {.slot.} = self.delegate.pinUnpinMessage(messageId, false) + proc keepUnreadChanged*(self: View) {.signal.} + proc getKeepUnread*(self: View): bool {.slot.} = + return self.keepUnread + + QtProperty[bool] keepUnread: + read = getKeepUnread + notify = keepUnreadChanged + + proc setKeepUnread*(self: View, value: bool) = + self.keepUnread = value + self.keepUnreadChanged() + + proc markMessageAsUnread*(self: View, messageId: string) {.slot.} = + self.delegate.markMessageAsUnread(messageId) + self.setKeepUnread(true) + + proc updateKeepUnread*(self: View, flag: bool) {.slot.} = + self.setKeepUnread(flag) + proc getMessageByIdAsJson*(self: View, messageId: string): string {.slot.} = let jsonObj = self.model.getMessageByIdAsJson(messageId) if(jsonObj.isNil): diff --git a/src/app/modules/main/chat_section/controller.nim b/src/app/modules/main/chat_section/controller.nim index 32bf50b224..e99253d1d0 100644 --- a/src/app/modules/main/chat_section/controller.nim +++ b/src/app/modules/main/chat_section/controller.nim @@ -135,6 +135,12 @@ proc init*(self: Controller) = self.chatService.updateUnreadMessagesAndMentions(args.chatId, args.allMessagesMarked, args.messagesCount, args.messagesWithMentionsCount) self.delegate.onMarkAllMessagesRead(chat) + self.events.on(message_service.SIGNAL_MESSAGE_MARKED_AS_UNREAD) do(e:Args): + let args = message_service.MessageMarkMessageAsUnreadArgs(e) + let chat = self.chatService.getChatById(args.chatId) + self.delegate.onMarkMessageAsUnread(chat) + + self.events.on(chat_service.SIGNAL_CHAT_LEFT) do(e: Args): let args = chat_service.ChatArgs(e) self.delegate.onCommunityChannelDeletedOrChatLeft(args.chatId) diff --git a/src/app/modules/main/chat_section/io_interface.nim b/src/app/modules/main/chat_section/io_interface.nim index a7efcece0f..2a42385615 100644 --- a/src/app/modules/main/chat_section/io_interface.nim +++ b/src/app/modules/main/chat_section/io_interface.nim @@ -94,6 +94,9 @@ method changeMutedOnChat*(self: AccessInterface, chatId: string, muted: bool) {. method onMarkAllMessagesRead*(self: AccessInterface, chat: ChatDto) {.base.} = raise newException(ValueError, "No implementation available") +method onMarkMessageAsUnread*(self: AccessInterface, chat: ChatDto) {.base.} = + raise newException(ValueError, "No implementation available") + method onCommunityMuted*(self: AccessInterface, chatId: string, muted: bool) {.base.} = raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/chat_section/module.nim b/src/app/modules/main/chat_section/module.nim index 807dd400a9..15a00380c9 100644 --- a/src/app/modules/main/chat_section/module.nim +++ b/src/app/modules/main/chat_section/module.nim @@ -899,6 +899,9 @@ method onJoinedCommunity*(self: Module) = method onMarkAllMessagesRead*(self: Module, chat: ChatDto) = self.updateBadgeNotifications(chat, hasUnreadMessages=false, unviewedMentionsCount=0) +method onMarkMessageAsUnread*(self: Module, chat: ChatDto) = + self.updateBadgeNotifications(chat, hasUnreadMessages=true, chat.unviewedMentionsCount) + method markAllMessagesRead*(self: Module, chatId: string) = self.controller.markAllMessagesRead(chatId) diff --git a/src/app/modules/shared_models/message_model.nim b/src/app/modules/shared_models/message_model.nim index 32104a3883..60a8d2fc54 100644 --- a/src/app/modules/shared_models/message_model.nim +++ b/src/app/modules/shared_models/message_model.nim @@ -784,6 +784,24 @@ QtObject: defer: index.delete self.dataChanged(index, index, @[ModelRole.Seen.int]) + proc setMessageMarker*(self: Model, messageId: string) = + self.firstUnseenMessageId = messageId + self.resetNewMessagesMarker() + + proc markMessageAsUnread*(self: Model, messageId: string) = + self.setMessageMarker(messageId) + + for i in 0 ..< self.items.len: + let item = self.items[i] + + if item.id == messageId and item.seen: + item.seen = false + let index = self.createIndex(i, 0, nil) + defer: index.delete + self.dataChanged(index, index, @[ModelRole.Seen.int]) + break + + proc markAsSeen*(self: Model, messages: seq[string]) = var messagesSet = toHashSet(messages) diff --git a/src/app_service/service/chat/service.nim b/src/app_service/service/chat/service.nim index 78dc4bbc9d..3899eb9c9e 100644 --- a/src/app_service/service/chat/service.nim +++ b/src/app_service/service/chat/service.nim @@ -763,6 +763,15 @@ QtObject: except Exception as e: error "error while getting members", msg = e.msg, communityID, chatId + proc updateUnreadMessage*(self: Service, chatID: string, messagesCount:int, messagesWithMentionsCount:int) = + var chat = self.getChatById(chatID) + if chat.id == "": + return + + chat.unviewedMessagesCount = messagesCount + chat.unviewedMentionsCount = messagesWithMentionsCount + self.updateOrAddChat(chat) + proc updateUnreadMessagesAndMentions*(self: Service, chatID: string, markAllAsRead: bool, markAsReadCount: int, markAsReadMentionsCount: int) = var chat = self.getChatById(chatID) if chat.id == "": diff --git a/src/app_service/service/message/async_tasks.nim b/src/app_service/service/message/async_tasks.nim index ce4b5d2565..ccfdf668ba 100644 --- a/src/app_service/service/message/async_tasks.nim +++ b/src/app_service/service/message/async_tasks.nim @@ -126,14 +126,16 @@ const asyncMarkAllMessagesReadTask: Task = proc(argEncoded: string) {.gcsafe, ni let response = status_go.markAllMessagesFromChatWithIdAsRead(arg.chatId) var activityCenterNotifications: JsonNode - if response.result["activityCenterNotifications"] != nil: - activityCenterNotifications = response.result["activityCenterNotifications"] + discard response.result.getProp("activityCenterNotifications", activityCenterNotifications) let responseJson = %*{ "chatId": arg.chatId, - "activityCenterNotifications": activityCenterNotifications, "error": response.error } + + if activityCenterNotifications != nil: + responseJson["activityCenterNotifications"] = activityCenterNotifications + arg.finish(responseJson) ################################################# @@ -157,8 +159,7 @@ const asyncMarkCertainMessagesReadTask: Task = proc(argEncoded: string) {.gcsafe discard response.result.getProp("countWithMentions", countWithMentions) var activityCenterNotifications: JsonNode - if response.result["activityCenterNotifications"] != nil: - activityCenterNotifications = response.result["activityCenterNotifications"] + discard response.result.getProp("activityCenterNotifications", activityCenterNotifications) var error = "" if(count == 0): @@ -169,9 +170,12 @@ const asyncMarkCertainMessagesReadTask: Task = proc(argEncoded: string) {.gcsafe "messagesIds": arg.messagesIds, "count": count, "countWithMentions": countWithMentions, - "activityCenterNotifications": activityCenterNotifications, "error": error } + + if activityCenterNotifications != nil: + responseJson["activityCenterNotifications"] = activityCenterNotifications + arg.finish(responseJson) @@ -295,3 +299,43 @@ const asyncGetMessageByMessageIdTask: Task = proc(argEncoded: string) {.gcsafe, } arg.finish(output) +################################################# +# Async mark message as unread +################################################# + +type + AsyncMarkMessageAsUnreadTaskArg = ref object of QObjectTaskArg + messageId*: string + chatId*: string + +const asyncMarkMessageAsUnreadTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[AsyncMarkMessageAsUnreadTaskArg](argEncoded) + + var responseJson = %*{ + "chatId": arg.chatId, + "messageId": arg.messageId, + "messagesCount": 0, + "messagesWithMentionsCount": 0, + "error": "" + } + + try: + let response = status_go.markMessageAsUnread(arg.chatId, arg.messageId) + + var activityCenterNotifications: JsonNode + discard response.result.getProp("activityCenterNotifications", activityCenterNotifications) + + if activityCenterNotifications != nil: + responseJson["activityCenterNotifications"] = activityCenterNotifications + + responseJson["messagesCount"] = %response.result["count"] + responseJson["messagesWithMentionsCount"] = %response.result["countWithMentions"] + + if response.error != nil: + responseJson["error"] = %response.error + + except Exception as e: + error "asyncMarkMessageAsUnreadTask failed", message = e.msg + responseJson["error"] = %e.msg + + arg.finish(responseJson) \ No newline at end of file diff --git a/src/app_service/service/message/service.nim b/src/app_service/service/message/service.nim index fd81d2ca0f..9f105ceb41 100644 --- a/src/app_service/service/message/service.nim +++ b/src/app_service/service/message/service.nim @@ -63,6 +63,7 @@ const SIGNAL_RELOAD_MESSAGES* = "reloadMessages" const SIGNAL_URLS_UNFURLED* = "urlsUnfurled" const SIGNAL_GET_MESSAGE_FINISHED* = "getMessageFinished" const SIGNAL_URLS_UNFURLING_PLAN_READY* = "urlsUnfurlingPlanReady" +const SIGNAL_MESSAGE_MARKED_AS_UNREAD* = "messageMarkedAsUnread" include async_tasks @@ -90,6 +91,12 @@ type messageId*: string actionInitiatedBy*: string + MessageMarkMessageAsUnreadArgs* = ref object of Args + chatId*: string + messageId*: string + messagesCount*: int + messagesWithMentionsCount*: int + MessagesMarkedAsReadArgs* = ref object of Args chatId*: string allMessagesMarked*: bool @@ -581,6 +588,20 @@ QtObject: except Exception as e: error "error: ", procName="pinUnpinMessage", errName = e.name, errDesription = e.msg + proc asyncMarkMessageAsUnread*(self: Service, chatId: string, messageId: string) = + if (chatId.len == 0): + error "empty chat id", procName="markAllMessagesRead" + return + + let arg = AsyncMarkMessageAsUnreadTaskArg( + tptr: cast[ByteAddress](asyncMarkMessageAsUnreadTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onAsyncMarkMessageAsUnread", + messageId: messageId, + chatId: chatId + ) + self.threadpool.start(arg) + proc getMessageByMessageId*(self: Service, messageId: string): GetMessageResult = try: result = GetMessageResult() @@ -723,8 +744,13 @@ QtObject: let data = MessagesMarkedAsReadArgs(chatId: chatId, allMessagesMarked: true) self.events.emit(SIGNAL_MESSAGES_MARKED_AS_READ, data) - self.events.emit(SIGNAL_PARSE_RAW_ACTIVITY_CENTER_NOTIFICATIONS, - RawActivityCenterNotificationsArgs(activityCenterNotifications: responseObj{"activityCenterNotifications"})) + + var activityCenterNotifications: JsonNode + discard responseObj.getProp("activityCenterNotifications", activityCenterNotifications) + + if activityCenterNotifications != nil: + self.events.emit(SIGNAL_PARSE_RAW_ACTIVITY_CENTER_NOTIFICATIONS, + RawActivityCenterNotificationsArgs(activityCenterNotifications: activityCenterNotifications)) proc markAllMessagesRead*(self: Service, chatId: string) = if (chatId.len == 0): @@ -740,6 +766,49 @@ QtObject: self.threadpool.start(arg) + proc onAsyncMarkMessageAsUnread*(self: Service, response: string) {.slot.} = + try: + let responseObj = response.parseJson + + if responseObj.kind != JObject: + raise newException(RpcException, "markMessageAsUnread response is not a json object") + + var error: string + discard responseObj.getProp("error", error) + + if error.len > 0: + error "error: ", procName="onAsyncMarkMessageAsUnread", errDescription=error + return + + var chatId, messageId: string + var count, countWithMentions: int + + discard responseObj.getProp("chatId", chatId) + discard responseObj.getProp("messageId", messageId) + discard responseObj.getProp("messagesCount", count) + discard responseObj.getProp("messagesWithMentionsCount", countWithMentions) + + let data = MessageMarkMessageAsUnreadArgs( + chatId: chatId, + messageId: messageId, + messagesCount: count, + messagesWithMentionsCount: countWithMentions + ) + + self.chatService.updateUnreadMessage(chatId, count, countWithMentions) + + self.events.emit(SIGNAL_MESSAGE_MARKED_AS_UNREAD, data) + + var activityCenterNotifications: JsonNode + discard responseObj.getProp("activityCenterNotifications", activityCenterNotifications) + + if activityCenterNotifications != nil: + self.events.emit(SIGNAL_PARSE_RAW_ACTIVITY_CENTER_NOTIFICATIONS, + RawActivityCenterNotificationsArgs(activityCenterNotifications: activityCenterNotifications)) + + except Exception as e: + error "error: ", procName="markMessageAsUnread", errName = e.name, errDesription = e.msg + proc onMarkCertainMessagesRead*(self: Service, response: string) {.slot.} = let responseObj = response.parseJson @@ -774,8 +843,13 @@ QtObject: messagesCount: count, messagesWithMentionsCount: countWithMentions) self.events.emit(SIGNAL_MESSAGES_MARKED_AS_READ, data) - self.events.emit(SIGNAL_PARSE_RAW_ACTIVITY_CENTER_NOTIFICATIONS, - RawActivityCenterNotificationsArgs(activityCenterNotifications: responseObj{"activityCenterNotifications"})) + + var activityCenterNotifications: JsonNode + discard responseObj.getProp("activityCenterNotifications", activityCenterNotifications) + + if activityCenterNotifications != nil: + self.events.emit(SIGNAL_PARSE_RAW_ACTIVITY_CENTER_NOTIFICATIONS, + RawActivityCenterNotificationsArgs(activityCenterNotifications: activityCenterNotifications)) proc markCertainMessagesRead*(self: Service, chatId: string, messagesIds: seq[string]) = if (chatId.len == 0): diff --git a/src/backend/messages.nim b/src/backend/messages.nim index 06934d3d01..208f52c253 100644 --- a/src/backend/messages.nim +++ b/src/backend/messages.nim @@ -32,6 +32,10 @@ proc pinUnpinMessage*(chatId: string, messageId: string, pin: bool): RpcResponse }] result = callPrivateRPC("sendPinMessage".prefix, payload) +proc markMessageAsUnread*(chatId: string, messageId: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %*[chatId, messageId] + result = callPrivateRPC("markMessageAsUnread".prefix, payload) + proc getMessageByMessageId*(messageId: string): RpcResponse[JsonNode] {.raises: [Exception].} = let payload = %* [messageId] result = callPrivateRPC("messageByMessageID".prefix, payload) diff --git a/test/nim/message_model_test.nim b/test/nim/message_model_test.nim index 2d87e537bc..f5e7c149c9 100644 --- a/test/nim/message_model_test.nim +++ b/test/nim/message_model_test.nim @@ -352,3 +352,66 @@ suite "mark as seen": check(model.items[0].seen == true) check(model.items[1].seen == true) check(model.items[2].seen == true) + +suite "mark message as unread": + setup: + let model = newModel() + + var msg1 = createTestMessageItem("0xa", 1) + msg1.seen = true + var msg2 = createTestMessageItem("0xb", 2) + msg2.seen = true + var msg3 = createTestMessageItem("0xc", 3) + msg3.seen = true + + model.insertItemsBasedOnClock(@[msg1, msg2, msg3]) + require(model.items.len == 3) + check(model.items[0].seen == true) + check(model.items[1].seen == true) + check(model.items[2].seen == true) + + test "mark message as unread": + model.markMessageAsUnread("0xa") + check(model.items[2].seen == false) + + model.markMessageAsUnread("0xb") + check(model.items[1].seen == false) + + model.markMessageAsUnread("0xc") + check(model.items[0].seen == false) + + test "mark an already unread message as unread": + model.markMessageAsUnread("0xa") + check(model.items[2].seen == false) + model.markMessageAsUnread("0xa") + check(model.items[2].seen == false) + + model.markMessageAsUnread("0xb") + check(model.items[1].seen == false) + model.markMessageAsUnread("0xb") + check(model.items[1].seen == false) + + model.markMessageAsUnread("0xc") + check(model.items[0].seen == false) + model.markMessageAsUnread("0xc") + check(model.items[0].seen == false) + + test "mark all messages as unread": + require(model.items.len == 3) + + model.markMessageAsUnread("0xa") + model.markMessageAsUnread("0xc") + model.markMessageAsUnread("0xb") + + + # Because new row is inserted for message marker + require(model.items.len == 4) + + check(model.items[0].seen == false) + check(model.items[1].seen == false) + + # message marker is inserted on top of the last inserted element + # last inserted element is `0xc` which is at position 0 + # and marker is insert last at position : position('0xb') - 1 equals to position 2 here + check(model.items[2].seen == true) + check(model.items[3].seen == false) \ No newline at end of file diff --git a/ui/app/AppLayouts/Chat/stores/MessageStore.qml b/ui/app/AppLayouts/Chat/stores/MessageStore.qml index 0b17925e55..8999ee31d7 100644 --- a/ui/app/AppLayouts/Chat/stores/MessageStore.qml +++ b/ui/app/AppLayouts/Chat/stores/MessageStore.qml @@ -19,6 +19,7 @@ QtObject { readonly property int chatType: messageModule ? messageModule.chatType : Constants.chatType.unknown readonly property string chatColor: messageModule ? messageModule.chatColor : Style.current.blue readonly property string chatIcon: messageModule ? messageModule.chatIcon : "" + readonly property bool keepUnread: messageModule ? messageModule.keepUnread : false onMessageModuleChanged: { if(!messageModule) @@ -36,6 +37,14 @@ QtObject { messageModule.loadMoreMessages() } + function setKeepUnread(flag: bool) { + if (!messageModule) { + return + } + + messageModule.updateKeepUnread(flag) + } + function getMessageByIdAsJson (id) { if (!messageModule) { console.warn("getMessageByIdAsJson: Failed to parse message, because messageModule is not set") @@ -124,6 +133,13 @@ QtObject { messageModule.deleteMessage(messageId) } + function markMessageAsUnread(messageId) { + if (!messageModule) { + return + } + messageModule.markMessageAsUnread(messageId) + } + function warnAndDeleteMessage(messageId) { if (localAccountSensitiveSettings.showDeleteMessageWarning) Global.openDeleteMessagePopup(messageId, this) diff --git a/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml b/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml index 4bd0382417..a1c6d612fb 100644 --- a/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml @@ -52,13 +52,18 @@ Item { readonly property real scrollY: chatLogView.visibleArea.yPosition * chatLogView.contentHeight readonly property bool isMostRecentMessageInViewport: chatLogView.visibleArea.yPosition >= 0.999 - chatLogView.visibleArea.heightRatio readonly property var chatDetails: chatContentModule && chatContentModule.chatDetails || null + readonly property bool keepUnread: messageStore.keepUnread readonly property var loadMoreMessagesIfScrollBelowThreshold: Backpressure.oneInTimeQueued(root, 100, function() { if(scrollY < 1000) messageStore.loadMoreMessages() }) + function setKeepUnread(flag: bool) { + root.messageStore.setKeepUnread(flag) + } + function markAllMessagesReadIfMostRecentMessageIsInViewport() { - if (!isMostRecentMessageInViewport || !chatLogView.visible) { + if (!isMostRecentMessageInViewport || !chatLogView.visible || keepUnread) { return } @@ -107,6 +112,7 @@ Item { target: !!d.chatDetails ? d.chatDetails : null function onActiveChanged() { + d.setKeepUnread(false) d.markAllMessagesReadIfMostRecentMessageIsInViewport() d.loadMoreMessagesIfScrollBelowThreshold() } diff --git a/ui/imports/shared/views/chat/MessageContextMenuView.qml b/ui/imports/shared/views/chat/MessageContextMenuView.qml index c63edab14e..cee4df68c0 100644 --- a/ui/imports/shared/views/chat/MessageContextMenuView.qml +++ b/ui/imports/shared/views/chat/MessageContextMenuView.qml @@ -47,6 +47,7 @@ StatusMenu { signal toggleReaction(string messageId, int emojiId) signal deleteMessage(string messageId) signal editClicked(string messageId) + signal markMessageAsUnread(string messageId) width: Math.max(emojiContainer.visible ? emojiContainer.width : 0, 230) @@ -153,6 +154,17 @@ StatusMenu { } } + StatusAction { + id: markMessageAsUnreadAction + text: qsTr("Mark as unread") + icon.name: "hide" + enabled: !root.disabledForChat + onTriggered: { + root.markMessageAsUnread(root.messageId) + root.close() + } + } + StatusMenuSeparator { visible: deleteMessageAction.enabled && (replyToMenuItem.enabled || diff --git a/ui/imports/shared/views/chat/MessageView.qml b/ui/imports/shared/views/chat/MessageView.qml index 87e95856db..dec306d5b5 100644 --- a/ui/imports/shared/views/chat/MessageView.qml +++ b/ui/imports/shared/views/chat/MessageView.qml @@ -980,6 +980,21 @@ Loader { } } }, + Loader { + active: !root.editModeOn && delegate.hovered && !delegate.hideQuickActions + visible: active + sourceComponent: StatusFlatRoundButton { + objectName: "markAsUnreadButton" + width: d.chatButtonSize + height: d.chatButtonSize + icon.name: "hide" + type: StatusFlatRoundButton.Type.Tertiary + tooltip.text: qsTr("Mark as unread") + onClicked: { + root.messageStore.markMessageAsUnread(root.messageId) + } + } + }, Loader { active: { if(!delegate.hovered) @@ -1102,6 +1117,10 @@ Loader { root.chatId) } + onMarkMessageAsUnread: (messageId) => { + root.messageStore.markMessageAsUnread(messageId) + } + onToggleReaction: (messageId, emojiId) => { root.messageStore.toggleReaction(messageId, emojiId) } diff --git a/vendor/status-go b/vendor/status-go index fe604b2806..271778a1e0 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit fe604b2806dfa51ea748c5e61c2a87cc7ffd9c4a +Subproject commit 271778a1e07e585a12790b4e2226f13e36ea89f4