From 2eee8c7a2dadc0ee59a72251c505b2a68869d421 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Fri, 22 May 2020 15:57:35 -0400 Subject: [PATCH] feat: show last message and unread message count --- src/app/chat/chatItem.nim | 18 +++++++ src/app/chat/core.nim | 18 +++++-- src/app/chat/view.nim | 51 ++++++++++++++----- src/app/signals/messages.nim | 72 ++++++++++++++++++--------- src/app/signals/types.nim | 34 +++++++++++-- src/nim_status_client.nim | 1 - ui/app/AppLayouts/Chat/ChatLayout.qml | 7 +-- 7 files changed, 151 insertions(+), 50 deletions(-) create mode 100644 src/app/chat/chatItem.nim diff --git a/src/app/chat/chatItem.nim b/src/app/chat/chatItem.nim new file mode 100644 index 0000000000..e408965dc2 --- /dev/null +++ b/src/app/chat/chatItem.nim @@ -0,0 +1,18 @@ +type ChatItem* = ref object + name*: string + lastMessage*: string + timestamp*: int64 + unviewedMessagesCount*: int + +proc newChatItem*(): ChatItem = + new(result) + result.name = "" + result.lastMessage = "" + result.timestamp = 0 + result.unviewedMessagesCount = 0 + +proc findByName*(self: seq[ChatItem], name: string): int = + result = -1 + for item in self: + inc result + if(item.name == name): break diff --git a/src/app/chat/core.nim b/src/app/chat/core.nim index 6f353fd7de..934390922d 100644 --- a/src/app/chat/core.nim +++ b/src/app/chat/core.nim @@ -2,9 +2,10 @@ import NimQml import json, eventemitter import ../../status/chat as status_chat import view +import chatItem import messages import ../signals/types -import ../../models/chat +import ../../models/chat as chat_model type ChatController* = ref object of SignalSubscriber view*: ChatsView @@ -40,9 +41,18 @@ proc load*(self: ChatController, chatId: string) = discard self.view.joinChat(chatId) self.view.setActiveChannelByIndex(0) -proc onSignal(self: ChatController, data: Signal) = - var chatSignal = cast[ChatSignal](data) - for message in chatSignal.messages: +method onSignal(self: ChatController, data: Signal) = + var messageSignal = cast[MessageSignal](data) + + for c in messageSignal.chats: + let channel = newChatitem() + channel.name = c.name + channel.lastMessage = c.lastMessage.text + channel.timestamp = c.timestamp + channel.unviewedMessagesCount = c.unviewedMessagesCount + self.view.updateChat(channel) + + for message in messageSignal.messages: let chatMessage = newChatMessage() chatMessage.userName = message.alias chatMessage.message = message.text diff --git a/src/app/chat/view.nim b/src/app/chat/view.nim index c30c05fe50..aa0b282b2e 100644 --- a/src/app/chat/view.nim +++ b/src/app/chat/view.nim @@ -2,17 +2,21 @@ import NimQml import Tables import messages import messageList +import chatItem import ../../models/chat type RoleNames {.pure.} = enum Name = UserRole + 1, + LastMessage = UserRole + 2 + Timestamp = UserRole + 3 + UnreadMessages = UserRole + 4 QtObject: type ChatsView* = ref object of QAbstractListModel model: ChatModel - names*: seq[string] + chats: seq[ChatItem] callResult: string messageList: Table[string, ChatMessageList] activeChannel: string @@ -24,7 +28,7 @@ QtObject: proc newChatsView*(model: ChatModel): ChatsView = new(result, delete) result.model = model - result.names = @[] + result.chats = @[] result.activeChannel = "" result.messageList = initTable[string, ChatMessageList]() result.setup() @@ -33,17 +37,29 @@ QtObject: if not self.messageList.hasKey(channel): self.messageList[channel] = newChatMessageList() - method rowCount(self: ChatsView, index: QModelIndex = nil): int = self.names.len + method rowCount(self: ChatsView, index: QModelIndex = nil): int = self.chats.len method data(self: ChatsView, index: QModelIndex, role: int): QVariant = if not index.isValid: return - if index.row < 0 or index.row >= self.names.len: + if index.row < 0 or index.row >= self.chats.len: return - return newQVariant(self.names[index.row]) + + let chatItem = self.chats[index.row] + let chatItemRole = role.RoleNames + case chatItemRole: + of RoleNames.Name: result = newQVariant(chatItem.name) + of RoleNames.Timestamp: result = newQVariant($chatItem.timestamp) + of RoleNames.LastMessage: result = newQVariant(chatItem.lastMessage) + of RoleNames.UnreadMessages: result = newQVariant(chatItem.unviewedMessagesCount) method roleNames(self: ChatsView): Table[int, string] = - { RoleNames.Name.int:"name"}.toTable + { + RoleNames.Name.int:"name", + RoleNames.Timestamp.int:"timestamp", + RoleNames.LastMessage.int: "lastMessage", + RoleNames.UnreadMessages.int: "unviewedMessagesCount" + }.toTable proc onSend*(self: ChatsView, inputJSON: string) {.slot.} = discard self.model.sendMessage(self.activeChannel, inputJSON) @@ -57,8 +73,8 @@ QtObject: proc activeChannelChanged*(self: ChatsView) {.signal.} proc setActiveChannelByIndex*(self: ChatsView, index: int) {.slot.} = - if self.activeChannel == self.names[index]: return - self.activeChannel = self.names[index] + if self.activeChannel == self.chats[index].name: return + self.activeChannel = self.chats[index].name self.activeChannelChanged() QtProperty[string] activeChannel: @@ -80,18 +96,27 @@ QtObject: proc addToList(self: ChatsView, channel: string): int = if(self.activeChannel == ""): self.setActiveChannel(channel) - - self.beginInsertRows(newQModelIndex(), self.names.len, self.names.len) - self.names.add(channel) + var chatItem = newChatItem() + chatItem.name = channel self.upsertChannel(channel) + self.beginInsertRows(newQModelIndex(), self.chats.len, self.chats.len) + self.chats.add(chatItem) self.endInsertRows() - result = self.names.len - 1 + result = self.chats.len - 1 proc joinChat*(self: ChatsView, channel: string): int {.slot.} = self.setActiveChannel(channel) if self.model.hasChannel(channel): - result = self.names.find(channel) + result = self.chats.findByName(channel) else: self.model.join(channel) result = self.addToList(channel) + + proc updateChat*(self: ChatsView, chat: ChatItem) = + var idx = self.chats.findByName(chat.name) + if idx > -1: + self.chats[idx] = chat + var x = self.createIndex(idx,0,nil) + var y = self.createIndex(idx,0,nil) + self.dataChanged(x, y, @[RoleNames.Timestamp.int, RoleNames.LastMessage.int, RoleNames.UnreadMessages.int]) diff --git a/src/app/signals/messages.nim b/src/app/signals/messages.nim index fddba6ae46..7fb4a6d571 100644 --- a/src/app/signals/messages.nim +++ b/src/app/signals/messages.nim @@ -1,33 +1,57 @@ import json import types +proc toMessage(jsonMsg: JsonNode): Message +proc toChat(jsonChat: JsonNode): Chat + proc fromEvent*(event: JsonNode): Signal = - var signal:ChatSignal = ChatSignal() + var signal:MessageSignal = MessageSignal() signal.messages = @[] if event["event"]{"messages"} != nil: for jsonMsg in event["event"]["messages"]: - let msg = Message( - alias: jsonMsg{"alias"}.getStr, - chatId: jsonMsg{"chatId"}.getStr, - clock: $jsonMsg{"clock"}.getInt, - contentType: jsonMsg{"contentType"}.getInt, - ensName: jsonMsg{"ensName"}.getStr, - fromAuthor: jsonMsg{"from"}.getStr, - id: jsonMsg{"identicon"}.getStr, - identicon: jsonMsg{"identicon"}.getStr, - lineCount: jsonMsg{"lineCount"}.getInt, - localChatId: jsonMsg{"localChatId"}.getStr, - messageType: jsonMsg{"messageType"}.getStr, - replace: jsonMsg{"replace"}.getStr, - responseTo: jsonMsg{"responseTo"}.getStr, - rtl: jsonMsg{"rtl"}.getBool, - seen: jsonMsg{"seen"}.getBool, - text: jsonMsg{"text"}.getStr, - timestamp: $jsonMsg{"timestamp"}.getInt, - whisperTimestamp: $jsonMsg{"whisperTimestamp"}.getInt, - isCurrentUser: false # TODO: this must compare the fromAuthor against current user because the messages received from the mailserver will arrive as signals too, and those include the current user messages - ) - signal.messages.add(msg) + signal.messages.add(jsonMsg.toMessage) - result = signal \ No newline at end of file + if event["event"]{"chats"} != nil: + for jsonChat in event["event"]["chats"]: + signal.chats.add(jsonChat.toChat) + + result = signal + +proc toChat(jsonChat: JsonNode): Chat = + result = Chat( + id: jsonChat{"id"}.getStr, + name: jsonChat{"name"}.getStr, + color: jsonChat{"color"}.getStr, + active: jsonChat{"active"}.getBool, + chatType: ChatType(jsonChat{"chatType"}.getInt), + timestamp: jsonChat{"timestamp"}.getBiggestInt, + lastClockValue: jsonChat{"lastClockValue"}.getBiggestInt, + deletedAtClockValue: jsonChat{"deletedAtClockValue"}.getBiggestInt, + unviewedMessagesCount: jsonChat{"unviewedMessagesCount"}.getInt, + lastMessage: jsonChat{"lastMessage"}.toMessage + ) + + +proc toMessage(jsonMsg: JsonNode): Message = + result = Message( + alias: jsonMsg{"alias"}.getStr, + chatId: jsonMsg{"chatId"}.getStr, + clock: $jsonMsg{"clock"}.getInt, + contentType: jsonMsg{"contentType"}.getInt, + ensName: jsonMsg{"ensName"}.getStr, + fromAuthor: jsonMsg{"from"}.getStr, + id: jsonMsg{"identicon"}.getStr, + identicon: jsonMsg{"identicon"}.getStr, + lineCount: jsonMsg{"lineCount"}.getInt, + localChatId: jsonMsg{"localChatId"}.getStr, + messageType: jsonMsg{"messageType"}.getStr, + replace: jsonMsg{"replace"}.getStr, + responseTo: jsonMsg{"responseTo"}.getStr, + rtl: jsonMsg{"rtl"}.getBool, + seen: jsonMsg{"seen"}.getBool, + text: jsonMsg{"text"}.getStr, + timestamp: $jsonMsg{"timestamp"}.getInt, + whisperTimestamp: $jsonMsg{"whisperTimestamp"}.getInt, + isCurrentUser: false # TODO: this must compare the fromAuthor against current user because the messages received from the mailserver will arrive as signals too, and those include the current user messages + ) \ No newline at end of file diff --git a/src/app/signals/types.nim b/src/app/signals/types.nim index 8b2a39c490..c56b664dc4 100644 --- a/src/app/signals/types.nim +++ b/src/app/signals/types.nim @@ -1,3 +1,5 @@ +import chronicles + type SignalSubscriber* = ref object of RootObj type Signal* = ref object of RootObj @@ -30,10 +32,32 @@ type Message* = object whisperTimestamp*: string isCurrentUser*: bool - -type ChatSignal* = ref object of Signal - messages*: seq[Message] - # Override this method method onSignal*(self: SignalSubscriber, data: Signal) {.base.} = - echo "Received a signal" # TODO: log signal received \ No newline at end of file + discard + # TODO: log signal received + +type ChatType* = enum + ChatTypeOneToOne = 1, + ChatTypePublic = 2, + ChatTypePrivateGroupChat = 3 + +type Chat* = object + id*: string # ID is the id of the chat, for public chats it is the name e.g. status, for one-to-one is the hex encoded public key and for group chats is a random uuid appended with the hex encoded pk of the creator of the chat + name*: string + color*: string + active*: bool # indicates whether the chat has been soft deleted + chatType*: ChatType + timestamp*: int64 # indicates the last time this chat has received/sent a message + lastClockValue*: int64 # indicates the last clock value to be used when sending messages + deletedAtClockValue*: int64 # indicates the clock value at time of deletion, messages with lower clock value of this should be discarded + unviewedMessagesCount*: int + lastMessage*: Message + # Group chat fields + # members ? + # membershipUpdateEvents # ? + + +type MessageSignal* = ref object of Signal + messages*: seq[Message] + chats*: seq[Chat] \ No newline at end of file diff --git a/src/nim_status_client.nim b/src/nim_status_client.nim index c6aca99dca..aa48de34e2 100644 --- a/src/nim_status_client.nim +++ b/src/nim_status_client.nim @@ -75,7 +75,6 @@ proc mainProc() = appState.subscribe(proc () = for channel in appState.channels: - echo channel.name chat.load(channel.name) ) diff --git a/ui/app/AppLayouts/Chat/ChatLayout.qml b/ui/app/AppLayouts/Chat/ChatLayout.qml index 9957a2997b..d7628914e8 100644 --- a/ui/app/AppLayouts/Chat/ChatLayout.qml +++ b/ui/app/AppLayouts/Chat/ChatLayout.qml @@ -181,7 +181,7 @@ SplitView { } Text { id: lastChatMessage - text: "Chatting blah blah..." + text: lastMessage || qsTr("No messages") anchors.right: contactNumberChatsCircle.left anchors.rightMargin: Theme.smallPadding elide: Text.ElideRight @@ -194,7 +194,7 @@ SplitView { } Text { id: contactTime - text: "12:22 AM" + text: timestamp anchors.right: parent.right anchors.rightMargin: Theme.padding anchors.top: parent.top @@ -212,9 +212,10 @@ SplitView { anchors.right: parent.right anchors.rightMargin: Theme.padding color: Theme.blue + visible: unviewedMessagesCount > 0 Text { id: contactNumberChats - text: qsTr("1") + text: unviewedMessagesCount anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter color: "white"