From dc3872fc8a7e00d8dbe777ad4d91066dd9606489 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Wed, 16 Dec 2020 22:39:40 -0800 Subject: [PATCH] fix contacts sort up on send msg; profile list shows unread; approved contacts sort on approved time --- go/constants/attributes.go | 2 ++ go/handlers/peerHandler.go | 3 +- go/ui/gcd.go | 40 ++++++++++++++----------- go/ui/manager.go | 58 +++++++++++++++++++++++++++++++----- qml/main.qml | 4 +++ qml/overlays/ChatOverlay.qml | 6 ---- qml/widgets/ContactList.qml | 16 ++++++++-- qml/widgets/ContactRow.qml | 4 +-- qml/widgets/ProfileList.qml | 4 ++- qml/widgets/ProfileRow.qml | 30 ++++++++++++++++++- 10 files changed, 128 insertions(+), 39 deletions(-) diff --git a/go/constants/attributes.go b/go/constants/attributes.go index 9af88ff3..06ec925b 100644 --- a/go/constants/attributes.go +++ b/go/constants/attributes.go @@ -6,6 +6,8 @@ const Name = "name" const LastRead = "last-read" const Picture = "picture" const ShowBlocked = "show-blocked" +const UnreadMsgCount = "unread-message-count" +const ApprovedTime = "approved-time" const ProfileTypeV1DefaultPassword = "v1-defaultPassword" const ProfileTypeV1Password = "v1-userPassword" diff --git a/go/handlers/peerHandler.go b/go/handlers/peerHandler.go index cd7337bb..262deee5 100644 --- a/go/handlers/peerHandler.go +++ b/go/handlers/peerHandler.go @@ -64,9 +64,8 @@ func PeerHandler(onion string, uiManager ui.Manager, subscribed chan bool) { case event.NewMessageFromGroup: //event.TimestampReceived, event.TimestampSent, event.Data, event.GroupID, event.RemotePeer ts, _ := time.Parse(time.RFC3339Nano, e.Data[event.TimestampSent]) uiManager.AddMessage(e.Data[event.GroupID], e.Data[event.RemotePeer], e.Data[event.Data], e.Data[event.RemotePeer] == peer.GetOnion(), hex.EncodeToString([]byte(e.Data[event.Signature])), ts, true) - case event.NewGroup: - group := peer.GetGroup( e.Data[event.GroupID]) + group := peer.GetGroup(e.Data[event.GroupID]) if group != nil { uiManager.AddContact(e.Data[event.GroupID]) } diff --git a/go/ui/gcd.go b/go/ui/gcd.go index 243fe80d..3d6ae68e 100644 --- a/go/ui/gcd.go +++ b/go/ui/gcd.go @@ -58,18 +58,18 @@ type GrandCentralDispatcher struct { _ func(pant int) `signal:"ChangeRootPane"` _ func(pane int) `signal:"ChangeProfilePane"` // profile management stuff - _ func() `signal:"Loaded"` - _ func(handle, displayname, image, tag string, online bool) `signal:"AddProfile"` - _ func() `signal:"ErrorLoaded0"` - _ func() `signal:"ResetProfile"` - _ func() `signal:"ResetProfileList"` - _ func(failed bool) `signal:"ChangePasswordResponse"` - _ func(onion string, online bool) `signal:"UpdateProfileNetworkStatus"` - _ func(onion string) `signal:"Notify"` + _ func() `signal:"Loaded"` + _ func(handle, displayname, image, tag string, unread int, online bool) `signal:"AddProfile"` + _ func() `signal:"ErrorLoaded0"` + _ func() `signal:"ResetProfile"` + _ func() `signal:"ResetProfileList"` + _ func(failed bool) `signal:"ChangePasswordResponse"` + _ func(onion string, online bool) `signal:"UpdateProfileNetworkStatus"` + _ func(onion string) `signal:"Notify"` // Group Creation - _ func(servers []string) `signal:"SupplyPeeredServers"` - _ func() `signal:"requestPeeredServers,auto"` + _ func(servers []string) `signal:"SupplyPeeredServers"` + _ func() `signal:"requestPeeredServers,auto"` // server management _ func(handle, displayname, image string, status int, autostart bool, bundle string, messages int, key_types []string, keys []string) `signal:"AddServer"` @@ -85,7 +85,7 @@ type GrandCentralDispatcher struct { _ func(handle, displayName string) `signal:"UpdateContactDisplayName"` _ func(handle, image string) `signal:"UpdateContactPicture"` _ func(handle string, status int, loading bool) `signal:"UpdateContactStatus"` - _ func(handle string) `signal:"IncContactUnreadCount"` + _ func(profile, contact string) `signal:"IncContactMessageCount"` _ func(handle string) `signal:"RemoveContact"` _ func(handle, key, value string) `signal:"UpdateContactAttribute"` @@ -127,7 +127,7 @@ type GrandCentralDispatcher struct { _ func(onion string) `signal:"loadMessagesPane,auto"` _ func(signal string) `signal:"broadcast,auto"` // convenience relay signal _ func(name, address string) `signal:"addPeer,auto"` - _ func(address string) `signal:"addGroup,auto"` + _ func(address string) `signal:"addGroup,auto"` _ func(str string) `signal:"createContact,auto"` _ func(str string) `signal:"popup,auto"` _ func(server, groupName string) `signal:"createGroup,auto"` @@ -152,7 +152,7 @@ type GrandCentralDispatcher struct { _ func() `constructor:"init"` // legacy overlay model support - _ func(onion string) `signal:"legacyLoadOverlay,auto"` + _ func(onion string) `signal:"legacyLoadOverlay,auto"` _ func(handle, from, displayName, message, image string, mID string, fromMe bool, ts int64, ackd bool, error bool) `signal:"AppendMessage"` _ func(handle, from, displayName, message, image string, mID string, fromMe bool, ts int64, ackd bool, error bool) `signal:"PrependMessage"` } @@ -323,7 +323,7 @@ func (this *GrandCentralDispatcher) sendMessage(message string) { the.Peer.SendMessageToPeer(this.SelectedConversation(), message) this.TimelineInterface.RequestEIR() } - + this.IncContactMessageCount(this.SelectedProfile(), this.SelectedConversation()) } func (this *GrandCentralDispatcher) loadMessagesPane(handle string) { @@ -378,7 +378,7 @@ func (this *GrandCentralDispatcher) loadMessagesPaneHelper(handle string) { func (this *GrandCentralDispatcher) legacyLoadOverlay(handle string) { // only do this for overlays 2 (bulletin) and 4 (lists) - go this.legacyLoadOverlay_helper(handle, []int{2,4}) + go this.legacyLoadOverlay_helper(handle, []int{2, 4}) } func contains(arr []int, x int) bool { @@ -424,7 +424,7 @@ func (this *GrandCentralDispatcher) legacyLoadOverlay_helper(handle string, over } } - } else {// !isGroup + } else { // !isGroup messages := the.CwtchApp.GetPeer(this.selectedProfile()).GetContact(handle).Timeline.GetMessages() for i := len(messages) - 1; i >= 0; i-- { from := messages[i].PeerID @@ -669,6 +669,8 @@ func (this *GrandCentralDispatcher) addPeer(name, address string) { if name != "" { the.Peer.SetContactAttribute(address, attr.GetLocalScope(constants.Name), name) } + the.Peer.SetContactAttribute(address, attr.GetLocalScope(constants.ApprovedTime), strconv.Itoa(int(time.Now().Unix()))) + the.Peer.PeerWithOnion(address) } @@ -700,6 +702,8 @@ func (this *GrandCentralDispatcher) createGroup(server, groupName string) { the.Peer.SetGroupAttribute(groupID, attr.GetLocalScope(constants.Name), groupName) the.Peer.JoinServer(server) + the.Peer.SetGroupAttribute(server, attr.GetLocalScope(constants.ApprovedTime), strconv.Itoa(int(time.Now().Unix()))) + this.GetUiManager(this.selectedProfile()).AddContact(groupID) } @@ -711,10 +715,11 @@ func (this *GrandCentralDispatcher) setPeerAuthorization(onion string, authoriza return } this.RemoveContact(onion) - this.GetUiManager(this.selectedProfile()).AddContact(onion) if model.Authorization(authorization) == model.AuthApproved { + the.Peer.SetContactAttribute(onion, attr.GetLocalScope(constants.ApprovedTime), strconv.Itoa(int(time.Now().Unix()))) the.Peer.PeerWithOnion(onion) } + this.GetUiManager(this.selectedProfile()).AddContact(onion) } func (this *GrandCentralDispatcher) inviteToGroup(onion, groupID string) { @@ -898,6 +903,7 @@ func (this *GrandCentralDispatcher) loadProfile(onion string) { } } } + the.Peer.SetAttribute(attr.GetSettingsScope(constants.UnreadMsgCount), "0") } func (this *GrandCentralDispatcher) createProfile(nick string, defaultPass bool, password string) { diff --git a/go/ui/manager.go b/go/ui/manager.go index 38461e2e..3c1fe83a 100644 --- a/go/ui/manager.go +++ b/go/ui/manager.go @@ -11,6 +11,7 @@ import ( "cwtch.im/ui/go/the" "git.openprivacy.ca/openprivacy/log" "runtime/debug" + "strconv" "strings" "time" ) @@ -190,8 +191,11 @@ func AddProfile(gcd *GrandCentralDispatcher, handle string) { online, _ := p.GetAttribute(attr.GetLocalScope(constants.PeerOnline)) - log.Debugf("AddProfile %v %v %v %v %v\n", handle, nick, picPath, tag, online) - gcd.AddProfile(handle, nick, picPath, tag, online == event.True) + unreadStr, _ := p.GetAttribute(attr.GetSettingsScope(constants.UnreadMsgCount)) + unread, _ := strconv.Atoi(unreadStr) + + log.Debugf("AddProfile %v %v %v %v %v %v\n", handle, nick, picPath, tag, unread, online) + gcd.AddProfile(handle, nick, picPath, tag, unread, online == event.True) } } @@ -241,6 +245,26 @@ func (this *manager) Acknowledge(handle, mID string) { }) } +func getContactLastMessageTime(contact *model.PublicProfile) int { + time := getLastMessageTime(&contact.Timeline) + + if time == 0 { + approvedTimeStr, _ := contact.GetAttribute(attr.GetLocalScope(constants.ApprovedTime)) + time, _ = strconv.Atoi(approvedTimeStr) + } + return time +} + +func getGroupLastMessageTime(group *model.Group) int { + time := getLastMessageTime(&group.Timeline) + + if time == 0 { + approvedTimeStr, _ := group.GetAttribute(attr.GetLocalScope(constants.ApprovedTime)) + time, _ = strconv.Atoi(approvedTimeStr) + } + return time +} + func getLastMessageTime(tl *model.Timeline) int { if len(tl.Messages) == 0 { return 0 @@ -260,7 +284,7 @@ func (this *manager) AddContact(handle string) { unread := countUnread(group.Timeline.GetMessages(), lastRead) picture := GetProfilePic(handle) - this.gcd.AddContact(handle, GetNick(handle), picture, unread, int(connections.ConnectionStateToType[group.State]), string(model.AuthApproved), false, getLastMessageTime(&group.Timeline)) + this.gcd.AddContact(handle, GetNick(handle), picture, unread, int(connections.ConnectionStateToType[group.State]), string(model.AuthApproved), false, getGroupLastMessageTime(group)) } return } else if !isPeer(handle) { @@ -275,7 +299,7 @@ func (this *manager) AddContact(handle string) { unread := countUnread(contact.Timeline.GetMessages(), lastRead) picture := GetProfilePic(handle) - this.gcd.AddContact(handle, GetNick(handle), picture, unread, int(connections.ConnectionStateToType[contact.State]), string(contact.Authorization), false, getLastMessageTime(&contact.Timeline)) + this.gcd.AddContact(handle, GetNick(handle), picture, unread, int(connections.ConnectionStateToType[contact.State]), string(contact.Authorization), false, getContactLastMessageTime(contact)) } }) } @@ -303,8 +327,11 @@ func (this *manager) MessageJustAdded() { func (this *manager) StoreAndNotify(pere peer.CwtchPeer, onion string, messageTxt string, sent time.Time, profileOnion string) { // Send a New Message from Peer Notification + // Android this.gcd.AndroidCwtchActivity.SetChannel(onion) this.gcd.AndroidCwtchActivity.NotificationChanged("New Message from Peer") + // Desktop + this.gcd.Notify(onion) this.gcd.DoIfProfileElse(this.profile, func() { this.gcd.DoIfConversationElse(onion, func() { @@ -315,16 +342,16 @@ func (this *manager) StoreAndNotify(pere peer.CwtchPeer, onion string, messageTx }, func() { pere.StoreMessage(onion, messageTxt, sent) }) - this.gcd.IncContactUnreadCount(onion) }, func() { the.CwtchApp.GetPeer(profileOnion).StoreMessage(onion, messageTxt, sent) + this.incUnreadMsgCount() }) - this.gcd.Notify(onion) + this.gcd.IncContactMessageCount(this.profile, onion) } // 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() { + this.gcd.DoIfProfileElse(this.profile, func() { this.gcd.DoIfConversation(handle, func() { updateLastReadTime(handle) // If the message is not from the user then add it, otherwise, just acknowledge. @@ -335,11 +362,14 @@ func (this *manager) AddMessage(handle string, from string, message string, from this.gcd.Acknowledged(messageID) } }) - this.gcd.IncContactUnreadCount(handle) + }, func() { + this.incUnreadMsgCount() }) + if !fromMe { this.gcd.Notify(handle) } + this.gcd.IncContactMessageCount(this.profile, handle) } func (this *manager) ReloadProfiles() { @@ -381,3 +411,15 @@ func (this *manager) ChangePasswordResponse(error bool) { func (this *manager) UpdateNetworkStatus(online bool) { this.gcd.UpdateProfileNetworkStatus(this.profile, online) } + +func (this *manager) incUnreadMsgCount() { + peer := the.CwtchApp.GetPeer(this.profile) + if peer != nil { + unreadMsgCountStr, _ := peer.GetAttribute(attr.GetSettingsScope(constants.UnreadMsgCount)) + unreadMsgCount, err := strconv.Atoi(unreadMsgCountStr) + if err != nil { + unreadMsgCount = 0 + } + peer.SetAttribute(attr.GetSettingsScope(constants.UnreadMsgCount), strconv.Itoa(unreadMsgCount+1)) + } +} diff --git a/qml/main.qml b/qml/main.qml index 5cde6080..a2a6f6a2 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -428,7 +428,11 @@ ApplicationWindow { // Until then I am leaving this here for documentation. // androidCwtchActivity.channel = onion // androidCwtchActivity.notification = "Message from " + onion; + + // Basic Desktop notification + windowItem.alert(0) } + } Component.onCompleted: Mutant.standard.imagePath = gcd.assetPath; diff --git a/qml/overlays/ChatOverlay.qml b/qml/overlays/ChatOverlay.qml index 76539d95..66f080e9 100644 --- a/qml/overlays/ChatOverlay.qml +++ b/qml/overlays/ChatOverlay.qml @@ -22,12 +22,6 @@ W.Overlay { target: mm onRowsInserted: { if (messagesListView.atYEnd) thymer.running = true - - //todo: this won't fire for non-active convos - windowItem.alert(0) - if (gcd.os == "android" && windowItem.activeFocusItem == null) { - androidCwtchActivity.notification = "New Content" - } } } diff --git a/qml/widgets/ContactList.qml b/qml/widgets/ContactList.qml index 6758d303..09b47eca 100644 --- a/qml/widgets/ContactList.qml +++ b/qml/widgets/ContactList.qml @@ -124,7 +124,6 @@ ColumnLayout { "_status": status, "_authorization": authorization, "_loading": loading, - "_loading": loading, "_lastMsgTs": lastMsgTs, } @@ -138,7 +137,7 @@ ColumnLayout { } } - onIncContactUnreadCount: function(handle) { + /*onIncContactUnreadCount: function(handle) { var ts = Math.round((new Date()).getTime() / 1000); for(var i = 0; i < contactsModel.count; i++){ if(contactsModel.get(i)["_handle"] == handle) { @@ -147,6 +146,19 @@ ColumnLayout { contactsModel.move(i, 0, 1) } } + }*/ + + onIncContactMessageCount: function(profile, handle) { + if (profile == gcd.selectedProfile) { + var ts = Math.round((new Date()).getTime() / 1000); + for(var i = 0; i < contactsModel.count; i++){ + if(contactsModel.get(i)["_handle"] == handle) { + var contact = contactsModel.get(i) + contact["_lastMsgTs"] = ts + contactsModel.move(i, 0, 1) + } + } + } } onResetProfile: function() { diff --git a/qml/widgets/ContactRow.qml b/qml/widgets/ContactRow.qml index 7078f5e9..271225dc 100644 --- a/qml/widgets/ContactRow.qml +++ b/qml/widgets/ContactRow.qml @@ -148,8 +148,8 @@ Opaque.PortraitRow { } } - onIncContactUnreadCount: function(_handle) { - if (handle == _handle && gcd.selectedConversation != handle) { + onIncContactMessageCount: function(_profile, _handle) { + if (_profile == gcd.selectedProfile && handle == _handle && gcd.selectedConversation != handle) { badge++ } } diff --git a/qml/widgets/ProfileList.qml b/qml/widgets/ProfileList.qml index 204a1c8d..9aaededd 100644 --- a/qml/widgets/ProfileList.qml +++ b/qml/widgets/ProfileList.qml @@ -36,7 +36,7 @@ ColumnLayout { Connections { // ADD/REMOVE CONTACT ENTRIES target: gcd - onAddProfile: function(handle, displayName, image, tag, online) { + onAddProfile: function(handle, displayName, image, tag, unread, online) { // don't add duplicates for (var i = 0; i < profilesModel.count; i++) { @@ -61,6 +61,7 @@ ColumnLayout { _image: image, _tag: tag, _online: online, + _unread: unread, }) } @@ -92,6 +93,7 @@ ColumnLayout { displayName: _displayName image: _image tag: _tag + unread: _unread Layout.fillWidth: true profileOnline: _online rowClicked: function(handle) { diff --git a/qml/widgets/ProfileRow.qml b/qml/widgets/ProfileRow.qml index c2b9f5e5..5ed760e4 100644 --- a/qml/widgets/ProfileRow.qml +++ b/qml/widgets/ProfileRow.qml @@ -23,6 +23,7 @@ RowLayout { property var rowClicked: {} property var editClicked: {} property bool profileOnline: false + property int unread: 0 Opaque.PortraitRow { id: prow @@ -44,7 +45,28 @@ RowLayout { hilightBackgroundColor: prow.badgeColor } - onClicked: rowClicked(handle) + Opaque.Badge { + id: unreadBadge + visible: unread > 0 + color: Theme.portraitContactBadgeColor + size: parent.height/4 + + anchors.right: parent.right + anchors.rightMargin: 25 * gcd.themeScale + anchors.leftMargin: 1 * gcd.themeScale + anchors.verticalCenter: parent.verticalCenter + + content: Label { + id: lblUnread + color: Theme.portraitContactBadgeTextColor + font.pixelSize: Theme.badgeTextSize * gcd.themeScale + font.weight: Font.Bold + text: unread > 99 ? "99+" : unread + } + } + + + onClicked: { unread = 0; rowClicked(handle) } function updateStatus() { if (gcd.torStatus != Const.statusOnline) { // Tor network offline @@ -84,6 +106,12 @@ RowLayout { prow.updateStatus() } } + + onIncContactMessageCount: function(_profile, _handle) { + if (_profile == handle) { + unread++ + } + } } Connections {