diff --git a/ui/app/AppLayouts/Chat/ChatLayout.qml b/ui/app/AppLayouts/Chat/ChatLayout.qml index 42345f2a4a..6d5d8da072 100644 --- a/ui/app/AppLayouts/Chat/ChatLayout.qml +++ b/ui/app/AppLayouts/Chat/ChatLayout.qml @@ -37,6 +37,7 @@ StackLayout { required property WalletStore.WalletAssetsStore walletAssetsStore required property SharedStores.CurrenciesStore currencyStore + property var mutualContactsModel property var sectionItemModel MembersModelAdaptor { @@ -154,8 +155,6 @@ StackLayout { objectName: "chatViewComponent" - emojiPopup: root.emojiPopup - stickersPopup: root.stickersPopup contactsStore: root.contactsStore sharedRootStore: root.sharedRootStore utilsStore: root.utilsStore @@ -164,6 +163,11 @@ StackLayout { communitiesStore: root.communitiesStore walletAssetsStore: root.walletAssetsStore currencyStore: root.currencyStore + + mutualContactsModel: root.mutualContactsModel + + emojiPopup: root.emojiPopup + stickersPopup: root.stickersPopup sendModalPopup: root.sendModalPopup sectionItemModel: root.sectionItemModel joinedMembersCount: membersModelAdaptor.joinedMembers.ModelCount.count diff --git a/ui/app/AppLayouts/Chat/views/ChatHeaderContentView.qml b/ui/app/AppLayouts/Chat/views/ChatHeaderContentView.qml index 7ba71f11f7..8df4414564 100644 --- a/ui/app/AppLayouts/Chat/views/ChatHeaderContentView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatHeaderContentView.qml @@ -22,6 +22,9 @@ Item { property alias searchButton: searchButton property RootStore rootStore + + property var mutualContactsModel + property var chatContentModule: root.rootStore.currentChatContentModule() || null property var emojiPopup property int padding: Theme.halfPadding @@ -346,7 +349,7 @@ Item { usersStore: UsersStore { usersModule: root.chatContentModule.usersModule } - contactsModel: root.rootStore.contactsStore.myContactsModel + contactsModel: root.mutualContactsModel onConfirmed: root.state = d.stateInfoButtonContent onRejected: root.state = d.stateInfoButtonContent diff --git a/ui/app/AppLayouts/Chat/views/ChatView.qml b/ui/app/AppLayouts/Chat/views/ChatView.qml index ad3925297d..ada6857713 100644 --- a/ui/app/AppLayouts/Chat/views/ChatView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatView.qml @@ -48,6 +48,9 @@ StatusSectionLayout { property CommunitiesStores.CommunitiesStore communitiesStore required property WalletStore.WalletAssetsStore walletAssetsStore required property SharedStores.CurrenciesStore currencyStore + + property var mutualContactsModel + required property var sendModalPopup property var sectionItemModel property int joinedMembersCount @@ -231,8 +234,11 @@ StatusSectionLayout { id: chatHeaderContentViewComponent ChatHeaderContentView { visible: !!root.rootStore.currentChatContentModule() + rootStore: root.rootStore + mutualContactsModel: root.mutualContactsModel emojiPopup: root.emojiPopup + onSearchButtonClicked: root.openAppSearch() onDisplayEditChannelPopup: { Global.openPopup(contactColumnLoader.item.createChannelPopup, { diff --git a/ui/app/AppLayouts/Chat/views/CreateChatView.qml b/ui/app/AppLayouts/Chat/views/CreateChatView.qml index 9f7243373b..6fb67f1fb0 100644 --- a/ui/app/AppLayouts/Chat/views/CreateChatView.qml +++ b/ui/app/AppLayouts/Chat/views/CreateChatView.qml @@ -23,6 +23,9 @@ Page { property SharedStores.UtilsStore utilsStore property ChatStores.RootStore rootStore property ChatStores.CreateChatPropertiesStore createChatPropertiesStore + + property var mutualContactsModel + property var emojiPopup: null property var stickersPopup: null @@ -64,7 +67,7 @@ Page { rootStore: root.rootStore utilsStore: root.utilsStore - contactsModel: root.rootStore.contactsStore.myContactsModel + contactsModel: root.mutualContactsModel function createChat() { if (model.count === 0) { @@ -180,7 +183,7 @@ Page { StatusBaseText { anchors.centerIn: parent width: Math.min(553, parent.width - 2 * Theme.padding) - visible: root.rootStore.contactsStore.myContactsModel.ModelCount.empty + visible: root.mutualContacts.ModelCount.empty horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap diff --git a/ui/app/AppLayouts/Profile/ProfileLayout.qml b/ui/app/AppLayouts/Profile/ProfileLayout.qml index ea057716f7..361859c9a5 100644 --- a/ui/app/AppLayouts/Profile/ProfileLayout.qml +++ b/ui/app/AppLayouts/Profile/ProfileLayout.qml @@ -54,6 +54,11 @@ StatusSectionLayout { required property CollectiblesStore collectiblesStore required property SharedStores.CurrenciesStore currencyStore + property var mutualContactsModel + property var blockedContactsModel + property var pendingReceivedRequestContactsModel + property var pendingSentRequestContactsModel + required property bool isCentralizedMetricsEnabled backButtonName: root.store.backButtonName @@ -108,7 +113,7 @@ StatusSectionLayout { syncingBadgeCount: root.store.devicesStore.devicesModel.count - root.store.devicesStore.devicesModel.pairedCount - messagingBadgeCount: root.store.contactsStore.receivedContactRequestsModel.count + messagingBadgeCount: root.pendingReceivedRequestContactsModel.ModelCount.count } headerBackground: AccountHeaderGradient { @@ -233,6 +238,11 @@ StatusSectionLayout { utilsStore: root.utilsStore sectionTitle: qsTr("Contacts") contentWidth: d.contentWidth + + mutualContactsModel: root.mutualContactsModel + blockedContactsModel: root.blockedContactsModel + pendingReceivedRequestContactsModel: root.pendingReceivedRequestContactsModel + pendingSentRequestContactsModel: root.pendingSentRequestContactsModel } } @@ -262,10 +272,11 @@ StatusSectionLayout { sourceComponent: MessagingView { implicitWidth: parent.width implicitHeight: parent.height - messagingStore: root.store.messagingStore - sectionTitle: settingsEntriesModel.getNameForSubsection(Constants.settingsSubsection.messaging) - contactsStore: root.store.contactsStore contentWidth: d.contentWidth + + sectionTitle: settingsEntriesModel.getNameForSubsection(Constants.settingsSubsection.messaging) + requestsCount: root.pendingReceivedRequestContactsModel.ModelCount.count + messagingStore: root.store.messagingStore } } diff --git a/ui/app/AppLayouts/Profile/stores/ContactsModelAdaptor.qml b/ui/app/AppLayouts/Profile/stores/ContactsModelAdaptor.qml deleted file mode 100644 index 5be2c2a488..0000000000 --- a/ui/app/AppLayouts/Profile/stores/ContactsModelAdaptor.qml +++ /dev/null @@ -1,81 +0,0 @@ -import QtQml 2.15 - -import StatusQ 0.1 -import StatusQ.Models 0.1 -import StatusQ.Core.Utils 0.1 - -import utils 1.0 - -import SortFilterProxyModel 0.2 - -QObject { - id: root - - /** - Expected model structure: - - pubKey [string] - unique identifier of a member, e.g "0x3234235" - displayName [string] - member's chosen name - preferredDisplayName [string] - calculated member name according to priorities (eg: nickname has higher priority) - ensName [string] - member's ENS name - isEnsVerified [bool] - whether the ENS name was verified on chain - localNickname [string] - local nickname set by the current user - alias [string] - generated 3 word name - icon [string] - thumbnail image of the user - colorId [string] - generated color ID for the user's profile - colorHash [string] - generated color hash for the user's profile - onlineStatus [int] - the online status of the member - isContact [bool] - whether the user is a mutual contact or not - isVerified [bool] - wheter the user has been marked as verified or not - isUntrustworthy [bool] - wheter the user has been marked as untrustworthy or not - isBlocked [bool] - whether the user has been blocked or not - contactRequest [int] - state of the contact request that was sent - isCurrentUser [bool] - whether the contact is actually ourselves - lastUpdated [int64] - clock of when last the contact was updated - lastUpdatedLocally [int64] - clock of when last the contact was updated locally - bio [string] - contacts's chosen bio text - thumbnailImage [string] - local url of the user's thumbnail image - largeImage [string] - local url of the user's large image - isContactRequestReceived [bool] - whether we received a contact request from that user - isContactRequestSent [bool] - whether we send a contact request to that user - isRemoved [bool] - whether we removed that contact - trustStatus [int] - the trust status of the user as an enum - **/ - property var allContacts - - readonly property var mutualContacts: SortFilterProxyModel { - sourceModel: root.allContacts ?? null - - filters: ValueFilter { - roleName: "isContact" - value: true - } - } - - readonly property var blockedContacts: SortFilterProxyModel { - sourceModel: root.allContacts ?? null - - filters: ValueFilter { - roleName: "isBlocked" - value: true - } - } - - readonly property var pendingReceivedRequestContacts: SortFilterProxyModel { - sourceModel: root.allContacts ?? null - - filters: ValueFilter { - roleName: "contactRequest" - value: Constants.ContactRequestState.Received - } - } - - readonly property var pendingSentRequestContacts: SortFilterProxyModel { - sourceModel: root.allContacts ?? null - - filters: ValueFilter { - roleName: "contactRequest" - value: Constants.ContactRequestState.Sent - } - } -} diff --git a/ui/app/AppLayouts/Profile/stores/ContactsStore.qml b/ui/app/AppLayouts/Profile/stores/ContactsStore.qml index c9862a45ea..b318492d78 100644 --- a/ui/app/AppLayouts/Profile/stores/ContactsStore.qml +++ b/ui/app/AppLayouts/Profile/stores/ContactsStore.qml @@ -18,21 +18,10 @@ QtObject { } } - property string myPublicKey: userProfile.pubKey + readonly property string myPublicKey: userProfile.pubKey // contactsModel holds all available contacts - property var contactsModel: d.contactsModuleInst.contactsModel - - readonly property var contactsModelAdaptor: ContactsModelAdaptor { - allContacts: contactsModel - } - - signal resolvedENS(string resolvedPubKey, string resolvedAddress, string uuid) - - property var myContactsModel: contactsModelAdaptor.mutualContacts - property var blockedContactsModel: contactsModelAdaptor.blockedContacts - property var receivedContactRequestsModel: contactsModelAdaptor.pendingReceivedRequestContacts - property var sentContactRequestsModel: contactsModelAdaptor.pendingSentRequestContacts + readonly property var contactsModel: d.contactsModuleInst.contactsModel readonly property var showcasePublicKey: d.contactsModuleInst.showcasePublicKey diff --git a/ui/app/AppLayouts/Profile/views/ContactsView.qml b/ui/app/AppLayouts/Profile/views/ContactsView.qml index 64ab696749..38e3641aea 100644 --- a/ui/app/AppLayouts/Profile/views/ContactsView.qml +++ b/ui/app/AppLayouts/Profile/views/ContactsView.qml @@ -2,6 +2,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 +import StatusQ 0.1 import StatusQ.Components 0.1 import StatusQ.Controls 0.1 import StatusQ.Core 0.1 @@ -27,6 +28,11 @@ SettingsContentBase { property ContactsStore contactsStore property SharedStores.UtilsStore utilsStore + property var mutualContactsModel + property var blockedContactsModel + property var pendingReceivedRequestContactsModel + property var pendingSentRequestContactsModel + property alias searchStr: searchBox.text property bool isPending: false @@ -116,16 +122,16 @@ SettingsContentBase { id: pendingRequestsBtn objectName: "ContactsView_PendingRequest_Button" width: implicitWidth - enabled: root.contactsStore.receivedContactRequestsModel.count > 0 || - root.contactsStore.sentContactRequestsModel.count > 0 + enabled: !root.pendingReceivedRequestContactsModel.ModelCount.empty || + !root.pendingSentRequestContactsModel.ModelCount.empty text: qsTr("Pending Requests") - badge.value: root.contactsStore.receivedContactRequestsModel.count + badge.value: root.pendingReceivedRequestContactsModel.ModelCount.count } StatusTabButton { id: blockedBtn objectName: "ContactsView_Blocked_Button" width: implicitWidth - enabled: root.contactsStore.blockedContactsModel.count > 0 + enabled: !root.blockedContactsModel.ModelCount.empty text: qsTr("Blocked") } } @@ -151,10 +157,11 @@ SettingsContentBase { spacing: Theme.padding ContactsListPanel { id: verifiedContacts + Layout.fillWidth: true title: qsTr("Trusted Contacts") visible: !noFriendsItem.visible && count > 0 - contactsModel: root.contactsStore.myContactsModel + contactsModel: root.mutualContactsModel searchString: searchBox.text onOpenContactContextMenu: root.openContextMenu(contactsModel, publicKey) panelUsage: Constants.contactsPanelUsage.verifiedMutualContacts @@ -165,10 +172,11 @@ SettingsContentBase { ContactsListPanel { id: mutualContacts + Layout.fillWidth: true visible: !noFriendsItem.visible && count > 0 title: qsTr("Contacts") - contactsModel: root.contactsStore.myContactsModel + contactsModel: root.mutualContactsModel searchString: searchBox.text onOpenContactContextMenu: root.openContextMenu(contactsModel, publicKey) panelUsage: Constants.contactsPanelUsage.mutualContacts @@ -180,9 +188,11 @@ SettingsContentBase { Item { id: noFriendsItem + Layout.fillWidth: true Layout.preferredHeight: visible ? (root.contentHeight - (2*searchBox.height) - contactsTabBar.height - contactsTabBar.anchors.topMargin) : 0 - visible: root.contactsStore.myContactsModel.count === 0 + visible: root.mutualContactsModel.ModelCount.empty + NoFriendsRectangle { anchors.centerIn: parent text: qsTr("You don't have any contacts yet") @@ -204,13 +214,14 @@ SettingsContentBase { } ContactsListPanel { id: receivedRequests + objectName: "receivedRequests_ContactsListPanel" Layout.fillWidth: true title: qsTr("Received") searchString: searchBox.text visible: count > 0 onOpenContactContextMenu: root.openContextMenu(contactsModel, publicKey) - contactsModel: root.contactsStore.receivedContactRequestsModel + contactsModel: root.pendingReceivedRequestContactsModel panelUsage: Constants.contactsPanelUsage.receivedContactRequest onSendMessageActionTriggered: { @@ -228,23 +239,26 @@ SettingsContentBase { ContactsListPanel { id: sentRequests + objectName: "sentRequests_ContactsListPanel" Layout.fillWidth: true title: qsTr("Sent") searchString: searchBox.text visible: count > 0 onOpenContactContextMenu: root.openContextMenu(contactsModel, publicKey) - contactsModel: root.contactsStore.sentContactRequestsModel + contactsModel: root.pendingSentRequestContactsModel panelUsage: Constants.contactsPanelUsage.sentContactRequest } } // BLOCKED ContactsListPanel { + id: blockedContacts + Layout.fillWidth: true searchString: searchBox.text onOpenContactContextMenu: root.openContextMenu(contactsModel, publicKey) - contactsModel: root.contactsStore.blockedContactsModel + contactsModel: root.blockedContactsModel panelUsage: Constants.contactsPanelUsage.blockedContacts visible: (stackLayout.currentIndex === 2) onVisibleChanged: { diff --git a/ui/app/AppLayouts/Profile/views/MessagingView.qml b/ui/app/AppLayouts/Profile/views/MessagingView.qml index 6ff11283ce..e7c8c3a956 100644 --- a/ui/app/AppLayouts/Profile/views/MessagingView.qml +++ b/ui/app/AppLayouts/Profile/views/MessagingView.qml @@ -24,7 +24,8 @@ SettingsContentBase { id: root property MessagingStore messagingStore - property ContactsStore contactsStore + + property alias requestsCount: contactRequestsIndicator.requestsCount ColumnLayout { id: generalColumn @@ -75,10 +76,12 @@ SettingsContentBase { // CONTACTS SECTION StatusContactRequestsIndicatorListItem { + id: contactRequestsIndicator + objectName: "MessagingView_ContactsListItem_btn" Layout.fillWidth: true title: qsTr("Contacts, Requests, and Blocked Users") - requestsCount: root.contactsStore.receivedContactRequestsModel.count + onClicked: Global.changeAppSectionBySectionType(Constants.appSection.profile, Constants.settingsSubsection.contacts) } diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index e373822fd3..4c68428471 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -46,6 +46,7 @@ import AppLayouts.Wallet.popups 1.0 as WalletPopups import AppLayouts.Wallet.stores 1.0 as WalletStores import AppLayouts.stores 1.0 as AppStores +import mainui.adaptors 1.0 import mainui.activitycenter.stores 1.0 import mainui.activitycenter.popups 1.0 @@ -104,6 +105,12 @@ Item { // set from main.qml property var sysPalette + ContactsModelAdaptor { + id: contactsModelAdaptor + + allContacts: appMain.profileSectionStore.contactsStore.contactsModel + } + // Central UI point for managing app toasts: ToastsManager { id: toastsManager @@ -431,6 +438,9 @@ Item { walletCollectiblesStore: appMain.walletCollectiblesStore buyCryptoStore: appMain.buyCryptoStore networkConnectionStore: appMain.networkConnectionStore + + mutualContactsModel: contactsModelAdaptor.mutualContacts + isDevBuild: !production onOpenExternalLink: globalConns.onOpenLink(link) @@ -906,8 +916,8 @@ Item { checked: model.active badge.value: model.notificationsCount badge.visible: model.sectionType === Constants.appSection.profile && - appMain.rootStore.contactStore.receivedContactRequestsModel.count ? true // pending CR request - : model.hasNotification + (contactsModelAdaptor.pendingReceivedRequestContacts.ModelCount.empty ? // pending contact request + model.hasNotification : true) badge.border.color: hovered ? Theme.palette.statusBadge.hoverBorderColor : Theme.palette.statusBadge.borderColor badge.border.width: 2 onClicked: { @@ -1395,6 +1405,8 @@ Item { stickersPopup: statusStickersPopupLoader.item sendViaPersonalChatEnabled: featureFlagsStore.sendViaPersonalChatEnabled && appMain.networkConnectionStore.sendBuyBridgeEnabled + mutualContactsModel: contactsModelAdaptor.mutualContacts + onProfileButtonClicked: { Global.changeAppSectionBySectionType(Constants.appSection.profile); } @@ -1467,6 +1479,11 @@ Item { isCentralizedMetricsEnabled: appMain.isCentralizedMetricsEnabled settingsSubSubsection: profileLoader.settingsSubSubsection + mutualContactsModel: contactsModelAdaptor.mutualContacts + blockedContactsModel: contactsModelAdaptor.blockedContacts + pendingReceivedRequestContactsModel: contactsModelAdaptor.pendingReceivedRequestContacts + pendingSentRequestContactsModel: contactsModelAdaptor.pendingSentRequestContacts + Binding on settingsSubsection { value: profileLoader.settingsSubsection } @@ -1560,6 +1577,8 @@ Item { walletAssetsStore: appMain.walletAssetsStore currencyStore: appMain.currencyStore + mutualContactsModel: contactsModelAdaptor.mutualContacts + onProfileButtonClicked: { Global.changeAppSectionBySectionType(Constants.appSection.profile); } @@ -1600,6 +1619,9 @@ Item { chatCommunitySectionModule: appMain.rootStore.mainModuleInst.getChatSectionModule() } createChatPropertiesStore: appMain.createChatPropertiesStore + + mutualContactsModel: contactsModelAdaptor.mutualContacts + emojiPopup: statusEmojiPopup.item stickersPopup: statusStickersPopupLoader.item } diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index b584b130a3..1a2088fe8c 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -52,6 +52,9 @@ QtObject { property WalletStores.CollectiblesStore walletCollectiblesStore property NetworkConnectionStore networkConnectionStore property WalletStores.BuyCryptoStore buyCryptoStore + + property var mutualContactsModel + property bool isDevBuild signal openExternalLink(string link) @@ -473,7 +476,7 @@ QtObject { InviteFriendsToCommunityPopup { rootStore: root.rootStore - contactsModel: root.rootStore.contactStore.myContactsModel + contactsModel: root.mutualContactsModel onClosed: destroy() } diff --git a/ui/app/mainui/adaptors/ContactsModelAdaptor.qml b/ui/app/mainui/adaptors/ContactsModelAdaptor.qml new file mode 100644 index 0000000000..85a43ab040 --- /dev/null +++ b/ui/app/mainui/adaptors/ContactsModelAdaptor.qml @@ -0,0 +1,78 @@ +import QtQml 2.15 + +import StatusQ.Core.Utils 0.1 +import utils 1.0 + +import SortFilterProxyModel 0.2 + +QObject { + id: root + + /** + Expected model structure: + + pubKey [string] - unique identifier of a member, e.g "0x3234235" + displayName [string] - member's chosen name + preferredDisplayName [string] - calculated member name according to priorities (eg: nickname has higher priority) + ensName [string] - member's ENS name + isEnsVerified [bool] - whether the ENS name was verified on chain + localNickname [string] - local nickname set by the current user + alias [string] - generated 3 word name + icon [string] - thumbnail image of the user + colorId [string] - generated color ID for the user's profile + colorHash [string] - generated color hash for the user's profile + onlineStatus [int] - the online status of the member + isContact [bool] - whether the user is a mutual contact or not + isVerified [bool] - wheter the user has been marked as verified or not + isUntrustworthy [bool] - wheter the user has been marked as untrustworthy or not + isBlocked [bool] - whether the user has been blocked or not + contactRequest [int] - state of the contact request that was sent + isCurrentUser [bool] - whether the contact is actually ourselves + lastUpdated [int64] - clock of when last the contact was updated + lastUpdatedLocally [int64] - clock of when last the contact was updated locally + bio [string] - contacts's chosen bio text + thumbnailImage [string] - local url of the user's thumbnail image + largeImage [string] - local url of the user's large image + isContactRequestReceived [bool] - whether we received a contact request from that user + isContactRequestSent [bool] - whether we send a contact request to that user + isRemoved [bool] - whether we removed that contact + trustStatus [int] - the trust status of the user as an enum + **/ + property var allContacts + + readonly property var mutualContacts: SortFilterProxyModel { + sourceModel: root.allContacts ?? null + + filters: ValueFilter { + roleName: "isContact" + value: true + } + } + + readonly property var blockedContacts: SortFilterProxyModel { + sourceModel: root.allContacts ?? null + + filters: ValueFilter { + roleName: "isBlocked" + value: true + } + } + + readonly property var pendingReceivedRequestContacts: SortFilterProxyModel { + sourceModel: root.allContacts ?? null + + filters: ValueFilter { + roleName: "contactRequest" + value: Constants.ContactRequestState.Received + } + } + + readonly property var pendingSentRequestContacts: SortFilterProxyModel { + sourceModel: root.allContacts ?? null + + filters: ValueFilter { + roleName: "contactRequest" + value: Constants.ContactRequestState.Sent + } + } +} diff --git a/ui/app/mainui/adaptors/qmldir b/ui/app/mainui/adaptors/qmldir new file mode 100644 index 0000000000..11680eeb47 --- /dev/null +++ b/ui/app/mainui/adaptors/qmldir @@ -0,0 +1 @@ +ContactsModelAdaptor 1.0 ContactsModelAdaptor.qml