fix(Profile flow): Send a contact request (CR)

- make `SendContactRequestModal.qml` use the common dialog, use the
contact details if we already have it
- make some minimal changes to the "Send ID verification" flow since it
shares the same dialog
- simplify the `CommonContactDialog.qml` footer/buttons handling
- adjust the menu item texts
- emit toasts when the action is performed
- display a tooltip over the compressed elided key

Fixes #13518
This commit is contained in:
Lukáš Tinkl 2024-02-16 12:56:29 +01:00 committed by Lukáš Tinkl
parent 610a9dc115
commit 2fa65968c0
13 changed files with 129 additions and 138 deletions

View File

@ -78,10 +78,10 @@ SplitView {
localNickname: localNickname.text,
thumbnailImage: "",
largeImage: userImage.checked ? Style.png("status-logo") : "",
isContact: isContact.checked,
isBlocked: isBlocked.checked,
isContact: ctrlIsContact.checked,
isBlocked: ctrlIsBlocked.checked,
isSyncing: false,
trustStatus: trustStatus.currentValue,
trustStatus: ctrlTrustStatus.currentValue,
verificationStatus: Constants.verificationStatus.unverified,
incomingVerificationStatus: Constants.verificationStatus.unverified,
contactRequestState: ctrlContactRequestState.currentValue,
@ -122,11 +122,22 @@ SplitView {
}
function blockContact(publicKey) {
logs.logEvent("rootStore::contactsStore::blockContact", ["publicKey"], arguments)
logs.logEvent("rootStore::contactStore::blockContact", ["publicKey"], arguments)
ctrlIsBlocked.checked = true
}
function unblockContact(publicKey) {
logs.logEvent("rootStore::contactsStore::unblockContact", ["publicKey"], arguments)
logs.logEvent("rootStore::contactStore::unblockContact", ["publicKey"], arguments)
ctrlIsBlocked.checked = false
}
function sendContactRequest(publicKey, message) {
logs.logEvent("rootStore::contactStore::sendContactRequest", ["publicKey", "message"], arguments)
ctrlContactRequestState.currentIndex = ctrlContactRequestState.indexOfValue(Constants.ContactRequestState.Sent)
}
function sendVerificationRequest(publicKey, challenge) {
logs.logEvent("rootStore::contactStore::sendVerificationRequest", ["publicKey", "challenge"], arguments)
}
}
}
@ -194,6 +205,7 @@ SplitView {
function markUntrustworthy(publicKey) {
logs.logEvent("contactsStore::markUntrustworthy", ["publicKey"], arguments)
ctrlTrustStatus.currentIndex = ctrlTrustStatus.indexOfValue(Constants.trustStatus.untrustworthy)
}
function removeContact(publicKey) {
@ -338,7 +350,7 @@ SplitView {
Layout.fillWidth: true
enabled: !switchOwnProfile.checked
CheckBox {
id: isContact
id: ctrlIsContact
enabled: true
checked: ctrlContactRequestState.currentValue === Constants.ContactRequestState.Mutual
text: "isContact"
@ -356,7 +368,7 @@ SplitView {
]
}
CheckBox {
id: isBlocked
id: ctrlIsBlocked
text: "isBlocked"
}
}
@ -365,7 +377,7 @@ SplitView {
enabled: !switchOwnProfile.checked
Label { text: "trustStatus:" }
ComboBox {
id: trustStatus
id: ctrlTrustStatus
textRole: "text"
valueRole: "value"
model: [

View File

@ -40,7 +40,6 @@ Item {
property bool isContactBlocked: false
property bool isChatBlocked: false
property bool isOneToOne: false
property bool isActiveChannel: false
signal openStickerPackPopup(string stickerPackId)
signal showReplyArea(string messageId, string author)
@ -391,7 +390,7 @@ Item {
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
text: qsTr("Send Contact Request")
onClicked: {
Global.openContactRequestPopup(root.chatId, null)
Global.openContactRequestPopup(root.chatId, null, null)
}
}
}

View File

@ -115,7 +115,7 @@ MembersSelectorBase {
if (root.model.count === 0 && !hasPendingContactRequest) {
// List is empty and not a contact yet. Open the contact request popup
Global.openContactRequestPopup(contactDetails.publicKey,
Global.openContactRequestPopup(contactDetails.publicKey, contactDetails,
popup => popup.closed.connect(root.rejected))
return
}

View File

@ -80,6 +80,7 @@ QtObject {
function sendContactRequest(pubKey, message) {
root.contactsModule.sendContactRequest(pubKey, message)
Global.displaySuccessToastMessage(qsTr("Contact request sent"))
}
function acceptContactRequest(pubKey, contactRequestId) {
@ -100,6 +101,7 @@ QtObject {
function sendVerificationRequest(pubKey, challenge) {
root.contactsModule.sendVerificationRequest(pubKey, challenge);
Global.displaySuccessToastMessage(qsTr("ID verification request sent"))
}
function cancelVerificationRequest(pubKey) {

View File

@ -157,15 +157,14 @@ QtObject {
openPopup(communityProfilePopup, { store: store, community: community, communitySectionModule: communitySectionModule})
}
function openSendIDRequestPopup(publicKey, cb) {
const contactDetails = Utils.getContactDetailsAsJson(publicKey, false)
const mainDisplayName = ProfileUtils.displayName(contactDetails.localNickname, contactDetails.name, contactDetails.displayName, contactDetails.alias)
function openSendIDRequestPopup(publicKey, contactDetails, cb) {
openPopup(sendIDRequestPopupComponent, {
userPublicKey: publicKey,
publicKey: publicKey,
contactDetails: contactDetails,
title: qsTr("Verify %1's Identity").arg(mainDisplayName),
challengeText: qsTr("Ask a question that only the real %1 will be able to answer e.g. a question about a shared experience, or ask %1 to enter a code or phrase you have sent to them via a different communication channel (phone, post, etc...).").arg(mainDisplayName),
buttonText: qsTr("Send verification request")
title: qsTr("Request ID verification"),
labelText: qsTr("Ask a question only they can answer"),
challengeText: qsTr("Ask your question..."),
buttonText: qsTr("Request ID verification")
}, cb)
}
@ -201,13 +200,12 @@ QtObject {
openPopup(inviteFriendsToCommunityPopup, { community: community, communitySectionModule: communitySectionModule }, cb)
}
function openContactRequestPopup(publicKey, cb) {
const contactDetails = Utils.getContactDetailsAsJson(publicKey, false)
function openContactRequestPopup(publicKey, contactDetails, cb) {
let details = contactDetails ?? Utils.getContactDetailsAsJson(publicKey, false)
const popupProperties = {
userPublicKey: publicKey,
contactDetails: contactDetails
publicKey: publicKey,
contactDetails: details
}
openPopup(sendContactRequestPopupComponent, popupProperties, cb)
}
@ -393,7 +391,7 @@ QtObject {
id: sendIDRequestPopupComponent
SendContactRequestModal {
rootStore: root.rootStore
onAccepted: root.rootStore.profileSectionStore.contactsStore.sendVerificationRequest(userPublicKey, message)
onAccepted: rootStore.contactStore.sendVerificationRequest(publicKey, message)
onClosed: destroy()
}
},
@ -413,7 +411,7 @@ QtObject {
SendContactRequestModal {
rootStore: root.rootStore
onAccepted: root.rootStore.profileSectionStore.contactsStore.sendContactRequest(userPublicKey, message)
onAccepted: rootStore.contactStore.sendContactRequest(publicKey, message)
onClosed: destroy()
}
},

View File

@ -56,7 +56,7 @@ StatusDialog {
sender: root.messageDetails.sender
amISender: root.messageDetails.amISender
messageOriginInfo: root.messageDetails.messageOriginInfo
tertiaryDetail: Utils.getElidedCompressedPk(sender.id)
tertiaryDetail: Utils.getCompressedPk(sender.id)
timestamp: root.timestamp
}

View File

@ -49,6 +49,6 @@ ActivityNotificationMessage {
enabled: root.contactDetails && !root.contactDetails.added && !root.contactDetails.isContactRequestReceived
size: StatusBaseButton.Size.Small
text: qsTr("Send Contact Request")
onClicked: Global.openContactRequestPopup(root.contactId, null)
onClicked: Global.openContactRequestPopup(root.contactId, root.contactDetails, null)
}
}

View File

@ -1,9 +1,11 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQml.Models 2.15
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups.Dialog 0.1
import utils 1.0
@ -18,27 +20,23 @@ StatusDialog {
default property alias content: contentLayout.children
readonly property string originalDisplayName: d.optionalDisplayName
property ObjectModel rightButtons
readonly property string mainDisplayName: ProfileUtils.displayName(contactDetails.localNickname, contactDetails.name,
contactDetails.displayName, contactDetails.alias)
readonly property string optionalDisplayName: ProfileUtils.displayName("", contactDetails.name, contactDetails.displayName, contactDetails.alias)
width: 480
horizontalPadding: 16
verticalPadding: 20
QtObject {
id: d
readonly property string mainDisplayName: ProfileUtils.displayName(contactDetails.localNickname, contactDetails.name,
contactDetails.displayName, contactDetails.alias)
readonly property string optionalDisplayName: ProfileUtils.displayName("", contactDetails.name, contactDetails.displayName, contactDetails.alias)
}
contentItem: ColumnLayout {
RowLayout {
Layout.fillWidth: true
spacing: Style.current.padding
UserImage {
name: d.mainDisplayName
name: root.mainDisplayName
pubkey: root.publicKey
image: Utils.addTimestampToURL(contactDetails.largeImage)
interactive: false
@ -64,7 +62,7 @@ StatusDialog {
font.bold: true
font.pixelSize: 17
elide: Text.ElideRight
text: d.mainDisplayName
text: root.mainDisplayName
}
StatusContactVerificationIcons {
id: verificationIcons
@ -83,7 +81,7 @@ StatusDialog {
id: contactSecondaryName
color: Theme.palette.baseColor1
font.pixelSize: 13
text: d.optionalDisplayName
text: root.optionalDisplayName
visible: !!contactDetails.localNickname
}
Rectangle {
@ -97,6 +95,13 @@ StatusDialog {
font.pixelSize: 13
color: Theme.palette.baseColor1
text: Utils.getElidedCompressedPk(root.publicKey)
HoverHandler {
id: keyHoverHandler
}
StatusToolTip {
text: Utils.getCompressedPk(root.publicKey)
visible: keyHoverHandler.hovered
}
}
}
EmojiHash {
@ -117,4 +122,8 @@ StatusDialog {
id: contentLayout
}
}
footer: StatusDialogFooter {
rightButtons: root.rightButtons
}
}

View File

@ -60,25 +60,23 @@ CommonContactDialog {
font.pixelSize: Theme.tertiaryTextFontSize
}
footer: StatusDialogFooter {
rightButtons: ObjectModel {
StatusFlatButton {
visible: !d.editMode
text: qsTr("Cancel")
onClicked: root.close()
}
StatusFlatButton {
visible: d.editMode
borderColor: "transparent"
type: StatusBaseButton.Type.Danger
text: qsTr("Remove nickname")
onClicked: root.removeNicknameRequested()
}
StatusButton {
enabled: root.nickname !== nicknameInput.text && nicknameInput.valid
text: d.editMode ? qsTr("Change nickname") : qsTr("Add nickname")
onClicked: root.editDone(nicknameInput.text)
}
rightButtons: ObjectModel {
StatusFlatButton {
visible: !d.editMode
text: qsTr("Cancel")
onClicked: root.close()
}
StatusFlatButton {
visible: d.editMode
borderColor: "transparent"
type: StatusBaseButton.Type.Danger
text: qsTr("Remove nickname")
onClicked: root.removeNicknameRequested()
}
StatusButton {
enabled: root.nickname !== nicknameInput.text && nicknameInput.valid
text: d.editMode ? qsTr("Change nickname") : qsTr("Add nickname")
onClicked: root.editDone(nicknameInput.text)
}
}
}

View File

@ -4,7 +4,6 @@ import QtQuick.Layouts 1.15
import QtQml.Models 2.15
import utils 1.0
import shared.controls.chat 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
@ -12,42 +11,35 @@ import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Popups.Dialog 0.1
StatusDialog {
CommonContactDialog {
id: root
property var rootStore
required property string userPublicKey
required property var contactDetails
property string challengeText: qsTr("Say who you are / why you want to become a contact...")
property string buttonText: qsTr("Send Contact Request")
property string labelText: qsTr("Why should they accept your contact request?")
property string challengeText: qsTr("Write a short message telling them who you are...")
property string buttonText: qsTr("Send contact request")
signal accepted(string message)
width: 480
horizontalPadding: Style.current.padding
verticalPadding: Style.current.bigPadding
title: qsTr("Send Contact Request to %1").arg(d.mainDisplayName)
title: qsTr("Send Contact Request")
onAboutToShow: {
messageInput.input.edit.forceActiveFocus()
// (request) update from mailserver
if (d.userDisplayName === "") {
root.rootStore.contactStore.requestContactInfo(root.userPublicKey)
root.rootStore.contactStore.requestContactInfo(root.publicKey)
d.loadingContactDetails = true
}
}
QtObject {
readonly property var d: QtObject {
id: d
readonly property int maxMsgLength: 280
readonly property int minMsgLength: 1
readonly property int msgHeight: 152
readonly property int contentSpacing: Style.current.halfPadding
property bool loadingContactDetails: false
@ -62,62 +54,47 @@ StatusDialog {
readonly property var userIcon: contactDetails.largeImage
}
Connections {
readonly property var _conn: Connections {
target: root.rootStore.contactStore.contactsModule
function onContactInfoRequestFinished(publicKey, ok) {
if (publicKey !== root.userPublicKey)
if (publicKey !== root.publicKey)
return
if (ok)
d.contactDetails = Utils.getContactDetailsAsJson(root.userPublicKey, false)
d.contactDetails = Utils.getContactDetailsAsJson(root.publicKey, false)
d.loadingContactDetails = false
}
}
contentItem: ColumnLayout {
spacing: d.contentSpacing
ProfileHeader {
Layout.fillWidth: true
displayName: d.mainDisplayName
pubkey: root.userPublicKey
icon: d.userIcon
userIsEnsVerified: d.userIsEnsVerified
isContact: d.contactDetails.isContact
trustStatus: d.contactDetails.trustStatus
onlineStatus: d.contactDetails.onlineStatus
isBlocked: d.contactDetails.isBlocked
imageSize: ProfileHeader.ImageSize.Middle
loading: d.loadingContactDetails
}
StatusInput {
id: messageInput
input.edit.objectName: "ProfileSendContactRequestModal_sayWhoYouAreInput"
Layout.fillWidth: true
charLimit: d.maxMsgLength
placeholderText: root.challengeText
input.multiline: true
minimumHeight: d.msgHeight
maximumHeight: d.msgHeight
input.verticalAlignment: TextEdit.AlignTop
validators: StatusMinLengthValidator {
minLength: d.minMsgLength
errorMessage: Utils.getErrorMessage(messageInput.errors, qsTr("who are you"))
}
StatusInput {
id: messageInput
input.edit.objectName: "ProfileSendContactRequestModal_sayWhoYouAreInput"
Layout.fillWidth: true
label: root.labelText
charLimit: d.maxMsgLength
placeholderText: root.challengeText
input.multiline: true
minimumHeight: d.msgHeight
maximumHeight: d.msgHeight
input.verticalAlignment: TextEdit.AlignTop
validators: StatusMinLengthValidator {
minLength: d.minMsgLength
errorMessage: Utils.getErrorMessage(messageInput.errors, qsTr("who are you"))
}
}
footer: StatusDialogFooter {
rightButtons: ObjectModel {
StatusButton {
objectName: "ProfileSendContactRequestModal_sendContactRequestButton"
enabled: messageInput.valid
text: root.buttonText
onClicked: {
root.accepted(messageInput.text);
root.close();
}
rightButtons: ObjectModel {
StatusFlatButton {
text: qsTr("Cancel")
onClicked: root.close()
}
StatusButton {
objectName: "ProfileSendContactRequestModal_sendContactRequestButton"
enabled: messageInput.valid
text: root.buttonText
onClicked: {
root.accepted(messageInput.text);
root.close();
}
}
}

View File

@ -174,10 +174,8 @@ Pane {
StatusButton {
objectName: "profileDialog_sendContactRequestButton"
size: StatusButton.Size.Small
text: qsTr("Send Contact Request")
onClicked: {
Global.openContactRequestPopup(root.publicKey, null)
}
text: qsTr("Send contact request")
onClicked: Global.openContactRequestPopup(root.publicKey, d.contactDetails, null)
}
}
@ -214,7 +212,7 @@ Pane {
font.weight: Font.Medium
color: Theme.palette.baseColor1
verticalAlignment: Text.AlignVCenter
text: qsTr("Contact Request Pending")
text: qsTr("Contact Request Pending")
}
}
}
@ -386,7 +384,7 @@ Pane {
enabled: !d.isContact && !d.isBlocked && d.contactRequestState !== Constants.ContactRequestState.Sent &&
d.contactDetails.trustStatus === Constants.trustStatus.untrustworthy // we have an action button otherwise
onTriggered: {
Global.openContactRequestPopup(root.publicKey, null)
Global.openContactRequestPopup(root.publicKey, d.contactDetails, null)
}
}
StatusAction {
@ -396,7 +394,7 @@ Pane {
d.outgoingVerificationStatus === Constants.verificationStatus.unverified &&
!d.isVerificationRequestReceived
onTriggered: {
Global.openSendIDRequestPopup(root.publicKey,
Global.openSendIDRequestPopup(root.publicKey, d.contactDetails,
popup => popup.accepted.connect(d.reload))
}
}
@ -552,6 +550,13 @@ Pane {
StatusBaseText {
color: Theme.palette.baseColor1
text: Utils.getElidedCompressedPk(root.publicKey)
HoverHandler {
id: keyHoverHandler
}
StatusToolTip {
text: Utils.getCompressedPk(root.publicKey)
visible: keyHoverHandler.hovered
}
}
CopyButton {
Layout.leftMargin: -4

View File

@ -143,10 +143,7 @@ StatusMenu {
objectName: "sendContactRequest_StatusItem"
enabled: !root.isMe && !root.isContact
&& !root.isBlockedContact && !root.hasPendingContactRequest && !root.isBridgedAccount
onTriggered: {
Global.openContactRequestPopup(root.selectedUserPublicKey, null)
root.close()
}
onTriggered: Global.openContactRequestPopup(root.selectedUserPublicKey, root.contactDetails, null)
}
StatusAction {
@ -159,10 +156,7 @@ StatusMenu {
&& root.outgoingVerificationStatus === Constants.verificationStatus.unverified
&& !root.hasActiveReceivedVerificationRequestFrom
&& !root.isBridgedAccount
onTriggered: {
Global.openSendIDRequestPopup(root.selectedUserPublicKey, null)
root.close()
}
onTriggered: Global.openSendIDRequestPopup(root.selectedUserPublicKey, root.contactDetails, null)
}
// TODO Mark as ID verified
StatusAction {
@ -193,10 +187,7 @@ StatusMenu {
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)
root.close()
}
onTriggered: Global.openNicknamePopupRequested(root.selectedUserPublicKey, root.contactDetails)
}
StatusMenuSeparator {

View File

@ -38,8 +38,8 @@ QtObject {
signal openImagePopup(var image, string url)
signal openProfilePopupRequested(string publicKey, var parentPopup, var cb)
signal openActivityCenterPopupRequested()
signal openSendIDRequestPopup(string publicKey, var cb)
signal openContactRequestPopup(string publicKey, var cb)
signal openSendIDRequestPopup(string publicKey, var contactDetails, var cb)
signal openContactRequestPopup(string publicKey, var contactDetails, var cb)
signal removeContactRequested(string displayName, string publicKey)
signal openInviteFriendsToCommunityPopup(var community, var communitySectionModule, var cb)
signal openIncomingIDRequestPopup(string publicKey, var cb)