feat(chat): ignore not loaded messages

closes: #8838
This commit is contained in:
Patryk Osmaczko 2022-12-20 11:58:50 +01:00 committed by osmaczko
parent dbaa285f4c
commit ae55e78faf
3 changed files with 125 additions and 81 deletions

View File

@ -1,17 +1,20 @@
import strformat
type type
CursorValue* = string
MessageCursor* = ref object MessageCursor* = ref object
value: string value: CursorValue
pending: bool pending: bool
mostRecent: bool mostRecent: bool
proc initMessageCursor*(value: string, pending: bool, proc initMessageCursor*(value: CursorValue, pending: bool,
mostRecent: bool): MessageCursor = mostRecent: bool): MessageCursor =
MessageCursor(value: value, pending: pending, mostRecent: mostRecent) MessageCursor(value: value, pending: pending, mostRecent: mostRecent)
proc getValue*(self: MessageCursor): string = proc getValue*(self: MessageCursor): CursorValue =
self.value self.value
proc setValue*(self: MessageCursor, value: string) = proc setValue*(self: MessageCursor, value: CursorValue) =
if value == "" or value == self.value: if value == "" or value == self.value:
self.mostRecent = true self.mostRecent = true
else: else:
@ -24,3 +27,18 @@ proc setPending*(self: MessageCursor) =
proc isFetchable*(self: MessageCursor): bool = proc isFetchable*(self: MessageCursor): bool =
return not (self.pending or self.mostRecent) return not (self.pending or self.mostRecent)
proc isEmpty*(self: MessageCursor): bool =
return self.value == ""
proc makeObsolete*(self: MessageCursor) =
self.mostRecent = false
proc isLessThan*(self: MessageCursor, value: CursorValue): bool =
return self.value < value
proc initCursorValue*(id: string, clock: int64): CursorValue =
return fmt"{clock:064}" & id
proc `$`*(self: MessageCursor): string =
return fmt"value:{self.value}, pending:{self.pending}, mostRecent:{self.mostRecent}"

View File

@ -156,6 +156,65 @@ QtObject:
messages.delete(i) messages.delete(i)
return return
proc isChatCursorInitialized(self: Service, chatId: string): bool =
return self.msgCursor.hasKey(chatId)
proc resetMessageCursor*(self: Service, chatId: string) =
if(not self.msgCursor.hasKey(chatId)):
return
self.msgCursor.del(chatId)
proc initOrGetMessageCursor(self: Service, chatId: string): MessageCursor =
if(not self.msgCursor.hasKey(chatId)):
self.msgCursor[chatId] = initMessageCursor(value="", pending=false, mostRecent=false)
return self.msgCursor[chatId]
proc initOrGetPinnedMessageCursor(self: Service, chatId: string): MessageCursor =
if(not self.pinnedMsgCursor.hasKey(chatId)):
self.pinnedMsgCursor[chatId] = initMessageCursor(value="", pending=false, mostRecent=false)
return self.pinnedMsgCursor[chatId]
proc asyncLoadMoreMessagesForChat*(self: Service, chatId: string, limit = MESSAGES_PER_PAGE) =
if (chatId.len == 0):
error "empty chat id", procName="asyncLoadMoreMessagesForChat"
return
let msgCursor = self.initOrGetMessageCursor(chatId)
let msgCursorValue = if (msgCursor.isFetchable()): msgCursor.getValue() else: CURSOR_VALUE_IGNORE
let pinnedMsgCursor = self.initOrGetPinnedMessageCursor(chatId)
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):
msgCursor.setPending()
if (pinnedMsgCursorValue != CURSOR_VALUE_IGNORE):
pinnedMsgCursor.setPending()
let arg = AsyncFetchChatMessagesTaskArg(
tptr: cast[ByteAddress](asyncFetchChatMessagesTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onAsyncLoadMoreMessagesForChat",
chatId: chatId,
msgCursor: msgCursorValue,
pinnedMsgCursor: pinnedMsgCursorValue,
limit: if(limit <= MESSAGES_PER_PAGE_MAX): limit else: MESSAGES_PER_PAGE_MAX
)
self.threadpool.start(arg)
proc asyncLoadInitialMessagesForChat*(self: Service, chatId: string) =
if(self.isChatCursorInitialized(chatId)):
return
self.asyncLoadMoreMessagesForChat(chatId)
proc handleMessagesUpdate(self: Service, chats: var seq[ChatDto], messages: var seq[MessageDto]) = proc handleMessagesUpdate(self: Service, chats: var seq[ChatDto], messages: var seq[MessageDto]) =
# We included `chats` in this condition cause that's the form how `status-go` sends updates. # We included `chats` in this condition cause that's the form how `status-go` sends updates.
# The first element from the `receivedData.chats` array contains details about the chat a messages received in # The first element from the `receivedData.chats` array contains details about the chat a messages received in
@ -170,32 +229,44 @@ QtObject:
# if (not chats[0].active): # if (not chats[0].active):
# return # return
for msg in messages:
if(msg.editedAt > 0):
let data = MessageEditedArgs(chatId: msg.localChatId, message: msg)
self.events.emit(SIGNAL_MESSAGE_EDITED, data)
for i in 0 ..< chats.len: for i in 0 ..< chats.len:
let chatId = chats[i].id
if(chats[i].chatType == ChatType.Unknown): if(chats[i].chatType == ChatType.Unknown):
error "error: new message with an unknown chat type received", chatId=chats[i].id error "error: new message with an unknown chat type received", chatId=chatId
continue continue
# Ignore 1-1 chats for which we are not contact # Ignore 1-1 chats for which we are not contact
if (chats[i].chatType == ChatType.OneToOne and not self.contactService.getContactById(chats[i].id).isContact): if(chats[i].chatType == ChatType.OneToOne and not self.contactService.getContactById(chatId).isContact):
continue continue
let currentChatCursor = self.initOrGetMessageCursor(chatId)
# Ignore messages update if chat haven't loaded any messages and try to load them from database instead
if(currentChatCursor.isEmpty()):
currentChatCursor.makeObsolete()
self.asyncLoadMoreMessagesForChat(chatId)
continue
var chatMessages: seq[MessageDto] var chatMessages: seq[MessageDto]
for msg in messages: for msg in messages:
if (msg.localChatId == chats[i].id): if(msg.localChatId != chatId):
chatMessages.add(msg) continue
if chats[i].communityId.len == 0: # Ignore messages older than current chat cursor
chats[i].communityId = singletonInstance.userProfile.getPubKey() let msgCursorValue = initCursorValue(msg.id, msg.clock)
if(not currentChatCursor.isLessThan(msgCursorValue)):
currentChatCursor.makeObsolete()
continue
if(msg.editedAt > 0):
let data = MessageEditedArgs(chatId: msg.localChatId, message: msg)
self.events.emit(SIGNAL_MESSAGE_EDITED, data)
chatMessages.add(msg)
let data = MessagesArgs( let data = MessagesArgs(
sectionId: chats[i].communityId, sectionId: if chats[i].communityId.len != 0: chats[i].communityId else: singletonInstance.userProfile.getPubKey(),
chatId: chats[i].id, chatId: chatId,
chatType: chats[i].chatType, chatType: chats[i].chatType,
lastMessageTimestamp: chats[i].timestamp.int, lastMessageTimestamp: chats[i].timestamp.int,
unviewedMessagesCount: chats[i].unviewedMessagesCount, unviewedMessagesCount: chats[i].unviewedMessagesCount,
@ -297,25 +368,6 @@ QtObject:
var receivedData = DiscordCommunityImportFinishedSignal(e) var receivedData = DiscordCommunityImportFinishedSignal(e)
self.handleMessagesReload(receivedData.communityId) self.handleMessagesReload(receivedData.communityId)
proc initialMessagesFetched(self: Service, chatId: string): bool =
return self.msgCursor.hasKey(chatId)
proc resetMessageCursor*(self: Service, chatId: string) =
if(not self.msgCursor.hasKey(chatId)):
return
self.msgCursor.del(chatId)
proc getMessageCursor(self: Service, chatId: string): MessageCursor =
if(not self.msgCursor.hasKey(chatId)):
self.msgCursor[chatId] = initMessageCursor(value="", pending=false, mostRecent=false)
return self.msgCursor[chatId]
proc getPinnedMessageCursor(self: Service, chatId: string): MessageCursor =
if(not self.pinnedMsgCursor.hasKey(chatId)):
self.pinnedMsgCursor[chatId] = initMessageCursor(value="", pending=false, mostRecent=false)
return self.pinnedMsgCursor[chatId]
proc getTransactionDetails*(self: Service, message: MessageDto): (string, string) = proc getTransactionDetails*(self: Service, message: MessageDto): (string, string) =
let networksDto = self.networkService.getNetworks() let networksDto = self.networkService.getNetworks()
var token = newTokenDto(networksDto[0].nativeCurrencyName, networksDto[0].chainId, parseAddress(ZERO_ADDRESS), networksDto[0].nativeCurrencySymbol, networksDto[0].nativeCurrencyDecimals, true, "") var token = newTokenDto(networksDto[0].nativeCurrencyName, networksDto[0].chainId, parseAddress(ZERO_ADDRESS), networksDto[0].nativeCurrencySymbol, networksDto[0].nativeCurrencyDecimals, true, "")
@ -346,8 +398,8 @@ QtObject:
var chatId: string var chatId: string
discard responseObj.getProp("chatId", chatId) discard responseObj.getProp("chatId", chatId)
let msgCursor = self.getMessageCursor(chatId) let msgCursor = self.initOrGetMessageCursor(chatId)
let pinnedMsgCursor = self.getPinnedMessageCursor(chatId) let pinnedMsgCursor = self.initOrGetPinnedMessageCursor(chatId)
# handling messages # handling messages
var msgCursorValue: string var msgCursorValue: string
@ -385,48 +437,6 @@ QtObject:
self.events.emit(SIGNAL_MESSAGES_LOADED, data) self.events.emit(SIGNAL_MESSAGES_LOADED, data)
proc asyncLoadMoreMessagesForChat*(self: Service, chatId: string, limit = MESSAGES_PER_PAGE) =
if (chatId.len == 0):
error "empty chat id", procName="asyncLoadMoreMessagesForChat"
return
let msgCursor = self.getMessageCursor(chatId)
let msgCursorValue = if (msgCursor.isFetchable()): msgCursor.getValue() else: CURSOR_VALUE_IGNORE
let pinnedMsgCursor = self.getPinnedMessageCursor(chatId)
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):
msgCursor.setPending()
if (pinnedMsgCursorValue != CURSOR_VALUE_IGNORE):
pinnedMsgCursor.setPending()
let arg = AsyncFetchChatMessagesTaskArg(
tptr: cast[ByteAddress](asyncFetchChatMessagesTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onAsyncLoadMoreMessagesForChat",
chatId: chatId,
msgCursor: msgCursorValue,
pinnedMsgCursor: pinnedMsgCursorValue,
limit: if(limit <= MESSAGES_PER_PAGE_MAX): limit else: MESSAGES_PER_PAGE_MAX
)
self.threadpool.start(arg)
proc asyncLoadInitialMessagesForChat*(self: Service, chatId: string) =
if(self.initialMessagesFetched(chatId)):
return
# we're here if initial messages are not loaded yet
self.asyncLoadMoreMessagesForChat(chatId)
proc addReaction*(self: Service, chatId: string, messageId: string, emojiId: int) = proc addReaction*(self: Service, chatId: string, messageId: string, emojiId: int) =
try: try:
let response = status_go.addReaction(chatId, messageId, emojiId) let response = status_go.addReaction(chatId, messageId, emojiId)

View File

@ -50,6 +50,10 @@ Item {
readonly property bool isMostRecentMessageInViewport: chatLogView.visibleArea.yPosition >= 0.999 - chatLogView.visibleArea.heightRatio readonly property bool isMostRecentMessageInViewport: chatLogView.visibleArea.yPosition >= 0.999 - chatLogView.visibleArea.heightRatio
readonly property var chatDetails: chatContentModule.chatDetails || null readonly property var chatDetails: chatContentModule.chatDetails || null
readonly property var loadMoreMessagesIfScrollBelowThreshold: Backpressure.oneInTime(root, 500, function() {
if(scrollY < 500) messageStore.loadMoreMessages()
})
function markAllMessagesReadIfMostRecentMessageIsInViewport() { function markAllMessagesReadIfMostRecentMessageIsInViewport() {
if (!isMostRecentMessageInViewport || !chatLogView.visible) { if (!isMostRecentMessageInViewport || !chatLogView.visible) {
return return
@ -89,6 +93,7 @@ Item {
function onActiveChanged() { function onActiveChanged() {
d.markAllMessagesReadIfMostRecentMessageIsInViewport() d.markAllMessagesReadIfMostRecentMessageIsInViewport()
d.loadMoreMessagesIfScrollBelowThreshold()
} }
function onHasUnreadMessagesChanged() { function onHasUnreadMessagesChanged() {
@ -108,6 +113,17 @@ Item {
} }
} }
Connections {
target: root.rootStore
enabled: d.chatDetails && d.chatDetails.active
function onLoadingHistoryMessagesInProgressChanged() {
if(!root.rootStore.loadingHistoryMessagesInProgress) {
d.loadMoreMessagesIfScrollBelowThreshold()
}
}
}
Item { Item {
id: loadingMessagesIndicator id: loadingMessagesIndicator
visible: root.rootStore.loadingHistoryMessagesInProgress visible: root.rootStore.loadingHistoryMessagesInProgress
@ -155,7 +171,7 @@ Item {
onContentYChanged: { onContentYChanged: {
scrollDownButton.visible = contentHeight - (d.scrollY + height) > 400 scrollDownButton.visible = contentHeight - (d.scrollY + height) > 400
if(d.scrollY < 500) messageStore.loadMoreMessages() d.loadMoreMessagesIfScrollBelowThreshold()
} }
onCountChanged: d.markAllMessagesReadIfMostRecentMessageIsInViewport() onCountChanged: d.markAllMessagesReadIfMostRecentMessageIsInViewport()