diff --git a/storybook/pages/MessageContextMenuPage.qml b/storybook/pages/MessageContextMenuPage.qml index 7ad5ab93dc3..4f142ebfe4b 100644 --- a/storybook/pages/MessageContextMenuPage.qml +++ b/storybook/pages/MessageContextMenuPage.qml @@ -11,6 +11,7 @@ import Models 1.0 import utils 1.0 import shared.views.chat 1.0 +import shared.status 1.0 SplitView { @@ -30,30 +31,22 @@ SplitView { color: Theme.palette.statusAppLayout.rightPanelBackgroundColor clip: true - RowLayout { + ColumnLayout { anchors.centerIn: parent - Button { - text: "Message context menu" - onClicked: { - menu1.createObject(this).popup() - } - } - Button { - text: "Message context menu (hide disabled items)" - onClicked: { - menu2.createObject(this).popup() - } - } - Button { - text: "Profile context menu" - onClicked: { - menu3.createObject(this).popup() + spacing: 10 + + RowLayout { + Button { + text: "Message context menu" + onClicked: { + menu1.createObject(this).popup() + } } - } - Button { - text: "Profile context menu (hide disabled items)" - onClicked: { - menu4.createObject(this).popup() + Button { + text: "Message context menu (hide disabled items)" + onClicked: { + menu2.createObject(this).popup() + } } } } @@ -79,53 +72,22 @@ SplitView { } } } - - Component { - id: menu3 - ProfileContextMenu { - anchors.centerIn: parent - hideDisabledItems: false - onClosed: { - destroy() - } - } - } - - Component { - id: menu4 - ProfileContextMenu { - anchors.centerIn: parent - hideDisabledItems: true - onClosed: { - destroy() - } - } - } - } - - LogsAndControlsPanel { - id: logsAndControlsPanel - - SplitView.minimumHeight: 100 - SplitView.preferredHeight: 150 - - logsView.logText: logs.logText } } - Pane { - SplitView.minimumWidth: 300 - SplitView.preferredWidth: 300 + LogsAndControlsPanel { + id: logsAndControlsPanel - ScrollView { - anchors.fill: parent + SplitView.minimumWidth: 150 + SplitView.preferredWidth: 250 - ColumnLayout { - spacing: 16 + logsView.logText: logs.logText - } + controls: ColumnLayout { + spacing: 16 } } + } // category: Views diff --git a/storybook/pages/ProfileContextMenuPage.qml b/storybook/pages/ProfileContextMenuPage.qml new file mode 100644 index 00000000000..5b02cbe91f6 --- /dev/null +++ b/storybook/pages/ProfileContextMenuPage.qml @@ -0,0 +1,280 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 + +import StatusQ.Controls 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Components 0.1 + +import Storybook 1.0 +import Models 1.0 + +import utils 1.0 +import shared.views.chat 1.0 +import shared.status 1.0 + +SplitView { + QtObject { + id: d + } + + Logs { id: logs } + + SplitView { + orientation: Qt.Vertical + SplitView.fillWidth: true + + Rectangle { + SplitView.fillWidth: true + SplitView.fillHeight: true + color: Theme.palette.statusAppLayout.rightPanelBackgroundColor + clip: true + + ColumnLayout { + anchors.centerIn: parent + spacing: 10 + + RowLayout { + Button { + text: "Profile context menu" + onClicked: { + menu1.createObject(this).popup() + } + } + Button { + text: "Profile context menu (hide disabled items)" + onClicked: { + menu2.createObject(this).popup() + } + } + } + } + + Component { + id: menu1 + ProfileContextMenu { + id: profileContextMenu + anchors.centerIn: parent + hideDisabledItems: false + profileType: profileTypeSelector.currentValue + trustStatus: trustStatusSelector.currentValue + contactType: contactTypeSelector.currentValue + ensVerified: ensVerifiedCheckBox.checked + onlineStatus: onlineStatusSelector.currentValue + hasLocalNickname: hasLocalNicknameCheckBox.checked + publicKey: publicKeyInput.text + + onOpenProfileClicked: () => { + logs.logEvent("Open profile clicked for:", profileContextMenu.publicKey) + } + onCreateOneToOneChat: () => { + logs.logEvent("Create one-to-one chat:", profileContextMenu.publicKey) + } + onReviewContactRequest: () => { + logs.logEvent("Review contact request:", profileContextMenu.publicKey) + } + onSendContactRequest: () => { + logs.logEvent("Send contact request:", profileContextMenu.publicKey) + } + onEditNickname: () => { + logs.logEvent("Edit nickname:", profileContextMenu.publicKey) + } + onRemoveNickname: (displayName) => { + logs.logEvent("Remove nickname:", profileContextMenu.publicKey, displayName) + } + onUnblockContact: () => { + logs.logEvent("Unblock contact:", profileContextMenu.publicKey) + } + onMarkAsUntrusted: () => { + logs.logEvent("Mark as untrusted:", profileContextMenu.publicKey) + } + onRemoveTrustStatus: () => { + logs.logEvent("Remove trust status:", profileContextMenu.publicKey) + } + onRemoveContact: () => { + logs.logEvent("Remove contact:", profileContextMenu.publicKey) + } + onBlockContact: () => { + logs.logEvent("Block contact:", profileContextMenu.publicKey) + } + onClosed: { + destroy() + } + } + } + + Component { + id: menu2 + ProfileContextMenu { + id: profileContextMenu + anchors.centerIn: parent + hideDisabledItems: true + profileType: profileTypeSelector.currentValue + trustStatus: trustStatusSelector.currentValue + contactType: contactTypeSelector.currentValue + ensVerified: ensVerifiedCheckBox.checked + onlineStatus: onlineStatusSelector.currentValue + hasLocalNickname: hasLocalNicknameCheckBox.checked + publicKey: publicKeyInput.text + + onOpenProfileClicked: () => { + logs.logEvent("Open profile clicked for:", profileContextMenu.publicKey) + } + onCreateOneToOneChat: () => { + logs.logEvent("Create one-to-one chat:", profileContextMenu.publicKey) + } + onReviewContactRequest: () => { + logs.logEvent("Review contact request:", profileContextMenu.publicKey) + } + onSendContactRequest: () => { + logs.logEvent("Send contact request:", profileContextMenu.publicKey) + } + onEditNickname: () => { + logs.logEvent("Edit nickname:", profileContextMenu.publicKey) + } + onRemoveNickname: (displayName) => { + logs.logEvent("Remove nickname:", profileContextMenu.publicKey, displayName) + } + onUnblockContact: () => { + logs.logEvent("Unblock contact:", profileContextMenu.publicKey) + } + onMarkAsUntrusted: () => { + logs.logEvent("Mark as untrusted:", profileContextMenu.publicKey) + } + onRemoveTrustStatus: () => { + logs.logEvent("Remove trust status:", profileContextMenu.publicKey) + } + onRemoveContact: () => { + logs.logEvent("Remove contact:", profileContextMenu.publicKey) + } + onBlockContact: () => { + logs.logEvent("Block contact:", profileContextMenu.publicKey) + } + + onClosed: { + destroy() + } + } + } + } + } + + LogsAndControlsPanel { + id: logsAndControlsPanel + + SplitView.minimumWidth: 150 + SplitView.preferredWidth: 250 + + logsView.logText: logs.logText + + controls: ColumnLayout { + spacing: 16 + + TextField { + id: publicKeyInput + Layout.fillWidth: true + placeholderText: "Enter public key" + } + + Label { + Layout.fillWidth: true + text: "Public Key: " + (publicKeyInput.text || "0x047d6710733523714e65e783f975f2c02f5a0f43ecf6febb4e0fadb48bffae32cdc749ea366b2649c271b76e568e9bf0c173596c6e54a2659293a46947b33c9d72") + elide: Text.ElideMiddle + } + + ComboBox { + id: profileTypeSelector + textRole: "text" + valueRole: "value" + model: [ + { text: "Regular", value: Constants.profileType.regular }, + { text: "Self", value: Constants.profileType.self }, + { text: "Blocked", value: Constants.profileType.blocked }, + { text: "Bridged", value: Constants.profileType.bridged } + ] + currentIndex: 0 + } + + ComboBox { + id: trustStatusSelector + textRole: "text" + valueRole: "value" + model: [ + { text: "Unknown", value: Constants.trustStatus.unknown }, + { text: "Trusted", value: Constants.trustStatus.trusted }, + { text: "Untrusted", value: Constants.trustStatus.untrustworthy } + ] + currentIndex: 0 + } + + ComboBox { + id: contactTypeSelector + textRole: "text" + valueRole: "value" + model: [ + { text: "Non Contact", value: Constants.contactType.nonContact }, + { text: "Contact", value: Constants.contactType.contact }, + { text: "Contact Request Received", value: Constants.contactType.contactRequestReceived }, + { text: "Contact Request Sent", value: Constants.contactType.contactRequestSent } + ] + currentIndex: 0 + } + + CheckBox { + id: ensVerifiedCheckBox + text: "ENS Verified" + checked: false + } + + Label { + Layout.fillWidth: true + text: "ENS Verified: " + (ensVerifiedCheckBox.checked ? "Yes" : "No") + } + + Label { + Layout.fillWidth: true + text: "Profile type: " + profileTypeSelector.currentText + } + + Label { + Layout.fillWidth: true + text: "Trust status: " + trustStatusSelector.currentText + } + + Label { + Layout.fillWidth: true + text: "Contact type: " + contactTypeSelector.currentText + } + + ComboBox { + id: onlineStatusSelector + textRole: "text" + valueRole: "value" + model: [ + { text: "Unknown", value: Constants.onlineStatus.unknown }, + { text: "Inactive", value: Constants.onlineStatus.inactive }, + { text: "Online", value: Constants.onlineStatus.online } + ] + currentIndex: 2 // Default to online + } + + Label { + Layout.fillWidth: true + text: "Online status: " + onlineStatusSelector.currentText + } + + CheckBox { + id: hasLocalNicknameCheckBox + text: "Has Local Nickname" + checked: false + } + + Label { + Layout.fillWidth: true + text: "Has Local Nickname: " + (hasLocalNicknameCheckBox.checked ? "Yes" : "No") + } + } + } +} + +// category: Views \ No newline at end of file diff --git a/ui/app/AppLayouts/Chat/panels/UserListPanel.qml b/ui/app/AppLayouts/Chat/panels/UserListPanel.qml index 83456d6b684..14c9227fcd3 100644 --- a/ui/app/AppLayouts/Chat/panels/UserListPanel.qml +++ b/ui/app/AppLayouts/Chat/panels/UserListPanel.qml @@ -127,11 +127,13 @@ Item { ringSettings.ringSpecModel: model.colorHash onClicked: { if (mouse.button === Qt.RightButton) { + const { profileType, trustStatus, contactType, ensVerified, onlineStatus, hasLocalNickname } = root.store.contactsStore.getProfileContext(model.pubKey, userProfile.pubKey) + Global.openMenu(profileContextMenuComponent, this, { - myPublicKey: root.store.myPublicKey(), - selectedUserPublicKey: model.pubKey, - selectedUserDisplayName: nickName || userName, - selectedUserIcon: model.icon, + profileType, trustStatus, contactType, ensVerified, onlineStatus, hasLocalNickname, + publicKey: model.pubKey, + displayName: nickName || userName, + userIcon: model.icon, }) } else if (mouse.button === Qt.LeftButton) { Global.openProfilePopup(model.pubKey) @@ -168,17 +170,44 @@ Item { id: profileContextMenuComponent ProfileContextMenu { - store: root.store + id: profileContextMenu margins: 8 - onOpenProfileClicked: { - Global.openProfilePopup(publicKey, null) - } + onOpenProfileClicked: Global.openProfilePopup(profileContextMenu.publicKey, null) onCreateOneToOneChat: { Global.changeAppSectionBySectionType(Constants.appSection.chat) - root.store.chatCommunitySectionModule.createOneToOneChat(communityId, chatId, ensName) + root.store.chatCommunitySectionModule.createOneToOneChat("", profileContextMenu.publicKey, "") + } + onReviewContactRequest: { + const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true) + Global.openReviewContactRequestPopup(profileContextMenu.publicKey, contactDetails, null) + } + onSendContactRequest: { + const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true) + Global.openContactRequestPopup(profileContextMenu.publicKey, contactDetails, null) + } + onEditNickname: { + const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true) + Global.openNicknamePopupRequested(profileContextMenu.publicKey, contactDetails, null) + } + onRemoveNickname: (displayName) => { + root.store.contactsStore.changeContactNickname(profileContextMenu.publicKey, "", displayName, true) + } + onUnblockContact: { + const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true) + Global.unblockContactRequested(profileContextMenu.publicKey, contactDetails) + } + onMarkAsUntrusted: { + const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true) + Global.markAsUntrustedRequested(profileContextMenu.publicKey, contactDetails) + } + onRemoveTrustStatus: root.store.contactsStore.removeTrustStatus(profileContextMenu.publicKey) + onRemoveContact: { + const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true) + Global.removeContactRequested(profileContextMenu.publicKey, contactDetails) } - onClosed: { - destroy() + onBlockContact: { + const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true) + Global.blockContactRequested(profileContextMenu.publicKey, contactDetails) } } } diff --git a/ui/app/AppLayouts/Communities/panels/MembersTabPanel.qml b/ui/app/AppLayouts/Communities/panels/MembersTabPanel.qml index 07fd3be58dc..c1a5f82a10e 100644 --- a/ui/app/AppLayouts/Communities/panels/MembersTabPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/MembersTabPanel.qml @@ -303,11 +303,15 @@ Item { onClicked: { if(mouse.button === Qt.RightButton) { + const { profileType, trustStatus, contactType, ensVerified, onlineStatus, hasLocalNickname } = root.rootStore.contactsStore.getProfileContext(model.pubKey, Global.userProfile.pubKey) + Global.openMenu(memberContextMenuComponent, this, { - selectedUserPublicKey: model.pubKey, - selectedUserDisplayName: memberItem.title, - selectedUserIcon: icon.name, - }) + profileType, trustStatus, contactType, ensVerified, onlineStatus, hasLocalNickname, + myPublicKey: Global.userProfile.pubKey, + publicKey: model.pubKey, + displayName: memberItem.title, + userIcon: icon.name, + }) } else { Global.openProfilePopup(model.pubKey) } @@ -321,18 +325,43 @@ Item { ProfileContextMenu { id: memberContextMenuView - store: root.rootStore - myPublicKey: Global.userProfile.pubKey - onOpenProfileClicked: { - Global.openProfilePopup(publicKey, null) - } + onOpenProfileClicked: Global.openProfilePopup(memberContextMenuView.publicKey, null) onCreateOneToOneChat: { Global.changeAppSectionBySectionType(Constants.appSection.chat) - root.rootStore.chatCommunitySectionModule.createOneToOneChat(communityId, chatId, ensName) + root.rootStore.chatCommunitySectionModule.createOneToOneChat("", membersContextMenuView.publicKey, "") + } + onReviewContactRequest: { + const contactDetails = memberContextMenuView.publicKey === "" ? {} : Utils.getContactDetailsAsJson(memberContextMenuView.publicKey, true, true) + Global.openReviewContactRequestPopup(memberContextMenuView.publicKey, contactDetails, null) + } + onSendContactRequest: { + const contactDetails = memberContextMenuView.publicKey === "" ? {} : Utils.getContactDetailsAsJson(memberContextMenuView.publicKey, true, true) + Global.openContactRequestPopup(memberContextMenuView.publicKey, contactDetails, null) + } + onEditNickname: { + const contactDetails = memberContextMenuView.publicKey === "" ? {} : Utils.getContactDetailsAsJson(memberContextMenuView.publicKey, true, true) + Global.openNicknamePopupRequested(memberContextMenuView.publicKey, contactDetails, null) + } + onRemoveNickname: (displayName) => { + root.store.contactsStore.changeContactNickname(memberContextMenuView.publicKey, "", displayName, true) + } + onUnblockContact: { + const contactDetails = memberContextMenuView.publicKey === "" ? {} : Utils.getContactDetailsAsJson(memberContextMenuView.publicKey, true, true) + Global.unblockContactRequested(memberContextMenuView.publicKey, contactDetails) + } + onMarkAsUntrusted: { + const contactDetails = memberContextMenuView.publicKey === "" ? {} : Utils.getContactDetailsAsJson(memberContextMenuView.publicKey, true, true) + Global.markAsUntrustedRequested(memberContextMenuView.publicKey, contactDetails) + } + onRemoveTrustStatus: root.store.contactsStore.removeTrustStatus(memberContextMenuView.publicKey) + onRemoveContact: { + const contactDetails = memberContextMenuView.publicKey === "" ? {} : Utils.getContactDetailsAsJson(memberContextMenuView.publicKey, true, true) + Global.removeContactRequested(memberContextMenuView.publicKey, contactDetails) } - onClosed: { - destroy() + onBlockContact: { + const contactDetails = memberContextMenuView.publicKey === "" ? {} : Utils.getContactDetailsAsJson(memberContextMenuView.publicKey, true, true) + Global.blockContactRequested(memberContextMenuView.publicKey, contactDetails) } } } diff --git a/ui/app/AppLayouts/Profile/stores/ContactsStore.qml b/ui/app/AppLayouts/Profile/stores/ContactsStore.qml index 5d41ad9eaea..4a35de46290 100644 --- a/ui/app/AppLayouts/Profile/stores/ContactsStore.qml +++ b/ui/app/AppLayouts/Profile/stores/ContactsStore.qml @@ -178,4 +178,44 @@ QtObject { function getLinkToProfile(publicKey) { return root.contactsModule.shareUserUrlWithData(publicKey) } + function getProfileContext(publicKey, myPublicKey, isBridgedAccount = false) { + const contactDetails = Utils.getContactDetailsAsJson(publicKey, true, true) + if (!contactDetails) + return { + profileType: getProfileType(publicKey, myPublicKey, isBridgedAccount, false), + trustStatus: Constants.trustStatus.unknown, + contactType: getContactType(Constants.ContactRequestState.None, false), + ensVerified: false, + onlineStatus: Constants.onlineStatus.unknown, + hasLocalNickname: false + } + + const isBlocked = contactDetails.isBlocked + const profileType = getProfileType(publicKey, myPublicKey, isBridgedAccount, isBlocked) + const contactType = getContactType(contactDetails.contactRequestState, contactDetails.isContact) + const trustStatus = contactDetails.trustStatus + const ensVerified = contactDetails.ensVerified + const onlineStatus = contactDetails.onlineStatus + const hasLocalNickname = !!contactDetails.localNickname + + return { profileType, trustStatus, contactType, ensVerified, onlineStatus, hasLocalNickname } + } + + function getProfileType(publicKey, myPublicKey, isBridgedAccount, isBlocked) { + if (publicKey === myPublicKey) return Constants.profileType.self + if (isBridgedAccount) return Constants.profileType.bridged + if (isBlocked) return Constants.profileType.blocked + return Constants.profileType.regular + } + + function getContactType(contactRequestState, isContact) { + switch (contactRequestState) { + case Constants.ContactRequestState.Received: + return Constants.contactType.contactRequestReceived + case Constants.ContactRequestState.Sent: + return Constants.contactType.contactRequestSent + default: + return isContact ? Constants.contactType.contact : Constants.contactType.nonContact + } + } } diff --git a/ui/app/AppLayouts/Profile/views/ContactsView.qml b/ui/app/AppLayouts/Profile/views/ContactsView.qml index 7dd62142e26..1883ec573d6 100644 --- a/ui/app/AppLayouts/Profile/views/ContactsView.qml +++ b/ui/app/AppLayouts/Profile/views/ContactsView.qml @@ -34,11 +34,14 @@ SettingsContentBase { } function openContextMenu(publicKey, name, icon) { + const { profileType, trustStatus, contactType, ensVerified, onlineStatus, hasLocalNickname } = root.contactsStore.getProfileContext(publicKey, root.contactsStore.myPublicKey) + Global.openMenu(contactContextMenuComponent, this, { - selectedUserPublicKey: publicKey, - selectedUserDisplayName: name, - selectedUserIcon: icon, - }) + profileType, trustStatus, contactType, ensVerified, onlineStatus, hasLocalNickname, + publicKey: publicKey, + displayName: name, + userIcon: icon, + }) } Item { @@ -49,23 +52,45 @@ SettingsContentBase { Component { id: contactContextMenuComponent - ProfileContextMenu { id: contactContextMenu - store: ({contactsStore: root.contactsStore}) - onOpenProfileClicked: function (pubkey) { - Global.openProfilePopup(pubkey, null, null) + onOpenProfileClicked: Global.openProfilePopup(contactContextMenu.publicKey, null, null) + onCreateOneToOneChat: root.contactsStore.joinPrivateChat(contactContextMenu.publicKey) + onReviewContactRequest: () => { + const contactDetails = contactContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(contactContextMenu.publicKey, true, true) + Global.openReviewContactRequestPopup(contactContextMenu.publicKey, contactDetails, null) + } + onSendContactRequest: () => { + const contactDetails = contactContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(contactContextMenu.publicKey, true, true) + Global.openContactRequestPopup(contactContextMenu.publicKey, contactDetails, null) + } + onEditNickname: () => { + const contactDetails = contactContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(contactContextMenu.publicKey, true, true) + Global.openNicknamePopupRequested(contactContextMenu.publicKey, contactDetails, null) } - onCreateOneToOneChat: function (communityId, chatId, ensName) { - root.contactsStore.joinPrivateChat(chatId) + onRemoveNickname: (displayName) => { + root.contactsStore.changeContactNickname(contactContextMenu.publicKey, "", displayName, true) } - onClosed: { - destroy() + onUnblockContact: () => { + const contactDetails = contactContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(contactContextMenu.publicKey, true, true) + Global.unblockContactRequested(contactContextMenu.publicKey, contactDetails) + } + onMarkAsUntrusted: () => { + const contactDetails = contactContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(contactContextMenu.publicKey, true, true) + Global.markAsUntrustedRequested(contactContextMenu.publicKey, contactDetails) + } + onRemoveTrustStatus: root.contactsStore.removeTrustStatus(contactContextMenu.publicKey) + onRemoveContact: () => { + const contactDetails = contactContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(contactContextMenu.publicKey, true, true) + Global.removeContactRequested(contactContextMenu.publicKey, contactDetails) + } + onBlockContact: () => { + const contactDetails = contactContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(contactContextMenu.publicKey, true, true) + Global.blockContactRequested(contactContextMenu.publicKey, contactDetails) } } } - SearchBox { id: searchBox anchors.left: parent.left @@ -173,7 +198,7 @@ SettingsContentBase { visible: root.contactsStore.myContactsModel.count === 0 NoFriendsRectangle { anchors.centerIn: parent - text: qsTr("You don’t have any contacts yet") + text: qsTr("You don't have any contacts yet") } } } diff --git a/ui/imports/shared/views/chat/MessageView.qml b/ui/imports/shared/views/chat/MessageView.qml index 19a464df784..926d9cbe94d 100644 --- a/ui/imports/shared/views/chat/MessageView.qml +++ b/ui/imports/shared/views/chat/MessageView.qml @@ -152,12 +152,14 @@ Loader { // so we don't enable to right click the unavailable profile return false } - - const params = { - selectedUserPublicKey: isReply ? quotedMessageFrom : root.senderId, - selectedUserDisplayName: isReply ? quotedMessageAuthorDetailsDisplayName : root.senderDisplayName, - selectedUserIcon: isReply ? quotedMessageAuthorDetailsThumbnailImage : root.senderIcon, - isBridgedAccount: isReply ? (quotedMessageContentType === Constants.messageContentType.bridgeMessageType) : root.isBridgeMessage + const publicKey = isReply ? quotedMessageFrom : root.senderId + const isBridgedAccount = isReply ? (quotedMessageContentType === Constants.messageContentType.bridgeMessageType) : root.isBridgeMessage + const { profileType, trustStatus, contactType, ensVerified, onlineStatus, hasLocalNickname } = root.contactsStore.getProfileContext(publicKey, root.rootStore.contactsStore.myPublicKey, isBridgedAccount) + + const params = { profileType, trustStatus, contactType, ensVerified, onlineStatus, hasLocalNickname, + publicKey: isReply ? quotedMessageFrom : root.senderId, + displayName: isReply ? quotedMessageAuthorDetailsDisplayName : root.senderDisplayName, + userIcon: isReply ? quotedMessageAuthorDetailsThumbnailImage : root.senderIcon, } Global.openMenu(profileContextMenuComponent, sender, params) @@ -1170,23 +1172,46 @@ Loader { Component { id: profileContextMenuComponent - ProfileContextMenu { - store: root.rootStore - onOpenProfileClicked: (publicKey) => { - Global.openProfilePopup(publicKey, null) - } - onCreateOneToOneChat: (communityId, chatId, ensName) => { + id: profileContextMenu + onOpenProfileClicked: Global.openProfilePopup(profileContextMenu.publicKey, null) + onCreateOneToOneChat: () => { Global.changeAppSectionBySectionType(Constants.appSection.chat) - root.rootStore.chatCommunitySectionModule.createOneToOneChat("", chatId, ensName) + root.rootStore.chatCommunitySectionModule.createOneToOneChat("", profileContextMenu.publicKey, "") } - onOpened: { - root.setMessageActive(root.messageId, true) + onReviewContactRequest: () => { + const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true) + Global.openReviewContactRequestPopup(profileContextMenu.publicKey, contactDetails, null) } - onClosed: { - root.setMessageActive(root.messageId, false) - destroy() + onSendContactRequest: () => { + const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true) + Global.openContactRequestPopup(profileContextMenu.publicKey, contactDetails, null) + } + onEditNickname: () => { + const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true) + Global.openNicknamePopupRequested(profileContextMenu.publicKey, contactDetails, null) + } + onRemoveNickname: () => { + root.rootStore.contactsStore.changeContactNickname(profileContextMenu.publicKey, "", profileContextMenu.displayName, true) + } + onUnblockContact: () => { + const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true) + Global.unblockContactRequested(profileContextMenu.publicKey, contactDetails) + } + onMarkAsUntrusted: () => { + const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true) + Global.markAsUntrustedRequested(profileContextMenu.publicKey, contactDetails) + } + onRemoveTrustStatus: root.rootStore.contactsStore.removeTrustStatus(profileContextMenu.publicKey) + onRemoveContact: () => { + const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true) + Global.removeContactRequested(profileContextMenu.publicKey, contactDetails) + } + onBlockContact: () => { + const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true) + Global.blockContactRequested(profileContextMenu.publicKey, contactDetails) } + onOpened: root.setMessageActive(root.messageId, true) } } diff --git a/ui/imports/shared/views/chat/ProfileContextMenu.qml b/ui/imports/shared/views/chat/ProfileContextMenu.qml index 9afe9f3bcc2..e17e33fa3d7 100644 --- a/ui/imports/shared/views/chat/ProfileContextMenu.qml +++ b/ui/imports/shared/views/chat/ProfileContextMenu.qml @@ -1,99 +1,38 @@ import QtQuick 2.15 +import QtQuick.Controls 2.15 import StatusQ.Popups 0.1 -import StatusQ.Components 0.1 import StatusQ.Core.Utils 0.1 as StatusQUtils - -import AppLayouts.Chat.stores 1.0 as ChatStores - import utils 1.0 import shared 1.0 -import shared.panels 1.0 -import shared.popups 1.0 import shared.status 1.0 import shared.controls.chat 1.0 import shared.controls.chat.menuItems 1.0 - StatusMenu { id: root - property ChatStores.RootStore store - - property string myPublicKey: "" - - property string selectedUserPublicKey: "" - property string selectedUserDisplayName: "" - property string selectedUserIcon: "" - - property bool isBridgedAccount: false - - readonly property bool isMe: { - return root.selectedUserPublicKey === root.store.contactsStore.myPublicKey; - } - readonly property var contactDetails: { - if (root.selectedUserPublicKey === "" || isMe) { - return {} - } - return Utils.getContactDetailsAsJson(root.selectedUserPublicKey, true, true); - } - readonly property bool isContact: { - return root.selectedUserPublicKey !== "" && !!contactDetails.isContact - } - readonly property bool isBlockedContact: (!!contactDetails && contactDetails.isBlocked) || false - - readonly property bool idVerificationFlowsEnabled: false // disabled temporarily as per https://github.com/status-im/status-desktop/issues/14954 - - readonly property int outgoingVerificationStatus: { - if (root.selectedUserPublicKey === "" || root.isMe || !root.isContact) { - return 0 - } - return contactDetails.verificationStatus - } - readonly property int incomingVerificationStatus: { - if (root.selectedUserPublicKey === "" || root.isMe || !root.isContact) { - return 0 - } - return contactDetails.incomingVerificationStatus - } - readonly property bool hasPendingContactRequest: { - return !root.isMe && root.selectedUserPublicKey !== "" && - contactDetails.contactRequestState === Constants.ContactRequestState.Received - } - readonly property bool hasActiveReceivedVerificationRequestFrom: { - if (!root.selectedUserPublicKey || root.isMe || !root.isContact) { - return false - } - return contactDetails.incomingVerificationStatus === Constants.verificationStatus.verifying || - contactDetails.incomingVerificationStatus === Constants.verificationStatus.verified - } - readonly property bool isVerificationRequestSent: { - if (!root.selectedUserPublicKey || root.isMe || !root.isContact) { - return false - } - return root.outgoingVerificationStatus !== Constants.verificationStatus.unverified && - root.outgoingVerificationStatus !== Constants.verificationStatus.verified && - root.outgoingVerificationStatus !== Constants.verificationStatus.trusted - } - readonly property bool isTrusted: { - if (!root.selectedUserPublicKey || root.isMe || !root.isContact) { - return false - } - return root.outgoingVerificationStatus === Constants.verificationStatus.trusted || - root.incomingVerificationStatus === Constants.verificationStatus.trusted - } - - readonly property bool userTrustIsUnknown: contactDetails && contactDetails.trustStatus === Constants.trustStatus.unknown - readonly property bool userIsUntrustworthy: contactDetails && contactDetails.trustStatus === Constants.trustStatus.untrustworthy - readonly property bool userIsLocallyTrusted: contactDetails && contactDetails.trustStatus === Constants.trustStatus.trusted - - signal openProfileClicked(string publicKey) - signal createOneToOneChat(string communityId, string chatId, string ensName) - - onClosed: { - // Reset selectedUserPublicKey so that associated properties get recalculated on re-open - selectedUserPublicKey = "" - } + property string publicKey: "" + property string displayName: "" + property string userIcon: "" + property int trustStatus: Constants.trustStatus.unknown + property int contactType: Constants.contactType.nonContact + property int onlineStatus: Constants.onlineStatus.unknown + property int profileType: Constants.profileType.regular + property bool ensVerified: false + property bool hasLocalNickname: false + + signal openProfileClicked + signal createOneToOneChat + signal reviewContactRequest + signal sendContactRequest + signal editNickname + signal removeNickname(string displayName) + signal unblockContact + signal markAsUntrusted + signal removeTrustStatus + signal removeContact + signal blockContact ProfileHeader { width: parent.width @@ -102,196 +41,140 @@ StatusMenu { displayNameVisible: false displayNamePlusIconsVisible: true editButtonVisible: false - displayName: StatusQUtils.Emoji.parse(root.selectedUserDisplayName, StatusQUtils.Emoji.size.verySmall) - pubkey: root.selectedUserPublicKey - icon: root.selectedUserIcon - trustStatus: contactDetails && contactDetails.trustStatus ? contactDetails.trustStatus - : Constants.trustStatus.unknown + displayName: StatusQUtils.Emoji.parse(root.displayName, StatusQUtils.Emoji.size.verySmall) + pubkey: root.publicKey + icon: root.userIcon + trustStatus: root.profileType === Constants.profileType.regular ? root.trustStatus : Constants.trustStatus.unknown + isContact: root.profileType === Constants.profileType.regular ? root.contactType === Constants.contactType.contact : false + isBlocked: root.profileType === Constants.profileType.blocked + isCurrentUser: root.profileType === Constants.profileType.self + userIsEnsVerified: root.ensVerified + isBridgedAccount: root.profileType === Constants.profileType.bridged Binding on onlineStatus { - value: contactDetails.onlineStatus - when: !root.isMe + value: root.onlineStatus + when: root.profileType !== Constants.profileType.bridged } - isContact: root.isContact - isBlocked: root.isBlockedContact - isCurrentUser: root.isMe - userIsEnsVerified: (!!contactDetails && contactDetails.ensVerified) || false - isBridgedAccount: root.isBridgedAccount } StatusMenuSeparator { topPadding: root.topPadding - visible: !root.isBridgedAccount } ViewProfileMenuItem { id: viewProfileAction objectName: "viewProfile_StatusItem" - enabled: !root.isBridgedAccount + enabled: root.profileType !== Constants.profileType.bridged onTriggered: { - root.openProfileClicked(root.selectedUserPublicKey) + root.openProfileClicked() root.close() } } + // Edit Nickname + StatusAction { + id: renameAction + objectName: "rename_StatusItem" + enabled: root.profileType === Constants.profileType.blocked || root.profileType === Constants.profileType.regular + text: root.hasLocalNickname ? qsTr("Edit nickname") : qsTr("Add nickname") + icon.name: "edit_pencil" + onTriggered: root.editNickname() + } + + // Review Contact Request StatusAction { text: qsTr("Review contact request") objectName: "reviewContactRequest_StatusItem" icon.name: "add-contact" - enabled: !root.isMe && !root.isContact && !root.isBridgedAccount && !root.isBlockedContact && root.hasPendingContactRequest - onTriggered: Global.openReviewContactRequestPopup(root.selectedUserPublicKey, root.contactDetails, null) + enabled: root.profileType === Constants.profileType.regular && root.contactType === Constants.contactType.contactRequestReceived + onTriggered: root.reviewContactRequest() } + // Send Message SendMessageMenuItem { id: sendMessageMenuItem objectName: "sendMessage_StatusItem" - enabled: root.isContact && !root.isBlockedContact && !root.isBridgedAccount + enabled: root.profileType === Constants.profileType.regular && root.contactType === Constants.contactType.contact onTriggered: { - root.createOneToOneChat("", root.selectedUserPublicKey, "") + root.createOneToOneChat() root.close() } } + // Send Contact Request SendContactRequestMenuItem { id: sendContactRequestMenuItem objectName: "sendContactRequest_StatusItem" - enabled: !root.isMe && !root.isContact && !root.isBlockedContact - && (contactDetails.contactRequestState === Constants.ContactRequestState.None || contactDetails.contactRequestState === Constants.ContactRequestState.Dismissed) - && !root.isBridgedAccount - onTriggered: Global.openContactRequestPopup(root.selectedUserPublicKey, root.contactDetails, null) - } - - StatusAction { - id: verifyIdentityAction - text: qsTr("Request ID verification") - objectName: "verifyIdentity_StatusItem" - icon.name: "checkmark-circle" - enabled: idVerificationFlowsEnabled - && !root.isMe && root.isContact - && !root.isBlockedContact - && !root.userIsLocallyTrusted - && root.outgoingVerificationStatus === Constants.verificationStatus.unverified - && !root.hasActiveReceivedVerificationRequestFrom - && !root.isBridgedAccount - onTriggered: Global.openSendIDRequestPopup(root.selectedUserPublicKey, root.contactDetails, null) - } - StatusAction { - text: qsTr("Mark as ID verified") - objectName: "markAsVerified_StatusItem" - icon.name: "checkmark-circle" - enabled: idVerificationFlowsEnabled && !root.isMe && root.isContact && !root.isBridgedAccount && !root.isBlockedContact && !(root.isTrusted || root.userIsLocallyTrusted) - onTriggered: Global.openMarkAsIDVerifiedPopup(root.selectedUserPublicKey, root.contactDetails, null) - } - StatusAction { - id: pendingIdentityAction - objectName: "pendingIdentity_StatusItem" - text: { - if (root.isVerificationRequestSent) { - if (root.incomingVerificationStatus !== Constants.verificationStatus.verified) - return qsTr("ID verification pending...") - return qsTr("Review ID verification reply") - } - return qsTr("Reply to ID verification request") - } - icon.name: root.isVerificationRequestSent && root.incomingVerificationStatus !== Constants.verificationStatus.verified ? "history" - : "checkmark-circle" - enabled: idVerificationFlowsEnabled && !root.isMe && root.isContact && !root.isBridgedAccount && !root.isBlockedContact && !(root.isTrusted || root.userIsLocallyTrusted) && - (root.hasActiveReceivedVerificationRequestFrom || root.isVerificationRequestSent) - - onTriggered: { - if (root.hasActiveReceivedVerificationRequestFrom) { - Global.openIncomingIDRequestPopup(root.selectedUserPublicKey, root.contactDetails, null) - } else if (root.isVerificationRequestSent) { - Global.openOutgoingIDRequestPopup(root.selectedUserPublicKey, root.contactDetails, null) - } - - root.close() - } - } - - StatusAction { - id: renameAction - objectName: "rename_StatusItem" - text: contactDetails.localNickname ? qsTr("Edit nickname") : qsTr("Add nickname") - icon.name: "edit_pencil" - enabled: !root.isMe && !root.isBridgedAccount - onTriggered: Global.openNicknamePopupRequested(root.selectedUserPublicKey, root.contactDetails, null) + enabled: root.profileType === Constants.profileType.regular && root.contactType === Constants.contactType.nonContact + onTriggered: root.sendContactRequest() } StatusMenuSeparator { - visible: blockMenuItem.enabled || unblockAction.enabled + topPadding: root.topPadding + enabled: removeNicknameAction.enabled || unblockAction.enabled || markUntrustworthyMenuItem.enabled || removeUntrustworthyMarkMenuItem.enabled || removeContactAction.enabled || blockMenuItem.enabled } + // Remove Nickname StatusAction { + id: removeNicknameAction text: qsTr("Remove nickname") icon.name: "delete" type: StatusAction.Type.Danger - enabled: !root.isMe && !!contactDetails.localNickname - onTriggered: root.store.contactsStore.changeContactNickname(root.selectedUserPublicKey, "", root.selectedUserDisplayName, true) + enabled: (root.profileType === Constants.profileType.blocked || root.profileType === Constants.profileType.regular) && root.hasLocalNickname + onTriggered: root.removeNickname(root.displayName) } + // Unblock User StatusAction { id: unblockAction objectName: "unblock_StatusItem" + enabled: root.profileType === Constants.profileType.blocked text: qsTr("Unblock user") icon.name: "cancel" type: StatusAction.Type.Danger - enabled: !root.isMe && root.isBlockedContact && !root.isBridgedAccount - onTriggered: Global.unblockContactRequested(root.selectedUserPublicKey, root.contactDetails) - } - - StatusAction { - objectName: "removeIDVerification_StatusItem" - text: qsTr("Remove ID verification") - icon.name: "delete" - type: StatusAction.Type.Danger - enabled: idVerificationFlowsEnabled && !root.isMe && root.isContact && !root.isBridgedAccount && (root.isTrusted || root.userIsLocallyTrusted) - onTriggered: Global.openRemoveIDVerificationDialog(root.selectedUserPublicKey, root.contactDetails, null) + onTriggered: root.unblockContact() } + // Mark as Untrusted StatusAction { id: markUntrustworthyMenuItem objectName: "markUntrustworthy_StatusItem" text: qsTr("Mark as untrusted") icon.name: "warning" type: StatusAction.Type.Danger - enabled: !root.isMe && !root.userIsUntrustworthy && !root.isBridgedAccount && !root.isBlockedContact - onTriggered: Global.markAsUntrustedRequested(root.selectedUserPublicKey, root.contactDetails) - } - - StatusAction { - text: qsTr("Cancel ID verification request") - icon.name: "delete" - type: StatusAction.Type.Danger - enabled: idVerificationFlowsEnabled && !root.isMe && root.isContact && !root.isBlockedContact && !root.isBridgedAccount && root.isVerificationRequestSent - onTriggered: root.store.contactsStore.cancelVerificationRequest(root.selectedUserPublicKey) + enabled: root.profileType === Constants.profileType.regular && root.trustStatus !== Constants.trustStatus.untrustworthy + onTriggered: root.markAsUntrusted() } + // Remove Untrustworthy Mark StatusAction { id: removeUntrustworthyMarkMenuItem objectName: "removeUntrustworthy_StatusItem" text: qsTr("Remove untrusted mark") icon.name: "warning" type: StatusAction.Type.Danger - enabled: !root.isMe && root.userIsUntrustworthy && !root.isBridgedAccount - onTriggered: root.store.contactsStore.removeTrustStatus(root.selectedUserPublicKey) + enabled: root.profileType === Constants.profileType.regular && root.trustStatus === Constants.trustStatus.untrustworthy + onTriggered: root.removeTrustStatus() } + // Remove Contact StatusAction { + id: removeContactAction text: qsTr("Remove contact") objectName: "removeContact_StatusItem" icon.name: "remove-contact" type: StatusAction.Type.Danger - enabled: root.isContact && !root.isBlockedContact && !root.hasPendingContactRequest && !root.isBridgedAccount - onTriggered: Global.removeContactRequested(root.selectedUserPublicKey, root.contactDetails) + enabled: root.profileType === Constants.profileType.regular && root.contactType === Constants.contactType.contact + onTriggered: root.removeContact() } + // Block User StatusAction { id: blockMenuItem objectName: "blockUser_StatusItem" text: qsTr("Block user") icon.name: "cancel" type: StatusAction.Type.Danger - enabled: !root.isMe && !root.isBlockedContact && !root.isBridgedAccount - onTriggered: Global.blockContactRequested(root.selectedUserPublicKey, root.contactDetails) + enabled: root.profileType === Constants.profileType.regular + onTriggered: root.blockContact() } } diff --git a/ui/imports/utils/Constants.qml b/ui/imports/utils/Constants.qml index 421fb13920f..1561d4ffd54 100644 --- a/ui/imports/utils/Constants.qml +++ b/ui/imports/utils/Constants.qml @@ -430,6 +430,13 @@ QtObject { readonly property int communityChat: 6 } + readonly property QtObject profileType: QtObject { + readonly property int regular: 0 + readonly property int self: 1 + readonly property int blocked: 2 + readonly property int bridged: 3 + } + readonly property QtObject memberRole: QtObject{ readonly property int none: 0 readonly property int owner: 1 @@ -475,6 +482,13 @@ QtObject { readonly property int responseToMessageWithId: 262 // ModelRole.ResponseToMessageWithId } + readonly property QtObject contactType: QtObject { + readonly property int nonContact: 0 + readonly property int contact: 1 + readonly property int contactRequestReceived: 2 + readonly property int contactRequestSent: 3 + } + readonly property QtObject trustStatus: QtObject { readonly property int unknown: 0 readonly property int trusted: 1