This repository has been archived on 2021-06-24. You can view files and clone it, but cannot push or open issues or pull requests.
ui/go/ui/manager.go

276 lines
8.5 KiB
Go
Raw Normal View History

package ui
2018-11-22 00:01:17 +00:00
import (
"cwtch.im/cwtch/app"
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/protocol/connections"
2018-11-28 22:14:02 +00:00
"cwtch.im/ui/go/constants"
"cwtch.im/ui/go/the"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
2019-02-04 23:00:12 +00:00
"runtime/debug"
"time"
2018-11-22 00:01:17 +00:00
)
func isGroup(id string) bool {
return len(id) == 32
}
func isPeer(id string) bool {
return len(id) == 56
}
func getOrDefault(id, key, defaultVal string) string {
var val string
var ok bool
if isGroup(id) {
val, ok = the.Peer.GetGroupAttribute(id, key)
} else {
val, ok = the.Peer.GetContactAttribute(id, key)
}
if ok {
return val
} else {
return defaultVal
}
}
func getWithSetDefault(id string, key, defaultVal string) string {
var val string
var ok bool
if isGroup(id) {
val, ok = the.Peer.GetGroupAttribute(id, key)
} else {
val, ok = the.Peer.GetContactAttribute(id, key)
}
if !ok {
val = defaultVal
if isGroup(id) {
the.Peer.SetGroupAttribute(id, key, defaultVal)
} else {
the.Peer.SetContactAttribute(id, key, defaultVal)
}
}
return val
}
// initLastReadTime checks and gets the Attributable's LastRead time or sets it to now
func initLastReadTime(id string) time.Time {
nowStr, _ := time.Now().MarshalText()
lastReadStr := getWithSetDefault(id, constants.LastRead, string(nowStr))
var lastRead time.Time
lastRead.UnmarshalText([]byte(lastReadStr))
return lastRead
}
func initProfilePicture(id string) string {
if isGroup(id) {
return getWithSetDefault(id, constants.Picture, RandomGroupImage(id))
} else {
return getWithSetDefault(id, constants.Picture, RandomProfileImage(id))
}
}
// getProfilePic supplies a profile pic to use. In groups we may not have a contact so it will generate one
func getProfilePic(id string) string {
if isGroup(id) {
if pic, exists := the.Peer.GetGroupAttribute(id, constants.Picture); !exists {
return RandomGroupImage(id)
} else {
return pic
}
} else {
if pic, exists := the.Peer.GetContactAttribute(id, constants.Picture); !exists {
return RandomProfileImage(id)
} else {
return pic
}
}
}
func updateLastReadTime(id string) {
lastRead, _ := time.Now().MarshalText()
if isGroup(id) {
the.Peer.SetGroupAttribute(id, constants.LastRead, string(lastRead))
} else {
the.Peer.SetContactAttribute(id, constants.LastRead, string(lastRead))
}
}
func countUnread(messages []model.Message, lastRead time.Time) int {
count := 0
for i := len(messages) - 1; i >= 0; i-- {
if messages[i].Timestamp.After(lastRead) || messages[i].Timestamp.Equal(lastRead) {
count++
} else {
break
}
}
return count
}
// AddProfile adds a new profile to the UI
func AddProfile(gcd *GrandCentralDispatcher, handle string) {
peer := the.CwtchApp.GetPeer(handle)
if peer != nil {
nick := peer.GetProfile().Name
if nick == "" {
nick = handle
peer.SetAttribute(constants.Nick, nick)
}
pic, ok := peer.GetAttribute(constants.Picture)
if !ok {
pic = RandomProfileImage(handle)
peer.SetAttribute(constants.Picture, pic)
}
tag, _ := peer.GetAttribute(app.AttributeTag)
log.Infof("AddProfile %v %v %v %v\n", handle, nick, pic, tag)
gcd.AddProfile(handle, nick, pic, tag)
}
}
type manager struct {
gcd *GrandCentralDispatcher
profile string
}
// Manager is a middleware helper for entities like peer event listeners wishing to trigger ui changes (via the gcd)
// each manager is for one profile/peer
// manager takes minimal arguments and builds the full struct of data (usually pulled from a cwtch peer) required to call the GCD to perform the ui action
// manager also performs call filtering based on UI state: users of manager can safely always call it on events and not have to worry about weather the relevant ui is active
// ie: you can always safely call AddMessage even if in the ui a different profile is selected. manager will check with gcd, and if the correct conditions are not met, it will not call on gcd to update the ui incorrectly
type Manager interface {
Acknowledge(mID string)
AddContact(Handle string)
AddSendMessageError(peer string, signature string, err string)
AddMessage(handle string, from string, message string, fromMe bool, messageID string, timestamp time.Time, Acknowledged bool)
ReloadProfiles()
UpdateContactDisplayName(handle string, name string)
UpdateContactStatus(handle string, status int, loading bool)
UpdateContactAttribute(handle, key, value string)
}
// NewManager returns a new Manager interface for a profile to the gcd
func NewManager(profile string, gcd *GrandCentralDispatcher) Manager {
return &manager{gcd: gcd, profile: profile}
}
// Acknowledge acknowledges the given message id in the UI
func (this *manager) Acknowledge(mID string) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.Acknowledged(mID)
})
2019-07-31 18:59:43 +00:00
}
func getLastMessageTime(tl *model.Timeline) int {
if len(tl.Messages) == 0 {
return 0
}
return int(tl.Messages[len(tl.Messages)-1].Timestamp.Unix())
}
// AddContact adds a new contact to the ui for this manager's profile
func (this *manager) AddContact(Handle string) {
this.gcd.DoIfProfile(this.profile, func() {
if isGroup(Handle) {
group := the.Peer.GetGroup(Handle)
if group != nil {
lastRead := initLastReadTime(group.GroupID)
unread := countUnread(group.Timeline.GetMessages(), lastRead)
picture := initProfilePicture(Handle)
nick, exists := group.GetAttribute(constants.Nick)
if !exists {
nick = Handle
}
this.gcd.AddContact(Handle, nick, picture, group.GroupServer, unread, int(connections.ConnectionStateToType[group.State]), false, false, getLastMessageTime(&group.Timeline))
}
return
} else if !isPeer(Handle) {
log.Errorf("sorry, unable to handle AddContact(%v)", Handle)
debug.PrintStack()
return
}
contact := the.Peer.GetContact(Handle)
if contact != nil {
lastRead := initLastReadTime(contact.Onion)
unread := countUnread(contact.Timeline.GetMessages(), lastRead)
picture := initProfilePicture(Handle)
nick, exists := contact.GetAttribute(constants.Nick)
if !exists {
nick = Handle
}
this.gcd.AddContact(Handle, nick, picture, "", unread, int(connections.ConnectionStateToType[contact.State]), contact.Blocked, false, getLastMessageTime(&contact.Timeline))
2019-02-05 21:09:38 +00:00
}
})
2018-11-22 00:01:17 +00:00
}
// AddSendMessageError adds an error not and icon to a message in a conversation in the ui for the message identified by the peer/sig combo
func (this *manager) AddSendMessageError(peer string, signature string, err string) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.DoIfConversation(peer, func() {
log.Debugf("Received Error Sending Message: %v", err)
// FIXME: Sometimes, for the first Peer message we send our error beats our message to the UI
time.Sleep(time.Second * 1)
this.gcd.GroupSendError(signature, err)
})
})
2019-02-20 21:45:42 +00:00
}
// AddMessage adds a message to the message pane for the supplied conversation if it is active
func (this *manager) AddMessage(handle string, from string, message string, fromMe bool, messageID string, timestamp time.Time, Acknowledged bool) {
this.gcd.DoIfProfile(this.profile, func() {
2018-11-22 00:01:17 +00:00
nick := getOrDefault(handle, constants.Nick, handle)
image := getProfilePic(handle)
// If we have this group loaded already
this.gcd.DoIfConversation(handle, func() {
updateLastReadTime(handle)
// If the message is not from the user then add it, otherwise, just acknowledge.
if !fromMe {
this.gcd.AppendMessage(handle, from, nick, message, image, messageID, fromMe, timestamp.Format(constants.TIME_FORMAT), false, false)
} else {
if !Acknowledged {
this.gcd.AppendMessage(handle, from, nick, message, image, messageID, fromMe, timestamp.Format(constants.TIME_FORMAT), false, false)
} else {
this.gcd.Acknowledged(messageID)
}
}
})
this.gcd.IncContactUnreadCount(handle)
})
2018-11-22 00:01:17 +00:00
}
func (this *manager) ReloadProfiles() {
this.gcd.reloadProfileList()
}
// UpdateContactDisplayName updates a contact's display name in the contact list and conversations
func (this *manager) UpdateContactDisplayName(handle string, name string) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.UpdateContactDisplayName(handle, name)
})
}
2019-10-21 19:19:18 +00:00
// UpdateContactStatus updates a contact's status in the ui
func (this *manager) UpdateContactStatus(handle string, status int, loading bool) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.UpdateContactStatus(handle, status, loading)
})
2018-11-28 20:34:49 +00:00
}
// UpdateContactAttribute update's a contacts attribute in the ui
func (this *manager) UpdateContactAttribute(handle, key, value string) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.UpdateContactAttribute(handle, key, value)
})
}