Merge branch 'lockProfile' of dan/ui into master
the build was successful Details

This commit is contained in:
Sarah Jamie Lewis 2019-11-20 13:25:13 -08:00 committed by Gogs
commit 5df3fd4cf4
11 changed files with 233 additions and 101 deletions

View File

@ -109,13 +109,13 @@ func App(gcd *ui.GrandCentralDispatcher, subscribed chan bool, reloadingFirst bo
}
log.Infof("NewPeer for %v\n", onion)
gcd.UIManager.AddProfile(onion)
ui.AddProfile(gcd, onion)
the.CwtchApp.AddPeerPlugin(onion, plugins.CONNECTIONRETRY)
the.CwtchApp.AddPeerPlugin(onion, plugins.NETWORKCHECK)
incSubscribed := make(chan bool)
go PeerHandler(onion, &gcd.UIManager, incSubscribed)
go PeerHandler(onion, gcd.GetUiManager(peer.GetProfile().Onion), incSubscribed)
<-incSubscribed
if e.Data[event.Status] != "running" {

View File

@ -9,7 +9,7 @@ import (
"time"
)
func PeerHandler(onion string, uiManager *ui.Manager, subscribed chan bool) {
func PeerHandler(onion string, uiManager ui.Manager, subscribed chan bool) {
peer := the.CwtchApp.GetPeer(onion)
eventBus := the.CwtchApp.GetEventBus(onion)
q := event.NewQueue()

View File

@ -5,6 +5,7 @@ import (
"cwtch.im/cwtch/protocol/connections"
"cwtch.im/ui/go/constants"
"github.com/therecipe/qt/qml"
"sync"
"cwtch.im/ui/go/the"
"encoding/base32"
@ -17,15 +18,23 @@ import (
type GrandCentralDispatcher struct {
core.QObject
UIManager Manager
QMLEngine *qml.QQmlApplicationEngine
Translator *core.QTranslator
uIManagers map[string]Manager // profile-onion : Manager
profileLock sync.Mutex
conversationLock sync.Mutex
m_selectedProfile string
m_selectedConversation string
_ string `property:"os"`
_ string `property:"currentOpenConversation"`
_ float32 `property:"themeScale"`
_ string `property:"version"`
_ string `property:"buildDate"`
_ string `property:"selectedProfile,auto"`
_ string `property:"selectedConversation,auto"`
// profile management stuff
_ func() `signal:"Loaded"`
@ -90,6 +99,83 @@ type GrandCentralDispatcher struct {
_ func() `signal:"onActivate,auto"`
_ func(password string) `signal:"unlockProfiles,auto"`
_ func(handle string) `signal:"loadProfile,auto"`
_ func() `constructor:"init"`
}
func (this *GrandCentralDispatcher) init() {
this.uIManagers = make(map[string]Manager)
}
// GetUiManager gets (and creates if required) a ui Manager for the supplied profile id
func (this *GrandCentralDispatcher) GetUiManager(profile string) Manager {
this.profileLock.Lock()
defer this.profileLock.Unlock()
if manager, exists := this.uIManagers[profile]; exists {
return manager
} else {
this.uIManagers[profile] = NewManager(profile, this)
return this.uIManagers[profile]
}
}
func (this *GrandCentralDispatcher) selectedProfile() string {
this.profileLock.Lock()
defer this.profileLock.Unlock()
return this.m_selectedProfile
}
func (this *GrandCentralDispatcher) setSelectedProfile(onion string) {
this.profileLock.Lock()
defer this.profileLock.Unlock()
this.m_selectedProfile = onion
}
func (this *GrandCentralDispatcher) selectedProfileChanged(onion string) {
this.SelectedProfileChanged(onion)
}
// DoIfProfile performs a gcd action for a profile IF it is the currently selected profile in the UI
// otherwise it does nothing. it also locks profile switching for the duration of the action
func (this *GrandCentralDispatcher) DoIfProfile(profile string, fn func()) {
this.profileLock.Lock()
defer this.profileLock.Unlock()
if this.m_selectedProfile == profile {
fn()
}
}
func (this *GrandCentralDispatcher) selectedConversation() string {
this.conversationLock.Lock()
defer this.conversationLock.Unlock()
return this.m_selectedConversation
}
func (this *GrandCentralDispatcher) setSelectedConversation(handle string) {
this.conversationLock.Lock()
defer this.conversationLock.Unlock()
this.m_selectedConversation = handle
}
func (this *GrandCentralDispatcher) selectedConversationChanged(handle string) {
this.SelectedConversationChanged(handle)
}
// DoIfConversation performs a gcd action for a conversation IF it is the currently selected conversation in the UI
// otherwise it does nothing. it also locks conversation switching for the duration of the action
func (this *GrandCentralDispatcher) DoIfConversation(conversation string, fn func()) {
this.conversationLock.Lock()
defer this.conversationLock.Unlock()
if this.m_selectedConversation == conversation {
fn()
}
}
func (this *GrandCentralDispatcher) sendMessage(message string, mID string) {
@ -98,14 +184,14 @@ func (this *GrandCentralDispatcher) sendMessage(message string, mID string) {
return
}
if this.CurrentOpenConversation() == "" {
if this.SelectedConversation() == "" {
this.InvokePopup("ui error")
return
}
if isGroup(this.CurrentOpenConversation()) {
if !the.Peer.GetGroup(this.CurrentOpenConversation()).Accepted {
err := the.Peer.AcceptInvite(this.CurrentOpenConversation())
if isGroup(this.SelectedConversation()) {
if !the.Peer.GetGroup(this.SelectedConversation()).Accepted {
err := the.Peer.AcceptInvite(this.SelectedConversation())
if err != nil {
log.Errorf("tried to mark a nonexistent group as existed. bad!")
return
@ -113,19 +199,19 @@ func (this *GrandCentralDispatcher) sendMessage(message string, mID string) {
}
var err error
mID, err = the.Peer.SendMessageToGroupTracked(this.CurrentOpenConversation(), message)
mID, err = the.Peer.SendMessageToGroupTracked(this.SelectedConversation(), message)
this.UIManager.AddMessage(this.CurrentOpenConversation(), "me", message, true, mID, time.Now(), false)
this.GetUiManager(this.selectedProfile()).AddMessage(this.SelectedConversation(), "me", message, true, mID, time.Now(), false)
if err != nil {
this.InvokePopup("failed to send message " + err.Error())
return
}
} else {
to := this.CurrentOpenConversation()
to := this.SelectedConversation()
mID = the.Peer.SendMessageToPeer(to, message)
this.UIManager.AddMessage(to, "me", message, true, mID, time.Now(), false)
this.GetUiManager(this.selectedProfile()).AddMessage(to, "me", message, true, mID, time.Now(), false)
}
}
@ -139,7 +225,7 @@ func (this *GrandCentralDispatcher) loadMessagesPaneHelper(handle string) {
return
}
this.ClearMessages()
this.SetCurrentOpenConversation(handle)
this.SetSelectedConversation(handle)
if isGroup(handle) { // LOAD GROUP
group := the.Peer.GetGroup(handle)
@ -245,17 +331,18 @@ func (this *GrandCentralDispatcher) saveSettings(zoom, locale string) {
}
func (this *GrandCentralDispatcher) requestPeerSettings() {
contact := the.Peer.GetContact(this.CurrentOpenConversation())
contact := the.Peer.GetContact(this.SelectedConversation())
if contact == nil {
log.Errorf("error: requested settings for unknown contact %v?", this.CurrentOpenConversation())
this.SupplyPeerSettings(this.CurrentOpenConversation(), this.CurrentOpenConversation(), false)
log.Errorf("error: requested settings for unknown contact %v?", this.SelectedConversation())
this.SupplyPeerSettings(this.SelectedConversation(), this.SelectedConversation(), false)
return
}
name, exists := contact.GetAttribute(constants.Nick)
if !exists {
log.Errorf("error: couldn't find contact %v", this.CurrentOpenConversation())
this.SupplyPeerSettings(this.CurrentOpenConversation(), this.CurrentOpenConversation(), contact.Blocked)
log.Errorf("error: couldn't find contact %v", this.SelectedConversation())
this.SupplyPeerSettings(this.SelectedConversation(), this.SelectedConversation(), contact.Blocked)
this.SupplyPeerSettings(this.SelectedConversation(), this.SelectedConversation(), contact.Blocked)
return
}
@ -379,7 +466,7 @@ func (this *GrandCentralDispatcher) importString(str string) {
the.Peer.PeerWithOnion(onion)
}
this.UIManager.AddContact(onion)
this.GetUiManager(this.selectedProfile()).AddContact(onion)
}
func (this *GrandCentralDispatcher) popup(str string) {
@ -400,7 +487,7 @@ func (this *GrandCentralDispatcher) createGroup(server, groupName string) {
return
}
this.UIManager.AddContact(groupID)
this.GetUiManager(this.selectedProfile()).AddContact(groupID)
the.Peer.SetGroupAttribute(groupID, constants.Nick, groupName)
@ -449,7 +536,7 @@ func (this *GrandCentralDispatcher) acceptGroup(groupID string) {
func (this *GrandCentralDispatcher) setAttribute(onion, key, value string) {
the.Peer.SetContactAttribute(onion, key, value)
this.UIManager.UpdateContactAttribute(onion, key, value)
this.GetUiManager(this.selectedProfile()).UpdateContactAttribute(onion, key, value)
}
func (this *GrandCentralDispatcher) blockUnknownPeers() {
@ -505,13 +592,13 @@ func (this *GrandCentralDispatcher) loadProfile(onion string) {
contacts := the.Peer.GetContacts()
for i := range contacts {
this.UIManager.AddContact(contacts[i])
this.GetUiManager(this.selectedProfile()).AddContact(contacts[i])
}
groups := the.Peer.GetGroups()
for i := range groups {
// Only join servers for active and explicitly accepted groups.
this.UIManager.AddContact(groups[i])
this.GetUiManager(this.selectedProfile()).AddContact(groups[i])
}
// load ui preferences

View File

@ -107,15 +107,8 @@ func countUnread(messages []model.Message, lastRead time.Time) int {
return count
}
type Manager struct {
gcd *GrandCentralDispatcher
}
func NewManager(gcd *GrandCentralDispatcher) Manager {
return Manager{gcd}
}
func (this *Manager) AddProfile(handle string) {
// 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
@ -130,12 +123,41 @@ func (this *Manager) AddProfile(handle string) {
peer.SetAttribute(constants.Picture, pic)
}
log.Infof("AddProfile %v %v %v\n", handle, nick, pic)
this.gcd.AddProfile(handle, nick, pic)
gcd.AddProfile(handle, nick, pic)
}
}
func (this *Manager) Acknowledge(mID string) {
this.gcd.Acknowledged(mID)
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)
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)
})
}
func getLastMessageTime(tl *model.Timeline) int {
@ -146,77 +168,99 @@ func getLastMessageTime(tl *model.Timeline) int {
return int(tl.Messages[len(tl.Messages)-1].Timestamp.Unix())
}
func (this *Manager) AddContact(Handle string) {
if isGroup(Handle) {
group := the.Peer.GetGroup(Handle)
if group != nil {
lastRead := initLastReadTime(group.GroupID)
unread := countUnread(group.Timeline.GetMessages(), lastRead)
// 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 := group.GetAttribute(constants.Nick)
nick, exists := contact.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))
this.gcd.AddContact(Handle, nick, picture, "", unread, int(connections.ConnectionStateToType[contact.State]), contact.Blocked, false, getLastMessageTime(&contact.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))
}
})
}
func (this *Manager) AddSendMessageError(peer string, signature string, err string) {
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)
// 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)
})
})
}
func (this *Manager) AddMessage(handle string, from string, message string, fromMe bool, messageID string, timestamp time.Time, Acknowledged bool) {
nick := getOrDefault(handle, constants.Nick, handle)
image := getProfilePic(handle)
// 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() {
// If we have this group loaded already
if this.gcd.CurrentOpenConversation() == handle {
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 {
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 {
this.gcd.Acknowledged(messageID)
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)
})
this.gcd.IncContactUnreadCount(handle)
})
}
func (this *Manager) UpdateContactDisplayName(handle string, name string) {
this.gcd.UpdateContactDisplayName(handle, name)
// 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)
})
}
func (this *Manager) UpdateContactStatus(handle string, status int, loading bool) {
this.gcd.UpdateContactStatus(handle, status, loading)
// 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)
})
}
func (this *Manager) UpdateContactAttribute(handle, key, value string) {
this.gcd.UpdateContactAttribute(handle, key, value)
// 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)
})
}

View File

@ -137,7 +137,6 @@ func mainUi(flagLocal bool, flagClientUI bool) {
gcd.SetVersion("development")
gcd.SetBuildDate("now")
}
gcd.UIManager = ui.NewManager(gcd)
//TODO: put theme stuff somewhere better
gcd.SetThemeScale(1.0)

View File

@ -93,7 +93,7 @@ ColumnLayout {
}
onUpdateContactStatus: function(_handle, _status, _loading) {
if (gcd.currentOpenConversation == _handle) {
if (gcd.selectedConversation == _handle) {
if (_loading == true) {
newposttitle.enabled = false
newpostbody.enabled = false

View File

@ -111,7 +111,7 @@ ColumnLayout {
}
onUpdateContactStatus: function(_handle, _status, _loading) {
if (gcd.currentOpenConversation == _handle) {
if (gcd.selectedConversation == _handle) {
// Group is Synced OR p2p is Authenticated
if ( (_handle.length == 32 && _status == 4) || (_handle.length == 56 && _status == 3) ) {
txtMessage.enabled = true

View File

@ -97,7 +97,7 @@ ColumnLayout {
}
onUpdateContactStatus: function(_handle, _status, _loading) {
if (gcd.currentOpenConversation == _handle) {
if (gcd.selectedConversation == _handle) {
if (_loading == true) {
newposttitle.enabled = false
btnSend.enabled = false

View File

@ -19,14 +19,14 @@ ColumnLayout {
StackToolbar {
id: toolbar
membership.visible: gcd.currentOpenConversation.length == 32
membership.visible: gcd.selectedConversation.length == 32
membership.onClicked: overlayStack.overlay = overlayStack.membershipOverlay
aux.onClicked: {
if (gcd.currentOpenConversation.length == 32) {
if (gcd.selectedConversation.length == 32) {
theStack.pane = theStack.groupProfilePane
gcd.requestGroupSettings(gcd.currentOpenConversation)
gcd.requestGroupSettings(gcd.selectedConversation)
} else {
theStack.pane = theStack.userProfilePane
gcd.requestPeerSettings()
@ -36,7 +36,7 @@ ColumnLayout {
}
RowLayout {
visible:!overlay.accepted && (gcd.currentOpenConversation.length == 32)
visible:!overlay.accepted && (gcd.selectedConversation.length == 32)
Text {
@ -49,8 +49,8 @@ ColumnLayout {
text: qsTr("accept-group-btn")
icon: "regular/heart"
onClicked: {
gcd.acceptGroup(gcd.currentOpenConversation)
gcd.requestGroupSettings(gcd.currentOpenConversation)
gcd.acceptGroup(gcd.selectedConversation)
gcd.requestGroupSettings(gcd.selectedConversation)
}
}
@ -59,7 +59,7 @@ ColumnLayout {
text: qsTr("reject-group-btn")
icon: "regular/trash-alt"
onClicked: {
gcd.leaveGroup(gcd.currentOpenConversation)
gcd.leaveGroup(gcd.selectedConversation)
theStack.pane = theStack.emptyPane
}
}

View File

@ -150,6 +150,7 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY
} else if (type == "profile") {
gcd.broadcast("ResetMessagePane")
gcd.broadcast("ResetProfile")
gcd.selectedProfile = handle
gcd.loadProfile(handle)
parentStack.pane = parentStack.profilePane
}
@ -191,7 +192,7 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY
}
onIncContactUnreadCount: function(handle) {
if (handle == _handle && gcd.currentOpenConversation != handle) {
if (handle == _handle && gcd.selectedConversation != handle) {
badge++
}
}

View File

@ -28,6 +28,7 @@ ColumnLayout {
anchors.top: parent.top
anchors.topMargin: 2
onClicked: function() {
gcd.selectedProfile = "none"
parentStack.pane = parentStack.managementPane
theStack.pane = theStack.emptyPane
}