feat(profile): re-design Contacts setting view

Fixes #5004
This commit is contained in:
Jonathan Rainville 2022-03-15 11:55:18 -04:00
parent 317ac06e43
commit 738a3ba93e
9 changed files with 200 additions and 281 deletions

@ -1 +1 @@
Subproject commit 381150a7b55d53b3cb59d9ee2c4f50985488c708 Subproject commit 189f66225c1120b0418f9865d9ae36f33e272709

View File

@ -2,18 +2,22 @@ import QtQuick 2.13
import QtQuick.Controls 2.13 import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13 import QtQuick.Layouts 1.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Popups 0.1 import StatusQ.Popups 0.1
import utils 1.0 import utils 1.0
import shared 1.0
import shared.panels 1.0
import shared.status 1.0
import shared.views 1.0
import shared.controls.chat 1.0
StatusListItem { StatusListItem {
id: container id: container
anchors.right: parent.right width: parent.width
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.rightMargin: -Style.current.padding
visible: container.isContact && (searchStr == "" || container.name.includes(searchStr)) visible: container.isContact && (searchStr == "" || container.name.includes(searchStr))
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0
title: container.name title: container.name
@ -28,13 +32,22 @@ StatusListItem {
property bool isBlocked: false property bool isBlocked: false
property string searchStr: "" property string searchStr: ""
property bool showSendMessageButton: false
signal openProfilePopup(string publicKey) signal openProfilePopup(string publicKey)
signal openChangeNicknamePopup(string publicKey)
signal sendMessageActionTriggered(string publicKey) signal sendMessageActionTriggered(string publicKey)
signal unblockContactActionTriggered(string publicKey)
signal blockContactActionTriggered(string publicKey)
signal removeContactActionTriggered(string publicKey)
components: [ components: [
StatusFlatRoundButton {
visible: showSendMessageButton
id: sendMessageBtn
width: visible ? 32 : 0
height: visible ? 32 : 0
icon.name: "chat"
type: StatusFlatRoundButton.Type.Secondary
onClicked: container.sendMessageActionTriggered(container.publicKey)
},
StatusFlatRoundButton { StatusFlatRoundButton {
id: menuButton id: menuButton
width: 32 width: 32
@ -53,9 +66,26 @@ StatusListItem {
menuButton.highlighted = false menuButton.highlighted = false
} }
ProfileHeader {
width: parent.width
displayName: container.name
pubkey: container.publicKey
icon: container.icon
isIdenticon: container.isIdenticon
}
Item {
height: 8
}
Separator {}
StatusMenuItem { StatusMenuItem {
text: qsTr("View Profile") text: qsTr("View Profile")
icon.name: "profile" icon.name: "profile"
icon.width: 16
icon.height: 16
onTriggered: { onTriggered: {
container.openProfilePopup(container.publicKey) container.openProfilePopup(container.publicKey)
menuButton.highlighted = false menuButton.highlighted = false
@ -65,6 +95,8 @@ StatusListItem {
StatusMenuItem { StatusMenuItem {
text: qsTr("Send message") text: qsTr("Send message")
icon.name: "chat" icon.name: "chat"
icon.width: 16
icon.height: 16
onTriggered: { onTriggered: {
container.sendMessageActionTriggered(container.publicKey) container.sendMessageActionTriggered(container.publicKey)
menuButton.highlighted = false menuButton.highlighted = false
@ -73,37 +105,15 @@ StatusListItem {
} }
StatusMenuItem { StatusMenuItem {
text: qsTr("Block User") text: qsTr("Rename")
icon.name: "cancel" icon.name: "edit_pencil"
icon.width: 16
icon.height: 16
onTriggered: {
container.openChangeNicknamePopup(container.publicKey)
menuButton.highlighted = false
}
enabled: !container.isBlocked enabled: !container.isBlocked
type: StatusMenuItem.Type.Danger
onTriggered: {
container.blockContactActionTriggered(container.publicKey)
menuButton.highlighted = false
}
}
StatusMenuItem {
text: qsTr("Remove contact")
icon.name: "remove-contact"
enabled: container.isContact
type: StatusMenuItem.Type.Danger
onTriggered: {
container.removeContactActionTriggered(container.publicKey)
menuButton.highlighted = false
}
}
StatusMenuItem {
text: qsTr("Unblock user")
icon.name: "cancel"
enabled: container.isBlocked
type: StatusMenuItem.Type.Danger
onTriggered: {
container.unblockContactActionTriggered(container.publicKey)
menuButton.highlighted = false
contactContextMenu.close()
}
} }
} }
} }

View File

@ -18,13 +18,12 @@ ListView {
property string lowerCaseSearchString: searchString.toLowerCase() property string lowerCaseSearchString: searchString.toLowerCase()
property string contactToRemove: "" property string contactToRemove: ""
property bool hideBlocked: false property bool hideBlocked: false
property bool showSendMessageButton
signal contactClicked(var contact) signal contactClicked(var contact)
signal openProfilePopup(var contact) signal openProfilePopup(var contact)
signal sendMessageActionTriggered(var contact) signal sendMessageActionTriggered(var contact)
signal unblockContactActionTriggered(var contact) signal openChangeNicknamePopup(var contact)
signal blockContactActionTriggered(var contact)
signal removeContactActionTriggered(var contact)
width: parent.width width: parent.width
@ -38,13 +37,12 @@ ListView {
isIdenticon: model.isIdenticon isIdenticon: model.isIdenticon
isContact: model.isContact isContact: model.isContact
isBlocked: model.isBlocked isBlocked: model.isBlocked
showSendMessageButton: contactList.showSendMessageButton
onClicked: contactList.contactClicked(model) onClicked: contactList.contactClicked(model)
onOpenProfilePopup: contactList.openProfilePopup(model) onOpenProfilePopup: contactList.openProfilePopup(model)
onSendMessageActionTriggered: contactList.sendMessageActionTriggered(model) onSendMessageActionTriggered: contactList.sendMessageActionTriggered(model)
onUnblockContactActionTriggered: contactList.unblockContactActionTriggered(model) onOpenChangeNicknamePopup: contactList.openChangeNicknamePopup(model)
onBlockContactActionTriggered: contactList.blockContactActionTriggered(model)
onRemoveContactActionTriggered: contactList.removeContactActionTriggered(model)
visible: { visible: {
if (hideBlocked && model.isBlocked) { if (hideBlocked && model.isBlocked) {

View File

@ -13,6 +13,11 @@ QtObject {
property var myContactsModel: contactsModule.myContactsModel property var myContactsModel: contactsModule.myContactsModel
property var blockedContactsModel: contactsModule.blockedContactsModel property var blockedContactsModel: contactsModule.blockedContactsModel
// TODO move contact requests back to the contacts module since we need them in the Profile
// also, having them in the chat section creates some waste, since no community has it
property var chatSectionModule: mainModule.getChatSectionModule()
property var contactRequestsModel: chatSectionModule.contactRequestsModel
function resolveENS(value) { function resolveENS(value) {
root.mainModuleInst.resolveENS(value, "") root.mainModuleInst.resolveENS(value, "")
} }
@ -46,4 +51,20 @@ QtObject {
function changeContactNickname(pubKey, nickname) { function changeContactNickname(pubKey, nickname) {
root.contactsModule.changeContactNickname(pubKey, nickname) root.contactsModule.changeContactNickname(pubKey, nickname)
} }
function acceptContactRequest(pubKey) {
chatSectionModule.acceptContactRequest(pubKey)
}
function acceptAllContactRequests() {
chatSectionModule.acceptAllContactRequests()
}
function rejectContactRequest(pubKey) {
chatSectionModule.rejectContactRequest(pubKey)
}
function rejectAllContactRequests() {
chatSectionModule.rejectAllContactRequests()
}
} }

View File

@ -17,6 +17,8 @@ import shared.controls 1.0
import "../stores" import "../stores"
import "../panels" import "../panels"
import "../popups" import "../popups"
// TODO remove this import when the ContactRequestPanel is moved to the the Profile completely
import "../../Chat/panels"
Item { Item {
id: root id: root
@ -65,115 +67,138 @@ Item {
anchors.top: titleText.bottom anchors.top: titleText.bottom
anchors.topMargin: 32 anchors.topMargin: 32
fontPixelSize: 15 fontPixelSize: 15
placeholderText: qsTr("Search by a display name or chat key")
} }
Item { TabBar {
id: addNewContact id: contactsTabBar
anchors.top: searchBox.bottom
anchors.topMargin: Style.current.bigPadding
width: addButton.width + usernameText.width + Style.current.padding
height: addButton.height
StatusRoundButton {
id: addButton
width: 40
height: 40
icon.name: "add"
type: StatusRoundButton.Type.Secondary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
id: usernameText
//% "Add new contact"
text: qsTrId("add-new-contact")
color: Style.current.blue
anchors.left: addButton.right
anchors.leftMargin: Style.current.padding
anchors.verticalCenter: addButton.verticalCenter
font.pixelSize: 15
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
addContactModal.open()
}
}
}
Item {
id: blockedContactsButton
anchors.top: addNewContact.bottom
anchors.topMargin: Style.current.bigPadding
width: parent.width width: parent.width
visible: root.contactsStore.blockedContactsModel.count > 0 anchors.top: searchBox.bottom
height: 64 anchors.topMargin: Style.current.padding
height: contactsBtn.height
StatusRoundButton { background: Rectangle {
id: blockButton color: Style.current.transparent
width: 40
height: 40
anchors.verticalCenter: parent.verticalCenter
icon.name: "cancel"
icon.color: Theme.palette.primaryColor1
} }
StatusTabButton {
StyledText { id: contactsBtn
id: blockButtonLabel anchors.top: parent.top
//% "Blocked contacts" btnText: qsTr("Contacts")
text: qsTrId("blocked-contacts")
color: Style.current.blue
anchors.left: blockButton.right
anchors.leftMargin: Style.current.padding
anchors.verticalCenter: blockButton.verticalCenter
font.pixelSize: 15
} }
StatusTabButton {
StyledText { id: pendingRequestsBtn
id: numberOfBlockedContacts enabled: root.contactsStore.contactRequestsModel.count > 0
text: root.contactsStore.blockedContactsModel.count anchors.left: contactsBtn.right
color: Style.current.darkGrey anchors.top: parent.top
anchors.right: parent.right anchors.leftMargin: Style.current.bigPadding
anchors.rightMargin: Style.current.padding btnText: qsTr("Pending Requests")
anchors.verticalCenter: blockButton.verticalCenter
font.pixelSize: 15
} }
StatusTabButton {
MouseArea { id: blockedBtn
anchors.fill: parent enabled: root.contactsStore.blockedContactsModel.count > 0
cursorShape: Qt.PointingHandCursor anchors.left: pendingRequestsBtn.right
onClicked: { anchors.leftMargin: Style.current.bigPadding
blockedContactsModal.open() anchors.top: parent.top
} btnText: qsTr("Blocked")
} }
} }
ModalPopup { StackLayout {
id: blockedContactsModal id: stackLayout
//% "Blocked contacts" width: parent.width
title: qsTrId("blocked-contacts") anchors.bottom: parent.bottom
anchors.top: contactsTabBar.bottom
anchors.topMargin: Style.current.padding
currentIndex: contactsTabBar.currentIndex
// CONTACTS
Item {
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
height: parent.height
ContactsListPanel {
id: contactListView
anchors.fill: parent
contactsModel: root.contactsStore.myContactsModel
clip: true
hideBlocked: true
searchString: searchBox.text
showSendMessageButton: true
onContactClicked: {
root.contactsStore.joinPrivateChat(contact.pubKey)
}
onOpenProfilePopup: {
Global.openProfilePopup(contact.pubKey)
}
onSendMessageActionTriggered: {
root.contactsStore.joinPrivateChat(contact.pubKey)
}
onOpenChangeNicknamePopup: {
Global.openProfilePopup(contact.pubKey, null, true)
}
}
NoFriendsRectangle {
visible: root.contactsStore.myContactsModel.count === 0
//% "You dont have any contacts yet"
text: qsTrId("you-don-t-have-any-contacts-yet")
width: parent.width
anchors.verticalCenter: parent.verticalCenter
}
}
// PENDING REQUESTS
Item {
ListView {
id: contactList
anchors.fill: parent
anchors.leftMargin: -Style.current.halfPadding
anchors.rightMargin: -Style.current.halfPadding
model: root.contactsStore.contactRequestsModel
clip: true
delegate: ContactRequestPanel {
contactName: model.name
contactIcon: model.icon
contactIconIsIdenticon: model.isIdenticon
onOpenProfilePopup: {
Global.openProfilePopup(model.pubKey)
}
onBlockContactActionTriggered: {
blockContactConfirmationDialog.contactName = model.name
blockContactConfirmationDialog.contactAddress = model.pubKey
blockContactConfirmationDialog.open()
}
onAcceptClicked: {
root.contactsStore.acceptContactRequest(model.pubKey)
}
onDeclineClicked: {
root.contactsStore.rejectContactRequest(model.pubKey)
}
}
}
}
// BLOCKED
ContactsListPanel { ContactsListPanel {
anchors.top: parent.top anchors.left: parent.left
anchors.bottom: parent.bottom anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
height: parent.height
clip: true clip: true
contactsModel: root.contactsStore.blockedContactsModel contactsModel: root.contactsStore.blockedContactsModel
onOpenProfilePopup: { onOpenProfilePopup: {
Global.openProfilePopup(contact.pubKey) Global.openProfilePopup(contact.pubKey)
} }
onRemoveContactActionTriggered: {
removeContactConfirmationDialog.value = contact.pubKey
removeContactConfirmationDialog.open()
}
onUnblockContactActionTriggered: {
root.contactsStore.unblockContact(contact.pubKey)
}
} }
} }
@ -184,146 +209,6 @@ Item {
height: 12 height: 12
} }
} }
ModalPopup {
id: addContactModal
//% "Add contact"
title: qsTrId("add-contact")
property string validationError: ""
function validate(value) {
if (!Utils.isChatKey(value) && !Utils.isValidETHNamePrefix(value)) {
addContactModal.validationError = qsTr("Enter a valid chat key or ENS username");
} else if (root.contactsStore.myPublicKey === value) {
//% "You can't add yourself"
addContactModal.validationError = qsTrId("you-can-t-add-yourself");
} else {
addContactModal.validationError = "";
}
return addContactModal.validationError === "";
}
property var lookupContact: Backpressure.debounce(addContactSearchInput, 400, function (value) {
root.isPending = true
searchResults.showProfileNotFoundMessage = false
root.contactsStore.resolveENS(value)
})
onOpened: {
addContactSearchInput.text = ""
searchResults.reset()
addContactSearchInput.forceActiveFocus()
}
Input {
id: addContactSearchInput
//% "Enter ENS username or chat key"
placeholderText: qsTrId("enter-contact-code")
customHeight: 44
fontPixelSize: 15
onTextEdited: {
if (addContactSearchInput.text === "") {
searchResults.reset();
return;
}
if (!addContactModal.validate(addContactSearchInput.text)) {
searchResults.reset();
root.isPending = false;
return;
}
Qt.callLater(addContactModal.lookupContact, addContactSearchInput.text);
}
Connections {
target: root.contactsStore.mainModuleInst
onResolvedENS: {
if (resolvedPubKey === "") {
searchResults.pubKey = ""
searchResults.showProfileNotFoundMessage = true
root.isPending = false
return
}
searchResults.username = Utils.isChatKey(addContactSearchInput.text) ? root.contactsStore.generateAlias(resolvedPubKey) : Utils.addStatusEns(addContactSearchInput.text.trim())
searchResults.userAlias = Utils.compactAddress(resolvedPubKey, 4)
searchResults.pubKey = resolvedPubKey
searchResults.showProfileNotFoundMessage = false
root.isPending = false
}
}
}
StyledText {
id: validationErrorMessage
text: addContactModal.validationError
visible: addContactModal.validationError !== ""
font.pixelSize: 13
color: Style.current.danger
anchors.top: addContactSearchInput.bottom
anchors.topMargin: Style.current.smallPadding
anchors.horizontalCenter: parent.horizontalCenter
}
SearchResults {
id: searchResults
anchors.top: addContactSearchInput.bottom
anchors.topMargin: Style.current.xlPadding
loading: root.isPending
resultClickable: false
onAddToContactsButtonClicked: root.contactsStore.addContact(pubKey)
}
}
ContactsListPanel {
id: contactListView
anchors.top: blockedContactsButton.visible ? blockedContactsButton.bottom : addNewContact.bottom
anchors.topMargin: Style.current.bigPadding
anchors.bottom: parent.bottom
contactsModel: root.contactsStore.myContactsModel
clip: true
hideBlocked: true
searchString: searchBox.text
// To show the correct status (added/not added)in the addContactModal.
onCountChanged: searchResults.isAddedContact = searchResults.isContactAdded()
onContactClicked: {
root.contactsStore.joinPrivateChat(contact.pubKey)
}
onOpenProfilePopup: {
Global.openProfilePopup(contact.pubKey)
}
onSendMessageActionTriggered: {
root.contactsStore.joinPrivateChat(contact.pubKey)
}
onBlockContactActionTriggered: {
blockContactConfirmationDialog.contactName = contact.name
blockContactConfirmationDialog.contactAddress = contact.pubKey
blockContactConfirmationDialog.open()
}
onRemoveContactActionTriggered: {
removeContactConfirmationDialog.value = contact.pubKey
removeContactConfirmationDialog.open()
}
onUnblockContactActionTriggered: {
root.contactsStore.unblockContact(contact.pubKey)
}
}
NoFriendsRectangle {
id: element
visible: root.contactsStore.myContactsModel.count === 0
//% "You dont have any contacts yet"
text: qsTrId("you-don-t-have-any-contacts-yet")
width: parent.width
anchors.verticalCenter: parent.verticalCenter
}
} }
// TODO: Make BlockContactConfirmationDialog a dynamic component on a future refactor // TODO: Make BlockContactConfirmationDialog a dynamic component on a future refactor

View File

@ -79,7 +79,7 @@ Item {
if (parentPopup){ if (parentPopup){
popup.parentPopup = parentPopup; popup.parentPopup = parentPopup;
} }
popup.openPopup(publicKey); popup.openPopup(publicKey, openNicknamePopup);
Global.profilePopupOpened = true; Global.profilePopupOpened = true;
} }
onOpenChangeProfilePicPopup: { onOpenChangeProfilePicPopup: {

View File

@ -48,7 +48,7 @@ StatusModal {
signal contactBlocked(publicKey: string) signal contactBlocked(publicKey: string)
signal contactAdded(publicKey: string) signal contactAdded(publicKey: string)
function openPopup(publicKey) { function openPopup(publicKey, openNicknamePopup) {
// All this should be improved more, but for now we leave it like this. // All this should be improved more, but for now we leave it like this.
let contactDetails = Utils.getContactDetailsAsJson(publicKey) let contactDetails = Utils.getContactDetailsAsJson(publicKey)
userPublicKey = publicKey userPublicKey = publicKey
@ -72,6 +72,10 @@ StatusModal {
isCurrentUser = popup.profileStore.pubkey === publicKey isCurrentUser = popup.profileStore.pubkey === publicKey
showFooter = !isCurrentUser showFooter = !isCurrentUser
popup.open() popup.open()
if (openNicknamePopup) {
nicknamePopup.open()
}
} }
header.title: userDisplayName header.title: userDisplayName

View File

@ -7,3 +7,4 @@ InvitationBubbleView 1.0 InvitationBubbleView.qml
NormalMessageView 1.0 NormalMessageView.qml NormalMessageView 1.0 NormalMessageView.qml
CompactMessageView 1.0 CompactMessageView.qml CompactMessageView 1.0 CompactMessageView.qml
MessageContextMenuView 1.0 MessageContextMenuView.qml MessageContextMenuView 1.0 MessageContextMenuView.qml
ProfileHeaderContextMenuView 1.0 ProfileHeaderContextMenuView.qml

View File

@ -28,11 +28,11 @@ QtObject {
signal settingsLoaded() signal settingsLoaded()
signal openBackUpSeedPopup() signal openBackUpSeedPopup()
signal openProfilePopupRequested(string publicKey, var parentPopup) signal openProfilePopupRequested(string publicKey, var parentPopup, bool openNicknamePopup)
signal openChangeProfilePicPopup() signal openChangeProfilePicPopup()
function openProfilePopup(publicKey, parentPopup){ function openProfilePopup(publicKey, parentPopup, openNicknamePopup){
openProfilePopupRequested(publicKey, parentPopup); openProfilePopupRequested(publicKey, parentPopup, openNicknamePopup);
} }
function openPopup(popupComponent, params = {}) { function openPopup(popupComponent, params = {}) {