Add Status Indicators for Group Messages

This commit is contained in:
Sarah Jamie Lewis 2019-02-20 12:07:12 -08:00
parent 1483492f61
commit 6e50004402
9 changed files with 59 additions and 34 deletions

View File

@ -29,8 +29,9 @@ func CwtchListener(callback func(message *gobjects.Message), groupID string, cha
m.Message, m.Message,
"", "",
m.PeerID == the.Peer.GetProfile().Onion, m.PeerID == the.Peer.GetProfile().Onion,
0, "0",
m.Timestamp, m.Timestamp,
false,
}) })
} }
} }

View File

@ -41,12 +41,14 @@ func IncomingListener(callback func(*gobjects.Message)) {
case event.NewMessageFromGroup://event.TimestampReceived, event.TimestampSent, event.Data, event.GroupID, event.RemotePeer case event.NewMessageFromGroup://event.TimestampReceived, event.TimestampSent, event.Data, event.GroupID, event.RemotePeer
ts, _ := time.Parse(time.RFC3339Nano, e.Data[event.TimestampSent]) ts, _ := time.Parse(time.RFC3339Nano, e.Data[event.TimestampSent])
callback(&gobjects.Message{ callback(&gobjects.Message{
MessageID: e.Data[event.Signature],
Handle: e.Data[event.GroupID], Handle: e.Data[event.GroupID],
From: e.Data[event.RemotePeer], From: e.Data[event.RemotePeer],
Message: e.Data[event.Data], Message: e.Data[event.Data],
Image: cwutil.RandomProfileImage(e.Data[event.RemotePeer]), Image: cwutil.RandomProfileImage(e.Data[event.RemotePeer]),
FromMe: e.Data[event.RemotePeer] == the.Peer.GetProfile().Onion, FromMe: e.Data[event.RemotePeer] == the.Peer.GetProfile().Onion,
Timestamp: ts, Timestamp: ts,
Acknowledged: true,
}) })
case event.NewGroupInvite: case event.NewGroupInvite:
log.Debugf("got a group invite!") log.Debugf("got a group invite!")

View File

@ -3,5 +3,5 @@ package gobjects
// a Letter is a very simple message object passed to us from the UI // a Letter is a very simple message object passed to us from the UI
type Letter struct { type Letter struct {
To, Message string To, Message string
MID uint MID string
} }

View File

@ -9,6 +9,7 @@ type Message struct {
Message string Message string
Image string Image string
FromMe bool FromMe bool
MessageID int MessageID string
Timestamp time.Time Timestamp time.Time
Acknowledged bool
} }

View File

@ -5,13 +5,11 @@ import (
"cwtch.im/ui/go/constants" "cwtch.im/ui/go/constants"
"cwtch.im/ui/go/cwutil" "cwtch.im/ui/go/cwutil"
"cwtch.im/cwtch/model"
"cwtch.im/ui/go/characters"
"cwtch.im/ui/go/gobjects" "cwtch.im/ui/go/gobjects"
"cwtch.im/ui/go/the" "cwtch.im/ui/go/the"
"encoding/base32" "encoding/base32"
"github.com/therecipe/qt/core"
"git.openprivacy.ca/openprivacy/libricochet-go/log" "git.openprivacy.ca/openprivacy/libricochet-go/log"
"github.com/therecipe/qt/core"
"strings" "strings"
"time" "time"
) )
@ -31,10 +29,10 @@ type GrandCentralDispatcher struct {
_ func(handle, key, value string) `signal:"UpdateContactAttribute"` _ func(handle, key, value string) `signal:"UpdateContactAttribute"`
// messages pane stuff // messages pane stuff
_ func(handle, from, displayName, message, image string, mID uint, fromMe bool, ts string) `signal:"AppendMessage"` _ func(handle, from, displayName, message, image string, mID string, fromMe bool, ts string, ackd bool) `signal:"AppendMessage"`
_ func() `signal:"ClearMessages"` _ func() `signal:"ClearMessages"`
_ func() `signal:"ResetMessagePane"` _ func() `signal:"ResetMessagePane"`
_ func(mID uint) `signal:"Acknowledged"` _ func(mID string) `signal:"Acknowledged"`
_ func(title string) `signal:"SetToolbarTitle"` _ func(title string) `signal:"SetToolbarTitle"`
// profile-area stuff // profile-area stuff
@ -47,7 +45,7 @@ type GrandCentralDispatcher struct {
_ func(onion, nick string) `signal:"SupplyPeerSettings"` _ func(onion, nick string) `signal:"SupplyPeerSettings"`
// signals emitted from the ui (written in go, below) // signals emitted from the ui (written in go, below)
_ func(message string, mid uint) `signal:"sendMessage,auto"` _ func(message string, mid string) `signal:"sendMessage,auto"`
_ func(onion string) `signal:"loadMessagesPane,auto"` _ func(onion string) `signal:"loadMessagesPane,auto"`
_ func(signal string) `signal:"broadcast,auto"` // convenience relay signal _ func(signal string) `signal:"broadcast,auto"` // convenience relay signal
_ func(str string) `signal:"importString,auto"` _ func(str string) `signal:"importString,auto"`
@ -64,7 +62,7 @@ type GrandCentralDispatcher struct {
_ func(onion, key, nick string) `signal:"setAttribute,auto"` _ func(onion, key, nick string) `signal:"setAttribute,auto"`
} }
func (this *GrandCentralDispatcher) sendMessage(message string, mID uint) { func (this *GrandCentralDispatcher) sendMessage(message string, mID string) {
if len(message) > 65530 { if len(message) > 65530 {
this.InvokePopup("message is too long") this.InvokePopup("message is too long")
return return
@ -87,20 +85,25 @@ func (this *GrandCentralDispatcher) sendMessage(message string, mID uint) {
this.UIState.UpdateContact(c.Handle) this.UIState.UpdateContact(c.Handle)
} }
the.Peer.SendMessageToGroup(this.CurrentOpenConversation(), message) var err error
return mID,err = the.Peer.SendMessageToGroupTracked(this.CurrentOpenConversation(), message)
} if err != nil {
this.InvokePopup("failed to send message " +err.Error())
return
}
} else {
// TODO: require explicit invite accept/reject instead of implicitly trusting on send // TODO: require explicit invite accept/reject instead of implicitly trusting on send
if !this.UIState.GetContact(this.CurrentOpenConversation()).Trusted { if !this.UIState.GetContact(this.CurrentOpenConversation()).Trusted {
this.UIState.GetContact(this.CurrentOpenConversation()).Trusted = true this.UIState.GetContact(this.CurrentOpenConversation()).Trusted = true
this.UIState.UpdateContact(this.CurrentOpenConversation()) this.UIState.UpdateContact(this.CurrentOpenConversation())
} }
select { // 1 weird trick to do a non-blocking send. this means the user can only send a limited number of messages select { // 1 weird trick to do a non-blocking send. this means the user can only send a limited number of messages
// before the channel buffer fills. TODO: stop the user from sending if the buffer is full // before the channel buffer fills. TODO: stop the user from sending if the buffer is full
case this.OutgoingMessages <- gobjects.Letter{this.CurrentOpenConversation(), message, mID}: case this.OutgoingMessages <- gobjects.Letter{this.CurrentOpenConversation(), message, mID}:
default: default:
}
} }
this.UIState.AddMessage(&gobjects.Message{ this.UIState.AddMessage(&gobjects.Message{
@ -110,8 +113,9 @@ func (this *GrandCentralDispatcher) sendMessage(message string, mID uint) {
message, message,
"", "",
true, true,
int(mID), mID,
time.Now(), time.Now(),
false,
}) })
} }
@ -174,9 +178,10 @@ func (this *GrandCentralDispatcher) loadMessagesPaneHelper(handle string) {
name, name,
tl[i].Message, tl[i].Message,
cwutil.RandomProfileImage(tl[i].PeerID), cwutil.RandomProfileImage(tl[i].PeerID),
0, string(tl[i].Signature),
tl[i].PeerID == the.Peer.GetProfile().Onion, tl[i].PeerID == the.Peer.GetProfile().Onion,
tl[i].Timestamp.Format(constants.TIME_FORMAT), tl[i].Timestamp.Format(constants.TIME_FORMAT),
tl[i].Received.Equal(time.Unix(0,0)) == false, // If the received timestamp is epoch, we have not yet received this message through an active server
) )
} }
return return
@ -204,12 +209,13 @@ func (this *GrandCentralDispatcher) loadMessagesPaneHelper(handle string) {
messages[i].DisplayName, messages[i].DisplayName,
messages[i].Message, messages[i].Message,
cwutil.RandomProfileImage(handle), cwutil.RandomProfileImage(handle),
uint(messages[i].MessageID), messages[i].MessageID,
messages[i].FromMe, messages[i].FromMe,
messages[i].Timestamp.Format(constants.TIME_FORMAT), messages[i].Timestamp.Format(constants.TIME_FORMAT),
true,
) )
for _,id := range the.AcknowledgementIDs[messages[i].Handle] { for _,id := range the.AcknowledgementIDs[messages[i].Handle] {
if int(id.ID) == messages[i].MessageID && id.Ack{ if id.ID == messages[i].MessageID && id.Ack{
this.Acknowledged(id.ID) this.Acknowledged(id.ID)
} }
} }
@ -412,8 +418,6 @@ func (this *GrandCentralDispatcher) createGroup(server, groupName string) {
})) }))
the.Peer.JoinServer(server) the.Peer.JoinServer(server)
group.NewMessage = make(chan model.Message)
go characters.CwtchListener(this.UIState.AddMessage, group.GroupID, group.NewMessage)
} }
func (this *GrandCentralDispatcher) inviteToGroup(onion, groupID string) { func (this *GrandCentralDispatcher) inviteToGroup(onion, groupID string) {

View File

@ -94,6 +94,11 @@ func (this *InterfaceState) AddMessage(m *gobjects.Message) {
this.messages[m.Handle] = make([]*gobjects.Message, 0) this.messages[m.Handle] = make([]*gobjects.Message, 0)
} }
// Ack message sent to group
this.parentGcd.Acknowledged(m.MessageID)
// Ack personal messages
// TODO: unify this with the above signature based approach
if m.Message == "ack" { if m.Message == "ack" {
// If an ack, swallow the message and ack from the list. // If an ack, swallow the message and ack from the list.
acklist := the.AcknowledgementIDs[m.From] acklist := the.AcknowledgementIDs[m.From]
@ -104,8 +109,14 @@ func (this *InterfaceState) AddMessage(m *gobjects.Message) {
} else { } else {
this.messages[m.Handle] = append(this.messages[m.Handle], m) this.messages[m.Handle] = append(this.messages[m.Handle], m)
// If we have this group loaded already
if this.parentGcd.CurrentOpenConversation() == m.Handle { if this.parentGcd.CurrentOpenConversation() == m.Handle {
this.parentGcd.AppendMessage(m.Handle, m.From, m.DisplayName, m.Message, m.Image, uint(m.MessageID), m.FromMe, m.Timestamp.Format(constants.TIME_FORMAT)) // If the message is not from the user then add it, otherwise, just acknowledge.
if !m.FromMe || !m.Acknowledged {
this.parentGcd.AppendMessage(m.Handle, m.From, m.DisplayName, m.Message, m.Image, m.MessageID, m.FromMe, m.Timestamp.Format(constants.TIME_FORMAT), m.Acknowledged)
} else {
this.parentGcd.Acknowledged(m.MessageID)
}
} else { } else {
c := this.GetContact(m.Handle) c := this.GetContact(m.Handle)
if c != nil { if c != nil {

View File

@ -10,7 +10,7 @@ var Peer libPeer.CwtchPeer
var CwtchDir string var CwtchDir string
type AckId struct { type AckId struct {
ID uint ID string
Ack bool Ack bool
} }

View File

@ -32,7 +32,7 @@ ColumnLayout {
txtMessage.text = "" txtMessage.text = ""
} }
onAppendMessage: function(handle, from, displayName, message, image, mid, fromMe, ts) { onAppendMessage: function(handle, from, displayName, message, image, mid, fromMe, ts, ackd) {
var msg var msg
try { try {
msg = JSON.parse(message) msg = JSON.parse(message)
@ -41,6 +41,7 @@ ColumnLayout {
} }
if (msg.o != 1) return if (msg.o != 1) return
messagesModel.append({ messagesModel.append({
"_handle": handle, "_handle": handle,
"_from": from, "_from": from,
@ -50,8 +51,11 @@ ColumnLayout {
"_mid": mid, "_mid": mid,
"_fromMe": fromMe, "_fromMe": fromMe,
"_ts": ts, "_ts": ts,
"_ackd": ackd,
}) })
// If the window is out of focus, alert the user (makes taskbar light up) // If the window is out of focus, alert the user (makes taskbar light up)
windowItem.alert(0) windowItem.alert(0)
@ -88,6 +92,7 @@ ColumnLayout {
messageID: _mid messageID: _mid
fromMe: _fromMe fromMe: _fromMe
timestamp: _ts timestamp: _ts
ackd: _ackd
} }
} }
} }

View File

@ -17,8 +17,9 @@ RowLayout {
property string from property string from
property string handle property string handle
property string displayName property string displayName
property int messageID property string messageID
property bool fromMe property bool fromMe
property bool ackd
property alias timestamp: ts.text property alias timestamp: ts.text
property alias image: imgProfile.source property alias image: imgProfile.source
property alias status: imgProfile.status property alias status: imgProfile.status
@ -28,7 +29,7 @@ RowLayout {
onAcknowledged: function(mid) { onAcknowledged: function(mid) {
if (mid == messageID) { if (mid == messageID) {
ack.source = "qrc:/qml/images/fontawesome/regular/check-circle.svg" root.ackd = true
} }
} }
@ -129,7 +130,7 @@ RowLayout {
Image { // ACKNOWLEDGEMENT ICON Image { // ACKNOWLEDGEMENT ICON
id: ack id: ack
anchors.right: parent.right anchors.right: parent.right
source: from == "me" ? "qrc:/qml/images/fontawesome/regular/hourglass.svg" : "qrc:/qml/images/fontawesome/regular/check-circle.svg" source: root.ackd ? "qrc:/qml/images/fontawesome/regular/check-circle.svg" : "qrc:/qml/images/fontawesome/regular/hourglass.svg"
height: 10 height: 10
sourceSize.height: 10 sourceSize.height: 10
visible: fromMe visible: fromMe