refactor ProfileContextMenu to make it a functional component

refactor ProfileContextMenu to make it a functional component

refactor ProfileContextMenu to make it a functional component

refactor ProfileContextMenu to make it a functional component

This refactor ProfileContextMenu to make it a functional component by:

refactored out direct calls to backend, and passing backend data structures and moved this logic to the callers, also refactored common calls between the callers
common types of context menus have been extracted to their sub components which removes a lot of logic too and makes the behaviour very clear
user verification workflow (which was already disabled) has been removed

refactor: use signals and call singletons on the parent instead

remove unused code for now from profile context menu

refactor profile context menu into two components; add property to storybook

extract blocked profile context menu and self profile context menu

use profileType instead of individual bools

refactor to pass trustStatus as an argument

make contact type a parameter

remove unnecessary method from RegularProfileContextMenu

add ensVerified property to ProfileContextMenu components

add onlineStatus property to ProfileContextMenu components

move ProfileContextMenu storybook controls to the right sidebar

move contactDetails logic up from the view

add local nickname property to ProfileContextMenu components

fix issue with missing signal; fix logs in storybook

use constant for profileType instead of string

refactor common code into a single method

refactor getProfileContext

remove references to contactDetails which are not longer needed

remove unnecessary comments

fix bridged constant

refactor into a single ProfileContextMenu component

refactor into a single ProfileContextMenu component

refactor into a single ProfileContextMenu component

simplify imports

remove unused store field

move methods from utils to contacts store

remove onClosed signal

remove unused param

rename ProfileContextMenu variables

simplify signals in ProfileContextMenu

remove ;

refactor: do early return

simplify ifs

move ProfileContextMenu to its own storybook page

fix wrong params

fix profile context menu separator

add missing signals to profile context menu on the members tab panel
This commit is contained in:
Iuri Matias 2024-09-06 11:55:44 -04:00
parent 1faa8e6561
commit 96d0760488
9 changed files with 594 additions and 307 deletions

View File

@ -11,6 +11,7 @@ import Models 1.0
import utils 1.0 import utils 1.0
import shared.views.chat 1.0 import shared.views.chat 1.0
import shared.status 1.0
SplitView { SplitView {
@ -30,30 +31,22 @@ SplitView {
color: Theme.palette.statusAppLayout.rightPanelBackgroundColor color: Theme.palette.statusAppLayout.rightPanelBackgroundColor
clip: true clip: true
RowLayout { ColumnLayout {
anchors.centerIn: parent anchors.centerIn: parent
Button { spacing: 10
text: "Message context menu"
onClicked: { RowLayout {
menu1.createObject(this).popup() Button {
text: "Message context menu"
onClicked: {
menu1.createObject(this).popup()
}
} }
} Button {
Button { text: "Message context menu (hide disabled items)"
text: "Message context menu (hide disabled items)" onClicked: {
onClicked: { menu2.createObject(this).popup()
menu2.createObject(this).popup() }
}
}
Button {
text: "Profile context menu"
onClicked: {
menu3.createObject(this).popup()
}
}
Button {
text: "Profile context menu (hide disabled items)"
onClicked: {
menu4.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 { LogsAndControlsPanel {
SplitView.minimumWidth: 300 id: logsAndControlsPanel
SplitView.preferredWidth: 300
ScrollView { SplitView.minimumWidth: 150
anchors.fill: parent SplitView.preferredWidth: 250
ColumnLayout { logsView.logText: logs.logText
spacing: 16
} controls: ColumnLayout {
spacing: 16
} }
} }
} }
// category: Views // category: Views

View File

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

View File

@ -127,11 +127,13 @@ Item {
ringSettings.ringSpecModel: model.colorHash ringSettings.ringSpecModel: model.colorHash
onClicked: { onClicked: {
if (mouse.button === Qt.RightButton) { if (mouse.button === Qt.RightButton) {
const { profileType, trustStatus, contactType, ensVerified, onlineStatus, hasLocalNickname } = root.store.contactsStore.getProfileContext(model.pubKey, userProfile.pubKey)
Global.openMenu(profileContextMenuComponent, this, { Global.openMenu(profileContextMenuComponent, this, {
myPublicKey: root.store.myPublicKey(), profileType, trustStatus, contactType, ensVerified, onlineStatus, hasLocalNickname,
selectedUserPublicKey: model.pubKey, publicKey: model.pubKey,
selectedUserDisplayName: nickName || userName, displayName: nickName || userName,
selectedUserIcon: model.icon, userIcon: model.icon,
}) })
} else if (mouse.button === Qt.LeftButton) { } else if (mouse.button === Qt.LeftButton) {
Global.openProfilePopup(model.pubKey) Global.openProfilePopup(model.pubKey)
@ -168,17 +170,44 @@ Item {
id: profileContextMenuComponent id: profileContextMenuComponent
ProfileContextMenu { ProfileContextMenu {
store: root.store id: profileContextMenu
margins: 8 margins: 8
onOpenProfileClicked: { onOpenProfileClicked: Global.openProfilePopup(profileContextMenu.publicKey, null)
Global.openProfilePopup(publicKey, null)
}
onCreateOneToOneChat: { onCreateOneToOneChat: {
Global.changeAppSectionBySectionType(Constants.appSection.chat) Global.changeAppSectionBySectionType(Constants.appSection.chat)
root.store.chatCommunitySectionModule.createOneToOneChat(communityId, chatId, ensName) root.store.chatCommunitySectionModule.createOneToOneChat("", profileContextMenu.publicKey, "")
} }
onClosed: { onReviewContactRequest: {
destroy() 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)
}
onBlockContact: {
const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true)
Global.blockContactRequested(profileContextMenu.publicKey, contactDetails)
} }
} }
} }

View File

@ -305,11 +305,15 @@ Item {
onClicked: { onClicked: {
if(mouse.button === Qt.RightButton) { 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, { Global.openMenu(memberContextMenuComponent, this, {
selectedUserPublicKey: model.pubKey, profileType, trustStatus, contactType, ensVerified, onlineStatus, hasLocalNickname,
selectedUserDisplayName: memberItem.title, myPublicKey: Global.userProfile.pubKey,
selectedUserIcon: icon.name, publicKey: model.pubKey,
}) displayName: memberItem.title,
userIcon: icon.name,
})
} else { } else {
Global.openProfilePopup(model.pubKey) Global.openProfilePopup(model.pubKey)
} }
@ -323,18 +327,43 @@ Item {
ProfileContextMenu { ProfileContextMenu {
id: memberContextMenuView id: memberContextMenuView
store: root.rootStore
myPublicKey: Global.userProfile.pubKey
onOpenProfileClicked: { onOpenProfileClicked: Global.openProfilePopup(memberContextMenuView.publicKey, null)
Global.openProfilePopup(publicKey, null)
}
onCreateOneToOneChat: { onCreateOneToOneChat: {
Global.changeAppSectionBySectionType(Constants.appSection.chat) Global.changeAppSectionBySectionType(Constants.appSection.chat)
root.rootStore.chatCommunitySectionModule.createOneToOneChat(communityId, chatId, ensName) root.rootStore.chatCommunitySectionModule.createOneToOneChat("", membersContextMenuView.publicKey, "")
} }
onClosed: { onReviewContactRequest: {
destroy() 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)
}
onBlockContact: {
const contactDetails = memberContextMenuView.publicKey === "" ? {} : Utils.getContactDetailsAsJson(memberContextMenuView.publicKey, true, true)
Global.blockContactRequested(memberContextMenuView.publicKey, contactDetails)
} }
} }
} }

View File

@ -178,4 +178,44 @@ QtObject {
function getLinkToProfile(publicKey) { function getLinkToProfile(publicKey) {
return root.contactsModule.shareUserUrlWithData(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
}
}
} }

View File

@ -34,11 +34,14 @@ SettingsContentBase {
} }
function openContextMenu(publicKey, name, icon) { function openContextMenu(publicKey, name, icon) {
const { profileType, trustStatus, contactType, ensVerified, onlineStatus, hasLocalNickname } = root.contactsStore.getProfileContext(publicKey, root.contactsStore.myPublicKey)
Global.openMenu(contactContextMenuComponent, this, { Global.openMenu(contactContextMenuComponent, this, {
selectedUserPublicKey: publicKey, profileType, trustStatus, contactType, ensVerified, onlineStatus, hasLocalNickname,
selectedUserDisplayName: name, publicKey: publicKey,
selectedUserIcon: icon, displayName: name,
}) userIcon: icon,
})
} }
Item { Item {
@ -49,23 +52,45 @@ SettingsContentBase {
Component { Component {
id: contactContextMenuComponent id: contactContextMenuComponent
ProfileContextMenu { ProfileContextMenu {
id: contactContextMenu id: contactContextMenu
store: ({contactsStore: root.contactsStore})
onOpenProfileClicked: function (pubkey) { onOpenProfileClicked: Global.openProfilePopup(contactContextMenu.publicKey, null, null)
Global.openProfilePopup(pubkey, 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)
} }
onCreateOneToOneChat: function (communityId, chatId, ensName) { onSendContactRequest: () => {
root.contactsStore.joinPrivateChat(chatId) const contactDetails = contactContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(contactContextMenu.publicKey, true, true)
Global.openContactRequestPopup(contactContextMenu.publicKey, contactDetails, null)
} }
onClosed: { onEditNickname: () => {
destroy() const contactDetails = contactContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(contactContextMenu.publicKey, true, true)
Global.openNicknamePopupRequested(contactContextMenu.publicKey, contactDetails, null)
}
onRemoveNickname: (displayName) => {
root.contactsStore.changeContactNickname(contactContextMenu.publicKey, "", displayName, true)
}
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 { SearchBox {
id: searchBox id: searchBox
anchors.left: parent.left anchors.left: parent.left
@ -173,7 +198,7 @@ SettingsContentBase {
visible: root.contactsStore.myContactsModel.count === 0 visible: root.contactsStore.myContactsModel.count === 0
NoFriendsRectangle { NoFriendsRectangle {
anchors.centerIn: parent anchors.centerIn: parent
text: qsTr("You dont have any contacts yet") text: qsTr("You don't have any contacts yet")
} }
} }
} }

View File

@ -154,12 +154,14 @@ Loader {
// so we don't enable to right click the unavailable profile // so we don't enable to right click the unavailable profile
return false return false
} }
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 = { const params = { profileType, trustStatus, contactType, ensVerified, onlineStatus, hasLocalNickname,
selectedUserPublicKey: isReply ? quotedMessageFrom : root.senderId, publicKey: isReply ? quotedMessageFrom : root.senderId,
selectedUserDisplayName: isReply ? quotedMessageAuthorDetailsDisplayName : root.senderDisplayName, displayName: isReply ? quotedMessageAuthorDetailsDisplayName : root.senderDisplayName,
selectedUserIcon: isReply ? quotedMessageAuthorDetailsThumbnailImage : root.senderIcon, userIcon: isReply ? quotedMessageAuthorDetailsThumbnailImage : root.senderIcon,
isBridgedAccount: isReply ? (quotedMessageContentType === Constants.messageContentType.bridgeMessageType) : root.isBridgeMessage
} }
Global.openMenu(profileContextMenuComponent, sender, params) Global.openMenu(profileContextMenuComponent, sender, params)
@ -1178,23 +1180,46 @@ Loader {
Component { Component {
id: profileContextMenuComponent id: profileContextMenuComponent
ProfileContextMenu { ProfileContextMenu {
store: root.rootStore id: profileContextMenu
onOpenProfileClicked: (publicKey) => { onOpenProfileClicked: Global.openProfilePopup(profileContextMenu.publicKey, null)
Global.openProfilePopup(publicKey, null) onCreateOneToOneChat: () => {
}
onCreateOneToOneChat: (communityId, chatId, ensName) => {
Global.changeAppSectionBySectionType(Constants.appSection.chat) Global.changeAppSectionBySectionType(Constants.appSection.chat)
root.rootStore.chatCommunitySectionModule.createOneToOneChat("", chatId, ensName) root.rootStore.chatCommunitySectionModule.createOneToOneChat("", profileContextMenu.publicKey, "")
} }
onOpened: { onReviewContactRequest: () => {
root.setMessageActive(root.messageId, true) const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true)
Global.openReviewContactRequestPopup(profileContextMenu.publicKey, contactDetails, null)
} }
onClosed: { onSendContactRequest: () => {
root.setMessageActive(root.messageId, false) const contactDetails = profileContextMenu.publicKey === "" ? {} : Utils.getContactDetailsAsJson(profileContextMenu.publicKey, true, true)
destroy() 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)
} }
} }

View File

@ -1,99 +1,38 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15
import StatusQ.Popups 0.1 import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils import StatusQ.Core.Utils 0.1 as StatusQUtils
import AppLayouts.Chat.stores 1.0 as ChatStores
import utils 1.0 import utils 1.0
import shared 1.0 import shared 1.0
import shared.panels 1.0
import shared.popups 1.0
import shared.status 1.0 import shared.status 1.0
import shared.controls.chat 1.0 import shared.controls.chat 1.0
import shared.controls.chat.menuItems 1.0 import shared.controls.chat.menuItems 1.0
StatusMenu { StatusMenu {
id: root id: root
property ChatStores.RootStore store 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
property string myPublicKey: "" signal openProfileClicked
signal createOneToOneChat
property string selectedUserPublicKey: "" signal reviewContactRequest
property string selectedUserDisplayName: "" signal sendContactRequest
property string selectedUserIcon: "" signal editNickname
signal removeNickname(string displayName)
property bool isBridgedAccount: false signal unblockContact
signal markAsUntrusted
readonly property bool isMe: { signal removeTrustStatus
return root.selectedUserPublicKey === root.store.contactsStore.myPublicKey; signal removeContact
} signal blockContact
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 = ""
}
ProfileHeader { ProfileHeader {
width: parent.width width: parent.width
@ -102,196 +41,140 @@ StatusMenu {
displayNameVisible: false displayNameVisible: false
displayNamePlusIconsVisible: true displayNamePlusIconsVisible: true
editButtonVisible: false editButtonVisible: false
displayName: StatusQUtils.Emoji.parse(root.selectedUserDisplayName, StatusQUtils.Emoji.size.verySmall) displayName: StatusQUtils.Emoji.parse(root.displayName, StatusQUtils.Emoji.size.verySmall)
pubkey: root.selectedUserPublicKey pubkey: root.publicKey
icon: root.selectedUserIcon icon: root.userIcon
trustStatus: contactDetails && contactDetails.trustStatus ? contactDetails.trustStatus trustStatus: root.profileType === Constants.profileType.regular ? root.trustStatus : Constants.trustStatus.unknown
: 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 { Binding on onlineStatus {
value: contactDetails.onlineStatus value: root.onlineStatus
when: !root.isMe when: root.profileType !== Constants.profileType.bridged
} }
isContact: root.isContact
isBlocked: root.isBlockedContact
isCurrentUser: root.isMe
userIsEnsVerified: (!!contactDetails && contactDetails.ensVerified) || false
isBridgedAccount: root.isBridgedAccount
} }
StatusMenuSeparator { StatusMenuSeparator {
topPadding: root.topPadding topPadding: root.topPadding
visible: !root.isBridgedAccount
} }
ViewProfileMenuItem { ViewProfileMenuItem {
id: viewProfileAction id: viewProfileAction
objectName: "viewProfile_StatusItem" objectName: "viewProfile_StatusItem"
enabled: !root.isBridgedAccount enabled: root.profileType !== Constants.profileType.bridged
onTriggered: { onTriggered: {
root.openProfileClicked(root.selectedUserPublicKey) root.openProfileClicked()
root.close() 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 { StatusAction {
text: qsTr("Review contact request") text: qsTr("Review contact request")
objectName: "reviewContactRequest_StatusItem" objectName: "reviewContactRequest_StatusItem"
icon.name: "add-contact" icon.name: "add-contact"
enabled: !root.isMe && !root.isContact && !root.isBridgedAccount && !root.isBlockedContact && root.hasPendingContactRequest enabled: root.profileType === Constants.profileType.regular && root.contactType === Constants.contactType.contactRequestReceived
onTriggered: Global.openReviewContactRequestPopup(root.selectedUserPublicKey, root.contactDetails, null) onTriggered: root.reviewContactRequest()
} }
// Send Message
SendMessageMenuItem { SendMessageMenuItem {
id: sendMessageMenuItem id: sendMessageMenuItem
objectName: "sendMessage_StatusItem" objectName: "sendMessage_StatusItem"
enabled: root.isContact && !root.isBlockedContact && !root.isBridgedAccount enabled: root.profileType === Constants.profileType.regular && root.contactType === Constants.contactType.contact
onTriggered: { onTriggered: {
root.createOneToOneChat("", root.selectedUserPublicKey, "") root.createOneToOneChat()
root.close() root.close()
} }
} }
// Send Contact Request
SendContactRequestMenuItem { SendContactRequestMenuItem {
id: sendContactRequestMenuItem id: sendContactRequestMenuItem
objectName: "sendContactRequest_StatusItem" objectName: "sendContactRequest_StatusItem"
enabled: !root.isMe && !root.isContact && !root.isBlockedContact enabled: root.profileType === Constants.profileType.regular && root.contactType === Constants.contactType.nonContact
&& (contactDetails.contactRequestState === Constants.ContactRequestState.None || contactDetails.contactRequestState === Constants.ContactRequestState.Dismissed) onTriggered: root.sendContactRequest()
&& !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)
} }
StatusMenuSeparator { 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 { StatusAction {
id: removeNicknameAction
text: qsTr("Remove nickname") text: qsTr("Remove nickname")
icon.name: "delete" icon.name: "delete"
type: StatusAction.Type.Danger type: StatusAction.Type.Danger
enabled: !root.isMe && !!contactDetails.localNickname enabled: (root.profileType === Constants.profileType.blocked || root.profileType === Constants.profileType.regular) && root.hasLocalNickname
onTriggered: root.store.contactsStore.changeContactNickname(root.selectedUserPublicKey, "", root.selectedUserDisplayName, true) onTriggered: root.removeNickname(root.displayName)
} }
// Unblock User
StatusAction { StatusAction {
id: unblockAction id: unblockAction
objectName: "unblock_StatusItem" objectName: "unblock_StatusItem"
enabled: root.profileType === Constants.profileType.blocked
text: qsTr("Unblock user") text: qsTr("Unblock user")
icon.name: "cancel" icon.name: "cancel"
type: StatusAction.Type.Danger type: StatusAction.Type.Danger
enabled: !root.isMe && root.isBlockedContact && !root.isBridgedAccount onTriggered: root.unblockContact()
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)
} }
// Mark as Untrusted
StatusAction { StatusAction {
id: markUntrustworthyMenuItem id: markUntrustworthyMenuItem
objectName: "markUntrustworthy_StatusItem" objectName: "markUntrustworthy_StatusItem"
text: qsTr("Mark as untrusted") text: qsTr("Mark as untrusted")
icon.name: "warning" icon.name: "warning"
type: StatusAction.Type.Danger type: StatusAction.Type.Danger
enabled: !root.isMe && !root.userIsUntrustworthy && !root.isBridgedAccount && !root.isBlockedContact enabled: root.profileType === Constants.profileType.regular && root.trustStatus !== Constants.trustStatus.untrustworthy
onTriggered: Global.markAsUntrustedRequested(root.selectedUserPublicKey, root.contactDetails) onTriggered: root.markAsUntrusted()
}
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)
} }
// Remove Untrustworthy Mark
StatusAction { StatusAction {
id: removeUntrustworthyMarkMenuItem id: removeUntrustworthyMarkMenuItem
objectName: "removeUntrustworthy_StatusItem" objectName: "removeUntrustworthy_StatusItem"
text: qsTr("Remove untrusted mark") text: qsTr("Remove untrusted mark")
icon.name: "warning" icon.name: "warning"
type: StatusAction.Type.Danger type: StatusAction.Type.Danger
enabled: !root.isMe && root.userIsUntrustworthy && !root.isBridgedAccount enabled: root.profileType === Constants.profileType.regular && root.trustStatus === Constants.trustStatus.untrustworthy
onTriggered: root.store.contactsStore.removeTrustStatus(root.selectedUserPublicKey) onTriggered: root.removeTrustStatus()
} }
// Remove Contact
StatusAction { StatusAction {
id: removeContactAction
text: qsTr("Remove contact") text: qsTr("Remove contact")
objectName: "removeContact_StatusItem" objectName: "removeContact_StatusItem"
icon.name: "remove-contact" icon.name: "remove-contact"
type: StatusAction.Type.Danger type: StatusAction.Type.Danger
enabled: root.isContact && !root.isBlockedContact && !root.hasPendingContactRequest && !root.isBridgedAccount enabled: root.profileType === Constants.profileType.regular && root.contactType === Constants.contactType.contact
onTriggered: Global.removeContactRequested(root.selectedUserPublicKey, root.contactDetails) onTriggered: root.removeContact()
} }
// Block User
StatusAction { StatusAction {
id: blockMenuItem id: blockMenuItem
objectName: "blockUser_StatusItem" objectName: "blockUser_StatusItem"
text: qsTr("Block user") text: qsTr("Block user")
icon.name: "cancel" icon.name: "cancel"
type: StatusAction.Type.Danger type: StatusAction.Type.Danger
enabled: !root.isMe && !root.isBlockedContact && !root.isBridgedAccount enabled: root.profileType === Constants.profileType.regular
onTriggered: Global.blockContactRequested(root.selectedUserPublicKey, root.contactDetails) onTriggered: root.blockContact()
} }
} }

View File

@ -430,6 +430,13 @@ QtObject {
readonly property int communityChat: 6 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 QtObject memberRole: QtObject{
readonly property int none: 0 readonly property int none: 0
readonly property int owner: 1 readonly property int owner: 1
@ -475,6 +482,13 @@ QtObject {
readonly property int responseToMessageWithId: 262 // ModelRole.ResponseToMessageWithId 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 QtObject trustStatus: QtObject {
readonly property int unknown: 0 readonly property int unknown: 0
readonly property int trusted: 1 readonly property int trusted: 1