From d12490ab1841fb245564085fb23a30b2ffc1153e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Thu, 29 Feb 2024 22:01:33 +0100 Subject: [PATCH] feat(Profile flow): Respond to/review an incoming CR - implement the new "Reviewing contact request" popup & flow - adjust storybook Fixes #13519 --- storybook/pages/ProfileDialogViewPage.qml | 16 +++++ ui/app/mainui/Popups.qml | 31 ++++++++++ .../ContactVerificationRequestPopup.qml | 2 - .../popups/ReviewContactRequestPopup.qml | 61 +++++++++++++++++++ ui/imports/shared/popups/qmldir | 1 + ui/imports/shared/views/ProfileDialogView.qml | 30 +++------ .../shared/views/chat/ProfileContextMenu.qml | 10 ++- ui/imports/utils/Global.qml | 1 + 8 files changed, 126 insertions(+), 26 deletions(-) create mode 100644 ui/imports/shared/popups/ReviewContactRequestPopup.qml diff --git a/storybook/pages/ProfileDialogViewPage.qml b/storybook/pages/ProfileDialogViewPage.qml index ca52660966..974a099216 100644 --- a/storybook/pages/ProfileDialogViewPage.qml +++ b/storybook/pages/ProfileDialogViewPage.qml @@ -148,6 +148,22 @@ SplitView { ctrlContactRequestState.currentIndex = ctrlContactRequestState.indexOfValue(Constants.ContactRequestState.Sent) } + function acceptContactRequest(publicKey, contactRequestId) { + logs.logEvent("rootStore::contactStore::acceptContactRequest", ["publicKey, contactRequestId"], arguments) + ctrlContactRequestState.currentIndex = ctrlContactRequestState.indexOfValue(Constants.ContactRequestState.Mutual) + } + + function getLatestContactRequestForContactAsJson(pubKey) { + logs.logEvent("rootStore::contactStore::getLatestContactRequestForContactAsJson", ["pubKey"], arguments) + return { + id: "123456789", + from: pubKey, + clock: Date.now(), + text: "Hey Jo, it’s Alex here, we met at devcon last week!", + contactRequestState: Constants.ContactRequestState.Received + } + } + function sendVerificationRequest(publicKey, challenge) { logs.logEvent("rootStore::contactStore::sendVerificationRequest", ["publicKey", "challenge"], arguments) ctrlVerificationStatus.currentIndex = ctrlVerificationStatus.indexOfValue(Constants.verificationStatus.verifying) diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index 4480efe3e4..3b1207ff04 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -53,6 +53,7 @@ QtObject { Global.openIncomingIDRequestPopup.connect(openIncomingIDRequestPopup) Global.openInviteFriendsToCommunityPopup.connect(openInviteFriendsToCommunityPopup) Global.openContactRequestPopup.connect(openContactRequestPopup) + Global.openReviewContactRequestPopup.connect(openReviewContactRequestPopup) Global.openChooseBrowserPopup.connect(openChooseBrowserPopup) Global.openDownloadModalRequested.connect(openDownloadModal) Global.openImagePopup.connect(openImagePopup) @@ -222,6 +223,19 @@ QtObject { openPopup(sendContactRequestPopupComponent, popupProperties, cb) } + function openReviewContactRequestPopup(publicKey, contactDetails, cb) { + try { + const crDetails = rootStore.contactStore.getLatestContactRequestForContactAsJson(publicKey) + if (crDetails.from !== publicKey) { + console.warn("Popups.openReviewContactRequestPopup: not matching publicKey:", publicKey) + return + } + openPopup(reviewContactRequestPopupComponent, {publicKey, contactDetails, crDetails}, cb) + } catch (e) { + console.error("Popups.openReviewContactRequestPopup: error getting or parsing contact request data", e) + } + } + function openPinnedMessagesPopup(store, messageStore, pinnedMessagesModel, messageToPin, chatId) { openPopup(pinnedMessagesPopup, { store: store, @@ -467,6 +481,23 @@ QtObject { } }, + Component { + id: reviewContactRequestPopupComponent + ReviewContactRequestPopup { + onAccepted: { + rootStore.contactStore.acceptContactRequest(publicKey, contactRequestId) + Global.displaySuccessToastMessage(qsTr("Contact request accepted")) + close() + } + onDiscarded: { + rootStore.contactStore.dismissContactRequest(publicKey, contactRequestId) + Global.displaySuccessToastMessage(qsTr("Contact request ignored")) + close() + } + onClosed: destroy() + } + }, + Component { id: backupSeedModalComponent BackupSeedModal { diff --git a/ui/imports/shared/popups/ContactVerificationRequestPopup.qml b/ui/imports/shared/popups/ContactVerificationRequestPopup.qml index b259ea4c62..f44dc71d13 100644 --- a/ui/imports/shared/popups/ContactVerificationRequestPopup.qml +++ b/ui/imports/shared/popups/ContactVerificationRequestPopup.qml @@ -8,8 +8,6 @@ import StatusQ.Components 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Core.Utils 0.1 as SQUtils -import shared.views.chat 1.0 - import utils 1.0 CommonContactDialog { diff --git a/ui/imports/shared/popups/ReviewContactRequestPopup.qml b/ui/imports/shared/popups/ReviewContactRequestPopup.qml new file mode 100644 index 0000000000..ac9105974a --- /dev/null +++ b/ui/imports/shared/popups/ReviewContactRequestPopup.qml @@ -0,0 +1,61 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQml.Models 2.15 + +import StatusQ.Controls 0.1 +import StatusQ.Core 0.1 +import StatusQ.Components 0.1 +import StatusQ.Core.Theme 0.1 + +import utils 1.0 + +CommonContactDialog { + id: root + + // expected roles: id, from, clock, text, contactRequestState + required property var crDetails + + signal accepted(string contactRequestId) + signal discarded(string contactRequestId) + + title: qsTr("Review contact request") + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: msgColumn.implicitHeight + msgColumn.anchors.topMargin + msgColumn.anchors.bottomMargin + color: "transparent" + border.width: 1 + border.color: Theme.palette.baseColor2 + radius: Style.current.radius + + ColumnLayout { + id: msgColumn + anchors.fill: parent + anchors.margins: Style.current.padding + + StatusTimeStampLabel { + Layout.maximumWidth: parent.width + timestamp: crDetails.clock + } + StatusBaseText { + Layout.fillWidth: true + wrapMode: Text.WordWrap + text: crDetails.text + } + } + } + + rightButtons: ObjectModel { + StatusFlatButton { + text: qsTr("Ignore") + objectName: "ignoreButton" + onClicked: root.discarded(crDetails.id ?? "") + } + StatusButton { + text: qsTr("Accept") + type: StatusBaseButton.Type.Success + objectName: "acceptButton" + onClicked: root.accepted(crDetails.id ?? "") + } + } +} diff --git a/ui/imports/shared/popups/qmldir b/ui/imports/shared/popups/qmldir index 55add577e2..a57a98e141 100644 --- a/ui/imports/shared/popups/qmldir +++ b/ui/imports/shared/popups/qmldir @@ -31,3 +31,4 @@ MarkAsUntrustedPopup 1.0 MarkAsUntrustedPopup.qml RemoveContactPopup 1.0 RemoveContactPopup.qml MarkAsIDVerifiedDialog 1.0 MarkAsIDVerifiedDialog.qml RemoveIDVerificationDialog 1.0 RemoveIDVerificationDialog.qml +ReviewContactRequestPopup 1.0 ReviewContactRequestPopup.qml diff --git a/ui/imports/shared/views/ProfileDialogView.qml b/ui/imports/shared/views/ProfileDialogView.qml index 48646d81a2..56b22d67a1 100644 --- a/ui/imports/shared/views/ProfileDialogView.qml +++ b/ui/imports/shared/views/ProfileDialogView.qml @@ -143,29 +143,14 @@ Pane { } } - // TODO a popup here instead of buttons Component { id: btnAcceptContactRequestComponent - ColumnLayout { - spacing: Style.current.halfPadding - - StatusBaseText { - color: Theme.palette.baseColor1 - font.pixelSize: 13 - text: qsTr("Review contact request") - } - - AcceptRejectOptionsButtonsPanel { - menuButton.visible: false - onAcceptClicked: { - root.contactsStore.acceptContactRequest(root.publicKey, "") - d.reload() - } - onDeclineClicked: { - root.contactsStore.dismissContactRequest(root.publicKey) - d.reload() - } - } + StatusButton { + objectName: "profileDialog_reviewContactRequestButton" + size: StatusButton.Size.Small + text: qsTr("Review contact request") + onClicked: Global.openReviewContactRequestPopup(root.publicKey, d.contactDetails, + popup => popup.closed.connect(d.reload)) } } @@ -175,7 +160,8 @@ Pane { objectName: "profileDialog_sendContactRequestButton" size: StatusButton.Size.Small text: qsTr("Send contact request") - onClicked: Global.openContactRequestPopup(root.publicKey, d.contactDetails, null) + onClicked: Global.openContactRequestPopup(root.publicKey, d.contactDetails, + popup => popup.closed.connect(d.reload)) } } diff --git a/ui/imports/shared/views/chat/ProfileContextMenu.qml b/ui/imports/shared/views/chat/ProfileContextMenu.qml index fb00d070bf..2cba684401 100644 --- a/ui/imports/shared/views/chat/ProfileContextMenu.qml +++ b/ui/imports/shared/views/chat/ProfileContextMenu.qml @@ -52,7 +52,7 @@ StatusMenu { } readonly property bool hasPendingContactRequest: { return !root.isMe && root.selectedUserPublicKey !== "" && - root.store.contactsStore.hasPendingContactRequest(root.selectedUserPublicKey); + contactDetails.contactRequestState === Constants.ContactRequestState.Received } readonly property bool hasActiveReceivedVerificationRequestFrom: { if (!root.selectedUserPublicKey || root.isMe || !root.isContact) { @@ -127,7 +127,13 @@ StatusMenu { } } - // TODO Review contact request popup + 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) + } SendMessageMenuItem { id: sendMessageMenuItem diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml index 30c0e07ddd..a42d3b7054 100644 --- a/ui/imports/utils/Global.qml +++ b/ui/imports/utils/Global.qml @@ -42,6 +42,7 @@ QtObject { signal openMarkAsIDVerifiedPopup(string publicKey, var contactDetails, var cb) signal openRemoveIDVerificationDialog(string publicKey, var contactDetails, var cb) signal openContactRequestPopup(string publicKey, var contactDetails, var cb) + signal openReviewContactRequestPopup(string publicKey, var contactDetails, var cb) signal markAsUntrustedRequested(string publicKey, var contactDetails) signal removeContactRequested(string publicKey, var contactDetails) signal openInviteFriendsToCommunityPopup(var community, var communitySectionModule, var cb)