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.Layouts 1.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
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 {
id: container
anchors.right: parent.right
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.rightMargin: -Style.current.padding
width: parent.width
visible: container.isContact && (searchStr == "" || container.name.includes(searchStr))
height: visible ? implicitHeight : 0
title: container.name
@ -28,13 +32,22 @@ StatusListItem {
property bool isBlocked: false
property string searchStr: ""
property bool showSendMessageButton: false
signal openProfilePopup(string publicKey)
signal openChangeNicknamePopup(string publicKey)
signal sendMessageActionTriggered(string publicKey)
signal unblockContactActionTriggered(string publicKey)
signal blockContactActionTriggered(string publicKey)
signal removeContactActionTriggered(string publicKey)
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 {
id: menuButton
width: 32
@ -53,9 +66,26 @@ StatusListItem {
menuButton.highlighted = false
}
ProfileHeader {
width: parent.width
displayName: container.name
pubkey: container.publicKey
icon: container.icon
isIdenticon: container.isIdenticon
}
Item {
height: 8
}
Separator {}
StatusMenuItem {
text: qsTr("View Profile")
icon.name: "profile"
icon.width: 16
icon.height: 16
onTriggered: {
container.openProfilePopup(container.publicKey)
menuButton.highlighted = false
@ -65,6 +95,8 @@ StatusListItem {
StatusMenuItem {
text: qsTr("Send message")
icon.name: "chat"
icon.width: 16
icon.height: 16
onTriggered: {
container.sendMessageActionTriggered(container.publicKey)
menuButton.highlighted = false
@ -73,37 +105,15 @@ StatusListItem {
}
StatusMenuItem {
text: qsTr("Block User")
icon.name: "cancel"
text: qsTr("Rename")
icon.name: "edit_pencil"
icon.width: 16
icon.height: 16
onTriggered: {
container.openChangeNicknamePopup(container.publicKey)
menuButton.highlighted = false
}
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 contactToRemove: ""
property bool hideBlocked: false
property bool showSendMessageButton
signal contactClicked(var contact)
signal openProfilePopup(var contact)
signal sendMessageActionTriggered(var contact)
signal unblockContactActionTriggered(var contact)
signal blockContactActionTriggered(var contact)
signal removeContactActionTriggered(var contact)
signal openChangeNicknamePopup(var contact)
width: parent.width
@ -38,13 +37,12 @@ ListView {
isIdenticon: model.isIdenticon
isContact: model.isContact
isBlocked: model.isBlocked
showSendMessageButton: contactList.showSendMessageButton
onClicked: contactList.contactClicked(model)
onOpenProfilePopup: contactList.openProfilePopup(model)
onSendMessageActionTriggered: contactList.sendMessageActionTriggered(model)
onUnblockContactActionTriggered: contactList.unblockContactActionTriggered(model)
onBlockContactActionTriggered: contactList.blockContactActionTriggered(model)
onRemoveContactActionTriggered: contactList.removeContactActionTriggered(model)
onOpenChangeNicknamePopup: contactList.openChangeNicknamePopup(model)
visible: {
if (hideBlocked && model.isBlocked) {

View File

@ -13,6 +13,11 @@ QtObject {
property var myContactsModel: contactsModule.myContactsModel
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) {
root.mainModuleInst.resolveENS(value, "")
}
@ -46,4 +51,20 @@ QtObject {
function 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 "../panels"
import "../popups"
// TODO remove this import when the ContactRequestPanel is moved to the the Profile completely
import "../../Chat/panels"
Item {
id: root
@ -65,115 +67,138 @@ Item {
anchors.top: titleText.bottom
anchors.topMargin: 32
fontPixelSize: 15
placeholderText: qsTr("Search by a display name or chat key")
}
Item {
id: addNewContact
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
TabBar {
id: contactsTabBar
width: parent.width
visible: root.contactsStore.blockedContactsModel.count > 0
height: 64
StatusRoundButton {
id: blockButton
width: 40
height: 40
anchors.verticalCenter: parent.verticalCenter
icon.name: "cancel"
icon.color: Theme.palette.primaryColor1
anchors.top: searchBox.bottom
anchors.topMargin: Style.current.padding
height: contactsBtn.height
background: Rectangle {
color: Style.current.transparent
}
StyledText {
id: blockButtonLabel
//% "Blocked 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 {
id: contactsBtn
anchors.top: parent.top
btnText: qsTr("Contacts")
}
StyledText {
id: numberOfBlockedContacts
text: root.contactsStore.blockedContactsModel.count
color: Style.current.darkGrey
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
anchors.verticalCenter: blockButton.verticalCenter
font.pixelSize: 15
StatusTabButton {
id: pendingRequestsBtn
enabled: root.contactsStore.contactRequestsModel.count > 0
anchors.left: contactsBtn.right
anchors.top: parent.top
anchors.leftMargin: Style.current.bigPadding
btnText: qsTr("Pending Requests")
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
blockedContactsModal.open()
}
StatusTabButton {
id: blockedBtn
enabled: root.contactsStore.blockedContactsModel.count > 0
anchors.left: pendingRequestsBtn.right
anchors.leftMargin: Style.current.bigPadding
anchors.top: parent.top
btnText: qsTr("Blocked")
}
}
ModalPopup {
id: blockedContactsModal
//% "Blocked contacts"
title: qsTrId("blocked-contacts")
StackLayout {
id: stackLayout
width: parent.width
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 {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.leftMargin: -Style.current.padding
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
height: parent.height
clip: true
contactsModel: root.contactsStore.blockedContactsModel
onOpenProfilePopup: {
Global.openProfilePopup(contact.pubKey)
}
onRemoveContactActionTriggered: {
removeContactConfirmationDialog.value = contact.pubKey
removeContactConfirmationDialog.open()
}
onUnblockContactActionTriggered: {
root.contactsStore.unblockContact(contact.pubKey)
}
}
}
@ -184,146 +209,6 @@ Item {
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

View File

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

View File

@ -48,7 +48,7 @@ StatusModal {
signal contactBlocked(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.
let contactDetails = Utils.getContactDetailsAsJson(publicKey)
userPublicKey = publicKey
@ -72,6 +72,10 @@ StatusModal {
isCurrentUser = popup.profileStore.pubkey === publicKey
showFooter = !isCurrentUser
popup.open()
if (openNicknamePopup) {
nicknamePopup.open()
}
}
header.title: userDisplayName

View File

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

View File

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