Jonathan Rainville a6e6546a08
Add a "Dismissed contact requests" tab in the Contacts panel (#17021)
* chore(ContactPanel): add tooltips to contact panel buttons

* feat(contacts): add a tab for dismissed contacts and a button to undo

Fixes #16844

Adds a tab in the Messaging>Contacts page that shows the dismissed contact requests.
it has a button to undo the rejection. That is simply a call to accept the old contact request, which then makes the two users mutual contacts
2025-01-14 10:18:31 -05:00

286 lines
9.2 KiB
QML

import QtQuick 2.15
import QtQuick.Layouts 1.15
import StatusQ 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 AppLayouts.Profile.panels 1.0
import AppLayouts.Profile.popups 1.0
import AppLayouts.Profile.stores 1.0
import shared 1.0
import shared.controls 1.0
import shared.stores 1.0 as SharedStores
import shared.views 1.0
import shared.views.chat 1.0
import utils 1.0
import SortFilterProxyModel 0.2
SettingsContentBase {
id: root
property ContactsStore contactsStore
property SharedStores.UtilsStore utilsStore
required property var mutualContactsModel
required property var blockedContactsModel
required property var pendingContactsModel
required property int pendingReceivedContactsCount
required property var dismissedReceivedRequestContactsModel
property alias searchStr: searchBox.text
titleRowComponentLoader.sourceComponent: StatusButton {
objectName: "ContactsView_ContactRequest_Button"
text: qsTr("Send contact request to chat key")
onClicked: sendContactRequestComponent.createObject(root).open()
}
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)
}
headerComponents: ColumnLayout {
width: root.contentWidth
spacing: Theme.padding
StatusTabBar {
id: contactsTabBar
Layout.fillWidth: true
StatusTabButton {
objectName: "ContactsView_Contacts_Button"
width: implicitWidth
text: qsTr("Contacts")
}
StatusTabButton {
objectName: "ContactsView_PendingRequest_Button"
width: implicitWidth
enabled: !root.pendingContactsModel.ModelCount.empty
text: qsTr("Pending Requests")
badge.value: root.pendingReceivedContactsCount
}
StatusTabButton {
objectName: "ContactsView_DismissedRequest_Button"
width: implicitWidth
enabled: !root.dismissedReceivedRequestContactsModel.ModelCount.empty
text: qsTr("Dismissed Requests")
}
StatusTabButton {
objectName: "ContactsView_Blocked_Button"
width: implicitWidth
enabled: !root.blockedContactsModel.ModelCount.empty
text: qsTr("Blocked")
}
}
SearchBox {
id: searchBox
Layout.fillWidth: true
placeholderText: qsTr("Search by name or chat key")
}
}
StackLayout {
width: root.contentWidth
height: root.availableHeight
currentIndex: contactsTabBar.currentIndex
ContactsList {
inviteButtonVisible: searchBox.text === ""
model: SortFilterProxyModel {
sourceModel: root.mutualContactsModel
filters: UserSearchFilter {
searchString: searchBox.text
}
sorters: [
RoleSorter {
roleName: "isVerified"
sortOrder: Qt.DescendingOrder
},
StringSorter {
roleName: "preferredDisplayName"
caseSensitivity: Qt.CaseInsensitive
}
]
}
section.property: "isVerified"
section.delegate: SectionComponent {
text: section === "true" ? qsTr("Trusted Contacts")
: qsTr("Contacts")
}
section.labelPositioning: ViewSection.InlineLabels |
ViewSection.CurrentLabelAtStart
}
ContactsList {
model: SortFilterProxyModel {
sourceModel: root.pendingContactsModel
filters: UserSearchFilter {
searchString: searchBox.text
}
sorters: [
FilterSorter { // Received CRs first
ValueFilter {
roleName: "contactRequest"
value: Constants.ContactRequestState.Received
}
},
StringSorter {
roleName: "preferredDisplayName"
caseSensitivity: Qt.CaseInsensitive
}
]
}
section.property: "contactRequest"
section.delegate: SectionComponent {
text: section === `${Constants.ContactRequestState.Received}`
? qsTr("Received") : qsTr("Sent")
}
section.labelPositioning: ViewSection.InlineLabels |
ViewSection.CurrentLabelAtStart
}
ContactsList {
model: SortFilterProxyModel {
sourceModel: root.dismissedReceivedRequestContactsModel
filters: UserSearchFilter {
searchString: searchBox.text
}
sorters: StringSorter {
roleName: "preferredDisplayName"
caseSensitivity: Qt.CaseInsensitive
}
}
}
ContactsList {
model: SortFilterProxyModel {
sourceModel: root.blockedContactsModel
filters: UserSearchFilter {
searchString: searchBox.text
}
sorters: StringSorter {
roleName: "preferredDisplayName"
caseSensitivity: Qt.CaseInsensitive
}
}
}
}
component ContactsList: ContactsListPanel {
onProfilePopupRequested: Global.openProfilePopup(publicKey)
onContextMenuRequested: root.openContextMenu(model, publicKey)
onSendMessageRequested: root.contactsStore.joinPrivateChat(publicKey)
onAcceptContactRequested: root.contactsStore.acceptContactRequest(publicKey, "")
onRejectContactRequested: root.contactsStore.dismissContactRequest(publicKey, "")
onRejectionRemoved: root.contactsStore.acceptContactRequest(publicKey, "")
}
component SectionComponent: Rectangle {
required property string section
property alias text: sectionText.text
width: ListView.view.width
height: sectionText.implicitHeight
color: Theme.palette.statusListItem.backgroundColor
StatusBaseText {
id: sectionText
width: parent.width
anchors.verticalCenter: parent.verticalCenter
topPadding: Theme.halfPadding
bottomPadding: Theme.halfPadding
color: Theme.palette.baseColor1
font.pixelSize: Theme.additionalTextSize
font.weight: Font.Medium
elide: Text.ElideRight
}
}
Component {
id: sendContactRequestComponent
SendContactRequestModal {
contactsStore: root.contactsStore
onClosed: destroy()
}
}
Component {
id: contactContextMenuComponent
ProfileContextMenu {
id: menu
property string pubKey
onOpenProfileClicked: Global.openProfilePopup(menu.pubKey, null, null)
onReviewContactRequest: Global.openReviewContactRequestPopup(menu.pubKey, null)
onSendContactRequest: Global.openContactRequestPopup(menu.pubKey, null)
onEditNickname: Global.openNicknamePopupRequested(menu.pubKey, null)
onUnblockContact: Global.unblockContactRequested(menu.pubKey)
onMarkAsUntrusted: Global.markAsUntrustedRequested(menu.pubKey)
onRemoveContact: Global.removeContactRequested(menu.pubKey)
onBlockContact: Global.blockContactRequested(menu.pubKey)
onCreateOneToOneChat: root.contactsStore.joinPrivateChat(menu.pubKey)
onRemoveTrustStatus: root.contactsStore.removeTrustStatus(menu.pubKey)
onRemoveNickname: root.contactsStore.changeContactNickname(menu.pubKey, "",
menu.displayName, true)
onMarkAsTrusted: Global.openMarkAsIDVerifiedPopup(menu.pubKey, null)
onRemoveTrustedMark: Global.openRemoveIDVerificationDialog(menu.pubKey, null)
onClosed: destroy()
}
}
}