- Manage participants inside group chat room

- Wires Set/Unset Admin to model function
- Fixed "is(not) now admin" messages inversion
- Fixed event log list not updating (building ID from timestamp and type)
- Updated text to reflect that a participant can no longer be in a conversation without an explicit "leaving" action from him (admin removed for example)
This commit is contained in:
Christophe Deschamps 2025-06-19 07:48:45 +02:00
parent 279ac22463
commit 0470988c32
13 changed files with 236 additions and 52 deletions

View file

@ -176,7 +176,6 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
if (event->isHandled()) {
mChatModelConnection->invokeToCore([this, event]() {
appendEventLogToEventLogList(event);
emit lUpdateUnreadCount();
emit lUpdateLastUpdatedTime();
});
}
@ -187,7 +186,7 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
&ChatModel::chatMessagesReceived, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::list<std::shared_ptr<linphone::EventLog>> &eventsLog) {
if (mChatModel->getMonitor() != chatRoom) return;
qDebug() << "EVENT LOGS RECEIVED IN CHATROOM" << mChatModel->getTitle();
qDebug() << "CHAT MESSAGE RECEIVED IN CHATROOM" << mChatModel->getTitle();
QList<QSharedPointer<EventLogCore>> list;
for (auto &e : eventsLog) {
auto event = EventLogCore::create(e);
@ -326,6 +325,15 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
mChatModelConnection->makeConnectToCore(&ChatCore::lRemoveParticipantAtIndex, [this](int index) {
mChatModelConnection->invokeToModel([this, index]() { mChatModel->removeParticipantAtIndex(index); });
});
mChatModelConnection->makeConnectToCore(&ChatCore::lSetParticipantsAddresses, [this](QStringList addresses) {
mChatModelConnection->invokeToModel([this, addresses]() { mChatModel->setParticipantAddresses(addresses); });
});
mChatModelConnection->makeConnectToCore(&ChatCore::lToggleParticipantAdminStatusAtIndex, [this](int index) {
mChatModelConnection->invokeToModel(
[this, index]() { mChatModel->toggleParticipantAdminStatusAtIndex(index); });
});
}
QDateTime ChatCore::getLastUpdatedTime() const {

View file

@ -60,6 +60,8 @@ public:
Q_PROPERTY(bool muted READ isMuted WRITE lSetMuted NOTIFY mutedChanged)
Q_PROPERTY(bool meAdmin READ getMeAdmin WRITE setMeAdmin NOTIFY meAdminChanged)
Q_PROPERTY(QVariantList participants READ getParticipantsGui NOTIFY participantsChanged)
Q_PROPERTY(QStringList participantsAddresses READ getParticipantsAddresses WRITE lSetParticipantsAddresses NOTIFY
participantsChanged)
// Should be call from model Thread. Will be automatically in App thread after initialization
static QSharedPointer<ChatCore> create(const std::shared_ptr<linphone::ChatRoom> &chatRoom);
@ -128,8 +130,7 @@ public:
QList<QSharedPointer<ParticipantCore>> buildParticipants(const std::shared_ptr<linphone::ChatRoom> &chatRoom) const;
QVariantList getParticipantsGui() const;
Q_INVOKABLE QStringList getParticipantsAddresses() const;
QStringList getParticipantsAddresses() const;
signals:
// used to close all the notifications when one is clicked
@ -168,6 +169,8 @@ signals:
void lEnableEphemeral(bool enable);
void lSetSubject(QString subject);
void lRemoveParticipantAtIndex(int index);
void lSetParticipantsAddresses(QStringList addresses);
void lToggleParticipantAdminStatusAtIndex(int index);
private:
QString id;

View file

@ -39,12 +39,15 @@ EventLogCore::EventLogCore(const std::shared_ptr<const linphone::EventLog> &even
mTimestamp = QDateTime::fromMSecsSinceEpoch(eventLog->getCreationTime() * 1000);
if (eventLog->getChatMessage()) {
mChatMessageCore = ChatMessageCore::create(eventLog->getChatMessage());
mEventId = eventLog->getChatMessage()->getMessageId();
mEventId = Utils::coreStringToAppString(eventLog->getChatMessage()->getMessageId());
} else if (eventLog->getCallLog()) {
mCallHistoryCore = CallHistoryCore::create(eventLog->getCallLog());
mEventId = eventLog->getCallLog()->getCallId();
} else {
mEventId = eventLog->getNotifyId();
mEventId = Utils::coreStringToAppString(eventLog->getCallLog()->getCallId());
} else { // getNotifyId
QString type = QString::fromLatin1(
QMetaEnum::fromType<LinphoneEnums::EventLogType>().valueToKey(static_cast<int>(mEventLogType)));
mEventId = type + QString::number(static_cast<qint64>(eventLog->getCreationTime()));
;
computeEvent(eventLog);
}
}
@ -55,7 +58,7 @@ EventLogCore::~EventLogCore() {
void EventLogCore::setSelf(QSharedPointer<EventLogCore> me) {
}
std::string EventLogCore::getEventLogId() {
QString EventLogCore::getEventLogId() {
return mEventId;
}
@ -127,11 +130,11 @@ void EventLogCore::computeEvent(const std::shared_ptr<const linphone::EventLog>
break;
case linphone::EventLog::Type::ConferenceParticipantSetAdmin:
mEventDetails =
tr("conference_participant_unset_admin_event").arg(ToolModel::getDisplayName(participantAddress));
tr("conference_participant_set_admin_event").arg(ToolModel::getDisplayName(participantAddress));
break;
case linphone::EventLog::Type::ConferenceParticipantUnsetAdmin:
mEventDetails =
tr("conference_participant_set_admin_event").arg(ToolModel::getDisplayName(participantAddress));
tr("conference_participant_unset_admin_event").arg(ToolModel::getDisplayName(participantAddress));
break;
default:
mHandled = false;

View file

@ -53,7 +53,7 @@ public:
EventLogCore(const std::shared_ptr<const linphone::EventLog> &eventLog);
~EventLogCore();
void setSelf(QSharedPointer<EventLogCore> me);
std::string getEventLogId();
QString getEventLogId();
QSharedPointer<ChatMessageCore> getChatMessageCore();
QSharedPointer<CallHistoryCore> getCallHistoryCore();
bool isHandled() const {
@ -62,7 +62,7 @@ public:
private:
DECLARE_ABSTRACT_OBJECT
std::string mEventId;
QString mEventId;
QSharedPointer<ChatMessageCore> mChatMessageCore = nullptr;
QSharedPointer<CallHistoryCore> mCallHistoryCore = nullptr;

View file

@ -3009,9 +3009,9 @@ Error</extracomment>
</message>
<message>
<location filename="../../view/Page/Layout/Chat/GroupChatInfoParticipants.qml" line="194"/>
<source>group_infos_add_participants_title</source>
<extracomment>&quot;Ajouter des participants&quot;</extracomment>
<translation>Add Participants</translation>
<source>group_infos_manage_participants_title</source>
<extracomment>&quot;Gérer les participants&quot;</extracomment>
<translation>Manage Participants</translation>
</message>
<message>
<location filename="../../view/Page/Layout/Chat/GroupChatInfoParticipants.qml" line="111"/>
@ -3058,6 +3058,18 @@ Error</extracomment>
</context>
<context>
<name>GroupConversationInfos</name>
<message>
<location filename="../../view/Page/Layout/Chat/GroupConversationInfos.qml" line="129"/>
<source>group_infos_manage_participants</source>
<extracomment>Participants</extracomment>
<translation>Participants</translation>
</message>
<message>
<location filename="../../view/Page/Layout/Chat/GroupConversationInfos.qml" line="117"/>
<source>group_infos_participants_edit_apply</source>
<extracomment>Apply</extracomment>
<translation>Apply</translation>
</message>
<message>
<location filename="../../view/Page/Layout/Chat/GroupConversationInfos.qml" line="129"/>
<source>group_infos_call</source>
@ -3914,6 +3926,12 @@ Error</extracomment>
<extracomment>&quot;Ajouter des participants&quot;</extracomment>
<translation>Teilnehmer hinzufügen</translation>
</message>
<message>
<location filename="../../view/Page/Main/Meeting/MeetingPage.qml"/>
<source>meeting_schedule_add_participants_apply</source>
<extracomment>Apply</extracomment>
<translation>Apply</translation>
</message>
<message>
<location filename="../../view/Page/Main/Meeting/MeetingPage.qml" line="524"/>
<source>add</source>

View file

@ -2811,7 +2811,7 @@ Only your correspondent can decrypt them.</translation>
<message>
<location filename="../../core/chat/message/EventLogCore.cpp" line="97"/>
<source>conference_participant_removed_event</source>
<translation>%1 has left</translation>
<translation>%1 is no longer in the conversation</translation>
</message>
<message>
<location filename="../../core/chat/message/EventLogCore.cpp" line="134"/>
@ -2927,9 +2927,9 @@ Only your correspondent can decrypt them.</translation>
<name>GroupChatInfoParticipants</name>
<message>
<location filename="../../view/Page/Layout/Chat/GroupChatInfoParticipants.qml" line="194"/>
<source>group_infos_add_participants_title</source>
<extracomment>&quot;Ajouter des participants&quot;</extracomment>
<translation>Add Participants</translation>
<source>group_infos_manage_participants_title</source>
<extracomment>&quot;Gérer les participants&quot;</extracomment>
<translation>Manage participants</translation>
</message>
<message>
<location filename="../../view/Page/Layout/Chat/GroupChatInfoParticipants.qml" line="81"/>
@ -2981,6 +2981,18 @@ Only your correspondent can decrypt them.</translation>
</context>
<context>
<name>GroupConversationInfos</name>
<message>
<location filename="../../view/Page/Layout/Chat/GroupConversationInfos.qml" line="129"/>
<source>group_infos_manage_participants</source>
<extracomment>Participants</extracomment>
<translation>Participants</translation>
</message>
<message>
<location filename="../../view/Page/Layout/Chat/GroupConversationInfos.qml" line="117"/>
<source>group_infos_participants_edit_apply</source>
<extracomment>Apply</extracomment>
<translation>Apply</translation>
</message>
<message>
<location filename="../../view/Page/Layout/Chat/GroupConversationInfos.qml" line="129"/>
<source>group_infos_call</source>
@ -3832,6 +3844,12 @@ Only your correspondent can decrypt them.</translation>
<extracomment>&quot;Ajouter des participants&quot;</extracomment>
<translation>Add participants</translation>
</message>
<message>
<location filename="../../view/Page/Main/Meeting/MeetingPage.qml"/>
<source>meeting_schedule_add_participants_apply</source>
<extracomment>Apply</extracomment>
<translation>Apply</translation>
</message>
<message>
<location filename="../../view/Page/Main/Meeting/MeetingPage.qml" line="524"/>
<source>add</source>

View file

@ -2811,7 +2811,7 @@ en bout. Seul votre correspondant peut les déchiffrer.</translation>
<message>
<location filename="../../core/chat/message/EventLogCore.cpp" line="97"/>
<source>conference_participant_removed_event</source>
<translation>%1 a quitté le groupe</translation>
<translation>%1 ne fait plus partie du groupe</translation>
</message>
<message>
<location filename="../../core/chat/message/EventLogCore.cpp" line="106"/>
@ -2927,9 +2927,9 @@ en bout. Seul votre correspondant peut les déchiffrer.</translation>
<name>GroupChatInfoParticipants</name>
<message>
<location filename="../../view/Page/Layout/Chat/GroupChatInfoParticipants.qml" line="194"/>
<source>group_infos_add_participants_title</source>
<extracomment>&quot;Ajouter des participants&quot;</extracomment>
<translation>Ajouter des Participants</translation>
<source>group_infos_manage_participants_title</source>
<extracomment>&quot;Gérer les participants&quot;</extracomment>
<translation>Gérer les participants</translation>
</message>
<message>
<location filename="../../view/Page/Layout/Chat/GroupChatInfoParticipants.qml" line="81"/>
@ -2981,6 +2981,18 @@ en bout. Seul votre correspondant peut les déchiffrer.</translation>
</context>
<context>
<name>GroupConversationInfos</name>
<message>
<location filename="../../view/Page/Layout/Chat/GroupConversationInfos.qml" line="129"/>
<source>group_infos_manage_participants</source>
<extracomment>Participants</extracomment>
<translation>Participants</translation>
</message>
<message>
<location filename="../../view/Page/Layout/Chat/GroupConversationInfos.qml" line="117"/>
<source>group_infos_participants_edit_apply</source>
<extracomment>Apply</extracomment>
<translation>Appliquer</translation>
</message>
<message>
<location filename="../../view/Page/Layout/Chat/GroupConversationInfos.qml" line="129"/>
<source>group_infos_call</source>
@ -3832,6 +3844,12 @@ en bout. Seul votre correspondant peut les déchiffrer.</translation>
<extracomment>&quot;Ajouter des participants&quot;</extracomment>
<translation>Ajouter des participants</translation>
</message>
<message>
<location filename="../../view/Page/Main/Meeting/MeetingPage.qml"/>
<source>meeting_schedule_add_participants_apply</source>
<extracomment>Appliquer</extracomment>
<translation>Appliquer</translation>
</message>
<message>
<location filename="../../view/Page/Main/Meeting/MeetingPage.qml" line="524"/>
<source>add</source>

View file

@ -168,6 +168,24 @@ void ChatModel::removeParticipantAtIndex(int index) const {
mMonitor->removeParticipant(participant);
}
void ChatModel::toggleParticipantAdminStatusAtIndex(int index) const {
auto participant = *std::next(mMonitor->getParticipants().begin(), index);
mMonitor->setParticipantAdminStatus(participant, !participant->isAdmin());
}
void ChatModel::setParticipantAddresses(const QStringList &addresses) const {
QSet<QString> s{addresses.cbegin(), addresses.cend()};
for (auto p : mMonitor->getParticipants()) {
auto address = Utils::coreStringToAppString(p->getAddress()->asStringUriOnly());
if (s.contains(address)) s.remove(address);
else mMonitor->removeParticipant(p);
}
for (const auto &a : s) {
auto address = linphone::Factory::get()->createAddress(Utils::appStringToCoreString(a));
if (address) mMonitor->addParticipant(address);
}
}
//---------------------------------------------------------------//
void ChatModel::onIsComposingReceived(const std::shared_ptr<linphone::ChatRoom> &chatRoom,

View file

@ -61,6 +61,8 @@ public:
void enableEphemeral(bool enable);
void setSubject(QString subject) const;
void removeParticipantAtIndex(int index) const;
void setParticipantAddresses(const QStringList &addresses) const;
void toggleParticipantAdminStatusAtIndex(int index) const;
signals:
void historyDeleted();

View file

@ -44,17 +44,23 @@ FocusScope{
nextItemInFocusChain(false).forceActiveFocus()
}
}
header: Text {
header: ColumnLayout {
Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
visible: mainItem.selectedParticipantsCount > 0
//: "%n participant(s) sélectionné(s)"
text: qsTr("add_participant_selected_count", '0', mainItem.selectedParticipantsCount).arg(mainItem.selectedParticipantsCount)
maximumLineCount: 1
color: DefaultStyle.grey_1000
font {
pixelSize: Math.round(12 * DefaultStyle.dp)
weight: Math.round(300 * DefaultStyle.dp)
Text {
Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
visible: mainItem.selectedParticipantsCount > 0
//: "%n participant(s) sélectionné(s)"
text: qsTr("add_participant_selected_count", '0', mainItem.selectedParticipantsCount).arg(mainItem.selectedParticipantsCount)
maximumLineCount: 1
color: DefaultStyle.grey_1000
font {
pixelSize: Math.round(12 * DefaultStyle.dp)
weight: Math.round(300 * DefaultStyle.dp)
}
}
Item {
Layout.preferredHeight: Math.round(10 * DefaultStyle.dp)
}
}
delegate: FocusScope {

View file

@ -15,7 +15,7 @@ ColumnLayout {
property var title: String
property var participants
property var chatCore
signal addParticipantRequested()
signal manageParticipantsRequested()
function isGroupEditable() {
return chatCore && chatCore.meAdmin && !chatCore.isReadOnly
@ -33,7 +33,7 @@ ColumnLayout {
Layout.topMargin: Math.round(9 * DefaultStyle.dp)
color: DefaultStyle.grey_100
radius: Math.round(15 * DefaultStyle.dp)
height: contentColumn.implicitHeight
height: participants.length > 0 ? contentColumn.implicitHeight : Math.round(90 * DefaultStyle.dp)
ColumnLayout {
id: contentColumn
@ -41,8 +41,7 @@ ColumnLayout {
spacing: Math.round(16 * DefaultStyle.dp)
Item {
visible: participants.length > 0
Layout.preferredHeight: Math.round(1 * DefaultStyle.dp)
Layout.topMargin: Math.round(7 * DefaultStyle.dp)
}
Repeater {
@ -133,7 +132,7 @@ ColumnLayout {
icon.height: Math.round(32 * DefaultStyle.dp)
onClicked: {
detailOptions.close()
participantCore.isAdmin = !participantCore.isAdmin
mainItem.chatCore.lToggleParticipantAdminStatusAtIndex(index)
}
}
IconLabelButton {
@ -184,21 +183,21 @@ ColumnLayout {
}
MediumButton {
id: addParticipant
id: manageParticipants
visible: mainItem.isGroupEditable()
height: Math.round(40 * DefaultStyle.dp)
icon.source: AppIcons.plusCircle
icon.width: Math.round(16 * DefaultStyle.dp)
icon.height: Math.round(16 * DefaultStyle.dp)
//: "Ajouter des participants"
text: qsTr("group_infos_add_participants_title")
//: "Gérer des participants"
text: qsTr("group_infos_manage_participants_title")
style: ButtonStyle.secondary
onClicked: mainItem.addParticipantRequested()
onClicked: mainItem.manageParticipantsRequested()
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: Math.round(17 * DefaultStyle.dp)
}
Item {
visible: !addParticipant.visible
visible: !manageParticipants.visible
Layout.bottomMargin: Math.round(7 * DefaultStyle.dp)
}
}

View file

@ -14,6 +14,8 @@ ColumnLayout {
property ChatGui chatGui
property var chatCore: chatGui.core
property var parentView
property bool manageParticipants: false
spacing: 0
Avatar {
@ -140,17 +142,100 @@ ColumnLayout {
//: "Réunion"
label: qsTr("group_infos_meeting")
button.onClicked: {
UtilsCpp.getMainWindow().scheduleMeeting(mainItem.chatCore.title, mainItem.chatCore.getParticipantsAddresses())
UtilsCpp.getMainWindow().scheduleMeeting(mainItem.chatCore.title, mainItem.chatCore.participantsAddresses)
}
}
}
Rectangle {
visible: mainItem.manageParticipants
Layout.fillHeight: true
Layout.fillWidth: true
Layout.topMargin: Math.round(9 * DefaultStyle.dp)
Layout.leftMargin: Math.round(17 * DefaultStyle.dp)
Layout.rightMargin: Math.round(10 * DefaultStyle.dp)
color: DefaultStyle.grey_100
radius: Math.round(15 * DefaultStyle.dp)
height: participantAddColumn.implicitHeight
ColumnLayout {
id: participantAddColumn
anchors.fill: parent
anchors.leftMargin: Math.round(17 * DefaultStyle.dp)
anchors.rightMargin: Math.round(10 * DefaultStyle.dp)
anchors.topMargin: Math.round(17 * DefaultStyle.dp)
spacing: Math.round(5 * DefaultStyle.dp)
RowLayout {
id: manageParticipantsButtons
spacing: Math.round(10 * DefaultStyle.dp)
Button {
id: manageParticipantsBackButton
style: ButtonStyle.noBackgroundOrange
icon.source: AppIcons.leftArrow
icon.width: Math.round(20 * DefaultStyle.dp)
icon.height: Math.round(20 * DefaultStyle.dp)
onClicked: mainItem.manageParticipants = false
}
Text {
text: qsTr("group_infos_manage_participants")
color: DefaultStyle.main1_500_main
maximumLineCount: 1
font: Typography.h4
Layout.fillWidth: true
}
SmallButton {
enabled: manageParticipantsLayout.selectedParticipantsCount.length != 0
Layout.leftMargin: Math.round(11 * DefaultStyle.dp)
focus: enabled
style: ButtonStyle.main
text: qsTr("group_infos_participants_edit_apply")
KeyNavigation.left: manageParticipantsBackButton
KeyNavigation.down: manageParticipantsLayout
onClicked: {
mainItem.chatCore.participantsAddresses = manageParticipantsLayout.selectedParticipants
mainItem.manageParticipants = false
}
}
}
AddParticipantsForm {
id: manageParticipantsLayout
visible: manageParticipants
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: Math.round(9 * DefaultStyle.dp)
Layout.bottomMargin: Math.round(17 * DefaultStyle.dp)
Layout.alignment: Qt.AlignVCenter
selectedParticipants: mainItem.chatCore.participantsAddresses
focus: true
onVisibleChanged: {
if (visible)
selectedParticipants = mainItem.chatCore.participantsAddresses
}
}
Item {
Layout.fillHeight: true
}
}
}
Component {
id: participantList
GroupChatInfoParticipants {
Layout.fillWidth: true
title: qsTr("group_infos_participants").arg(mainItem.chatCore.participants.length)
participants: mainItem.chatCore.participants
chatCore: mainItem.chatCore
onManageParticipantsRequested: mainItem.manageParticipants = true
}
}
ScrollView {
id: scrollView
visible: !mainItem.manageParticipants
Layout.fillHeight: true
Layout.fillWidth: true
Layout.topMargin: Math.round(30 * DefaultStyle.dp)
clip: true
Layout.leftMargin: Math.round(15 * DefaultStyle.dp)
Layout.rightMargin: Math.round(15 * DefaultStyle.dp)
@ -158,12 +243,18 @@ ColumnLayout {
ColumnLayout {
spacing: 0
width: scrollView.width
GroupChatInfoParticipants {
Loader {
id: participantLoader
Layout.fillWidth: true
title: qsTr("group_infos_participants").arg(mainItem.chatCore.participants.length)
participants: mainItem.chatCore.participants
chatCore: mainItem.chatCore
sourceComponent: participantList
Connections {
target: mainItem.chatCore
onParticipantsChanged : { // hacky reload to update intric height
participantLoader.active = false
participantLoader.active = true
}
}
}
ChatInfoActionsGroup {

View file

@ -532,7 +532,7 @@ AbstractMainPage {
Layout.leftMargin: Math.round(11 * DefaultStyle.dp)
focus: enabled
style: ButtonStyle.main
text: qsTr("add")
text: qsTr("meeting_schedule_add_participants_apply")
KeyNavigation.left: addParticipantsBackButton
KeyNavigation.down: addParticipantLayout
onClicked: {