feat: Determine if a message was sent

This commit is contained in:
Richard Ramos 2020-07-01 14:24:13 -04:00 committed by Iuri Matias
parent 7eb44366da
commit 64452e71b9
17 changed files with 202 additions and 55 deletions

View File

@ -1,6 +1,7 @@
import NimQml, eventemitter, chronicles, tables
import ../../status/chat as chat_model
import ../../status/mailservers as mailserver_model
import ../../status/messages as messages_model
import ../../signals/types
import ../../status/libstatus/types as status_types
import ../../status/libstatus/wallet as status_wallet
@ -27,45 +28,8 @@ proc delete*(self: ChatController) =
delete self.variant
delete self.view
proc handleChatEvents(self: ChatController) =
# Display already saved messages
self.status.events.on("messagesLoaded") do(e:Args):
self.view.pushMessages(MsgsLoadedArgs(e).messages)
self.status.events.on("contactUpdate") do(e: Args):
var evArgs = ContactUpdateArgs(e)
self.view.updateUsernames(evArgs.contacts)
self.status.events.on("chatUpdate") do(e: Args):
var evArgs = ChatUpdateArgs(e)
self.view.updateUsernames(evArgs.contacts)
self.view.updateChats(evArgs.chats)
self.view.pushMessages(evArgs.messages)
self.status.events.on("chatHistoryCleared") do(e: Args):
var args = ChannelArgs(e)
self.view.clearMessages(args.chat.id)
self.status.events.on("channelJoined") do(e: Args):
var channel = ChannelArgs(e)
discard self.view.chats.addChatItemToList(channel.chat)
self.status.chat.chatMessages(channel.chat.id)
self.view.setActiveChannel(channel.chat.id)
self.status.events.on("channelLeft") do(e: Args):
discard self.view.chats.removeChatItemFromList(self.view.activeChannel.chatItem.id)
self.status.events.on("activeChannelChanged") do(e: Args):
self.view.setActiveChannel(ChatIdArg(e).chatId)
proc handleMailserverEvents(self: ChatController) =
self.status.events.on("mailserverTopics") do(e: Args):
self.status.mailservers.addTopics(TopicArgs(e).topics)
if(self.status.mailservers.isSelectedMailserverAvailable):
self.status.mailservers.requestMessages()
self.status.events.on("mailserverAvailable") do(e:Args):
self.status.mailservers.requestMessages()
include event_handling
include signal_handling
proc init*(self: ChatController) =
self.handleMailserverEvents()
@ -83,16 +47,10 @@ proc init*(self: ChatController) =
self.view.addRecentStickerToList(sticker)
self.status.chat.addStickerToRecent(sticker)
proc handleMessage(self: ChatController, data: MessageSignal) =
self.status.chat.update(data.chats, data.messages)
proc handleDiscoverySummary(self: ChatController, data: DiscoverySummarySignal) =
## Handle mailserver peers being added and removed
self.status.mailservers.peerSummaryChange(data.enodes)
method onSignal(self: ChatController, data: Signal) =
case data.signalType:
of SignalType.Message: handleMessage(self, MessageSignal(data))
of SignalType.DiscoverySummary: handleDiscoverySummary(self, DiscoverySummarySignal(data))
of SignalType.EnvelopeSent: handleEnvelopeSent(self, EnvelopeSentSignal(data))
else:
warn "Unhandled signal received", signalType = data.signalType

View File

@ -0,0 +1,48 @@
proc handleChatEvents(self: ChatController) =
# Display already saved messages
self.status.events.on("messagesLoaded") do(e:Args):
self.view.pushMessages(MsgsLoadedArgs(e).messages)
self.status.events.on("contactUpdate") do(e: Args):
var evArgs = ContactUpdateArgs(e)
self.view.updateUsernames(evArgs.contacts)
self.status.events.on("chatUpdate") do(e: Args):
var evArgs = ChatUpdateArgs(e)
self.view.updateUsernames(evArgs.contacts)
self.view.updateChats(evArgs.chats)
self.view.pushMessages(evArgs.messages)
self.status.events.on("chatHistoryCleared") do(e: Args):
var args = ChannelArgs(e)
self.view.clearMessages(args.chat.id)
self.status.events.on("channelJoined") do(e: Args):
var channel = ChannelArgs(e)
discard self.view.chats.addChatItemToList(channel.chat)
self.status.chat.chatMessages(channel.chat.id)
self.view.setActiveChannel(channel.chat.id)
self.status.events.on("channelLeft") do(e: Args):
discard self.view.chats.removeChatItemFromList(self.view.activeChannel.chatItem.id)
self.status.events.on("activeChannelChanged") do(e: Args):
self.view.setActiveChannel(ChatIdArg(e).chatId)
self.status.events.on("sendingMessage") do(e:Args):
var msg = MessageArgs(e)
self.status.messages.trackMessage(msg.id, msg.channel)
self.status.events.on("messageSent") do(e:Args):
var msg = MessageSentArgs(e)
self.view.markMessageAsSent(msg.chatId, msg.id)
proc handleMailserverEvents(self: ChatController) =
self.status.events.on("mailserverTopics") do(e: Args):
self.status.mailservers.addTopics(TopicArgs(e).topics)
if(self.status.mailservers.isSelectedMailserverAvailable):
self.status.mailservers.requestMessages()
self.status.events.on("mailserverAvailable") do(e:Args):
self.status.mailservers.requestMessages()

View File

@ -0,0 +1,10 @@
proc handleMessage(self: ChatController, data: MessageSignal) =
self.status.chat.update(data.chats, data.messages)
proc handleDiscoverySummary(self: ChatController, data: DiscoverySummarySignal) =
## Handle mailserver peers being added and removed
self.status.mailservers.peerSummaryChange(data.enodes)
proc handleEnvelopeSent(self: ChatController, data: EnvelopeSentSignal) =
self.status.messages.updateStatus(data.messageIds)

View File

@ -147,6 +147,9 @@ QtObject:
for k in self.messageList.keys:
self.messageList[k].updateUsernames(contacts)
proc markMessageAsSent*(self:ChatsView, chat: string, messageId: string) =
self.messageList[chat].markMessageAsSent(messageId)
proc getMessageList(self: ChatsView): QVariant {.slot.} =
self.upsertChannel(self.activeChannel.id)
return newQVariant(self.messageList[self.activeChannel.id])
@ -160,7 +163,7 @@ QtObject:
self.messagePushed()
proc sendMessage*(self: ChatsView, message: string) {.slot.} =
discard self.status.chat.sendMessage(self.activeChannel.id, message)
self.status.chat.sendMessage(self.activeChannel.id, message)
proc addRecentStickerToList*(self: ChatsView, sticker: Sticker) =
self.stickers[RECENT_STICKERS].addStickerToList(sticker)

View File

@ -21,6 +21,7 @@ type
ChatId = UserRole + 10
SectionIdentifier = UserRole + 11
Id = UserRole + 12
OutgoingStatus = UserRole + 13
QtObject:
type
@ -71,6 +72,7 @@ QtObject:
of ChatMessageRoles.ChatId: result = newQVariant(message.chatId)
of ChatMessageRoles.SectionIdentifier: result = newQVariant(sectionIdentifier(message))
of ChatMessageRoles.Id: result = newQVariant(message.id)
of ChatMessageRoles.OutgoingStatus: result = newQVariant(message.outgoingStatus)
method roleNames(self: ChatMessageList): Table[int, string] =
{
@ -85,7 +87,8 @@ QtObject:
ChatMessageRoles.FromAuthor.int:"fromAuthor",
ChatMessageRoles.ChatId.int:"chatId",
ChatMessageRoles.SectionIdentifier.int: "sectionIdentifier",
ChatMessageRoles.Id.int: "messageId"
ChatMessageRoles.Id.int: "messageId",
ChatMessageRoles.OutgoingStatus.int: "outgoingStatus"
}.toTable
proc add*(self: ChatMessageList, message: Message) =
@ -104,6 +107,15 @@ QtObject:
self.messages = @[]
self.endResetModel()
proc markMessageAsSent*(self: ChatMessageList, messageId: string)=
let topLeft = self.createIndex(0, 0, nil)
let bottomRight = self.createIndex(self.messages.len, 0, nil)
for m in self.messages.mitems:
if m.id == messageId:
m.outgoingStatus = "sent"
self.dataChanged(topLeft, bottomRight, @[ChatMessageRoles.OutgoingStatus.int])
proc updateUsernames*(self: ChatMessageList, contacts: seq[Profile]) =
let topLeft = self.createIndex(0, 0, nil)
let bottomRight = self.createIndex(self.messages.len, 0, nil)

View File

@ -107,6 +107,7 @@ proc mainProc() =
signalController.addSubscriber(SignalType.Message, chat)
signalController.addSubscriber(SignalType.Message, profile)
signalController.addSubscriber(SignalType.DiscoverySummary, chat)
signalController.addSubscriber(SignalType.EnvelopeSent, chat)
signalController.addSubscriber(SignalType.NodeLogin, login)
signalController.addSubscriber(SignalType.NodeLogin, onboarding)
signalController.addSubscriber(SignalType.NodeStopped, login)

View File

@ -1,6 +1,6 @@
import NimQml, tables, json, chronicles, strutils, json_serialization
import ../status/libstatus/types as status_types
import types, messages, discovery, whisperFilter
import types, messages, discovery, whisperFilter, envelopes
logScope:
topics = "signals"
@ -57,6 +57,8 @@ QtObject:
case signalType:
of SignalType.Message:
signal = messages.fromEvent(jsonSignal)
of SignalType.EnvelopeSent:
signal = envelopes.fromEvent(jsonSignal)
of SignalType.WhisperFilterAdded:
signal = whisperFilter.fromEvent(jsonSignal)
of SignalType.Wallet:

10
src/signals/envelopes.nim Normal file
View File

@ -0,0 +1,10 @@
import json
import types
proc fromEvent*(jsonSignal: JsonNode): Signal =
var signal:EnvelopeSentSignal = EnvelopeSentSignal()
if jsonSignal["event"].kind != JNull and jsonSignal["event"].hasKey("ids") and jsonSignal["event"]["ids"].kind != JNull:
for messageId in jsonSignal["event"]["ids"]:
signal.messageIds.add(messageId.getStr)
result = signal

View File

@ -140,7 +140,8 @@ proc toMessage*(jsonMsg: JsonNode): Message =
text: jsonMsg{"text"}.getStr,
timestamp: $jsonMsg{"timestamp"}.getInt,
whisperTimestamp: $jsonMsg{"whisperTimestamp"}.getInt,
isCurrentUser: $jsonMsg{"outgoingStatus"}.getStr == "sending",
outgoingStatus: $jsonMsg{"outgoingStatus"}.getStr,
isCurrentUser: $jsonMsg{"outgoingStatus"}.getStr == "sending" or $jsonMsg{"outgoingStatus"}.getStr == "sent",
stickerHash: "",
parsedText: @[]
)

View File

@ -17,6 +17,9 @@ type NodeSignal* = ref object of Signal
type WalletSignal* = ref object of Signal
content*: string
type EnvelopeSentSignal* = ref object of Signal
messageIds*: seq[string]
# Override this method
method onSignal*(self: SignalSubscriber, data: Signal) {.base.} =
error "onSignal must be overriden in controller. Signal is unhandled"

View File

@ -5,6 +5,7 @@ import libstatus/types
import profile/profile
import chat/[chat, message]
import ../signals/messages
import ../signals/types as signal_types
import ens
import eth/common/eth_types
@ -35,6 +36,10 @@ type
channels*: Table[string, Chat]
msgCursor*: Table[string, string]
recentStickers*: seq[Sticker]
MessageArgs* = ref object of Args
id*: string
channel*: string
include chat/utils
@ -148,10 +153,11 @@ proc clearHistory*(self: ChatModel, chatId: string) =
proc setActiveChannel*(self: ChatModel, chatId: string) =
self.events.emit("activeChannelChanged", ChatIdArg(chatId: chatId))
proc sendMessage*(self: ChatModel, chatId: string, msg: string): string =
var sentMessage = status_chat.sendChatMessage(chatId, msg)
self.emitUpdate(sentMessage)
sentMessage
proc sendMessage*(self: ChatModel, chatId: string, msg: string) =
var response = status_chat.sendChatMessage(chatId, msg)
var (chats, messages) = self.processChatUpdate(parseJson(response))
self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[]))
self.events.emit("sendingMessage", MessageArgs(id: messages[0].id, channel: messages[0].chatId))
proc addStickerToRecent*(self: ChatModel, sticker: Sticker, save: bool = false) =
self.recentStickers.insert(sticker, 0)
@ -164,7 +170,9 @@ proc addStickerToRecent*(self: ChatModel, sticker: Sticker, save: bool = false)
proc sendSticker*(self: ChatModel, chatId: string, sticker: Sticker) =
var response = status_chat.sendStickerMessage(chatId, sticker)
self.addStickerToRecent(sticker, save = true)
self.emitUpdate(response)
var (chats, messages) = self.processChatUpdate(parseJson(response))
self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[]))
self.events.emit("sendingMessage", MessageArgs(id: messages[0].id, channel: messages[0].chatId))
proc chatMessages*(self: ChatModel, chatId: string, initialLoad:bool = true) =
if not self.msgCursor.hasKey(chatId):

View File

@ -41,6 +41,7 @@ type Message* = object
whisperTimestamp*: string
isCurrentUser*: bool
stickerHash*: string
outgoingStatus*: string
proc `$`*(self: Message): string =
result = fmt"Message(id:{self.id}, chatId:{self.chatId}, clock:{self.clock}, from:{self.fromAuthor}, type:{self.contentType})"

View File

@ -124,3 +124,7 @@ proc kickGroupMember*(chatId: string, pubKey: string): string =
proc makeAdmin*(chatId: string, pubKey: string): string =
callPrivateRPC("addAdminsToGroupChat".prefix, %* [nil, chatId, [pubKey]])
proc updateOutgoingMessageStatus*(messageId: string, status: string): string =
result = callPrivateRPC("updateMessageOutgoingStatus".prefix, %* [messageId, status])
# TODO: handle errors

48
src/status/messages.nim Normal file
View File

@ -0,0 +1,48 @@
import tables, sets, eventemitter
import libstatus/chat
type
MessageDetails = object
status: string
chatId: string
MessagesModel* = ref object
events*: EventEmitter
messages*: Table[string, MessageDetails]
confirmations*: HashSet[string]
MessageSentArgs* = ref object of Args
id*: string
chatId*: string
proc newMessagesModel*(events: EventEmitter): MessagesModel =
result = MessagesModel()
result.events = events
result.messages = initTable[string, MessageDetails]()
result.confirmations = initHashSet[string]()
proc delete*(self: MessagesModel) =
discard
# For each message sent we call trackMessage to register the message id,
# and wait until an EnvelopeSent signals is emitted for that message. However
# due to communication being async, it's possible that the signal arrives
# first, hence why we check if there's a confirmation (an envelope.sent)
# inside trackMessage to emit the "messageSent" event
proc trackMessage*(self: MessagesModel, id: string, chatId: string) =
self.messages[id] = MessageDetails(status: "sending", chatId: chatId)
if self.confirmations.contains(id):
self.confirmations.excl(id)
self.messages[id].status = "sent"
discard updateOutgoingMessageStatus(id, "sent")
self.events.emit("messageSent", MessageSentArgs(id: id, chatId: chatId))
proc updateStatus*(self: MessagesModel, messageIds: seq[string]) =
for messageId in messageIds:
if self.messages.hasKey(messageId):
self.messages[messageId].status = "sent"
discard updateOutgoingMessageStatus(messageId, "sent")
self.events.emit("messageSent", MessageSentArgs(id: messageId, chatId: self.messages[messageId].chatId))
else:
self.confirmations.incl(messageId)

View File

@ -9,12 +9,14 @@ import accounts as accounts
import wallet as wallet
import node as node
import mailservers as mailservers
import messages as messages
import contacts as contacts
import profile
type Status* = ref object
events*: EventEmitter
chat*: ChatModel
messages*: MessagesModel
mailservers*: MailserverModel
accounts*: AccountModel
wallet*: WalletModel
@ -31,6 +33,7 @@ proc newStatusInstance*(): Status =
result.wallet.initEvents()
result.node = node.newNodeModel()
result.mailservers = mailservers.newMailserverModel(result.events)
result.messages = messages.newMessagesModel(result.events)
result.profile = profile.newProfileModel()
result.contacts = contacts.newContactModel(result.events)

View File

@ -122,6 +122,7 @@ ScrollView {
timestamp: model.timestamp
sticker: model.sticker
contentType: model.contentType
outgoingStatus: model.outgoingStatus
authorCurrentMsg: msgDelegate.ListView.section
authorPrevMsg: msgDelegate.ListView.previousSection
profileClick: profilePopup.openPopup.bind(profilePopup)

View File

@ -18,6 +18,7 @@ Item {
property string sticker: "Qme8vJtyrEHxABcSVGPF95PtozDgUyfr1xGjePmFdZgk9v"
property int contentType: 1 // constants don't work in default props
property string chatId: "chatId"
property string outgoingStatus: ""
property string authorCurrentMsg: "authorCurrentMsg"
property string authorPrevMsg: "authorPrevMsg"
@ -328,6 +329,39 @@ Item {
// Probably only want to show this when clicking?
visible: true
}
StyledTextEdit {
id: sentMessage
color: Theme.darkGrey
text: qsTr("Sent")
anchors.top: contentType === Constants.stickerType ? stickerId.bottom : chatText.bottom
anchors.bottomMargin: Theme.padding
anchors.right: chatTime.left
anchors.rightMargin: Theme.padding
font.pixelSize: 10
readOnly: true
visible: isCurrentUser && outgoingStatus == "sent"
}
SVGImage {
id: sendingImg
visible: isCurrentUser && outgoingStatus == "sending"
anchors.top: chatText.top
anchors.right: chatText.left
anchors.rightMargin: 15
source: "../../../img/settings.svg"
width: 15
height: 15
fillMode: Image.Stretch
RotationAnimator {
target: sendingImg;
from: 0;
to: 360;
duration: 1200
running: true
loops: Animation.Infinite
}
}
}
}