From bac4accb1b800db06714367137f71e613edd5efe Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Tue, 22 Oct 2019 11:55:16 -0700 Subject: [PATCH] profile manager: screen, unlock, select, back to Also fixing issues on switching profiles and maintaining ui state around contact ordering and unread count --- go/constants/attributes.go | 1 + go/handlers/appHandler.go | 74 +++++++++++-------------- go/handlers/peerHandler.go | 44 ++++++++------- go/ui/gcd.go | 44 +++++++++++++++ go/ui/manager.go | 27 +++++++-- i18n/translation_de.ts | 35 +++++++++--- i18n/translation_en.qm | Bin 4561 -> 4954 bytes i18n/translation_en.ts | 37 ++++++++++--- i18n/translation_fr.ts | 35 +++++++++--- i18n/translation_pt.ts | 35 +++++++++--- main.go | 2 +- qml.qrc | 1 + qml/main.qml | 24 +++++++- qml/panes/ProfileManagerPane.qml | 84 ++++++++++++++++++++++++++++ qml/widgets/ContactList.qml | 5 ++ qml/widgets/ContactRow.qml | 71 ++++++++++++++++-------- qml/widgets/MyProfile.qml | 16 +++++- qml/widgets/ProfileList.qml | 92 +++++++++++++++++++++++++++++++ 18 files changed, 505 insertions(+), 122 deletions(-) create mode 100644 qml/panes/ProfileManagerPane.qml create mode 100644 qml/widgets/ProfileList.qml diff --git a/go/constants/attributes.go b/go/constants/attributes.go index 10065d64..0868de16 100644 --- a/go/constants/attributes.go +++ b/go/constants/attributes.go @@ -3,3 +3,4 @@ package constants const Nick = "nick" const LastRead = "last-read" const Picture = "picture" +const DefaultPassword = "default-password" diff --git a/go/handlers/appHandler.go b/go/handlers/appHandler.go index 50f2cd5e..7baa03d6 100644 --- a/go/handlers/appHandler.go +++ b/go/handlers/appHandler.go @@ -12,7 +12,7 @@ import ( "time" ) -func App(gcd *ui.GrandCentralDispatcher, subscribed chan bool) { +func App(gcd *ui.GrandCentralDispatcher, subscribed chan bool, reloadingFirst bool) { q := event.NewQueue() the.AppBus.Subscribe(event.NewPeer, q) the.AppBus.Subscribe(event.PeerError, q) @@ -21,9 +21,12 @@ func App(gcd *ui.GrandCentralDispatcher, subscribed chan bool) { the.AppBus.Subscribe(event.NetworkStatus, q) the.AppBus.Subscribe(event.ReloadDone, q) subscribed <- true + loadingV1Accounts := !reloadingFirst networkOffline := false - timeSinceLastSuccess := time.Unix(0,0) + timeSinceLastSuccess := time.Unix(0, 0) + + gcd.Loaded() for { e := q.Next() @@ -74,65 +77,54 @@ func App(gcd *ui.GrandCentralDispatcher, subscribed chan bool) { case event.AppError: if e.Data[event.Error] == event.AppErrLoaded0 { - // TODO: only an error if other profiles are not loaded - log.Infoln("couldn't load your config file. attempting to create one now") - if gcd.Version() == "development" { - the.CwtchApp.CreatePeer("tester", the.AppPassword) + + if loadingV1Accounts { + loadingV1Accounts = false + // TODO: only an error if other profiles are not loaded + if len(the.CwtchApp.ListPeers()) == 0 { + log.Infoln("couldn't load your config file. attempting to create one now") + if gcd.Version() == "development" { + the.CwtchApp.CreatePeer("tester", the.AppPassword) + } else { + the.CwtchApp.CreatePeer("alice", the.AppPassword) + } + } } else { - the.CwtchApp.CreatePeer("alice", the.AppPassword) + gcd.ErrorLoaded0() } } case event.ReloadDone: if the.Peer == nil { + loadingV1Accounts = true the.CwtchApp.LoadProfiles(the.AppPassword) } case event.NewPeer: - if the.Peer != nil { - continue - } onion := e.Data[event.Identity] + peer := the.CwtchApp.GetPeer(onion) + + if loadingV1Accounts { + the.CwtchApp.GetPeer(onion).SetAttribute(constants.DefaultPassword, "true") + loadingV1Accounts = false + } + + log.Infof("NewPeer for %v\n", onion) + gcd.UIManager.AddProfile(onion) the.CwtchApp.AddPeerPlugin(onion, plugins.CONNECTIONRETRY) the.CwtchApp.AddPeerPlugin(onion, plugins.NETWORKCHECK) - the.Peer = the.CwtchApp.GetPeer(onion) - the.EventBus = the.CwtchApp.GetEventBus(onion) - incSubscribed := make(chan bool) - go PeerHandler(&gcd.UIManager, incSubscribed) + go PeerHandler(onion, &gcd.UIManager, incSubscribed) <-incSubscribed - pic, exists := the.Peer.GetAttribute(constants.Picture) - if !exists { - pic = ui.RandomProfileImage(the.Peer.GetProfile().Onion) - the.Peer.SetAttribute(constants.Picture, pic) - } - gcd.UpdateMyProfile(the.Peer.GetProfile().Name, the.Peer.GetProfile().Onion, pic) - - contacts := the.Peer.GetContacts() - for i := range contacts { - gcd.UIManager.AddContact(contacts[i]) - } - - groups := the.Peer.GetGroups() - for i := range groups { - // Only join servers for active and explicitly accepted groups. - gcd.UIManager.AddContact(groups[i]) - } - if e.Data[event.Status] != "running" { - the.CwtchApp.LaunchPeers() + peer.Listen() + peer.StartPeersConnections() + peer.StartGroupConnections() } - // load ui preferences - gcd.RequestSettings() - locale, exists := the.Peer.GetProfile().GetAttribute(constants.LocaleSetting) - if exists { - gcd.SetLocale_helper(locale) - } - - blockUnkownPeers, exists := the.Peer.GetProfile().GetAttribute(constants.BlockUnknownPeersSetting) + blockUnkownPeers, exists := peer.GetProfile().GetAttribute(constants.BlockUnknownPeersSetting) if exists && blockUnkownPeers == "true" { the.EventBus.Publish(event.NewEvent(event.BlockUnknownPeers, map[event.Field]string{})) } diff --git a/go/handlers/peerHandler.go b/go/handlers/peerHandler.go index 497f28f7..a753db98 100644 --- a/go/handlers/peerHandler.go +++ b/go/handlers/peerHandler.go @@ -9,18 +9,20 @@ import ( "time" ) -func PeerHandler(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() - the.EventBus.Subscribe(event.NewMessageFromPeer, q) - the.EventBus.Subscribe(event.PeerAcknowledgement, q) - the.EventBus.Subscribe(event.NewMessageFromGroup, q) - the.EventBus.Subscribe(event.NewGroupInvite, q) - the.EventBus.Subscribe(event.SendMessageToGroupError, q) - the.EventBus.Subscribe(event.SendMessageToPeerError, q) - the.EventBus.Subscribe(event.ServerStateChange, q) - the.EventBus.Subscribe(event.PeerStateChange, q) - the.EventBus.Subscribe(event.PeerCreated, q) - the.EventBus.Subscribe(event.NetworkStatus, q) + eventBus.Subscribe(event.NewMessageFromPeer, q) + eventBus.Subscribe(event.PeerAcknowledgement, q) + eventBus.Subscribe(event.NewMessageFromGroup, q) + eventBus.Subscribe(event.NewGroupInvite, q) + eventBus.Subscribe(event.SendMessageToGroupError, q) + eventBus.Subscribe(event.SendMessageToPeerError, q) + eventBus.Subscribe(event.ServerStateChange, q) + eventBus.Subscribe(event.PeerStateChange, q) + eventBus.Subscribe(event.PeerCreated, q) + eventBus.Subscribe(event.NetworkStatus, q) subscribed <- true @@ -43,8 +45,8 @@ func PeerHandler(uiManager *ui.Manager, subscribed chan bool) { case event.NewMessageFromPeer: //event.TimestampReceived, event.RemotePeer, event.Data ts, _ := time.Parse(time.RFC3339Nano, e.Data[event.TimestampReceived]) uiManager.AddMessage(e.Data[event.RemotePeer], e.Data[event.RemotePeer], e.Data[event.Data], false, e.EventID, ts, true) - if the.Peer.GetContact(e.Data[event.RemotePeer]) == nil { - the.Peer.AddContact(e.Data[event.RemotePeer], e.Data[event.RemotePeer], false) + if peer.GetContact(e.Data[event.RemotePeer]) == nil { + peer.AddContact(e.Data[event.RemotePeer], e.Data[event.RemotePeer], false) } case event.PeerAcknowledgement: @@ -52,10 +54,10 @@ func PeerHandler(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] == the.Peer.GetProfile().Onion, e.Data[event.Signature], ts, true) + uiManager.AddMessage(e.Data[event.GroupID], e.Data[event.RemotePeer], e.Data[event.Data], e.Data[event.RemotePeer] == peer.GetProfile().Onion, e.Data[event.Signature], ts, true) case event.NewGroupInvite: - gid, err := the.Peer.GetProfile().ProcessInvite(e.Data[event.GroupInvite], e.Data[event.RemotePeer]) - group := the.Peer.GetGroup(gid) + gid, err := peer.GetProfile().ProcessInvite(e.Data[event.GroupInvite], e.Data[event.RemotePeer]) + group := peer.GetGroup(gid) if err == nil && group != nil { uiManager.AddContact(gid) } @@ -70,18 +72,18 @@ func PeerHandler(uiManager *ui.Manager, subscribed chan bool) { cxnState := connections.ConnectionStateToType[e.Data[event.ConnectionState]] // if it's not in the.PeerHandler it's new. Only add once Authed - if _, exists := the.Peer.GetProfile().Contacts[e.Data[event.RemotePeer]]; !exists { + if _, exists := peer.GetProfile().Contacts[e.Data[event.RemotePeer]]; !exists { // Contact does not exist, we will add them but we won't know who they are until they are authenticated // So if we get any other state from an unknown contact we do nothing // (the next exists check will fail) if cxnState == connections.AUTHENTICATED { - the.Peer.AddContact(e.Data[event.RemotePeer], e.Data[event.RemotePeer], false) + peer.AddContact(e.Data[event.RemotePeer], e.Data[event.RemotePeer], false) uiManager.AddContact(e.Data[event.RemotePeer]) } } // if it's in the.PeerHandler its either existing and needs an update or not in the UI and needs to be added - if contact, exists := the.Peer.GetProfile().Contacts[e.Data[event.RemotePeer]]; exists { + if contact, exists := peer.GetProfile().Contacts[e.Data[event.RemotePeer]]; exists { contact.State = e.Data[event.ConnectionState] uiManager.UpdateContactStatus(contact.Onion, int(cxnState), false) @@ -89,9 +91,9 @@ func PeerHandler(uiManager *ui.Manager, subscribed chan bool) { case event.ServerStateChange: serverOnion := e.Data[event.GroupServer] state := connections.ConnectionStateToType[e.Data[event.ConnectionState]] - groups := the.Peer.GetGroups() + groups := peer.GetGroups() for _, groupID := range groups { - group := the.Peer.GetGroup(groupID) + group := peer.GetGroup(groupID) if group != nil && group.GroupServer == serverOnion { group.State = e.Data[event.ConnectionState] loading := false diff --git a/go/ui/gcd.go b/go/ui/gcd.go index 3ff20789..1c3c8e7b 100644 --- a/go/ui/gcd.go +++ b/go/ui/gcd.go @@ -27,6 +27,12 @@ type GrandCentralDispatcher struct { _ string `property:"version"` _ string `property:"buildDate"` + // profile management stuff + _ func() `signal:"Loaded"` + _ func(handle, displayname, image string) `signal:"AddProfile"` + _ func() `signal:"ErrorLoaded0"` + _ func() `signal:"ResetProfile"` + // contact list stuff _ func(handle, displayName, image, server string, badge, status int, blocked bool, loading bool, lastMsgTime int) `signal:"AddContact"` _ func(handle, displayName string) `signal:"UpdateContactDisplayName"` @@ -82,6 +88,8 @@ type GrandCentralDispatcher struct { _ func() `signal:"allowUnknownPeers,auto"` _ func() `signal:"blockUnknownPeers,auto"` _ func() `signal:"onActivate,auto"` + _ func(password string) `signal:"unlockProfiles,auto"` + _ func(handle string) `signal:"loadProfile,auto"` } func (this *GrandCentralDispatcher) sendMessage(message string, mID string) { @@ -295,6 +303,8 @@ func (this *GrandCentralDispatcher) broadcast(signal string) { log.Debugf("unhandled broadcast signal: %v", signal) case "ResetMessagePane": this.ResetMessagePane() + case "ResetProfile": + this.ResetProfile() } } @@ -477,3 +487,37 @@ func (this *GrandCentralDispatcher) SetLocale_helper(locale string) { core.QCoreApplication_InstallTranslator(this.Translator) this.QMLEngine.Retranslate() } + +func (this *GrandCentralDispatcher) unlockProfiles(password string) { + the.CwtchApp.LoadProfiles(password) +} + +func (this *GrandCentralDispatcher) loadProfile(onion string) { + the.Peer = the.CwtchApp.GetPeer(onion) + the.EventBus = the.CwtchApp.GetEventBus(onion) + + pic, exists := the.Peer.GetAttribute(constants.Picture) + if !exists { + pic = RandomProfileImage(the.Peer.GetProfile().Onion) + the.Peer.SetAttribute(constants.Picture, pic) + } + this.UpdateMyProfile(the.Peer.GetProfile().Name, the.Peer.GetProfile().Onion, pic) + + contacts := the.Peer.GetContacts() + for i := range contacts { + this.UIManager.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]) + } + + // load ui preferences + this.RequestSettings() + locale, exists := the.Peer.GetProfile().GetAttribute(constants.LocaleSetting) + if exists { + this.SetLocale_helper(locale) + } +} diff --git a/go/ui/manager.go b/go/ui/manager.go index 11423539..b2f0ad34 100644 --- a/go/ui/manager.go +++ b/go/ui/manager.go @@ -98,7 +98,7 @@ func updateLastReadTime(id string) { 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) { + if messages[i].Timestamp.After(lastRead) || messages[i].Timestamp.Equal(lastRead) { count++ } else { break @@ -115,6 +115,25 @@ func NewManager(gcd *GrandCentralDispatcher) Manager { return Manager{gcd} } +func (this *Manager) AddProfile(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) + } + log.Infof("AddProfile %v %v %v\n", handle, nick, pic) + this.gcd.AddProfile(handle, nick, pic) + } +} + func (this *Manager) Acknowledge(mID string) { this.gcd.Acknowledged(mID) } @@ -170,13 +189,12 @@ func (this *Manager) AddSendMessageError(peer string, signature string, err stri } func (this *Manager) AddMessage(handle string, from string, message string, fromMe bool, messageID string, timestamp time.Time, Acknowledged bool) { - updateLastReadTime(handle) - nick := getOrDefault(handle, constants.Nick, handle) image := getProfilePic(handle) // 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) @@ -187,9 +205,8 @@ func (this *Manager) AddMessage(handle string, from string, message string, from this.gcd.Acknowledged(messageID) } } - } else { - this.gcd.IncContactUnreadCount(handle) } + this.gcd.IncContactUnreadCount(handle) } func (this *Manager) UpdateContactDisplayName(handle string, name string) { diff --git a/i18n/translation_de.ts b/i18n/translation_de.ts index e7a778a9..5398cd7a 100644 --- a/i18n/translation_de.ts +++ b/i18n/translation_de.ts @@ -145,18 +145,18 @@ Klicken, um DM zu senden - + could-not-send-msg-error Could not send this message Nachricht konnte nicht gesendet werden - + acknowledged-label bestätigt - + pending-label Bestätigung ausstehend @@ -164,25 +164,25 @@ MyProfile - + copy-btn Button for copying profile onion address to clipboard Kopieren - + copied-clipboard-notification Copied to clipboard in die Zwischenablage kopiert - + new-group-btn create new group button Neue Gruppe anlegen - + paste-address-to-add-contact ex: "... paste an address here to add a contact ..." Adresse hier hinzufügen, um einen Kontakt aufzunehmen @@ -273,6 +273,27 @@ löschen + + ProfileManagerPane + + + enter-profile-password + Please enter password: + + + + + error-0-profiles-loaded-for-password + 0 profiles loaded with that password + + + + + unlock + Unlock + + + SettingsPane diff --git a/i18n/translation_en.qm b/i18n/translation_en.qm index 6ec2aa99957749a8126d16bce2bc9488f4c7423e..5149964b83b8349c7af5bf0d86c42bebe3b366e0 100644 GIT binary patch delta 803 zcma)2T}TvB6#iysc6QvE9e>!A#0Q}ek_Hs|dB~rjjFf~ee;%S9hMlWz4l~2dmI~q) zR0@J*AQ6F4P$Yr$Y|_^_=i|`UMc~4v!>8sQ-9) z`sQ^&I24}kd=BJ(2roH=$7HcgqdC!0v8PrAGNC;|BJB*KQY3zRNDuN@ z+b`3AMya?YP6SQT;iF%velHEk1%UWnnyDdo{JS73^Es1~OC)4hRB0b3WBt(`m&O34 zFM6kU5XjjZz1J{KyeFeSdVeAVVL>(qcGEhNviFJBCT8Wij>n|9Qi;D_CW5&jhVClb z!3kPtiE?e2h(g^#R1PSQR(*#zV)_6HiLS)v>KDm*zgpGWOdGwe9?7S7u1VEztq>p7 zzFNO;Uy9btoz6~htwN=O7C11VBMm0`U7~+l979jD2&K98mSgHkR%fKr&8#b(<;9m$ zrW^^FVD!zSyOfUQ6Tqe1SwlT^!lv3-CoN=Fnz@?e+HEP5X^!rBowjTELkbcB*r;22 zJ987d#W*~tDz+lV7$+nJxfkU6UVv+z*(+1|1UuqZ_xV}uRm8<8KW%+#U90LQZGwZy7 zc?=BPCaf3B&oD6XRj@uP0P=aRvZ?6;b#oQ7)!6efFfchoXbwgQ&Ha$=`(B_pcNzP1 z(L4qQ&WjvTv0oV&c*{AuxrG=Q*bZ}?umd`g`5uJkS-`nt$0MM9tXw?lQyJft!F1-^{aUD$pFJN(jv(#&hrw$V0ZgiQPblaP8o|>UkGvZV;bM zScoDpT&nnD1c3?|gZUEIzXKY`z}IdMQYetgz#t&8`4VFo%jP(?(;S;)1pYAr020Gy Ai2wiq diff --git a/i18n/translation_en.ts b/i18n/translation_en.ts index 97294b40..2f41ac53 100644 --- a/i18n/translation_en.ts +++ b/i18n/translation_en.ts @@ -103,7 +103,7 @@ Update - + Update @@ -145,18 +145,18 @@ Click to DM - + could-not-send-msg-error Could not send this message Could not send this message - + acknowledged-label Acknowledged - + pending-label Pending @@ -164,25 +164,25 @@ MyProfile - + copy-btn Button for copying profile onion address to clipboard Copy - + copied-clipboard-notification Copied to clipboard Copied to clipboard - + new-group-btn create new group button Create new group - + paste-address-to-add-contact ex: "... paste an address here to add a contact ..." ... paste an address here to add a contact... @@ -273,6 +273,27 @@ Delete + + ProfileManagerPane + + + enter-profile-password + Please enter password: + Please enter password + + + + error-0-profiles-loaded-for-password + 0 profiles loaded with that password + 0 profiles loaded with that password + + + + unlock + Unlock + Unlock + + SettingsPane diff --git a/i18n/translation_fr.ts b/i18n/translation_fr.ts index 0cdbf03a..f8fe744e 100644 --- a/i18n/translation_fr.ts +++ b/i18n/translation_fr.ts @@ -145,18 +145,18 @@ Envoyer un message privé - + could-not-send-msg-error Could not send this message Impossible d'envoyer ce message - + acknowledged-label Confirmé - + pending-label En attente @@ -164,25 +164,25 @@ MyProfile - + copy-btn Button for copying profile onion address to clipboard Copier - + copied-clipboard-notification Copied to clipboard Copié dans le presse-papier - + new-group-btn create new group button Créer un nouveau groupe - + paste-address-to-add-contact ex: "... paste an address here to add a contact ..." ... coller une adresse ici pour ajouter un contact... @@ -273,6 +273,27 @@ Effacer + + ProfileManagerPane + + + enter-profile-password + Please enter password: + + + + + error-0-profiles-loaded-for-password + 0 profiles loaded with that password + + + + + unlock + Unlock + + + SettingsPane diff --git a/i18n/translation_pt.ts b/i18n/translation_pt.ts index 72acd517..a5f828f2 100644 --- a/i18n/translation_pt.ts +++ b/i18n/translation_pt.ts @@ -145,18 +145,18 @@ Clique para DM - + could-not-send-msg-error Could not send this message Não deu para enviar esta mensagem - + acknowledged-label Confirmada - + pending-label Pendente @@ -164,25 +164,25 @@ MyProfile - + copy-btn Button for copying profile onion address to clipboard Copiar - + copied-clipboard-notification Copied to clipboard Copiado - + new-group-btn create new group button Criar novo grupo - + paste-address-to-add-contact ex: "... paste an address here to add a contact ..." … cole um endereço aqui para adicionar um contato… @@ -273,6 +273,27 @@ Deletar + + ProfileManagerPane + + + enter-profile-password + Please enter password: + + + + + error-0-profiles-loaded-for-password + 0 profiles loaded with that password + + + + + unlock + Unlock + + + SettingsPane diff --git a/main.go b/main.go index 1b514f52..b6da2e69 100644 --- a/main.go +++ b/main.go @@ -255,7 +255,7 @@ func loadNetworkingAndFiles(gcd *ui.GrandCentralDispatcher, service bool, client if !service { the.AppBus = the.CwtchApp.GetPrimaryBus() subscribed := make(chan bool) - go handlers.App(gcd, subscribed) + go handlers.App(gcd, subscribed, clientUI) <-subscribed } diff --git a/qml.qrc b/qml.qrc index b4097e8e..7f33a9a4 100644 --- a/qml.qrc +++ b/qml.qrc @@ -13,6 +13,7 @@ qml/panes/PeerSettingsPane.qml qml/panes/SettingsPane.qml qml/panes/SplashPane.qml + qml/panes/ProfileManagerPane.qml qml/styles/CwtchComboBoxStyle.qml qml/styles/CwtchExpandingButton.qml qml/styles/CwtchTextAreaStyle.qml diff --git a/qml/main.qml b/qml/main.qml index 71b3db84..dcc2a1b4 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -113,9 +113,14 @@ ApplicationWindow { StackLayout { id: parentStack - currentIndex: 0 anchors.fill: parent + currentIndex: 0 + readonly property int splashPane: 0 + readonly property int managementPane: 1 + readonly property int profilePane: 2 + property alias pane: parentStack.currentIndex + Rectangle { // Splash pane color: "#FFFFFF" //Layout.fillHeight: true @@ -131,6 +136,18 @@ ApplicationWindow { running: true } } + + Rectangle { // Profile login/management pane + anchors.fill: parent + visible: false + color: "#D2C0DD" + + ProfileManagerPane { + id: profilesPane + anchors.fill: parent + } + } + RowLayout { // CONTAINS EVERYTHING EXCEPT THE TOOLBAR /* anchors.left: ratio >= 0.92 ? parent.left : toolbar.right anchors.top: ratio >= 0.92 ? toolbar.bottom : parent.top @@ -247,6 +264,11 @@ ApplicationWindow { onSetToolbarTitle: function(str) { theStack.title = str } + + onLoaded: function() { + parentStack.pane = parentStack.managementPane + splashPane.running = false + } } Connections { diff --git a/qml/panes/ProfileManagerPane.qml b/qml/panes/ProfileManagerPane.qml new file mode 100644 index 00000000..cd0e1d0c --- /dev/null +++ b/qml/panes/ProfileManagerPane.qml @@ -0,0 +1,84 @@ +import QtQuick 2.0 +import QtGraphicalEffects 1.0 +import QtQuick 2.7 +import QtQuick.Controls 2.4 +import QtQuick.Controls.Material 2.0 +import QtQuick.Layouts 1.3 +import QtQuick.Window 2.11 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +import "../widgets" +import "../widgets/controls" +import "../styles" + + +ColumnLayout { + id: thecol + anchors.fill: parent + //leftPadding: 10 + //spacing: 5 + + ScalingLabel { + anchors.horizontalCenter: parent.horizontalCenter + wrapMode: TextEdit.Wrap + //: Please enter password: + text: qsTr("enter-profile-password")+":" + } + + TextField { + id: txtPassword + anchors.horizontalCenter: parent.horizontalCenter + style: CwtchTextFieldStyle{ width: thecol.width * 0.8 } + echoMode: TextInput.Password + } + + ScalingLabel { + id: error + anchors.horizontalCenter: parent.horizontalCenter + color: "red" + //: 0 profiles loaded with that password + text: qsTr("error-0-profiles-loaded-for-password") + visible: false + } + + SimpleButton { + id: "button" + anchors.horizontalCenter: parent.horizontalCenter + + icon: "solid/unlock-alt" + //: Unlock + text: qsTr("unlock") + + onClicked: { + gcd.unlockProfiles(txtPassword.text) + txtPassword.text = "" + error.visible = false + } + } + + Connections { // ADD/REMOVE CONTACT ENTRIES + target: gcd + + onErrorLoaded0: function() { + error.visible = true + } + } + + + Rectangle { // THE LEFT PANE WITH TOOLS AND CONTACTS + color: "#D2C0DD" + width: thecol.width + + Layout.fillHeight: true + Layout.fillWidth: true + Layout.minimumWidth: Layout.maximumWidth + //Layout.maximumWidth: theStack.pane == theStack.emptyPane ? parent.width : 450 + + ProfileList { + anchors.fill: parent + } + } + + +} diff --git a/qml/widgets/ContactList.qml b/qml/widgets/ContactList.qml index bcacf3be..1552475d 100644 --- a/qml/widgets/ContactList.qml +++ b/qml/widgets/ContactList.qml @@ -99,6 +99,10 @@ ColumnLayout { contactsModel.move(i, 0, 1) } } + } + + onResetProfile: function() { + contactsModel.clear() } } @@ -117,6 +121,7 @@ ColumnLayout { status: _status blocked: _blocked loading: _loading + type: "contact" } } diff --git a/qml/widgets/ContactRow.qml b/qml/widgets/ContactRow.qml index 5f497ec5..e6dd9e67 100644 --- a/qml/widgets/ContactRow.qml +++ b/qml/widgets/ContactRow.qml @@ -27,6 +27,7 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY property alias status: imgProfile.status property string server property bool background: true + property string type Rectangle { // CONTACT ENTRY BACKGROUND COLOR @@ -39,23 +40,42 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY ContactPicture { id: imgProfile - showStatus: true + showStatus: type == "contact" } - Label { // CONTACT NAME - id: cn - leftPadding: 10 - rightPadding: 10 - //wrapMode: Text.WordWrap + ColumnLayout { + anchors.left: imgProfile.right anchors.right: loading ? loadingProgress.left : rectUnread.left anchors.verticalCenter: parent.verticalCenter - font.pixelSize: 16 * gcd.themeScale - font.strikeout: blocked - textFormat: Text.PlainText - //fontSizeMode: Text.HorizontalFit - elide: Text.ElideRight - color: "#000000" + + + Label { // CONTACT NAME + id: cn + leftPadding: 10 + rightPadding: 10 + //wrapMode: Text.WordWrap + font.pixelSize: 16 * gcd.themeScale + font.strikeout: blocked + textFormat: Text.PlainText + //fontSizeMode: Text.HorizontalFit + elide: Text.ElideRight + color: "#000000" + } + + Label { // Onion + id: onion + text: handle + leftPadding: 10 + rightPadding: 10 + font.pixelSize: 10 * gcd.themeScale + font.strikeout: blocked + textFormat: Text.PlainText + //fontSizeMode: Text.HorizontalFit + elide: Text.ElideRight + color: "#000000" + } + } Rectangle { // UNREAD MESSAGES? @@ -116,15 +136,22 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY hoverEnabled: true onClicked: { - if (displayName != "me") { - gcd.broadcast("ResetMessagePane") - isActive = true - theStack.pane = theStack.messagePane - gcd.loadMessagesPane(handle) - badge = 0 - if (handle.length == 32) { - gcd.requestGroupSettings(handle) - } + if (type == "contact") { + if (displayName != "me") { + gcd.broadcast("ResetMessagePane") + isActive = true + theStack.pane = theStack.messagePane + gcd.loadMessagesPane(handle) + badge = 0 + if (handle.length == 32) { + gcd.requestGroupSettings(handle) + } + } + } else if (type == "profile") { + gcd.broadcast("ResetMessagePane") + gcd.broadcast("ResetProfile") + gcd.loadProfile(handle) + parentStack.pane = parentStack.profilePane } } @@ -164,7 +191,7 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY } onIncContactUnreadCount: function(handle) { - if (handle == _handle) { + if (handle == _handle && gcd.currentOpenConversation != handle) { badge++ } } diff --git a/qml/widgets/MyProfile.qml b/qml/widgets/MyProfile.qml index 78599a5a..d634c69a 100644 --- a/qml/widgets/MyProfile.qml +++ b/qml/widgets/MyProfile.qml @@ -19,6 +19,20 @@ ColumnLayout { property string onion + SimpleButton {// BACK BUTTON + id: btnBack + icon: "solid/arrow-circle-left" + anchors.left: parent.left + //anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 2 + anchors.top: parent.top + anchors.topMargin: 2 + onClicked: function() { + parentStack.pane = parentStack.managementPane + theStack.pane = theStack.emptyPane + } + } + Item{ height: 6 } Item { // PROFILE IMAGE @@ -229,8 +243,6 @@ ColumnLayout { lblNick.text = _nick onion = _onion image = _image - parentStack.currentIndex = 1 - splashPane.running = false } onTorStatus: function(code, str) { diff --git a/qml/widgets/ProfileList.qml b/qml/widgets/ProfileList.qml new file mode 100644 index 00000000..8e8db340 --- /dev/null +++ b/qml/widgets/ProfileList.qml @@ -0,0 +1,92 @@ +import QtGraphicalEffects 1.0 +import QtQuick 2.7 +import QtQuick.Controls 2.4 +import QtQuick.Controls.Material 2.0 +import QtQuick.Layouts 1.3 + +ColumnLayout { + id: root + + + MouseArea { + anchors.fill: parent + + onClicked: { + forceActiveFocus() + } + } + + Flickable { // Profile List + id: sv + clip: true + Layout.minimumHeight: 100 + Layout.fillHeight: true + Layout.minimumWidth: parent.width + Layout.maximumWidth: parent.width + contentWidth: colContacts.width + contentHeight: colContacts.height + boundsBehavior: Flickable.StopAtBounds + maximumFlickVelocity: 400 + + + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AlwaysOn + } + + ColumnLayout { + id: colContacts + width: root.width + spacing: 0 + + Connections { // ADD/REMOVE CONTACT ENTRIES + target: gcd + + onAddProfile: function(handle, displayName, image) { + + console.log("ProfileList onAddProfile for: " + handle) + for (var i = 0; i < profilesModel.count; i++) { + if (profilesModel.get(i)["_handle"] == handle) { + return + } + } + + profilesModel.append({ + "_handle": handle, + "_displayName": displayName, + "_image": image + }) + } + + /* + onRemoveProfile: function(handle) { + for(var i = 0; i < profilesModel.count; i++){ + if(profilesModel.get(i)["_handle"] == handle) { + console.log("deleting contact " + profilesModel.get(i)["_handle"]) + profilesModel.remove(i) + return + } + } + }*/ + } + + ListModel { // Profile OBJECTS ARE STORED HERE ... + id: profilesModel + } + + Repeater { + model: profilesModel // ... AND DISPLAYED HERE + delegate: ContactRow { + handle: _handle + displayName: _displayName + image: _image + server: "" + badge: 0 + status: 0 + blocked: false + loading: false + type: "profile" + } + } + } + } +} -- 2.25.1