From b580f0a8101e00a4d5bf9758c8ceee90ee13840a Mon Sep 17 00:00:00 2001 From: mprakhov Date: Fri, 24 Mar 2023 13:41:33 +0100 Subject: [PATCH] feat:(@desktop/channels): loading state when switching channels/chats --- .../chat_content/messages/controller.nim | 10 ++- .../chat_content/messages/io_interface.nim | 6 +- .../chat_content/messages/module.nim | 14 ++-- .../chat_content/messages/view.nim | 18 ++++- .../main/chat_section/chat_content/module.nim | 1 - .../service/message/async_tasks.nim | 34 +++++++++- src/app_service/service/message/service.nim | 42 ++++++++---- .../Chat/views/ChatMessagesView.qml | 28 +++++++- .../Chat/views/MessagesLoadingView.qml | 65 +++++++++++++++++++ 9 files changed, 193 insertions(+), 25 deletions(-) create mode 100644 ui/app/AppLayouts/Chat/views/MessagesLoadingView.qml 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 2d8df6ce50..86b069136c 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 @@ -212,6 +212,12 @@ proc init*(self: Controller) = if (community.id == self.sectionId): self.delegate.updateCommunityDetails(community) + self.events.on(SIGNAL_MESSAGE_FIRST_UNSEEN) do(e: Args): + let args = MessageFirstUnseen(e) + if (args.chatId != self.chatId): + return + self.delegate.onFirstUnseenMessageId(args.messageId) + proc getMySectionId*(self: Controller): string = return self.sectionId @@ -280,8 +286,8 @@ proc setSearchedMessageId*(self: Controller, searchedMessageId: string) = proc clearSearchedMessageId*(self: Controller) = self.setSearchedMessageId("") -proc getFirstUnseenMessageId*(self: Controller): string = - self.messageService.getFirstUnseenMessageIdFor(self.chatId) +proc getAsyncFirstUnseenMessageId*(self: Controller) = + self.messageService.getAsyncFirstUnseenMessageId(self.chatId) proc getLoadingMessagesPerPageFactor*(self: Controller): int = return self.loadingMessagesPerPageFactor 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 264e25fa86..f1d3685245 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 @@ -153,11 +153,11 @@ method resendChatMessage*(self: AccessInterface, messageId: string): string = method resetNewMessagesMarker*(self: AccessInterface) = raise newException(ValueError, "No implementation available") -method scrollToNewMessagesMarker*(self: AccessInterface) = - raise newException(ValueError, "No implementation available") - method markAllMessagesRead*(self: AccessInterface) = raise newException(ValueError, "No implementation available") method updateCommunityDetails*(self: AccessInterface, community: CommunityDto) = + raise newException(ValueError, "No implementation available") + +method onFirstUnseenMessageId*(self: AccessInterface, messageId: string) = raise newException(ValueError, "No implementation available") \ No newline at end of file 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 2bee71000b..02a76933d1 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 @@ -681,16 +681,12 @@ method resendChatMessage*(self: Module, messageId: string): string = return self.controller.resendChatMessage(messageId) method resetNewMessagesMarker*(self: Module) = - self.view.model().setFirstUnseenMessageId(self.controller.getFirstUnseenMessageId()) - self.view.model().resetNewMessagesMarker() + self.controller.getAsyncFirstUnseenMessageId() method removeNewMessagesMarker*(self: Module) = self.view.model().setFirstUnseenMessageId("") self.view.model().resetNewMessagesMarker() -method scrollToNewMessagesMarker*(self: Module) = - self.scrollToMessage(self.view.model().getFirstUnseenMessageId()) - method markAllMessagesRead*(self: Module) = self.view.model().markAllAsSeen() @@ -721,3 +717,11 @@ proc updateItemsByAlbum(self: Module, items: var seq[Item], message: MessageDto) items[i] = item return true return false + +method onFirstUnseenMessageId*(self: Module, messageId: string) = + self.view.model().setFirstUnseenMessageId(messageId) + self.view.model().resetNewMessagesMarker() + let index = self.view.model().findIndexForMessageId(messageId) + if (index != -1): + self.view.emitScrollToFirstUnreadMessageSignal(index) + self.view.setFirstUnseenMessageLoaded(true) 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 04b6ecbf5a..cc944e5b94 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: chatColor: string chatIcon: string chatType: int + firstUnseenMessageLoaded: bool proc delete*(self: View) = self.model.delete @@ -233,4 +234,19 @@ QtObject: proc setChatType*(self: View, value: int) = self.chatType = value - self.chatTypeChanged() \ No newline at end of file + self.chatTypeChanged() + + proc firstUnseenMessageLoadedChanged*(self: View) {.signal.} + proc getFirstUnseenMessageLoaded*(self: View): bool {.slot.} = + return self.firstUnseenMessageLoaded + proc setFirstUnseenMessageLoaded*(self: View, value: bool) = + self.firstUnseenMessageLoaded = value + self.firstUnseenMessageLoadedChanged() + + QtProperty[bool] firstUnseenMessageLoaded: + read = getFirstUnseenMessageLoaded + notify = firstUnseenMessageLoadedChanged + + proc scrollToFirstUnreadMessage(self: View, messageIndex: int) {.signal.} + proc emitScrollToFirstUnreadMessageSignal*(self: View, messageIndex: int) = + self.scrollToFirstUnreadMessage(messageIndex) \ 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 8f323199f8..9f8ec787b4 100644 --- a/src/app/modules/main/chat_section/chat_content/module.nim +++ b/src/app/modules/main/chat_section/chat_content/module.nim @@ -387,7 +387,6 @@ method contactTrustStatusChanged*(self: Module, publicKey: string, isUntrustwort method onMadeActive*(self: Module) = self.messagesModule.resetNewMessagesMarker() - self.messagesModule.scrollToNewMessagesMarker() self.view.setActive() method onMadeInactive*(self: Module) = diff --git a/src/app_service/service/message/async_tasks.nim b/src/app_service/service/message/async_tasks.nim index a237998ed6..b0d008c2af 100644 --- a/src/app_service/service/message/async_tasks.nim +++ b/src/app_service/service/message/async_tasks.nim @@ -233,4 +233,36 @@ const asyncGetLinkPreviewDataTask: Task = proc(argEncoded: string) {.gcsafe, nim previewData["links"].add(responseJson) let tpl: tuple[previewData: JsonNode, uuid: string] = (previewData, arg.uuid) - arg.finish(tpl) \ No newline at end of file + arg.finish(tpl) + +################################################# +# Async get first unseen message id +################################################# +type + AsyncGetFirstUnseenMessageIdForTaskArg = ref object of QObjectTaskArg + chatId: string + +const asyncGetFirstUnseenMessageIdForTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[AsyncGetFirstUnseenMessageIdForTaskArg](argEncoded) + + let responseJson = %*{ + "messageId": "", + "chatId": arg.chatId, + "error": "" + } + + try: + let response = status_go.firstUnseenMessageID(arg.chatId) + + if(not response.error.isNil): + error "error getFirstUnseenMessageIdFor: ", errDescription = response.error.message + responseJson["error"] = %response.error.message + else: + responseJson["messageId"] = %response.result.getStr() + + except Exception as e: + error "error: ", procName = "getFirstUnseenMessageIdFor", errName = e.name, + errDesription = e.msg, chatId=arg.chatId + 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 9a8951bae3..7a36b5c7af 100644 --- a/src/app_service/service/message/service.nim +++ b/src/app_service/service/message/service.nim @@ -40,6 +40,7 @@ const WEEK_AS_MILLISECONDS = initDuration(seconds = 60*60*24*7).inMilliSeconds # Signals which may be emitted by this service: const SIGNAL_MESSAGES_LOADED* = "messagesLoaded" +const SIGNAL_MESSAGE_FIRST_UNSEEN* = "messageFirstUnseen" const SIGNAL_NEW_MESSAGE_RECEIVED* = "newMessageReceived" const SIGNAL_MESSAGE_PINNED* = "messagePinned" const SIGNAL_MESSAGE_UNPINNED* = "messageUnpinned" @@ -117,6 +118,10 @@ type ReloadMessagesArgs* = ref object of Args communityId*: string + MessageFirstUnseen* = ref object of Args + chatId*: string + messageId*: string + QtObject: type Service* = ref object of QObject events: EventEmitter @@ -188,9 +193,6 @@ QtObject: let pinnedMsgCursorValue = if (pinnedMsgCursor.isFetchable()): pinnedMsgCursor.getValue() else: CURSOR_VALUE_IGNORE if(msgCursorValue == CURSOR_VALUE_IGNORE and pinnedMsgCursorValue == 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 if(msgCursorValue != CURSOR_VALUE_IGNORE): @@ -386,7 +388,6 @@ QtObject: let responseObj = response.parseJson if (responseObj.kind != JObject): info "load more messages response is not a json object" - # notify view, this is important self.events.emit(SIGNAL_MESSAGES_LOADED, MessagesLoadedArgs()) return @@ -654,18 +655,37 @@ QtObject: self.threadpool.start(arg) - proc getFirstUnseenMessageIdFor*(self: Service, chatId: string): string = + proc getAsyncFirstUnseenMessageId*(self: Service, chatId: string) = + let arg = AsyncGetFirstUnseenMessageIdForTaskArg( + tptr: cast[ByteAddress](asyncGetFirstUnseenMessageIdForTaskArg), + vptr: cast[ByteAddress](self.vptr), + slot: "onGetFirstUnseenMessageIdFor", + chatId: chatId, + ) + + self.threadpool.start(arg) + + proc onGetFirstUnseenMessageIdFor*(self: Service, response: string) {.slot.} = try: - let response = status_go.firstUnseenMessageID(chatId) + let responseObj = response.parseJson - if(not response.error.isNil): - error "error getFirstUnseenMessageIdFor: ", errDescription = response.error.message + var error: string + discard responseObj.getProp("error", error) - result = response.result.getStr() + var chatId: string + discard responseObj.getProp("chatId", chatId) + var messageId = "" + + if(error.len > 0): + error "error: ", procName="onGetFirstUnseenMessageIdFor", errDescription=error + else: + discard responseObj.getProp("messageId", messageId) + + self.events.emit(SIGNAL_MESSAGE_FIRST_UNSEEN, MessageFirstUnseen(chatId: chatId, messageId: messageId)) + except Exception as e: - error "error: ", procName = "getFirstUnseenMessageIdFor", errName = e.name, - errDesription = e.msg + error "error: ", procName="onGetFirstUnseenMessageIdFor", errName = e.name, errDesription = e.msg proc onAsyncGetLinkPreviewData*(self: Service, response: string) {.slot.} = let responseObj = response.parseJson diff --git a/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml b/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml index ceb9f67877..59849d4a89 100644 --- a/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml @@ -83,6 +83,13 @@ Item { chatLogView.itemAtIndex(messageIndex).startMessageFoundAnimation() } + function onScrollToFirstUnreadMessage(messageIndex) { + if (d.isMostRecentMessageInViewport) { + chatLogView.positionViewAtIndex(messageIndex, ListView.Center) + chatLogView.itemAtIndex(messageIndex).startMessageFoundAnimation() + } + } + function onMessageSearchOngoingChanged() { d.markAllMessagesReadIfMostRecentMessageIsInViewport() } @@ -145,8 +152,27 @@ Item { } } + Loader { + id: loadingMessagesView + + readonly property bool show: !messageStore.messageModule.firstUnseenMessageLoaded || + !messageStore.messageModule.initialMessagesLoaded + active: show + visible: show + anchors.top: loadingMessagesIndicator.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + sourceComponent: + MessagesLoadingView { + anchors.margins: 16 + anchors.fill: parent + } + } + StatusListView { id: chatLogView + visible: !loadingMessagesView.visible objectName: "chatLogView" anchors.top: loadingMessagesIndicator.bottom anchors.bottom: parent.bottom @@ -270,7 +296,7 @@ Item { quotedMessageAuthorDetailsEnsVerified: model.quotedMessageAuthorEnsVerified quotedMessageAuthorDetailsIsContact: model.quotedMessageAuthorIsContact quotedMessageAuthorDetailsColorHash: model.quotedMessageAuthorColorHash - + gapFrom: model.gapFrom gapTo: model.gapTo diff --git a/ui/app/AppLayouts/Chat/views/MessagesLoadingView.qml b/ui/app/AppLayouts/Chat/views/MessagesLoadingView.qml new file mode 100644 index 0000000000..6243a1f051 --- /dev/null +++ b/ui/app/AppLayouts/Chat/views/MessagesLoadingView.qml @@ -0,0 +1,65 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Components 0.1 + +ListView { + spacing: 20 + interactive: false + clip: true + + model: ListModel { + Component.onCompleted: { + var numElements = 20 + for (var i = 1; i < numElements; ++i) { + if (i % 5 === 0) + append({ "isImage": true, "thirdLine": false }) + else if (i % 3 === 0) + append({ "isImage": false, "thirdLine": true }) + else + append({ "isImage": false, "thirdLine": false }) + } + } + } + + delegate: Item { + + implicitHeight: layoutContent.implicitHeight + implicitWidth: layoutContent.implicitWidth + + RowLayout { + id: layoutContent + anchors.fill: parent + spacing: 8 + + LoadingComponent { + Layout.alignment: Qt.AlignTop + radius: width / 2 + height: 44 + width: 44 + } + + ColumnLayout { + spacing: 4 + LoadingComponent { + radius: 4 + height: 20 + width: 124 + } + LoadingComponent { + radius: 16 + height: model.isImage ? 194 : 18 + width: model.isImage ? 147 : 335 + } + LoadingComponent { + visible: thirdLine + radius: 4 + height: 18 + width: 215 + } + } + } + } +} +