From 5343348aa9975687047c12b20af01b2c23912ebc Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Wed, 8 Apr 2020 15:30:10 -0700 Subject: [PATCH] Redesign my profile widget to match design. It dynamically resizes and shapes depending if the side pane is open. Fixes for ellipse label and portrait size, positioning, margins --- go/handlers/peerHandler.go | 6 +- go/ui/gcd.go | 7 +- go/ui/manager.go | 17 +- qml.qrc | 1 + qml/main.qml | 5 +- qml/styles/CwtchTextFieldStyle.qml | 2 +- qml/widgets/ContactList.qml | 4 +- qml/widgets/EllipsisLabel.qml | 56 +++++ qml/widgets/Message.qml | 3 +- qml/widgets/MyProfile.qml | 331 +++++++++++------------------ qml/widgets/Portrait.qml | 7 +- qml/widgets/PortraitRow.qml | 71 +++---- 12 files changed, 240 insertions(+), 270 deletions(-) create mode 100644 qml/widgets/EllipsisLabel.qml diff --git a/go/handlers/peerHandler.go b/go/handlers/peerHandler.go index 3799b594..9909e745 100644 --- a/go/handlers/peerHandler.go +++ b/go/handlers/peerHandler.go @@ -108,9 +108,11 @@ func PeerHandler(onion string, uiManager ui.Manager, subscribed chan bool) { if exists && scope == attr.PublicScope { switch path { case constants.Name: - uiManager.UpdateContactDisplayName(onion, val) + peer.SetContactAttribute(onion, attr.GetPublicScope(constants.Name), val) + uiManager.UpdateContactDisplayName(onion) case constants.Picture: - uiManager.UpdateContactPicture(onion, val) + peer.SetContactAttribute(onion, attr.GetPublicScope(constants.Picture), val) + uiManager.UpdateContactPicture(onion) } } diff --git a/go/ui/gcd.go b/go/ui/gcd.go index b4ebe0dc..64bd1bbd 100644 --- a/go/ui/gcd.go +++ b/go/ui/gcd.go @@ -68,8 +68,8 @@ type GrandCentralDispatcher struct { _ func(loading bool) `signal:"SetLoadingState"` // profile-area stuff - _ func(name, onion, image string) `signal:"UpdateMyProfile"` - _ func(status int, str string) `signal:"TorStatus"` + _ func(name, onion, image, tag string) `signal:"UpdateMyProfile"` + _ func(status int, str string) `signal:"TorStatus"` // settings helpers _ func(str string) `signal:"InvokePopup"` @@ -605,7 +605,8 @@ func (this *GrandCentralDispatcher) loadProfile(onion string) { pic = NewImage(RandomProfileImage(onion), TypeImageDistro) the.Peer.SetAttribute(attr.GetPublicScope(constants.Picture), ImageToString(pic)) } - this.UpdateMyProfile(the.Peer.GetName(), the.Peer.GetOnion(), getPicturePath(pic)) + tag, _ := the.Peer.GetAttribute(app.AttributeTag) + this.UpdateMyProfile(the.Peer.GetName(), the.Peer.GetOnion(), getPicturePath(pic), tag) contacts := the.Peer.GetContacts() for i := range contacts { diff --git a/go/ui/manager.go b/go/ui/manager.go index 3ad2fe73..53f8f38e 100644 --- a/go/ui/manager.go +++ b/go/ui/manager.go @@ -67,7 +67,7 @@ func getNick(id string) string { return nick } else { nick, exists := the.Peer.GetContactAttribute(id, attr.GetLocalScope(constants.Name)) - if !exists { + if !exists || nick == "" { nick, exists = the.Peer.GetContactAttribute(id, attr.GetPeerScope(constants.Name)) if !exists { nick = id @@ -203,8 +203,8 @@ type Manager interface { ReloadProfiles() - UpdateContactDisplayName(handle string, name string) - UpdateContactPicture(handle string, picVal string) + UpdateContactDisplayName(handle string) + UpdateContactPicture(handle string) UpdateContactStatus(handle string, status int, loading bool) UpdateContactAttribute(handle, key, value string) @@ -304,19 +304,16 @@ func (this *manager) ReloadProfiles() { } // UpdateContactDisplayName updates a contact's display name in the contact list and conversations -func (this *manager) UpdateContactDisplayName(handle string, name string) { +func (this *manager) UpdateContactDisplayName(handle string) { this.gcd.DoIfProfile(this.profile, func() { - this.gcd.UpdateContactDisplayName(handle, name) + this.gcd.UpdateContactDisplayName(handle, getNick(handle)) }) } // UpdateContactPicture updates a contact's picture in the contact list and conversations -func (this *manager) UpdateContactPicture(handle string, picVal string) { +func (this *manager) UpdateContactPicture(handle string) { this.gcd.DoIfProfile(this.profile, func() { - pic, err := StringToImage(picVal) - if err == nil { - this.gcd.UpdateContactPicture(handle, getPicturePath(pic)) - } + this.gcd.UpdateContactPicture(handle, getProfilePic(handle)) }) } diff --git a/qml.qrc b/qml.qrc index c524d5b1..cfb4b0f6 100644 --- a/qml.qrc +++ b/qml.qrc @@ -28,6 +28,7 @@ qml/widgets/InplaceEditText.qml qml/widgets/Message.qml qml/widgets/ScalingLabel.qml + qml/widgets/EllipsisLabel.qml qml/widgets/MyProfile.qml qml/widgets/ProfileList.qml qml/widgets/RadioButton.qml diff --git a/qml/main.qml b/qml/main.qml index c1a58094..c83d2948 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -87,7 +87,7 @@ ApplicationWindow { property alias pane: parentStack.currentIndex Rectangle { // Splash pane - color: "#FFFFFF" + color: Theme.backgroundMainColor //Layout.fillHeight: true //Layout.minimumWidth: Layout.maximumWidth //Layout.minimumHeight: parent.height @@ -130,7 +130,7 @@ ApplicationWindow { spacing: 0 Rectangle { // THE LEFT PANE WITH TOOLS AND CONTACTS - color: "#D2C0DD" + color: Theme.backgroundMainColor Layout.fillHeight: true Layout.minimumWidth: Layout.maximumWidth Layout.maximumWidth: theStack.pane == theStack.emptyPane ? parent.width : Theme.sidePaneMinSize @@ -139,6 +139,7 @@ ApplicationWindow { ContactList{ anchors.fill: parent + dualPane: theStack.pane != theStack.emptyPane || theStack.pane == undefined } } diff --git a/qml/styles/CwtchTextFieldStyle.qml b/qml/styles/CwtchTextFieldStyle.qml index 5b17266d..83225859 100644 --- a/qml/styles/CwtchTextFieldStyle.qml +++ b/qml/styles/CwtchTextFieldStyle.qml @@ -5,7 +5,7 @@ TextFieldStyle { id: root textColor: "black" font.pointSize: 10 * gcd.themeScale - property int width: 100 + property int width: parent.width background: Rectangle { radius: 2 diff --git a/qml/widgets/ContactList.qml b/qml/widgets/ContactList.qml index 13630fbb..67244d5c 100644 --- a/qml/widgets/ContactList.qml +++ b/qml/widgets/ContactList.qml @@ -7,6 +7,8 @@ import QtQuick.Layouts 1.3 ColumnLayout { id: root + property alias dualPane: myprof.dualPane + MouseArea { anchors.fill: parent @@ -16,7 +18,7 @@ ColumnLayout { } } - MyProfile{ // CURRENT PROFILE INFO AND CONTROL BAR + MyProfile { // CURRENT PROFILE INFO AND CONTROL BAR id: myprof } diff --git a/qml/widgets/EllipsisLabel.qml b/qml/widgets/EllipsisLabel.qml new file mode 100644 index 00000000..71e6f09e --- /dev/null +++ b/qml/widgets/EllipsisLabel.qml @@ -0,0 +1,56 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.4 +import QtQuick.Controls.Material 2.0 +import QtQuick.Layouts 1.3 +import CustomQmlTypes 1.0 +import "../widgets" as Widgets +import "../theme" + +Item { + //anchors.right: parent.right + anchors.left: parent.left + + property string text + + property alias color: label.color + property alias pixelSize: label.font.pixelSize + property alias weight: label.font.weight + property alias strikeout: label.font.strikeout + + height: textMetric.height + width: textMetric.width + 10 + anchors.leftMargin: 10 + + Label { + id: label + textFormat: Text.PlainText + + elide: Text.ElideRight + text: textMetric.text + } + + TextMetrics { + id: textMetric + text: text + font: label.font + } + + /*onWidthChanged: { + setTextResize() + }*/ + + onTextChanged: { + setTextResize() + } + + function setTextResize() { + textMetric.text = text + var i = 2 + // - 30 for padding + while (textMetric.width > parent.width - (30 * gcd.themeScale) && parent.width > 50) { + textMetric.text = text.slice(0, text.length - (i * 3)) + "..." + i++ + } + } + +} \ No newline at end of file diff --git a/qml/widgets/Message.qml b/qml/widgets/Message.qml index ee97fd07..2ad33ef2 100644 --- a/qml/widgets/Message.qml +++ b/qml/widgets/Message.qml @@ -45,7 +45,8 @@ Item { Portrait { id: imgProfile anchors.left: parent.left - handle: root.from + // TODO: currently unused? + //handle: root.from visible: !fromMe //showStatus: false //highlight: ima.containsMouse diff --git a/qml/widgets/MyProfile.qml b/qml/widgets/MyProfile.qml index 087959fe..0fea4fc9 100644 --- a/qml/widgets/MyProfile.qml +++ b/qml/widgets/MyProfile.qml @@ -9,224 +9,149 @@ import QtQuick.Controls 1.4 import "." as Widgets import "../styles" +import "../theme" -ColumnLayout { +Item { id: root anchors.fill: parent width: parent.width + height: profile.height + searchAddText.height + 10 + implicitHeight: profile.height + searchAddText.height + 10 + property string image property string nick property string onion + property string tag + property bool dualPane: false + + property real logscale: 4 * Math.log10(gcd.themeScale + 1) + + onDualPaneChanged: { realignProfile() } + + function realignProfile() { + if (dualPane) { + profile.height = 78 * logscale + + portrait.baseWidth = 78 * logscale + portrait.height = 78 * logscale + + portrait.anchors.horizontalCenter = undefined + portrait.anchors.left = profile.left + portrait.anchors.leftMargin = 25 * logscale + + nameRect.anchors.horizontalCenter = undefined + nameRect.anchors.left = portrait.right + + nameRect.anchors.top = undefined + nameRect.anchors.verticalCenter = portrait.verticalCenter + } else { + profile.height = (150 * logscale) + + portrait.baseWidth = 100 * logscale + portrait.height = 100 * logscale + + portrait.anchors.left = undefined + portrait.anchors.leftMargin = undefined + portrait.anchors.horizontalCenter = profile.horizontalCenter + + nameRect.anchors.left = undefined + nameRect.anchors.horizontalCenter = profile.horizontalCenter + + nameRect.anchors.verticalCenter = undefined + nameRect.anchors.top = portrait.bottom + } + } + + Component.onCompleted: { realignProfile() } - Widgets.Button {// 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() { - gcd.selectedProfile = "none" - gcd.reloadProfileList() - parentStack.pane = parentStack.managementPane - theStack.pane = theStack.emptyPane - } - } - Item{ height: 6 } + Rectangle { - Item { // PROFILE IMAGE - id: imgProfile - implicitWidth: 96 - implicitHeight: 96 - anchors.horizontalCenter: parent.horizontalCenter + anchors.left: parent.left + anchors.right: parent.right + width: parent.width + id: profile - Rectangle { // WHITE CIRCLE BORDER - width: 96 - height: 96 - color: "#FFFFFF" - radius: width / 2 + Portrait { + id: portrait - Image { // ACTUAL IMAGE - id: imgProfileImg - source: gcd.assetPath + root.image - anchors.fill: parent - fillMode: Image.PreserveAspectFit - visible: false - } + source: root.image - Image { // INNER CIRCLE MASK - id: mask - fillMode: Image.PreserveAspectFit - visible: false - source: "qrc:/qml/images/extra/clipcircle.png" - } + badgeColor: Theme.portraitProfileBadgeColor + portraitBorderColor: Theme.portraitOnlineBorderColor + portraitColor: Theme.portraitOnlineBackgroundColor - OpacityMask { // WE PUT IT ALL TOGETHER ANNND - anchors.fill: imgProfileImg - source: imgProfileImg - maskSource: mask - } + badgeContent: Image {// Profle Type + id: profiletype + source: tag == "v1-userPassword" ? gcd.assetPath + "/fontawesome/solid/lock.svg" : gcd.assetPath + "/fontawesome/solid/lock-open.svg" + height: Theme.badgeTextSize * gcd.themeScale + width: height + } + } + + Rectangle { + id: nameRect + + height: name.height + width: name.width + addBtn.width + + EllipsisLabel { + id: name + + anchors.right: undefined + anchors.left: undefined + + color: Theme.portraitOnlineTextColor + pixelSize: Theme.usernameSize * gcd.themeScale + weight: Font.Bold + text: nick + } + + Widgets.Button { // Add Button + id: addBtn + + anchors.left: name.right //name.left + name.textWidth + anchors.top: name.top + + icon: "solid/plus" + + height: name.height + width: height + radius: width * 0.3 + onClicked: { + + } + } + } + + } + + // TODO Remove for new global topbar + Widgets.Button {// BACK BUTTON + id: btnBack + icon: "solid/arrow-circle-left" + anchors.left: parent.left + anchors.leftMargin: 2 + anchors.top: parent.top + anchors.topMargin: 2 + onClicked: function() { + gcd.selectedProfile = "none" + gcd.reloadProfileList() + parentStack.pane = parentStack.managementPane + theStack.pane = theStack.emptyPane + } + } - Rectangle { // TOR STATUS INDICATOR - color: "#FFFFFF" - width: 12 - height: 12 - radius: 3 - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.margins: 8 - - - Rectangle { //0: no tor, 1: progress 0%, 2: progress 1-99%, 3: DONE - id: rectTorStatus - color: code == 3 ? "green": code == 2 ? "yellow" : code == 1 ? "orange" : "red" - width: 8 - height: 8 - radius: 2 - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.margins: 2 - - property int code - property string message - property bool hovered - - - MouseArea { - anchors.fill: parent - hoverEnabled: true - - onEntered: rectTorStatus.hovered = true - - onExited: rectTorStatus.hovered = false - } - - - ToolTip.visible: hovered - ToolTip.delay: 400 - ToolTip.timeout: 5000 - ToolTip.text: message - } - } - } - } - - InplaceEditText { // USER NICKNAME - id: lblNick - anchors.horizontalCenter: parent.horizontalCenter - Layout.minimumWidth: parent.width - Layout.alignment: Qt.AlignHCenter - - onUpdated: { - gcd.updateNick(onion, lblNick.text) - } - } - - /* - Text { - id: txtNick - fontSizeMode: Text.HorizontalFit - minimumPixelSize: 10 - font.pixelSize: 20 - width: 195 - anchors.horizontalCenter: parent.horizontalCenter - text: cbNick.editText - - MouseArea { - anchors.fill: parent - onClicked: { - parent.visible = false - cbNick.visible = true - } - } - } - - ComboBox { // USER NICKNAME - id: cbNick - anchors.horizontalCenter: parent.horizontalCenter - popup.font.pixelSize: 12 - width: 200 - font.pixelSize: 20 - model: ["erinn", "erinn (open privacy)", "supergirl", "add new profile..."] - visible: false - - onCurrentTextChanged: { - visible = false - txtNick.visible = true - } - } - */ - - Text { // ONION ADDRESS - id: lblOnion - fontSizeMode: Text.HorizontalFit - minimumPointSize: 2 - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - text: onion - } - - Row { // TOOLS FOR EDITING PROFILE - anchors.horizontalCenter: parent.horizontalCenter - spacing: gcd.themeScale * 2 - - - TextEdit { // USED TO POWER THE COPY/PASTE BUTTON - id: txtHidden - visible: false - } - - Widgets.Button { // COPY ONION ADDRESS BUTTON - icon: "regular/clipboard" - //: Button for copying profile onion address to clipboard - text: qsTr("copy-btn") - - onClicked: { - //: Copied to clipboard - gcd.popup(qsTr("copied-clipboard-notification")) - txtHidden.text = nick.replace(" ", "~") + "~" + onion - txtHidden.selectAll() - txtHidden.copy() - } - } - - Widgets.Button { // SETTINGS BUTTON - icon: "solid/cog" - - onClicked: theStack.pane = theStack.settingsPane - } - - Widgets.Button { // SIGN OUT BUTTON - icon: "solid/sign-out-alt" - - onClicked: { - gcd.popup("not yet implemented, sorry :(") - } - } - } - - Row { - anchors.horizontalCenter: parent.horizontalCenter - spacing: gcd.themeScale * 2 - - - Widgets.Button { // CREATE GROUP BUTTON - icon: "regular/clipboard" - //: create new group button - text: qsTr("new-group-btn") - - onClicked: theStack.pane = theStack.addGroupPane - } - } - - TextField { + TextField { + id: searchAddText + anchors.top: profile.bottom anchors.horizontalCenter: parent.horizontalCenter - style: CwtchTextFieldStyle{ width: 400 } + + style: CwtchTextFieldStyle{ } + width: parent.width - 30 //: ex: "... paste an address here to add a contact ..." placeholderText: qsTr("paste-address-to-add-contact") horizontalAlignment: TextInput.AlignHCenter @@ -242,16 +167,16 @@ ColumnLayout { Connections { target: gcd - onUpdateMyProfile: function(_nick, _onion, _image) { + onUpdateMyProfile: function(_nick, _onion, _image, _tag) { nick = _nick - lblNick.text = _nick onion = _onion image = _image + tag = _tag } - onTorStatus: function(code, str) { + /*onTorStatus: function(code, str) { rectTorStatus.code = code rectTorStatus.message = str - } + }*/ } } diff --git a/qml/widgets/Portrait.qml b/qml/widgets/Portrait.qml index d405c032..ce9526cb 100644 --- a/qml/widgets/Portrait.qml +++ b/qml/widgets/Portrait.qml @@ -11,20 +11,23 @@ Item { implicitWidth: baseWidth implicitHeight: baseWidth - property string handle property string source property alias badgeColor: badge.color property real logscale: 4 * Math.log10(gcd.themeScale + 1) - property int baseWidth: parent.height + property int baseWidth: 78 * logscale + height: 78 * logscale property alias portraitBorderColor: mainImage.color property alias portraitColor: imageInner.color property alias badgeVisible: badge.visible property alias badgeContent: badge.content + Rectangle { id: mainImage + //anchors.leftMargin: baseWidth * 0.1 + anchors.horizontalCenter: parent.horizontalCenter width: baseWidth * 0.8 height: width anchors.verticalCenter: parent.verticalCenter diff --git a/qml/widgets/PortraitRow.qml b/qml/widgets/PortraitRow.qml index d506ce58..953edfc8 100644 --- a/qml/widgets/PortraitRow.qml +++ b/qml/widgets/PortraitRow.qml @@ -18,7 +18,7 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY implicitHeight: 78 * logscale + 3 //height property real logscale: 4 * Math.log10(gcd.themeScale + 1) - property string displayName //: nameTxtmetric.text + property string displayName property alias image: portrait.source property string handle property bool isActive @@ -30,9 +30,12 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY property alias portraitColor: portrait.portraitColor property alias nameColor: cn.color property alias onionColor: onion.color + property alias onionVisible: onion.visible property alias badgeVisible: portrait.badgeVisible property alias badgeContent: portrait.badgeContent + property alias hoverEnabled: buttonMA.hoverEnabled + property alias content: extraMeta.children // TODO: should be in ContactRow property bool blocked @@ -55,64 +58,42 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY } ColumnLayout { + id: portraitMeta anchors.left: portrait.right + anchors.right: parent.right anchors.leftMargin: 4 * logscale anchors.verticalCenter: parent.verticalCenter - Label { // CONTACT NAME + spacing: 2 * gcd.themeScale + + + EllipsisLabel { // CONTACT NAME id: cn - leftPadding: 10 - rightPadding: 10 - //wrapMode: Text.WordWrap - font.pixelSize: Theme.usernameSize * gcd.themeScale - font.weight: Font.Bold - font.strikeout: blocked - elide: Text.ElideRight - text: nameTxtmetric.text - } - - TextMetrics { - id: nameTxtmetric + pixelSize: Theme.usernameSize * gcd.themeScale + weight: Font.Bold + strikeout: blocked text: displayName - font: cn.font } - - Label { // Onion + EllipsisLabel { // Onion id: onion - text: onionTxtmetric.text - leftPadding: 10 - rightPadding: 10 - font.pixelSize: Theme.secondaryTextSize * gcd.themeScale - font.strikeout: blocked - textFormat: Text.PlainText - elide: Text.ElideRight + text: handle + pixelSize: Theme.secondaryTextSize * gcd.themeScale + strikeout: blocked } - TextMetrics { - id: onionTxtmetric - text: handle - font: onion.font - } + onWidthChanged: { + cn.setTextResize() + onion.setTextResize() + } } - onWidthChanged: { - nameTxtmetric.text = displayName - onionTxtmetric.text = handle - var i = 2 - var maxWidth = Math.max(200, width - portrait.width - (50 * logscale)) - - while (nameTxtmetric.width > maxWidth) { - nameTxtmetric.text = displayName.slice(0, displayName.length - (i * 3)) + "..." - i++ - } - i = 2 - while (onionTxtmetric.width > maxWidth) { - onionTxtmetric.text = handle.slice(0, handle.length - (i * 3)) + "..." - i++ - } + Column { + id: extraMeta + anchors.left: portraitMeta.right + anchors.verticalCenter: parent.verticalCenter } } @@ -147,7 +128,7 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY onUpdateContactDisplayName: function(_handle, _displayName) { if (handle == _handle) { - displayName = _displayName + (_blocked == true ? " (blocked)" : "") + displayName = _displayName + (blocked == true ? " (blocked)" : "") } }