Redesign my profile widget to match design. It dynamically resizes and
the build was successful Details

shapes depending if the side pane is open. Fixes for ellipse label and
portrait size, positioning, margins
This commit is contained in:
Dan Ballard 2020-04-08 15:30:10 -07:00
parent 2f251aa4da
commit 5343348aa9
12 changed files with 240 additions and 270 deletions

View File

@ -108,9 +108,11 @@ func PeerHandler(onion string, uiManager ui.Manager, subscribed chan bool) {
if exists && scope == attr.PublicScope { if exists && scope == attr.PublicScope {
switch path { switch path {
case constants.Name: case constants.Name:
uiManager.UpdateContactDisplayName(onion, val) peer.SetContactAttribute(onion, attr.GetPublicScope(constants.Name), val)
uiManager.UpdateContactDisplayName(onion)
case constants.Picture: case constants.Picture:
uiManager.UpdateContactPicture(onion, val) peer.SetContactAttribute(onion, attr.GetPublicScope(constants.Picture), val)
uiManager.UpdateContactPicture(onion)
} }
} }

View File

@ -68,8 +68,8 @@ type GrandCentralDispatcher struct {
_ func(loading bool) `signal:"SetLoadingState"` _ func(loading bool) `signal:"SetLoadingState"`
// profile-area stuff // profile-area stuff
_ func(name, onion, image string) `signal:"UpdateMyProfile"` _ func(name, onion, image, tag string) `signal:"UpdateMyProfile"`
_ func(status int, str string) `signal:"TorStatus"` _ func(status int, str string) `signal:"TorStatus"`
// settings helpers // settings helpers
_ func(str string) `signal:"InvokePopup"` _ func(str string) `signal:"InvokePopup"`
@ -605,7 +605,8 @@ func (this *GrandCentralDispatcher) loadProfile(onion string) {
pic = NewImage(RandomProfileImage(onion), TypeImageDistro) pic = NewImage(RandomProfileImage(onion), TypeImageDistro)
the.Peer.SetAttribute(attr.GetPublicScope(constants.Picture), ImageToString(pic)) 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() contacts := the.Peer.GetContacts()
for i := range contacts { for i := range contacts {

View File

@ -67,7 +67,7 @@ func getNick(id string) string {
return nick return nick
} else { } else {
nick, exists := the.Peer.GetContactAttribute(id, attr.GetLocalScope(constants.Name)) 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)) nick, exists = the.Peer.GetContactAttribute(id, attr.GetPeerScope(constants.Name))
if !exists { if !exists {
nick = id nick = id
@ -203,8 +203,8 @@ type Manager interface {
ReloadProfiles() ReloadProfiles()
UpdateContactDisplayName(handle string, name string) UpdateContactDisplayName(handle string)
UpdateContactPicture(handle string, picVal string) UpdateContactPicture(handle string)
UpdateContactStatus(handle string, status int, loading bool) UpdateContactStatus(handle string, status int, loading bool)
UpdateContactAttribute(handle, key, value string) 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 // 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.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 // 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() { this.gcd.DoIfProfile(this.profile, func() {
pic, err := StringToImage(picVal) this.gcd.UpdateContactPicture(handle, getProfilePic(handle))
if err == nil {
this.gcd.UpdateContactPicture(handle, getPicturePath(pic))
}
}) })
} }

View File

@ -28,6 +28,7 @@
<file>qml/widgets/InplaceEditText.qml</file> <file>qml/widgets/InplaceEditText.qml</file>
<file>qml/widgets/Message.qml</file> <file>qml/widgets/Message.qml</file>
<file>qml/widgets/ScalingLabel.qml</file> <file>qml/widgets/ScalingLabel.qml</file>
<file>qml/widgets/EllipsisLabel.qml</file>
<file>qml/widgets/MyProfile.qml</file> <file>qml/widgets/MyProfile.qml</file>
<file>qml/widgets/ProfileList.qml</file> <file>qml/widgets/ProfileList.qml</file>
<file>qml/widgets/RadioButton.qml</file> <file>qml/widgets/RadioButton.qml</file>

View File

@ -87,7 +87,7 @@ ApplicationWindow {
property alias pane: parentStack.currentIndex property alias pane: parentStack.currentIndex
Rectangle { // Splash pane Rectangle { // Splash pane
color: "#FFFFFF" color: Theme.backgroundMainColor
//Layout.fillHeight: true //Layout.fillHeight: true
//Layout.minimumWidth: Layout.maximumWidth //Layout.minimumWidth: Layout.maximumWidth
//Layout.minimumHeight: parent.height //Layout.minimumHeight: parent.height
@ -130,7 +130,7 @@ ApplicationWindow {
spacing: 0 spacing: 0
Rectangle { // THE LEFT PANE WITH TOOLS AND CONTACTS Rectangle { // THE LEFT PANE WITH TOOLS AND CONTACTS
color: "#D2C0DD" color: Theme.backgroundMainColor
Layout.fillHeight: true Layout.fillHeight: true
Layout.minimumWidth: Layout.maximumWidth Layout.minimumWidth: Layout.maximumWidth
Layout.maximumWidth: theStack.pane == theStack.emptyPane ? parent.width : Theme.sidePaneMinSize Layout.maximumWidth: theStack.pane == theStack.emptyPane ? parent.width : Theme.sidePaneMinSize
@ -139,6 +139,7 @@ ApplicationWindow {
ContactList{ ContactList{
anchors.fill: parent anchors.fill: parent
dualPane: theStack.pane != theStack.emptyPane || theStack.pane == undefined
} }
} }

View File

@ -5,7 +5,7 @@ TextFieldStyle {
id: root id: root
textColor: "black" textColor: "black"
font.pointSize: 10 * gcd.themeScale font.pointSize: 10 * gcd.themeScale
property int width: 100 property int width: parent.width
background: Rectangle { background: Rectangle {
radius: 2 radius: 2

View File

@ -7,6 +7,8 @@ import QtQuick.Layouts 1.3
ColumnLayout { ColumnLayout {
id: root id: root
property alias dualPane: myprof.dualPane
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@ -16,7 +18,7 @@ ColumnLayout {
} }
} }
MyProfile{ // CURRENT PROFILE INFO AND CONTROL BAR MyProfile { // CURRENT PROFILE INFO AND CONTROL BAR
id: myprof id: myprof
} }

View File

@ -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++
}
}
}

View File

@ -45,7 +45,8 @@ Item {
Portrait { Portrait {
id: imgProfile id: imgProfile
anchors.left: parent.left anchors.left: parent.left
handle: root.from // TODO: currently unused?
//handle: root.from
visible: !fromMe visible: !fromMe
//showStatus: false //showStatus: false
//highlight: ima.containsMouse //highlight: ima.containsMouse

View File

@ -9,224 +9,149 @@ import QtQuick.Controls 1.4
import "." as Widgets import "." as Widgets
import "../styles" import "../styles"
import "../theme"
ColumnLayout { Item {
id: root id: root
anchors.fill: parent anchors.fill: parent
width: parent.width width: parent.width
height: profile.height + searchAddText.height + 10
implicitHeight: profile.height + searchAddText.height + 10
property string image property string image
property string nick property string nick
property string onion 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 anchors.left: parent.left
id: imgProfile anchors.right: parent.right
implicitWidth: 96 width: parent.width
implicitHeight: 96 id: profile
anchors.horizontalCenter: parent.horizontalCenter
Rectangle { // WHITE CIRCLE BORDER Portrait {
width: 96 id: portrait
height: 96
color: "#FFFFFF"
radius: width / 2
Image { // ACTUAL IMAGE source: root.image
id: imgProfileImg
source: gcd.assetPath + root.image
anchors.fill: parent
fillMode: Image.PreserveAspectFit
visible: false
}
Image { // INNER CIRCLE MASK badgeColor: Theme.portraitProfileBadgeColor
id: mask portraitBorderColor: Theme.portraitOnlineBorderColor
fillMode: Image.PreserveAspectFit portraitColor: Theme.portraitOnlineBackgroundColor
visible: false
source: "qrc:/qml/images/extra/clipcircle.png"
}
OpacityMask { // WE PUT IT ALL TOGETHER ANNND badgeContent: Image {// Profle Type
anchors.fill: imgProfileImg id: profiletype
source: imgProfileImg source: tag == "v1-userPassword" ? gcd.assetPath + "/fontawesome/solid/lock.svg" : gcd.assetPath + "/fontawesome/solid/lock-open.svg"
maskSource: mask 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 TextField {
color: "#FFFFFF" id: searchAddText
width: 12 anchors.top: profile.bottom
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 {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
style: CwtchTextFieldStyle{ width: 400 }
style: CwtchTextFieldStyle{ }
width: parent.width - 30
//: ex: "... paste an address here to add a contact ..." //: ex: "... paste an address here to add a contact ..."
placeholderText: qsTr("paste-address-to-add-contact") placeholderText: qsTr("paste-address-to-add-contact")
horizontalAlignment: TextInput.AlignHCenter horizontalAlignment: TextInput.AlignHCenter
@ -242,16 +167,16 @@ ColumnLayout {
Connections { Connections {
target: gcd target: gcd
onUpdateMyProfile: function(_nick, _onion, _image) { onUpdateMyProfile: function(_nick, _onion, _image, _tag) {
nick = _nick nick = _nick
lblNick.text = _nick
onion = _onion onion = _onion
image = _image image = _image
tag = _tag
} }
onTorStatus: function(code, str) { /*onTorStatus: function(code, str) {
rectTorStatus.code = code rectTorStatus.code = code
rectTorStatus.message = str rectTorStatus.message = str
} }*/
} }
} }

View File

@ -11,20 +11,23 @@ Item {
implicitWidth: baseWidth implicitWidth: baseWidth
implicitHeight: baseWidth implicitHeight: baseWidth
property string handle
property string source property string source
property alias badgeColor: badge.color property alias badgeColor: badge.color
property real logscale: 4 * Math.log10(gcd.themeScale + 1) 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 portraitBorderColor: mainImage.color
property alias portraitColor: imageInner.color property alias portraitColor: imageInner.color
property alias badgeVisible: badge.visible property alias badgeVisible: badge.visible
property alias badgeContent: badge.content property alias badgeContent: badge.content
Rectangle { Rectangle {
id: mainImage id: mainImage
//anchors.leftMargin: baseWidth * 0.1
anchors.horizontalCenter: parent.horizontalCenter
width: baseWidth * 0.8 width: baseWidth * 0.8
height: width height: width
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter

View File

@ -18,7 +18,7 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY
implicitHeight: 78 * logscale + 3 //height implicitHeight: 78 * logscale + 3 //height
property real logscale: 4 * Math.log10(gcd.themeScale + 1) property real logscale: 4 * Math.log10(gcd.themeScale + 1)
property string displayName //: nameTxtmetric.text property string displayName
property alias image: portrait.source property alias image: portrait.source
property string handle property string handle
property bool isActive property bool isActive
@ -30,9 +30,12 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY
property alias portraitColor: portrait.portraitColor property alias portraitColor: portrait.portraitColor
property alias nameColor: cn.color property alias nameColor: cn.color
property alias onionColor: onion.color property alias onionColor: onion.color
property alias onionVisible: onion.visible
property alias badgeVisible: portrait.badgeVisible property alias badgeVisible: portrait.badgeVisible
property alias badgeContent: portrait.badgeContent property alias badgeContent: portrait.badgeContent
property alias hoverEnabled: buttonMA.hoverEnabled
property alias content: extraMeta.children
// TODO: should be in ContactRow // TODO: should be in ContactRow
property bool blocked property bool blocked
@ -55,64 +58,42 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY
} }
ColumnLayout { ColumnLayout {
id: portraitMeta
anchors.left: portrait.right anchors.left: portrait.right
anchors.right: parent.right
anchors.leftMargin: 4 * logscale anchors.leftMargin: 4 * logscale
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Label { // CONTACT NAME spacing: 2 * gcd.themeScale
EllipsisLabel { // CONTACT NAME
id: cn id: cn
leftPadding: 10 pixelSize: Theme.usernameSize * gcd.themeScale
rightPadding: 10 weight: Font.Bold
//wrapMode: Text.WordWrap strikeout: blocked
font.pixelSize: Theme.usernameSize * gcd.themeScale
font.weight: Font.Bold
font.strikeout: blocked
elide: Text.ElideRight
text: nameTxtmetric.text
}
TextMetrics {
id: nameTxtmetric
text: displayName text: displayName
font: cn.font
} }
EllipsisLabel { // Onion
Label { // Onion
id: onion id: onion
text: onionTxtmetric.text text: handle
leftPadding: 10 pixelSize: Theme.secondaryTextSize * gcd.themeScale
rightPadding: 10 strikeout: blocked
font.pixelSize: Theme.secondaryTextSize * gcd.themeScale
font.strikeout: blocked
textFormat: Text.PlainText
elide: Text.ElideRight
} }
TextMetrics { onWidthChanged: {
id: onionTxtmetric cn.setTextResize()
text: handle onion.setTextResize()
font: onion.font }
}
} }
onWidthChanged: { Column {
nameTxtmetric.text = displayName id: extraMeta
onionTxtmetric.text = handle anchors.left: portraitMeta.right
var i = 2 anchors.verticalCenter: parent.verticalCenter
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++
}
} }
} }
@ -147,7 +128,7 @@ Item { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY
onUpdateContactDisplayName: function(_handle, _displayName) { onUpdateContactDisplayName: function(_handle, _displayName) {
if (handle == _handle) { if (handle == _handle) {
displayName = _displayName + (_blocked == true ? " (blocked)" : "") displayName = _displayName + (blocked == true ? " (blocked)" : "")
} }
} }