parent
dbaa285f4c
commit
ae55e78faf
|
@ -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}"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue