Jonathan Rainville 50132c5a0e
Refactor contacts models to have a single model, remove useless properties and improve updating (#16667)
* refactor(contacts): refactor 5 contact models into one and filter in QML

Fixes #16549

Refactors the 5 types of contact models (all, mutuals, banned, received and sent) into only the `allContacts` and use an Adaptor on the QML side to filter into the needed models.
This cleans the Nim side a lot and makes applying updates to the contacts' model way simpler.

* chore(contacts): remove useless and duplicated contact properties

OptionalName and isSyncing were never used.
DefaultDisplayName was not really used and is actually a duplication of preferredDisplayName, so I replaced the limited usages of DefaultDisplayName by preferredDisplayName

* refactor(contacts): improve updates by not removing and re-adding

We used to update contact items by removing them from the models and re-adding them. This is highly inefficient.
Instead, the proper way is to update only the values that changed.

* user_model: onItemChanged signal removed

* user_model: sorting by online status no longer needed on nim side

* Chat/RootStore: contactsModel property removed

* ContactsStore encapsulation improved

* ContactsStore: contacts model adaptor moved outside store

---------

Co-authored-by: Michał Cieślak <michalcieslak@status.im>
2024-11-28 09:15:34 -05:00

289 lines
11 KiB
QML

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
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import utils 1.0
import shared.controls 1.0
import shared.panels 1.0
import shared.popups 1.0
import shared.stores 1.0 as SharedStores
import shared.views 1.0
import shared.views.chat 1.0
import "../stores"
import "../panels"
import "../popups"
SettingsContentBase {
id: root
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
titleRowComponentLoader.sourceComponent: StatusButton {
objectName: "ContactsView_ContactRequest_Button"
text: qsTr("Send contact request to chat key")
onClicked: {
Global.openPopup(sendContactRequest);
}
}
function openContextMenu(model, pubKey) {
const entry = ModelUtils.getByKey(model, "pubKey", pubKey)
const profileType = Utils.getProfileType(entry.isCurrentUser, false, entry.isBlocked)
const contactType = Utils.getContactType(entry.contactRequest, entry.isContact)
const params = {
pubKey, profileType, contactType,
compressedPubKey: entry.compressedPubKey,
emojiHash: root.utilsStore.getEmojiHash(pubKey),
displayName: entry.preferredDisplayName,
userIcon: entry.icon,
colorHash: entry.colorHash,
colorId: entry.colorId,
trustStatus: entry.trustStatus,
onlineStatus: entry.onlineStatus,
ensVerified: entry.isEnsVerified,
hasLocalNickname: !!entry.localNickname
}
Global.openMenu(contactContextMenuComponent, this, params)
}
Item {
id: contentItem
width: root.contentWidth
height: (searchBox.height + contactsTabBar.height
+ stackLayout.height + (2 * Theme.bigPadding))
Component {
id: contactContextMenuComponent
ProfileContextMenu {
id: contactContextMenu
property string pubKey
onOpenProfileClicked: Global.openProfilePopup(contactContextMenu.pubKey, null, null)
onReviewContactRequest: Global.openReviewContactRequestPopup(contactContextMenu.pubKey, null)
onSendContactRequest: Global.openContactRequestPopup(contactContextMenu.pubKey, null)
onEditNickname: Global.openNicknamePopupRequested(contactContextMenu.pubKey, null)
onUnblockContact: Global.unblockContactRequested(contactContextMenu.pubKey)
onMarkAsUntrusted: Global.markAsUntrustedRequested(contactContextMenu.pubKey)
onRemoveContact: Global.removeContactRequested(contactContextMenu.pubKey)
onBlockContact: Global.blockContactRequested(contactContextMenu.pubKey)
onCreateOneToOneChat: root.contactsStore.joinPrivateChat(contactContextMenu.pubKey)
onRemoveTrustStatus: root.contactsStore.removeTrustStatus(contactContextMenu.pubKey)
onRemoveNickname: root.contactsStore.changeContactNickname(contactContextMenu.pubKey, "",
contactContextMenu.displayName, true)
onMarkAsTrusted: Global.openMarkAsIDVerifiedPopup(contactContextMenu.pubKey, null)
onRemoveTrustedMark: Global.openRemoveIDVerificationDialog(contactContextMenu.pubKey, null)
onClosed: destroy()
}
}
SearchBox {
id: searchBox
anchors.left: parent.left
anchors.right: parent.right
placeholderText: qsTr("Search by a display name or chat key")
}
StatusTabBar {
id: contactsTabBar
anchors.left: parent.left
anchors.right: parent.right
anchors.top: searchBox.bottom
anchors.topMargin: Theme.padding
StatusTabButton {
id: contactsBtn
leftPadding: Theme.padding
width: implicitWidth
text: qsTr("Contacts")
}
StatusTabButton {
id: pendingRequestsBtn
objectName: "ContactsView_PendingRequest_Button"
width: implicitWidth
enabled: !root.pendingReceivedRequestContactsModel.ModelCount.empty ||
!root.pendingSentRequestContactsModel.ModelCount.empty
text: qsTr("Pending Requests")
badge.value: root.pendingReceivedRequestContactsModel.ModelCount.count
}
StatusTabButton {
id: blockedBtn
objectName: "ContactsView_Blocked_Button"
width: implicitWidth
enabled: !root.blockedContactsModel.ModelCount.empty
text: qsTr("Blocked")
}
}
StackLayout {
id: stackLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: contactsTabBar.bottom
currentIndex: contactsTabBar.currentIndex
anchors.topMargin: Theme.padding
// CONTACTS
ColumnLayout {
Layout.fillWidth: true
Layout.minimumHeight: 0
Layout.maximumHeight: (verifiedContacts.height + mutualContacts.height + noFriendsItem.height)
visible: (stackLayout.currentIndex === 0)
onVisibleChanged: {
if (visible) {
stackLayout.height = height+contactsTabBar.anchors.topMargin;
}
}
spacing: Theme.padding
ContactsListPanel {
id: verifiedContacts
Layout.fillWidth: true
title: qsTr("Trusted Contacts")
visible: !noFriendsItem.visible && count > 0
contactsModel: root.mutualContactsModel
searchString: searchBox.text
onOpenContactContextMenu: root.openContextMenu(contactsModel, publicKey)
panelUsage: Constants.contactsPanelUsage.verifiedMutualContacts
onSendMessageActionTriggered: {
root.contactsStore.joinPrivateChat(publicKey)
}
}
ContactsListPanel {
id: mutualContacts
Layout.fillWidth: true
visible: !noFriendsItem.visible && count > 0
title: qsTr("Contacts")
contactsModel: root.mutualContactsModel
searchString: searchBox.text
onOpenContactContextMenu: root.openContextMenu(contactsModel, publicKey)
panelUsage: Constants.contactsPanelUsage.mutualContacts
onSendMessageActionTriggered: {
root.contactsStore.joinPrivateChat(publicKey)
}
}
Item {
id: noFriendsItem
Layout.fillWidth: true
Layout.preferredHeight: visible ? (root.contentHeight - (2*searchBox.height) - contactsTabBar.height - contactsTabBar.anchors.topMargin) : 0
visible: root.mutualContactsModel.ModelCount.empty
NoFriendsRectangle {
anchors.centerIn: parent
text: qsTr("You don't have any contacts yet")
}
}
}
// PENDING REQUESTS
ColumnLayout {
Layout.fillWidth: true
Layout.minimumHeight: 0
Layout.maximumHeight: (receivedRequests.height + sentRequests.height)
spacing: Theme.padding
visible: (stackLayout.currentIndex === 1)
onVisibleChanged: {
if (visible) {
stackLayout.height = height+contactsTabBar.anchors.topMargin;
}
}
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.pendingReceivedRequestContactsModel
panelUsage: Constants.contactsPanelUsage.receivedContactRequest
onSendMessageActionTriggered: {
root.contactsStore.joinPrivateChat(publicKey)
}
onContactRequestAccepted: {
root.contactsStore.acceptContactRequest(publicKey, "")
}
onContactRequestRejected: {
root.contactsStore.dismissContactRequest(publicKey, "")
}
}
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.pendingSentRequestContactsModel
panelUsage: Constants.contactsPanelUsage.sentContactRequest
}
}
// BLOCKED
ContactsListPanel {
id: blockedContacts
Layout.fillWidth: true
searchString: searchBox.text
onOpenContactContextMenu: root.openContextMenu(contactsModel, publicKey)
contactsModel: root.blockedContactsModel
panelUsage: Constants.contactsPanelUsage.blockedContacts
visible: (stackLayout.currentIndex === 2)
onVisibleChanged: {
if (visible) {
stackLayout.height = height;
}
}
}
}
Component {
id: loadingIndicator
StatusLoadingIndicator {
width: 12
height: 12
}
}
Component {
id: sendContactRequest
SendContactRequestModal {
contactsStore: root.contactsStore
onClosed: destroy()
}
}
}
}