1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-04-21 21:52:03 +02:00

swarmdetailspanel: show kicked contacts if administrator

This patch avoid for non-admins to try to re-add kicked members
as filtered out from the list. However kicked members are only
visible for administrators.

Change-Id: Ie01b7071c072d147bbc0f39e477cc24d7fd58b1a
This commit is contained in:
Sébastien Blin 2023-01-05 16:08:44 -05:00
parent 06ab19f213
commit 3570b23d8a
16 changed files with 216 additions and 44 deletions

View file

@ -215,6 +215,7 @@ set(COMMON_SOURCES
${APP_SRC_DIR}/wizardviewstepmodel.cpp
${APP_SRC_DIR}/avatarregistry.cpp
${APP_SRC_DIR}/currentconversation.cpp
${APP_SRC_DIR}/currentconversationmembers.cpp
${APP_SRC_DIR}/currentaccount.cpp
${APP_SRC_DIR}/videodevices.cpp
${APP_SRC_DIR}/videoprovider.cpp
@ -275,6 +276,7 @@ set(COMMON_HEADERS
${APP_SRC_DIR}/wizardviewstepmodel.h
${APP_SRC_DIR}/avatarregistry.h
${APP_SRC_DIR}/currentconversation.h
${APP_SRC_DIR}/currentconversationmembers.h
${APP_SRC_DIR}/currentaccount.h
${APP_SRC_DIR}/videodevices.h
${APP_SRC_DIR}/videoprovider.h

2
daemon

@ -1 +1 @@
Subproject commit 47f3fd14ab0532ff94fd5317f885689928d9293b
Subproject commit 3481da56c29e378bf3003d067c06e409d5c52c32

View file

@ -828,6 +828,7 @@ Item {
property string goToConversation: qsTr("Go to conversation")
property string promoteAdministrator: qsTr("Promote to administrator")
property string kickMember: qsTr("Kick member")
property string reinstateMember: qsTr("Reinstate member")
property string administrator: qsTr("Administrator")
property string invited: qsTr("Invited")
property string removeMember: qsTr("Remove member")

View file

@ -24,6 +24,7 @@ CurrentConversation::CurrentConversation(LRCInstance* lrcInstance, QObject* pare
: QObject(parent)
, lrcInstance_(lrcInstance)
{
uris_ = new CurrentConversationMembers(lrcInstance, this);
// whenever the account changes, reconnect the new conversation model
// for updates to the conversation and call state/id
connect(lrcInstance_,
@ -59,7 +60,23 @@ CurrentConversation::updateData()
if (auto optConv = accInfo.conversationModel->getConversationForUid(convId)) {
auto& convInfo = optConv->get();
set_lastSelfMessageId(convInfo.lastSelfMessageId);
set_uris(convInfo.participantsUris());
QStringList uris, bannedUris;
auto isAdmin = false;
for (const auto& p : convInfo.participants) {
if (p.uri == accInfo.profileInfo.uri) {
isAdmin = p.role == member::Role::ADMIN;
}
if (p.role == member::Role::BANNED) {
bannedUris.push_back(p.uri);
} else {
uris.push_back(p.uri);
}
}
if (isAdmin) {
for (const auto& banned : bannedUris)
uris.push_back(banned);
}
uris_->setMembers(accountId, convId, uris);
set_isSwarm(convInfo.isSwarm());
set_isLegacy(convInfo.isLegacy());
set_isCoreDialog(convInfo.isCoreDialog());
@ -170,6 +187,12 @@ CurrentConversation::setInfo(const QString& key, const QString& value)
accInfo.conversationModel->updateConversationInfos(convId, infos);
}
CurrentConversationMembers*
CurrentConversation::uris() const
{
return uris_;
}
void
CurrentConversation::onConversationUpdated(const QString& convId)
{

View file

@ -19,6 +19,7 @@
#pragma once
#include "lrcinstance.h"
#include "currentconversationmembers.h"
#include <QObject>
#include <QString>
@ -32,7 +33,6 @@ class CurrentConversation final : public QObject
QML_PROPERTY(QString, id)
QML_PROPERTY(QString, title)
QML_PROPERTY(QString, description)
QML_PROPERTY(QStringList, uris)
QML_PROPERTY(bool, isSwarm)
QML_PROPERTY(bool, isLegacy)
QML_PROPERTY(bool, isCoreDialog)
@ -66,6 +66,7 @@ public:
Q_INVOKABLE QString getPreference(const QString& key) const;
Q_INVOKABLE MapStringString getPreferences() const;
Q_INVOKABLE void setInfo(const QString& key, const QString& value);
CurrentConversationMembers* uris() const;
Q_SIGNALS:
void scrollTo(const QString& msgId);
@ -87,6 +88,7 @@ Q_SIGNALS:
private:
LRCInstance* lrcInstance_;
CurrentConversationMembers* uris_;
void connectModel();
};

View file

@ -0,0 +1,76 @@
/*
* Copyright (C) 2023 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "currentconversationmembers.h"
#include <algorithm>
#include <random>
CurrentConversationMembers::CurrentConversationMembers(LRCInstance* lrcInstance, QObject* parent)
: QAbstractListModel(parent)
, lrcInstance_(lrcInstance)
{}
int
CurrentConversationMembers::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return members_.size();
}
void
CurrentConversationMembers::setMembers(const QString& accountId,
const QString& convId,
const QStringList& members)
{
beginResetModel();
accountId_ = accountId;
convId_ = convId;
members_ = members;
set_count(members.size());
endResetModel();
}
QVariant
CurrentConversationMembers::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
auto member = members_.at(index.row());
switch (role) {
case Members::Role::MemberUri:
return QVariant::fromValue(member);
case Members::Role::MemberRole:
return QVariant::fromValue(
lrcInstance_->getAccountInfo(accountId_).conversationModel->memberRole(convId_, member));
}
return QVariant();
}
QHash<int, QByteArray>
CurrentConversationMembers::roleNames() const
{
using namespace Members;
QHash<int, QByteArray> roles;
#define X(role) roles[role] = #role;
MEMBERS_ROLES
#undef X
return roles;
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (C) 2023 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "lrcinstance.h"
#include "appsettingsmanager.h"
#include "qtutils.h"
#include <QAbstractListModel>
#include <QObject>
#define MEMBERS_ROLES \
X(MemberUri) \
X(MemberRole)
namespace Members {
Q_NAMESPACE
enum Role {
DummyRole = Qt::UserRole + 1,
#define X(role) role,
MEMBERS_ROLES
#undef X
};
Q_ENUM_NS(Role)
} // namespace Members
class CurrentConversationMembers : public QAbstractListModel
{
Q_OBJECT
QML_PROPERTY(int, count)
public:
explicit CurrentConversationMembers(LRCInstance* lrcInstance, QObject* parent = nullptr);
void setMembers(const QString& accountId, const QString& convId, const QStringList& members);
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
private:
LRCInstance* lrcInstance_;
QString accountId_;
QString convId_;
QStringList members_;
};

View file

@ -63,10 +63,9 @@ Rectangle {
model: ContactAdapter.getContactSelectableModel(type)
Connections {
enabled: visible
target: CurrentConversation
target: CurrentConversationMembers
function onUrisChanged(uris) {
function onCountChanged() {
contactPickerListView.model = ContactAdapter.getContactSelectableModel(type)
}
}

View file

@ -157,13 +157,6 @@ Rectangle {
Connections {
target: CurrentConversation
function onUrisChanged(uris) {
if (CurrentConversation.uris.length >= 8 && addMemberPanel.visible) {
swarmDetailsPanel.visible = false
addMemberPanel.visible = !addMemberPanel.visible
}
}
function onNeedsHost() {
viewCoordinator.presentDialog(
appWindow,
@ -171,6 +164,17 @@ Rectangle {
}
}
Connections {
target: CurrentConversationMembers
function onCountChanged() {
if (CurrentConversationMembers.count >= 8 && addMemberPanel.visible) {
swarmDetailsPanel.visible = false
addMemberPanel.visible = !addMemberPanel.visible
}
}
}
onAddToConversationClicked: {
swarmDetailsPanel.visible = false
if (addMemberPanel.visible) {

View file

@ -208,7 +208,7 @@ Rectangle {
normalColor: JamiTheme.chatviewBgColor
imageColor: JamiTheme.chatviewButtonColor
visible: CurrentConversation.uris.length < 8 && addMemberVisibility
visible: CurrentConversationMembers.count < 8 && addMemberVisibility
onClicked: addToConversationClicked()
}

View file

@ -60,7 +60,7 @@ Popup {
if (isCall) {
pluginhandlerPickerListView.model = PluginAdapter.getMediaHandlerSelectableModel(CurrentCall.id)
} else {
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversation.uris[0]
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversationMembers[0]
pluginhandlerPickerListView.model = PluginAdapter.getChatHandlerSelectableModel(LRCInstance.currentAccountId, peerId)
}
}
@ -72,7 +72,7 @@ Popup {
pluginhandlerPickerListView.model = PluginAdapter.getMediaHandlerSelectableModel(CurrentCall.id)
} else {
var accountId = LRCInstance.currentAccountId
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversation.uris[0]
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversationMembers[0]
PluginModel.toggleChatHandler(handlerId, accountId, peerId, !isLoaded)
pluginhandlerPickerListView.model = PluginAdapter.getChatHandlerSelectableModel(accountId, peerId)
}
@ -127,7 +127,7 @@ Popup {
if (isCall) {
return PluginAdapter.getMediaHandlerSelectableModel(CurrentCall.id)
} else {
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversation.uris[0]
var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversationMembers[0]
return PluginAdapter.getChatHandlerSelectableModel(LRCInstance.currentAccountId, peerId)
}
}

View file

@ -238,7 +238,7 @@ Rectangle {
down: tabBar.currentIndex === 1
labelText: {
var membersNb = CurrentConversation.uris.length;
var membersNb = CurrentConversationMembers.count;
if (membersNb > 1)
return JamiStrings.members.arg(membersNb)
return JamiStrings.member
@ -605,7 +605,7 @@ Rectangle {
}
}
model: CurrentConversation.uris
model: CurrentConversationMembers
delegate: ItemDelegate {
id: member
@ -626,11 +626,11 @@ Rectangle {
id: memberMouseArea
anchors.fill: parent
enabled: modelData !== CurrentAccount.uri
enabled: MemberUri !== CurrentAccount.uri
acceptedButtons: Qt.RightButton
onClicked: function (mouse) {
var position = mapToItem(members, mouse.x, mouse.y)
contextMenu.openMenuAt(position.x, position.y, modelData)
contextMenu.openMenuAt(position.x, position.y, MemberUri)
}
}
@ -640,19 +640,17 @@ Rectangle {
anchors.rightMargin: JamiTheme.preferredMarginSize
Avatar {
id: avatar
width: JamiTheme.smartListAvatarSize
height: JamiTheme.smartListAvatarSize
Layout.leftMargin: JamiTheme.preferredMarginSize
Layout.topMargin: JamiTheme.preferredMarginSize / 2
z: -index
opacity: {
var role = UtilsAdapter.getParticipantRole(CurrentAccount.id, CurrentConversation.id, modelData)
return role === Member.Role.INVITED ? 0.5 : 1
}
opacity: (MemberRole === Member.Role.INVITED || MemberRole === Member.Role.BANNED)? 0.5 : 1
imageId: CurrentAccount.uri == modelData ? CurrentAccount.id : modelData
showPresenceIndicator: UtilsAdapter.getContactPresence(CurrentAccount.id, modelData)
mode: CurrentAccount.uri == modelData ? Avatar.Mode.Account : Avatar.Mode.Contact
imageId: CurrentAccount.uri == MemberUri ? CurrentAccount.id : MemberUri
showPresenceIndicator: UtilsAdapter.getContactPresence(CurrentAccount.id, MemberUri)
mode: CurrentAccount.uri == MemberUri ? Avatar.Mode.Account : Avatar.Mode.Contact
}
ElidedTextLabel {
@ -662,16 +660,12 @@ Rectangle {
Layout.topMargin: JamiTheme.preferredMarginSize / 2
Layout.fillWidth: true
eText: UtilsAdapter.getContactBestName(CurrentAccount.id, modelData)
eText: UtilsAdapter.getContactBestName(CurrentAccount.id, MemberUri)
maxWidth: width
font.pointSize: JamiTheme.participantFontSize
color: JamiTheme.primaryForegroundColor
opacity: {
var role = UtilsAdapter.getParticipantRole(CurrentAccount.id, CurrentConversation.id, modelData)
return role === Member.Role.INVITED ? 0.5 : 1
}
opacity: (MemberRole === Member.Role.INVITED || MemberRole === Member.Role.BANNED)? 0.5 : 1
font.kerning: true
verticalAlignment: Text.AlignVCenter
@ -682,27 +676,25 @@ Rectangle {
}
ElidedTextLabel {
id: role
id: roleLabel
Layout.preferredHeight: JamiTheme.preferredFieldHeight
Layout.topMargin: JamiTheme.preferredMarginSize / 2
eText: {
var role = UtilsAdapter.getParticipantRole(CurrentAccount.id, CurrentConversation.id, modelData)
if (role === Member.Role.ADMIN)
if (MemberRole === Member.Role.ADMIN)
return JamiStrings.administrator
if (role === Member.Role.INVITED)
if (MemberRole === Member.Role.INVITED)
return JamiStrings.invited
if (MemberRole === Member.Role.BANNED)
return JamiStrings.banned
return ""
}
maxWidth: JamiTheme.preferredFieldWidth
font.pointSize: JamiTheme.participantFontSize
color: JamiTheme.textColorHovered
opacity: {
var role = UtilsAdapter.getParticipantRole(CurrentAccount.id, CurrentConversation.id, modelData)
return role === Member.Role.INVITED ? 0.5 : 1
}
opacity: (MemberRole === Member.Role.INVITED || MemberRole === Member.Role.BANNED)? 0.5 : 1
font.kerning: true
horizontalAlignment: Text.AlignRight

View file

@ -74,14 +74,19 @@ ContextMenuAutoLoader {
},
GeneralMenuItem {
id: kickMember
itemName: JamiStrings.kickMember
property var memberRole: UtilsAdapter.getParticipantRole(CurrentAccount.id, conversationId, participantUri)
itemName: memberRole === Member.Role.BANNED ? JamiStrings.reinstateMember : JamiStrings.kickMember
iconSource: JamiResources.kick_member_svg
canTrigger: role === Member.Role.ADMIN
onClicked: {
if (memberRole === Member.Role.BANNED) {
MessagesAdapter.addConversationMember(conversationId, participantUri)
} else {
MessagesAdapter.removeConversationMember(conversationId, participantUri)
}
}
}
]
Component.onCompleted: menuItemsToLoad = menuItems

View file

@ -516,6 +516,13 @@ MessagesAdapter::removeConversationMember(const QString& convUid, const QString&
accInfo.conversationModel->removeConversationMember(convUid, memberUri);
}
void
MessagesAdapter::addConversationMember(const QString& convUid, const QString& memberUri)
{
auto& accInfo = lrcInstance_->getCurrentAccountInfo();
accInfo.conversationModel->addConversationMember(convUid, memberUri);
}
void
MessagesAdapter::removeContact(const QString& convUid, bool banContact)
{

View file

@ -85,6 +85,7 @@ protected:
Q_INVOKABLE void connectConversationModel();
Q_INVOKABLE void sendConversationRequest();
Q_INVOKABLE void removeConversation(const QString& convUid);
Q_INVOKABLE void addConversationMember(const QString& convUid, const QString& participantUri);
Q_INVOKABLE void removeConversationMember(const QString& convUid, const QString& participantUri);
Q_INVOKABLE void removeContact(const QString& convUid, bool banContact = false);
Q_INVOKABLE void clearConversationHistory(const QString& accountId, const QString& convUid);

View file

@ -141,6 +141,7 @@ registerTypes(QQmlEngine* engine,
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, pluginAdapter, "PluginAdapter");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentCall, "CurrentCall");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentConversation, "CurrentConversation");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentConversation->uris(), "CurrentConversationMembers");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentAccount, "CurrentAccount");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, videoDevices, "VideoDevices");
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentAccountToMigrate, "CurrentAccountToMigrate")