feat:(@desktop/channels): loading state when switching channels/chats

This commit is contained in:
mprakhov 2023-03-24 13:41:33 +01:00 committed by Jonathan Rainville
parent 5479880cde
commit b580f0a810
9 changed files with 193 additions and 25 deletions

View File

@ -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

View File

@ -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")

View File

@ -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)

View File

@ -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()
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)

View File

@ -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) =

View File

@ -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)
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)

View File

@ -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

View File

@ -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

View File

@ -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
}
}
}
}
}