From 7323889a8c2ce8ec60169ddfd4a234f755041382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Cie=C5=9Blak?= Date: Thu, 21 Nov 2024 16:10:58 +0100 Subject: [PATCH] ContactDetails decomposed into smaller, more specialized components Closes: #16793 --- storybook/pages/ContactDetailsPage.qml | 194 ----------- storybook/pages/ProfileDialogViewPage.qml | 308 +++++------------ .../qmlTests/tests/tst_AllContactsAdaptor.qml | 137 ++++++++ .../qmlTests/tests/tst_ContactDetails.qml | 320 ------------------ .../Profile/helpers/ContactDetails.qml | 125 ++----- .../Profile/helpers/ContactModelEntry.qml | 57 ++++ ui/app/AppLayouts/Profile/helpers/qmldir | 1 + .../Profile/views/MyProfileView.qml | 28 +- .../views/profile/MyProfilePreview.qml | 4 +- ui/app/mainui/AppMain.qml | 20 ++ ui/app/mainui/Popups.qml | 25 +- ui/app/mainui/adaptors/AllContactsAdaptor.qml | 97 ++++++ ui/app/mainui/adaptors/qmldir | 1 + ui/imports/shared/popups/ProfileDialog.qml | 7 +- ui/imports/shared/views/ProfileDialogView.qml | 28 +- 15 files changed, 475 insertions(+), 877 deletions(-) delete mode 100644 storybook/pages/ContactDetailsPage.qml create mode 100644 storybook/qmlTests/tests/tst_AllContactsAdaptor.qml delete mode 100644 storybook/qmlTests/tests/tst_ContactDetails.qml create mode 100644 ui/app/AppLayouts/Profile/helpers/ContactModelEntry.qml create mode 100644 ui/app/mainui/adaptors/AllContactsAdaptor.qml diff --git a/storybook/pages/ContactDetailsPage.qml b/storybook/pages/ContactDetailsPage.qml deleted file mode 100644 index 4976db9379..0000000000 --- a/storybook/pages/ContactDetailsPage.qml +++ /dev/null @@ -1,194 +0,0 @@ -import QtTest 1.15 - -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.15 -import StatusQ.Core.Utils 0.1 -import Models 1.0 - -import AppLayouts.Profile.helpers 1.0 -import AppLayouts.Profile.stores 1.0 - -SplitView { - id: root - - Pane { - SplitView.fillWidth: true - SplitView.fillHeight: true - contentItem: ColumnLayout { - clip: true - spacing: 5 - Label { - Layout.fillWidth: true - text: "publicKey: " + contactDetails.publicKey - font.bold: true - } - Label { - Layout.fillWidth: true - text: "loading: " + contactDetails.loading - font.bold: true - } - Label { - Layout.fillWidth: true - text: "displayName: " + contactDetails.displayName - } - Label { - Layout.fillWidth: true - text: "ensName: " + contactDetails.ensName - } - Label { - Layout.fillWidth: true - text: "ensVerified: " + contactDetails.ensVerified - } - Label { - Layout.fillWidth: true - text: "localNickname: " + contactDetails.localNickname - } - Label { - Layout.fillWidth: true - text: "alias: " + contactDetails.alias - } - Label { - Layout.fillWidth: true - text: "icon: " + contactDetails.icon - } - Label { - Layout.fillWidth: true - text: "colorId: " + contactDetails.colorId - } - Label { - Layout.fillWidth: true - text: "colorHash: " + contactDetails.colorHash - } - Label { - Layout.fillWidth: true - text: "onlineStatus: " + contactDetails.onlineStatus - } - Label { - Layout.fillWidth: true - text: "isContact: " + contactDetails.isContact - } - Label { - Layout.fillWidth: true - text: "isCurrentUser: " + contactDetails.isCurrentUser - } - Label { - Layout.fillWidth: true - text: "isVerified: " + contactDetails.isVerified - } - Label { - Layout.fillWidth: true - text: "isUntrustworthy: " + contactDetails.isUntrustworthy - } - Label { - Layout.fillWidth: true - text: "isBlocked: " + contactDetails.isBlocked - } - Label { - Layout.fillWidth: true - text: "contactRequestState: " + contactDetails.contactRequestState - } - - Pane { - contentItem: RowLayout { - ComboBox { - id: pubKeySelector - model: [...ModelUtils.modelToFlatArray(myContactsModel, "pubKey"), "myPubKey", "none"] - ModelChangeTracker { - id: modelChangeTracker - model: myContactsModel - onRevisionChanged: { - pubKeySelector.model = [...ModelUtils.modelToFlatArray(myContactsModel, "pubKey"), "myPubKey", "none"] - } - } - } - } - } - } - } - - Pane { - SplitView.fillHeight: true - SplitView.preferredWidth: 500 - contentItem: UsersModelEditor { - id: myContactsModelEditor - model: myContactsModel - - onRemoveClicked: (index) => { - myContactsModel.remove(index, 1) - } - onRemoveAllClicked: () => { - myContactsModel.clear() - } - onAddClicked: () => { - myContactsModel.append(getNewUser(myContactsModel.count)) - } - } - } - - UsersModel { - id: myContactsModel - } - - ContactsStore { - id: contactsStoreMock - readonly property string myPublicKey: "0x123" - readonly property UsersModel contactsModel: myContactsModel - function requestContactInfo(pubKey) { - myContactsModel.append({ - pubKey: pubKey, - displayName: "displayName", - ensName: "ensName", - ensVerified: true, - localNickname: "localNickname", - alias: "alias", - icon: "icon", - colorId: 1, - colorHash: [], - onlineStatus: 1, - isContact: true, - isCurrentUser: false, - isVerified: true, - isUntrustworthy: false, - isBlocked: false, - contactRequestState: 3, - preferredDisplayName: "preferredDisplayName", - lastUpdated: 1234567890, - lastUpdatedLocally: 1234567890, - thumbnailImage: "thumbnailImage", - largeImage: "largeImage", - isContactRequestReceived: false, - isContactRequestSent: false, - removed: false, - trustStatus: 1, - bio: "bio" - }) - } - } - - ProfileStore { - id: profileStoreMock - readonly property string displayName: "myDisplayName" - readonly property string name: "myEnsName" - readonly property string username: "myUsername" - readonly property string icon: "myIcon" - readonly property int colorId: 1 - readonly property var colorHash: {} - readonly property int currentUserStatus: 1 - readonly property string preferredDisplayName: "myPreferredDisplayName" - readonly property string thumbnailImage: "myThumbnailImage" - readonly property string largeImage: "myLargeImage" - readonly property string bio: "myBio" - } - - ContactDetails { - id: contactDetails - contactsStore: contactsStoreMock - profileStore: profileStoreMock - publicKey: pubKeySelector.currentText === "myPubKey" ? "0x123" : pubKeySelector.currentText - } -} -// category: Contacts - -// Page is working in general but throwing multiple "Cannot read property" when changing id via combo box -// status: decent diff --git a/storybook/pages/ProfileDialogViewPage.qml b/storybook/pages/ProfileDialogViewPage.qml index 973b7faf08..77ae210f34 100644 --- a/storybook/pages/ProfileDialogViewPage.qml +++ b/storybook/pages/ProfileDialogViewPage.qml @@ -13,6 +13,7 @@ import StatusQ.Core.Utils 0.1 as StatusQUtils import AppLayouts.stores 1.0 as AppLayoutStores import AppLayouts.Profile.stores 1.0 as ProfileStores +import AppLayouts.Profile.helpers 1.0 import AppLayouts.Wallet.stores 1.0 import Storybook 1.0 @@ -22,23 +23,17 @@ SplitView { id: root property bool globalUtilsReady: false - property bool mainModuleReady: false // globalUtilsInst mock QtObject { - function getColorId(publicKey) { return colorId.value } - - function getColorHashAsJson(publicKey, skipEnsVerification=false) { - if (skipEnsVerification) - return - return JSON.stringify([{colorId: 0, segmentLength: 1}, - {colorId: 19, segmentLength: 2}]) - } - function addTimestampToURL(url) { return url } + function isCompressedPubKey() { + return false + } + Component.onCompleted: { Utils.globalUtilsInst = this root.globalUtilsReady = true @@ -50,45 +45,6 @@ SplitView { } } - // mainModuleInst mock - QtObject { - function isEnsVerified(publicKey) { - return ensVerified.checked - } - - function getContactDetailsAsJson(publicKey, getVerificationRequest=true, getOnlineStatus=false, includeDetails=false) { - return JSON.stringify({ displayName: displayName.text, - optionalName: "", - displayIcon: "", - publicKey: publicKey, - name: name.text, - ensVerified: ensVerified.checked, - alias: "Mock Alias Triplet", - lastUpdated: Date.now(), - lastUpdatedLocally: Date.now(), - localNickname: localNickname.text, - thumbnailImage: "", - largeImage: userImage.checked ? Theme.png("status-logo") : "", - isContact: ctrlIsContact.checked, - isBlocked: ctrlIsBlocked.checked, - isSyncing: false, - trustStatus: ctrlTrustStatus.currentValue, - verificationStatus: ctrlVerificationStatus.currentValue, - contactRequestState: ctrlContactRequestState.currentValue, - bio: bio.text, - onlineStatus: ctrlOnlineStatus.currentValue - }) - } - Component.onCompleted: { - Utils.mainModuleInst = this - root.mainModuleReady = true - } - Component.onDestruction: { - root.mainModuleReady = false - Utils.mainModuleInst = {} - } - } - ListModel { id: linksModel ListElement { @@ -147,144 +103,6 @@ SplitView { Logs { id: logs } - Popups { - popupParent: root - sharedRootStore: SharedStores.RootStore {} - rootStore: AppLayoutStores.RootStore { - property var contactStore: QtObject { - property var contactsModule: null - - function changeContactNickname(publicKey, newNickname, displayName, isEdit) { - logs.logEvent("rootStore::contactsStore::changeContactNickname", ["publicKey", "newNickname", "displayName", "isEdit"], arguments) - localNickname.text = newNickname - } - - function blockContact(publicKey) { - logs.logEvent("rootStore::contactStore::blockContact", ["publicKey"], arguments) - ctrlIsBlocked.checked = true - } - - function unblockContact(publicKey) { - logs.logEvent("rootStore::contactStore::unblockContact", ["publicKey"], arguments) - ctrlIsBlocked.checked = false - } - - function sendContactRequest(publicKey, message) { - logs.logEvent("rootStore::contactStore::sendContactRequest", ["publicKey", "message"], arguments) - ctrlContactRequestState.currentIndex = ctrlContactRequestState.indexOfValue(Constants.ContactRequestState.Sent) - } - - function acceptContactRequest(publicKey, contactRequestId) { - logs.logEvent("rootStore::contactStore::acceptContactRequest", ["publicKey, contactRequestId"], arguments) - ctrlContactRequestState.currentIndex = ctrlContactRequestState.indexOfValue(Constants.ContactRequestState.Mutual) - } - - function getLatestContactRequestForContactAsJson(pubKey) { - logs.logEvent("rootStore::contactStore::getLatestContactRequestForContactAsJson", ["pubKey"], arguments) - return { - id: "123456789", - from: pubKey, - clock: Date.now(), - text: "Hey Jo, it’s Alex here, we met at devcon last week!", - contactRequestState: Constants.ContactRequestState.Received - } - } - - function sendVerificationRequest(publicKey, challenge) { - logs.logEvent("rootStore::contactStore::sendVerificationRequest", ["publicKey", "challenge"], arguments) - ctrlVerificationStatus.currentIndex = ctrlVerificationStatus.indexOfValue(Constants.verificationStatus.verifying) - } - - function markUntrustworthy(publicKey) { - logs.logEvent("rootStore::contactStore::markUntrustworthy", ["publicKey"], arguments) - ctrlTrustStatus.currentIndex = ctrlTrustStatus.indexOfValue(Constants.trustStatus.untrustworthy) - ctrlVerificationStatus.currentIndex = ctrlVerificationStatus.indexOfValue(Constants.verificationStatus.unverified) - ctrlIncomingVerificationStatus.currentIndex = ctrlIncomingVerificationStatus.indexOfValue(Constants.verificationStatus.unverified) - } - - function markAsTrusted(publicKey) { - logs.logEvent("rootStore::contactStore::markAsTrusted", ["publicKey"], arguments) - ctrlTrustStatus.currentIndex = ctrlTrustStatus.indexOfValue(Constants.trustStatus.trusted) - ctrlVerificationStatus.currentIndex = ctrlVerificationStatus.indexOfValue(Constants.verificationStatus.trusted) - ctrlIncomingVerificationStatus.currentIndex = ctrlIncomingVerificationStatus.indexOfValue(Constants.verificationStatus.trusted) - } - - function removeContact(publicKey) { - logs.logEvent("rootStore::contactStore::removeContact", ["publicKey"], arguments) - ctrlContactRequestState.currentIndex = ctrlContactRequestState.indexOfValue(Constants.ContactRequestState.None) - ctrlIsContact.checked = false - } - - function verifiedTrusted(publicKey) { - logs.logEvent("rootStore::contactStore::verifiedTrusted", ["publicKey"], arguments) - ctrlTrustStatus.currentIndex = ctrlTrustStatus.indexOfValue(Constants.trustStatus.trusted) - ctrlVerificationStatus.currentIndex = ctrlVerificationStatus.indexOfValue(Constants.verificationStatus.trusted) - ctrlIncomingVerificationStatus.currentIndex = ctrlIncomingVerificationStatus.indexOfValue(Constants.verificationStatus.trusted) - } - - function removeTrustStatus(publicKey) { - logs.logEvent("rootStore::contactStore::removeTrustStatus", ["publicKey"], arguments) - ctrlTrustStatus.currentIndex = ctrlTrustStatus.indexOfValue(Constants.trustStatus.unknown) - ctrlVerificationStatus.currentIndex = ctrlVerificationStatus.indexOfValue(Constants.verificationStatus.unverified) - ctrlIncomingVerificationStatus.currentIndex = ctrlIncomingVerificationStatus.indexOfValue(Constants.verificationStatus.unverified) - } - - function removeTrustVerificationStatus(publicKey) { - logs.logEvent("rootStore::contactStore::removeTrustVerificationStatus", ["publicKey"], arguments) - ctrlTrustStatus.currentIndex = ctrlTrustStatus.indexOfValue(Constants.trustStatus.unknown) - ctrlVerificationStatus.currentIndex = ctrlVerificationStatus.indexOfValue(Constants.verificationStatus.unverified) - ctrlIncomingVerificationStatus.currentIndex = ctrlIncomingVerificationStatus.indexOfValue(Constants.verificationStatus.unverified) - } - - function cancelVerificationRequest(pubKey) { - logs.logEvent("rootStore::contactStore::cancelVerificationRequest", ["pubKey"], arguments) - ctrlVerificationStatus.currentIndex = ctrlVerificationStatus.indexOfValue(Constants.verificationStatus.unverified) - ctrlIncomingVerificationStatus.currentIndex = ctrlIncomingVerificationStatus.indexOfValue(Constants.verificationStatus.unverified) - } - - function declineVerificationRequest(pubKey) { - logs.logEvent("rootStore::contactStore::declineVerificationRequest", ["pubKey"], arguments) - ctrlVerificationStatus.currentIndex = ctrlVerificationStatus.indexOfValue(Constants.verificationStatus.unverified) - ctrlIncomingVerificationStatus.currentIndex = ctrlIncomingVerificationStatus.indexOfValue(Constants.verificationStatus.unverified) - } - - function acceptVerificationRequest(pubKey, response) { - logs.logEvent("rootStore::contactStore::acceptVerificationRequest", ["pubKey"], arguments) - ctrlVerificationStatus.currentIndex = ctrlVerificationStatus.indexOfValue(Constants.verificationStatus.verifying) - } - - function verifiedUntrustworthy(pubKey) { - logs.logEvent("rootStore::contactStore::verifiedUntrustworthy", ["pubKey"], arguments) - ctrlVerificationStatus.currentIndex = ctrlVerificationStatus.indexOfValue(Constants.verificationStatus.unverified) - ctrlIncomingVerificationStatus.currentIndex = ctrlIncomingVerificationStatus.indexOfValue(Constants.verificationStatus.unverified) - ctrlTrustStatus.currentIndex = ctrlTrustStatus.indexOfValue(Constants.trustStatus.untrustworthy) - } - - function getSentVerificationDetailsAsJson(pubKey) { - return { - requestStatus: ctrlVerificationStatus.currentValue, - challenge: "The real Alex would know this 100%! What’s my favourite colour?", - response: ctrlIncomingVerificationStatus.currentValue === Constants.verificationStatus.verified ? "Yellow!" : "", - displayName: ProfileUtils.displayName(localNickname.text, name.text, displayName.text), - icon: Theme.png("status-logo"), - requestedAt: Date.now() - 86400000, - repliedAt: Date.now() - } - } - - function getVerificationDetailsFromAsJson(pubKey) { - return { - from: "0xdeadbeef", - challenge: "The real Alex would know this 100%! What’s my favourite colour?", - response: "", - requestedAt: Date.now() - 86400000, - } - } - } - } - communityTokensStore: SharedStores.CommunityTokensStore {} - } - SplitView { orientation: Qt.Vertical SplitView.fillWidth: true @@ -299,15 +117,38 @@ SplitView { clip: true Loader { - active: root.globalUtilsReady && root.mainModuleReady + active: root.globalUtilsReady width: parent.availableWidth height: parent.availableHeight sourceComponent: ProfileDialogView { implicitWidth: 640 + contactDetails: ContactDetails { + publicKey: "0x0000x" + + displayName: displayNameTextField.text + localNickname: localNicknameTextField.text + ensVerified: ensVerifiedCheckBox.checked + ensName: ensNameTextField.text + name: ensNameTextField.text + + isCurrentUser: ownProfileSwitch.checked + + largeImage: userImageCheckBox.checked ? Theme.png("status-logo") : "" + + onlineStatus: onlineStatusComboBox.currentValue + + isBlocked: isBlockedCheckBox.checked + + colorHash: [{colorId: 0, segmentLength: 1}, + {colorId: 4, segmentLength: 2}] + + colorId: colorIdSpinBox.value + + } + readOnly: ctrlReadOnly.checked - publicKey: switchOwnProfile.checked ? "0xdeadbeef" : "0xrandomguy" onCloseRequested: logs.logEvent("closeRequested()") @@ -323,50 +164,38 @@ SplitView { collectiblesModel: CollectiblesModel {} profileStore: ProfileStores.ProfileStore { - readonly property string pubkey: "0xdeadbeef" - readonly property string ensName: name.text - function getQrCodeSource() { return "https://upload.wikimedia.org/wikipedia/commons/4/41/QR_Code_Example.svg" } } contactsStore: ProfileStores.ContactsStore { - readonly property string myPublicKey: "0xdeadbeef" - function joinPrivateChat(publicKey) { logs.logEvent("contactsStore::joinPrivateChat", ["publicKey"], arguments) } function markUntrustworthy(publicKey) { logs.logEvent("contactsStore::markUntrustworthy", ["publicKey"], arguments) - ctrlTrustStatus.currentIndex = ctrlTrustStatus.indexOfValue(Constants.trustStatus.untrustworthy) } function removeContact(publicKey) { logs.logEvent("contactsStore::removeContact", ["publicKey"], arguments) - ctrlContactRequestState.currentIndex = ctrlContactRequestState.indexOfValue(Constants.ContactRequestState.None) - ctrlIsContact.checked = false } function acceptContactRequest(publicKey, contactRequestId) { logs.logEvent("contactsStore::acceptContactRequest", ["publicKey, contactRequestId"], arguments) - ctrlContactRequestState.currentIndex = ctrlContactRequestState.indexOfValue(Constants.ContactRequestState.Mutual) } function dismissContactRequest(publicKey, contactRequestId) { logs.logEvent("contactsStore::dismissContactRequest", ["publicKey, contactRequestId"], arguments) - ctrlContactRequestState.currentIndex = ctrlContactRequestState.indexOfValue(Constants.ContactRequestState.Dismissed) } function removeTrustStatus(publicKey) { logs.logEvent("contactsStore::removeTrustStatus", ["publicKey"], arguments) - ctrlTrustStatus.currentIndex = ctrlTrustStatus.indexOfValue(Constants.trustStatus.unknown) } function removeTrustVerificationStatus(publicKey) { logs.logEvent("contactsStore::removeTrustVerificationStatus", ["publicKey"], arguments) - ctrlTrustStatus.currentIndex = ctrlTrustStatus.indexOfValue(Constants.trustStatus.unknown) } function verifiedUntrustworthy(publicKey) { @@ -380,8 +209,6 @@ SplitView { function cancelVerificationRequest(pubKey) { logs.logEvent("contactsStore::cancelVerificationRequest", ["pubKey"], arguments) - ctrlVerificationStatus.currentIndex = ctrlVerificationStatus.indexOfValue(Constants.verificationStatus.unverified) - ctrlIncomingVerificationStatus.currentIndex = ctrlIncomingVerificationStatus.indexOfValue(Constants.verificationStatus.unverified) } function getLinkToProfile(publicKey) { @@ -390,7 +217,6 @@ SplitView { function changeContactNickname(publicKey, newNickname, displayName, isEdit) { logs.logEvent("contactsStore::changeContactNickname", ["publicKey", "newNickname", "displayName", "isEdit"], arguments) - localNickname.text = newNickname } function requestProfileShowcase(publicKey) { @@ -431,48 +257,64 @@ SplitView { RowLayout { Layout.fillWidth: true Switch { - id: switchOwnProfile + id: ownProfileSwitch + text: "Own profile" checked: false } Switch { id: ctrlReadOnly + text: "Readonly (preview)" - visible: switchOwnProfile.checked + visible: ownProfileSwitch.checked checked: false } } RowLayout { Layout.fillWidth: true Label { text: "localNickname:" } + TextField { - id: localNickname + id: localNicknameTextField + text: "Alex" placeholderText: "Local Nickname" } - Label { text: "displayName:" } + + Label { + text: "displayName:" + } + TextField { - id: displayName + id: displayNameTextField + text: "Alex Pella" placeholderText: "Display Name" } + CheckBox { - id: ensVerified + id: ensVerifiedCheckBox + checked: true text: "ensVerified" } - Label { text: "name:" } + Label { + text: "name:" + } + TextField { - id: name - enabled: ensVerified.checked - text: ensVerified.checked ? "8⃣6⃣.eth" : "" + id: ensNameTextField + + enabled: ensVerifiedCheckBox.checked + text: ensVerifiedCheckBox.checked ? "8⃣6⃣.eth" : "" placeholderText: "ENS name" } } RowLayout { CheckBox { - id: userImage + id: userImageCheckBox + text: "User image" checked: true } @@ -481,18 +323,25 @@ SplitView { text: "or" } Label { - enabled: !userImage.checked + enabled: !userImageCheckBox.checked + text: "colorId" } SpinBox { - id: colorId - enabled: !userImage.checked + id: colorIdSpinBox + + enabled: !userImageCheckBox.checked from: 0 to: 11 // Theme.palette.userCustomizationColors.length } - Label { text: "onlineStatus" } + + Label { + text: "onlineStatus" + } + ComboBox { - id: ctrlOnlineStatus + id: onlineStatusComboBox + textRole: "text" valueRole: "value" model: [ @@ -504,15 +353,19 @@ SplitView { } RowLayout { Layout.fillWidth: true - enabled: !switchOwnProfile.checked + enabled: !ownProfileSwitch.checked + CheckBox { id: ctrlIsContact + enabled: true checked: ctrlContactRequestState.currentValue === Constants.ContactRequestState.Mutual text: "isContact" } + ComboBox { id: ctrlContactRequestState + textRole: "text" valueRole: "value" model: [ @@ -523,9 +376,14 @@ SplitView { { value: Constants.ContactRequestState.Dismissed, text: "Dismissed" } ] } - Label { text: "trustStatus:" } + + Label { + text: "trustStatus:" + } + ComboBox { id: ctrlTrustStatus + textRole: "text" valueRole: "value" model: [ @@ -534,8 +392,10 @@ SplitView { { value: Constants.trustStatus.untrustworthy, text: "untrustworthy" } ] } + CheckBox { - id: ctrlIsBlocked + id: isBlockedCheckBox + text: "isBlocked" } } @@ -615,3 +475,5 @@ Say hi, or find me on Twitter, GitHub, or Mastodon. // https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=724%3A15511&t=h8DUW6Eysawqe5u0-0 // https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=6%3A16845&t=h8DUW6Eysawqe5u0-0 // https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=4%3A25437&t=h8DUW6Eysawqe5u0-0 + +// status: decent diff --git a/storybook/qmlTests/tests/tst_AllContactsAdaptor.qml b/storybook/qmlTests/tests/tst_AllContactsAdaptor.qml new file mode 100644 index 0000000000..70171a2020 --- /dev/null +++ b/storybook/qmlTests/tests/tst_AllContactsAdaptor.qml @@ -0,0 +1,137 @@ +import QtQuick 2.15 +import QtTest 1.15 +import QtQml 2.15 + +import utils 1.0 + +import StatusQ.Core.Utils 0.1 +import mainui.adaptors 1.0 + +Item { + id: root + + Component { + id: testComponent + + AllContactsAdaptor { + selfPubKey: "0x0000x" + } + } + + Component { + id: contatsModelComponent + + ListModel { + ListElement { + pubKey: "0x0001x" + displayName: "displayName 1" + } + ListElement { + pubKey: "0x0002x" + displayName: "displayName 2" + } + } + } + + TestCase { + name: "AllContactsAdaptorTest" + + function test_selfEntry() { + const contactDetails = createTemporaryObject(testComponent, root) + const model = contactDetails.allContactsModel + + compare(model.rowCount(), 1) + compare(ModelUtils.get(model, 0).pubKey, "0x0000x") + + contactDetails.selfDisplayName = "Display name" + contactDetails.selfName = "@name" + contactDetails.selfPreferredDisplayName = "Preferred display name" + contactDetails.selfAlias = "Alias" + contactDetails.selfIcon = "Icon" + contactDetails.selfColorId = 42 + contactDetails.selfColorHash = "Color hash" + contactDetails.selfOnlineStatus = Constants.onlineStatus.online + contactDetails.selfThumbnailImage = "Thumbnail image" + contactDetails.selfLargeImage = "Large image" + contactDetails.selfBio = "Bio" + + compare(ModelUtils.get(model, 0).displayName, "Display name") + compare(ModelUtils.get(model, 0).alias, "Alias") + compare(ModelUtils.get(model, 0).bio, "Bio") + compare(ModelUtils.get(model, 0).colorHash, "Color hash") + compare(ModelUtils.get(model, 0).colorId, 42) + compare(ModelUtils.get(model, 0).contactRequestState, Constants.ContactRequestState.None) + compare(ModelUtils.get(model, 0).displayName, "Display name") + compare(ModelUtils.get(model, 0).ensName, "@name") + compare(ModelUtils.get(model, 0).isEnsVerified, true) + compare(ModelUtils.get(model, 0).icon, "Icon") + compare(ModelUtils.get(model, 0).isBlocked, false) + compare(ModelUtils.get(model, 0).isContact, false) + compare(ModelUtils.get(model, 0).isContactRequestReceived, false) + compare(ModelUtils.get(model, 0).isContactRequestSent, false) + compare(ModelUtils.get(model, 0).isCurrentUser, true) + compare(ModelUtils.get(model, 0).isUntrustworthy, false) + compare(ModelUtils.get(model, 0).isVerified, false) + compare(ModelUtils.get(model, 0).largeImage, "Large image") + compare(ModelUtils.get(model, 0).lastUpdated, 0) + compare(ModelUtils.get(model, 0).lastUpdatedLocally, 0) + compare(ModelUtils.get(model, 0).localNickname, "") + compare(ModelUtils.get(model, 0).onlineStatus, Constants.onlineStatus.online) + compare(ModelUtils.get(model, 0).preferredDisplayName, "Preferred display name") + compare(ModelUtils.get(model, 0).removed, false) + compare(ModelUtils.get(model, 0).thumbnailImage, "Thumbnail image") + compare(ModelUtils.get(model, 0).trustStatus, Constants.trustStatus.unknown) + } + + function test_accessToContacts() { + const contactsModel = createTemporaryObject(contatsModelComponent, root) + const contactDetails = createTemporaryObject(testComponent, root, + { contactsModel }) + const model = contactDetails.allContactsModel + + compare(model.rowCount(), 3) + + compare(ModelUtils.get(model, 0).pubKey, "0x0000x") + compare(ModelUtils.get(model, 1).pubKey, "0x0001x") + compare(ModelUtils.get(model, 2).pubKey, "0x0002x") + + compare(ModelUtils.get(model, 0).displayName, "") + compare(ModelUtils.get(model, 1).displayName, "displayName 1") + compare(ModelUtils.get(model, 2).displayName, "displayName 2") + } + + function test_roleNames() { + const contactDetails = createTemporaryObject(testComponent, root) + const model = contactDetails.allContactsModel + const roleNames = ModelUtils.roleNames(model) + + verify(roleNames.includes("pubKey")) + + verify(roleNames.includes("alias")) + verify(roleNames.includes("bio")) + verify(roleNames.includes("colorHash")) + verify(roleNames.includes("colorId")) + verify(roleNames.includes("contactRequestState")) + verify(roleNames.includes("displayName")) + verify(roleNames.includes("ensName")) + verify(roleNames.includes("isEnsVerified")) + verify(roleNames.includes("icon")) + verify(roleNames.includes("isBlocked")) + verify(roleNames.includes("isContact")) + verify(roleNames.includes("isContactRequestReceived")) + verify(roleNames.includes("isContactRequestSent")) + verify(roleNames.includes("isCurrentUser")) + verify(roleNames.includes("isUntrustworthy")) + verify(roleNames.includes("isVerified")) + verify(roleNames.includes("largeImage")) + verify(roleNames.includes("lastUpdated")) + verify(roleNames.includes("lastUpdatedLocally")) + verify(roleNames.includes("localNickname")) + verify(roleNames.includes("onlineStatus")) + verify(roleNames.includes("preferredDisplayName")) + verify(roleNames.includes("removed")) + verify(roleNames.includes("thumbnailImage")) + verify(roleNames.includes("trustStatus")) + } + } +} diff --git a/storybook/qmlTests/tests/tst_ContactDetails.qml b/storybook/qmlTests/tests/tst_ContactDetails.qml deleted file mode 100644 index ed8feb820b..0000000000 --- a/storybook/qmlTests/tests/tst_ContactDetails.qml +++ /dev/null @@ -1,320 +0,0 @@ -import QtQuick 2.15 -import QtTest 1.15 -import QtQml 2.15 - -import AppLayouts.Profile.helpers 1.0 -import AppLayouts.Profile.stores 1.0 - -Item { - id: root - - Component { - id: testComponent - ContactDetails { - id: contactDetails - } - } - - Component { - id: failingTestComponent - ContactDetails { - id: contactDetails - } - } - - Component { - id: contactsStore - ContactsStore { - readonly property string myPublicKey: "0x123" - readonly property ListModel contactsModel: ListModel { id: myContactsModel } - property var requestContactInfo: requestContactInfoCall - function requestContactInfoCall(pubKey) { - myContactsModel.append({ - pubKey: pubKey, - displayName: "displayName", - ensName: "ensName", - isEnsVerified: true, - localNickname: "localNickname", - alias: "alias", - icon: "icon", - colorId: 1, - colorHash: [], - onlineStatus: 1, - isContact: true, - isCurrentUser: false, - isVerified: true, - isUntrustworthy: false, - isBlocked: false, - contactRequest: 3, - preferredDisplayName: "preferredDisplayName", - lastUpdated: 1234567890, - lastUpdatedLocally: 1234567890, - thumbnailImage: "thumbnailImage", - largeImage: "largeImage", - isContactRequestReceived: false, - isContactRequestSent: false, - isRemoved: false, - trustStatus: 1, - bio: "bio" - }) - } - } - } - - Component { - id: profileStore - ProfileStore { - id: profileStoreMock - readonly property string displayName: "myDisplayName" - readonly property string name: "myEnsName" - readonly property string username: "myUsername" - readonly property string icon: "myIcon" - readonly property int colorId: 1 - readonly property var colorHash: {1} - readonly property int currentUserStatus: 1 - readonly property string preferredDisplayName: "myPreferredDisplayName" - readonly property string thumbnailImage: "myThumbnailImage" - readonly property string largeImage: "myLargeImage" - readonly property string bio: "myBio" - } - } - - TestCase { - name: "ContactDetailsTest" - function test_initialization() { - const contactDetails = createTemporaryObject(testComponent, root, { - contactsStore: createTemporaryObject(contactsStore, root), - profileStore: createTemporaryObject(profileStore, root), - publicKey: "" - }) - - verify(!!contactDetails, "Expected the contact details to initialize") - } - - function test_initializationOwnProfile() { - const contactDetails = createTemporaryObject(testComponent, root, { - contactsStore: createTemporaryObject(contactsStore, root), - profileStore: createTemporaryObject(profileStore, root), - publicKey: "0x123" - }) - - compare(contactDetails.loading, false, "Expected the loading flag to be false") - compare(contactDetails.publicKey,"0x123", "Expected the public key to be set") - compare(contactDetails.contactsStore.myPublicKey,"0x123", "Expected the contacts store to be set") - compare(contactDetails.profileStore.displayName,"myDisplayName", "Expected the profile store to be set") - compare(contactDetails.displayName, contactDetails.profileStore.displayName, "Expected the display name to be set") - compare(contactDetails.ensName, contactDetails.profileStore.name, "Expected the ens name to be set") - compare(contactDetails.ensVerified, false, "Expected the ensVerified to be set") - compare(contactDetails.localNickname, "", "Expected the local nickname to be empty") - compare(contactDetails.alias, contactDetails.profileStore.username, "Expected the alias to be set") - compare(contactDetails.icon, contactDetails.profileStore.icon, "Expected the icon to be set") - compare(contactDetails.colorId, contactDetails.profileStore.colorId, "Expected the color id to be set") - compare(contactDetails.colorHash, contactDetails.profileStore.colorHash, "Expected the color hash to be empty") - compare(contactDetails.onlineStatus, contactDetails.profileStore.currentUserStatus, "Expected the online status to be set") - compare(contactDetails.thumbnailImage, contactDetails.profileStore.thumbnailImage, "Expected the is contact flag to be set") - compare(contactDetails.largeImage, contactDetails.profileStore.largeImage, "Expected the is contact flag to be set") - compare(contactDetails.bio, contactDetails.profileStore.bio, "Expected the is contact flag to be set") - compare(contactDetails.isContact, false, "Expected the is contact flag to be set") - compare(contactDetails.isCurrentUser, true, "Expected the is contact flag to be set") - } - - function test_initializationWithContact() { - const contactsStoreMock = createTemporaryObject(contactsStore, root) - contactsStoreMock.requestContactInfo("0x321") //appending new contact to the model - - const contactDetails = createTemporaryObject(testComponent, root, { - contactsStore: contactsStoreMock, - profileStore: createTemporaryObject(profileStore, root), - publicKey: "0x321" - }) - - compare(contactDetails.loading, false, "Expected the loading flag to be false") - compare(contactDetails.publicKey,"0x321", "Expected the public key to be set") - compare(contactDetails.displayName, "displayName", "Expected the display name to be set") - compare(contactDetails.ensName, "ensName", "Expected the ens name to be set") - compare(contactDetails.ensVerified, true, "Expected the ensVerified to be set") - compare(contactDetails.localNickname, "localNickname", "Expected the local nickname to be set") - compare(contactDetails.alias, "alias", "Expected the alias to be set") - compare(contactDetails.icon, "icon", "Expected the icon to be set") - compare(contactDetails.colorId, 1, "Expected the color id to be set") - compare(contactDetails.onlineStatus, 1, "Expected the online status to be set") - compare(contactDetails.thumbnailImage, "thumbnailImage", "Expected the thumbnailImage to be set") - compare(contactDetails.largeImage, "largeImage", "Expected the largeImage to be set") - compare(contactDetails.bio, "bio", "Expected the bio to be set") - compare(contactDetails.isContact, true, "Expected the is contact flag to be set") - compare(contactDetails.isCurrentUser, false, "Expected the isCurrentUser flag to be set") - compare(contactDetails.isVerified, true, "Expected the isVerified flag to be set") - compare(contactDetails.isUntrustworthy, false, "Expected the isUntrustworthy flag to be set") - compare(contactDetails.isBlocked, false, "Expected the isBlocked flag to be set") - compare(contactDetails.contactRequestState, 3, "Expected the contactRequestState flag to be set") - compare(contactDetails.preferredDisplayName, "preferredDisplayName", "Expected the preferredDisplayName to be set") - compare(contactDetails.lastUpdated, 1234567890, "Expected the lastUpdated to be set") - compare(contactDetails.lastUpdatedLocally, 1234567890, "Expected the lastUpdatedLocally to be set") - compare(contactDetails.isContactRequestReceived, false, "Expected the isContactRequestReceived flag to be set") - compare(contactDetails.isContactRequestSent, false, "Expected the isContactRequestSent flag to be set") - compare(contactDetails.removed, false, "Expected the removed flag to be set") - compare(contactDetails.trustStatus, 1, "Expected the trustStatus flag to be set") - } - - function test_initFails() { - ignoreWarning(new RegExp("Required property publicKey was not initialized")) - ignoreWarning(new RegExp("Required property contactsStore was not initialized")) - ignoreWarning(new RegExp("Required property profileStore was not initialized")) - - const contactDetails = createTemporaryObject(failingTestComponent, root) - verify(!contactDetails, "Expected the contact details to fail to initialize") - } - - function test_initWithEmptyContacts() { - const contactsStoreMock = createTemporaryObject(contactsStore, root) - let requestContactInfoCallCount = 0 - contactsStoreMock.requestContactInfo = function(pubKey) { - requestContactInfoCallCount++ - } - const contactDetails = createTemporaryObject(testComponent, root, { - contactsStore: contactsStoreMock, - profileStore: createTemporaryObject(profileStore, root), - publicKey: "0x1234" - }) - - compare(requestContactInfoCallCount, 1, "Expected the requestContactInfo to be called") - compare(contactDetails.loading, true, "Expected the loading flag to be true") - compare(contactDetails.publicKey,"0x1234", "Expected the public key to be set") - - //add the contact - contactsStoreMock.requestContactInfo = contactsStoreMock.requestContactInfoCall - contactsStoreMock.requestContactInfo("0x1234") - - compare(contactDetails.loading, false, "Expected the loading flag to be false") - compare(contactDetails.publicKey,"0x1234", "Expected the public key to be set") - compare(contactDetails.displayName, "displayName", "Expected the display name to be set") - compare(contactDetails.ensName, "ensName", "Expected the ens name to be set") - compare(contactDetails.ensVerified, true, "Expected the ensVerified to be set") - } - - function test_contactRemovedFromModel() { - const contactsStoreMock = createTemporaryObject(contactsStore, root) - contactsStoreMock.requestContactInfo("0x1234") //appending new contact to the model - - const contactDetails = createTemporaryObject(testComponent, root, { - contactsStore: contactsStoreMock, - profileStore: createTemporaryObject(profileStore, root), - publicKey: "0x1234" - }) - - compare(contactDetails.loading, false, "Expected the loading flag to be false") - compare(contactDetails.publicKey,"0x1234", "Expected the public key to be set") - compare(contactDetails.displayName, "displayName", "Expected the display name to be set") - compare(contactDetails.ensName, "ensName", "Expected the ens name to be set") - compare(contactDetails.ensVerified, true, "Expected the ensVerified to be set") - - // removing from model should not clear the contact details - contactsStoreMock.contactsModel.remove(0) - - compare(contactDetails.loading, false, "Expected the loading flag to be true") - compare(contactDetails.publicKey,"0x1234", "Expected the public key to be set") - compare(contactDetails.displayName, "displayName", "Expected the display name to be empty") - compare(contactDetails.ensName, "ensName", "Expected the ens name to be empty") - compare(contactDetails.ensVerified, true, "Expected the ensVerified to be false") - } - - function test_liveUpdate() { - const contactsStoreMock = createTemporaryObject(contactsStore, root) - contactsStoreMock.requestContactInfo("0x1234") //appending new contact to the model - - const contactDetails = createTemporaryObject(testComponent, root, { - contactsStore: contactsStoreMock, - profileStore: createTemporaryObject(profileStore, root), - publicKey: "0x1234" - }) - - compare(contactDetails.loading, false, "Expected the loading flag to be false") - compare(contactDetails.publicKey,"0x1234", "Expected the public key to be set") - compare(contactDetails.displayName, "displayName", "Expected the display name to be set") - compare(contactDetails.ensName, "ensName", "Expected the ens name to be set") - compare(contactDetails.ensVerified, true, "Expected the ensVerified to be set") - - // updating the contact should update the contact details - contactsStoreMock.contactsModel.set(0, { - pubKey: "0x1234", - displayName: "newDisplayName", - ensName: "newEnsName", - isEnsVerified: false, - localNickname: "newLocalNickname", - alias: "newAlias", - icon: "newIcon", - colorId: 2, - colorHash: [], - onlineStatus: 2, - isContact: false, - isCurrentUser: true, - isVerified: false, - isUntrustworthy: true, - isBlocked: true, - contactRequest: 2, - preferredDisplayName: "newPreferredDisplayName", - lastUpdated: 1234567891, - lastUpdatedLocally: 1234567891, - thumbnailImage: "newThumbnailImage", - largeImage: "newLargeImage", - isContactRequestReceived: true, - isContactRequestSent: true, - isRemoved: true, - trustStatus: 2, - bio: "newBio" - }) - - compare(contactDetails.loading, false, "Expected the loading flag to be false") - compare(contactDetails.publicKey,"0x1234", "Expected the public key to be set") - compare(contactDetails.displayName, "newDisplayName", "Expected the display name to be set") - compare(contactDetails.ensName, "newEnsName", "Expected the ens name to be set") - compare(contactDetails.ensVerified, false, "Expected the ensVerified to be set") - compare(contactDetails.localNickname, "newLocalNickname", "Expected the local nickname to be set") - compare(contactDetails.alias, "newAlias", "Expected the alias to be set") - compare(contactDetails.icon, "newIcon", "Expected the icon to be set") - compare(contactDetails.colorId, 2, "Expected the color id to be set") - compare(contactDetails.onlineStatus, 2, "Expected the online status to be set") - compare(contactDetails.thumbnailImage, "newThumbnailImage", "Expected the thumbnailImage to be set") - compare(contactDetails.largeImage, "newLargeImage", "Expected the largeImage to be set") - compare(contactDetails.bio, "newBio", "Expected the bio to be set") - compare(contactDetails.isContact, false, "Expected the is contact flag to be set") - compare(contactDetails.isCurrentUser, true, "Expected the isCurrentUser flag to be set") - compare(contactDetails.isVerified, false, "Expected the isVerified flag to be set") - compare(contactDetails.isUntrustworthy, true, "Expected the isUntrustworthy flag to be set") - compare(contactDetails.isBlocked, true, "Expected the isBlocked flag to be set") - compare(contactDetails.contactRequestState, 2, "Expected the contactRequestState flag to be set") - compare(contactDetails.preferredDisplayName, "newPreferredDisplayName", "Expected the preferredDisplayName to be set") - compare(contactDetails.lastUpdated, 1234567891, "Expected the lastUpdated to be set") - compare(contactDetails.lastUpdatedLocally, 1234567891, "Expected the lastUpdatedLocally to be set") - compare(contactDetails.isContactRequestReceived, true, "Expected the isContactRequestReceived flag to be set") - compare(contactDetails.isContactRequestSent, true, "Expected the isContactRequestSent flag to be set") - compare(contactDetails.removed, true, "Expected the removed flag to be set") - compare(contactDetails.trustStatus, 2, "Expected the trustStatus flag to be set") - } - - function test_changingPublicKeyFromOwnToContact() { - const contactsStoreMock = createTemporaryObject(contactsStore, root) - const contactDetails = createTemporaryObject(testComponent, root, { - contactsStore: contactsStoreMock, - profileStore: createTemporaryObject(profileStore, root), - publicKey: "0x123" - }) - - compare(contactDetails.loading, false, "Expected the loading flag to be false") - compare(contactDetails.publicKey,"0x123", "Expected the public key to be set") - compare(contactDetails.contactsStore.myPublicKey,"0x123", "Expected the contacts store to be set") - compare(contactDetails.profileStore.displayName,"myDisplayName", "Expected the profile store to be set") - compare(contactDetails.displayName, contactDetails.profileStore.displayName, "Expected the display name to be set") - compare(contactDetails.ensName, contactDetails.profileStore.name, "Expected the ens name to be set") - - contactDetails.publicKey = "0x321" - - compare(contactDetails.loading, false, "Expected the loading flag to be false") - compare(contactDetails.publicKey,"0x321", "Expected the public key to be set") - compare(contactDetails.displayName, "displayName", "Expected the display name to be set") - compare(contactDetails.ensName, "ensName", "Expected the ens name to be set") - compare(contactDetails.ensVerified, true, "Expected the ensVerified to be set") - compare(contactDetails.localNickname, "localNickname", "Expected the local nickname to be set") - } - } -} diff --git a/ui/app/AppLayouts/Profile/helpers/ContactDetails.qml b/ui/app/AppLayouts/Profile/helpers/ContactDetails.qml index afe4cde82c..163b2237b4 100644 --- a/ui/app/AppLayouts/Profile/helpers/ContactDetails.qml +++ b/ui/app/AppLayouts/Profile/helpers/ContactDetails.qml @@ -1,103 +1,34 @@ -import QtQuick 2.15 +import QtQml 2.15 -import StatusQ 0.1 -import StatusQ.Core.Utils 0.1 - -import AppLayouts.Profile.stores 1.0 - -import utils 1.0 - -QObject { - id: root - - required property ContactsStore contactsStore - required property ProfileStore profileStore +QtObject { required property string publicKey - - readonly property alias loading: d.loading - - // model properties - readonly property string displayName: d.contactDetails.displayName ?? "" - readonly property string ensName: d.contactDetails.ensName ?? "" - readonly property bool ensVerified: d.contactDetails.isEnsVerified ?? false - readonly property string localNickname: d.contactDetails.localNickname ?? "" - readonly property string alias: d.contactDetails.alias ?? "" - readonly property string icon: d.contactDetails.icon ?? "" - readonly property int colorId: d.contactDetails.colorId ?? 0 - readonly property var colorHash: d.contactDetails.colorHash ?? [] - readonly property int onlineStatus: d.contactDetails.onlineStatus ?? Constants.onlineStatus.inactive - readonly property bool isContact: d.contactDetails.isContact ?? false - readonly property bool isCurrentUser: d.contactDetails.isCurrentUser ?? false - readonly property bool isVerified: d.contactDetails.isVerified ?? false - readonly property bool isUntrustworthy: d.contactDetails.isUntrustworthy ?? false - readonly property bool isBlocked: d.contactDetails.isBlocked ?? false - readonly property int contactRequestState: d.contactDetails.contactRequest ?? Constants.ContactRequestState.None - readonly property string preferredDisplayName: d.contactDetails.preferredDisplayName ?? "" - readonly property int lastUpdated: d.contactDetails.lastUpdated ?? 0 - readonly property int lastUpdatedLocally: d.contactDetails.lastUpdatedLocally ?? 0 - readonly property string thumbnailImage: d.contactDetails.thumbnailImage ?? "" - readonly property string largeImage: d.contactDetails.largeImage ?? "" - readonly property bool isContactRequestReceived: d.contactDetails.isContactRequestReceived ?? false - readonly property bool isContactRequestSent: d.contactDetails.isContactRequestSent ?? false - readonly property bool removed: d.contactDetails.isRemoved ?? false - readonly property int trustStatus: d.contactDetails.trustStatus ?? Constants.trustStatus.unknown - readonly property string bio: d.contactDetails.bio ?? "" + property string displayName + property string ensName + property bool ensVerified + property string localNickname + property string alias + property string icon + property int colorId + property var colorHash + property int onlineStatus + property bool isContact + property bool isCurrentUser + property bool isVerified + property bool isUntrustworthy + property bool isBlocked + property int contactRequestState + property string preferredDisplayName + property int lastUpdated + property int lastUpdatedLocally + property string thumbnailImage + property string largeImage + property bool isContactRequestReceived + property bool isContactRequestSent + property bool removed + property int trustStatus + property string bio // Backwards compatibility properties - Don't use in new code // TODO: #14965 - Try to remove these properties - readonly property string name: ensName - - // Extra properties provided by getContactDetailsAsJson, not available in the model - // TODO: #14964 - Review all the model rolenames and fill the rest of the properties with data from the model - //readonly property var socialLinks: d.contactDetails.socialLinks ?? [] - - ModelEntry { - id: itemData - sourceModel: root.publicKey !== "" && !d.isMe ? contactsStore.contactsModel : null - key: "pubKey" - value: root.publicKey - cacheOnRemoval: true - } - - QObject { - id: d - readonly property bool loading: !itemData.available && !isMe - onLoadingChanged: { - if (loading) { - contactsStore.requestContactInfo(root.publicKey) - } - } - - readonly property bool isMe: root.contactsStore.myPublicKey === root.publicKey - readonly property var ownProfile: QObject { - readonly property string displayName: root.profileStore.displayName - readonly property string ensName: root.profileStore.name - readonly property bool isEnsVerified: root.profileStore.name !== "" && Utils.isValidEns(root.profileStore.name) - readonly property string localNickname: "" - readonly property string preferredDisplayName: root.profileStore.preferredDisplayName - readonly property string name: preferredDisplayName - readonly property string alias: root.profileStore.username - readonly property string icon: root.profileStore.icon - readonly property int colorId: root.profileStore.colorId - readonly property var colorHash: root.profileStore.colorHash - readonly property int onlineStatus: root.profileStore.currentUserStatus - readonly property bool isContact: false - readonly property bool isCurrentUser: true - readonly property bool isVerified: false - readonly property bool isUntrustworthy: false - readonly property bool isBlocked: false - readonly property int contactRequestState: Constants.ContactRequestState.None - readonly property int lastUpdated: 0 - readonly property int lastUpdatedLocally: 0 - readonly property string thumbnailImage: root.profileStore.thumbnailImage - readonly property string largeImage: root.profileStore.largeImage - readonly property bool isContactRequestReceived: Constants.ContactRequestState.None - readonly property bool isContactRequestSent: Constants.ContactRequestState.None - readonly property bool removed: false - readonly property int trustStatus: Constants.trustStatus.unknown - readonly property string bio: root.profileStore.bio - } - - readonly property var contactDetails: !isMe ? itemData.item : ownProfile - } + property string name//: ensName } diff --git a/ui/app/AppLayouts/Profile/helpers/ContactModelEntry.qml b/ui/app/AppLayouts/Profile/helpers/ContactModelEntry.qml new file mode 100644 index 0000000000..5f86fab148 --- /dev/null +++ b/ui/app/AppLayouts/Profile/helpers/ContactModelEntry.qml @@ -0,0 +1,57 @@ +import StatusQ 0.1 +import StatusQ.Core.Utils 0.1 + +import utils 1.0 + +/** + * Wrapper over generic ModelEntry to expose entries from model of contacts. + */ +QObject { + id: root + + required property string publicKey + required property var contactsModel + + readonly property ContactDetails contactDetails: ContactDetails { + readonly property var entry: itemData.item + + publicKey: root.publicKey + displayName: entry.displayName ?? "" + ensName: entry.ensName ?? "" + ensVerified: entry.isEnsVerified ?? false + localNickname: entry.localNickname ?? "" + alias: entry.alias ?? "" + icon: entry.icon ?? "" + colorId: entry.colorId ?? 0 + colorHash: entry.colorHash ?? [] + onlineStatus: entry.onlineStatus ?? Constants.onlineStatus.inactive + isContact: entry.isContact ?? false + isCurrentUser: entry.isCurrentUser ?? false + isVerified: entry.isVerified ?? false + isUntrustworthy: entry.isUntrustworthy ?? false + isBlocked: entry.isBlocked ?? false + contactRequestState: entry.contactRequest ?? Constants.ContactRequestState.None + preferredDisplayName: entry.preferredDisplayName ?? "" + lastUpdated: entry.lastUpdated ?? 0 + lastUpdatedLocally: entry.lastUpdatedLocally ?? 0 + thumbnailImage: entry.thumbnailImage ?? "" + largeImage: entry.largeImage ?? "" + isContactRequestReceived: entry.isContactRequestReceived ?? false + isContactRequestSent: entry.isContactRequestSent ?? false + removed: entry.isRemoved ?? false + trustStatus: entry.trustStatus ?? Constants.trustStatus.unknown + bio: entry.bio ?? "" + + // Backwards compatibility properties - Don't use in new code + // TODO: #14965 - Try to remove these properties + name: ensName + } + + ModelEntry { + id: itemData + sourceModel: root.contactsModel + key: "pubKey" + value: root.publicKey + cacheOnRemoval: true + } +} diff --git a/ui/app/AppLayouts/Profile/helpers/qmldir b/ui/app/AppLayouts/Profile/helpers/qmldir index 97487427bd..fbf545dd4d 100644 --- a/ui/app/AppLayouts/Profile/helpers/qmldir +++ b/ui/app/AppLayouts/Profile/helpers/qmldir @@ -1,4 +1,5 @@ ContactDetails 1.0 ContactDetails.qml +ContactModelEntry 1.0 ContactModelEntry.qml ProfileShowcaseDirtyState 1.0 ProfileShowcaseDirtyState.qml ProfileShowcaseModelAdapter 1.0 ProfileShowcaseModelAdapter.qml ProfileShowcaseModels 1.0 ProfileShowcaseModels.qml diff --git a/ui/app/AppLayouts/Profile/views/MyProfileView.qml b/ui/app/AppLayouts/Profile/views/MyProfileView.qml index b6fe94c080..5ead589107 100644 --- a/ui/app/AppLayouts/Profile/views/MyProfileView.qml +++ b/ui/app/AppLayouts/Profile/views/MyProfileView.qml @@ -49,12 +49,6 @@ SettingsContentBase { property bool toastClashesWithDirtyBubble readonly property alias sideBySidePreviewComponent: myProfilePreviewComponent - readonly property QtObject liveValues: QtObject { - readonly property string displayName: descriptionPanel.displayName.text - readonly property string bio: descriptionPanel.bio.text - readonly property url profileLargeImage: profileHeader.previewIcon - } - enum TabIndex { Identity = 0, Communities = 1, @@ -133,6 +127,17 @@ SettingsContentBase { property QObject priv: QObject { id: priv + readonly property ContactDetails liveContactDetails: ContactDetails { + publicKey: root.profileStore.pubkey + colorId: root.profileStore.colorId + colorHash: root.profileStore.colorHash + onlineStatus: root.profileStore.currentUserStatus + + displayName: descriptionPanel.displayName.text + bio: descriptionPanel.bio.text + largeImage: profileHeader.previewIcon + } + readonly property bool hasAnyProfileShowcaseChanges: showcaseModels.dirty readonly property bool isIdentityTabDirty: (!descriptionPanel.isEnsName && descriptionPanel.displayName.text !== profileStore.displayName) || @@ -403,16 +408,16 @@ SettingsContentBase { Component { id: profilePreview + ProfileDialog { - publicKey: root.contactsStore.myPublicKey + contactDetails: priv.liveContactDetails + profileStore: root.profileStore contactsStore: root.contactsStore walletStore: WalletStores.RootStore utilsStore: root.utilsStore sendToAccountEnabled: root.sendToAccountEnabled onClosed: destroy() - dirtyValues: root.liveValues - dirty: root.dirty showcaseCommunitiesModel: priv.showcaseModels.communitiesVisibleModel showcaseAccountsModel: priv.showcaseModels.accountsVisibleModel @@ -427,13 +432,14 @@ SettingsContentBase { Component { id: myProfilePreviewComponent + MyProfilePreview { + contactDetails: priv.liveContactDetails + profileStore: root.profileStore contactsStore: root.contactsStore utilsStore: root.utilsStore sendToAccountEnabled: root.sendToAccountEnabled - dirtyValues: root.liveValues - dirty: root.dirty showcaseCommunitiesModel: priv.showcaseModels.communitiesVisibleModel showcaseAccountsModel: priv.showcaseModels.accountsVisibleModel diff --git a/ui/app/AppLayouts/Profile/views/profile/MyProfilePreview.qml b/ui/app/AppLayouts/Profile/views/profile/MyProfilePreview.qml index 5b0426af59..066c09a85f 100644 --- a/ui/app/AppLayouts/Profile/views/profile/MyProfilePreview.qml +++ b/ui/app/AppLayouts/Profile/views/profile/MyProfilePreview.qml @@ -9,13 +9,13 @@ import shared.controls 1.0 import shared.views 1.0 as SharedViews Item { + property alias contactDetails: profilePreview.contactDetails + property alias profileStore: profilePreview.profileStore property alias contactsStore: profilePreview.contactsStore property alias utilsStore: profilePreview.utilsStore property alias sendToAccountEnabled: profilePreview.sendToAccountEnabled - property alias dirtyValues: profilePreview.dirtyValues - property alias dirty: profilePreview.dirty property alias showcaseCommunitiesModel: profilePreview.showcaseCommunitiesModel property alias showcaseAccountsModel: profilePreview.showcaseAccountsModel diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index 0cd874d691..c278dcc8da 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -105,6 +105,25 @@ Item { // set from main.qml property var sysPalette + AllContactsAdaptor { + id: allContacsAdaptor + + contactsModel: appMain.rootStore.contactStore.contactsModel + + selfPubKey: appMain.profileStore.pubkey + selfDisplayName : appMain.profileStore.displayName + selfName: appMain.profileStore.name + selfPreferredDisplayName: appMain.profileStore.preferredName + selfAlias: appMain.profileStore.username + selfIcon: appMain.profileStore.icon + selfColorId: appMain.profileStore.colorId + selfColorHash: appMain.profileStore.colorHash + selfOnlineStatus: appMain.profileStore.currentUserStatus + selfThumbnailImage: appMain.profileStore.thumbnailImage + selfLargeImage: appMain.profileStore.largeImage + selfBio: appMain.profileStore.bio + } + ContactsModelAdaptor { id: contactsModelAdaptor @@ -601,6 +620,7 @@ Item { buyCryptoStore: appMain.buyCryptoStore networkConnectionStore: appMain.networkConnectionStore + allContactsModel: allContacsAdaptor.allContactsModel mutualContactsModel: contactsModelAdaptor.mutualContacts isDevBuild: !production diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index 1a2088fe8c..03761c786d 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -23,6 +23,7 @@ import AppLayouts.Wallet.popups.swap 1.0 import AppLayouts.Wallet.popups.buy 1.0 import AppLayouts.Wallet.popups 1.0 import AppLayouts.Communities.stores 1.0 +import AppLayouts.Profile.helpers 1.0 import AppLayouts.Wallet.stores 1.0 as WalletStores import AppLayouts.Chat.stores 1.0 as ChatStores @@ -53,6 +54,7 @@ QtObject { property NetworkConnectionStore networkConnectionStore property WalletStores.BuyCryptoStore buyCryptoStore + property var allContactsModel property var mutualContactsModel property bool isDevBuild @@ -548,7 +550,16 @@ QtObject { ProfileDialog { id: profilePopup - property bool isCurrentUser: publicKey === rootStore.profileSectionStore.profileStore.pubkey + property alias publicKey: contactModelEntry.publicKey + readonly property bool isCurrentUser: contactDetails.isCurrentUser + + ContactModelEntry { + id: contactModelEntry + + contactsModel: root.allContactsModel + } + + contactDetails: contactModelEntry.contactDetails profileStore: rootStore.profileSectionStore.profileStore contactsStore: rootStore.profileSectionStore.contactsStore @@ -557,10 +568,14 @@ QtObject { sendToAccountEnabled: root.networkConnectionStore.sendBuyBridgeEnabled - showcaseCommunitiesModel: isCurrentUser ? rootStore.profileSectionStore.ownShowcaseCommunitiesModel : rootStore.profileSectionStore.contactShowcaseCommunitiesModel - showcaseAccountsModel: isCurrentUser ? rootStore.profileSectionStore.ownShowcaseAccountsModel : rootStore.profileSectionStore.contactShowcaseAccountsModel - showcaseCollectiblesModel: isCurrentUser ? rootStore.profileSectionStore.ownShowcaseCollectiblesModel : rootStore.profileSectionStore.contactShowcaseCollectiblesModel - showcaseSocialLinksModel: isCurrentUser ? rootStore.profileSectionStore.ownShowcaseSocialLinksModel : rootStore.profileSectionStore.contactShowcaseSocialLinksModel + showcaseCommunitiesModel: isCurrentUser ? rootStore.profileSectionStore.ownShowcaseCommunitiesModel + : rootStore.profileSectionStore.contactShowcaseCommunitiesModel + showcaseAccountsModel: isCurrentUser ? rootStore.profileSectionStore.ownShowcaseAccountsModel + : rootStore.profileSectionStore.contactShowcaseAccountsModel + showcaseCollectiblesModel: isCurrentUser ? rootStore.profileSectionStore.ownShowcaseCollectiblesModel + : rootStore.profileSectionStore.contactShowcaseCollectiblesModel + showcaseSocialLinksModel: isCurrentUser ? rootStore.profileSectionStore.ownShowcaseSocialLinksModel + : rootStore.profileSectionStore.contactShowcaseSocialLinksModel assetsModel: rootStore.globalAssetsModel collectiblesModel: rootStore.globalCollectiblesModel diff --git a/ui/app/mainui/adaptors/AllContactsAdaptor.qml b/ui/app/mainui/adaptors/AllContactsAdaptor.qml new file mode 100644 index 0000000000..ae950d43fb --- /dev/null +++ b/ui/app/mainui/adaptors/AllContactsAdaptor.qml @@ -0,0 +1,97 @@ +import QtQuick 2.15 + +import StatusQ 0.1 +import StatusQ.Core.Utils 0.1 +import utils 1.0 + +import SortFilterProxyModel 0.2 + +/** + * Adaptor concatenating model of contacts with own profile details into single + model in order to use it as a complete source of profile info, with no + distinction between own and contat's profiles. + */ +QObject { + id: root + + /* Model with details (not including self) */ + property alias contactsModel: mainSource.model + + /* Self-profile details */ + property string selfPubKey + property string selfDisplayName + property string selfName + property string selfPreferredDisplayName + property string selfAlias + property string selfIcon + property int selfColorId + property var selfColorHash + property int selfOnlineStatus + property string selfThumbnailImage + property string selfLargeImage + property string selfBio + + readonly property ConcatModel allContactsModel: ConcatModel { + id: concatModel + + expectedRoles: [ + "pubKey", "displayName", "ensName", "isEnsVerified", "localNickname", + "alias", "icon", "colorId", "colorHash", "onlineStatus", + "isContact", "isCurrentUser", "isVerified", "isUntrustworthy", + "isBlocked", "contactRequestState", "preferredDisplayName", + "lastUpdated", "lastUpdatedLocally", "thumbnailImage", "largeImage", + "isContactRequestReceived", "isContactRequestSent", "removed", + "trustStatus", "bio" + ] + + markerRoleName: "" + + sources: [ + SourceModel { + model: ObjectProxyModel { + sourceModel: ListModel { + ListElement { + _: "" // empty role to prevent warning + } + } + + delegate: QtObject { + readonly property string pubKey: root.selfPubKey + readonly property string displayName: root.selfDisplayName + readonly property string ensName: root.selfName + readonly property bool isEnsVerified: root.selfName !== "" + && Utils.isValidEns(root.selfName) + readonly property string localNickname: "" + readonly property string preferredDisplayName: root.selfPreferredDisplayName + readonly property string name: preferredDisplayName + readonly property string alias: root.selfAlias + readonly property string icon: root.selfIcon + readonly property int colorId: root.selfColorId + readonly property var colorHash: root.selfColorHash + readonly property int onlineStatus: root.selfOnlineStatus + readonly property bool isContact: false + readonly property bool isCurrentUser: true + readonly property bool isVerified: false + readonly property bool isUntrustworthy: false + readonly property bool isBlocked: false + readonly property int contactRequestState: Constants.ContactRequestState.None + readonly property int lastUpdated: 0 + readonly property int lastUpdatedLocally: 0 + readonly property string thumbnailImage: root.selfThumbnailImage + readonly property string largeImage: root.selfLargeImage + readonly property bool isContactRequestReceived: Constants.ContactRequestState.None + readonly property bool isContactRequestSent: Constants.ContactRequestState.None + readonly property bool removed: false + readonly property int trustStatus: Constants.trustStatus.unknown + readonly property string bio: root.selfBio + } + + exposedRoles: concatModel.expectedRoles + } + }, + SourceModel { + id: mainSource + } + ] + } +} diff --git a/ui/app/mainui/adaptors/qmldir b/ui/app/mainui/adaptors/qmldir index 11680eeb47..78fb51feab 100644 --- a/ui/app/mainui/adaptors/qmldir +++ b/ui/app/mainui/adaptors/qmldir @@ -1 +1,2 @@ +AllContactsAdaptor 1.0 AllContactsAdaptor.qml ContactsModelAdaptor 1.0 ContactsModelAdaptor.qml diff --git a/ui/imports/shared/popups/ProfileDialog.qml b/ui/imports/shared/popups/ProfileDialog.qml index 965367c2a2..9c29191a40 100644 --- a/ui/imports/shared/popups/ProfileDialog.qml +++ b/ui/imports/shared/popups/ProfileDialog.qml @@ -1,4 +1,4 @@ -import QtQuick 2.14 +import QtQuick 2.15 import QtQuick.Controls 2.15 import StatusQ.Popups.Dialog 0.1 @@ -11,7 +11,7 @@ StatusDialog { property var parentPopup - property alias publicKey: profileView.publicKey + property alias contactDetails: profileView.contactDetails property alias profileStore: profileView.profileStore property alias contactsStore: profileView.contactsStore @@ -28,9 +28,6 @@ StatusDialog { property alias assetsModel: profileView.assetsModel property alias collectiblesModel: profileView.collectiblesModel - - property alias dirtyValues: profileView.dirtyValues - property alias dirty: profileView.dirty implicitHeight: implicitContentHeight + (header.visible ? header.height : 0) width: 640 diff --git a/ui/imports/shared/views/ProfileDialogView.qml b/ui/imports/shared/views/ProfileDialogView.qml index a1b095c31d..07df45c9fb 100644 --- a/ui/imports/shared/views/ProfileDialogView.qml +++ b/ui/imports/shared/views/ProfileDialogView.qml @@ -30,9 +30,10 @@ import AppLayouts.Wallet.stores 1.0 as WalletStores Pane { id: root + required property ContactDetails contactDetails property bool readOnly // inside settings/profile/preview - property string publicKey: contactsStore.myPublicKey + readonly property string publicKey: contactDetails.publicKey readonly property alias isCurrentUser: d.isCurrentUser property ProfileStores.ProfileStore profileStore @@ -42,9 +43,6 @@ Pane { property alias sendToAccountEnabled: showcaseView.sendToAccountEnabled - property var dirtyValues: ({}) - property bool dirty: false - property var showcaseCommunitiesModel property var showcaseAccountsModel property var showcaseCollectiblesModel @@ -65,17 +63,10 @@ Pane { id: background } - ContactDetails { - id: contactDetails - publicKey: root.publicKey - contactsStore: root.contactsStore - profileStore: root.profileStore - } - QtObject { id: d - readonly property bool isCurrentUser: root.profileStore.pubkey === root.publicKey + readonly property bool isCurrentUser: contactDetails.isCurrentUser readonly property string userDisplayName: contactDetails.displayName readonly property string userNickName: contactDetails.localNickname readonly property string prettyEnsName: contactDetails.name @@ -225,10 +216,8 @@ Pane { id: userImage Layout.alignment: Qt.AlignTop objectName: "ProfileDialog_userImage" - name: root.dirty ? root.dirtyValues.displayName - : d.mainDisplayName - image: root.dirty ? root.dirtyValues.profileLargeImage - : Utils.addTimestampToURL(contactDetails.largeImage) + name: d.mainDisplayName + image: Utils.addTimestampToURL(contactDetails.largeImage) colorId: contactDetails.colorId colorHash: contactDetails.colorHash @@ -399,7 +388,7 @@ Pane { font.bold: true font.pixelSize: 22 elide: Text.ElideRight - text: StatusQUtils.Emoji.parse(root.dirty ? root.dirtyValues.displayName : d.mainDisplayName, StatusQUtils.Emoji.size.middle) + text: StatusQUtils.Emoji.parse(d.mainDisplayName, StatusQUtils.Emoji.size.middle) } StatusContactVerificationIcons { id: verificationIcons @@ -465,7 +454,7 @@ Pane { id: bioText width: bioScrollView.availableWidth wrapMode: Text.WrapAtWordBoundaryOrAnywhere - text: root.dirty ? root.dirtyValues.bio.trim() : contactDetails.bio.trim() + text: contactDetails.bio.trim() } } EmojiHash { @@ -527,8 +516,7 @@ Pane { Layout.preferredHeight: 300 currentTabIndex: showcaseTabBar.currentIndex - mainDisplayName: root.dirty ? root.dirtyValues.displayName - : d.mainDisplayName + mainDisplayName: d.mainDisplayName readOnly: root.readOnly communitiesModel: root.showcaseCommunitiesModel