From 7a407662e25c74592098a8bda5935b9749907e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Tue, 27 Sep 2022 23:26:26 +0200 Subject: [PATCH] feat: New Profile Modal Fixes: #7360 --- src/app/modules/main/module.nim | 3 +- src/app/modules/shared_models/user_model.nim | 3 +- .../StatusContactVerificationIcons.qml | 68 +- .../src/StatusQ/Controls/StatusBaseButton.qml | 3 +- .../StatusQ/Controls/StatusPickerButton.qml | 2 +- .../src/StatusQ/Core/StatusCenteredFlow.qml | 6 +- ui/StatusQ/src/StatusQ/Core/Theme/Theme.qml | 2 +- .../StatusQ/Popups/Dialog/StatusDialog.qml | 5 +- .../src/StatusQ/Popups/StatusColorDialog.qml | 4 +- .../src/assets/img/icons/checkmark-circle.svg | 3 +- .../src/assets/img/icons/tiny/exclamation.svg | 3 + .../{subtract.svg => tiny-exclamation.svg} | 0 ui/StatusQ/src/assets/img/icons/warning.svg | 4 +- .../Chat/popups/ContactRequestsPopup.qml | 12 +- .../Chat/popups/RenameGroupPopup.qml | 3 +- .../popups/community/CommunityDetailPopup.qml | 2 +- .../popups/community/CreateCategoryPopup.qml | 9 +- .../AppLayouts/Chat/stores/MessageStore.qml | 6 +- ui/app/AppLayouts/Chat/stores/RootStore.qml | 4 +- ui/app/AppLayouts/Chat/stores/StickerData.qml | 12 +- .../Chat/stores/StickerPackData.qml | 9 +- .../AppLayouts/Chat/views/ChatContentView.qml | 2 +- .../Chat/views/ChatContextMenuView.qml | 3 - ui/app/AppLayouts/Chat/views/ChatView.qml | 16 +- .../popups/BeforeGetStartedModal.qml | 1 - ui/app/AppLayouts/Profile/ProfileLayout.qml | 5 +- .../controls/WalletAccountDelegate.qml | 2 + .../Profile/panels/ContactPanel.qml | 2 +- .../Profile/stores/ProfileSectionStore.qml | 2 +- .../AppLayouts/Profile/views/ContactsView.qml | 60 +- .../views/EnsTermsAndConditionsView.qml | 2 +- .../Profile/views/MessagingView.qml | 2 +- .../Profile/views/MyProfileView.qml | 12 +- .../Profile/views/NotificationsView.qml | 2 +- .../views/profile/MyProfilePreview.qml | 12 +- .../views/profile/MyProfileSettingsView.qml | 3 - .../AppLayouts/Wallet/views/RightTabView.qml | 1 - ui/app/mainui/AppMain.qml | 116 ++- ui/app/mainui/AppSearch.qml | 1 + ui/imports/shared/controls/AddressInput.qml | 2 +- .../shared/controls/CopyToClipBoardButton.qml | 14 +- ui/imports/shared/controls/EmojiHash.qml | 6 +- .../shared/controls/chat/ProfileHeader.qml | 23 +- .../chat/menuItems/MuteChatMenuItem.qml | 2 +- .../chat/menuItems/SendMessageMenuItem.qml | 2 +- .../AcceptRejectOptionsButtonsPanel.qml | 28 +- ui/imports/shared/panels/ImageLoader.qml | 2 +- ui/imports/shared/panels/ModuleWarning.qml | 8 +- .../shared/panels/ProfileBioSocialsPanel.qml | 20 +- ui/imports/shared/panels/Separator.qml | 10 +- .../popups/BlockContactConfirmationDialog.qml | 1 - .../ContactVerificationRequestPopup.qml | 12 +- ui/imports/shared/popups/DisplayNamePopup.qml | 2 +- ui/imports/shared/popups/NicknamePopup.qml | 11 +- ...utgoingContactVerificationRequestPopup.qml | 110 +++ ui/imports/shared/popups/ProfileDialog.qml | 29 + ui/imports/shared/popups/ProfilePopup.qml | 474 ------------ .../shared/popups/SendContactRequestModal.qml | 13 +- .../UnblockContactConfirmationDialog.qml | 1 - ui/imports/shared/popups/qmldir | 3 +- .../status/StatusChatImageQtyValidator.qml | 2 +- .../status/StatusETHTransactionModal.qml | 2 +- ui/imports/shared/status/StatusGifPopup.qml | 4 +- .../shared/status/StatusStickersPopup.qml | 2 +- ui/imports/shared/stores/RootStore.qml | 1 - ui/imports/shared/views/AssetsView.qml | 2 +- ui/imports/shared/views/HistoryView.qml | 2 +- ui/imports/shared/views/ProfileDialogView.qml | 704 ++++++++++++++++++ ui/imports/shared/views/ProfileView.qml | 484 ------------ .../views/chat/MessageContextMenuView.qml | 24 +- ui/imports/shared/views/qmldir | 2 +- ui/imports/utils/Constants.qml | 12 +- ui/imports/utils/Global.qml | 105 ++- ui/imports/utils/Utils.qml | 2 +- 74 files changed, 1222 insertions(+), 1306 deletions(-) create mode 100644 ui/StatusQ/src/assets/img/icons/tiny/exclamation.svg rename ui/StatusQ/src/assets/img/icons/tiny/{subtract.svg => tiny-exclamation.svg} (100%) create mode 100644 ui/imports/shared/popups/OutgoingContactVerificationRequestPopup.qml create mode 100644 ui/imports/shared/popups/ProfileDialog.qml delete mode 100644 ui/imports/shared/popups/ProfilePopup.qml create mode 100644 ui/imports/shared/views/ProfileDialogView.qml delete mode 100644 ui/imports/shared/views/ProfileView.qml diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index 31cc1565a9..cb2ba2bc29 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -760,6 +760,7 @@ method getContactDetailsAsJson*[T](self: Module[T], publicKey: string, getVerifi requestStatus = self.getVerificationRequestFrom(publicKey).status.int let jsonObj = %* { "displayName": name, + "optionalName": self.controller.getContactDetails(contact.id).optionalName, # original display name, if renamed "displayIcon": contact.image.thumbnail, "publicKey": contact.id, "name": contact.name, @@ -982,4 +983,4 @@ method runAuthenticationPopup*[T](self: Module[T], keyUid: string, bip44Path: st self.keycardSharedModule.runFlow(keycard_shared_module.FlowType.Authentication, keyUid, bip44Path, txHash) method onDisplayKeycardSharedModuleFlow*[T](self: Module[T]) = - self.view.emitDisplayKeycardSharedModuleFlow() \ No newline at end of file + self.view.emitDisplayKeycardSharedModuleFlow() diff --git a/src/app/modules/shared_models/user_model.nim b/src/app/modules/shared_models/user_model.nim index 08f3fb639e..7211ff6437 100644 --- a/src/app/modules/shared_models/user_model.nim +++ b/src/app/modules/shared_models/user_model.nim @@ -229,6 +229,7 @@ QtObject: self.items[ind].ensName = ensName self.items[ind].localNickname = localNickname self.items[ind].alias = alias + self.items[ind].icon = icon self.items[ind].isUntrustworthy = isUntrustworthy let index = self.createIndex(ind, 0, nil) @@ -310,4 +311,4 @@ QtObject: return self.items.map(i => i.pubKey) proc containsItemWithPubKey*(self: Model, pubKey: string): bool = - return self.findIndexByPubKey(pubKey) != -1 \ No newline at end of file + return self.findIndexByPubKey(pubKey) != -1 diff --git a/ui/StatusQ/src/StatusQ/Components/StatusContactVerificationIcons.qml b/ui/StatusQ/src/StatusQ/Components/StatusContactVerificationIcons.qml index 7d5de32393..2067860e57 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusContactVerificationIcons.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusContactVerificationIcons.qml @@ -1,21 +1,23 @@ -import QtQuick 2.0 +import QtQuick 2.14 import StatusQ.Core 0.1 +import StatusQ.Controls 0.1 import StatusQ.Core.Theme 0.1 Row { id: root property bool isContact: false - property var trustIndicator: StatusContactVerificationIcons.TrustedType.None + property int trustIndicator: StatusContactVerificationIcons.TrustedType.None + property bool tiny: true property StatusAssetSettings mutualConnectionIcon: StatusAssetSettings { - name: "tiny/tiny-contact" + name: root.tiny ? "tiny/tiny-contact" : "tiny/contact" color: Theme.palette.indirectColor1 - width: dummyImage.width - height: dummyImage.height - bgWidth: 10 - bgHeight: 10 + width: Math.min(bgWidth, dummyImage.width) + height: Math.min(bgHeight, dummyImage.height) + bgWidth: root.tiny ? 10 : 16.5 + bgHeight: root.tiny ? 10 : 16.5 bgColor: Theme.palette.primaryColor1 // Only used to get implicit width and height from the actual image property Image dummyImage: Image { @@ -26,12 +28,13 @@ Row { property StatusAssetSettings trustContactIcon: StatusAssetSettings { // None and Untrustworthy types, same aspect (Icon will not be visible in case of None type): - name: root.trustIndicator === StatusContactVerificationIcons.TrustedType.Verified ? "tiny/tiny-checkmark" : "tiny/subtract" + name: root.trustIndicator === StatusContactVerificationIcons.TrustedType.Verified ? root.tiny ? "tiny/tiny-checkmark" : "tiny/checkmark" + : root.tiny ? "tiny/tiny-exclamation" : "tiny/exclamation" color: Theme.palette.indirectColor1 - width: dummyImage.width - height: dummyImage.height - bgWidth: 10 - bgHeight: 10 + width: Math.min(bgWidth, dummyImage.width) + height: Math.min(bgHeight, dummyImage.height) + bgWidth: root.tiny ? 10 : 16 + bgHeight: root.tiny ? 10 : 16 bgColor: root.trustIndicator === StatusContactVerificationIcons.TrustedType.Verified ? Theme.palette.primaryColor1 : Theme.palette.dangerColor1 // Only used to get implicit width and height from the actual image property Image dummyImage: Image { @@ -49,27 +52,34 @@ Row { spacing: 4 visible: root.isContact || (root.trustIndicator !== StatusContactVerificationIcons.TrustedType.None) + HoverHandler { + id: hoverHandler + } + + StatusToolTip { + text: { + if (root.isContact) { + if (root.trustIndicator === StatusContactVerificationIcons.TrustedType.Verified) + return qsTr("Verified contact") + if (root.trustIndicator === StatusContactVerificationIcons.TrustedType.Untrustworthy) + return qsTr("Untrustworthy contact") + return qsTr("Contact") + } + if (root.trustIndicator === StatusContactVerificationIcons.TrustedType.Untrustworthy) + return qsTr("Untrustworthy") + return "" + } + + visible: hoverHandler.hovered && text + } + StatusRoundIcon { - visible: root.isContact - asset.name: root.mutualConnectionIcon.name - asset.width: root.mutualConnectionIcon.width - asset.height: root.mutualConnectionIcon.height - asset.rotation: root.mutualConnectionIcon.rotation - asset.color: root.mutualConnectionIcon.color - asset.bgColor: root.mutualConnectionIcon.bgColor - asset.bgWidth: root.mutualConnectionIcon.bgWidth - asset.bgHeight: root.mutualConnectionIcon.bgHeight + visible: root.isContact && root.trustIndicator !== StatusContactVerificationIcons.TrustedType.Verified + asset: root.mutualConnectionIcon } StatusRoundIcon { visible: root.trustIndicator !== StatusContactVerificationIcons.TrustedType.None - asset.name: root.trustContactIcon.name - asset.width: root.trustContactIcon.width - asset.height: root.trustContactIcon.height - asset.rotation: root.trustContactIcon.rotation - asset.color: root.trustContactIcon.color - asset.bgColor: root.trustContactIcon.bgColor - asset.bgWidth: root.trustContactIcon.bgWidth - asset.bgHeight: root.trustContactIcon.bgHeight + asset: root.trustContactIcon } } diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusBaseButton.qml b/ui/StatusQ/src/StatusQ/Controls/StatusBaseButton.qml index 6ab39fb1ac..02d91a39ed 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusBaseButton.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusBaseButton.qml @@ -70,6 +70,7 @@ Button { icon.height: 24 icon.width: 24 + icon.color: d.textColor background: Rectangle { radius: root.radius @@ -90,7 +91,7 @@ Button { rotation: root.asset.rotation opacity: !loading && root.icon.name !== "" visible: root.icon.name !== "" - color: d.textColor + color: root.icon.color } StatusEmoji { Layout.preferredWidth: visible ? root.icon.width : 0 diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusPickerButton.qml b/ui/StatusQ/src/StatusQ/Controls/StatusPickerButton.qml index 949dd9dae2..3a6ad9bd3f 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusPickerButton.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusPickerButton.qml @@ -14,7 +14,7 @@ Button { property var type: StatusPickerButton.Type.Next /*! - \qmlproperty StatusAssetSettings StatusPickerButton::image + \qmlproperty StatusAssetSettings StatusPickerButton::asset This property holds the image settings information. */ property StatusAssetSettings asset: StatusAssetSettings { diff --git a/ui/StatusQ/src/StatusQ/Core/StatusCenteredFlow.qml b/ui/StatusQ/src/StatusQ/Core/StatusCenteredFlow.qml index 7fe978ad53..bd329978fc 100644 --- a/ui/StatusQ/src/StatusQ/Core/StatusCenteredFlow.qml +++ b/ui/StatusQ/src/StatusQ/Core/StatusCenteredFlow.qml @@ -57,10 +57,14 @@ Item { onCenteredChanged: flow.onPositioningComplete() + implicitHeight: flow.implicitHeight + Flow { id: flow - anchors.fill: parent + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right onPositioningComplete: { if (!root.centered || children.length === 0) diff --git a/ui/StatusQ/src/StatusQ/Core/Theme/Theme.qml b/ui/StatusQ/src/StatusQ/Core/Theme/Theme.qml index 662f0f203e..ddff0de891 100644 --- a/ui/StatusQ/src/StatusQ/Core/Theme/Theme.qml +++ b/ui/StatusQ/src/StatusQ/Core/Theme/Theme.qml @@ -14,7 +14,7 @@ QtObject { FontSizeXXL } - property QtObject palette: StatusLightTheme {} + property ThemePalette palette: StatusLightTheme {} property int primaryTextFontSize: 15 property int secondaryTextFontSize: 14 diff --git a/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialog.qml b/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialog.qml index feed40321e..5ba571f001 100644 --- a/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialog.qml +++ b/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialog.qml @@ -10,6 +10,8 @@ import StatusQ.Core.Theme 0.1 Dialog { id: root + property string subtitle + anchors.centerIn: Overlay.overlay padding: 16 @@ -25,8 +27,9 @@ Dialog { background: StatusDialogBackground {} header: StatusDialogHeader { - visible: root.title + visible: root.title || root.subtitle headline.title: root.title + headline.subtitle: root.subtitle actions.closeButton.onClicked: root.close() } diff --git a/ui/StatusQ/src/StatusQ/Popups/StatusColorDialog.qml b/ui/StatusQ/src/StatusQ/Popups/StatusColorDialog.qml index a5209a41db..6e5d9962a8 100644 --- a/ui/StatusQ/src/StatusQ/Popups/StatusColorDialog.qml +++ b/ui/StatusQ/src/StatusQ/Popups/StatusColorDialog.qml @@ -77,7 +77,7 @@ StatusModal { validators: [ StatusRegularExpressionValidator { regularExpression: /^#(?:[0-9a-fA-F]{3}){1,2}$/ - errorMessage: qsTr("This is not a valid color") + errorMessage: qsTr("This is not a valid colour") } ] validationMode: StatusInput.ValidationMode.Always @@ -115,7 +115,7 @@ StatusModal { } StatusBaseText { - text: qsTr("Standart colours") + text: qsTr("Standard colours") font.pixelSize: 15 } diff --git a/ui/StatusQ/src/assets/img/icons/checkmark-circle.svg b/ui/StatusQ/src/assets/img/icons/checkmark-circle.svg index 1f1c1fd608..d7e7be03ed 100644 --- a/ui/StatusQ/src/assets/img/icons/checkmark-circle.svg +++ b/ui/StatusQ/src/assets/img/icons/checkmark-circle.svg @@ -1,4 +1,3 @@ - - + diff --git a/ui/StatusQ/src/assets/img/icons/tiny/exclamation.svg b/ui/StatusQ/src/assets/img/icons/tiny/exclamation.svg new file mode 100644 index 0000000000..a4f451572a --- /dev/null +++ b/ui/StatusQ/src/assets/img/icons/tiny/exclamation.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/StatusQ/src/assets/img/icons/tiny/subtract.svg b/ui/StatusQ/src/assets/img/icons/tiny/tiny-exclamation.svg similarity index 100% rename from ui/StatusQ/src/assets/img/icons/tiny/subtract.svg rename to ui/StatusQ/src/assets/img/icons/tiny/tiny-exclamation.svg diff --git a/ui/StatusQ/src/assets/img/icons/warning.svg b/ui/StatusQ/src/assets/img/icons/warning.svg index 75d435681f..4808e96489 100644 --- a/ui/StatusQ/src/assets/img/icons/warning.svg +++ b/ui/StatusQ/src/assets/img/icons/warning.svg @@ -1,5 +1,3 @@ - - - + diff --git a/ui/app/AppLayouts/Chat/popups/ContactRequestsPopup.qml b/ui/app/AppLayouts/Chat/popups/ContactRequestsPopup.qml index 38949ba97d..fd14cb209c 100644 --- a/ui/app/AppLayouts/Chat/popups/ContactRequestsPopup.qml +++ b/ui/app/AppLayouts/Chat/popups/ContactRequestsPopup.qml @@ -37,9 +37,7 @@ ModalPopup { Global.openProfilePopup(model.pubKey) } onBlockContactActionTriggered: { - blockContactConfirmationDialog.contactName = model.displayName - blockContactConfirmationDialog.contactAddress = model.pubKey - blockContactConfirmationDialog.open() + Global.blockContactRequested(model.pubKey, model.displayName) } onAcceptClicked: { popup.store.acceptContactRequest(model.pubKey) @@ -54,14 +52,6 @@ ModalPopup { width: parent.width height: children[0].height - BlockContactConfirmationDialog { - id: blockContactConfirmationDialog - onBlockButtonClicked: { - popup.store.blockContact(blockContactConfirmationDialog.contactAddress) - blockContactConfirmationDialog.close() - } - } - ConfirmationDialog { id: declineAllDialog header.title: qsTr("Decline all contacts") diff --git a/ui/app/AppLayouts/Chat/popups/RenameGroupPopup.qml b/ui/app/AppLayouts/Chat/popups/RenameGroupPopup.qml index d848bda7cf..97c7582088 100644 --- a/ui/app/AppLayouts/Chat/popups/RenameGroupPopup.qml +++ b/ui/app/AppLayouts/Chat/popups/RenameGroupPopup.qml @@ -23,7 +23,6 @@ StatusDialog { signal updateGroupChatDetails(string groupName, string groupColor, string groupImage) - anchors.centerIn: parent title: qsTr("Edit group name and image") width: 480 height: 610 @@ -34,7 +33,7 @@ StatusDialog { } onOpened: { - groupName.forceActiveFocus(Qt.MouseFocusReason) + groupName.input.edit.forceActiveFocus() groupName.text = root.activeGroupName.substring(0, d.nameCharLimit) colorSelectionGrid.selectedColor = activeGroupColor diff --git a/ui/app/AppLayouts/Chat/popups/community/CommunityDetailPopup.qml b/ui/app/AppLayouts/Chat/popups/community/CommunityDetailPopup.qml index eb6c89d3ba..5b659c3e04 100644 --- a/ui/app/AppLayouts/Chat/popups/community/CommunityDetailPopup.qml +++ b/ui/app/AppLayouts/Chat/popups/community/CommunityDetailPopup.qml @@ -81,7 +81,7 @@ StatusModal { } StatusBaseText { - text: qsTr("%1 members").arg(nbMembers) + text: qsTr("%n member(s)", "", nbMembers) font.pixelSize: 15 font.weight: Font.Medium color: Theme.palette.directColor1 diff --git a/ui/app/AppLayouts/Chat/popups/community/CreateCategoryPopup.qml b/ui/app/AppLayouts/Chat/popups/community/CreateCategoryPopup.qml index 58de1e2419..e39c577d2e 100644 --- a/ui/app/AppLayouts/Chat/popups/community/CreateCategoryPopup.qml +++ b/ui/app/AppLayouts/Chat/popups/community/CreateCategoryPopup.qml @@ -34,7 +34,7 @@ StatusModal { root.channels = [] root.store.prepareEditCategoryModel(categoryId); } - root.contentItem.categoryName.input.forceActiveFocus(Qt.MouseFocusReason) + root.contentItem.categoryName.input.edit.forceActiveFocus() } onClosed: destroy() @@ -42,9 +42,7 @@ StatusModal { return contentItem.categoryName.valid } - header.title: isEdit ? - qsTr("Edit category") : - qsTr("New category") + header.title: isEdit ? qsTr("Edit category") : qsTr("New category") contentItem: Column { property alias categoryName: nameInput @@ -59,6 +57,7 @@ StatusModal { anchors.leftMargin: 16 input.edit.objectName: "createOrEditCommunityCategoryNameInput" + input.clearable: true label: qsTr("Category title") charLimit: maxCategoryNameLength placeholderText: qsTr("Name the category") @@ -123,6 +122,8 @@ StatusModal { anchors.horizontalCenter: parent.horizontalCenter height: visible ? implicitHeight : 0 title: "#" + model.name + asset.width: 30 + asset.height: 30 asset.emoji: model.emoji asset.color: model.color asset.imgIsIdenticon: false diff --git a/ui/app/AppLayouts/Chat/stores/MessageStore.qml b/ui/app/AppLayouts/Chat/stores/MessageStore.qml index e5f1a8e3c4..e0f6341617 100644 --- a/ui/app/AppLayouts/Chat/stores/MessageStore.qml +++ b/ui/app/AppLayouts/Chat/stores/MessageStore.qml @@ -1,8 +1,6 @@ -import QtQuick 2.13 +import QtQuick 2.14 import utils 1.0 -import StatusQ.Core.Utils 0.1 as StatusQUtils - QtObject { id: root @@ -172,7 +170,7 @@ QtObject { function interpretMessage(msg) { if (msg.startsWith("/shrug")) { - return msg.replace("/shrug", "") + " ¯\\\\\\_(ツ)\\_/¯" + return msg.replace("/shrug", "") + " ¯\\\\\\_(ツ)\\_/¯" } if (msg.startsWith("/tableflip")) { return msg.replace("/tableflip", "") + " (╯°□°)╯︵ ┻━┻" diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index 52334ee50f..9479681074 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -401,9 +401,9 @@ QtObject { if (index > -1) { const pk = link.substring(index + 3) result.title = qsTr("Start a 1-on-1 chat with %1") - .arg(isChatKey(pk) ? globalUtils.generateAlias(pk) : ("@" + removeStatusEns(pk))) + .arg(Utils.isChatKey(pk) ? globalUtils.generateAlias(pk) : ("@" + Utils.removeStatusEns(pk))) result.callback = function () { - if (isChatKey(pk)) { + if (Utils.isChatKey(pk)) { chatCommunitySectionModule.createOneToOneChat("", pk, "") } else { // Not Refactored Yet diff --git a/ui/app/AppLayouts/Chat/stores/StickerData.qml b/ui/app/AppLayouts/Chat/stores/StickerData.qml index bb5a369ac8..604262d95c 100644 --- a/ui/app/AppLayouts/Chat/stores/StickerData.qml +++ b/ui/app/AppLayouts/Chat/stores/StickerData.qml @@ -1,8 +1,4 @@ -import QtQuick 2.3 -import QtQuick.Controls 2.3 -import QtQuick.Controls 2.12 as QQC2 -import QtQuick.Layouts 1.3 -import Qt.labs.platform 1.1 +import QtQml.Models 2.14 ListModel { ListElement { @@ -58,9 +54,3 @@ ListModel { url: "QmY4QULmzFQ2AAbEuMvnd3Nd7qD8eWtyxiLD9CAf3kFZWU" } } - -/*##^## -Designer { - D{i:0;autoSize:true;height:480;width:640} -} -##^##*/ diff --git a/ui/app/AppLayouts/Chat/stores/StickerPackData.qml b/ui/app/AppLayouts/Chat/stores/StickerPackData.qml index 23153a0102..541ad1569d 100644 --- a/ui/app/AppLayouts/Chat/stores/StickerPackData.qml +++ b/ui/app/AppLayouts/Chat/stores/StickerPackData.qml @@ -1,5 +1,4 @@ -import QtQuick 2.13 -import QtQuick.Controls 2.13 +import QtQml.Models 2.14 ListModel { ListElement { @@ -24,9 +23,3 @@ ListModel { thumbnail: "QmZdTTRiMvupRUWq6ctVbuPfEmc8Js53TmBKyjSYNHmGdi" } } - -/*##^## -Designer { - D{i:0;autoSize:true;height:480;width:640} -} -##^##*/ diff --git a/ui/app/AppLayouts/Chat/views/ChatContentView.qml b/ui/app/AppLayouts/Chat/views/ChatContentView.qml index d8a472571c..d3175283d7 100644 --- a/ui/app/AppLayouts/Chat/views/ChatContentView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatContentView.qml @@ -108,7 +108,7 @@ ColumnLayout { } onOpenProfileClicked: { - Global.openProfilePopup(publicKey, null, state) + Global.openProfilePopup(publicKey, null) } onDeleteMessage: { diff --git a/ui/app/AppLayouts/Chat/views/ChatContextMenuView.qml b/ui/app/AppLayouts/Chat/views/ChatContextMenuView.qml index 2310eec199..c680dd8d93 100644 --- a/ui/app/AppLayouts/Chat/views/ChatContextMenuView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatContextMenuView.qml @@ -185,9 +185,6 @@ StatusPopupMenu { Global.openPopup(deleteChatConfirmationDialogComponent) close() } - onClosed: { - destroy() - } } } diff --git a/ui/app/AppLayouts/Chat/views/ChatView.qml b/ui/app/AppLayouts/Chat/views/ChatView.qml index 6dc01b5aaa..ac2350fe46 100644 --- a/ui/app/AppLayouts/Chat/views/ChatView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatView.qml @@ -173,26 +173,12 @@ StatusSectionLayout { } } - ConfirmationDialog { - id: removeContactConfirmationDialog - header.title: qsTr("Remove contact") - confirmationText: qsTr("Are you sure you want to remove this contact?") - onConfirmButtonClicked: { - let pk = chatColumn.contactToRemove - if (Utils.getContactDetailsAsJson(pk).isAdded) { - root.contactsStore.removeContact(pk) - } - removeContactConfirmationDialog.parentPopup.close(); - removeContactConfirmationDialog.close(); - } - } - MessageContextMenuView { id: quickActionMessageOptionsMenu store: root.rootStore onOpenProfileClicked: { - Global.openProfilePopup(publicKey, null, state) + Global.openProfilePopup(publicKey, null) } onCreateOneToOneChat: { Global.changeAppSectionBySectionType(Constants.appSection.chat) diff --git a/ui/app/AppLayouts/Onboarding/popups/BeforeGetStartedModal.qml b/ui/app/AppLayouts/Onboarding/popups/BeforeGetStartedModal.qml index dd4acf6f2e..ef267092b6 100644 --- a/ui/app/AppLayouts/Onboarding/popups/BeforeGetStartedModal.qml +++ b/ui/app/AppLayouts/Onboarding/popups/BeforeGetStartedModal.qml @@ -14,7 +14,6 @@ StatusDialog { id: root width: 480 - anchors.centerIn: parent closePolicy: Popup.NoAutoClose header: StatusDialogHeader { diff --git a/ui/app/AppLayouts/Profile/ProfileLayout.qml b/ui/app/AppLayouts/Profile/ProfileLayout.qml index 8554250539..16a58b3099 100644 --- a/ui/app/AppLayouts/Profile/ProfileLayout.qml +++ b/ui/app/AppLayouts/Profile/ProfileLayout.qml @@ -38,10 +38,6 @@ StatusSectionLayout { } } - Component.onCompleted: { - Global.privacyModuleInst = store.privacyStore.privacyModule - } - QtObject { id: d @@ -106,6 +102,7 @@ StatusSectionLayout { walletStore: root.store.walletStore profileStore: root.store.profileStore privacyStore: root.store.privacyStore + contactsStore: root.store.contactsStore sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.profile) contentWidth: d.contentWidth } diff --git a/ui/app/AppLayouts/Profile/controls/WalletAccountDelegate.qml b/ui/app/AppLayouts/Profile/controls/WalletAccountDelegate.qml index a046bbfcf3..0df0da7657 100644 --- a/ui/app/AppLayouts/Profile/controls/WalletAccountDelegate.qml +++ b/ui/app/AppLayouts/Profile/controls/WalletAccountDelegate.qml @@ -20,6 +20,8 @@ StatusListItem { asset.letterSize: 14 asset.isLetterIdenticon: !!account.emoji asset.bgColor: Theme.palette.primaryColor3 + asset.width: 40 + asset.height: 40 width: parent.width components: !showShevronIcon ? [] : [ shevronIcon ] diff --git a/ui/app/AppLayouts/Profile/panels/ContactPanel.qml b/ui/app/AppLayouts/Profile/panels/ContactPanel.qml index 0b4028e7b1..fb7fe4e93d 100644 --- a/ui/app/AppLayouts/Profile/panels/ContactPanel.qml +++ b/ui/app/AppLayouts/Profile/panels/ContactPanel.qml @@ -70,7 +70,7 @@ StatusListItem { asset.isImage: asset.name.includes("data") asset.isLetterIdenticon: root.iconSource.toString() === "" ringSettings { - ringSpecModel: Utils.getColorHashAsJson(root.publicKey) + ringSpecModel: d.ensVerified ? undefined : Utils.getColorHashAsJson(root.publicKey, true) ringPxSize: Math.max(asset.width / 24.0) } diff --git a/ui/app/AppLayouts/Profile/stores/ProfileSectionStore.qml b/ui/app/AppLayouts/Profile/stores/ProfileSectionStore.qml index badb0a2052..c7442f0ebf 100644 --- a/ui/app/AppLayouts/Profile/stores/ProfileSectionStore.qml +++ b/ui/app/AppLayouts/Profile/stores/ProfileSectionStore.qml @@ -16,7 +16,7 @@ QtObject { property var profileSectionModuleInst: profileSectionModule - property bool fetchingUpdate: aboutModule.fetching + readonly property bool fetchingUpdate: aboutModuleInst.fetching property ContactsStore contactsStore: ContactsStore { contactsModule: profileSectionModuleInst.contactsModule diff --git a/ui/app/AppLayouts/Profile/views/ContactsView.qml b/ui/app/AppLayouts/Profile/views/ContactsView.qml index dab22d2fb3..db920fa92a 100644 --- a/ui/app/AppLayouts/Profile/views/ContactsView.qml +++ b/ui/app/AppLayouts/Profile/views/ContactsView.qml @@ -27,8 +27,6 @@ SettingsContentBase { headerComponents: [ StatusButton { - implicitHeight: 38 - size: StatusBaseButton.Size.Normal text: qsTr("Send contact request to chat key") onClicked: { sendContactRequest.open() @@ -54,8 +52,8 @@ SettingsContentBase { store: ({contactsStore: root.contactsStore}) isProfile: true - onOpenProfileClicked: function (pubkey, state) { - Global.openProfilePopup(pubkey, null, state) + onOpenProfileClicked: function (pubkey) { + Global.openProfilePopup(pubkey, null) } onCreateOneToOneChat: function (communityId, chatId, ensName) { @@ -196,6 +194,10 @@ SettingsContentBase { contactsModel: root.contactsStore.receivedContactRequestsModel panelUsage: Constants.contactsPanelUsage.receivedContactRequest + onSendMessageActionTriggered: { + root.contactsStore.joinPrivateChat(publicKey) + } + onContactRequestAccepted: { root.contactsStore.acceptContactRequest(publicKey) } @@ -205,20 +207,7 @@ SettingsContentBase { } onShowVerificationRequest: { - try { - let request = root.contactsStore.getVerificationDetailsFromAsJson(publicKey) - Global.openPopup(contactVerificationRequestPopupComponent, { - senderPublicKey: request.from, - senderDisplayName: request.displayName, - senderIcon: request.icon, - challengeText: request.challenge, - responseText: request.response, - messageTimestamp: request.requestedAt, - responseTimestamp: request.repliedAt - }) - } catch (e) { - console.error("Error getting or parsing verification data", e) - } + Global.openIncomingIDRequestPopup(publicKey) } } @@ -311,41 +300,6 @@ SettingsContentBase { } } - // TODO: Make BlockContactConfirmationDialog a dynamic component on a future refactor - BlockContactConfirmationDialog { - id: blockContactConfirmationDialog - onBlockButtonClicked: { - root.contactsStore.blockContact(blockContactConfirmationDialog.contactAddress) - blockContactConfirmationDialog.close() - } - } - - - // TODO: Make ConfirmationDialog a dynamic component on a future refactor - ConfirmationDialog { - id: removeContactConfirmationDialog - header.title: qsTr("Remove contact") - confirmationText: qsTr("Are you sure you want to remove this contact?") - onConfirmButtonClicked: { - if (Utils.getContactDetailsAsJson(removeContactConfirmationDialog.value).isAdded) { - root.contactsStore.removeContact(removeContactConfirmationDialog.value); - } - removeContactConfirmationDialog.close() - } - } - - Component { - id: contactVerificationRequestPopupComponent - ContactVerificationRequestPopup { - onResponseSent: { - root.contactsStore.acceptVerificationRequest(senderPublicKey, response) - } - onVerificationRefused: { - root.contactsStore.declineVerificationRequest(senderPublicKey) - } - } - } - Loader { id: sendContactRequest width: parent.width diff --git a/ui/app/AppLayouts/Profile/views/EnsTermsAndConditionsView.qml b/ui/app/AppLayouts/Profile/views/EnsTermsAndConditionsView.qml index eb9fe44799..2ae3578c65 100644 --- a/ui/app/AppLayouts/Profile/views/EnsTermsAndConditionsView.qml +++ b/ui/app/AppLayouts/Profile/views/EnsTermsAndConditionsView.qml @@ -64,7 +64,7 @@ Item { gasPrice, tipLimit, overallLimit, - password, + password, eip1559Enabled, ) } diff --git a/ui/app/AppLayouts/Profile/views/MessagingView.qml b/ui/app/AppLayouts/Profile/views/MessagingView.qml index a63d26e66d..b0a0150f9a 100644 --- a/ui/app/AppLayouts/Profile/views/MessagingView.qml +++ b/ui/app/AppLayouts/Profile/views/MessagingView.qml @@ -200,7 +200,7 @@ SettingsContentBase { Connections { target: Global - onSettingsLoaded: { + function onSettingsLoaded() { generalColumn.populatePreviewableSites() } } diff --git a/ui/app/AppLayouts/Profile/views/MyProfileView.qml b/ui/app/AppLayouts/Profile/views/MyProfileView.qml index 790ae523c4..9e7153c142 100644 --- a/ui/app/AppLayouts/Profile/views/MyProfileView.qml +++ b/ui/app/AppLayouts/Profile/views/MyProfileView.qml @@ -24,6 +24,7 @@ SettingsContentBase { property WalletStore walletStore property ProfileStore profileStore property PrivacyStore privacyStore + property ContactsStore contactsStore titleRowComponentLoader.sourceComponent: StatusButton { objectName: "profileSettingsChangePasswordButton" @@ -35,8 +36,14 @@ SettingsContentBase { dirty: settingsView.dirty saveChangesButtonEnabled: settingsView.valid - onResetChangesClicked: settingsView.reset() - onSaveChangesClicked: settingsView.save() + onResetChangesClicked: { + settingsView.reset() + profilePreview.reload() + } + onSaveChangesClicked: { + settingsView.save() + profilePreview.reload() + } ColumnLayout { id: layout @@ -73,6 +80,7 @@ SettingsContentBase { id: profilePreview Layout.fillWidth: true profileStore: root.profileStore + contactsStore: root.contactsStore } } diff --git a/ui/app/AppLayouts/Profile/views/NotificationsView.qml b/ui/app/AppLayouts/Profile/views/NotificationsView.qml index a678282a1a..536acbeb41 100644 --- a/ui/app/AppLayouts/Profile/views/NotificationsView.qml +++ b/ui/app/AppLayouts/Profile/views/NotificationsView.qml @@ -58,7 +58,7 @@ SettingsContentBase { StatusListItem { property string lowerCaseSearchString: searchBox.text.toLowerCase() - width: parent.width + width: ListView.view.width height: visible ? implicitHeight : 0 visible: lowerCaseSearchString === "" || model.itemId.toLowerCase().includes(lowerCaseSearchString) || diff --git a/ui/app/AppLayouts/Profile/views/profile/MyProfilePreview.qml b/ui/app/AppLayouts/Profile/views/profile/MyProfilePreview.qml index d346b4e7b4..951dd404e6 100644 --- a/ui/app/AppLayouts/Profile/views/profile/MyProfilePreview.qml +++ b/ui/app/AppLayouts/Profile/views/profile/MyProfilePreview.qml @@ -1,4 +1,4 @@ -import QtQuick 2.13 +import QtQuick 2.14 import QtGraphicalEffects 1.14 import shared.views 1.0 as SharedViews @@ -7,6 +7,7 @@ import StatusQ.Core.Theme 0.1 Item { property alias profileStore: profilePreview.profileStore + property alias contactsStore: profilePreview.contactsStore implicitHeight: profilePreview.implicitHeight + profilePreview.anchors.topMargin @@ -16,10 +17,15 @@ Item { + profilePreview.anchors.leftMargin + profilePreview.anchors.rightMargin - SharedViews.ProfileView { + function reload() { + profilePreview.reload() + } + + SharedViews.ProfileDialogView { id: profilePreview anchors.fill: parent anchors.margins: 24 + readOnly: true } DropShadow { @@ -32,4 +38,4 @@ Item { color: "#40000000" source: profilePreview } -} \ No newline at end of file +} diff --git a/ui/app/AppLayouts/Profile/views/profile/MyProfileSettingsView.qml b/ui/app/AppLayouts/Profile/views/profile/MyProfileSettingsView.qml index ca4fcccd2c..b25becea37 100644 --- a/ui/app/AppLayouts/Profile/views/profile/MyProfileSettingsView.qml +++ b/ui/app/AppLayouts/Profile/views/profile/MyProfileSettingsView.qml @@ -181,7 +181,6 @@ ColumnLayout { visible: communitiesRepeater.count == 0 width: parent.width horizontalAlignment: Text.AlignHCenter - font.pixelSize: 15 color: Theme.palette.directColor1 text: qsTr("You haven't joined any communities yet") } @@ -204,7 +203,6 @@ ColumnLayout { visible: accountsRepeater.count == 0 width: parent.width horizontalAlignment: Text.AlignHCenter - font.pixelSize: 15 color: Theme.palette.directColor1 text: qsTr("You don't have any wallet accounts yet") } @@ -233,5 +231,4 @@ ColumnLayout { } } } - } diff --git a/ui/app/AppLayouts/Wallet/views/RightTabView.qml b/ui/app/AppLayouts/Wallet/views/RightTabView.qml index aae6813bda..c90853f16f 100644 --- a/ui/app/AppLayouts/Wallet/views/RightTabView.qml +++ b/ui/app/AppLayouts/Wallet/views/RightTabView.qml @@ -52,7 +52,6 @@ Item { } ColumnLayout { - anchors.fill: parent WalletHeader { Layout.fillWidth: true locale: RootStore.locale diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index fda7dbde2f..d83093deed 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -84,13 +84,19 @@ Item { }) return downloadPage } + onOpenProfilePopupRequested: { - var popup = profilePopupComponent.createObject(appMain); - if (parentPopup) { - popup.parentPopup = parentPopup; - } - popup.openPopup(publicKey, state); - Global.profilePopupOpened = true; + Global.openPopup(profilePopupComponent, {publicKey: publicKey, parentPopup: parentPopup}) + Global.profilePopupOpened = true + } + onOpenNicknamePopupRequested: { + Global.openPopup(nicknamePopupComponent, {publicKey: publicKey, nickname: nickname, "header.subTitle": subtitle}) + } + onBlockContactRequested: { + Global.openPopup(blockContactConfirmationComponent, {contactName: contactName, contactAddress: publicKey}) + } + onUnblockContactRequested: { + Global.openPopup(unblockContactConfirmationComponent, {contactName: contactName, contactAddress: publicKey}) } onOpenActivityCenterPopupRequested: { @@ -106,28 +112,30 @@ Item { onDisplayToastMessage: { appMain.rootStore.mainModuleInst.displayEphemeralNotification(title, subTitle, icon, loading, ephNotifType, url); } - onOpenEditDisplayNamePopup: { - var popup = displayNamePopupComponent.createObject(appMain) - popup.open() - } + onOpenEditDisplayNamePopup: Global.openPopup(displayNamePopupComponent) } function changeAppSectionBySectionId(sectionId) { mainModule.setActiveSectionById(sectionId) } - property Component backupSeedModalComponent: BackupSeedModal { - id: backupSeedModal - anchors.centerIn: parent - privacyStore: appMain.rootStore.profileSectionStore.privacyStore - onClosed: destroy() + Component { + id: backupSeedModalComponent + BackupSeedModal { + anchors.centerIn: parent + privacyStore: appMain.rootStore.profileSectionStore.privacyStore + onClosed: destroy() + } } - property Component displayNamePopupComponent: DisplayNamePopup { - anchors.centerIn: parent - profileStore: appMain.rootStore.profileSectionStore.profileStore - onClosed: { - destroy() + Component { + id: displayNamePopupComponent + DisplayNamePopup { + anchors.centerIn: parent + profileStore: appMain.rootStore.profileSectionStore.profileStore + onClosed: { + destroy() + } } } @@ -164,21 +172,24 @@ Item { } } - property Component profilePopupComponent: ProfilePopup { - id: profilePopup - anchors.centerIn: parent - profileStore: appMain.rootStore.profileSectionStore.profileStore - contactsStore: appMain.rootStore.profileSectionStore.contactsStore - onClosed: { - if (profilePopup.parentPopup) { - profilePopup.parentPopup.close(); + Component { + id: profilePopupComponent + ProfileDialog { + id: profilePopup + profileStore: appMain.rootStore.profileSectionStore.profileStore + contactsStore: appMain.rootStore.profileSectionStore.contactsStore + onClosed: { + if (profilePopup.parentPopup) { + profilePopup.parentPopup.close() + } + Global.profilePopupOpened = false + destroy() } - Global.profilePopupOpened = false; - destroy(); } } - property Component changeProfilePicComponent: Component { + Component { + id: changeProfilePicComponent ImageCropWorkflow { title: qsTr("Profile Picture") acceptButtonText: qsTr("Make this my Profile Pic") @@ -636,7 +647,6 @@ Item { } } } - } Item { @@ -711,8 +721,6 @@ Item { ChatLayout { id: chatLayoutContainer - Layout.fillWidth: true - Layout.fillHeight: true chatView.emojiPopup: statusEmojiPopup @@ -955,6 +963,44 @@ Item { } } + Component { + id: nicknamePopupComponent + NicknamePopup { + onEditDone: { + if (nickname !== newNickname) { + appMain.rootStore.contactStore.changeContactNickname(publicKey, newNickname) + Global.nickNameChanged(publicKey, newNickname) + } + close() + } + onClosed: destroy() + } + } + + Component { + id: unblockContactConfirmationComponent + UnblockContactConfirmationDialog { + onUnblockButtonClicked: { + appMain.rootStore.contactStore.unblockContact(contactAddress) + Global.contactUnblocked(contactAddress) + close() + } + onClosed: destroy() + } + } + + Component { + id: blockContactConfirmationComponent + BlockContactConfirmationDialog { + onBlockButtonClicked: { + appMain.rootStore.contactStore.blockContact(contactAddress) + Global.contactBlocked(contactAddress) + close() + } + onClosed: destroy() + } + } + // Add SendModal here as it is used by the Wallet as well as the Browser Loader { id: sendModal @@ -1183,8 +1229,8 @@ Item { } catch (e) { console.error('Could not parse the whitelist for sites', e) } + Global.privacyModuleInst = appMain.rootStore.profileSectionStore.privacyStore.privacyModule Global.settingsHasLoaded(); - Global.errorSound = errorSound; } Loader { diff --git a/ui/app/mainui/AppSearch.qml b/ui/app/mainui/AppSearch.qml index a2ae2eae7c..e745a0b5bb 100644 --- a/ui/app/mainui/AppSearch.qml +++ b/ui/app/mainui/AppSearch.qml @@ -44,6 +44,7 @@ Item { onItemClicked: { appSearch.store.setSearchLocation(firstLevelItemValue, secondLevelItemValue) + searchPopup.forceActiveFocus() if(searchPopup.searchText !== "") searchMessages(searchPopup.searchText) } diff --git a/ui/imports/shared/controls/AddressInput.qml b/ui/imports/shared/controls/AddressInput.qml index 146b8c31e2..af699bdf43 100644 --- a/ui/imports/shared/controls/AddressInput.qml +++ b/ui/imports/shared/controls/AddressInput.qml @@ -15,7 +15,7 @@ Item { property string ensAsyncValidationError: qsTr("ENS Username not found") property alias input: contactFieldAndList.chatKey property string selectedAddress - property var isValid: false + property bool isValid: false property alias isPending: contactFieldAndList.loading property bool isResolvedAddress: false property int parentWidth diff --git a/ui/imports/shared/controls/CopyToClipBoardButton.qml b/ui/imports/shared/controls/CopyToClipBoardButton.qml index 79e69dd142..7c1d818f18 100644 --- a/ui/imports/shared/controls/CopyToClipBoardButton.qml +++ b/ui/imports/shared/controls/CopyToClipBoardButton.qml @@ -2,8 +2,6 @@ import QtQuick 2.13 import StatusQ.Controls 0.1 -import shared.stores 1.0 - import utils 1.0 StatusRoundButton { @@ -32,16 +30,6 @@ StatusRoundButton { id: toolTip text: qsTr("Copied!") orientation: tooltipUnder ? StatusToolTip.Orientation.Bottom: StatusToolTip.Orientation.Top - } - - Timer { - id: hideTimer - interval: 2000 - running: toolTip.visible - onTriggered: { - toolTip.visible = false; - } + timeout: 2000 } } - - diff --git a/ui/imports/shared/controls/EmojiHash.qml b/ui/imports/shared/controls/EmojiHash.qml index 5768934eed..43f2de957c 100644 --- a/ui/imports/shared/controls/EmojiHash.qml +++ b/ui/imports/shared/controls/EmojiHash.qml @@ -4,12 +4,12 @@ import StatusQ.Core.Utils 0.1 as StatusQUtils import StatusQ.Components 0.1 import utils 1.0 -import shared.panels 1.0 Item { id: root property bool compact: false + property bool oneRow property string publicKey readonly property real size: compact ? 10 : 15 @@ -20,8 +20,8 @@ Item { Grid { id: positioner - rows: 2 - columnSpacing: 0.2 + rows: root.oneRow ? 1 : 2 + columnSpacing: root.oneRow ? 4 : 2 rowSpacing: root.compact ? 4 : 6 Repeater { diff --git a/ui/imports/shared/controls/chat/ProfileHeader.qml b/ui/imports/shared/controls/chat/ProfileHeader.qml index 1ff74465d9..b8efb79bac 100644 --- a/ui/imports/shared/controls/chat/ProfileHeader.qml +++ b/ui/imports/shared/controls/chat/ProfileHeader.qml @@ -140,24 +140,11 @@ Item { } } - Loader { - sourceComponent: SVGImage { - height: compact ? 10 : 16 - width: compact ? 10 : 16 - source: Style.svg("contact") - } - active: isContact && !root.isCurrentUser - visible: active - } - - Loader { - sourceComponent: VerificationLabel { - trustStatus: root.trustStatus - height: compact ? 10 : 16 - width: compact ? 10 : 16 - } - active: root.trustStatus !== Constants.trustStatus.unknown && !root.isCurrentUser - visible: active + StatusContactVerificationIcons { + Layout.alignment: Qt.AlignVCenter + visible: !root.isCurrentUser + isContact: root.isContact + trustIndicator: root.trustStatus } Loader { diff --git a/ui/imports/shared/controls/chat/menuItems/MuteChatMenuItem.qml b/ui/imports/shared/controls/chat/menuItems/MuteChatMenuItem.qml index 834488a860..6769a07fc3 100644 --- a/ui/imports/shared/controls/chat/menuItems/MuteChatMenuItem.qml +++ b/ui/imports/shared/controls/chat/menuItems/MuteChatMenuItem.qml @@ -5,6 +5,6 @@ import StatusQ.Popups 0.1 StatusMenuItem { property bool muted: false - text: !muted ? qsTr("Mute chat") : qsTr("Unmute chat") + text: muted ? qsTr("Unmute Chat") : qsTr("Mute Chat") icon.name: "notification" } diff --git a/ui/imports/shared/controls/chat/menuItems/SendMessageMenuItem.qml b/ui/imports/shared/controls/chat/menuItems/SendMessageMenuItem.qml index f6e7df2c70..c79f563743 100644 --- a/ui/imports/shared/controls/chat/menuItems/SendMessageMenuItem.qml +++ b/ui/imports/shared/controls/chat/menuItems/SendMessageMenuItem.qml @@ -3,6 +3,6 @@ import QtQuick 2.14 import StatusQ.Popups 0.1 StatusMenuItem { - text: qsTr("Send message") + text: qsTr("Send Message") icon.name: "chat" } diff --git a/ui/imports/shared/panels/AcceptRejectOptionsButtonsPanel.qml b/ui/imports/shared/panels/AcceptRejectOptionsButtonsPanel.qml index 763dcbb523..0de7fe21c2 100644 --- a/ui/imports/shared/panels/AcceptRejectOptionsButtonsPanel.qml +++ b/ui/imports/shared/panels/AcceptRejectOptionsButtonsPanel.qml @@ -10,26 +10,16 @@ import shared.controls.chat.menuItems 1.0 Row { id: root + height: declineBtn.height + spacing: Style.current.halfPadding + + property alias menuButton: menuButton signal acceptClicked() signal declineClicked() signal blockClicked() signal profileClicked() - height: acceptBtn.height - spacing: Style.current.halfPadding - - StatusFlatRoundButton { - id: acceptBtn - width: 32 - height: 32 - anchors.verticalCenter: parent.verticalCenter - icon.name: "checkmark-circle" - icon.color: Style.current.success - backgroundHoverColor: Utils.setColorAlpha(Style.current.success, 0.1) - onClicked: root.acceptClicked() - } - StatusFlatRoundButton { id: declineBtn width: 32 @@ -41,6 +31,16 @@ Row { onClicked: root.declineClicked() } + StatusFlatRoundButton { + id: acceptBtn + width: 32 + height: 32 + anchors.verticalCenter: parent.verticalCenter + icon.name: "checkmark-circle" + icon.color: Style.current.success + backgroundHoverColor: Utils.setColorAlpha(Style.current.success, 0.1) + onClicked: root.acceptClicked() + } StatusFlatRoundButton { id: menuButton diff --git a/ui/imports/shared/panels/ImageLoader.qml b/ui/imports/shared/panels/ImageLoader.qml index d275503af3..15681c8368 100644 --- a/ui/imports/shared/panels/ImageLoader.qml +++ b/ui/imports/shared/panels/ImageLoader.qml @@ -55,7 +55,7 @@ Rectangle { Connections { enabled: !!mainModule target: enabled ? mainModule : undefined - onOnlineStatusChanged: { + function onOnlineStatusChanged(connected) { if (connected && root.state !== "ready" && root.visible && root.source && diff --git a/ui/imports/shared/panels/ModuleWarning.qml b/ui/imports/shared/panels/ModuleWarning.qml index 288f438161..858a8565cb 100644 --- a/ui/imports/shared/panels/ModuleWarning.qml +++ b/ui/imports/shared/panels/ModuleWarning.qml @@ -8,9 +8,6 @@ import StatusQ.Core.Theme 0.1 import utils 1.0 -import StatusQ.Core 0.1 -import StatusQ.Core.Theme 0.1 - Item { id: root @@ -29,7 +26,7 @@ Item { signal clicked() signal closeClicked() signal showStarted() - signal showFinihsed() + signal showFinished() signal hideStarted() signal hideFinished() @@ -73,7 +70,7 @@ Item { root.showStarted() } onFinished: { - root.showFinihsed() + root.showFinished() } } @@ -134,7 +131,6 @@ Item { text: root.text font.pixelSize: 13 font.weight: Font.Medium - anchors.verticalCenter: parent.verticalCenter color: Theme.palette.indirectColor1 linkColor: color onLinkActivated: root.linkActivated(link) diff --git a/ui/imports/shared/panels/ProfileBioSocialsPanel.qml b/ui/imports/shared/panels/ProfileBioSocialsPanel.qml index 2f085e3e40..9b439c3a13 100644 --- a/ui/imports/shared/panels/ProfileBioSocialsPanel.qml +++ b/ui/imports/shared/panels/ProfileBioSocialsPanel.qml @@ -1,4 +1,5 @@ import QtQuick 2.14 +import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import QtQml.Models 2.14 @@ -10,7 +11,7 @@ import "../controls" import SortFilterProxyModel 0.2 -Item { +Control { id: root property string bio @@ -18,9 +19,6 @@ Item { onUserSocialLinksJsonChanged: d.buildSocialLinksModel() - implicitWidth: layout.implicitWidth - implicitHeight: layout.implicitHeight - QtObject { id: d @@ -83,11 +81,7 @@ Item { ] } - ColumnLayout { - id: layout - - anchors.fill: parent - + contentItem: ColumnLayout { spacing: 20 StatusBaseText { @@ -95,15 +89,15 @@ Item { visible: text !== "" text: root.bio wrapMode: Text.Wrap + font.weight: Font.Medium + lineHeight: 1.2 } - Flow { + StatusCenteredFlow { Layout.fillWidth: true Layout.fillHeight: true - Layout.leftMargin: 10 - Layout.rightMargin: 10 - spacing: 16 + spacing: Style.current.halfPadding visible: repeater.count > 0 Repeater { diff --git a/ui/imports/shared/panels/Separator.qml b/ui/imports/shared/panels/Separator.qml index 277956258a..1af4755bb0 100644 --- a/ui/imports/shared/panels/Separator.qml +++ b/ui/imports/shared/panels/Separator.qml @@ -10,10 +10,10 @@ Item { height: root.visible ? implicitHeight : 0 anchors.topMargin: Style.current.padding Rectangle { - id: separator - width: parent.width - height: 1 - color: root.color - anchors.verticalCenter: parent.verticalCenter + id: separator + width: parent.width + height: 1 + color: root.color + anchors.verticalCenter: parent.verticalCenter } } diff --git a/ui/imports/shared/popups/BlockContactConfirmationDialog.qml b/ui/imports/shared/popups/BlockContactConfirmationDialog.qml index c0f569eb8f..a2dcdd1073 100644 --- a/ui/imports/shared/popups/BlockContactConfirmationDialog.qml +++ b/ui/imports/shared/popups/BlockContactConfirmationDialog.qml @@ -15,7 +15,6 @@ ModalPopup { height: 237 width: 400 - property Popup parentPopup property string contactAddress: "" property string contactName: "" diff --git a/ui/imports/shared/popups/ContactVerificationRequestPopup.qml b/ui/imports/shared/popups/ContactVerificationRequestPopup.qml index 5a84b227f6..8e24e8ffae 100644 --- a/ui/imports/shared/popups/ContactVerificationRequestPopup.qml +++ b/ui/imports/shared/popups/ContactVerificationRequestPopup.qml @@ -1,6 +1,5 @@ import QtQuick 2.13 import QtQuick.Controls 2.13 -import QtQuick.Dialogs 1.3 import utils 1.0 @@ -37,9 +36,6 @@ StatusModal { onOpened: { verificationResponse.input.edit.forceActiveFocus(Qt.MouseFocusReason) } - onClosed: { - root.destroy(); - } contentItem: Item { anchors.left: parent.left @@ -69,6 +65,7 @@ StatusModal { messageTimestamp: root.messageTimestamp senderId: root.senderPublicKey senderDisplayName: root.senderDisplayName + senderIsEnsVerified: Utils.isEnsVerified(root.senderPublicKey) senderIcon: root.senderIcon messageText: root.challengeText messageContentType: Constants.messageContentType.messageType @@ -100,7 +97,8 @@ StatusModal { shouldRepeatHeader: true messageTimestamp: root.responseTimestamp senderId: userProfile.pubKey - senderDisplayName: userProfile.name + senderDisplayName: userProfile.displayName + senderIsEnsVerified: !!userProfile.ensName senderIcon: userProfile.icon messageText: root.responseText messageContentType: Constants.messageContentType.messageType @@ -115,9 +113,9 @@ StatusModal { wrapMode: Text.WordWrap anchors.top: responseMessage.bottom anchors.topMargin: 58 - text: qsTr("You're answer has been sent to %1.").arg(root.senderDisplayName) + text: qsTr("Your answer has been sent to %1.").arg(root.senderDisplayName) font.pixelSize: 13 - horizontalAlignment: Text.AlignHCenter + horizontalAlignment: Text.AlignHCenter } } diff --git a/ui/imports/shared/popups/DisplayNamePopup.qml b/ui/imports/shared/popups/DisplayNamePopup.qml index 6a5381fb0f..88e2468644 100644 --- a/ui/imports/shared/popups/DisplayNamePopup.qml +++ b/ui/imports/shared/popups/DisplayNamePopup.qml @@ -47,6 +47,6 @@ StatusModal { } ] - onOpened: { displayNameInput.input.forceActiveFocus(Qt.MouseFocusReason) } + onOpened: { displayNameInput.input.edit.forceActiveFocus() } } diff --git a/ui/imports/shared/popups/NicknamePopup.qml b/ui/imports/shared/popups/NicknamePopup.qml index 68b31c7f29..6ac8f7d1d0 100644 --- a/ui/imports/shared/popups/NicknamePopup.qml +++ b/ui/imports/shared/popups/NicknamePopup.qml @@ -18,15 +18,18 @@ StatusModal { anchors.centerIn: parent header.title: qsTr("Nickname") + header.subTitleElide: Text.ElideMiddle + + /*required*/ property string publicKey + property string nickname - property alias nickname: nicknameInput.text readonly property int nicknameLength: nicknameInput.text.length readonly property int maxNicknameLength: 32 signal editDone(string newNickname) onOpened: { - nicknameInput.forceActiveFocus(Qt.MouseFocusReason); + nicknameInput.input.edit.forceActiveFocus() } contentItem: Item { @@ -51,9 +54,9 @@ StatusModal { StatusInput { id: nicknameInput placeholderText: qsTr("Nickname") - + input.clearable: true width: parent.width - + text: popup.nickname charLimit: maxNicknameLength validationMode: StatusInput.ValidationMode.IgnoreInvalidInput validators: [ diff --git a/ui/imports/shared/popups/OutgoingContactVerificationRequestPopup.qml b/ui/imports/shared/popups/OutgoingContactVerificationRequestPopup.qml new file mode 100644 index 0000000000..34633d7037 --- /dev/null +++ b/ui/imports/shared/popups/OutgoingContactVerificationRequestPopup.qml @@ -0,0 +1,110 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 +import QtQml.Models 2.14 + +import utils 1.0 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Popups.Dialog 0.1 + +import shared.views.chat 1.0 + +StatusDialog { + id: root + + /* required*/ property string userPublicKey + property int verificationStatus + property string verificationChallenge + property string verificationResponse + property string verificationResponseDisplayName + property string verificationResponseIcon + property string verificationRequestedAt + property string verificationRepliedAt + + signal verificationRequestCanceled(string userPublicKey) + signal untrustworthyVerified(string userPublicKey) + signal trustedVerified(string userPublicKey) + + title: qsTr("Verify %1's Identity").arg(root.verificationResponseDisplayName) + + footer: StatusDialogFooter { + leftButtons: ObjectModel { + StatusButton { + text: qsTr("Cancel verification") + type: StatusBaseButton.Type.Danger + visible: root.verificationStatus !== Constants.verificationStatus.verified + onClicked: { + root.verificationRequestCanceled(root.userPublicKey) + root.close() + } + } + } + + rightButtons: ObjectModel { + StatusButton { + text: qsTr("Mark Untrustworthy") + enabled: root.verificationResponse !== "" + type: StatusBaseButton.Type.Danger + onClicked: { + root.untrustworthyVerified(root.userPublicKey) + root.close() + } + } + StatusButton { + text: qsTr("Confirm Identity") + enabled: root.verificationResponse !== "" + type: StatusBaseButton.Type.Primary + onClicked: { + root.trustedVerified(root.userPublicKey) + root.close() + } + } + } + } + + contentItem: ColumnLayout { + MessageView { + id: challengeMessage + Layout.fillWidth: true + isMessage: true + shouldRepeatHeader: true + messageTimestamp: root.verificationRequestedAt + senderId: userProfile.pubKey + senderDisplayName: userProfile.name + senderIcon: userProfile.icon + senderIsEnsVerified: !!userProfile.ensName + messageText: root.verificationChallenge + messageContentType: Constants.messageContentType.messageType + placeholderMessage: true + } + MessageView { + id: responseMessage + visible: root.verificationResponse !== "" + Layout.fillWidth: true + isMessage: true + shouldRepeatHeader: true + messageTimestamp: root.verificationRepliedAt + senderId: root.userPublicKey + senderDisplayName: root.verificationResponseDisplayName + senderIcon: root.verificationResponseIcon + senderIsEnsVerified: Utils.isEnsVerified(root.userPublicKey) + messageText: root.verificationResponse + messageContentType: Constants.messageContentType.messageType + placeholderMessage: true + } + StatusBaseText { + id: waitingForText + visible: !root.verificationResponse + text: qsTr("Waiting for %1's response...").arg(root.verificationResponseDisplayName) + font.pixelSize: Style.current.additionalTextSize + horizontalAlignment : Text.AlignHCenter + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + wrapMode: Text.WordWrap + color: Theme.palette.baseColor1 + } + } +} diff --git a/ui/imports/shared/popups/ProfileDialog.qml b/ui/imports/shared/popups/ProfileDialog.qml new file mode 100644 index 0000000000..044a10cd95 --- /dev/null +++ b/ui/imports/shared/popups/ProfileDialog.qml @@ -0,0 +1,29 @@ +import QtQuick 2.14 + +import StatusQ.Popups.Dialog 0.1 + +import shared.views 1.0 + +StatusDialog { + id: root + + property var parentPopup + + property string publicKey + + property var profileStore + property var contactsStore + + width: 640 + padding: 0 + + header: null + footer: null + + contentItem: ProfileDialogView { + publicKey: root.publicKey + profileStore: root.profileStore + contactsStore: root.contactsStore + onCloseRequested: root.close() + } +} diff --git a/ui/imports/shared/popups/ProfilePopup.qml b/ui/imports/shared/popups/ProfilePopup.qml deleted file mode 100644 index bc903d1a83..0000000000 --- a/ui/imports/shared/popups/ProfilePopup.qml +++ /dev/null @@ -1,474 +0,0 @@ -import QtQuick 2.13 -import QtQuick.Controls 2.13 -import QtQuick.Layouts 1.13 -import QtQml.Models 2.14 -import QtGraphicalEffects 1.13 - -import utils 1.0 -import shared 1.0 -import shared.popups 1.0 -import shared.stores 1.0 -import shared.views 1.0 as SharedViews -import shared.controls.chat 1.0 -import shared.panels 1.0 - -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 - -StatusDialog { - id: popup - - property Popup parentPopup - - property var profileStore - property var contactsStore - - property string userPublicKey: "" - property string userDisplayName: "" - property string userName: "" - property string userNickname: "" - property string userEnsName: "" - property string userIcon: "" - property string userBio: "" - property string userSocialLinks: "" - property int userTrustStatus: Constants.trustStatus.unknown - property int outgoingVerificationStatus: Constants.verificationStatus.unverified - property int incomingVerificationStatus: Constants.verificationStatus.unverified - property string text: "" - property string challenge: "" - property string response: "" - - property bool userIsEnsVerified: false - property bool userIsBlocked: false - property bool userIsUntrustworthy: false - property bool userTrustIsUnknown: false - property bool isCurrentUser: false - property bool isAddedContact: false - property bool isContact: false - property bool isVerificationSent: false - property bool isVerified: false - property bool isTrusted: false - property bool hasReceivedVerificationRequest: false - - property bool showRemoveVerified: false - property bool showVerifyIdentitySection: false - property bool showVerificationPendingSection: false - property bool showIdentityVerified: false - property bool showIdentityVerifiedUntrustworthy: false - - property string verificationChallenge: "" - property string verificationResponse: "" - property string verificationResponseDisplayName: "" - property string verificationResponseIcon: "" - property string verificationRequestedAt: "" - property string verificationRepliedAt: "" - - signal blockButtonClicked(name: string, address: string) - signal unblockButtonClicked(name: string, address: string) - signal removeButtonClicked(address: string) - - signal contactUnblocked(publicKey: string) - signal contactBlocked(publicKey: string) - - function openPopup(publicKey, state = "") { - // All this should be improved more, but for now we leave it like this. - const contactDetails = Utils.getContactDetailsAsJson(publicKey); - - isCurrentUser = popup.profileStore.pubkey === publicKey; - - userPublicKey = publicKey; - userDisplayName = isCurrentUser ? Qt.binding(() => { return popup.profileStore.displayName }) : contactDetails.displayName; - userName = contactDetails.alias; - userNickname = contactDetails.localNickname; - userEnsName = contactDetails.name; - userIcon = contactDetails.largeImage; - userBio = contactDetails.bio; - userSocialLinks = contactDetails.socialLinks; - userIsEnsVerified = contactDetails.ensVerified; - userIsBlocked = contactDetails.isBlocked; - isAddedContact = contactDetails.isAdded; - isContact = contactDetails.isContact - userTrustStatus = contactDetails.trustStatus - userTrustIsUnknown = contactDetails.trustStatus === Constants.trustStatus.unknown - userIsUntrustworthy = contactDetails.trustStatus === Constants.trustStatus.untrustworthy - outgoingVerificationStatus = contactDetails.verificationStatus - incomingVerificationStatus = contactDetails.incomingVerificationStatus - isVerificationSent = outgoingVerificationStatus !== Constants.verificationStatus.unverified - - if (isContact && popup.contactsStore.hasReceivedVerificationRequestFrom(publicKey)) { - popup.hasReceivedVerificationRequest = true - } - - if(isContact && isVerificationSent) { - let verificationDetails = popup.contactsStore.getSentVerificationDetailsAsJson(publicKey); - - outgoingVerificationStatus = verificationDetails.requestStatus; - verificationChallenge = verificationDetails.challenge; - verificationResponse = verificationDetails.response; - verificationResponseDisplayName = verificationDetails.displayName; - verificationResponseIcon = verificationDetails.icon; - verificationRequestedAt = verificationDetails.requestedAt; - verificationRepliedAt = verificationDetails.repliedAt; - } - isTrusted = outgoingVerificationStatus === Constants.verificationStatus.trusted - || incomingVerificationStatus === Constants.verificationStatus.trusted - isVerified = outgoingVerificationStatus === Constants.verificationStatus.verified - - text = ""; // this is most likely unneeded - - popup.open(); - - if (state === Constants.profilePopupStates.openNickname) { - profileView.nicknamePopup.open(); - } else if (state === Constants.profilePopupStates.contactRequest) { - d.openContactRequestPopup() - } else if (state === Constants.profilePopupStates.blockUser) { - blockUser(); - } else if (state === Constants.profilePopupStates.unblockUser) { - unblockUser(); - } else if (state === Constants.profilePopupStates.verifyIdentity) { - showVerifyIdentitySection = true; - } else if (state === Constants.profilePopupStates.respondToPendingRequest) { - popup.openPendingRequestPopup() - } else if (state === Constants.profilePopupStates.showVerificationPendingSection) { - popup.showVerificationPendingSection = true - profileView.wizardAnimation.running = true - } - } - - function blockUser() { - profileView.blockContactConfirmationDialog.contactName = userDisplayName; - profileView.blockContactConfirmationDialog.contactAddress = userPublicKey; - profileView.blockContactConfirmationDialog.open(); - } - - function unblockUser() { - profileView.unblockContactConfirmationDialog.contactName = userDisplayName; - profileView.unblockContactConfirmationDialog.contactAddress = userPublicKey; - profileView.unblockContactConfirmationDialog.open(); - } - - function openPendingRequestPopup() { - try { - let request = popup.contactsStore.getVerificationDetailsFromAsJson(popup.userPublicKey) - Global.openPopup(contactVerificationRequestPopupComponent, { - senderPublicKey: request.from, - senderDisplayName: request.displayName, - senderIcon: request.icon, - challengeText: request.challenge, - responseText: request.response, - messageTimestamp: request.requestedAt, - responseTimestamp: request.repliedAt - }) - } catch (e) { - console.error("Error getting or parsing verification data", e) - } - } - - QtObject { - id: d - - function openContactRequestPopup() { - let contactRequestPopup = Global.openContactRequestPopup(popup.userPublicKey) - contactRequestPopup.closed.connect(popup.close) - } - } - - width: 700 - padding: 8 - - header: StatusDialogHeader { - id: dialogHeader - headline.title: { - if(showVerifyIdentitySection || showVerificationPendingSection){ - return qsTr("Verify %1's Identity").arg(userDisplayName) - } - return popup.isCurrentUser ? qsTr("My Profile") : - qsTr("%1's Profile").arg(userDisplayName) - } - - headline.subtitle: popup.isCurrentUser ? "" : Utils.getElidedCompressedPk(userPublicKey) - - actions { - customButtons: ObjectModel { - StatusFlatRoundButton { - type: StatusFlatRoundButton.Type.Secondary - width: 32 - height: 32 - - icon.width: 20 - icon.height: 20 - icon.name: "qr" - onClicked: profileView.qrCodePopup.open() - } - } - - closeButton.onClicked: popup.close() - } - } - - footer: StatusDialogFooter { - visible: !popup.isCurrentUser - - leftButtons: ObjectModel { - StatusButton { - text: qsTr("Cancel verification") - visible: !isVerified && isContact && isVerificationSent && showVerificationPendingSection - onClicked: { - popup.contactsStore.cancelVerificationRequest(userPublicKey); - popup.close() - } - } - } - - rightButtons: ObjectModel { - StatusFlatButton { - text: userIsBlocked ? - qsTr("Unblock User") : - qsTr("Block User") - type: StatusBaseButton.Type.Danger - visible: !isAddedContact - onClicked: userIsBlocked ? unblockUser() : blockUser() - } - - StatusFlatButton { - visible: !showRemoveVerified && !showIdentityVerified && !showVerifyIdentitySection && !showVerificationPendingSection && !userIsBlocked && isAddedContact - type: StatusBaseButton.Type.Danger - text: qsTr('Remove Contact') - onClicked: { - profileView.removeContactConfirmationDialog.parentPopup = popup; - profileView.removeContactConfirmationDialog.open(); - } - } - - StatusButton { - text: qsTr("Send Contact Request") - visible: !userIsBlocked && !isAddedContact - onClicked: d.openContactRequestPopup() - } - - StatusButton { - text: qsTr("Mark Untrustworthy") - visible: !showIdentityVerifiedUntrustworthy && !showIdentityVerified && !showVerifyIdentitySection && userTrustIsUnknown - enabled: !showVerificationPendingSection || verificationResponse !== "" - type: StatusBaseButton.Type.Danger - onClicked: { - if (showVerificationPendingSection) { - popup.showIdentityVerified = false; - popup.showIdentityVerifiedUntrustworthy = true; - popup.showVerificationPendingSection = false; - popup.showVerifyIdentitySection = false; - profileView.stepsListModel.setProperty(2, "stepCompleted", true); - popup.contactsStore.verifiedUntrustworthy(userPublicKey); - } else { - popup.contactsStore.markUntrustworthy(userPublicKey); - popup.close(); - } - } - } - - StatusButton { - text: qsTr("Remove 'Identity Verified' status") - visible: isTrusted && !showIdentityVerified && !showRemoveVerified - type: StatusBaseButton.Type.Danger - onClicked: { - showRemoveVerified = true - } - } - - StatusButton { - text: qsTr("No") - visible: showRemoveVerified - type: StatusBaseButton.Type.Danger - onClicked: { - showRemoveVerified = false - } - } - - StatusButton { - text: qsTr("Yes") - visible: showRemoveVerified - onClicked: { - popup.contactsStore.removeTrustStatus(userPublicKey); - popup.close(); - } - } - - StatusButton { - text: qsTr("Remove Untrustworthy Mark") - visible: userIsUntrustworthy - onClicked: { - popup.contactsStore.removeTrustStatus(userPublicKey); - popup.close(); - } - } - - StatusButton { - text: qsTr("Verify Identity") - visible: !showIdentityVerifiedUntrustworthy && !showIdentityVerified && - !showVerifyIdentitySection && isContact && !isVerificationSent - && !hasReceivedVerificationRequest - onClicked: { - popup.showVerifyIdentitySection = true - } - } - - StatusButton { - text: qsTr("Verify Identity pending...") - visible: (!showIdentityVerifiedUntrustworthy && !showIdentityVerified && !isTrusted - && isContact && isVerificationSent && !showVerificationPendingSection) || - (hasReceivedVerificationRequest && !isTrusted) - onClicked: { - if (hasReceivedVerificationRequest) { - popup.openPendingRequestPopup() - } else { - popup.showVerificationPendingSection = true - profileView.wizardAnimation.running = true - } - } - } - - StatusButton { - text: qsTr("Send verification request") - visible: showVerifyIdentitySection && isContact && !isVerificationSent - onClicked: { - popup.contactsStore.sendVerificationRequest(userPublicKey, Utils.escapeHtml(profileView.challengeTxt.input.text)); - profileView.stepsListModel.setProperty(1, "stepCompleted", true); - Global.displayToastMessage(qsTr("Verification request sent"), - "", - "checkmark-circle", - false, - Constants.ephemeralNotificationType.normal, - ""); - popup.close(); - } - } - - StatusButton { - text: qsTr("Confirm Identity") - visible: isContact && isVerificationSent && !isTrusted && showVerificationPendingSection - enabled: verificationChallenge !== "" && verificationResponse !== "" - onClicked: { - popup.showIdentityVerified = true; - popup.showIdentityVerifiedUntrustworthy = false; - popup.showVerificationPendingSection = false; - popup.showVerifyIdentitySection = false; - profileView.stepsListModel.setProperty(2, "stepCompleted", true); - popup.contactsStore.verifiedTrusted(userPublicKey); - popup.isTrusted = true - } - } - - StatusButton { - visible: showIdentityVerified || showIdentityVerifiedUntrustworthy - text: qsTr("Rename") - onClicked: { - profileView.nicknamePopup.open() - } - } - - StatusButton { - visible: showIdentityVerified || showIdentityVerifiedUntrustworthy - text: qsTr("Close") - onClicked: { - popup.close(); - } - } - } - } - - StatusScrollView { - id: scrollView - - anchors.fill: parent - padding: 0 - - SharedViews.ProfileView { - id: profileView - - width: scrollView.availableWidth - - profileStore: popup.profileStore - contactsStore: popup.contactsStore - - - userPublicKey: popup.userPublicKey - userDisplayName: popup.userDisplayName - userName: popup.userName - userNickname: popup.userNickname - userEnsName: popup.userEnsName - userIcon: popup.userIcon - userBio: popup.userBio - userSocialLinks: popup.userSocialLinks - userIsEnsVerified: popup.userIsEnsVerified - userIsBlocked: popup.userIsBlocked - isAddedContact: popup.isAddedContact - isCurrentUser: popup.isCurrentUser - - isContact: popup.isContact - isVerificationSent: popup.isVerificationSent - isVerified: popup.isVerified - isTrusted: popup.isTrusted - hasReceivedVerificationRequest: popup.hasReceivedVerificationRequest - - userTrustStatus: popup.userTrustStatus - outgoingVerificationStatus: popup.outgoingVerificationStatus - - showVerifyIdentitySection: popup.showVerifyIdentitySection - showVerificationPendingSection: popup.showVerificationPendingSection - showIdentityVerified: popup.showIdentityVerified - showIdentityVerifiedUntrustworthy: popup.showIdentityVerifiedUntrustworthy - - challenge: popup.challenge - response: popup.response - - userIsUntrustworthy: popup.userIsUntrustworthy - userTrustIsUnknown: popup.userTrustIsUnknown - - verificationChallenge: popup.verificationChallenge - verificationResponse: popup.verificationResponse - verificationResponseDisplayName: popup.verificationResponseDisplayName - verificationResponseIcon: popup.verificationResponseIcon - verificationRequestedAt: popup.verificationRequestedAt - verificationRepliedAt: popup.verificationRepliedAt - - onContactUnblocked: { - popup.close() - popup.contactUnblocked(publicKey) - } - - onContactBlocked: { - popup.close() - popup.contactBlocked(publicKey) - } - - onContactAdded: { - popup.close() - popup.contactAdded(publicKey) - } - - onContactRemoved: { - popup.close() - } - - onNicknameEdited: { - popup.close() - } - } - } - - Component { - id: contactVerificationRequestPopupComponent - ContactVerificationRequestPopup { - onResponseSent: { - popup.contactsStore.acceptVerificationRequest(senderPublicKey, response) - } - onVerificationRefused: { - popup.contactsStore.declineVerificationRequest(senderPublicKey) - } - } - } -} diff --git a/ui/imports/shared/popups/SendContactRequestModal.qml b/ui/imports/shared/popups/SendContactRequestModal.qml index dbd93b86ee..dd51215577 100644 --- a/ui/imports/shared/popups/SendContactRequestModal.qml +++ b/ui/imports/shared/popups/SendContactRequestModal.qml @@ -19,9 +19,12 @@ StatusModal { property string userIcon: "" property bool userIsEnsVerified + property string challengeText: qsTr("Say who you are / why you want to become a contact...") + property string buttonText: qsTr("Send Contact Request") + signal accepted(string message) - padding: 16 + padding: Style.current.padding header.title: qsTr("Send Contact Request to %1").arg(userDisplayName) QtObject { @@ -33,6 +36,10 @@ StatusModal { readonly property int contentSpacing: 5 } + onAboutToShow: { + messageInput.input.edit.forceActiveFocus() + } + ColumnLayout { id: content anchors.fill: parent @@ -55,7 +62,7 @@ StatusModal { id: messageInput charLimit: d.maxMsgLength - placeholderText: qsTr("Say who you are / why you want to become a contact...") + placeholderText: root.challengeText input.multiline: true minimumHeight: d.msgHeight maximumHeight: d.msgHeight @@ -71,7 +78,7 @@ StatusModal { rightButtons: StatusButton { enabled: messageInput.valid - text: qsTr("Send Contact Request") + text: root.buttonText onClicked: { root.accepted(Utils.escapeHtml(messageInput.text)); root.close(); diff --git a/ui/imports/shared/popups/UnblockContactConfirmationDialog.qml b/ui/imports/shared/popups/UnblockContactConfirmationDialog.qml index b54be91f82..d6bf87fe8d 100644 --- a/ui/imports/shared/popups/UnblockContactConfirmationDialog.qml +++ b/ui/imports/shared/popups/UnblockContactConfirmationDialog.qml @@ -15,7 +15,6 @@ ModalPopup { height: 237 width: 400 - property Popup parentPopup property string contactAddress: "" property string contactName: "" diff --git a/ui/imports/shared/popups/qmldir b/ui/imports/shared/popups/qmldir index 89e823b52e..870324edca 100644 --- a/ui/imports/shared/popups/qmldir +++ b/ui/imports/shared/popups/qmldir @@ -4,6 +4,7 @@ SettingsDirtyToastMessage 1.0 SettingsDirtyToastMessage.qml ConfirmationDialog 1.0 ConfirmationDialog.qml CommunityIntroDialog 1.0 CommunityIntroDialog.qml ContactVerificationRequestPopup 1.0 ContactVerificationRequestPopup.qml +OutgoingContactVerificationRequestPopup 1.0 OutgoingContactVerificationRequestPopup.qml DownloadModal 1.0 DownloadModal.qml DownloadPage 1.0 DownloadPage.qml InviteFriendsPopup 1.0 InviteFriendsPopup.qml @@ -17,7 +18,7 @@ UnblockContactConfirmationDialog 1.0 UnblockContactConfirmationDialog.qml UserStatusContextMenu 1.0 UserStatusContextMenu.qml SignTransactionModal 1.0 SignTransactionModal.qml SelectAccountModal 1.0 SelectAccountModal.qml -ProfilePopup 1.0 ProfilePopup.qml +ProfileDialog 1.0 ProfileDialog.qml ImageCropWorkflow 1.0 ImageCropWorkflow.qml ImportCommunityPopup 1.0 ImportCommunityPopup.qml DisplayNamePopup 1.0 DisplayNamePopup.qml diff --git a/ui/imports/shared/status/StatusChatImageQtyValidator.qml b/ui/imports/shared/status/StatusChatImageQtyValidator.qml index aa2e9bd038..4b170a9b89 100644 --- a/ui/imports/shared/status/StatusChatImageQtyValidator.qml +++ b/ui/imports/shared/status/StatusChatImageQtyValidator.qml @@ -8,7 +8,7 @@ import ".." StatusChatImageValidator { id: root - errorMessage: qsTr("You can only upload %1 images at a time").arg(Constants.maxUploadFiles) + errorMessage: qsTr("You can only upload %n image(s) at a time", "", Constants.maxUploadFiles) onImagesChanged: { root.isValid = images.length <= Constants.maxUploadFiles diff --git a/ui/imports/shared/status/StatusETHTransactionModal.qml b/ui/imports/shared/status/StatusETHTransactionModal.qml index 90a4942cc6..560aa354f3 100644 --- a/ui/imports/shared/status/StatusETHTransactionModal.qml +++ b/ui/imports/shared/status/StatusETHTransactionModal.qml @@ -57,7 +57,7 @@ ModalPopup { root.close(); } catch (e) { console.error('Error sending the transaction', e) - sendingError.text = "Error sending the transaction: " + e.message; + sendingError.text = qsTr("Error sending the transaction: %1").arg(e.message); return sendingError.open() } } diff --git a/ui/imports/shared/status/StatusGifPopup.qml b/ui/imports/shared/status/StatusGifPopup.qml index 8a7f3f3edd..0016db36f1 100644 --- a/ui/imports/shared/status/StatusGifPopup.qml +++ b/ui/imports/shared/status/StatusGifPopup.qml @@ -63,8 +63,6 @@ Popup { searchBox.input.edit.forceActiveFocus() if (RootStore.isTenorWarningAccepted) { RootStore.getTrendingsGifs() - } else { - confirmationPopupLoader.active = true } } @@ -202,7 +200,7 @@ Popup { sourceComponent: ConfirmationPopup { visible: true } - active: false + active: !RootStore.isTenorWarningAccepted } Component { diff --git a/ui/imports/shared/status/StatusStickersPopup.qml b/ui/imports/shared/status/StatusStickersPopup.qml index 4977b561e8..041f95ce4c 100644 --- a/ui/imports/shared/status/StatusStickersPopup.qml +++ b/ui/imports/shared/status/StatusStickersPopup.qml @@ -45,7 +45,7 @@ Popup { } Connections { target: mainModule - onOnlineStatusChanged: { + function onOnlineStatusChanged() { root.close() } } diff --git a/ui/imports/shared/stores/RootStore.qml b/ui/imports/shared/stores/RootStore.qml index cd28071e9c..3a0db36c77 100644 --- a/ui/imports/shared/stores/RootStore.qml +++ b/ui/imports/shared/stores/RootStore.qml @@ -179,7 +179,6 @@ QtObject { function findTokenSymbolByAddress(address) { return walletSectionAllTokens.findTokenSymbolByAddress(address) - } function getNameForSavedWalletAddress(address) { diff --git a/ui/imports/shared/views/AssetsView.qml b/ui/imports/shared/views/AssetsView.qml index b34134ebc4..bc1ad5c7f7 100644 --- a/ui/imports/shared/views/AssetsView.qml +++ b/ui/imports/shared/views/AssetsView.qml @@ -44,7 +44,7 @@ Item { delegate: StatusListItem { readonly property string balance: enabledNetworkBalance // Needed for the tests objectName: "AssetView_TokenListItem_" + symbol - width: parent.width + width: ListView.view.width title: name subTitle: `${enabledNetworkBalance} ${symbol}` asset.name: symbol ? Style.png("tokens/" + symbol) : "" diff --git a/ui/imports/shared/views/HistoryView.qml b/ui/imports/shared/views/HistoryView.qml index ef55196342..7e0c42ffec 100644 --- a/ui/imports/shared/views/HistoryView.qml +++ b/ui/imports/shared/views/HistoryView.qml @@ -35,7 +35,7 @@ ColumnLayout { target: RootStore.history onLoadingTrxHistoryChanged: function(isLoading, address) { if (historyView.account.address.toLowerCase() === address.toLowerCase()) { - root.isLoading = isLoading + historyView.isLoading = isLoading } } } diff --git a/ui/imports/shared/views/ProfileDialogView.qml b/ui/imports/shared/views/ProfileDialogView.qml new file mode 100644 index 0000000000..2c7ca24068 --- /dev/null +++ b/ui/imports/shared/views/ProfileDialogView.qml @@ -0,0 +1,704 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 +import QtGraphicalEffects 1.14 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 +import StatusQ.Popups 0.1 +import StatusQ.Popups.Dialog 0.1 +import StatusQ.Core.Utils 0.1 as SQUtils + +import utils 1.0 +import shared.controls 1.0 +import shared.panels 1.0 +import shared.popups 1.0 +import shared.controls.chat 1.0 +import shared.controls.chat.menuItems 1.0 + +Pane { + id: root + + property bool readOnly + + property string publicKey: contactsStore.myPublicKey + + property var profileStore + property var contactsStore + + signal closeRequested() + + padding: 0 + topPadding: 40 + + background: StatusDialogBackground { + id: background + } + + QtObject { + id: d + property var contactDetails: Utils.getContactDetailsAsJson(root.publicKey) + + function reload() { + contactDetails = Utils.getContactDetailsAsJson(root.publicKey) + } + + readonly property bool isCurrentUser: root.profileStore.pubkey === root.publicKey + readonly property string userDisplayName: contactDetails.displayName + readonly property string userNickName: contactDetails.localNickname + readonly property string prettyEnsName: '@' + Utils.removeStatusEns(contactDetails.name) + readonly property bool isContact: contactDetails.isContact + readonly property bool isBlocked: contactDetails.isBlocked + + readonly property bool isContactRequestSent: contactDetails.isAdded + readonly property bool isContactRequestReceived: contactDetails.hasAddedUs + + readonly property int outgoingVerificationStatus: contactDetails.verificationStatus + readonly property int incomingVerificationStatus: contactDetails.incomingVerificationStatus + + readonly property bool isVerificationRequestSent: + outgoingVerificationStatus !== Constants.verificationStatus.unverified && + outgoingVerificationStatus !== Constants.verificationStatus.verified && + outgoingVerificationStatus !== Constants.verificationStatus.trusted + readonly property bool isVerificationRequestReceived: d.isCurrentUser ? false : root.contactsStore.hasReceivedVerificationRequestFrom(root.publicKey) + + readonly property bool isTrusted: outgoingVerificationStatus === Constants.verificationStatus.trusted || + incomingVerificationStatus === Constants.verificationStatus.trusted + readonly property bool isVerified: outgoingVerificationStatus === Constants.verificationStatus.verified + + readonly property string linkToProfile: { + let user = "" + if (d.isCurrentUser) + user = root.profileStore.ensName + else + user = contactDetails.name + if (!user) + user = root.publicKey + return Constants.userLinkPrefix + user + } + + readonly property var conns: Connections { + target: Global + function onNickNameChanged(publicKey, nickname) { + if (publicKey === root.publicKey) d.reload() + } + function onContactBlocked(publicKey) { + if (publicKey === root.publicKey) d.reload() + } + function onContactUnblocked(publicKey) { + if (publicKey === root.publicKey) d.reload() + } + } + } + + function reload() { + d.reload() + } + + Component { + id: btnEditProfileComponent + StatusButton { + size: StatusButton.Size.Small + text: qsTr("Edit Profile") + enabled: !root.readOnly + onClicked: { + Global.changeAppSectionBySectionType(Constants.appSection.profile) + root.closeRequested() + } + } + } + + Component { + id: btnSendMessageComponent + StatusButton { + size: StatusButton.Size.Small + text: qsTr("Send Message") + onClicked: { + root.contactsStore.joinPrivateChat(root.publicKey) + root.closeRequested() + } + } + } + + Component { + id: btnAcceptContactRequestComponent + ColumnLayout { + spacing: Style.current.halfPadding + + StatusBaseText { + color: Theme.palette.baseColor1 + font.pixelSize: 13 + text: qsTr("Respond to contact request") + } + + AcceptRejectOptionsButtonsPanel { + menuButton.visible: false + onAcceptClicked: { + root.contactsStore.acceptContactRequest(root.publicKey) + d.reload() + } + onDeclineClicked: { + root.contactsStore.dismissContactRequest(root.publicKey) + d.reload() + } + } + } + } + + Component { + id: btnSendContactRequestComponent + StatusButton { + size: StatusButton.Size.Small + text: qsTr("Send Contact Request") + onClicked: { + let contactRequestPopup = Global.openContactRequestPopup(root.publicKey) + contactRequestPopup.accepted.connect(d.reload) + } + } + } + + Component { + id: btnBlockUserComponent + StatusButton { + size: StatusButton.Size.Small + type: StatusBaseButton.Type.Danger + text: qsTr("Block User") + onClicked: Global.blockContactRequested(root.publicKey, d.userDisplayName) + } + } + + Component { + id: btnUnblockUserComponent + StatusButton { + size: StatusButton.Size.Small + text: qsTr("Unblock User") + onClicked: Global.unblockContactRequested(root.publicKey, d.userDisplayName) + } + } + + Component { + id: txtPendingContactRequestComponent + StatusBaseText { + font.pixelSize: 13 + font.weight: Font.Medium + color: Theme.palette.baseColor1 + verticalAlignment: Text.AlignVCenter + text: qsTr("Contact Request Pending...") + } + } + + Component { + id: txtRejectedContactRequestComponent + StatusBaseText { + font.pixelSize: 13 + font.weight: Font.Medium + color: Theme.palette.baseColor1 + verticalAlignment: Text.AlignVCenter + text: qsTr("Contact Request Rejected") + } + } + + Component { + id: btnRespondToIdRequestComponent + StatusButton { + size: StatusButton.Size.Small + text: qsTr("Respond to ID Request") + onClicked: { + let idRequestPopup = Global.openIncomingIDRequestPopup(root.publicKey) + idRequestPopup.closed.connect(d.reload) + } + } + } + + ConfirmationDialog { + id: removeContactConfirmationDialog + header.title: qsTr("Remove contact '%1'").arg(d.userDisplayName) + confirmationText: qsTr("This will remove the user as a contact. Please confirm.") + onConfirmButtonClicked: { + root.contactsStore.removeContact(root.publicKey) + close() + d.reload() + } + } + + ConfirmationDialog { + id: removeVerificationConfirmationDialog + header.title: qsTr("Remove contact verification") + confirmationText: qsTr("This will remove the contact's verified status. Please confirm.") + onConfirmButtonClicked: { + root.contactsStore.removeTrustStatus(root.publicKey) + close() + d.reload() + } + } + + ColumnLayout { + id: column + spacing: 20 + anchors { + fill: parent + leftMargin: Style.current.bigPadding + rightMargin: Style.current.bigPadding + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.current.halfPadding + + UserImage { + Layout.alignment: Qt.AlignTop + objectName: "ProfileDialog_userImage" + name: d.userDisplayName + pubkey: root.publicKey + image: d.contactDetails.largeImage + interactive: false + imageWidth: 80 + imageHeight: imageWidth + showRing: !d.contactDetails.ensVerified + } + + ColumnLayout { + Layout.fillWidth: true + Layout.leftMargin: 4 + Layout.alignment: Qt.AlignTop + spacing: 4 + 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) + objectName: "ProfileDialog_displayName" + font.bold: true + font.pixelSize: 22 + elide: Text.ElideRight + text: d.userDisplayName + } + StatusContactVerificationIcons { + id: verificationIcons + anchors.left: contactName.right + anchors.leftMargin: Style.current.halfPadding + anchors.verticalCenter: contactName.verticalCenter + objectName: "ProfileDialog_userVerificationIcons" + visible: !d.isCurrentUser + isContact: d.isContact + trustIndicator: d.contactDetails.trustStatus + tiny: false + } + } + StatusBaseText { + id: contactSecondaryName + font.pixelSize: 12 + color: Theme.palette.baseColor1 + text: { + let result = "" + if (d.userNickName) { + if (d.contactDetails.ensVerified) + result = d.prettyEnsName + else + result = d.contactDetails.optionalName // original display name + } + if (result) + return "(%1)".arg(result) + return "" + } + visible: text + } + EmojiHash { + objectName: "ProfileDialog_userEmojiHash" + publicKey: root.publicKey + } + } + + Loader { + Layout.alignment: Qt.AlignTop + Layout.preferredHeight: menuButton.visible ? menuButton.height : -1 + sourceComponent: { + // current user + if (d.isCurrentUser) + return btnEditProfileComponent + + // contact request, outgoing, rejected + if (!d.isContact && d.isContactRequestSent && d.outgoingVerificationStatus === Constants.verificationStatus.declined) + return txtRejectedContactRequestComponent + // contact request, outgoing, pending + if (!d.isContact && d.isContactRequestSent) + return txtPendingContactRequestComponent + + // contact request, incoming, pending + if (!d.isContact && d.isContactRequestReceived) + return btnAcceptContactRequestComponent + + // contact request, incoming, rejected + if (d.isContactRequestSent && d.incomingVerificationStatus === Constants.verificationStatus.declined) + return btnBlockUserComponent + + // verified contact request, incoming, pending + if (d.isContact && !d.isTrusted && d.isVerificationRequestReceived) + return btnRespondToIdRequestComponent + + // block user + if (!d.isContact && !d.isBlocked && + (d.contactDetails.trustStatus === Constants.trustStatus.untrustworthy || d.outgoingVerificationStatus === Constants.verificationStatus.declined)) + return btnBlockUserComponent + + // send contact request + if (!d.isContact && !d.isBlocked && !d.isContactRequestSent) + return btnSendContactRequestComponent + + // blocked contact + if (d.isBlocked) + return btnUnblockUserComponent + + // send message + if (d.isContact && !d.isBlocked) + return btnSendMessageComponent + + console.warn("!!! UNHANDLED CONTACT ACTION BUTTON; PUBKEY", root.publicKey) + } + } + + StatusFlatButton { + id: menuButton + Layout.alignment: Qt.AlignTop + Layout.preferredWidth: height + + visible: !d.isCurrentUser + size: StatusBaseButton.Size.Small + horizontalPadding: 6 + verticalPadding: 6 + icon.name: "more" + icon.color: Theme.palette.directColor1 + highlighted: moreMenu.opened + onClicked: moreMenu.popup(-moreMenu.width + width, height + 4) + + StatusPopupMenu { + id: moreMenu + width: 230 + + SendContactRequestMenuItem { + enabled: !d.isContact && !d.isBlocked && !d.isContactRequestSent && !d.contactDetails.removed && + d.contactDetails.trustStatus === Constants.trustStatus.untrustworthy // we have an action button otherwise + onTriggered: { + moreMenu.close() + let contactRequestPopup = Global.openContactRequestPopup(root.publicKey) + contactRequestPopup.closed.connect(d.reload) + } + } + StatusMenuItem { + text: qsTr("Verify Identity") + icon.name: "checkmark-circle" + enabled: d.isContact && !d.isBlocked && + d.outgoingVerificationStatus === Constants.verificationStatus.unverified && + !d.isVerificationRequestReceived + onTriggered: { + moreMenu.close() + let idRequestPopup = Global.openSendIDRequestPopup(root.publicKey) + idRequestPopup.accepted.connect(d.reload) + } + } + StatusMenuItem { + text: qsTr("ID Request Pending...") + icon.name: "checkmark-circle" + enabled: d.isContact && !d.isBlocked && !d.isTrusted && d.isVerificationRequestSent + onTriggered: { + moreMenu.close() + let idRequestPopup = Global.openOutgoingIDRequestPopup(root.publicKey) + idRequestPopup.closed.connect(d.reload) + } + } + StatusMenuItem { + text: qsTr("Rename") + icon.name: "edit_pencil" + onTriggered: { + moreMenu.close() + Global.openNicknamePopupRequested(root.publicKey, d.userNickName, + "%1 (%2)".arg(d.userDisplayName).arg(Utils.getElidedCompressedPk(root.publicKey))) + } + } + StatusMenuItem { + text: qsTr("Reverse Contact Rejection") + icon.name: "refresh" + enabled: d.contactDetails.removed + onTriggered: { + moreMenu.close() + root.contactsStore.removeContactRequestRejection(root.publicKey) + d.reload() + } + } + StatusMenuItem { + text: qsTr("Copy Link to Profile") + icon.name: "copy" + onTriggered: { + moreMenu.close() + root.profileStore.copyToClipboard(d.linkToProfile) + } + } + StatusMenuItem { + text: qsTr("Unblock User") + icon.name: "remove-circle" + enabled: d.isBlocked + onTriggered: { + moreMenu.close() + Global.unblockContactRequested(root.publicKey, d.userDisplayName) + } + } + StatusMenuSeparator {} + StatusMenuItem { + text: qsTr("Mark as Untrustworthy") + icon.name: "warning" + type: StatusMenuItem.Type.Danger + enabled: d.contactDetails.trustStatus === Constants.trustStatus.unknown + onTriggered: { + moreMenu.close() + if (d.isContact && !d.isTrusted && d.isVerificationRequestReceived) + root.contactsStore.verifiedUntrustworthy(root.publicKey) + else + root.contactsStore.markUntrustworthy(root.publicKey) + d.reload() + } + } + StatusMenuItem { + text: qsTr("Remove Untrustworthy Mark") + icon.name: "warning" + enabled: d.contactDetails.trustStatus === Constants.trustStatus.untrustworthy + onTriggered: { + moreMenu.close() + root.contactsStore.removeTrustStatus(root.publicKey) + d.reload() + } + } + StatusMenuItem { + text: qsTr("Remove Identity Verification") + icon.name: "warning" + type: StatusMenuItem.Type.Danger + enabled: d.isContact && d.isTrusted + onTriggered: { + moreMenu.close() + removeVerificationConfirmationDialog.open() + } + } + StatusMenuItem { + text: qsTr("Remove Contact") + icon.name: "remove-contact" + type: StatusMenuItem.Type.Danger + enabled: d.isContact && !d.isBlocked + onTriggered: { + moreMenu.close() + removeContactConfirmationDialog.open() + } + } + StatusMenuItem { + text: qsTr("Cancel Contact Request") + icon.name: "cancel" + type: StatusMenuItem.Type.Danger + enabled: !d.isContact && d.isContactRequestSent && !d.contactDetails.removed + onTriggered: { + moreMenu.close() + root.contactsStore.removeContact(root.publicKey) + d.reload() + } + } + StatusMenuItem { + text: qsTr("Block User") + icon.name: "cancel" + type: StatusMenuItem.Type.Danger + enabled: !d.isBlocked + onTriggered: { + moreMenu.close() + Global.blockContactRequested(root.publicKey, d.userDisplayName) + } + } + } + } + } + + StatusDialogDivider { + Layout.fillWidth: true + Layout.leftMargin: -column.anchors.leftMargin + Layout.rightMargin: -column.anchors.rightMargin + Layout.topMargin: -column.spacing + Layout.bottomMargin: -column.spacing + opacity: scrollView.atYBeginning ? 0 : 1 + Behavior on opacity { OpacityAnimator {} } + } + + StatusScrollView { + id: scrollView + Layout.fillWidth: true + Layout.fillHeight: true + Layout.leftMargin: -column.anchors.leftMargin + Layout.rightMargin: -column.anchors.rightMargin + Layout.topMargin: -column.spacing + padding: 0 + + ColumnLayout { + width: scrollView.width + spacing: 20 + + ProfileBioSocialsPanel { + Layout.fillWidth: true + Layout.leftMargin: column.anchors.leftMargin + Style.current.halfPadding + Layout.rightMargin: column.anchors.rightMargin + Style.current.halfPadding + bio: d.contactDetails.bio + userSocialLinksJson: d.contactDetails.socialLinks + } + + GridLayout { + Layout.fillWidth: true + Layout.leftMargin: column.anchors.leftMargin + Layout.rightMargin: column.anchors.rightMargin + flow: GridLayout.TopToBottom + rowSpacing: Style.current.halfPadding + columnSpacing: Style.current.bigPadding + visible: d.isCurrentUser + enabled: visible + columns: 2 + rows: 4 + + StatusBaseText { + Layout.fillWidth: true + text: qsTr("Link to Profile") + font.pixelSize: 13 + } + + StatusBaseInput { + Layout.fillWidth: true + Layout.preferredHeight: 56 + leftPadding: 14 + rightPadding: Style.current.halfPadding + topPadding: 0 + bottomPadding: 0 + placeholder.rightPadding: Style.current.halfPadding + placeholderText: d.linkToProfile + placeholderTextColor: Theme.palette.directColor1 + edit.readOnly: true + rightComponent: StatusButton { + anchors.verticalCenter: parent.verticalCenter + borderColor: Theme.palette.primaryColor1 + size: StatusBaseButton.Size.Tiny + text: qsTr("Copy") + onClicked: { + text = qsTr("Copied") + root.profileStore.copyToClipboard(d.linkToProfile) + } + } + } + + StatusBaseText { + Layout.fillWidth: true + Layout.topMargin: Style.current.smallPadding + text: qsTr("Emoji Hash") + font.pixelSize: 13 + } + + StatusBaseInput { + Layout.fillWidth: true + Layout.preferredHeight: 56 + leftPadding: 14 + rightPadding: Style.current.halfPadding + topPadding: 0 + bottomPadding: 0 + edit.readOnly: true + leftComponent: EmojiHash { + publicKey: root.publicKey + oneRow: !root.readOnly + } + rightComponent: StatusButton { + anchors.verticalCenter: parent.verticalCenter + borderColor: Theme.palette.primaryColor1 + size: StatusBaseButton.Size.Tiny + text: qsTr("Copy") + onClicked: { + root.profileStore.copyToClipboard(Utils.getEmojiHashAsJson(root.publicKey).join("").toString()) + text = qsTr("Copied") + } + } + } + + Rectangle { + Layout.rowSpan: 4 + Layout.fillHeight: true + Layout.preferredWidth: height + Layout.alignment: Qt.AlignCenter + color: "transparent" + border.width: 1 + border.color: Theme.palette.baseColor2 + radius: Style.current.halfPadding + + Image { + anchors.centerIn: parent + asynchronous: true + fillMode: Image.PreserveAspectFit + width: 170 + height: width + mipmap: true + smooth: false + source: root.profileStore.getQrCodeSource(root.profileStore.pubkey) + } + } + } + + StatusTabBar { + Layout.fillWidth: true + Layout.leftMargin: column.anchors.leftMargin + Layout.rightMargin: column.anchors.rightMargin + bottomPadding: -4 + + StatusTabButton { + leftPadding: 0 + width: implicitWidth + text: qsTr("Tokens") + } + StatusTabButton { + width: implicitWidth + text: qsTr("NFTs") + } + StatusTabButton { + width: implicitWidth + text: qsTr("Communities") + } + StatusTabButton { + width: implicitWidth + text: qsTr("Accounts") + } + } + + StatusDialogBackground { + Layout.fillWidth: true + Layout.topMargin: -column.spacing + Layout.preferredHeight: 300 + color: Theme.palette.baseColor4 + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: parent.radius + color: parent.color + } + + StatusBaseText { + anchors.centerIn: parent + color: Theme.palette.baseColor1 + text: qsTr("More content to appear here soon...") + } + } + } + } + } + + layer.enabled: !root.readOnly // profile preview has its own layer.effect + layer.effect: OpacityMask { + maskSource: Rectangle { + anchors.centerIn: parent + width: column.width + height: column.height + radius: background.radius + } + } +} diff --git a/ui/imports/shared/views/ProfileView.qml b/ui/imports/shared/views/ProfileView.qml deleted file mode 100644 index ac12740b78..0000000000 --- a/ui/imports/shared/views/ProfileView.qml +++ /dev/null @@ -1,484 +0,0 @@ -import QtQuick 2.13 -import QtQuick.Controls 2.13 -import QtQuick.Layouts 1.13 -import QtGraphicalEffects 1.13 - -import utils 1.0 -import shared 1.0 -import shared.popups 1.0 -import shared.stores 1.0 -import shared.views.chat 1.0 -import shared.controls.chat 1.0 -import shared.panels 1.0 - -import StatusQ.Core 0.1 -import StatusQ.Core.Theme 0.1 -import StatusQ.Core.Utils 0.1 as StatusQUtils -import StatusQ.Components 0.1 -import StatusQ.Controls 0.1 -import StatusQ.Popups 0.1 - -import "../panels" - -Rectangle { - id: root - - property Popup parentPopup - - property var profileStore - property var contactsStore - - - property string userPublicKey: profileStore.pubkey - property string userDisplayName: profileStore.displayName - property string userName: profileStore.username - property string userNickname: profileStore.details.localNickname - property string userEnsName: profileStore.ensName - property string userIcon: profileStore.profileLargeImage - property string userBio: profileStore.bio - property string userSocialLinks: profileStore.socialLinksJson - property string text: "" - - property bool userIsEnsVerified: profileStore.details.ensVerified - property bool userIsBlocked: profileStore.details.isBlocked - property bool isCurrentUser: profileStore.pubkey === userPublicKey - property bool isAddedContact: false - - property int userTrustStatus: Constants.trustStatus.unknown - property int outgoingVerificationStatus: Constants.verificationStatus.unverified - - property string challenge: "" - property string response: "" - - property bool userIsUntrustworthy: false - property bool userTrustIsUnknown: false - property bool isContact: false - property bool isVerificationSent: false - property bool isVerified: false - property bool isTrusted: false - property bool hasReceivedVerificationRequest: false - - property bool showRemoveVerified: false - property bool showVerifyIdentitySection: false - property bool showVerificationPendingSection: false - property bool showIdentityVerified: false - property bool showIdentityVerifiedUntrustworthy: false - - property string verificationChallenge: "" - property string verificationResponse: "" - property string verificationResponseDisplayName: "" - property string verificationResponseIcon: "" - property string verificationRequestedAt: "" - property string verificationRepliedAt: "" - - readonly property alias qrCodePopup: qrCodePopup - readonly property alias unblockContactConfirmationDialog: unblockContactConfirmationDialog - readonly property alias blockContactConfirmationDialog: blockContactConfirmationDialog - readonly property alias removeContactConfirmationDialog: removeContactConfirmationDialog - readonly property alias wizardAnimation: wizardAnimation - readonly property alias challengeTxt: challengeTxt - readonly property alias stepsListModel: stepsListModel - readonly property alias nicknamePopup: nicknamePopup - - readonly property int animationDuration: 500 - - signal contactUnblocked(publicKey: string) - signal contactBlocked(publicKey: string) - signal contactAdded(publicKey: string) - signal contactRemoved(publicKey: string) - signal nicknameEdited(publicKey: string) - - objectName: "profileView" - implicitWidth: modalContent.implicitWidth - implicitHeight: modalContent.implicitHeight - - color: Theme.palette.statusAppLayout.backgroundColor - radius: 8 - - QtObject { - id: d - readonly property string subTitle: root.userIsEnsVerified ? root.userName : Utils.getElidedCompressedPk(userPublicKey) - readonly property int subTitleElide: Text.ElideMiddle - } - - SequentialAnimation { - id: wizardAnimation - ScriptAction { - id: step1 - property int loadingTime: 0 - Behavior on loadingTime { NumberAnimation { duration: animationDuration }} - onLoadingTimeChanged: { - if (isVerificationSent) { - stepsListModel.setProperty(1, "loadingTime", step1.loadingTime); - } - } - script: { - step1.loadingTime = animationDuration; - stepsListModel.setProperty(0, "loadingTime", step1.loadingTime); - - if (isVerificationSent) { - stepsListModel.setProperty(0, "stepCompleted", true); - } - } - } - PauseAnimation { - duration: animationDuration + 100 - } - ScriptAction { - id: step2 - property int loadingTime: 0 - Behavior on loadingTime { NumberAnimation { duration: animationDuration } } - onLoadingTimeChanged: { - if (isVerificationSent && !!verificationResponse) { - stepsListModel.setProperty(2, "loadingTime", step2.loadingTime); - } - } - script: { - if (isVerificationSent && !!verificationChallenge) { - step2.loadingTime = animationDuration; - if (isVerificationSent && !!verificationResponse) { - stepsListModel.setProperty(1, "stepCompleted", true); - } - } - } - } - PauseAnimation { - duration: animationDuration + 100 - } - ScriptAction { - script: { - if (outgoingVerificationStatus === Constants.verificationStatus.trusted) { - stepsListModel.setProperty(2, "stepCompleted", true); - } - } - } - } - - ColumnLayout { - id: modalContent - anchors.fill: parent - - Item { - Layout.fillWidth: true - implicitHeight: 32 - } - - ProfileHeader { - Layout.fillWidth: true - - displayName: root.userDisplayName - pubkey: root.userPublicKey - icon: root.isCurrentUser ? root.profileStore.profileLargeImage : root.userIcon - trustStatus: root.userTrustStatus - isContact: root.isContact - store: root.profileStore - isCurrentUser: root.isCurrentUser - userIsEnsVerified: root.userIsEnsVerified - - displayNameVisible: false - displayNamePlusIconsVisible: true - pubkeyVisibleWithCopy: true - pubkeyVisible: false - imageSize: ProfileHeader.ImageSize.Middle - editImageButtonVisible: root.isCurrentUser - onEditClicked: { - if(!isCurrentUser){ - nicknamePopup.open() - } else { - Global.openEditDisplayNamePopup() - } - } - } - - StatusBanner { - Layout.fillWidth: true - visible: root.userIsBlocked - type: StatusBanner.Type.Danger - statusText: qsTr("Blocked") - } - - ProfileBioSocialsPanel { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - bio: root.userBio - userSocialLinksJson: root.userSocialLinks - } - - StatusDescriptionListItem { - Layout.fillWidth: true - visible: !showVerifyIdentitySection && !showVerificationPendingSection && !showIdentityVerified - title: qsTr("Chat key") - subTitle: Utils.getCompressedPk(root.userPublicKey) - subTitleComponent.elide: Text.ElideMiddle - subTitleComponent.width: 320 - subTitleComponent.font.family: Theme.palette.monoFont.name - tooltip.text: qsTr("Copied to clipboard") - tooltip.timeout: 1000 - asset.name: "copy" - iconButton.onClicked: { - globalUtils.copyToClipboard(subTitle) - tooltip.open(); - } - } - - StatusDescriptionListItem { - Layout.fillWidth: true - visible: !showVerifyIdentitySection && !showVerificationPendingSection && !showIdentityVerified - title: qsTr("Share Profile URL") - subTitle: { - let user = "" - if (isCurrentUser) { - user = root.profileStore.ensName !== "" ? root.profileStore.ensName : StatusQUtils.Utils.elideText(root.profileStore.pubkey, 5) - } else if (userIsEnsVerified) { - user = userEnsName - } - - if (user === ""){ - user = StatusQUtils.Utils.elideText(userPublicKey, 5) - } - return Constants.userLinkPrefix + user; - } - tooltip.text: qsTr("Copied to clipboard") - tooltip.timeout: 1000 - asset.name: "copy" - iconButton.onClicked: { - let user = "" - if (isCurrentUser) { - user = root.profileStore.ensName !== "" ? root.profileStore.ensName : root.profileStore.pubkey - } else { - user = (userEnsName !== "" ? userEnsName : userPublicKey) - } - root.profileStore.copyToClipboard(Constants.userLinkPrefix + user) - tooltip.open(); - } - } - - ListModel { - id: stepsListModel - ListElement {description: qsTr("Send Request"); loadingTime: 0; stepCompleted: false} - ListElement {description: qsTr("Receive Response"); loadingTime: 0; stepCompleted: false} - ListElement {description: qsTr("Confirm Identity"); loadingTime: 0; stepCompleted: false} - } - - StatusWizardStepper { - id: wizardStepper - maxDuration: animationDuration - visible: showVerifyIdentitySection || showVerificationPendingSection || showIdentityVerified || showIdentityVerifiedUntrustworthy - width: parent.width - stepsModel: stepsListModel - } - - Separator { - visible: wizardStepper.visible - implicitHeight: 32 - } - - StatusBaseText { - id: confirmLbl - visible: showIdentityVerified - text: qsTr("You have confirmed %1's identity. From now on this verification emblem will always be displayed alongside %1's nickname.").arg(userDisplayName) - font.pixelSize: Style.current.additionalTextSize - horizontalAlignment : Text.AlignHCenter - Layout.alignment: Qt.AlignHCenter - Layout.preferredWidth: 363 - wrapMode: Text.WordWrap - color: Theme.palette.baseColor1 - } - - StatusBaseText { - id: confirmUntrustworthyLbl - visible: showIdentityVerifiedUntrustworthy - text: qsTr("You have marked %1 as Untrustworthy. From now on this Untrustworthy emblem will always be displayed alongside %1's nickname.").arg(userDisplayName) - font.pixelSize: Style.current.additionalTextSize - horizontalAlignment : Text.AlignHCenter - Layout.alignment: Qt.AlignHCenter - Layout.preferredWidth: 363 - wrapMode: Text.WordWrap - color: Theme.palette.baseColor1 - } - - Item { - visible: checkboxIcon.visible || dangerIcon.visible - Layout.fillWidth: true - implicitHeight: visible ? 16 : 0 - } - - StatusRoundIcon { - id: checkboxIcon - visible: confirmLbl.visible - asset.name: "checkbox" - asset.width: 16 - asset.height: 16 - asset.color: Theme.palette.white - Layout.alignment: Qt.AlignHCenter - color: Theme.palette.primaryColor1 - width: 32 - height: 32 - } - - StatusDescriptionListItem { - Layout.fillWidth: true - visible: !showVerifyIdentitySection && !showVerificationPendingSection && !showIdentityVerified - title: root.userIsEnsVerified ? qsTr("ENS username") : qsTr("Username") - subTitle: root.userIsEnsVerified ? root.userEnsName : root.userName - tooltip.text: qsTr("Copied to clipboard") - tooltip.timeout: 1000 - asset.name: "copy" - iconButton.onClicked: { - globalUtils.copyToClipboard(subTitle) - tooltip.open(); - } - } - - StatusRoundIcon { - id: dangerIcon - visible: confirmUntrustworthyLbl.visible - asset.name: "tiny/subtract" - asset.width: 5 - asset.height: 21 - asset.color: Theme.palette.white - Layout.alignment: Qt.AlignHCenter - color: Theme.palette.dangerColor1 - width: 32 - height: 32 - } - - Item { - visible: checkboxIcon.visible || dangerIcon.visible - height: visible ? 16 : 0 - Layout.fillWidth: true - } - - StatusInput { - id: challengeTxt - visible: showVerifyIdentitySection - charLimit: 280 - input.text: root.challenge - Layout.fillWidth: true - Layout.rightMargin: d.contentMargins - Layout.leftMargin: d.contentMargins - input.multiline: true - minimumHeight: 152 - maximumHeight: 152 - placeholderText: 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(userDisplayName) - } - - MessageView { - id: challengeMessage - visible: root.showVerificationPendingSection - Layout.fillWidth: true - isMessage: true - shouldRepeatHeader: true - messageTimestamp: root.verificationRequestedAt - senderId: profileStore.pubkey - senderDisplayName: userProfile.name - senderIcon: userProfile.icon - messageText: root.verificationChallenge - messageContentType: Constants.messageContentType.messageType - placeholderMessage: true - } - - MessageView { - id: responseMessage - visible: root.showVerificationPendingSection && !!root.verificationResponse - Layout.fillWidth: true - isMessage: true - shouldRepeatHeader: true - messageTimestamp: root.verificationRepliedAt - senderId: root.userPublicKey - senderDisplayName: root.verificationResponseDisplayName - senderIcon: root.verificationResponseIcon - messageText: root.verificationResponse - messageContentType: Constants.messageContentType.messageType - placeholderMessage: true - } - - Item { - visible: waitingForText.visible - height: 32 - Layout.fillWidth: true - } - - StatusBaseText { - id: waitingForText - visible: showVerificationPendingSection && !verificationResponse - text: qsTr("Waiting for %1's response...").arg(userIsEnsVerified ? userEnsName : userDisplayName) - font.pixelSize: Style.current.additionalTextSize - horizontalAlignment : Text.AlignHCenter - Layout.alignment: Qt.AlignHCenter - Layout.preferredWidth: 363 - wrapMode: Text.WordWrap - color: Theme.palette.baseColor1 - } - - Item { - height: 32 - Layout.fillWidth: true - } - } - - // TODO: replace with StatusModal - ModalPopup { - id: qrCodePopup - width: 320 - height: 320 - Image { - asynchronous: true - fillMode: Image.PreserveAspectFit - source: globalUtils.qrCode(userPublicKey) - anchors.horizontalCenter: parent.horizontalCenter - height: 212 - width: 212 - mipmap: true - smooth: false - } - } - - UnblockContactConfirmationDialog { - id: unblockContactConfirmationDialog - onUnblockButtonClicked: { - root.contactsStore.unblockContact(userPublicKey) - unblockContactConfirmationDialog.close(); - root.contactUnblocked(userPublicKey) - } - } - - BlockContactConfirmationDialog { - id: blockContactConfirmationDialog - onBlockButtonClicked: { - root.contactsStore.blockContact(userPublicKey) - blockContactConfirmationDialog.close(); - root.contactBlocked(userPublicKey) - } - } - - ConfirmationDialog { - id: removeContactConfirmationDialog - header.title: qsTr("Remove contact") - confirmationText: qsTr("Are you sure you want to remove this contact?") - onConfirmButtonClicked: { - if (isAddedContact) { - root.contactsStore.removeContact(userPublicKey); - } - removeContactConfirmationDialog.close(); - root.contactRemoved(userPublicKey) - } - } - - NicknamePopup { - id: nicknamePopup - nickname: root.userNickname - header.subTitle: d.subTitle - header.subTitleElide: d.subTitleElide - onEditDone: { - if (root.userNickname !== newNickname) - { - root.userNickname = newNickname; - root.contactsStore.changeContactNickname(userPublicKey, newNickname); - } - root.nicknameEdited(userPublicKey) - } - } -} diff --git a/ui/imports/shared/views/chat/MessageContextMenuView.qml b/ui/imports/shared/views/chat/MessageContextMenuView.qml index aacb4e874f..4ddf56eb79 100644 --- a/ui/imports/shared/views/chat/MessageContextMenuView.qml +++ b/ui/imports/shared/views/chat/MessageContextMenuView.qml @@ -106,7 +106,7 @@ StatusPopupMenu { property var emojiReactionsReactedByUser: [] - signal openProfileClicked(string publicKey, string state) + signal openProfileClicked(string publicKey) signal pinMessage(string messageId) signal unpinMessage(string messageId) signal pinnedMessagesLimitReached(string messageId) @@ -238,7 +238,7 @@ StatusPopupMenu { id: viewProfileAction enabled: root.isProfile && !root.pinnedPopup onTriggered: { - root.openProfileClicked(root.selectedUserPublicKey, "") + root.openProfileClicked(root.selectedUserPublicKey) root.close() } } @@ -256,8 +256,7 @@ StatusPopupMenu { enabled: root.isProfile && !root.isMe && !root.isContact && !root.isBlockedContact && !root.hasPendingContactRequest onTriggered: { - root.openProfileClicked(root.selectedUserPublicKey, - Constants.profilePopupStates.contactRequest) + Global.openContactRequestPopup(root.selectedUserPublicKey) root.close() } } @@ -270,8 +269,7 @@ StatusPopupMenu { && root.outgoingVerificationStatus === Constants.verificationStatus.unverified && !root.hasReceivedVerificationRequestFrom onTriggered: { - root.openProfileClicked(root.selectedUserPublicKey, - Constants.profilePopupStates.verifyIdentity) + Global.openSendIDRequestPopup(root.selectedUserPublicKey) root.close() } } @@ -288,11 +286,9 @@ StatusPopupMenu { || root.isVerificationRequestSent) onTriggered: { if (hasReceivedVerificationRequestFrom) { - root.openProfileClicked(root.selectedUserPublicKey, - Constants.profilePopupStates.respondToPendingRequest) + Global.openIncomingIDRequestPopup(root.selectedUserPublicKey) } else if (root.isVerificationRequestSent) { - root.openProfileClicked(root.selectedUserPublicKey, - Constants.profilePopupStates.showVerificationPendingSection) + Global.openOutgoingIDRequestPopup(root.selectedUserPublicKey) } root.close() @@ -304,8 +300,8 @@ StatusPopupMenu { icon.name: "edit_pencil" enabled: root.isProfile && !root.isMe onTriggered: { - root.openProfileClicked(root.selectedUserPublicKey, - Constants.profilePopupStates.openNickname) + Global.openNicknamePopupRequested(root.selectedUserPublicKey, d.contactDetails.localNickname, + "%1 (%2)".arg(root.selectedUserDisplayName).arg(Utils.getElidedCompressedPk(root.selectedUserPublicKey))) root.close() } } @@ -314,7 +310,7 @@ StatusPopupMenu { text: qsTr("Unblock User") icon.name: "remove-circle" enabled: root.isProfile && !root.isMe && root.isBlockedContact - onTriggered: root.store.contactsStore.unblockContact(root.selectedUserPublicKey) + onTriggered: Global.unblockContactRequested(root.selectedUserPublicKey, root.selectedUserDisplayName) } StatusMenuSeparator { @@ -344,7 +340,7 @@ StatusPopupMenu { icon.name: "cancel" type: StatusMenuItem.Type.Danger enabled: root.isProfile && !root.isMe && !root.isBlockedContact - onTriggered: root.store.contactsStore.blockContact(root.selectedUserPublicKey) + onTriggered: Global.blockContactRequested(root.selectedUserPublicKey, root.selectedUserDisplayName) } StatusMenuItem { diff --git a/ui/imports/shared/views/qmldir b/ui/imports/shared/views/qmldir index b6cc69e0ac..947b3e7976 100644 --- a/ui/imports/shared/views/qmldir +++ b/ui/imports/shared/views/qmldir @@ -7,7 +7,7 @@ TransactionPreview 1.0 TransactionPreview.qml TransactionSigner 1.0 TransactionSigner.qml TransactionStackView 1.0 TransactionStackView.qml PasswordView 1.0 PasswordView.qml -ProfileView 1.0 ProfileView.qml +ProfileDialogView 1.0 ProfileDialogView.qml AssetsView 1.0 AssetsView.qml HistoryView 1.0 HistoryView.qml AssetsDetailView 1.0 AssetsDetailView.qml diff --git a/ui/imports/utils/Constants.qml b/ui/imports/utils/Constants.qml index e05c97d156..bb3538da29 100644 --- a/ui/imports/utils/Constants.qml +++ b/ui/imports/utils/Constants.qml @@ -280,16 +280,6 @@ QtObject { readonly property int blockedContacts: 6 } - readonly property QtObject profilePopupStates: QtObject { - readonly property string openNickname: "openNickname" - readonly property string contactRequest: "contactRequest" - readonly property string blockUser: "blockUser" - readonly property string unblockUser: "unblockUser" - readonly property string verifyIdentity: "verifyIdentity" - readonly property string showVerificationPendingSection: "showVerificationPendingSection" - readonly property string respondToPendingRequest: "respondToPendingRequest" - } - readonly property QtObject validators: QtObject { readonly property list displayName: [ StatusMinLengthValidator { @@ -567,7 +557,7 @@ QtObject { readonly property int maxNumberOfPins: 3 readonly property var acceptedImageExtensions: [".png", ".jpg", ".jpeg", ".svg", ".gif"] - readonly property var acceptedDragNDropImageExtensions: [".png", ".jpg", ".jpeg", ".heif", "tif", ".tiff"] + readonly property var acceptedDragNDropImageExtensions: [".png", ".jpg", ".jpeg", ".heif", ".tif", ".tiff"] readonly property string mentionSpanTag: `` diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml index b6f0db4e66..e80a26aa60 100644 --- a/ui/imports/utils/Global.qml +++ b/ui/imports/utils/Global.qml @@ -38,7 +38,16 @@ Item { signal openCreateChatView() signal closeCreateChatView() - signal openProfilePopupRequested(string publicKey, var parentPopup, string state) + signal openProfilePopupRequested(string publicKey, var parentPopup) + + signal openNicknamePopupRequested(string publicKey, string nickname, string subtitle) + signal nickNameChanged(string publicKey, string nickname) + + signal blockContactRequested(string publicKey, string contactName) + signal contactBlocked(string publicKey) + signal unblockContactRequested(string publicKey, string contactName) + signal contactUnblocked(string publicKey) + signal openChangeProfilePicPopup() signal displayToastMessage(string title, string subTitle, string icon, bool loading, int ephNotifType, string url) signal openEditDisplayNamePopup() @@ -50,7 +59,7 @@ Item { userPublicKey: publicKey, userDisplayName: contactDetails.displayName, userIcon: contactDetails.largeImage, - userIsEnsVerified: contactDetails.ensVerified, + userIsEnsVerified: contactDetails.ensVerified }) } @@ -61,8 +70,56 @@ Item { }) } - function openProfilePopup(publicKey, parentPopup, state = "") { - openProfilePopupRequested(publicKey, parentPopup, state); + function openSendIDRequestPopup(publicKey) { + const contactDetails = Utils.getContactDetailsAsJson(publicKey); + return openPopup(sendIDRequestPopupComponent, { + userPublicKey: publicKey, + userDisplayName: contactDetails.displayName, + userIcon: contactDetails.largeImage, + userIsEnsVerified: contactDetails.ensVerified, + "header.title": qsTr("Verify %1's Identity").arg(contactDetails.displayName), + 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(contactDetails.displayName), + buttonText: qsTr("Send verification request") + }) + } + + function openIncomingIDRequestPopup(publicKey) { + try { + const request = appMain.rootStore.profileSectionStore.contactsStore.getVerificationDetailsFromAsJson(publicKey) + return openPopup(contactVerificationRequestPopupComponent, { + senderPublicKey: request.from, + senderDisplayName: request.displayName, + senderIcon: request.icon, + challengeText: request.challenge, + responseText: request.response, + messageTimestamp: request.requestedAt, + responseTimestamp: request.repliedAt + }) + } catch (e) { + console.error("Error getting or parsing verification data", e) + } + } + + function openOutgoingIDRequestPopup(publicKey) { + try { + const verificationDetails = appMain.rootStore.profileSectionStore.contactsStore.getSentVerificationDetailsAsJson(publicKey) + return openPopup(contactOutgoingVerificationRequestPopupComponent, { + userPublicKey: publicKey, + verificationStatus: verificationDetails.requestStatus, + verificationChallenge: verificationDetails.challenge, + verificationResponse: verificationDetails.response, + verificationResponseDisplayName: verificationDetails.displayName, + verificationResponseIcon: verificationDetails.icon, + verificationRequestedAt: verificationDetails.requestedAt, + verificationRepliedAt: verificationDetails.repliedAt + }) + } catch (e) { + console.error("Error getting or parsing verification data", e) + } + } + + function openProfilePopup(publicKey, parentPopup) { + openProfilePopupRequested(publicKey, parentPopup) } function openActivityCenterPopup() { @@ -145,9 +202,45 @@ Item { anchors.centerIn: parent rootStore: appMain.rootStore contactsStore: appMain.rootStore.contactStore - onClosed: { - destroy() + onClosed: destroy() + } + } + + Component { + id: sendIDRequestPopupComponent + SendContactRequestModal { + anchors.centerIn: parent + onAccepted: appMain.rootStore.profileSectionStore.contactsStore.sendVerificationRequest(userPublicKey, message) + onClosed: destroy() + } + } + + Component { + id: contactVerificationRequestPopupComponent + ContactVerificationRequestPopup { + onResponseSent: { + appMain.rootStore.profileSectionStore.contactsStore.acceptVerificationRequest(senderPublicKey, response) } + onVerificationRefused: { + appMain.rootStore.profileSectionStore.contactsStore.declineVerificationRequest(senderPublicKey) + } + onClosed: destroy() + } + } + + Component { + id: contactOutgoingVerificationRequestPopupComponent + OutgoingContactVerificationRequestPopup { + onVerificationRequestCanceled: { + appMain.rootStore.profileSectionStore.contactsStore.cancelVerificationRequest(userPublicKey) + } + onUntrustworthyVerified: { + appMain.rootStore.profileSectionStore.contactsStore.verifiedUntrustworthy(userPublicKey) + } + onTrustedVerified: { + appMain.rootStore.profileSectionStore.contactsStore.verifiedTrusted(userPublicKey) + } + onClosed: destroy() } } } diff --git a/ui/imports/utils/Utils.qml b/ui/imports/utils/Utils.qml index 9b2b6f31db..2c2d741df5 100644 --- a/ui/imports/utils/Utils.qml +++ b/ui/imports/utils/Utils.qml @@ -580,7 +580,7 @@ QtObject { function isEnsVerified(publicKey, getVerificationRequest=true) { if (!publicKey) - return false + return false return getContactDetailsAsJson(publicKey, getVerificationRequest).ensVerified }