From f8e5b25a09716e95b4288baae889a2dbfaeea244 Mon Sep 17 00:00:00 2001 From: Iuri Matias Date: Mon, 21 Jun 2021 15:35:32 -0400 Subject: [PATCH] refactor: extract messages from chat view refactor: extract messages from chat view refactor: extract messages from chat view refactor: extract messages from chat view update references to messageView fix setup remove duplicated method --- src/app/chat/core.nim | 3 +- src/app/chat/signal_handling.nim | 3 +- src/app/chat/view.nim | 471 +++--------------- src/app/chat/views/messages.nim | 422 ++++++++++++++++ ui/app/AppLayouts/Chat/ChatColumn.qml | 36 +- .../ChatComponents/ActivityChannelBadge.qml | 2 +- .../ChatColumn/ChatComponents/InputArea.qml | 6 +- .../Chat/ChatColumn/ChatMessages.qml | 4 +- ui/app/AppLayouts/Chat/ChatColumn/Message.qml | 18 +- .../MessageComponents/ChatReply.qml | 2 +- .../ChatColumn/MessageComponents/Retry.qml | 2 +- .../CommunityComponents/CommunityButton.qml | 2 +- .../CreateChannelPopup.qml | 2 +- .../Chat/ContactsColumn/ChannelList.qml | 2 +- .../Chat/components/FetchMoreMessages.qml | 8 +- .../Chat/components/GroupInfoPopup.qml | 2 +- .../Chat/components/MessageContextMenu.qml | 4 +- .../Chat/components/PinnedMessagesPopup.qml | 2 +- ui/app/AppLayouts/Timeline/TimelineLayout.qml | 4 +- ui/app/AppMain.qml | 4 +- ui/shared/SearchResults.qml | 2 +- ui/shared/status/StatusChatInfo.qml | 4 +- 22 files changed, 546 insertions(+), 459 deletions(-) create mode 100644 src/app/chat/views/messages.nim diff --git a/src/app/chat/core.nim b/src/app/chat/core.nim index 7cfe838160..5896447bd5 100644 --- a/src/app/chat/core.nim +++ b/src/app/chat/core.nim @@ -36,7 +36,8 @@ proc init*(self: ChatController) = let pubKey = self.status.settings.getSetting[:string](Setting.PublicKey, "0x0") let messagesFromContactsOnly = self.status.settings.getSetting[:bool](Setting.MessagesFromContactsOnly, false, true) - self.view.pubKey = pubKey + # self.view.pubKey = pubKey + self.view.setPubKey(pubKey) self.status.chat.init(pubKey, messagesFromContactsOnly) self.status.stickers.init() self.view.reactions.init() diff --git a/src/app/chat/signal_handling.nim b/src/app/chat/signal_handling.nim index 5e120ce717..2b546ca4bf 100644 --- a/src/app/chat/signal_handling.nim +++ b/src/app/chat/signal_handling.nim @@ -26,7 +26,7 @@ proc handleSignals(self: ChatController) = for messageId in data.messageIds: if self.status.messages.messages.hasKey(messageId): let chatId = self.status.messages.messages[messageId].chatId - self.view.messageList[chatId].checkTimeout(messageId) + self.view.messageView.messageList[chatId].checkTimeout(messageId) self.status.events.on(SignalType.CommunityFound.event) do(e: Args): var data = CommunitySignal(e) @@ -41,4 +41,3 @@ proc handleSignals(self: ChatController) = # TODO: retry mailserver request up to N times or change mailserver # If > N, then self.view.hideLoadingIndicator() - \ No newline at end of file diff --git a/src/app/chat/view.nim b/src/app/chat/view.nim index 79fe7187af..777b1a30e8 100644 --- a/src/app/chat/view.nim +++ b/src/app/chat/view.nim @@ -9,7 +9,7 @@ import ../../status/ens as status_ens import ../../status/chat/[chat, message] import ../../status/profile/profile import web3/[conversions, ethtypes] -import views/[channels_list, message_list, chat_item, suggestions_list, reactions, stickers, groups, transactions, communities, community_list, community_item, format_input, ens, activity_notification_list, channel] +import views/[channels_list, message_list, chat_item, suggestions_list, reactions, stickers, groups, transactions, communities, community_list, community_item, format_input, ens, activity_notification_list, channel, messages] import ../utils/image_utils import ../../status/tasks/[qt, task_runner_impl] import ../../status/tasks/marathon/mailserver/worker @@ -23,14 +23,10 @@ logScope: topics = "chats-view" type - ChatViewRoles {.pure.} = enum - MessageList = UserRole + 1 GetLinkPreviewDataTaskArg = ref object of QObjectTaskArg link: string uuid: string AsyncActivityNotificationLoadTaskArg = ref object of QObjectTaskArg - AsyncMessageLoadTaskArg = ref object of QObjectTaskArg - chatId: string const getLinkPreviewDataTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let arg = decode[GetLinkPreviewDataTaskArg](argEncoded) @@ -50,34 +46,6 @@ proc getLinkPreviewData[T](self: T, slot: string, link: string, uuid: string) = ) self.status.tasks.threadpool.start(arg) -const asyncMessageLoadTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = - let arg = decode[AsyncMessageLoadTaskArg](argEncoded) - var messages: JsonNode - var msgCallSuccess: bool - let msgCallResult = status_chat.rpcChatMessages(arg.chatId, newJString(""), 20, msgCallSuccess) - if(msgCallSuccess): - messages = msgCallResult.parseJson()["result"] - - var reactions: JsonNode - var reactionsCallSuccess: bool - let reactionsCallResult = status_chat.rpcReactions(arg.chatId, newJString(""), 20, reactionsCallSuccess) - if(reactionsCallSuccess): - reactions = reactionsCallResult.parseJson()["result"] - - var pinnedMessages: JsonNode - var pinnedMessagesCallSuccess: bool - let pinnedMessagesCallResult = status_chat.rpcPinnedChatMessages(arg.chatId, newJString(""), 20, pinnedMessagesCallSuccess) - if(pinnedMessagesCallSuccess): - pinnedMessages = pinnedMessagesCallResult.parseJson()["result"] - - let responseJson = %*{ - "chatId": arg.chatId, - "messages": messages, - "reactions": reactions, - "pinnedMessages": pinnedMessages - } - arg.finish(responseJson) - const asyncActivityNotificationLoadTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let arg = decode[AsyncActivityNotificationLoadTaskArg](argEncoded) var activityNotifications: JsonNode @@ -91,15 +59,6 @@ const asyncActivityNotificationLoadTask: Task = proc(argEncoded: string) {.gcsaf } arg.finish(responseJson) -proc asyncMessageLoad[T](self: T, slot: string, chatId: string) = - let arg = AsyncMessageLoadTaskArg( - tptr: cast[ByteAddress](asyncMessageLoadTask), - vptr: cast[ByteAddress](self.vptr), - slot: slot, - chatId: chatId - ) - self.status.tasks.threadpool.start(arg) - proc asyncActivityNotificationLoad[T](self: T, slot: string) = let arg = AsyncActivityNotificationLoadTaskArg( tptr: cast[ByteAddress](asyncActivityNotificationLoadTask), @@ -115,21 +74,17 @@ QtObject: formatInputView: FormatInputView ensView: EnsView channelView*: ChannelView + messageView*: MessageView currentSuggestions*: SuggestionsList activityNotificationList*: ActivityNotificationList callResult: string - messageList*: OrderedTable[string, ChatMessageList] - pinnedMessagesList*: OrderedTable[string, ChatMessageList] reactions*: ReactionView stickers*: StickersView groups*: GroupsView transactions*: TransactionsView communities*: CommunitiesView replyTo: string - channelOpenTime*: Table[string, int64] connected: bool - unreadMessageCnt: int - loadingMessages: bool timelineChat: Chat pubKey*: string @@ -140,18 +95,11 @@ QtObject: self.ensView.delete self.currentSuggestions.delete self.activityNotificationList.delete - for msg in self.messageList.values: - msg.delete - for msg in self.pinnedMessagesList.values: - msg.delete self.reactions.delete self.stickers.delete self.groups.delete self.transactions.delete - self.messageList = initOrderedTable[string, ChatMessageList]() - self.pinnedMessagesList = initOrderedTable[string, ChatMessageList]() self.communities.delete - self.channelOpenTime = initTable[string, int64]() self.QAbstractListModel.delete proc newChatsView*(status: Status): ChatsView = @@ -161,21 +109,21 @@ QtObject: result.ensView = newEnsView(status) result.communities = newCommunitiesView(status) result.channelView = newChannelView(status, result.communities) + result.messageView = newMessageView(status, result.channelView, result.communities) result.connected = false result.currentSuggestions = newSuggestionsList() result.activityNotificationList = newActivityNotificationList(status) - result.messageList = initOrderedTable[string, ChatMessageList]() - result.pinnedMessagesList = initOrderedTable[string, ChatMessageList]() - result.reactions = newReactionView(status, result.messageList.addr, result.channelView.activeChannel) + result.reactions = newReactionView(status, result.messageView.messageList.addr, result.channelView.activeChannel) result.stickers = newStickersView(status, result.channelView.activeChannel) result.groups = newGroupsView(status,result.channelView.activeChannel) result.transactions = newTransactionsView(status) - result.unreadMessageCnt = 0 - result.loadingMessages = false - result.messageList[status_utils.getTimelineChatId()] = newChatMessageList(status_utils.getTimelineChatId(), result.status, false) result.setup() + proc setPubKey*(self: ChatsView, pubKey: string) = + self.pubKey = pubKey + self.messageView.pubKey = pubKey + proc getFormatInput(self: ChatsView): QVariant {.slot.} = newQVariant(self.formatInputView) QtProperty[QVariant] formatInputView: read = getFormatInput @@ -198,70 +146,13 @@ QtObject: self.channelView.activeChannelChanged() self.triggerActiveChannelChange() - proc getMessageListIndexById(self: ChatsView, id: string): int - - proc replaceMentionsWithPubKeys(self: ChatsView, mentions: seq[string], contacts: seq[Profile], message: string, predicate: proc (contact: Profile): string): string = - var updatedMessage = message - for mention in mentions: - let matches = contacts.filter(c => "@" & predicate(c).toLowerAscii == mention.toLowerAscii).map(c => c.address) - if matches.len > 0: - let pubKey = matches[0] - var startIndex = 0 - var index = updatedMessage.find(mention) - - while index > -1: - if index == 0 or updatedMessage[index-1] == ' ': - updatedMessage = updatedMessage.replaceWord(mention, '@' & pubKey) - startIndex = index + mention.len - index = updatedMessage.find(mention, startIndex) - - result = updatedMessage + proc getMessageView*(self: ChatsView): QVariant {.slot.} = newQVariant(self.messageView) + QtProperty[QVariant] messageView: + read = getMessageView proc plainText(self: ChatsView, input: string): string {.slot.} = result = plain_text(input) - proc sendMessage*(self: ChatsView, message: string, replyTo: string, contentType: int = ContentType.Message.int, isStatusUpdate: bool = false, contactsString: string = "") {.slot.} = - let aliasPattern = re(r"(@[A-z][a-z]+ [A-z][a-z]* [A-z][a-z]*)", flags = {reStudy, reIgnoreCase}) - let ensPattern = re(r"(@\w+(?=(\.stateofus)?\.eth))", flags = {reStudy, reIgnoreCase}) - let namePattern = re(r"(@\w+)", flags = {reStudy, reIgnoreCase}) - - var contacts: seq[Profile] - if (contactsString == ""): - contacts = self.status.contacts.getContacts() - else: - let contactsJSON = parseJson(contactsString) - contacts = @[] - for contact in contactsJSON: - contacts.add(Profile( - address: contact["address"].str, - alias: contact["alias"].str, - ensName: contact["ensName"].str - )) - - let aliasMentions = findAll(message, aliasPattern) - let ensMentions = findAll(message, ensPattern) - let nameMentions = findAll(message, namePattern) - - var m = self.replaceMentionsWithPubKeys(aliasMentions, contacts, message, (c => c.alias)) - m = self.replaceMentionsWithPubKeys(ensMentions, contacts, m, (c => c.ensName)) - m = self.replaceMentionsWithPubKeys(nameMentions, contacts, m, (c => c.ensName.split(".")[0])) - - var channelId = self.channelView.activeChannel.id - - if isStatusUpdate: - channelId = "@" & self.pubKey - - self.status.chat.sendMessage(channelId, m, replyTo, contentType) - - proc verifyMessageSent*(self: ChatsView, data: string) {.slot.} = - let messageData = data.parseJson - self.messageList[messageData["chatId"].getStr].checkTimeout(messageData["id"].getStr) - - proc resendMessage*(self: ChatsView, chatId: string, messageId: string) {.slot.} = - self.status.messages.trackMessage(messageId, chatId) - self.status.chat.resendMessage(messageId) - self.messageList[chatId].resetTimeOut(messageId) - proc sendImage*(self: ChatsView, imagePath: string, isStatusUpdate: bool = false): string {.slot.} = result = "" try: @@ -297,12 +188,8 @@ QtObject: error "Error sending images", msg = e.msg result = fmt"Error sending images: {e.msg}" - proc sendingMessage*(self: ChatsView) {.signal.} - proc appReady*(self: ChatsView) {.signal.} - proc sendingMessageFailed*(self: ChatsView) {.signal.} - proc alias*(self: ChatsView, pubKey: string): string {.slot.} = if (pubKey == ""): return "" @@ -328,49 +215,6 @@ QtObject: read = getActivityNotificationList notify = activityNotificationsChanged - proc upsertChannel(self: ChatsView, channel: string) = - var chat: Chat = nil - if self.status.chat.channels.hasKey(channel): - chat = self.status.chat.channels[channel] - else: - chat = self.communities.getChannel(channel) - if not self.messageList.hasKey(channel): - self.beginInsertRows(newQModelIndex(), self.messageList.len, self.messageList.len) - self.messageList[channel] = newChatMessageList(channel, self.status, not chat.isNil and chat.chatType != ChatType.Profile) - self.channelOpenTime[channel] = now().toTime.toUnix * 1000 - self.endInsertRows(); - if not self.pinnedMessagesList.hasKey(channel): - self.pinnedMessagesList[channel] = newChatMessageList(channel, self.status, false) - - proc messagePushed*(self: ChatsView, messageIndex: int) {.signal.} - proc newMessagePushed*(self: ChatsView) {.signal.} - - proc messageNotificationPushed*(self: ChatsView, chatId: string, text: string, messageType: string, chatType: int, timestamp: string, identicon: string, username: string, hasMention: bool, isAddedContact: bool, channelName: string) {.signal.} - - proc messagesCleared*(self: ChatsView) {.signal.} - - proc clearMessages*(self: ChatsView, id: string) = - let channel = self.channelView.getChannelById(id) - if (channel == nil): - return - self.messageList[id].clear(not channel.isNil and channel.chatType != ChatType.Profile) - self.messagesCleared() - - proc isAddedContact*(self: ChatsView, id: string): bool {.slot.} = - result = self.status.contacts.isAdded(id) - - proc pushPinnedMessages*(self:ChatsView, pinnedMessages: var seq[Message]) = - for msg in pinnedMessages.mitems: - self.upsertChannel(msg.chatId) - - var message = self.messageList[msg.chatId].getMessageById(msg.id) - message.pinnedBy = msg.pinnedBy - message.isPinned = true - - self.pinnedMessagesList[msg.chatId].add(message) - # put the message as pinned in the message list - self.messageList[msg.chatId].changeMessagePinned(msg.id, true, msg.pinnedBy) - proc pushActivityCenterNotifications*(self:ChatsView, activityCenterNotifications: seq[ActivityCenterNotification]) = self.activityNotificationList.addActivityNotificationItemsToList(activityCenterNotifications) self.activityNotificationsChanged() @@ -386,42 +230,11 @@ QtObject: self.channelView.activeChannel.setChatItem(self.timelineChat) self.activeChannelChanged() - proc pushMessages*(self:ChatsView, messages: var seq[Message]) = - for msg in messages.mitems: - self.upsertChannel(msg.chatId) - msg.userName = self.status.chat.getUserName(msg.fromAuthor, msg.alias) - var msgIndex:int; - if self.status.chat.channels.hasKey(msg.chatId): - let chat = self.status.chat.channels[msg.chatId] - if (chat.chatType == ChatType.Profile): - let timelineChatId = status_utils.getTimelineChatId() - self.messageList[timelineChatId].add(msg) - if self.channelView.activeChannel.id == timelineChatId: self.activeChannelChanged() - msgIndex = self.messageList[timelineChatId].messages.len - 1 - else: - self.messageList[msg.chatId].add(msg) - msgIndex = self.messageList[msg.chatId].messages.len - 1 - self.messagePushed(msgIndex) - if self.channelOpenTime.getOrDefault(msg.chatId, high(int64)) < msg.timestamp.parseFloat.fromUnixFloat.toUnix: - var channel = self.channelView.chats.getChannelById(msg.chatId) - if (channel == nil): - channel = self.communities.getChannel(msg.chatId) - if (channel == nil): - continue - - if msg.chatId == self.channelView.activeChannel.id: - discard self.status.chat.markMessagesSeen(msg.chatId, @[msg.id]) - self.newMessagePushed() - - if not channel.muted: - let isAddedContact = channel.chatType.isOneToOne and self.isAddedContact(channel.id) - self.messageNotificationPushed(msg.chatId, escape_html(msg.text), msg.messageType, channel.chatType.int, msg.timestamp, msg.identicon, msg.userName, msg.hasMention, isAddedContact, channel.name) - proc updateUsernames*(self:ChatsView, contacts: seq[Profile]) = if contacts.len > 0: # Updating usernames for all the messages list - for k in self.messageList.keys: - self.messageList[k].updateUsernames(contacts) + for k in self.messageView.messageList.keys: + self.messageView.messageList[k].updateUsernames(contacts) self.channelView.activeChannel.contactsUpdated() proc updateChannelForContacts*(self: ChatsView, contacts: seq[Profile]) = @@ -441,44 +254,10 @@ QtObject: self.channelView.activeChannel.setChatItem(channel) self.activeChannelChanged() - - proc markMessageAsSent*(self:ChatsView, chat: string, messageId: string) = - if self.messageList.contains(chat): - self.messageList[chat].markMessageAsSent(messageId) - else: - error "Message could not be marked as sent", chat, messageId - - proc getMessageIndex(self: ChatsView, chatId: string, messageId: string): int {.slot.} = - if (not self.messageList.hasKey(chatId)): - return -1 - result = self.messageList[chatId].getMessageIndex(messageId) - - proc getMessageData(self: ChatsView, chatId: string, index: int, data: string): string {.slot.} = - if (not self.messageList.hasKey(chatId)): - return - - return self.messageList[chatId].getMessageData(index, data) - - proc getMessageList(self: ChatsView): QVariant {.slot.} = - self.upsertChannel(self.channelView.activeChannel.id) - return newQVariant(self.messageList[self.channelView.activeChannel.id]) - - QtProperty[QVariant] messageList: - read = getMessageList - notify = triggerActiveChannelChange - - proc getPinnedMessagesList(self: ChatsView): QVariant {.slot.} = - self.upsertChannel(self.channelView.activeChannel.id) - return newQVariant(self.pinnedMessagesList[self.channelView.activeChannel.id]) - - QtProperty[QVariant] pinnedMessagesList: - read = getPinnedMessagesList - notify = triggerActiveChannelChange - proc pushChatItem*(self: ChatsView, chatItem: Chat) = discard self.channelView.chats.addChatItemToList(chatItem) - self.messagePushed(self.messageList[chatItem.id].messages.len - 1) - + self.messageView.messagePushed(self.messageView.messageList[chatItem.id].messages.len - 1) + proc setTimelineChat*(self: ChatsView, chatItem: Chat) = self.timelineChat = chatItem @@ -499,54 +278,9 @@ QtObject: return -1 selectedChannel.chatType.int - proc messagesLoaded*(self: ChatsView) {.signal.} - - proc loadMoreMessages*(self: ChatsView) {.slot.} = - trace "Loading more messages", chaId = self.channelView.activeChannel.id - self.status.chat.chatMessages(self.channelView.activeChannel.id, false) - self.status.chat.chatReactions(self.channelView.activeChannel.id, false) - self.messagesLoaded(); - - proc loadMoreMessagesWithIndex*(self: ChatsView, channelIndex: int) {.slot.} = - if (self.channelView.chats.chats.len == 0): return - let selectedChannel = self.channelView.getChannel(channelIndex) - if (selectedChannel == nil): return - trace "Loading more messages", chaId = selectedChannel.id - self.status.chat.chatMessages(selectedChannel.id, false) - self.status.chat.chatReactions(selectedChannel.id, false) - self.messagesLoaded(); - - proc loadingMessagesChanged*(self: ChatsView, value: bool) {.signal.} - - proc asyncMessageLoad*(self: ChatsView, chatId: string) {.slot.} = - self.asyncMessageLoad("asyncMessageLoaded", chatId) - proc asyncActivityNotificationLoad*(self: ChatsView) {.slot.} = self.asyncActivityNotificationLoad("asyncActivityNotificationLoaded") - proc asyncMessageLoaded*(self: ChatsView, rpcResponse: string) {.slot.} = - let - rpcResponseObj = rpcResponse.parseJson - chatId = rpcResponseObj{"chatId"}.getStr - - if chatId == "": # .getStr() returns "" when field is not found - return - - let messages = rpcResponseObj{"messages"} - if(messages != nil and messages.kind != JNull): - let chatMessages = libstatus_chat.parseChatMessagesResponse(messages) - self.status.chat.chatMessages(chatId, true, chatMessages[0], chatMessages[1]) - - let rxns = rpcResponseObj{"reactions"} - if(rxns != nil and rxns.kind != JNull): - let reactions = status_chat.parseReactionsResponse(chatId, rxns) - self.status.chat.chatReactions(chatId, true, reactions[0], reactions[1]) - - let pinnedMsgs = rpcResponseObj{"pinnedMessages"} - if(pinnedMsgs != nil and pinnedMsgs.kind != JNull): - let pinnedMessages = libstatus_chat.parseChatMessagesResponse(pinnedMsgs) - self.status.chat.pinnedMessagesByChatID(chatId, pinnedMessages[0], pinnedMessages[1]) - proc asyncActivityNotificationLoaded*(self: ChatsView, rpcResponse: string) {.slot.} = let rpcResponseObj = rpcResponse.parseJson @@ -554,77 +288,32 @@ QtObject: let activityNotifications = parseActivityCenterNotifications(rpcResponseObj["activityNotifications"]) self.status.chat.activityCenterNotifications(activityNotifications[0], activityNotifications[1]) - proc hideLoadingIndicator*(self: ChatsView) {.slot.} = - self.loadingMessages = false - self.loadingMessagesChanged(false) - - proc setLoadingMessages*(self: ChatsView, value: bool) {.slot.} = - self.loadingMessages = value - self.loadingMessagesChanged(value) - - proc isLoadingMessages(self: ChatsView): QVariant {.slot.} = - return newQVariant(self.loadingMessages) - - QtProperty[QVariant] loadingMessages: - read = isLoadingMessages - write = setLoadingMessages - notify = loadingMessagesChanged - - proc requestMoreMessages*(self: ChatsView, fetchRange: int) {.slot.} = - self.loadingMessages = true - self.loadingMessagesChanged(true) - let mailserverWorker = self.status.tasks.marathon[MailserverWorker().name] - let task = RequestMessagesTaskArg( `method`: "requestMoreMessages", chatId: self.channelView.activeChannel.id) - mailserverWorker.start(task) - - proc fillGaps*(self: ChatsView, messageId: string) {.slot.} = - self.loadingMessages = true - self.loadingMessagesChanged(true) - discard self.status.mailservers.fillGaps(self.channelView.activeChannel.id, @[messageId]) - proc removeChat*(self: ChatsView, chatId: string) = discard self.channelView.chats.removeChatItemFromList(chatId) - if (self.messageList.hasKey(chatId)): - let index = self.getMessageListIndexById(chatId) + if (self.messageView.messageList.hasKey(chatId)): + let index = self.messageView.getMessageListIndexById(chatId) self.beginRemoveRows(newQModelIndex(), index, index) - self.messageList[chatId].delete - self.messageList.del(chatId) + self.messageView.messageList[chatId].delete + self.messageView.messageList.del(chatId) self.endRemoveRows() proc toggleReaction*(self: ChatsView, messageId: string, emojiId: int) {.slot.} = if self.channelView.activeChannel.id == status_utils.getTimelineChatId(): - let message = self.messageList[status_utils.getTimelineChatId()].getMessageById(messageId) + let message = self.messageView.messageList[status_utils.getTimelineChatId()].getMessageById(messageId) self.reactions.toggle(messageId, message.chatId, emojiId) else: self.reactions.toggle(messageId, self.channelView.activeChannel.id, emojiId) proc removeMessagesFromTimeline*(self: ChatsView, chatId: string) = - self.messageList[status_utils.getTimelineChatId()].deleteMessagesByChatId(chatId) - self.activeChannelChanged() - - proc unreadMessages*(self: ChatsView): int {.slot.} = - result = self.unreadMessageCnt - - proc unreadMessagesCntChanged*(self: ChatsView) {.signal.} - - QtProperty[int] unreadMessagesCount: - read = unreadMessages - notify = unreadMessagesCntChanged - - proc calculateUnreadMessages*(self: ChatsView) = - var unreadTotal = 0 - for chatItem in self.channelView.chats.chats: - unreadTotal = unreadTotal + chatItem.unviewedMessagesCount - if unreadTotal != self.unreadMessageCnt: - self.unreadMessageCnt = unreadTotal - self.unreadMessagesCntChanged() + self.messageView.messageList[status_utils.getTimelineChatId()].deleteMessagesByChatId(chatId) + self.channelView.activeChannelChanged() proc updateChats*(self: ChatsView, chats: seq[Chat]) = for chat in chats: if (chat.communityId != ""): self.communities.updateCommunityChat(chat) return - self.upsertChannel(chat.id) + self.messageView.upsertChannel(chat.id) self.channelView.chats.updateChat(chat) if(self.channelView.activeChannel.id == chat.id): self.channelView.activeChannel.setChatItem(chat) @@ -633,10 +322,7 @@ QtObject: if self.channelView.contextChannel.id == chat.id: self.channelView.contextChannel.setChatItem(chat) self.channelView.contextChannelChanged() - self.calculateUnreadMessages() - - proc deleteMessage*(self: ChatsView, channelId: string, messageId: string) = - self.messageList[channelId].deleteMessage(messageId) + self.messageView.calculateUnreadMessages() proc isConnected*(self: ChatsView): bool {.slot.} = result = self.status.network.isConnected @@ -675,80 +361,17 @@ QtObject: QtProperty[QVariant] transactions: read = getTransactions - method rowCount*(self: ChatsView, index: QModelIndex = nil): int = - result = self.messageList.len - - method data(self: ChatsView, index: QModelIndex, role: int): QVariant = - if not index.isValid: - return - if index.row < 0 or index.row >= self.messageList.len: - return - return newQVariant(toSeq(self.messageList.values)[index.row]) - - method roleNames(self: ChatsView): Table[int, string] = - { - ChatViewRoles.MessageList.int:"messages" - }.toTable - - proc removeMessagesByUserId(self: ChatsView, publicKey: string) {.slot.} = - for k in self.messageList.keys: - self.messageList[k].removeMessagesByUserId(publicKey) - - proc getMessageListIndex(self: ChatsView): int {.slot.} = - var idx = -1 - for msg in toSeq(self.messageList.values): - idx = idx + 1 - if(self.channelView.activeChannel.id == msg.id): return idx - return idx - - proc getMessageListIndexById(self: ChatsView, id: string): int {.slot.} = - var idx = -1 - for msg in toSeq(self.messageList.values): - idx = idx + 1 - if(id == msg.id): return idx - return idx - - proc addPinMessage*(self: ChatsView, messageId: string, chatId: string, pinnedBy: string) = - self.upsertChannel(chatId) - self.messageList[chatId].changeMessagePinned(messageId, true, pinnedBy) - var message = self.messageList[chatId].getMessageById(messageId) - message.pinnedBy = pinnedBy - self.pinnedMessagesList[chatId].add(message) - - proc removePinMessage*(self: ChatsView, messageId: string, chatId: string) = - self.upsertChannel(chatId) - self.messageList[chatId].changeMessagePinned(messageId, false, "") - try: - self.pinnedMessagesList[chatId].remove(messageId) - except Exception as e: - error "Error removing ", msg = e.msg - - proc pinMessage*(self: ChatsView, messageId: string, chatId: string) {.slot.} = - self.status.chat.setPinMessage(messageId, chatId, true) - self.addPinMessage(messageId, chatId, self.pubKey) - - proc unPinMessage*(self: ChatsView, messageId: string, chatId: string) {.slot.} = - self.status.chat.setPinMessage(messageId, chatId, false) - self.removePinMessage(messageId, chatId) - - proc addPinnedMessages*(self: ChatsView, pinnedMessages: seq[Message]) = - for pinnedMessage in pinnedMessages: - if (pinnedMessage.isPinned): - self.addPinMessage(pinnedMessage.id, pinnedMessage.localChatId, pinnedMessage.pinnedBy) - else: - self.removePinMessage(pinnedMessage.id, pinnedMessage.localChatId) - proc isActiveMailserverResult(self: ChatsView, resultEncoded: string) {.slot.} = let isActiveMailserverAvailable = decode[bool](resultEncoded) if isActiveMailserverAvailable: - self.setLoadingMessages(true) + self.messageView.setLoadingMessages(true) let mailserverWorker = self.status.tasks.marathon[MailserverWorker().name] task = RequestMessagesTaskArg(`method`: "requestMessages") mailserverWorker.start(task) proc requestAllHistoricMessagesResult(self: ChatsView, resultEncoded: string) {.slot.} = - self.setLoadingMessages(true) + self.messageView.setLoadingMessages(true) proc createCommunityChannel*(self: ChatsView, communityId: string, name: string, description: string, categoryId: string): string {.slot.} = try: @@ -789,3 +412,45 @@ QtObject: proc setActiveChannel*(self: ChatsView, channel: string) {.slot.} = self.channelView.setActiveChannel(channel) + # proc activeChannelChanged*(self: ChatsView) = + # self.channelView.activeChannelChanged() + + proc requestMoreMessages*(self: ChatsView, fetchRange: int) {.slot.} = + self.messageView.loadingMessages = true + self.messageView.loadingMessagesChanged(true) + let mailserverWorker = self.status.tasks.marathon[MailserverWorker().name] + let task = RequestMessagesTaskArg( `method`: "requestMoreMessages", chatId: self.channelView.activeChannel.id) + mailserverWorker.start(task) + + proc pushMessages*(self: ChatsView, messages: var seq[Message]) = + self.messageView.pushMessages(messages) + + proc pushPinnedMessages*(self: ChatsView, pinnedMessages: var seq[Message]) = + self.messageView.pushPinnedMessages(pinnedMessages) + + proc hideLoadingIndicator*(self: ChatsView) {.slot.} = + self.messageView.hideLoadingIndicator() + + proc deleteMessage*(self: ChatsView, channelId: string, messageId: string) = + self.messageView.deleteMessage(channelId, messageId) + + proc addPinnedMessages*(self: ChatsView, pinnedMessages: seq[Message]) = + self.messageView.addPinnedMessages(pinnedMessages) + + proc clearMessages*(self: ChatsView, id: string) = + self.messageView.clearMessages(id) + + proc asyncMessageLoad*(self: ChatsView, chatId: string) {.slot.} = + self.messageView.asyncMessageLoad(chatId) + + proc calculateUnreadMessages*(self: ChatsView) = + self.messageView.calculateUnreadMessages() + + proc sendingMessage*(self: ChatsView) = + self.messageView.sendingMessage() + + proc sendingMessageFailed*(self: ChatsView) = + self.messageView.sendingMessageFailed() + + proc markMessageAsSent*(self: ChatsView, chat: string, messageId: string) = + self.messageView.markMessageAsSent(chat, messageId) diff --git a/src/app/chat/views/messages.nim b/src/app/chat/views/messages.nim new file mode 100644 index 0000000000..d5f59d16b8 --- /dev/null +++ b/src/app/chat/views/messages.nim @@ -0,0 +1,422 @@ +import NimQml, Tables, json, sequtils, chronicles, times, re, sugar, strutils, os, strformat, algorithm + +import ../../../status/[status, contacts, types, mailservers] +import ../../../status/signals/types as signal_types +import ../../../status/ens as status_ens +import ../../../status/chat as status_chat +import ../../../status/messages as status_messages +import ../../../status/utils as status_utils +import ../../../status/chat/[chat, message] +import ../../../status/profile/profile +import ../../../status/tasks/[qt, task_runner_impl] + +import communities, chat_item, channels_list, communities, community_list, message_list, channel + +# TODO: remove me +import ../../../status/libstatus/chat as libstatus_chat + +logScope: + topics = "messages-view" + +type + ChatViewRoles {.pure.} = enum + MessageList = UserRole + 1 + +type + AsyncMessageLoadTaskArg = ref object of QObjectTaskArg + chatId: string + +const asyncMessageLoadTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[AsyncMessageLoadTaskArg](argEncoded) + var messages: JsonNode + var msgCallSuccess: bool + let msgCallResult = status_chat.rpcChatMessages(arg.chatId, newJString(""), 20, msgCallSuccess) + if(msgCallSuccess): + messages = msgCallResult.parseJson()["result"] + + var reactions: JsonNode + var reactionsCallSuccess: bool + let reactionsCallResult = status_chat.rpcReactions(arg.chatId, newJString(""), 20, reactionsCallSuccess) + if(reactionsCallSuccess): + reactions = reactionsCallResult.parseJson()["result"] + + var pinnedMessages: JsonNode + var pinnedMessagesCallSuccess: bool + let pinnedMessagesCallResult = status_chat.rpcPinnedChatMessages(arg.chatId, newJString(""), 20, pinnedMessagesCallSuccess) + if(pinnedMessagesCallSuccess): + pinnedMessages = pinnedMessagesCallResult.parseJson()["result"] + + let responseJson = %*{ + "chatId": arg.chatId, + "messages": messages, + "reactions": reactions, + "pinnedMessages": pinnedMessages + } + arg.finish(responseJson) + +proc asyncMessageLoad[T](self: T, slot: string, chatId: string) = + let arg = AsyncMessageLoadTaskArg( + tptr: cast[ByteAddress](asyncMessageLoadTask), + vptr: cast[ByteAddress](self.vptr), + slot: slot, + chatId: chatId + ) + self.status.tasks.threadpool.start(arg) + +QtObject: + type MessageView* = ref object of QAbstractListModel + status: Status + messageList*: OrderedTable[string, ChatMessageList] + pinnedMessagesList*: OrderedTable[string, ChatMessageList] + channelView*: ChannelView + communities*: CommunitiesView + pubKey*: string + loadingMessages*: bool + unreadMessageCnt: int + channelOpenTime*: Table[string, int64] + + proc setup(self: MessageView) = self.QAbstractListModel.setup + proc delete*(self: MessageView) = + for msg in self.messageList.values: + msg.delete + for msg in self.pinnedMessagesList.values: + msg.delete + self.messageList = initOrderedTable[string, ChatMessageList]() + self.pinnedMessagesList = initOrderedTable[string, ChatMessageList]() + self.channelOpenTime = initTable[string, int64]() + # self.QObject.delete + self.QAbstractListModel.delete + + proc newMessageView*(status: Status, channelView: ChannelView, communitiesView: CommunitiesView): MessageView = + new(result, delete) + result.status = status + result.channelView = channelView + result.communities = communitiesView + result.messageList = initOrderedTable[string, ChatMessageList]() + result.pinnedMessagesList = initOrderedTable[string, ChatMessageList]() + result.messageList[status_utils.getTimelineChatId()] = newChatMessageList(status_utils.getTimelineChatId(), result.status, false) + result.loadingMessages = false + result.unreadMessageCnt = 0 + result.setup + + # proc getMessageListIndexById(self: MessageView, id: string): int + + proc replaceMentionsWithPubKeys(self: MessageView, mentions: seq[string], contacts: seq[Profile], message: string, predicate: proc (contact: Profile): string): string = + var updatedMessage = message + for mention in mentions: + let matches = contacts.filter(c => "@" & predicate(c).toLowerAscii == mention.toLowerAscii).map(c => c.address) + if matches.len > 0: + let pubKey = matches[0] + var startIndex = 0 + var index = updatedMessage.find(mention) + + while index > -1: + if index == 0 or updatedMessage[index-1] == ' ': + updatedMessage = updatedMessage.replaceWord(mention, '@' & pubKey) + startIndex = index + mention.len + index = updatedMessage.find(mention, startIndex) + + result = updatedMessage + + proc sendMessage*(self: MessageView, message: string, replyTo: string, contentType: int = ContentType.Message.int, isStatusUpdate: bool = false, contactsString: string = "") {.slot.} = + let aliasPattern = re(r"(@[A-z][a-z]+ [A-z][a-z]* [A-z][a-z]*)", flags = {reStudy, reIgnoreCase}) + let ensPattern = re(r"(@\w+(?=(\.stateofus)?\.eth))", flags = {reStudy, reIgnoreCase}) + let namePattern = re(r"(@\w+)", flags = {reStudy, reIgnoreCase}) + + var contacts: seq[Profile] + if (contactsString == ""): + contacts = self.status.contacts.getContacts() + else: + let contactsJSON = parseJson(contactsString) + contacts = @[] + for contact in contactsJSON: + contacts.add(Profile( + address: contact["address"].str, + alias: contact["alias"].str, + ensName: contact["ensName"].str + )) + + let aliasMentions = findAll(message, aliasPattern) + let ensMentions = findAll(message, ensPattern) + let nameMentions = findAll(message, namePattern) + + var m = self.replaceMentionsWithPubKeys(aliasMentions, contacts, message, (c => c.alias)) + m = self.replaceMentionsWithPubKeys(ensMentions, contacts, m, (c => c.ensName)) + m = self.replaceMentionsWithPubKeys(nameMentions, contacts, m, (c => c.ensName.split(".")[0])) + + var channelId = self.channelView.activeChannel.id + + if isStatusUpdate: + channelId = "@" & self.pubKey + + self.status.chat.sendMessage(channelId, m, replyTo, contentType) + + proc verifyMessageSent*(self: MessageView, data: string) {.slot.} = + let messageData = data.parseJson + self.messageList[messageData["chatId"].getStr].checkTimeout(messageData["id"].getStr) + + proc resendMessage*(self: MessageView, chatId: string, messageId: string) {.slot.} = + self.status.messages.trackMessage(messageId, chatId) + self.status.chat.resendMessage(messageId) + self.messageList[chatId].resetTimeOut(messageId) + + proc sendingMessage*(self: MessageView) {.signal.} + + proc sendingMessageFailed*(self: MessageView) {.signal.} + + proc messagePushed*(self: MessageView, messageIndex: int) {.signal.} + proc newMessagePushed*(self: MessageView) {.signal.} + + proc messagesCleared*(self: MessageView) {.signal.} + + proc clearMessages*(self: MessageView, id: string) = + let channel = self.channelView.getChannelById(id) + if (channel == nil): + return + self.messageList[id].clear(not channel.isNil and channel.chatType != ChatType.Profile) + self.messagesCleared() + + proc upsertChannel*(self: MessageView, channel: string) = + var chat: Chat = nil + if self.status.chat.channels.hasKey(channel): + chat = self.status.chat.channels[channel] + else: + chat = self.communities.getChannel(channel) + if not self.messageList.hasKey(channel): + self.beginInsertRows(newQModelIndex(), self.messageList.len, self.messageList.len) + self.messageList[channel] = newChatMessageList(channel, self.status, not chat.isNil and chat.chatType != ChatType.Profile) + self.channelOpenTime[channel] = now().toTime.toUnix * 1000 + self.endInsertRows(); + if not self.pinnedMessagesList.hasKey(channel): + self.pinnedMessagesList[channel] = newChatMessageList(channel, self.status, false) + + proc pushPinnedMessages*(self:MessageView, pinnedMessages: var seq[Message]) = + for msg in pinnedMessages.mitems: + self.upsertChannel(msg.chatId) + + var message = self.messageList[msg.chatId].getMessageById(msg.id) + message.pinnedBy = msg.pinnedBy + message.isPinned = true + + self.pinnedMessagesList[msg.chatId].add(message) + # put the message as pinned in the message list + self.messageList[msg.chatId].changeMessagePinned(msg.id, true, msg.pinnedBy) + + proc isAddedContact*(self: MessageView, id: string): bool {.slot.} = + result = self.status.contacts.isAdded(id) + + proc messageNotificationPushed*(self: MessageView, chatId: string, text: string, messageType: string, chatType: int, timestamp: string, identicon: string, username: string, hasMention: bool, isAddedContact: bool, channelName: string) {.signal.} + + proc pushMessages*(self:MessageView, messages: var seq[Message]) = + for msg in messages.mitems: + self.upsertChannel(msg.chatId) + msg.userName = self.status.chat.getUserName(msg.fromAuthor, msg.alias) + var msgIndex:int; + if self.status.chat.channels.hasKey(msg.chatId): + let chat = self.status.chat.channels[msg.chatId] + if (chat.chatType == ChatType.Profile): + let timelineChatId = status_utils.getTimelineChatId() + self.messageList[timelineChatId].add(msg) + # if self.channelView.activeChannel.id == timelineChatId: self.activeChannelChanged() + if self.channelView.activeChannel.id == timelineChatId: self.channelView.activeChannelChanged() + msgIndex = self.messageList[timelineChatId].messages.len - 1 + else: + self.messageList[msg.chatId].add(msg) + msgIndex = self.messageList[msg.chatId].messages.len - 1 + self.messagePushed(msgIndex) + if self.channelOpenTime.getOrDefault(msg.chatId, high(int64)) < msg.timestamp.parseFloat.fromUnixFloat.toUnix: + var channel = self.channelView.chats.getChannelById(msg.chatId) + if (channel == nil): + channel = self.communities.getChannel(msg.chatId) + if (channel == nil): + continue + + if msg.chatId == self.channelView.activeChannel.id: + discard self.status.chat.markMessagesSeen(msg.chatId, @[msg.id]) + self.newMessagePushed() + + if not channel.muted: + let isAddedContact = channel.chatType.isOneToOne and self.isAddedContact(channel.id) + self.messageNotificationPushed(msg.chatId, escape_html(msg.text), msg.messageType, channel.chatType.int, msg.timestamp, msg.identicon, msg.userName, msg.hasMention, isAddedContact, channel.name) + + proc markMessageAsSent*(self:MessageView, chat: string, messageId: string) = + if self.messageList.contains(chat): + self.messageList[chat].markMessageAsSent(messageId) + else: + error "Message could not be marked as sent", chat, messageId + + proc getMessageIndex(self: MessageView, chatId: string, messageId: string): int {.slot.} = + if (not self.messageList.hasKey(chatId)): + return -1 + result = self.messageList[chatId].getMessageIndex(messageId) + + proc getMessageData(self: MessageView, chatId: string, index: int, data: string): string {.slot.} = + if (not self.messageList.hasKey(chatId)): + return + + return self.messageList[chatId].getMessageData(index, data) + + proc getMessageList(self: MessageView): QVariant {.slot.} = + self.upsertChannel(self.channelView.activeChannel.id) + return newQVariant(self.messageList[self.channelView.activeChannel.id]) + + QtProperty[QVariant] messageList: + read = getMessageList + notify = activeChannelChanged + + proc getPinnedMessagesList(self: MessageView): QVariant {.slot.} = + self.upsertChannel(self.channelView.activeChannel.id) + return newQVariant(self.pinnedMessagesList[self.channelView.activeChannel.id]) + + QtProperty[QVariant] pinnedMessagesList: + read = getPinnedMessagesList + notify = activeChannelChanged + + proc messagesLoaded*(self: MessageView) {.signal.} + + proc loadMoreMessages*(self: MessageView) {.slot.} = + trace "Loading more messages", chaId = self.channelView.activeChannel.id + self.status.chat.chatMessages(self.channelView.activeChannel.id, false) + self.status.chat.chatReactions(self.channelView.activeChannel.id, false) + self.messagesLoaded(); + + proc loadMoreMessagesWithIndex*(self: MessageView, channelIndex: int) {.slot.} = + if (self.channelView.chats.chats.len == 0): return + let selectedChannel = self.channelView.getChannel(channelIndex) + if (selectedChannel == nil): return + trace "Loading more messages", chaId = selectedChannel.id + self.status.chat.chatMessages(selectedChannel.id, false) + self.status.chat.chatReactions(selectedChannel.id, false) + self.messagesLoaded(); + + proc loadingMessagesChanged*(self: MessageView, value: bool) {.signal.} + + proc asyncMessageLoad*(self: MessageView, chatId: string) {.slot.} = + self.asyncMessageLoad("asyncMessageLoaded", chatId) + + proc asyncMessageLoaded*(self: MessageView, rpcResponse: string) {.slot.} = + let + rpcResponseObj = rpcResponse.parseJson + chatId = rpcResponseObj{"chatId"}.getStr + + if chatId == "": # .getStr() returns "" when field is not found + return + + let messages = rpcResponseObj{"messages"} + if(messages != nil and messages.kind != JNull): + let chatMessages = libstatus_chat.parseChatMessagesResponse(messages) + self.status.chat.chatMessages(chatId, true, chatMessages[0], chatMessages[1]) + + let rxns = rpcResponseObj{"reactions"} + if(rxns != nil and rxns.kind != JNull): + let reactions = status_chat.parseReactionsResponse(chatId, rxns) + self.status.chat.chatReactions(chatId, true, reactions[0], reactions[1]) + + let pinnedMsgs = rpcResponseObj{"pinnedMessages"} + if(pinnedMsgs != nil and pinnedMsgs.kind != JNull): + let pinnedMessages = libstatus_chat.parseChatMessagesResponse(pinnedMsgs) + self.status.chat.pinnedMessagesByChatID(chatId, pinnedMessages[0], pinnedMessages[1]) + + proc hideLoadingIndicator*(self: MessageView) {.slot.} = + self.loadingMessages = false + self.loadingMessagesChanged(false) + + proc setLoadingMessages*(self: MessageView, value: bool) {.slot.} = + self.loadingMessages = value + self.loadingMessagesChanged(value) + + proc isLoadingMessages(self: MessageView): QVariant {.slot.} = + return newQVariant(self.loadingMessages) + + QtProperty[QVariant] loadingMessages: + read = isLoadingMessages + write = setLoadingMessages + notify = loadingMessagesChanged + + proc fillGaps*(self: MessageView, messageId: string) {.slot.} = + self.loadingMessages = true + self.loadingMessagesChanged(true) + discard self.status.mailservers.fillGaps(self.channelView.activeChannel.id, @[messageId]) + + proc unreadMessages*(self: MessageView): int {.slot.} = + result = self.unreadMessageCnt + + proc unreadMessagesCntChanged*(self: MessageView) {.signal.} + + QtProperty[int] unreadMessagesCount: + read = unreadMessages + notify = unreadMessagesCntChanged + + proc calculateUnreadMessages*(self: MessageView) = + var unreadTotal = 0 + for chatItem in self.channelView.chats.chats: + unreadTotal = unreadTotal + chatItem.unviewedMessagesCount + if unreadTotal != self.unreadMessageCnt: + self.unreadMessageCnt = unreadTotal + self.unreadMessagesCntChanged() + + proc deleteMessage*(self: MessageView, channelId: string, messageId: string) = + self.messageList[channelId].deleteMessage(messageId) + + proc removeMessagesByUserId(self: MessageView, publicKey: string) {.slot.} = + for k in self.messageList.keys: + self.messageList[k].removeMessagesByUserId(publicKey) + + proc getMessageListIndex(self: MessageView): int {.slot.} = + var idx = -1 + for msg in toSeq(self.messageList.values): + idx = idx + 1 + if(self.channelView.activeChannel.id == msg.id): return idx + return idx + + proc getMessageListIndexById*(self: MessageView, id: string): int {.slot.} = + var idx = -1 + for msg in toSeq(self.messageList.values): + idx = idx + 1 + if(id == msg.id): return idx + return idx + + proc addPinMessage*(self: MessageView, messageId: string, chatId: string, pinnedBy: string) = + self.upsertChannel(chatId) + self.messageList[chatId].changeMessagePinned(messageId, true, pinnedBy) + var message = self.messageList[chatId].getMessageById(messageId) + message.pinnedBy = pinnedBy + self.pinnedMessagesList[chatId].add(message) + + proc removePinMessage*(self: MessageView, messageId: string, chatId: string) = + self.upsertChannel(chatId) + self.messageList[chatId].changeMessagePinned(messageId, false, "") + try: + self.pinnedMessagesList[chatId].remove(messageId) + except Exception as e: + error "Error removing ", msg = e.msg + + proc pinMessage*(self: MessageView, messageId: string, chatId: string) {.slot.} = + self.status.chat.setPinMessage(messageId, chatId, true) + self.addPinMessage(messageId, chatId, self.pubKey) + + proc unPinMessage*(self: MessageView, messageId: string, chatId: string) {.slot.} = + self.status.chat.setPinMessage(messageId, chatId, false) + self.removePinMessage(messageId, chatId) + + proc addPinnedMessages*(self: MessageView, pinnedMessages: seq[Message]) = + for pinnedMessage in pinnedMessages: + if (pinnedMessage.isPinned): + self.addPinMessage(pinnedMessage.id, pinnedMessage.localChatId, pinnedMessage.pinnedBy) + else: + self.removePinMessage(pinnedMessage.id, pinnedMessage.localChatId) + + method rowCount*(self: MessageView, index: QModelIndex = nil): int = + result = self.messageList.len + + method data(self: MessageView, index: QModelIndex, role: int): QVariant = + if not index.isValid: + return + if index.row < 0 or index.row >= self.messageList.len: + return + return newQVariant(toSeq(self.messageList.values)[index.row]) + + method roleNames(self: MessageView): Table[int, string] = + { + ChatViewRoles.MessageList.int:"messages" + }.toTable diff --git a/ui/app/AppLayouts/Chat/ChatColumn.qml b/ui/app/AppLayouts/Chat/ChatColumn.qml index 305241cc60..781fba0c3f 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn.qml @@ -90,14 +90,14 @@ StackLayout { property var suggestionsObj: ([]) function addSuggestionFromMessageList(i){ - const contactAddr = chatsModel.messageList.getMessageData(i, "publicKey"); + const contactAddr = chatsModel.messageView.messageList.getMessageData(i, "publicKey"); if(idMap[contactAddr]) return; suggestionsObj.push({ - alias: chatsModel.messageList.getMessageData(i, "alias"), - ensName: chatsModel.messageList.getMessageData(i, "ensName"), + alias: chatsModel.messageView.messageList.getMessageData(i, "alias"), + ensName: chatsModel.messageView.messageList.getMessageData(i, "ensName"), address: contactAddr, - identicon: chatsModel.messageList.getMessageData(i, "identicon"), - localNickname: chatsModel.messageList.getMessageData(i, "localName") + identicon: chatsModel.messageView.messageList.getMessageData(i, "identicon"), + localNickname: chatsModel.messageView.messageList.getMessageData(i, "localName") }) chatInput.suggestionsList.append(suggestionsObj[suggestionsObj.length - 1]); idMap[contactAddr] = true; @@ -123,7 +123,7 @@ StackLayout { chatInput.suggestionsList.append(suggestionsObj[suggestionsObj.length - 1]); idMap[contactAddr] = true; } - const len2 = chatsModel.messageList.rowCount(); + const len2 = chatsModel.messageView.messageList.rowCount(); for (let f = 0; f < len2; f++) { addSuggestionFromMessageList(f); } @@ -132,12 +132,12 @@ StackLayout { function showReplyArea() { isReply = true; isImage = false; - let replyMessageIndex = chatsModel.messageList.getMessageIndex(SelectedMessage.messageId); + let replyMessageIndex = chatsModel.messageView.messageList.getMessageIndex(SelectedMessage.messageId); if (replyMessageIndex === -1) return; - let userName = chatsModel.messageList.getMessageData(replyMessageIndex, "userName") - let message = chatsModel.messageList.getMessageData(replyMessageIndex, "message") - let identicon = chatsModel.messageList.getMessageData(replyMessageIndex, "identicon") + let userName = chatsModel.messageView.messageList.getMessageData(replyMessageIndex, "userName") + let message = chatsModel.messageView.messageList.getMessageData(replyMessageIndex, "message") + let identicon = chatsModel.messageView.messageList.getMessageData(replyMessageIndex, "identicon") chatInput.showReplyArea(userName, message, identicon) } @@ -165,7 +165,7 @@ StackLayout { isBlocked = profileModel.contacts.isContactBlocked(activeChatId); } onContactBlocked: { - chatsModel.removeMessagesByUserId(publicKey) + chatsModel.messageView.removeMessagesByUserId(publicKey) } } @@ -260,7 +260,7 @@ StackLayout { Layout.fillHeight: true clip: true Repeater { - model: chatsModel + model: chatsModel.messageView Loader { active: false sourceComponent: ChatMessages { @@ -273,7 +273,7 @@ StackLayout { Connections { target: chatsModel.channelView onActiveChannelChanged: { - stackLayoutChatMessages.currentIndex = chatsModel.getMessageListIndex(chatsModel.channelView.activeChannelIndex) + stackLayoutChatMessages.currentIndex = chatsModel.messageView.getMessageListIndex(chatsModel.channelView.activeChannelIndex) if(stackLayoutChatMessages.currentIndex > -1 && !stackLayoutChatMessages.children[stackLayoutChatMessages.currentIndex].active){ stackLayoutChatMessages.children[stackLayoutChatMessages.currentIndex].active = true; } @@ -295,7 +295,7 @@ StackLayout { } Connections { - target: chatsModel + target: chatsModel.messageView onMessagePushed: { addSuggestionFromMessageList(messageIndex); } @@ -321,9 +321,9 @@ StackLayout { Layout.preferredWidth: parent.width height: chatInput.height Layout.preferredHeight: height - + Connections { - target: chatsModel + target: chatsModel.messageView onLoadingMessagesChanged: if(value){ loadingMessagesIndicator.active = true @@ -336,7 +336,7 @@ StackLayout { Loader { id: loadingMessagesIndicator - active: chatsModel.loadingMessages + active: chatsModel.messageView.loadingMessages sourceComponent: loadingIndicator anchors.right: parent.right anchors.bottom: chatInput.top @@ -396,7 +396,7 @@ StackLayout { let msg = chatsModel.plainText(Emoji.deparse(chatInput.textInput.text)) if (msg.length > 0){ msg = chatInput.interpretMessage(msg) - chatsModel.sendMessage(msg, chatInput.isReply ? SelectedMessage.messageId : "", Utils.isOnlyEmoji(msg) ? Constants.emojiType : Constants.messageType, false, JSON.stringify(suggestionsObj)); + chatsModel.messageView.sendMessage(msg, chatInput.isReply ? SelectedMessage.messageId : "", Utils.isOnlyEmoji(msg) ? Constants.emojiType : Constants.messageType, false, JSON.stringify(suggestionsObj)); if(event) event.accepted = true sendMessageSound.stop(); Qt.callLater(sendMessageSound.play); diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ActivityChannelBadge.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ActivityChannelBadge.qml index ed2f37b45a..ff354386da 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ActivityChannelBadge.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ActivityChannelBadge.qml @@ -48,7 +48,7 @@ Rectangle { Item { property int replyMessageIndex: chatsModel.getMessageIndex(chatId, responseTo) - property string repliedMessageContent: replyMessageIndex > -1 ? chatsModel.getMessageData(chatId, replyMessageIndex, "message") : ""; + property string repliedMessageContent: replyMessageIndex > -1 ? chatsModel.messageView.getMessageData(chatId, replyMessageIndex, "message") : ""; onReplyMessageIndexChanged: { diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/InputArea.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/InputArea.qml index 8eefd2b16c..b1d9931ec0 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/InputArea.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/InputArea.qml @@ -11,7 +11,7 @@ Item { height: chatInput.height Connections { - target: chatsModel + target: chatsModel.messageView onLoadingMessagesChanged: if(value){ loadingMessagesIndicator.active = true @@ -24,7 +24,7 @@ Item { Loader { id: loadingMessagesIndicator - active: chatsModel.loadingMessages + active: chatsModel.messageView.loadingMessages sourceComponent: loadingIndicator anchors.right: parent.right anchors.bottom: chatInput.top @@ -81,7 +81,7 @@ Item { let msg = chatsModel.plainText(Emoji.deparse(chatInput.textInput.text)) if (msg.length > 0){ msg = chatInput.interpretMessage(msg) - chatsModel.sendMessage(msg, chatInput.isReply ? SelectedMessage.messageId : "", Utils.isOnlyEmoji(msg) ? Constants.emojiType : Constants.messageType, false, JSON.stringify(suggestionsObj)); + chatsModel.messageView.sendMessage(msg, chatInput.isReply ? SelectedMessage.messageId : "", Utils.isOnlyEmoji(msg) ? Constants.emojiType : Constants.messageType, false, JSON.stringify(suggestionsObj)); if(event) event.accepted = true sendMessageSound.stop(); Qt.callLater(sendMessageSound.play); diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml index 5f40ca500c..74368a95a6 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml @@ -170,7 +170,7 @@ ScrollView { } Connections { - target: chatsModel + target: chatsModel.messageView onMessagesLoaded: { loadingMessages = false; } @@ -266,7 +266,7 @@ ScrollView { property var loadMsgs : Backpressure.oneInTime(chatLogView, 500, function() { if(loadingMessages) return; loadingMessages = true; - chatsModel.loadMoreMessages(); + chatsModel.messageView.loadMoreMessages(); }); onContentYChanged: { diff --git a/ui/app/AppLayouts/Chat/ChatColumn/Message.qml b/ui/app/AppLayouts/Chat/ChatColumn/Message.qml index 0c50074c51..081c54814d 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/Message.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/Message.qml @@ -83,14 +83,14 @@ Item { property bool isExpired: (outgoingStatus === "sending" && (Math.floor(timestamp) + 180000) < Date.now()) property bool isStatusUpdate: false - property int replyMessageIndex: chatsModel.messageList.getMessageIndex(responseTo); - property string repliedMessageAuthor: replyMessageIndex > -1 ? chatsModel.messageList.getMessageData(replyMessageIndex, "userName") : ""; - property string repliedMessageAuthorPubkey: replyMessageIndex > -1 ? chatsModel.messageList.getMessageData(replyMessageIndex, "publicKey") : ""; + property int replyMessageIndex: chatsModel.messageView.messageList.getMessageIndex(responseTo); + property string repliedMessageAuthor: replyMessageIndex > -1 ? chatsModel.messageView.messageList.getMessageData(replyMessageIndex, "userName") : ""; + property string repliedMessageAuthorPubkey: replyMessageIndex > -1 ? chatsModel.messageView.messageList.getMessageData(replyMessageIndex, "publicKey") : ""; property bool repliedMessageAuthorIsCurrentUser: replyMessageIndex > -1 ? repliedMessageAuthorPubkey === profileModel.profile.pubKey : ""; - property string repliedMessageContent: replyMessageIndex > -1 ? chatsModel.messageList.getMessageData(replyMessageIndex, "message") : ""; - property int repliedMessageType: replyMessageIndex > -1 ? parseInt(chatsModel.messageList.getMessageData(replyMessageIndex, "contentType")) : 0; - property string repliedMessageImage: replyMessageIndex > -1 ? chatsModel.messageList.getMessageData(replyMessageIndex, "image") : ""; - property string repliedMessageUserIdenticon: replyMessageIndex > -1 ? chatsModel.messageList.getMessageData(replyMessageIndex, "identicon") : ""; + property string repliedMessageContent: replyMessageIndex > -1 ? chatsModel.messageView.messageList.getMessageData(replyMessageIndex, "message") : ""; + property int repliedMessageType: replyMessageIndex > -1 ? parseInt(chatsModel.messageView.messageList.getMessageData(replyMessageIndex, "contentType")) : 0; + property string repliedMessageImage: replyMessageIndex > -1 ? chatsModel.messageView.messageList.getMessageData(replyMessageIndex, "image") : ""; + property string repliedMessageUserIdenticon: replyMessageIndex > -1 ? chatsModel.messageView.messageList.getMessageData(replyMessageIndex, "identicon") : ""; property string repliedMessageUserImage: replyMessageIndex > -1 ? appMain.getProfileImage(repliedMessageAuthorPubkey, repliedMessageAuthorIsCurrentUser , false) || "" : ""; property var imageClick: function () {} @@ -256,7 +256,7 @@ Item { cursorShape: Qt.PointingHandCursor anchors.fill: parent onClicked: { - chatsModel.fillGaps(messageId) + chatsModel.messageView.fillGaps(messageId) root.visible = false; root.height = 0; } @@ -323,7 +323,7 @@ Item { fetchMoreButton.visible = false; fetchDate.visible = false; timer.setTimeout(function(){ - chatsModel.hideLoadingIndicator(); + chatsModel.messageView.hideLoadingIndicator(); fetchLoaderIndicator.active = false; fetchMoreButton.visible = true; fetchDate.visible = true; diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml index 75ac8f88a4..0ad6a31390 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml @@ -128,7 +128,7 @@ Loader { id: stickerId imageHeight: 56 imageWidth: 56 - stickerData: chatsModel.messageList.getMessageData(replyMessageIndex, "sticker") + stickerData: chatsModel.messageView.messageList.getMessageData(replyMessageIndex, "sticker") contentType: repliedMessageType container: root.container } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/Retry.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/Retry.qml index c0fe5ca72a..5f6ed023aa 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/Retry.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/Retry.qml @@ -14,7 +14,7 @@ StyledText { cursorShape: Qt.PointingHandCursor anchors.fill: parent onClicked: { - chatsModel.resendMessage(chatId, messageId) + chatsModel.messageView.resendMessage(chatId, messageId) } } } diff --git a/ui/app/AppLayouts/Chat/CommunityComponents/CommunityButton.qml b/ui/app/AppLayouts/Chat/CommunityComponents/CommunityButton.qml index 89ecc53530..1cfab034f4 100644 --- a/ui/app/AppLayouts/Chat/CommunityComponents/CommunityButton.qml +++ b/ui/app/AppLayouts/Chat/CommunityComponents/CommunityButton.qml @@ -52,7 +52,7 @@ StatusIconTabButton { height: 22 Text { id: messageCount - font.pixelSize: chatsModel.unreadMessagesCount > 99 ? 10 : 12 + font.pixelSize: chatsModel.messageView.unreadMessagesCount > 99 ? 10 : 12 color: Style.current.white anchors.centerIn: parent text: unviewedMessagesCount > 99 ? "99+" : unviewedMessagesCount diff --git a/ui/app/AppLayouts/Chat/CommunityComponents/CreateChannelPopup.qml b/ui/app/AppLayouts/Chat/CommunityComponents/CreateChannelPopup.qml index 94f36d74d0..62c35d408d 100644 --- a/ui/app/AppLayouts/Chat/CommunityComponents/CreateChannelPopup.qml +++ b/ui/app/AppLayouts/Chat/CommunityComponents/CreateChannelPopup.qml @@ -195,7 +195,7 @@ ModalPopup { StyledText { id: nbPinMessagesText - text: chatsModel.pinnedMessagesList.count + text: chatsModel.messageView.pinnedMessagesList.count anchors.verticalCenter: parent.verticalCenter padding: 0 font.pixelSize: 15 diff --git a/ui/app/AppLayouts/Chat/ContactsColumn/ChannelList.qml b/ui/app/AppLayouts/Chat/ContactsColumn/ChannelList.qml index 83edd0e38d..f192d3a514 100644 --- a/ui/app/AppLayouts/Chat/ContactsColumn/ChannelList.qml +++ b/ui/app/AppLayouts/Chat/ContactsColumn/ChannelList.qml @@ -106,7 +106,7 @@ Item { Connections { target: chatsModel.channelView onActiveChannelChanged: { - chatsModel.hideLoadingIndicator() + chatsModel.messageView.hideLoadingIndicator() chatGroupsListView.currentIndex = chatsModel.channelView.activeChannelIndex SelectedMessage.reset(); chatColumn.isReply = false; diff --git a/ui/app/AppLayouts/Chat/components/FetchMoreMessages.qml b/ui/app/AppLayouts/Chat/components/FetchMoreMessages.qml index 3ae6a1453f..f2018a1ac3 100644 --- a/ui/app/AppLayouts/Chat/components/FetchMoreMessages.qml +++ b/ui/app/AppLayouts/Chat/components/FetchMoreMessages.qml @@ -17,7 +17,7 @@ PopupMenu { onTriggered: { chatsModel.requestMoreMessages(Constants.fetchRangeLast24Hours) timer.setTimeout(function(){ - chatsModel.hideLoadingIndicator() + chatsModel.messageView.hideLoadingIndicator() }, 3000); } } @@ -28,7 +28,7 @@ PopupMenu { onTriggered: { chatsModel.requestMoreMessages(Constants.fetchRangeLast2Days) timer.setTimeout(function(){ - chatsModel.hideLoadingIndicator() + chatsModel.messageView.hideLoadingIndicator() }, 4000); } } @@ -39,7 +39,7 @@ PopupMenu { onTriggered: { chatsModel.requestMoreMessages(Constants.fetchRangeLast3Days) timer.setTimeout(function(){ - chatsModel.hideLoadingIndicator() + chatsModel.messageView.hideLoadingIndicator() }, 5000); } } @@ -50,7 +50,7 @@ PopupMenu { onTriggered: { chatsModel.requestMoreMessages(Constants.fetchRangeLast7Days) timer.setTimeout(function(){ - chatsModel.hideLoadingIndicator() + chatsModel.messageView.hideLoadingIndicator() }, 7000); } } diff --git a/ui/app/AppLayouts/Chat/components/GroupInfoPopup.qml b/ui/app/AppLayouts/Chat/components/GroupInfoPopup.qml index f557371dde..70b3b6e16e 100644 --- a/ui/app/AppLayouts/Chat/components/GroupInfoPopup.qml +++ b/ui/app/AppLayouts/Chat/components/GroupInfoPopup.qml @@ -212,7 +212,7 @@ ModalPopup { } StatusSettingsLineButton { - property int pinnedCount: chatsModel.pinnedMessagesList.count + property int pinnedCount: chatsModel.messageView.pinnedMessagesList.count id: pinnedMessagesBtn visible: pinnedCount > 0 diff --git a/ui/app/AppLayouts/Chat/components/MessageContextMenu.qml b/ui/app/AppLayouts/Chat/components/MessageContextMenu.qml index 9b8278af13..ac56ca2080 100644 --- a/ui/app/AppLayouts/Chat/components/MessageContextMenu.qml +++ b/ui/app/AppLayouts/Chat/components/MessageContextMenu.qml @@ -143,11 +143,11 @@ PopupMenu { qsTr("Pin") onTriggered: { if (pinnedMessage) { - chatsModel.unPinMessage(messageId, chatsModel.channelView.activeChannel.id) + chatsModel.messageView.unPinMessage(messageId, chatsModel.channelView.activeChannel.id) return } - chatsModel.pinMessage(messageId, chatsModel.channelView.activeChannel.id) + chatsModel.messageView.pinMessage(messageId, chatsModel.channelView.activeChannel.id) messageContextMenu.close() } icon.source: "../../../img/pin" diff --git a/ui/app/AppLayouts/Chat/components/PinnedMessagesPopup.qml b/ui/app/AppLayouts/Chat/components/PinnedMessagesPopup.qml index aaac0961d0..79b9f3699b 100644 --- a/ui/app/AppLayouts/Chat/components/PinnedMessagesPopup.qml +++ b/ui/app/AppLayouts/Chat/components/PinnedMessagesPopup.qml @@ -55,7 +55,7 @@ ModalPopup { ListView { id: pinnedMessageListView - model: chatsModel.pinnedMessagesList + model: chatsModel.messageView.pinnedMessagesList height: parent.height anchors.left: parent.left anchors.leftMargin: -Style.current.padding diff --git a/ui/app/AppLayouts/Timeline/TimelineLayout.qml b/ui/app/AppLayouts/Timeline/TimelineLayout.qml index eaa6affc78..d3634f6d42 100644 --- a/ui/app/AppLayouts/Timeline/TimelineLayout.qml +++ b/ui/app/AppLayouts/Timeline/TimelineLayout.qml @@ -83,7 +83,7 @@ ScrollView { var msg = chatsModel.plainText(Emoji.deparse(statusUpdateInput.textInput.text)) if (msg.length > 0){ msg = statusUpdateInput.interpretMessage(msg) - chatsModel.sendMessage(msg, "", Utils.isOnlyEmoji(msg) ? Constants.emojiType : Constants.messageType, true, ""); + chatsModel.messageView.sendMessage(msg, "", Utils.isOnlyEmoji(msg) ? Constants.emojiType : Constants.messageType, true, ""); statusUpdateInput.textInput.text = ""; if(event) event.accepted = true sendMessageSound.stop() @@ -97,7 +97,7 @@ ScrollView { anchors.top: statusUpdateInput.bottom anchors.topMargin: 40 anchors.horizontalCenter: parent.horizontalCenter - visible: chatsModel.messageList.rowCount() === 0 + visible: chatsModel.messageView.messageList.rowCount() === 0 } ListView { diff --git a/ui/app/AppMain.qml b/ui/app/AppMain.qml index 869fbfcc98..ffc0a42d57 100644 --- a/ui/app/AppMain.qml +++ b/ui/app/AppMain.qml @@ -444,7 +444,7 @@ RowLayout { checked: !chatsModel.communities.activeCommunity.active && sLayout.currentIndex === Utils.getAppSectionIndex(Constants.chat) Rectangle { - property int badgeCount: chatsModel.unreadMessagesCount + profileModel.contacts.contactRequests.count + property int badgeCount: chatsModel.messageView.unreadMessagesCount + profileModel.contacts.contactRequests.count id: chatBadge visible: chatBadge.badgeCount > 0 @@ -540,7 +540,7 @@ RowLayout { Layout.fillHeight: true // Loaders do not have access to the context, so props need to be set // Adding a "_" to avoid a binding loop - property var _chatsModel: chatsModel + property var _chatsModel: chatsModel.messageView property var _walletModel: walletModel property var _utilsModel: utilsModel property var _web3Provider: web3Provider diff --git a/ui/shared/SearchResults.qml b/ui/shared/SearchResults.qml index f9dffbe7b9..597bc5d26d 100644 --- a/ui/shared/SearchResults.qml +++ b/ui/shared/SearchResults.qml @@ -20,7 +20,7 @@ Item { property string address: "" property bool resultClickable: true - property bool isAddedContact: pubKey != "" ? chatsModel.isAddedContact(pubKey) : false + property bool isAddedContact: pubKey != "" ? chatsModel.messageView.isAddedContact(pubKey) : false signal resultClicked(string pubKey) signal addToContactsButtonClicked(string pubKey) diff --git a/ui/shared/status/StatusChatInfo.qml b/ui/shared/status/StatusChatInfo.qml index 8984266045..04efc063d7 100644 --- a/ui/shared/status/StatusChatInfo.qml +++ b/ui/shared/status/StatusChatInfo.qml @@ -151,7 +151,7 @@ Item { property bool hovered: false id: pinnedMessagesGroup - visible: chatType !== Constants.chatTypePublic && chatsModel.pinnedMessagesList.count > 0 + visible: chatType !== Constants.chatTypePublic && chatsModel.messageView.pinnedMessagesList.count > 0 width: childrenRect.width height: vertiSep.height anchors.left: chatInfo.right @@ -184,7 +184,7 @@ Item { StyledText { id: nbPinnedMessagesText color: pinnedMessagesGroup.hovered ? Style.current.textColor : Style.current.secondaryText - text: chatsModel.pinnedMessagesList.count + text: chatsModel.messageView.pinnedMessagesList.count font.pixelSize: 12 font.underline: pinnedMessagesGroup.hovered anchors.left: pinImg.right