feat: introduce "fetch more messages" button to request old messages

Closes #149
This commit is contained in:
Pascal Precht 2020-09-04 13:55:24 +02:00 committed by Iuri Matias
parent 464a2018d4
commit 1481f2648f
9 changed files with 125 additions and 9 deletions

View File

@ -78,7 +78,7 @@ proc handleMailserverEvents(self: ChatController) =
self.status.events.on("mailserverAvailable") do(e:Args): self.status.events.on("mailserverAvailable") do(e:Args):
let mailserverTopics = self.status.mailservers.getMailserverTopics() let mailserverTopics = self.status.mailservers.getMailserverTopics()
var fromValue: int64 = times.toUnix(times.getTime()) - 86400 var fromValue = times.toUnix(times.getTime()) - 86400 # today - 24 hours
if mailserverTopics.len > 0: if mailserverTopics.len > 0:
fromValue = min(mailserverTopics.map(topic => topic.lastRequest)) fromValue = min(mailserverTopics.map(topic => topic.lastRequest))

View File

@ -1,6 +1,8 @@
import NimQml, Tables, json, sequtils, chronicles, times, re, sugar, strutils, os, strformat import NimQml, Tables, json, sequtils, chronicles, times, re, sugar, strutils, os, strformat
import ../../status/status import ../../status/status
import ../../status/mailservers
import ../../status/libstatus/accounts/constants import ../../status/libstatus/accounts/constants
import ../../status/libstatus/mailservers as status_mailservers
import ../../status/accounts as status_accounts import ../../status/accounts as status_accounts
import ../../status/chat as status_chat import ../../status/chat as status_chat
import ../../status/messages as status_messages import ../../status/messages as status_messages
@ -35,6 +37,7 @@ QtObject:
channelOpenTime*: Table[string, int64] channelOpenTime*: Table[string, int64]
connected: bool connected: bool
unreadMessageCnt: int unreadMessageCnt: int
oldestMessageTimestamp: int64
proc setup(self: ChatsView) = self.QAbstractListModel.setup proc setup(self: ChatsView) = self.QAbstractListModel.setup
@ -62,6 +65,27 @@ QtObject:
result.pubKey = "" result.pubKey = ""
result.setup() result.setup()
proc oldestMessageTimestampChanged*(self: ChatsView) {.signal.}
proc getOldestMessageTimestamp*(self: ChatsView): QVariant {.slot.} =
newQVariant($self.oldestMessageTimestamp)
QtProperty[QVariant] oldestMsgTimestamp:
read = getOldestMessageTimestamp
notify = oldestMessageTimestampChanged
proc setLastMessageTimestamp(self: ChatsView, force = false) =
if self.status.chat.lastMessageTimestamps.hasKey(self.activeChannel.id):
if force or self.status.chat.lastMessageTimestamps[self.activeChannel.id] <= self.oldestMessageTimestamp:
self.oldestMessageTimestamp = self.status.chat.lastMessageTimestamps[self.activeChannel.id]
else:
let topics = self.status.mailservers.getMailserverTopicsByChatId(self.activeChannel.id)
if topics.len > 0:
self.oldestMessageTimestamp = topics[0].lastRequest
else:
self.oldestMessageTimestamp = times.toUnix(times.getTime())
self.oldestMessageTimestampChanged()
proc addStickerPackToList*(self: ChatsView, stickerPack: StickerPack, isInstalled, isBought: bool) = proc addStickerPackToList*(self: ChatsView, stickerPack: StickerPack, isInstalled, isBought: bool) =
self.stickerPacks.addStickerPackToList(stickerPack, newStickerList(stickerPack.stickers), isInstalled, isBought) self.stickerPacks.addStickerPackToList(stickerPack, newStickerList(stickerPack.stickers), isInstalled, isBought)
@ -206,6 +230,7 @@ QtObject:
self.status.chat.setActiveChannel(selectedChannel.id) self.status.chat.setActiveChannel(selectedChannel.id)
discard self.status.chat.markAllChannelMessagesRead(selectedChannel.id) discard self.status.chat.markAllChannelMessagesRead(selectedChannel.id)
self.currentSuggestions.setNewData(self.status.contacts.getContacts()) self.currentSuggestions.setNewData(self.status.contacts.getContacts())
self.setLastMessageTimestamp(true)
self.activeChannelChanged() self.activeChannelChanged()
proc getActiveChannelIdx(self: ChatsView): QVariant {.slot.} = proc getActiveChannelIdx(self: ChatsView): QVariant {.slot.} =
@ -237,6 +262,7 @@ QtObject:
self.activeChannel.setChatItem(self.chats.getChannel(self.chats.chats.findIndexById(channel))) self.activeChannel.setChatItem(self.chats.getChannel(self.chats.chats.findIndexById(channel)))
discard self.status.chat.markAllChannelMessagesRead(self.activeChannel.id) discard self.status.chat.markAllChannelMessagesRead(self.activeChannel.id)
self.currentSuggestions.setNewData(self.status.contacts.getContacts()) self.currentSuggestions.setNewData(self.status.contacts.getContacts())
self.setLastMessageTimestamp(true)
self.activeChannelChanged() self.activeChannelChanged()
proc getActiveChannel*(self: ChatsView): QVariant {.slot.} = proc getActiveChannel*(self: ChatsView): QVariant {.slot.} =
@ -373,6 +399,8 @@ QtObject:
trace "Loading more messages", chaId = self.activeChannel.id trace "Loading more messages", chaId = self.activeChannel.id
self.status.chat.chatMessages(self.activeChannel.id, false) self.status.chat.chatMessages(self.activeChannel.id, false)
self.status.chat.chatReactions(self.activeChannel.id, false) self.status.chat.chatReactions(self.activeChannel.id, false)
if self.status.chat.msgCursor[self.activeChannel.id] == "":
self.setLastMessageTimestamp()
self.messagesLoaded(); self.messagesLoaded();
proc loadMoreMessagesWithIndex*(self: ChatsView, channelIndex: int) {.slot.} = proc loadMoreMessagesWithIndex*(self: ChatsView, channelIndex: int) {.slot.} =
@ -382,6 +410,16 @@ QtObject:
trace "Loading more messages", chaId = selectedChannel.id trace "Loading more messages", chaId = selectedChannel.id
self.status.chat.chatMessages(selectedChannel.id, false) self.status.chat.chatMessages(selectedChannel.id, false)
self.status.chat.chatReactions(selectedChannel.id, false) self.status.chat.chatReactions(selectedChannel.id, false)
self.setLastMessageTimestamp()
self.messagesLoaded();
proc requestMoreMessages*(self: ChatsView) {.slot.} =
let topics = self.status.mailservers.getMailserverTopicsByChatId(self.activeChannel.id).map(topic => topic.topic)
let currentOldestMessageTimestamp = self.oldestMessageTimestamp
self.oldestMessageTimestamp = self.oldestMessageTimestamp - 86400
self.status.mailservers.requestMessages(topics, self.oldestMessageTimestamp, currentOldestMessageTimestamp, true)
self.oldestMessageTimestampChanged()
self.messagesLoaded(); self.messagesLoaded();
proc leaveChatByIndex*(self: ChatsView, channelIndex: int) {.slot.} = proc leaveChatByIndex*(self: ChatsView, channelIndex: int) {.slot.} =

View File

@ -52,6 +52,10 @@ QtObject:
include message_format include message_format
proc fetchMoreMessagesButton(self: ChatMessageList): Message =
result = Message()
result.contentType = ContentType.FetchMoreMessagesButton;
proc chatIdentifier(self: ChatMessageList, chatId:string): Message = proc chatIdentifier(self: ChatMessageList, chatId:string): Message =
result = Message() result = Message()
result.contentType = ContentType.ChatIdentifier; result.contentType = ContentType.ChatIdentifier;
@ -59,7 +63,7 @@ QtObject:
proc newChatMessageList*(chatId: string, status: Status): ChatMessageList = proc newChatMessageList*(chatId: string, status: Status): ChatMessageList =
new(result, delete) new(result, delete)
result.messages = @[result.chatIdentifier(chatId)] result.messages = @[result.chatIdentifier(chatId), result.fetchMoreMessagesButton()]
result.messageIndex = initTable[string, int]() result.messageIndex = initTable[string, int]()
result.timedoutMessages = initHashSet[string]() result.timedoutMessages = initHashSet[string]()
result.status = status result.status = status

View File

@ -1,4 +1,4 @@
import eventemitter, json, strutils, sequtils, tables, chronicles, sugar import eventemitter, json, strutils, sequtils, tables, chronicles, sugar, times
import libstatus/contracts as status_contracts import libstatus/contracts as status_contracts
import libstatus/chat as status_chat import libstatus/chat as status_chat
import libstatus/mailservers as status_mailservers import libstatus/mailservers as status_mailservers
@ -51,6 +51,7 @@ type
availableStickerPacks*: Table[int, StickerPack] availableStickerPacks*: Table[int, StickerPack]
installedStickerPacks*: Table[int, StickerPack] installedStickerPacks*: Table[int, StickerPack]
purchasedStickerPacks*: seq[int] purchasedStickerPacks*: seq[int]
lastMessageTimestamps*: Table[string, int64]
MessageArgs* = ref object of Args MessageArgs* = ref object of Args
id*: string id*: string
@ -69,6 +70,7 @@ proc newChatModel*(events: EventEmitter): ChatModel =
result.availableStickerPacks = initTable[int, StickerPack]() result.availableStickerPacks = initTable[int, StickerPack]()
result.installedStickerPacks = initTable[int, StickerPack]() result.installedStickerPacks = initTable[int, StickerPack]()
result.purchasedStickerPacks = @[] result.purchasedStickerPacks = @[]
result.lastMessageTimestamps = initTable[string, int64]()
proc delete*(self: ChatModel) = proc delete*(self: ChatModel) =
@ -79,6 +81,15 @@ proc update*(self: ChatModel, chats: seq[Chat], messages: seq[Message], emojiRea
if chat.isActive: if chat.isActive:
self.channels[chat.id] = chat self.channels[chat.id] = chat
for message in messages:
let chatId = message.chatId
let ts = times.convert(Milliseconds, Seconds, message.whisperTimestamp.parseInt())
if not self.lastMessageTimestamps.hasKey(chatId):
self.lastMessageTimestamps[chatId] = ts
else:
if self.lastMessageTimestamps[chatId] > ts:
self.lastMessageTimestamps[chatId] = ts
self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[], emojiReactions: emojiReactions)) self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[], emojiReactions: emojiReactions))
proc hasChannel*(self: ChatModel, chatId: string): bool = proc hasChannel*(self: ChatModel, chatId: string): bool =
@ -293,6 +304,12 @@ proc chatMessages*(self: ChatModel, chatId: string, initialLoad:bool = true) =
let messageTuple = status_chat.chatMessages(chatId, self.msgCursor[chatId]) let messageTuple = status_chat.chatMessages(chatId, self.msgCursor[chatId])
self.msgCursor[chatId] = messageTuple[0]; self.msgCursor[chatId] = messageTuple[0];
if messageTuple[1].len > 0:
let lastMsgIndex = messageTuple[1].len - 1
let ts = times.convert(Milliseconds, Seconds, messageTuple[1][lastMsgIndex].whisperTimestamp.parseInt())
self.lastMessageTimestamps[chatId] = ts
self.events.emit("messagesLoaded", MsgsLoadedArgs(messages: messageTuple[1])) self.events.emit("messagesLoaded", MsgsLoadedArgs(messages: messageTuple[1]))
proc chatReactions*(self: ChatModel, chatId: string, initialLoad:bool = true) = proc chatReactions*(self: ChatModel, chatId: string, initialLoad:bool = true) =

View File

@ -1,6 +1,7 @@
import strformat import strformat
type ContentType* {.pure.} = enum type ContentType* {.pure.} = enum
FetchMoreMessagesButton = -2
ChatIdentifier = -1, ChatIdentifier = -1,
Unknown = 0, Unknown = 0,
Message = 1, Message = 1,

View File

@ -63,10 +63,13 @@ proc update*(peer: string) =
proc delete*(peer: string) = proc delete*(peer: string) =
discard callPrivateRPC("mailservers_deleteMailserver", %* [peer]) discard callPrivateRPC("mailservers_deleteMailserver", %* [peer])
proc requestMessages*(topics: seq[string], symKeyID: string, peer: string, numberOfMessages: int, fromTimestamp: int64 = 0) = proc requestMessages*(topics: seq[string], symKeyID: string, peer: string, numberOfMessages: int, fromTimestamp: int64 = 0, toTimestamp: int64 = 0, force: bool = false) =
var fromValue = (times.toUnix(times.getTime()) - 86400) var toValue = times.toUnix(times.getTime())
var fromValue = toValue - 86400
if fromTimestamp != 0: if fromTimestamp != 0:
fromValue = fromTimestamp fromValue = fromTimestamp
if toTimestamp != 0:
toValue = toTimestamp
echo callPrivateRPC("requestMessages".prefix, %* [ echo callPrivateRPC("requestMessages".prefix, %* [
{ {
@ -76,7 +79,9 @@ proc requestMessages*(topics: seq[string], symKeyID: string, peer: string, numbe
"timeout": 30, "timeout": 30,
"limit": numberOfMessages, "limit": numberOfMessages,
"cursor": nil, "cursor": nil,
"from": fromValue "from": fromValue,
"to": toValue,
"force": force
} }
]) ])

View File

@ -1,4 +1,4 @@
import algorithm, json, random, math, os, tables, sets, chronicles, eventemitter, sequtils, locks import algorithm, json, random, math, os, tables, sets, chronicles, eventemitter, sequtils, locks, sugar
import libstatus/core as status_core import libstatus/core as status_core
import libstatus/chat as status_chat import libstatus/chat as status_chat
import libstatus/mailservers as status_mailservers import libstatus/mailservers as status_mailservers
@ -128,10 +128,10 @@ proc peerSummaryChange*(self: MailserverModel, peers: seq[string]) =
self.nodes[peer] = MailserverStatus.Connected self.nodes[peer] = MailserverStatus.Connected
self.events.emit("peerConnected", MailserverArg(peer: peer)) self.events.emit("peerConnected", MailserverArg(peer: peer))
proc requestMessages*(self: MailserverModel, topics: seq[string], fromValue: int64 = 0) = proc requestMessages*(self: MailserverModel, topics: seq[string], fromValue: int64 = 0, toValue: int64 = 0, force: bool = false) =
debug "Requesting messages from", mailserver=self.selectedMailserver debug "Requesting messages from", mailserver=self.selectedMailserver
let generatedSymKey = status_chat.generateSymKeyFromPassword() let generatedSymKey = status_chat.generateSymKeyFromPassword()
status_mailservers.requestMessages(topics, generatedSymKey, self.selectedMailserver, 1000, fromValue) status_mailservers.requestMessages(topics, generatedSymKey, self.selectedMailserver, 1000, fromValue, toValue, force)
proc getMailserverTopics*(self: MailserverModel): seq[MailserverTopic] = proc getMailserverTopics*(self: MailserverModel): seq[MailserverTopic] =
let response = status_mailservers.getMailserverTopics() let response = status_mailservers.getMailserverTopics()
@ -147,6 +147,10 @@ proc getMailserverTopics*(self: MailserverModel): seq[MailserverTopic] =
lastRequest: topic["last-request"].getInt lastRequest: topic["last-request"].getInt
)) ))
proc getMailserverTopicsByChatId*(self: MailserverModel, chatId: string): seq[MailServerTopic] =
result = self.getMailserverTopics()
.filter(topic => topic.chatIds.contains(chatId))
proc addMailserverTopic*(self: MailserverModel, topic: MailserverTopic) = proc addMailserverTopic*(self: MailserverModel, topic: MailserverTopic) =
discard status_mailservers.addMailserverTopic(topic) discard status_mailservers.addMailserverTopic(topic)

View File

@ -75,6 +75,8 @@ Item {
switch(contentType) { switch(contentType) {
case Constants.chatIdentifier: case Constants.chatIdentifier:
return channelIdentifierComponent return channelIdentifierComponent
case Constants.fetchMoreMessagesButton:
return fetchMoreMessagesButtonComponent
case Constants.systemMessagePrivateGroupType: case Constants.systemMessagePrivateGroupType:
return privateGroupHeaderComponent return privateGroupHeaderComponent
default: default:
@ -83,6 +85,50 @@ Item {
} }
} }
Component {
id: fetchMoreMessagesButtonComponent
Item {
id: wrapper
height: wrapper.visible ? fetchMoreButton.height + fetchDate.height + 3 + Style.current.smallPadding*2 : 0
anchors.left: parent.left
anchors.right: parent.right
Separator {
id: sep1
}
StyledText {
id: fetchMoreButton
font.weight: Font.Medium
font.pixelSize: 15
color: Style.current.blue
text: qsTr("↓ Fetch more messages")
horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: sep1.bottom
anchors.topMargin: Style.current.smallPadding
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onClicked: {
chatsModel.requestMoreMessages()
}
}
}
StyledText {
id: fetchDate
anchors.top: fetchMoreButton.bottom
anchors.topMargin: 3
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
color: Style.current.darkGrey
text: qsTr("before %1").arg(new Date(chatsModel.oldestMsgTimestamp*1000).toDateString())
}
Separator {
anchors.top: fetchDate.bottom
anchors.topMargin: Style.current.smallPadding
}
}
}
Component { Component {
id: channelIdentifierComponent id: channelIdentifierComponent
ChannelIdentifier { ChannelIdentifier {

View File

@ -7,6 +7,7 @@ QtObject {
readonly property int chatTypePublic: 2 readonly property int chatTypePublic: 2
readonly property int chatTypePrivateGroupChat: 3 readonly property int chatTypePrivateGroupChat: 3
readonly property int fetchMoreMessagesButton: -2
readonly property int chatIdentifier: -1 readonly property int chatIdentifier: -1
readonly property int unknownContentType: 0 readonly property int unknownContentType: 0
readonly property int messageType: 1 readonly property int messageType: 1