diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml index 9377704a68a0e82ba6f8e26d262c290ae327c102..69e2e18b34c7337f3333e20ac55e0b4548fb6ff6 100644 --- a/src/app/constant/JamiStrings.qml +++ b/src/app/constant/JamiStrings.qml @@ -355,6 +355,7 @@ Item { // BannedContacts property string tipBannedContactsDisplay: qsTr("Display banned contacts") + property string banned: qsTr("Banned") property string tipBannedContactsHide: qsTr("Hide banned contacts") // DeleteAccountDialog diff --git a/src/app/conversationlistmodel.cpp b/src/app/conversationlistmodel.cpp index 389729380d7bf6b3d3d21d455c7786219bde20d5..88301cb45cded7f30bde578760711d967d07acfc 100644 --- a/src/app/conversationlistmodel.cpp +++ b/src/app/conversationlistmodel.cpp @@ -120,7 +120,7 @@ ConversationListProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& s if (ignored_.contains(index.data(Role::UID).toString())) { match = true; } else if (index.data(Role::IsBanned).toBool()) { - if (!rx.isValid()) { + if (!rx.pattern().isEmpty() && rx.isValid()) { Q_FOREACH (const auto& filter, toFilter) { auto matchResult = rx.match(filter); if (matchResult.hasMatch() && matchResult.captured(0) == filter) { diff --git a/src/app/conversationlistmodelbase.cpp b/src/app/conversationlistmodelbase.cpp index 975d725f162c62ba6b244b13332fc582ceeb45fc..13d69e04f1996a4d892b3dda7dc0f8781274123f 100644 --- a/src/app/conversationlistmodelbase.cpp +++ b/src/app/conversationlistmodelbase.cpp @@ -128,6 +128,11 @@ ConversationListModelBase::dataForItem(item_t item, int role) const return QVariant(static_cast<int>(item.mode)); case Role::UID: return QVariant(item.uid); + case Role::IsBanned: + if (!item.isCoreDialog()) { + return false; + } + break; case Role::Uris: return QVariant(model_->peersForConversation(item.uid).toList()); case Role::Monikers: { diff --git a/src/app/currentconversation.cpp b/src/app/currentconversation.cpp index 534d6881c17afedf792762bb762fd592abf11672..5de28c6bbb672bd51c1e93a91ffa697d90578a39 100644 --- a/src/app/currentconversation.cpp +++ b/src/app/currentconversation.cpp @@ -84,6 +84,7 @@ CurrentConversation::updateData() if (isCoreDialog_) try { auto& contact = accInfo.contactModel->getContact(members.at(0)); + set_isBanned(contact.isBanned); isContact = contact.profileInfo.type != profile::Type::TEMPORARY; } catch (const std::exception& e) { qInfo() << "Contact not found: " << e.what(); diff --git a/src/app/currentconversation.h b/src/app/currentconversation.h index 58e393c8065180566bdf9b541eaed00e73bcfa1c..6aa85f3d44d2c43a89c6a7ef090a4250f3ef8861 100644 --- a/src/app/currentconversation.h +++ b/src/app/currentconversation.h @@ -40,6 +40,7 @@ class CurrentConversation final : public QObject QML_PROPERTY(bool, readOnly) QML_PROPERTY(bool, needsSyncing) QML_PROPERTY(bool, isSip) + QML_PROPERTY(bool, isBanned) QML_PROPERTY(QString, callId) QML_PROPERTY(QString, color) QML_PROPERTY(call::Status, callState) diff --git a/src/app/mainview/components/ChatView.qml b/src/app/mainview/components/ChatView.qml index e3777b146fb0682982f892555ae97d4da853ce75..0dfc8f6d806024feeb9708bcad08c733f31df954 100644 --- a/src/app/mainview/components/ChatView.qml +++ b/src/app/mainview/components/ChatView.qml @@ -171,7 +171,9 @@ Rectangle { id: chatViewFooter visible: { - if (CurrentConversation.needsSyncing || CurrentConversation.readOnly) + if (CurrentConversation.isBlocked) + return false + else if (CurrentConversation.needsSyncing || CurrentConversation.readOnly) return false else if (CurrentConversation.isSwarm && CurrentConversation.isRequest) return false diff --git a/src/app/mainview/components/ChatViewHeader.qml b/src/app/mainview/components/ChatViewHeader.qml index 3ad3baacab595e37c20902f7694a439996bea285..8f80044d47f1dd860b00049b43c2d16785112496 100644 --- a/src/app/mainview/components/ChatViewHeader.qml +++ b/src/app/mainview/components/ChatViewHeader.qml @@ -209,7 +209,7 @@ Rectangle { PushButton { id: sendContactRequestButton - visible: CurrentConversation.isTemporary + visible: CurrentConversation.isTemporary || CurrentConversation.isBanned source: JamiResources.add_people_24dp_svg toolTipText: JamiStrings.addToConversations @@ -217,7 +217,9 @@ Rectangle { normalColor: JamiTheme.chatviewBgColor imageColor: JamiTheme.chatviewButtonColor - onClicked: MessagesAdapter.sendConversationRequest() + onClicked: CurrentConversation.isBanned ? + MessagesAdapter.unbanConversation(CurrentConversation.id) + : MessagesAdapter.sendConversationRequest() } PushButton { diff --git a/src/app/mainview/components/ConversationListView.qml b/src/app/mainview/components/ConversationListView.qml index e1f523ab9834464eeb8fbad4105cff59ff703908..931205b27c838f63e5dd66125ca1a52837899886 100644 --- a/src/app/mainview/components/ConversationListView.qml +++ b/src/app/mainview/components/ConversationListView.qml @@ -128,6 +128,7 @@ JamiListView { "title": model.dataForRow(row, ConversationList.Title), "uri": model.dataForRow(row, ConversationList.URI), "isSwarm": model.dataForRow(row, ConversationList.IsSwarm), + "isBanned": model.dataForRow(row, ConversationList.IsBanned), "mode": model.dataForRow(row, ConversationList.Mode), "readOnly": model.dataForRow(row, ConversationList.ReadOnly) } @@ -135,6 +136,7 @@ JamiListView { responsibleAccountId = LRCInstance.currentAccountId responsibleConvUid = item.convId isSwarm = item.isSwarm + isBanned = item.isBanned mode = item.mode contactType = LRCInstance.currentAccountType readOnly = item.readOnly diff --git a/src/app/mainview/components/ConversationSmartListContextMenu.qml b/src/app/mainview/components/ConversationSmartListContextMenu.qml index 042871dff756571386bfcfc7342c4deabb3b0da0..e92bd4c6ce17d304cdca20c5a36a97abe4cbebeb 100644 --- a/src/app/mainview/components/ConversationSmartListContextMenu.qml +++ b/src/app/mainview/components/ConversationSmartListContextMenu.qml @@ -30,6 +30,7 @@ ContextMenuAutoLoader { property string responsibleAccountId: "" property string responsibleConvUid: "" + property bool isBanned: false property bool isSwarm: false property var mode: undefined property int contactType: Profile.Type.INVALID @@ -70,7 +71,7 @@ ContextMenuAutoLoader { GeneralMenuItem { id: clearConversation - canTrigger: !isSwarm && !hasCall + canTrigger: !isSwarm && !hasCall && !root.isBanned itemName: JamiStrings.clearConversation iconSource: JamiResources.ic_clear_24dp_svg onClicked: MessagesAdapter.clearConversationHistory( @@ -80,7 +81,7 @@ ContextMenuAutoLoader { GeneralMenuItem { id: removeContact - canTrigger: !hasCall + canTrigger: !hasCall && !root.isBanned itemName: { if (mode !== Conversation.Mode.ONE_TO_ONE && mode !== Conversation.Mode.NON_SWARM) return JamiStrings.removeConversation @@ -126,12 +127,21 @@ ContextMenuAutoLoader { GeneralMenuItem { id: blockContact - canTrigger: !hasCall && contactType !== Profile.Type.SIP + canTrigger: !hasCall && contactType !== Profile.Type.SIP && !root.isBanned itemName: !(mode && mode !== Conversation.Mode.ONE_TO_ONE && mode !== Conversation.Mode.NON_SWARM) ? JamiStrings.blockContact : JamiStrings.blockSwarm iconSource: JamiResources.block_black_24dp_svg addMenuSeparatorAfter: contactType !== Profile.Type.SIP onClicked: MessagesAdapter.blockConversation(responsibleConvUid) }, + GeneralMenuItem { + id: unblockContact + + canTrigger: root.isBanned + itemName: JamiStrings.reinstateContact + iconSource: JamiResources.round_remove_circle_24dp_svg + addMenuSeparatorAfter: contactType !== Profile.Type.SIP + onClicked: MessagesAdapter.unbanConversation(responsibleConvUid) + }, GeneralMenuItem { id: contactDetails diff --git a/src/app/mainview/components/SmartListItemDelegate.qml b/src/app/mainview/components/SmartListItemDelegate.qml index 580f15c222b0dfd804d057c529fbcb85d78e3365..c5f11d6121bf5e2967a5d47cf42aeb31be3e0a1f 100644 --- a/src/app/mainview/components/SmartListItemDelegate.qml +++ b/src/app/mainview/components/SmartListItemDelegate.qml @@ -116,6 +116,7 @@ ItemDelegate { } RowLayout { visible: ContactType !== Profile.Type.TEMPORARY + && !IsBanned && LastInteractionDate !== undefined && interactive Layout.fillWidth: true @@ -151,6 +152,16 @@ ItemDelegate { lineHeight: font.family === "Segoe UI Emoji" ? 1.25 : 1 } } + Text { + Layout.fillWidth: true + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignVCenter + text: JamiStrings.banned + visible: IsBanned + font.pointSize: JamiTheme.smartlistItemFontSize + font.weight: Font.Bold + color: JamiTheme.textColor + } } // read-only conversation indicator diff --git a/src/app/messagesadapter.cpp b/src/app/messagesadapter.cpp index ddea15a6283345719a744349908197d0a3a1a8d6..3ba15c1c7e1f4d90c45850008ccaa18837ad33f4 100644 --- a/src/app/messagesadapter.cpp +++ b/src/app/messagesadapter.cpp @@ -368,6 +368,19 @@ MessagesAdapter::unbanContact(int index) } } +void +MessagesAdapter::unbanConversation(const QString& convUid) +{ + auto& accInfo = lrcInstance_->getCurrentAccountInfo(); + try { + const auto contactUri = accInfo.conversationModel->peersForConversation(convUid).at(0); + auto contactInfo = accInfo.contactModel->getContact(contactUri); + accInfo.contactModel->addContact(contactInfo); + } catch (const std::out_of_range& e) { + qDebug() << e.what(); + } +} + void MessagesAdapter::clearConversationHistory(const QString& accountId, const QString& convUid) { diff --git a/src/app/messagesadapter.h b/src/app/messagesadapter.h index d51ecacf2f4b9c7c0633dc1cf7381c30c8f06e68..3d28af31628d34c21e4cae28369170df3b44e9ff 100644 --- a/src/app/messagesadapter.h +++ b/src/app/messagesadapter.h @@ -90,6 +90,7 @@ protected: Q_INVOKABLE void refuseInvitation(const QString& convUid = ""); Q_INVOKABLE void blockConversation(const QString& convUid = ""); Q_INVOKABLE void unbanContact(int index); + Q_INVOKABLE void unbanConversation(const QString& convUid); Q_INVOKABLE void sendMessage(const QString& message); Q_INVOKABLE void sendFile(const QString& message); Q_INVOKABLE void acceptFile(const QString& arg); diff --git a/src/libclient/contactmodel.cpp b/src/libclient/contactmodel.cpp index 63e4825db36d951177f5fd57f3e05f7cd4df4385..425b97057e8a6f5e7cab1c882f53424c7ad7e302 100644 --- a/src/libclient/contactmodel.cpp +++ b/src/libclient/contactmodel.cpp @@ -344,6 +344,15 @@ ContactModel::addToContacts(const QString& contactUri) void ContactModel::removeContact(const QString& contactUri, bool banned) { + try { + const auto& contact = getContact(contactUri); + if (contact.isBanned) { + qWarning() << "Contact already banned"; + return; + } + } catch (...) { + } + bool emitContactRemoved = false; { std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_); @@ -872,8 +881,9 @@ ContactModelPimpl::slotContactRemoved(const QString& accountId, std::lock_guard<std::mutex> lk(contactsMtx_); auto contact = contacts.find(contactUri); - if (contact == contacts.end()) + if (contact == contacts.end()) { return; + } if (contact->profileInfo.type == profile::Type::PENDING) { Q_EMIT behaviorController.trustRequestTreated(linked.owner.id, contactUri); @@ -911,8 +921,9 @@ ContactModelPimpl::slotContactRemoved(const QString& accountId, linked.owner.conversationModel->refreshFilter(); if (banned) { Q_EMIT linked.bannedStatusChanged(contactUri, true); + } else { + Q_EMIT linked.contactRemoved(contactUri); } - Q_EMIT linked.contactRemoved(contactUri); } void diff --git a/src/libclient/conversationmodel.cpp b/src/libclient/conversationmodel.cpp index 6cf8654646e00247f0947a096aeb7f4fc2f64d05..6f0029066570fd475bae62f200ba5aa28ac64440 100644 --- a/src/libclient/conversationmodel.cpp +++ b/src/libclient/conversationmodel.cpp @@ -814,20 +814,14 @@ ConversationModel::removeConversation(const QString& uid, bool banned) "participant"; return; } - if (conversation.isSwarm()) { + if (!conversation.isCoreDialog()) { if (conversation.isRequest) ConfigurationManager::instance().declineConversationRequest(owner.id, uid); else ConfigurationManager::instance().removeConversation(owner.id, uid); - - // Still some other conversation, do nothing else - if (!banned) - return; + } else { + owner.contactModel->removeContact(peers.front(), banned); } - - if (!conversation.isCoreDialog()) - return; - owner.contactModel->removeContact(peers.front(), banned); } void @@ -2575,21 +2569,33 @@ ConversationModelPimpl::slotConversationRemoved(const QString& accountId, auto& conversation = getConversationForUid(conversationId).get(); auto& peers = peersForConversation(conversation); + if (peers.isEmpty()) { + removeConversation(); + return; + } + auto contactUri = peers.first(); + contact::Info contact; + try { + contact = linked.owner.contactModel->getContact(contactUri); + } catch (...) { + } if (conversation.mode == conversation::Mode::ONE_TO_ONE) { - if (peers.isEmpty()) { - removeConversation(); - return; - } - - auto contactUri = peers.first(); removeConversation(); // If it's a 1:1 conversation and we don't have any more conversation // we can remove the contact auto conv = storage::getConversationsWithPeer(db, contactUri); if (conv.empty()) - linked.owner.contactModel->removeContact(contactUri, true); + linked.owner.contactModel->removeContact(contactUri, false); + + if (contact.isBanned && conv.empty()) { + // Add 1:1 conv for banned + auto c = storage::beginConversationWithPeer(db, contactUri); + addConversationWith(c, contactUri, false); + Q_EMIT linked.conversationReady(c, contactUri); + Q_EMIT linked.newConversation(c); + } } else { removeConversation(); }