From 1481f2648f61558c7fbdaca5366c4e60173c732c Mon Sep 17 00:00:00 2001 From: Pascal Precht Date: Fri, 4 Sep 2020 13:55:24 +0200 Subject: [PATCH] feat: introduce "fetch more messages" button to request old messages Closes #149 --- src/app/chat/event_handling.nim | 2 +- src/app/chat/view.nim | 38 +++++++++++++++ src/app/chat/views/message_list.nim | 6 ++- src/status/chat.nim | 19 +++++++- src/status/chat/message.nim | 1 + src/status/libstatus/mailservers.nim | 11 +++-- src/status/mailservers.nim | 10 ++-- ui/app/AppLayouts/Chat/ChatColumn/Message.qml | 46 +++++++++++++++++++ ui/imports/Constants.qml | 1 + 9 files changed, 125 insertions(+), 9 deletions(-) diff --git a/src/app/chat/event_handling.nim b/src/app/chat/event_handling.nim index 9151aea7e6..1b5b74f5dd 100644 --- a/src/app/chat/event_handling.nim +++ b/src/app/chat/event_handling.nim @@ -78,7 +78,7 @@ proc handleMailserverEvents(self: ChatController) = self.status.events.on("mailserverAvailable") do(e:Args): 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: fromValue = min(mailserverTopics.map(topic => topic.lastRequest)) diff --git a/src/app/chat/view.nim b/src/app/chat/view.nim index a795836426..98b188bf1c 100644 --- a/src/app/chat/view.nim +++ b/src/app/chat/view.nim @@ -1,6 +1,8 @@ import NimQml, Tables, json, sequtils, chronicles, times, re, sugar, strutils, os, strformat import ../../status/status +import ../../status/mailservers import ../../status/libstatus/accounts/constants +import ../../status/libstatus/mailservers as status_mailservers import ../../status/accounts as status_accounts import ../../status/chat as status_chat import ../../status/messages as status_messages @@ -35,6 +37,7 @@ QtObject: channelOpenTime*: Table[string, int64] connected: bool unreadMessageCnt: int + oldestMessageTimestamp: int64 proc setup(self: ChatsView) = self.QAbstractListModel.setup @@ -62,6 +65,27 @@ QtObject: result.pubKey = "" 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) = self.stickerPacks.addStickerPackToList(stickerPack, newStickerList(stickerPack.stickers), isInstalled, isBought) @@ -206,6 +230,7 @@ QtObject: self.status.chat.setActiveChannel(selectedChannel.id) discard self.status.chat.markAllChannelMessagesRead(selectedChannel.id) self.currentSuggestions.setNewData(self.status.contacts.getContacts()) + self.setLastMessageTimestamp(true) self.activeChannelChanged() proc getActiveChannelIdx(self: ChatsView): QVariant {.slot.} = @@ -237,6 +262,7 @@ QtObject: self.activeChannel.setChatItem(self.chats.getChannel(self.chats.chats.findIndexById(channel))) discard self.status.chat.markAllChannelMessagesRead(self.activeChannel.id) self.currentSuggestions.setNewData(self.status.contacts.getContacts()) + self.setLastMessageTimestamp(true) self.activeChannelChanged() proc getActiveChannel*(self: ChatsView): QVariant {.slot.} = @@ -373,6 +399,8 @@ QtObject: trace "Loading more messages", chaId = self.activeChannel.id self.status.chat.chatMessages(self.activeChannel.id, false) self.status.chat.chatReactions(self.activeChannel.id, false) + if self.status.chat.msgCursor[self.activeChannel.id] == "": + self.setLastMessageTimestamp() self.messagesLoaded(); proc loadMoreMessagesWithIndex*(self: ChatsView, channelIndex: int) {.slot.} = @@ -382,6 +410,16 @@ QtObject: trace "Loading more messages", chaId = selectedChannel.id self.status.chat.chatMessages(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(); proc leaveChatByIndex*(self: ChatsView, channelIndex: int) {.slot.} = diff --git a/src/app/chat/views/message_list.nim b/src/app/chat/views/message_list.nim index d70cfd25f2..59ad000ed9 100644 --- a/src/app/chat/views/message_list.nim +++ b/src/app/chat/views/message_list.nim @@ -52,6 +52,10 @@ QtObject: include message_format + proc fetchMoreMessagesButton(self: ChatMessageList): Message = + result = Message() + result.contentType = ContentType.FetchMoreMessagesButton; + proc chatIdentifier(self: ChatMessageList, chatId:string): Message = result = Message() result.contentType = ContentType.ChatIdentifier; @@ -59,7 +63,7 @@ QtObject: proc newChatMessageList*(chatId: string, status: Status): ChatMessageList = new(result, delete) - result.messages = @[result.chatIdentifier(chatId)] + result.messages = @[result.chatIdentifier(chatId), result.fetchMoreMessagesButton()] result.messageIndex = initTable[string, int]() result.timedoutMessages = initHashSet[string]() result.status = status diff --git a/src/status/chat.nim b/src/status/chat.nim index e0fec48d76..a120262a36 100644 --- a/src/status/chat.nim +++ b/src/status/chat.nim @@ -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/chat as status_chat import libstatus/mailservers as status_mailservers @@ -51,6 +51,7 @@ type availableStickerPacks*: Table[int, StickerPack] installedStickerPacks*: Table[int, StickerPack] purchasedStickerPacks*: seq[int] + lastMessageTimestamps*: Table[string, int64] MessageArgs* = ref object of Args id*: string @@ -69,6 +70,7 @@ proc newChatModel*(events: EventEmitter): ChatModel = result.availableStickerPacks = initTable[int, StickerPack]() result.installedStickerPacks = initTable[int, StickerPack]() result.purchasedStickerPacks = @[] + result.lastMessageTimestamps = initTable[string, int64]() proc delete*(self: ChatModel) = @@ -79,6 +81,15 @@ proc update*(self: ChatModel, chats: seq[Chat], messages: seq[Message], emojiRea if chat.isActive: 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)) 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]) 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])) proc chatReactions*(self: ChatModel, chatId: string, initialLoad:bool = true) = diff --git a/src/status/chat/message.nim b/src/status/chat/message.nim index 6ba3adb7c4..a2335245bf 100644 --- a/src/status/chat/message.nim +++ b/src/status/chat/message.nim @@ -1,6 +1,7 @@ import strformat type ContentType* {.pure.} = enum + FetchMoreMessagesButton = -2 ChatIdentifier = -1, Unknown = 0, Message = 1, diff --git a/src/status/libstatus/mailservers.nim b/src/status/libstatus/mailservers.nim index a0a0a2a5bc..ceef246054 100644 --- a/src/status/libstatus/mailservers.nim +++ b/src/status/libstatus/mailservers.nim @@ -63,10 +63,13 @@ proc update*(peer: string) = proc delete*(peer: string) = discard callPrivateRPC("mailservers_deleteMailserver", %* [peer]) -proc requestMessages*(topics: seq[string], symKeyID: string, peer: string, numberOfMessages: int, fromTimestamp: int64 = 0) = - var fromValue = (times.toUnix(times.getTime()) - 86400) +proc requestMessages*(topics: seq[string], symKeyID: string, peer: string, numberOfMessages: int, fromTimestamp: int64 = 0, toTimestamp: int64 = 0, force: bool = false) = + var toValue = times.toUnix(times.getTime()) + var fromValue = toValue - 86400 if fromTimestamp != 0: fromValue = fromTimestamp + if toTimestamp != 0: + toValue = toTimestamp echo callPrivateRPC("requestMessages".prefix, %* [ { @@ -76,7 +79,9 @@ proc requestMessages*(topics: seq[string], symKeyID: string, peer: string, numbe "timeout": 30, "limit": numberOfMessages, "cursor": nil, - "from": fromValue + "from": fromValue, + "to": toValue, + "force": force } ]) diff --git a/src/status/mailservers.nim b/src/status/mailservers.nim index b2314a1809..e77da64f4a 100644 --- a/src/status/mailservers.nim +++ b/src/status/mailservers.nim @@ -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/chat as status_chat import libstatus/mailservers as status_mailservers @@ -128,10 +128,10 @@ proc peerSummaryChange*(self: MailserverModel, peers: seq[string]) = self.nodes[peer] = MailserverStatus.Connected 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 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] = let response = status_mailservers.getMailserverTopics() @@ -147,6 +147,10 @@ proc getMailserverTopics*(self: MailserverModel): seq[MailserverTopic] = 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) = discard status_mailservers.addMailserverTopic(topic) diff --git a/ui/app/AppLayouts/Chat/ChatColumn/Message.qml b/ui/app/AppLayouts/Chat/ChatColumn/Message.qml index 9a38addab4..b6f8ea7a6e 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/Message.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/Message.qml @@ -75,6 +75,8 @@ Item { switch(contentType) { case Constants.chatIdentifier: return channelIdentifierComponent + case Constants.fetchMoreMessagesButton: + return fetchMoreMessagesButtonComponent case Constants.systemMessagePrivateGroupType: return privateGroupHeaderComponent 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 { id: channelIdentifierComponent ChannelIdentifier { diff --git a/ui/imports/Constants.qml b/ui/imports/Constants.qml index 2079807462..a9199cdd44 100644 --- a/ui/imports/Constants.qml +++ b/ui/imports/Constants.qml @@ -7,6 +7,7 @@ QtObject { readonly property int chatTypePublic: 2 readonly property int chatTypePrivateGroupChat: 3 + readonly property int fetchMoreMessagesButton: -2 readonly property int chatIdentifier: -1 readonly property int unknownContentType: 0 readonly property int messageType: 1