diff --git a/storybook/pages/ProfileDialogViewPage.qml b/storybook/pages/ProfileDialogViewPage.qml index 630fb27930..52004d6d39 100644 --- a/storybook/pages/ProfileDialogViewPage.qml +++ b/storybook/pages/ProfileDialogViewPage.qml @@ -116,8 +116,9 @@ SplitView { popupParent: root rootStore: QtObject { property var contactStore: QtObject { - function changeContactNickname(publicKey, newNickname) { - logs.logEvent("rootStore::contactStore::changeContactNickname", ["publicKey", "newNickname"], arguments) + function changeContactNickname(publicKey, newNickname, displayName, isEdit) { + logs.logEvent("rootStore::contactsStore::changeContactNickname", ["publicKey", "newNickname", "displayName", "isEdit"], arguments) + localNickname.text = newNickname } function blockContact(publicKey) { @@ -175,7 +176,7 @@ SplitView { logs.logEvent("profileStore::copyToClipboard", ["text"], arguments) } function requestProfileShowcase(publicKey) { - console.warn("STUB: requestProfileShowcase(publicKey)") + logs.logEvent("profileStore::requestProfileShowcase", ["publicKey"], arguments) } readonly property var profileShowcaseCommunitiesModel: CommunitiesModel {} @@ -222,6 +223,11 @@ SplitView { function getLinkToProfile(publicKey) { return Constants.userLinkPrefix + publicKey } + + function changeContactNickname(publicKey, newNickname, displayName, isEdit) { + logs.logEvent("contactsStore::changeContactNickname", ["publicKey", "newNickname", "displayName", "isEdit"], arguments) + localNickname.text = newNickname + } } walletStore: QtObject { @@ -281,12 +287,13 @@ SplitView { Label { text: "localNickname:" } TextField { id: localNickname - text: "MockNickname" + text: "Nick" placeholderText: "Local Nickname" } Label { text: "displayName:" } TextField { id: displayName + text: "Alex Pella" placeholderText: "Display Name" } } diff --git a/ui/app/AppLayouts/Profile/stores/ContactsStore.qml b/ui/app/AppLayouts/Profile/stores/ContactsStore.qml index e27d8f52dd..21577e49dd 100644 --- a/ui/app/AppLayouts/Profile/stores/ContactsStore.qml +++ b/ui/app/AppLayouts/Profile/stores/ContactsStore.qml @@ -61,8 +61,21 @@ QtObject { root.contactsModule.removeContact(pubKey) } - function changeContactNickname(pubKey, nickname) { + function changeContactNickname(pubKey, nickname, displayName, isEdit) { root.contactsModule.changeContactNickname(pubKey, nickname) + + let message = "" + if (nickname === "") { // removed nickname + message = qsTr("Nickname for %1 removed").arg(displayName) + } else { + if (isEdit) + message = qsTr("Nickname for %1 changed").arg(displayName) // changed nickname + else + message = qsTr("Nickname for %1 added").arg(displayName) // added a new nickname + } + if (!!message) { + Global.displaySuccessToastMessage(message) + } } function sendContactRequest(pubKey, message) { diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index 3ebfbd2428..2dcd6d5727 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -60,7 +60,6 @@ QtObject { Global.unblockContactRequested.connect(openUnblockContactPopup) Global.openChangeProfilePicPopup.connect(openChangeProfilePicPopup) Global.openBackUpSeedPopup.connect(openBackUpSeedPopup) - Global.openEditDisplayNamePopup.connect(openEditDisplayNamePopup) Global.openPinnedMessagesPopupRequested.connect(openPinnedMessagesPopup) Global.openCommunityProfilePopupRequested.connect(openCommunityProfilePopup) Global.createCommunityPopupRequested.connect(openCreateCommunityPopup) @@ -133,8 +132,8 @@ QtObject { openPopup(profilePopupComponent, {publicKey: publicKey, parentPopup: parentPopup}, cb) } - function openNicknamePopup(publicKey: string, nickname: string, subtitle: string) { - openPopup(nicknamePopupComponent, {publicKey: publicKey, nickname: nickname, "headerSettings.subTitle": subtitle}) + function openNicknamePopup(publicKey: string, contactDetails) { + openPopup(nicknamePopupComponent, {publicKey, contactDetails}) } function openBlockContactPopup(publicKey: string, contactName: string) { @@ -154,10 +153,6 @@ QtObject { openPopup(backupSeedModalComponent) } - function openEditDisplayNamePopup() { - openPopup(displayNamePopupComponent) - } - function openCommunityProfilePopup(store, community, communitySectionModule) { openPopup(communityProfilePopup, { store: store, community: community, communitySectionModule: communitySectionModule}) } @@ -431,15 +426,6 @@ QtObject { } }, - // FIXME remove, unused - Component { - id: displayNamePopupComponent - DisplayNamePopup { - profileStore: rootStore.profileSectionStore.profileStore - onClosed: destroy() - } - }, - Component { id: downloadPageComponent DownloadPage { @@ -527,11 +513,14 @@ QtObject { NicknamePopup { onEditDone: { if (nickname !== newNickname) { - rootStore.contactStore.changeContactNickname(publicKey, newNickname) - Global.contactRenamed(publicKey) + rootStore.contactStore.changeContactNickname(publicKey, newNickname, originalDisplayName, !!nickname) } close() } + onRemoveNicknameRequested: { + rootStore.contactStore.changeContactNickname(publicKey, "", originalDisplayName, true) + close() + } onClosed: destroy() } }, diff --git a/ui/imports/shared/popups/CommonContactDialog.qml b/ui/imports/shared/popups/CommonContactDialog.qml new file mode 100644 index 0000000000..a1e62308a1 --- /dev/null +++ b/ui/imports/shared/popups/CommonContactDialog.qml @@ -0,0 +1,120 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Components 0.1 +import StatusQ.Popups.Dialog 0.1 + +import utils 1.0 +import shared.controls 1.0 +import shared.controls.chat 1.0 + +StatusDialog { + id: root + + required property string publicKey + required property var contactDetails + + default property alias content: contentLayout.children + + readonly property string originalDisplayName: d.optionalDisplayName + + 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 + pubkey: root.publicKey + image: Utils.addTimestampToURL(contactDetails.largeImage) + interactive: false + imageWidth: 60 + imageHeight: 60 + ensVerified: contactDetails.ensVerified + onlineStatus: contactDetails.onlineStatus + } + + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 0 + + Item { + id: contactRow + Layout.fillWidth: true + Layout.preferredHeight: childrenRect.height + StatusBaseText { + id: contactName + anchors.left: parent.left + width: Math.min(implicitWidth, contactRow.width - verificationIcons.width - verificationIcons.anchors.leftMargin) + font.bold: true + font.pixelSize: 17 + elide: Text.ElideRight + text: d.mainDisplayName + } + StatusContactVerificationIcons { + id: verificationIcons + anchors.left: contactName.right + anchors.leftMargin: Style.current.halfPadding + anchors.verticalCenter: contactName.verticalCenter + isContact: contactDetails.isContact + trustIndicator: contactDetails.trustStatus + isBlocked: contactDetails.isBlocked + tiny: false + } + } + RowLayout { + spacing: Style.current.halfPadding + StatusBaseText { + id: contactSecondaryName + color: Theme.palette.baseColor1 + font.pixelSize: 13 + text: d.optionalDisplayName + visible: !!contactDetails.localNickname + } + Rectangle { + Layout.preferredWidth: 4 + Layout.preferredHeight: 4 + radius: width/2 + color: Theme.palette.baseColor1 + visible: contactSecondaryName.visible + } + StatusBaseText { + font.pixelSize: 13 + color: Theme.palette.baseColor1 + text: Utils.getElidedCompressedPk(root.publicKey) + } + } + EmojiHash { + Layout.topMargin: 4 + publicKey: root.publicKey + oneRow: true + } + } + } + + StatusDialogDivider { + Layout.fillWidth: true + Layout.topMargin: 15 + Layout.bottomMargin: 15 + } + + ColumnLayout { + id: contentLayout + } + } +} diff --git a/ui/imports/shared/popups/DisplayNamePopup.qml b/ui/imports/shared/popups/DisplayNamePopup.qml deleted file mode 100644 index 200cf814fa..0000000000 --- a/ui/imports/shared/popups/DisplayNamePopup.qml +++ /dev/null @@ -1,52 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.3 -import QtQuick.Layouts 1.3 -import QtQml.Models 2.3 - -import utils 1.0 -import shared 1.0 -import shared.panels 1.0 -import shared.popups 1.0 -import shared.controls 1.0 -import StatusQ.Controls 0.1 -import StatusQ.Popups 0.1 -import StatusQ.Controls.Validators 0.1 - -StatusModal { - id: root - property var profileStore - - width: 420 - height: 250 - closePolicy: Popup.NoAutoClose - headerSettings.title: qsTr("Edit") - contentItem: Item { - StatusInput { - id: displayNameInput - input.edit.objectName: "DisplayNamePopup_displayNameInput" - width: parent.width - Style.current.padding - anchors.top: parent.top - anchors.topMargin: Style.current.padding - anchors.horizontalCenter: parent.horizontalCenter - placeholderText: qsTr("Display Name") - input.text: root.profileStore.displayName - validators: Constants.validators.displayName - } - } - - rightButtons: [ - StatusButton { - id: okBtn - objectName: "DisplayNamePopup_okButton" - text: qsTr("OK") - enabled: !!displayNameInput.text && displayNameInput.valid - onClicked: { - root.profileStore.setDisplayName(displayNameInput.text) - root.close() - } - } - ] - - onOpened: { displayNameInput.input.edit.forceActiveFocus() } -} - diff --git a/ui/imports/shared/popups/NicknamePopup.qml b/ui/imports/shared/popups/NicknamePopup.qml index 602c93519c..ab97b89c8c 100644 --- a/ui/imports/shared/popups/NicknamePopup.qml +++ b/ui/imports/shared/popups/NicknamePopup.qml @@ -1,85 +1,84 @@ -import QtQuick 2.14 - -import utils 1.0 -import shared.controls 1.0 +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQml.Models 2.15 import StatusQ 0.1 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Controls 0.1 import StatusQ.Controls.Validators 0.1 -import StatusQ.Popups 0.1 +import StatusQ.Popups.Dialog 0.1 -StatusModal { - id: popup +import shared.controls 1.0 - width: 400 - height: 340 - anchors.centerIn: parent +CommonContactDialog { + id: root - headerSettings.title: qsTr("Nickname") - headerSettings.subTitleElide: Text.ElideMiddle - - /*required*/ property string publicKey - property string nickname - - readonly property int nicknameLength: nicknameInput.text.length - readonly property int maxNicknameLength: 32 + readonly property string nickname: contactDetails.localNickname signal editDone(string newNickname) + signal removeNicknameRequested() + + title: d.editMode ? qsTr("Edit nickname") : qsTr("Add nickname") onOpened: { nicknameInput.input.edit.forceActiveFocus() } - contentItem: Item { - width: popup.width - height: childrenRect.height + readonly property var d: QtObject { + id: d + readonly property bool editMode: root.nickname !== "" + readonly property int maxNicknameLength: 32 + } - Column { - anchors.top: parent.top - anchors.topMargin: 16 - anchors.horizontalCenter: parent.horizontalCenter - width: parent.width - 32 - spacing: 16 - - StatusBaseText { - id: descriptionText - text: qsTr("Nicknames help you identify others in Status. Only you can see the nicknames you’ve added") - wrapMode: Text.WordWrap - color: Theme.palette.baseColor1 - width: parent.width + StatusInput { + Layout.fillWidth: true + id: nicknameInput + label: qsTr("Nickname") + input.clearable: true + text: root.nickname + charLimit: d.maxNicknameLength + validationMode: StatusInput.ValidationMode.IgnoreInvalidInput + validators: [ + StatusValidator { + validatorObj: RXValidator { regularExpression: /^[\w\d_ -]*$/u } + validate: (value) => validatorObj.test(value) } - - StatusInput { - id: nicknameInput - placeholderText: qsTr("Nickname") - input.clearable: true - width: parent.width - text: popup.nickname - charLimit: maxNicknameLength - validationMode: StatusInput.ValidationMode.IgnoreInvalidInput - validators: [ - StatusValidator { - validatorObj: RXValidator { regularExpression: /^[\w\d_ -]*$/u } - validate: (value) => validatorObj.test(value) - } - ] - Keys.onReleased: { - if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { - editDone(nicknameInput.text) - } - } + ] + Keys.onReleased: { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + root.editDone(nicknameInput.text) } } } - rightButtons: [ - StatusButton { - id: doneBtn - text: qsTr("Done") - enabled: nicknameInput.valid - onClicked: editDone(nicknameInput.text) + StatusBaseText { + Layout.fillWidth: true + text: qsTr("Nicknames help you identify others and are only visible to you") + wrapMode: Text.WordWrap + color: Theme.palette.baseColor1 + 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) + } } - ] + } } diff --git a/ui/imports/shared/popups/UserStatusContextMenu.qml b/ui/imports/shared/popups/UserStatusContextMenu.qml index 4c15a251ce..c803ae4587 100644 --- a/ui/imports/shared/popups/UserStatusContextMenu.qml +++ b/ui/imports/shared/popups/UserStatusContextMenu.qml @@ -6,6 +6,7 @@ import QtQml.Models 2.3 import utils 1.0 import shared.controls.chat 1.0 import shared.panels 1.0 +import shared.controls.chat.menuItems 1.0 import StatusQ.Components 0.1 import StatusQ.Popups 0.1 @@ -28,16 +29,23 @@ StatusMenu { StatusMenuSeparator { } - StatusAction { + ViewProfileMenuItem { objectName: "userStatusViewMyProfileAction" - text: qsTr("View My Profile") - icon.name: "profile" onTriggered: { Global.openProfilePopup(root.store.userProfileInst.pubKey) root.close() } } + StatusAction { + text: qsTr("Copy link to profile") + icon.name: "copy" + onTriggered: { + Utils.copyToClipboard(root.store.contactStore.getLinkToProfile(root.store.userProfileInst.pubKey)) + root.close() + } + } + StatusMenuSeparator { } diff --git a/ui/imports/shared/popups/qmldir b/ui/imports/shared/popups/qmldir index 0603b853f3..d23da3c98e 100644 --- a/ui/imports/shared/popups/qmldir +++ b/ui/imports/shared/popups/qmldir @@ -1,4 +1,5 @@ ChatCommandsPopup 1.0 ChatCommandsPopup.qml +CommonContactDialog 1.0 CommonContactDialog.qml BlockContactConfirmationDialog 1.0 BlockContactConfirmationDialog.qml SettingsDirtyToastMessage 1.0 SettingsDirtyToastMessage.qml ConfirmationDialog 1.0 ConfirmationDialog.qml @@ -16,7 +17,6 @@ UserStatusContextMenu 1.0 UserStatusContextMenu.qml ProfileDialog 1.0 ProfileDialog.qml ImageCropWorkflow 1.0 ImageCropWorkflow.qml ImportCommunityPopup 1.0 ImportCommunityPopup.qml -DisplayNamePopup 1.0 DisplayNamePopup.qml SendContactRequestModal 1.0 SendContactRequestModal.qml GetSyncCodeInstructionsPopup 1.0 GetSyncCodeInstructionsPopup.qml NoPermissionsToJoinPopup 1.0 NoPermissionsToJoinPopup.qml diff --git a/ui/imports/shared/views/ProfileDialogView.qml b/ui/imports/shared/views/ProfileDialogView.qml index 708914078a..e73c2d8bfe 100644 --- a/ui/imports/shared/views/ProfileDialogView.qml +++ b/ui/imports/shared/views/ProfileDialogView.qml @@ -111,15 +111,6 @@ Pane { d.reload() } } - - readonly property var conns4: Connections { - target: Global - - function onContactRenamed(pubKey) { - if (pubKey === root.publicKey) - d.reload() - } - } } function reload() { @@ -395,7 +386,6 @@ Pane { enabled: !d.isContact && !d.isBlocked && d.contactRequestState !== Constants.ContactRequestState.Sent && d.contactDetails.trustStatus === Constants.trustStatus.untrustworthy // we have an action button otherwise onTriggered: { - moreMenu.close() Global.openContactRequestPopup(root.publicKey, null) } } @@ -406,7 +396,6 @@ Pane { d.outgoingVerificationStatus === Constants.verificationStatus.unverified && !d.isVerificationRequestReceived onTriggered: { - moreMenu.close() Global.openSendIDRequestPopup(root.publicKey, popup => popup.accepted.connect(d.reload)) } @@ -417,7 +406,6 @@ Pane { icon.name: "checkmark-circle" enabled: d.isContact && !d.isBlocked && !d.isTrusted && d.isVerificationRequestSent onTriggered: { - moreMenu.close() Global.openOutgoingIDRequestPopup(root.publicKey, popup => popup.closed.connect(d.reload)) } @@ -426,9 +414,7 @@ Pane { text: d.userNickName ? qsTr("Edit nickname") : qsTr("Add nickname") icon.name: "edit_pencil" onTriggered: { - moreMenu.close() - Global.openNicknamePopupRequested(root.publicKey, d.userNickName, - "%1 (%2)".arg(d.optionalDisplayName).arg(Utils.getElidedCompressedPk(root.publicKey))) + Global.openNicknamePopupRequested(root.publicKey, d.contactDetails) } } StatusAction { @@ -436,7 +422,6 @@ Pane { icon.name: "qr" enabled: !d.isCurrentUser onTriggered: { - moreMenu.close() Global.openPopup(shareProfileCmp) } } @@ -444,19 +429,23 @@ Pane { text: qsTr("Copy link to profile") icon.name: "copy" onTriggered: { - moreMenu.close() root.profileStore.copyToClipboard(d.linkToProfile) } } StatusMenuSeparator {} - // TODO Remove nickname + StatusAction { + text: qsTr("Remove nickname") + icon.name: "delete" + type: StatusAction.Type.Danger + enabled: !d.isCurrentUser && !!d.contactDetails.localNickname + onTriggered: root.contactsStore.changeContactNickname(root.publicKey, "", d.optionalDisplayName, true) + } StatusAction { text: qsTr("Unblock user") icon.name: "cancel" type: StatusAction.Type.Danger enabled: d.isBlocked onTriggered: { - moreMenu.close() Global.unblockContactRequested(root.publicKey, d.mainDisplayName) } } @@ -466,7 +455,6 @@ Pane { type: StatusAction.Type.Danger enabled: d.contactDetails.trustStatus === Constants.trustStatus.unknown && !d.isBlocked onTriggered: { - moreMenu.close() if (d.isContact && !d.isTrusted && d.isVerificationRequestReceived) root.contactsStore.verifiedUntrustworthy(root.publicKey) else @@ -480,7 +468,6 @@ Pane { type: StatusAction.Type.Danger enabled: d.contactDetails.trustStatus === Constants.trustStatus.untrustworthy && !d.isBlocked onTriggered: { - moreMenu.close() root.contactsStore.removeTrustStatus(root.publicKey) d.reload() } @@ -491,7 +478,6 @@ Pane { type: StatusAction.Type.Danger enabled: d.isContact && d.isTrusted onTriggered: { - moreMenu.close() removeVerificationConfirmationDialog.open() } } @@ -501,8 +487,7 @@ Pane { type: StatusAction.Type.Danger enabled: d.isContact && !d.isBlocked && d.contactRequestState !== Constants.ContactRequestState.Sent onTriggered: { - Global.removeContactRequested(d.mainDisplayName, root.publicKey); - moreMenu.close(); + Global.removeContactRequested(d.mainDisplayName, root.publicKey) } } StatusAction { @@ -511,7 +496,6 @@ Pane { type: StatusAction.Type.Danger enabled: !d.isBlocked onTriggered: { - moreMenu.close() Global.blockContactRequested(root.publicKey, d.mainDisplayName) } } diff --git a/ui/imports/shared/views/chat/ProfileContextMenu.qml b/ui/imports/shared/views/chat/ProfileContextMenu.qml index 23440be94f..4a6457f7de 100644 --- a/ui/imports/shared/views/chat/ProfileContextMenu.qml +++ b/ui/imports/shared/views/chat/ProfileContextMenu.qml @@ -194,8 +194,7 @@ StatusMenu { icon.name: "edit_pencil" enabled: !root.isMe && !root.isBridgedAccount onTriggered: { - Global.openNicknamePopupRequested(root.selectedUserPublicKey, contactDetails.localNickname, - "%1 (%2)".arg(root.selectedUserDisplayName).arg(Utils.getElidedCompressedPk(root.selectedUserPublicKey))) + Global.openNicknamePopupRequested(root.selectedUserPublicKey, root.contactDetails) root.close() } } @@ -206,6 +205,14 @@ StatusMenu { || removeUntrustworthyMarkMenuItem.enabled } + StatusAction { + 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) + } + StatusAction { id: unblockAction objectName: "unblock_StatusItem" diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml index 6f04b04aac..78c0eeac1d 100644 --- a/ui/imports/utils/Global.qml +++ b/ui/imports/utils/Global.qml @@ -31,13 +31,12 @@ QtObject { signal openPopupRequested(var popupComponent, var params) signal closePopupRequested() - signal openNicknamePopupRequested(string publicKey, string nickname, string subtitle) + signal openNicknamePopupRequested(string publicKey, var contactDetails) signal openDownloadModalRequested(bool available, string version, string url) signal openChangeProfilePicPopup(var cb) signal openBackUpSeedPopup() signal openImagePopup(var image, string url) signal openProfilePopupRequested(string publicKey, var parentPopup, var cb) - signal openEditDisplayNamePopup() signal openActivityCenterPopupRequested() signal openSendIDRequestPopup(string publicKey, var cb) signal openContactRequestPopup(string publicKey, var cb) @@ -49,7 +48,6 @@ QtObject { signal openDownloadImageDialog(string imageSource) signal openExportControlNodePopup(var community) signal openImportControlNodePopup(var community) - signal contactRenamed(string publicKey) signal openTransferOwnershipPopup(string communityId, string communityName, string communityLogo, @@ -131,4 +129,15 @@ QtObject { menu.popup() return menu } + + function displaySuccessToastMessage(title: string, subTitle = "") { + root.displayToastMessage( + title, + subTitle, + "checkmark-circle", + false, + Constants.ephemeralNotificationType.success, + "" + ) + } }