From 16a33f8fa7832394209c18776d9c349e656a86ad Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Wed, 22 Dec 2021 13:00:44 +0100 Subject: [PATCH] refactor(@desktop/chat-messages): load more messages - load more messages on scroll up for chat/channel added - sending messages improved in terms of adding new messages to appropriate position - scroll to message added on the qml side - qml connected to the sending message success/failed signals --- .../chat_content/messages/controller.nim | 11 +- .../messages/controller_interface.nim | 3 + .../chat_content/messages/module.nim | 95 +++++--- .../module_controller_delegate_interface.nim | 6 + .../module_view_delegate_interface.nim | 3 + .../chat_content/messages/view.nim | 45 +++- .../main/chat_section/chat_content/module.nim | 2 + .../modules/shared_models/message_item.nim | 2 +- .../modules/shared_models/message_model.nim | 23 ++ src/app_service/service/chat/service.nim | 8 +- .../service/message/async_tasks.nim | 49 ++-- src/app_service/service/message/service.nim | 41 +++- .../AppLayouts/Chat/stores/MessageStore.qml | 10 + .../Chat/views/ChatMessagesView.qml | 224 ++++++++++-------- 14 files changed, 348 insertions(+), 174 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 3b40d16aad..2d89679e6c 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 @@ -56,7 +56,13 @@ method init*(self: Controller) = let args = MessageSendingSuccess(e) if(self.chatId != args.chat.id): return - self.delegate.newMessagesLoaded(@[args.message], @[], @[]) + self.delegate.onSendingMessageSuccess(args.message) + + self.events.on(SIGNAL_SENDING_FAILED) do(e:Args): + let args = ChatArgs(e) + if(self.chatId != args.chatId): + return + self.delegate.onSendingMessageError() self.events.on(SIGNAL_MESSAGE_PINNED) do(e:Args): let args = MessagePinUnpinArgs(e) @@ -100,6 +106,9 @@ method getOneToOneChatNameAndImage*(self: Controller): tuple[name: string, image method belongsToCommunity*(self: Controller): bool = return self.belongsToCommunity +method loadMoreMessages*(self: Controller) = + self.messageService.asyncLoadMoreMessagesForChat(self.chatId) + method addReaction*(self: Controller, messageId: string, emojiId: int) = self.messageService.addReaction(self.chatId, messageId, emojiId) diff --git a/src/app/modules/main/chat_section/chat_content/messages/controller_interface.nim b/src/app/modules/main/chat_section/chat_content/messages/controller_interface.nim index bc4392861c..0e8a3a527a 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/controller_interface.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/controller_interface.nim @@ -31,6 +31,9 @@ method getOneToOneChatNameAndImage*(self: AccessInterface): tuple[name: string, method belongsToCommunity*(self: AccessInterface): bool {.base.} = raise newException(ValueError, "No implementation available") +method loadMoreMessages*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + method addReaction*(self: AccessInterface, messageId: string, emojiId: int) {.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 76c9dff13b..953ed6fb07 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 @@ -81,44 +81,73 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se pinnedMessages: seq[PinnedMessageDto]) = var viewItems: seq[Item] - for m in messages: - let sender = self.controller.getContactById(m.`from`) - let senderDisplayName = sender.userNameOrAlias() - let amISender = m.`from` == singletonInstance.userProfile.getPubKey() - var senderIcon = sender.identicon - var isSenderIconIdenticon = sender.identicon.len > 0 - if(sender.image.thumbnail.len > 0): - senderIcon = sender.image.thumbnail - isSenderIconIdenticon = false + if(messages.len > 0): + for m in messages: + let sender = self.controller.getContactById(m.`from`) + let senderDisplayName = sender.userNameOrAlias() + let amISender = m.`from` == singletonInstance.userProfile.getPubKey() + var senderIcon = sender.identicon + var isSenderIconIdenticon = sender.identicon.len > 0 + if(sender.image.thumbnail.len > 0): + senderIcon = sender.image.thumbnail + isSenderIconIdenticon = false - var item = initItem(m.id, m.responseTo, m.`from`, senderDisplayName, sender.localNickname, senderIcon, - isSenderIconIdenticon, amISender, m.outgoingStatus, m.text, m.image, m.seen, m.timestamp, m.contentType.ContentType, - m.messageType) + var item = initItem(m.id, m.responseTo, m.`from`, senderDisplayName, sender.localNickname, senderIcon, + isSenderIconIdenticon, amISender, m.outgoingStatus, m.text, m.image, m.seen, m.timestamp, m.contentType.ContentType, + m.messageType) - for r in reactions: - if(r.messageId == m.id): - var emojiIdAsEnum: EmojiId - if(message_reaction_item.toEmojiIdAsEnum(r.emojiId, emojiIdAsEnum)): - let userWhoAddedThisReaction = self.controller.getContactById(r.`from`) - let didIReactWithThisEmoji = userWhoAddedThisReaction.id == singletonInstance.userProfile.getPubKey() - item.addReaction(emojiIdAsEnum, didIReactWithThisEmoji, userWhoAddedThisReaction.id, - userWhoAddedThisReaction.userNameOrAlias(), r.id) - else: - error "wrong emoji id found when loading messages" + for r in reactions: + if(r.messageId == m.id): + var emojiIdAsEnum: EmojiId + if(message_reaction_item.toEmojiIdAsEnum(r.emojiId, emojiIdAsEnum)): + let userWhoAddedThisReaction = self.controller.getContactById(r.`from`) + let didIReactWithThisEmoji = userWhoAddedThisReaction.id == singletonInstance.userProfile.getPubKey() + item.addReaction(emojiIdAsEnum, didIReactWithThisEmoji, userWhoAddedThisReaction.id, + userWhoAddedThisReaction.userNameOrAlias(), r.id) + else: + error "wrong emoji id found when loading messages" - for p in pinnedMessages: - if(p.message.id == m.id): - item.pinned = true + for p in pinnedMessages: + if(p.message.id == m.id): + item.pinned = true - # messages are sorted from the most recent to the least recent one - viewItems.add(item) + # messages are sorted from the most recent to the least recent one + viewItems.add(item) - # ChatIdentifier message will be always the first message (the oldest one) - viewItems.add(self.createChatIdentifierItem()) - # Delete the old ChatIdentifier message first - self.view.model().removeItem(CHAT_IDENTIFIER_MESSAGE_ID) - # Add new loaded messages - self.view.model().prependItems(viewItems) + # ChatIdentifier message will be always the first message (the oldest one) + viewItems.add(self.createChatIdentifierItem()) + # Delete the old ChatIdentifier message first + self.view.model().removeItem(CHAT_IDENTIFIER_MESSAGE_ID) + # Add new loaded messages + self.view.model().appendItems(viewItems) + + if(not self.view.getInitialMessagesLoaded()): + self.view.initialMessagesAreLoaded() + + self.view.setLoadingHistoryMessagesInProgress(false) + +method onSendingMessageSuccess*(self: Module, message: MessageDto) = + let sender = self.controller.getContactById(message.`from`) + let senderDisplayName = sender.userNameOrAlias() + let amISender = message.`from` == singletonInstance.userProfile.getPubKey() + var senderIcon = sender.identicon + var isSenderIconIdenticon = sender.identicon.len > 0 + if(sender.image.thumbnail.len > 0): + senderIcon = sender.image.thumbnail + isSenderIconIdenticon = false + + var item = initItem(message.id, message.responseTo, message.`from`, senderDisplayName, sender.localNickname, + senderIcon, isSenderIconIdenticon, amISender, message.outgoingStatus, message.text, message.image, message.seen, + message.timestamp, message.contentType.ContentType, message.messageType) + + self.view.model().prependItem(item) + self.view.emitSendingMessageSuccessSignal() + +method onSendingMessageError*(self: Module) = + self.view.emitSendingMessageErrorSignal() + +method loadMoreMessages*(self: Module) = + self.controller.loadMoreMessages() method toggleReaction*(self: Module, messageId: string, emojiId: int) = var emojiIdAsEnum: EmojiId diff --git a/src/app/modules/main/chat_section/chat_content/messages/private_interfaces/module_controller_delegate_interface.nim b/src/app/modules/main/chat_section/chat_content/messages/private_interfaces/module_controller_delegate_interface.nim index dcf526b5cf..e0a08c2d53 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/private_interfaces/module_controller_delegate_interface.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/private_interfaces/module_controller_delegate_interface.nim @@ -11,4 +11,10 @@ method onReactionRemoved*(self: AccessInterface, messageId: string, emojiId: int raise newException(ValueError, "No implementation available") method onPinUnpinMessage*(self: AccessInterface, messageId: string, pin: bool) {.base.} = + raise newException(ValueError, "No implementation available") + +method onSendingMessageSuccess*(self: AccessInterface, message: MessageDto) {.base.} = + raise newException(ValueError, "No implementation available") + +method onSendingMessageError*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/chat_section/chat_content/messages/private_interfaces/module_view_delegate_interface.nim b/src/app/modules/main/chat_section/chat_content/messages/private_interfaces/module_view_delegate_interface.nim index 48b60d135a..144c70cb8d 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/private_interfaces/module_view_delegate_interface.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/private_interfaces/module_view_delegate_interface.nim @@ -1,6 +1,9 @@ method viewDidLoad*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") +method loadMoreMessages*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + method toggleReaction*(self: AccessInterface, messageId: string, emojiId: int) {.base.} = raise newException(ValueError, "No implementation available") 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 cb640e2b3f..79695e5de7 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 @@ -8,6 +8,8 @@ QtObject: delegate: io_interface.AccessInterface model: Model modelVariant: QVariant + initialMessagesLoaded: bool + loadingHistoryMessagesInProgress: bool proc delete*(self: View) = self.model.delete @@ -20,6 +22,8 @@ QtObject: result.delegate = delegate result.model = newModel() result.modelVariant = newQVariant(result.model) + result.initialMessagesLoaded = false + result.loadingHistoryMessagesInProgress = false proc load*(self: View) = self.delegate.viewDidLoad() @@ -29,7 +33,6 @@ QtObject: proc getModel(self: View): QVariant {.slot.} = return self.modelVariant - QtProperty[QVariant] model: read = getModel @@ -64,4 +67,42 @@ QtObject: return self.delegate.amIChatAdmin() proc getNumberOfPinnedMessages*(self: View): int {.slot.} = - return self.delegate.getNumberOfPinnedMessages() \ No newline at end of file + return self.delegate.getNumberOfPinnedMessages() + + proc initialMessagesLoadedChanged*(self: View) {.signal.} + proc getInitialMessagesLoaded*(self: View): bool {.slot.} = + return self.initialMessagesLoaded + QtProperty[bool] initialMessagesLoaded: + read = getInitialMessagesLoaded + notify = initialMessagesLoadedChanged + + proc initialMessagesAreLoaded*(self: View) = # this is not a slot + if (self.initialMessagesLoaded): + return + self.initialMessagesLoaded = true + self.initialMessagesLoadedChanged() + + proc loadingHistoryMessagesInProgressChanged*(self: View) {.signal.} + proc getLoadingHistoryMessagesInProgress*(self: View): bool {.slot.} = + return self.loadingHistoryMessagesInProgress + QtProperty[bool] loadingHistoryMessagesInProgress: + read = getLoadingHistoryMessagesInProgress + notify = loadingHistoryMessagesInProgressChanged + + proc setLoadingHistoryMessagesInProgress*(self: View, value: bool) = # this is not a slot + if (value == self.loadingHistoryMessagesInProgress): + return + self.loadingHistoryMessagesInProgress = value + self.loadingHistoryMessagesInProgressChanged() + + proc loadMoreMessages*(self: View) {.slot.} = + self.setLoadingHistoryMessagesInProgress(true) + self.delegate.loadMoreMessages() + + proc messageSuccessfullySent*(self: View) {.signal.} + proc emitSendingMessageSuccessSignal*(self: View) = + self.messageSuccessfullySent() + + proc sendingMessageFailed*(self: View) {.signal.} + proc emitSendingMessageErrorSignal*(self: View) = + self.sendingMessageFailed() \ No newline at end of file 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 2803d0a680..09ebd7ae38 100644 --- a/src/app/modules/main/chat_section/chat_content/module.nim +++ b/src/app/modules/main/chat_section/chat_content/module.nim @@ -173,6 +173,8 @@ method newPinnedMessagesLoaded*(self: Module, pinnedMessages: seq[PinnedMessageD viewItems = item & viewItems # messages are sorted from the most recent to the least recent one + if(viewItems.len == 0): + return self.view.pinnedModel().prependItems(viewItems) method unpinMessage*(self: Module, messageId: string) = diff --git a/src/app/modules/shared_models/message_item.nim b/src/app/modules/shared_models/message_item.nim index ac3501cd45..97dad56abe 100644 --- a/src/app/modules/shared_models/message_item.nim +++ b/src/app/modules/shared_models/message_item.nim @@ -1,4 +1,4 @@ -import Tables, json, strformat +import json, strformat import ../../../app_service/common/types export types.ContentType diff --git a/src/app/modules/shared_models/message_model.nim b/src/app/modules/shared_models/message_model.nim index ec94e56e16..a9b4f89ef8 100644 --- a/src/app/modules/shared_models/message_model.nim +++ b/src/app/modules/shared_models/message_model.nim @@ -158,6 +158,20 @@ QtObject: self.endInsertRows() self.countChanged() + proc appendItems*(self: Model, items: seq[Item]) = + if(items.len == 0): + return + + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + + let first = self.items.len + let last = first + items.len - 1 + self.beginInsertRows(parentModelIndex, first, last) + self.items.add(items) + self.endInsertRows() + self.countChanged() + proc appendItem*(self: Model, item: Item) = let parentModelIndex = newQModelIndex() defer: parentModelIndex.delete @@ -167,6 +181,15 @@ QtObject: self.endInsertRows() self.countChanged() + proc prependItem*(self: Model, item: Item) = + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + + self.beginInsertRows(parentModelIndex, 0, 0) + self.items.insert(item, 0) + self.endInsertRows() + self.countChanged() + proc removeItem*(self: Model, messageId: string) = let ind = self.findIndexForMessageId(messageId) if(ind == -1): diff --git a/src/app_service/service/chat/service.nim b/src/app_service/service/chat/service.nim index 08458d6cfc..439d79fb69 100644 --- a/src/app_service/service/chat/service.nim +++ b/src/app_service/service/chat/service.nim @@ -111,8 +111,8 @@ QtObject: proc processMessageUpdateAfterSend*(self: Service, response: RpcResponse[JsonNode]): (seq[ChatDto], seq[MessageDto]) = result = self.parseChatResponse(response) var (chats, messages) = result - if chats.len == 0 and messages.len == 0: - self.events.emit(SIGNAL_SENDING_FAILED, Args()) + if chats.len == 0 or messages.len == 0: + error "no chats or messages in the parsed response" return # This fixes issue#3490 @@ -251,7 +251,9 @@ QtObject: preferredUsername, communityId) - discard self.processMessageUpdateAfterSend(response) + let (chats, messages) = self.processMessageUpdateAfterSend(response) + if chats.len == 0 or messages.len == 0: + self.events.emit(SIGNAL_SENDING_FAILED, ChatArgs(chatId: chatId)) except Exception as e: error "Error sending message", msg = e.msg diff --git a/src/app_service/service/message/async_tasks.nim b/src/app_service/service/message/async_tasks.nim index ce685da02f..acd2164f0a 100644 --- a/src/app_service/service/message/async_tasks.nim +++ b/src/app_service/service/message/async_tasks.nim @@ -14,34 +14,37 @@ type const asyncFetchChatMessagesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let arg = decode[AsyncFetchChatMessagesTaskArg](argEncoded) + var responseJson = %*{ + "chatId": arg.chatId + } + # handle messages - var messagesArr: JsonNode - var messagesCursor: string - let msgsResponse = status_go.fetchMessages(arg.chatId, arg.msgCursor, arg.limit) - discard msgsResponse.result.getProp("cursor", messagesCursor) - discard msgsResponse.result.getProp("messages", messagesArr) + if(arg.msgCursor != CURSOR_VALUE_IGNORE): + var messagesArr: JsonNode + var messagesCursor: JsonNode + let msgsResponse = status_go.fetchMessages(arg.chatId, arg.msgCursor, arg.limit) + discard msgsResponse.result.getProp("cursor", messagesCursor) + discard msgsResponse.result.getProp("messages", messagesArr) + responseJson["messages"] = messagesArr + responseJson["messagesCursor"] = messagesCursor # handle pinned messages - var pinnedMsgArr: JsonNode - var pinnedMsgCursor: string - let pinnedMsgsResponse = status_go.fetchPinnedMessages(arg.chatId, arg.pinnedMsgCursor, arg.limit) - discard pinnedMsgsResponse.result.getProp("cursor", pinnedMsgCursor) - discard pinnedMsgsResponse.result.getProp("pinnedMessages", pinnedMsgArr) + if(arg.pinnedMsgCursor != CURSOR_VALUE_IGNORE): + var pinnedMsgArr: JsonNode + var pinnedMsgCursor: JsonNode + let pinnedMsgsResponse = status_go.fetchPinnedMessages(arg.chatId, arg.pinnedMsgCursor, arg.limit) + discard pinnedMsgsResponse.result.getProp("cursor", pinnedMsgCursor) + discard pinnedMsgsResponse.result.getProp("pinnedMessages", pinnedMsgArr) + responseJson["pinnedMessages"] = pinnedMsgArr + responseJson["pinnedMessagesCursor"] = pinnedMsgCursor # handle reactions - var reactionsArr: JsonNode - # messages and reactions are using the same cursor - let rResponse = status_go.fetchReactions(arg.chatId, arg.msgCursor, arg.limit) - reactionsArr = rResponse.result - - let responseJson = %*{ - "chatId": arg.chatId, - "messages": messagesArr, - "messagesCursor": messagesCursor, - "pinnedMessages": pinnedMsgArr, - "pinnedMessagesCursor": pinnedMsgCursor, - "reactions": reactionsArr - } + if(arg.msgCursor != CURSOR_VALUE_IGNORE): + # messages and reactions are using the same cursor + var reactionsArr: JsonNode + let rResponse = status_go.fetchReactions(arg.chatId, arg.msgCursor, arg.limit) + reactionsArr = rResponse.result + responseJson["pinnedMessages"] = reactionsArr arg.finish(responseJson) diff --git a/src/app_service/service/message/service.nim b/src/app_service/service/message/service.nim index 803278e8b6..f9bb93292e 100644 --- a/src/app_service/service/message/service.nim +++ b/src/app_service/service/message/service.nim @@ -13,12 +13,11 @@ export message_dto export pinned_msg_dto export reaction_dto -include async_tasks - logScope: topics = "messages-service" const MESSAGES_PER_PAGE = 20 +const CURSOR_VALUE_IGNORE = "ignore" # Signals which may be emitted by this service: const SIGNAL_MESSAGES_LOADED* = "new-messagesLoaded" #Once we are done with refactoring we should remove "new-" from all signals @@ -29,6 +28,8 @@ const SIGNAL_MESSAGES_MARKED_AS_READ* = "new-messagesMarkedAsRead" const SIGNAL_MESSAGE_REACTION_ADDED* = "new-messageReactionAdded" const SIGNAL_MESSAGE_REACTION_REMOVED* = "new-messageReactionRemoved" +include async_tasks + type SearchMessagesLoadedArgs* = ref object of Args messages*: seq[MessageDto] @@ -59,7 +60,9 @@ QtObject: events: EventEmitter threadpool: ThreadPool msgCursor: Table[string, string] + lastUsedMsgCursor: Table[string, string] pinnedMsgCursor: Table[string, string] + lastUsedPinnedMsgCursor: Table[string, string] numOfPinnedMessagesPerChat: Table[string, int] # [chat_id, num_of_pinned_messages] proc delete*(self: Service) = @@ -71,7 +74,9 @@ QtObject: result.events = events result.threadpool = threadpool result.msgCursor = initTable[string, string]() + result.lastUsedMsgCursor = initTable[string, string]() result.pinnedMsgCursor = initTable[string, string]() + result.lastUsedPinnedMsgCursor = initTable[string, string]() proc initialMessagesFetched(self: Service, chatId: string): bool = return self.msgCursor.hasKey(chatId) @@ -99,11 +104,18 @@ QtObject: var chatId: string discard responseObj.getProp("chatId", chatId) + + # this is important case we don't want to fetch the same messages multiple times. + self.lastUsedMsgCursor[chatId] = self.msgCursor[chatId] + self.lastUsedPinnedMsgCursor[chatId] = self.pinnedMsgCursor[chatId] # handling messages var msgCursor: string if(responseObj.getProp("messagesCursor", msgCursor)): - self.msgCursor[chatId] = msgCursor + if(msgCursor.len > 0): + self.msgCursor[chatId] = msgCursor + else: + self.msgCursor[chatId] = self.lastUsedMsgCursor[chatId] var messagesArr: JsonNode var messages: seq[MessageDto] @@ -113,7 +125,10 @@ QtObject: # handling pinned messages var pinnedMsgCursor: string if(responseObj.getProp("pinnedMessagesCursor", pinnedMsgCursor)): - self.pinnedMsgCursor[chatId] = pinnedMsgCursor + if(pinnedMsgCursor.len > 0): + self.pinnedMsgCursor[chatId] = pinnedMsgCursor + else: + self.pinnedMsgCursor[chatId] = self.lastUsedPinnedMsgCursor[chatId] var pinnedMsgArr: JsonNode var pinnedMessages: seq[PinnedMessageDto] @@ -142,13 +157,27 @@ QtObject: error "empty chat id", methodName="asyncLoadMoreMessagesForChat" return + var msgCursor = self.getCurrentMessageCursor(chatId) + if(self.lastUsedMsgCursor.hasKey(chatId) and msgCursor == self.lastUsedMsgCursor[chatId]): + msgCursor = CURSOR_VALUE_IGNORE + + var pinnedMsgCursor = self.getCurrentPinnedMessageCursor(chatId) + if(self.lastUsedPinnedMsgCursor.hasKey(chatId) and pinnedMsgCursor == self.lastUsedPinnedMsgCursor[chatId]): + pinnedMsgCursor = CURSOR_VALUE_IGNORE + + if(msgCursor == CURSOR_VALUE_IGNORE and pinnedMsgCursor == CURSOR_VALUE_IGNORE): + # it's important to emit signal in case we are not fetching messages, so we can update the view appropriatelly. + let data = MessagesLoadedArgs(chatId: chatId) + self.events.emit(SIGNAL_MESSAGES_LOADED, data) + return + let arg = AsyncFetchChatMessagesTaskArg( tptr: cast[ByteAddress](asyncFetchChatMessagesTask), vptr: cast[ByteAddress](self.vptr), slot: "onAsyncLoadMoreMessagesForChat", chatId: chatId, - msgCursor: self.getCurrentMessageCursor(chatId), - pinnedMsgCursor: self.getCurrentPinnedMessageCursor(chatId), + msgCursor: msgCursor, + pinnedMsgCursor: pinnedMsgCursor, limit: MESSAGES_PER_PAGE ) diff --git a/ui/app/AppLayouts/Chat/stores/MessageStore.qml b/ui/app/AppLayouts/Chat/stores/MessageStore.qml index c63623f13d..c71ed4a589 100644 --- a/ui/app/AppLayouts/Chat/stores/MessageStore.qml +++ b/ui/app/AppLayouts/Chat/stores/MessageStore.qml @@ -5,6 +5,16 @@ QtObject { id: root property var messageModule + property var messagesModel: messageModule.model + + function loadMoreMessages () { + if(!messageModule) + return + if(!messageModule.initialMessagesLoaded || messageModule.loadingHistoryMessagesInProgress) + return + + messageModule.loadMoreMessages() + } function getMessageByIdAsJson (id) { if(!messageModule) diff --git a/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml b/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml index 7c3befa8b1..5a85aea81d 100644 --- a/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml @@ -36,9 +36,33 @@ Item { property int countOnStartUp: 0 signal openStickerPackPopup(string stickerPackId) + Item { + id: loadingMessagesIndicator + visible: messageStore.messageModule.loadingHistoryMessagesInProgress + anchors.top: parent.top + anchors.left: parent.left + height: visible? 20 : 0 + width: parent.width + + Loader { + active: messageStore.messageModule.loadingHistoryMessagesInProgress + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + sourceComponent: Component { + LoadingAnimation { + width: 18 + height: 18 + } + } + } + } + ListView { id: chatLogView - anchors.fill: parent + anchors.top: loadingMessagesIndicator.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right spacing: 0 boundsBehavior: Flickable.StopAtBounds clip: true @@ -92,9 +116,9 @@ Item { // } // } -// ScrollBar.vertical: ScrollBar { -// visible: chatLogView.visibleArea.heightRatio < 1 -// } + ScrollBar.vertical: ScrollBar { + visible: chatLogView.visibleArea.heightRatio < 1 + } // Connections { // id: contentHeightConnection @@ -112,80 +136,79 @@ Item { id: timer } -// Button { -// readonly property int buttonPadding: 5 + Button { + readonly property int buttonPadding: 5 -// id: scrollDownButton -// visible: false -// height: 32 -// width: nbMessages.width + arrowImage.width + 2 * Style.current.halfPadding + (nbMessages.visible ? scrollDownButton.buttonPadding : 0) -// anchors.bottom: parent.bottom -// anchors.right: parent.right -// anchors.rightMargin: Style.current.padding -// background: Rectangle { -// color: Style.current.buttonSecondaryColor -// border.width: 0 -// radius: 16 -// } -// onClicked: { -// newMessages = 0 -// scrollDownButton.visible = false -// chatLogView.scrollToBottom(true) -// } + id: scrollDownButton + visible: false + height: 32 + width: nbMessages.width + arrowImage.width + 2 * Style.current.halfPadding + (nbMessages.visible ? scrollDownButton.buttonPadding : 0) + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.rightMargin: Style.current.padding + background: Rectangle { + color: Style.current.buttonSecondaryColor + border.width: 0 + radius: 16 + } + onClicked: { + newMessages = 0 + scrollDownButton.visible = false + chatLogView.scrollToBottom(true) + } -// StyledText { -// id: nbMessages -// visible: newMessages > 0 -// width: visible ? implicitWidth : 0 -// text: newMessages -// anchors.verticalCenter: parent.verticalCenter -// anchors.left: parent.left -// color: Style.current.pillButtonTextColor -// font.pixelSize: 15 -// anchors.leftMargin: Style.current.halfPadding -// } + StyledText { + id: nbMessages + visible: newMessages > 0 + width: visible ? implicitWidth : 0 + text: newMessages + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + color: Style.current.pillButtonTextColor + font.pixelSize: 15 + anchors.leftMargin: Style.current.halfPadding + } -// SVGImage { -// id: arrowImage -// width: 24 -// height: 24 -// anchors.verticalCenter: parent.verticalCenter -// anchors.left: nbMessages.right -// source: Style.svg("leave_chat") -// anchors.leftMargin: nbMessages.visible ? scrollDownButton.buttonPadding : 0 -// rotation: -90 + SVGImage { + id: arrowImage + width: 24 + height: 24 + anchors.verticalCenter: parent.verticalCenter + anchors.left: nbMessages.right + source: Style.svg("leave_chat") + anchors.leftMargin: nbMessages.visible ? scrollDownButton.buttonPadding : 0 + rotation: -90 -// ColorOverlay { -// anchors.fill: parent -// source: parent -// color: Style.current.pillButtonTextColor -// } -// } + ColorOverlay { + anchors.fill: parent + source: parent + color: Style.current.pillButtonTextColor + } + } -// MouseArea { -// cursorShape: Qt.PointingHandCursor -// anchors.fill: parent -// onPressed: mouse.accepted = false -// } -// } + MouseArea { + cursorShape: Qt.PointingHandCursor + anchors.fill: parent + onPressed: mouse.accepted = false + } + } function scrollToBottom(force, caller) { - // Not Refactored Yet -// if (!force && !chatLogView.atYEnd) { -// // User has scrolled up, we don't want to scroll back -// return false -// } -// if (caller && caller !== chatLogView.itemAtIndex(chatLogView.count - 1)) { -// // If we have a caller, only accept its request if it's the last message -// return false -// } -// // Call this twice and with a timer since the first scroll to bottom might have happened before some stuff loads -// // meaning that the scroll will not actually be at the bottom on switch -// // Add a small delay because images, even though they say they say they are loaed, they aren't shown yet -// Qt.callLater(chatLogView.positionViewAtBeginning) -// timer.setTimeout(function() { -// Qt.callLater(chatLogView.positionViewAtBeginning) -// }, 100); + if (!force && !chatLogView.atYEnd) { + // User has scrolled up, we don't want to scroll back + return false + } + if (caller && caller !== chatLogView.itemAtIndex(chatLogView.count - 1)) { + // If we have a caller, only accept its request if it's the last message + return false + } + // Call this twice and with a timer since the first scroll to bottom might have happened before some stuff loads + // meaning that the scroll will not actually be at the bottom on switch + // Add a small delay because images, even though they say they say they are loaed, they aren't shown yet + Qt.callLater(chatLogView.positionViewAtBeginning) + timer.setTimeout(function() { + Qt.callLater(chatLogView.positionViewAtBeginning) + }, 100); return true } @@ -198,24 +221,24 @@ Item { // } // } -// Connections { - // Not Refactored Yet -// target: root.store.chatsModelInst.messageView + Connections { + target: messageStore.messageModule -// onSendingMessageSuccess: { -// chatLogView.scrollToBottom(true) -// } + onMessageSuccessfullySent: { + chatLogView.scrollToBottom(true) + } -// onSendingMessageFailed: { -// sendingMsgFailedPopup.open(); -// } + onSendingMessageFailed: { + sendingMsgFailedPopup.open(); + } + // Not Refactored Yet // onNewMessagePushed: { // if (!chatLogView.scrollToBottom()) { // newMessages++ // } // } -// } + } // Connections { // Not Refactored Yet @@ -274,24 +297,15 @@ Item { // } // } - // Not Refactored Yet -// property var loadMsgs : Backpressure.oneInTime(chatLogView, 500, function() { -// if(!messages.initialMessagesLoaded || messages.loadingHistoryMessages) -// return + onContentYChanged: { + scrollDownButton.visible = contentHeight - (scrollY + height) > 400 + let loadMore = scrollDownButton.visible && scrollY < 500 + if(loadMore){ + messageStore.loadMoreMessages() + } + } -// root.store.chatsModelInst.messageView.loadMoreMessages(chatId); -// }); - -// onContentYChanged: { -// scrollDownButton.visible = (contentHeight - (scrollY + height) > 400) -// if(scrollDownButton.visible && scrollY < 500){ -// loadMsgs(); -// } -// } - - model: messageStore.messageModule.model - section.property: "sectionIdentifier" - section.criteria: ViewSection.FullString + model: messageStore.messagesModel // Not Refactored Yet //Component.onCompleted: scrollToBottom(true) @@ -330,11 +344,11 @@ Item { } } -// MessageDialog { -// id: sendingMsgFailedPopup -// standardButtons: StandardButton.Ok -// //% "Failed to send message." -// text: qsTrId("failed-to-send-message-") -// icon: StandardIcon.Critical -// } + MessageDialog { + id: sendingMsgFailedPopup + standardButtons: StandardButton.Ok + //% "Failed to send message." + text: qsTrId("failed-to-send-message-") + icon: StandardIcon.Critical + } }