From 0470988c324d92af1ad10d506e51df98c732c460 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Thu, 19 Jun 2025 07:48:45 +0200 Subject: [PATCH] - 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) --- Linphone/core/chat/ChatCore.cpp | 12 +- Linphone/core/chat/ChatCore.hpp | 7 +- Linphone/core/chat/message/EventLogCore.cpp | 17 +-- Linphone/core/chat/message/EventLogCore.hpp | 4 +- Linphone/data/languages/de.ts | 24 +++- Linphone/data/languages/en.ts | 26 ++++- Linphone/data/languages/fr_FR.ts | 26 ++++- Linphone/model/chat/ChatModel.cpp | 18 +++ Linphone/model/chat/ChatModel.hpp | 2 + .../Page/Form/Meeting/AddParticipantsForm.qml | 26 +++-- .../Layout/Chat/GroupChatInfoParticipants.qml | 19 ++-- .../Layout/Chat/GroupConversationInfos.qml | 105 ++++++++++++++++-- .../view/Page/Main/Meeting/MeetingPage.qml | 2 +- 13 files changed, 236 insertions(+), 52 deletions(-) diff --git a/Linphone/core/chat/ChatCore.cpp b/Linphone/core/chat/ChatCore.cpp index 5871f683..bda402b0 100644 --- a/Linphone/core/chat/ChatCore.cpp +++ b/Linphone/core/chat/ChatCore.cpp @@ -176,7 +176,6 @@ void ChatCore::setSelf(QSharedPointer me) { if (event->isHandled()) { mChatModelConnection->invokeToCore([this, event]() { appendEventLogToEventLogList(event); - emit lUpdateUnreadCount(); emit lUpdateLastUpdatedTime(); }); } @@ -187,7 +186,7 @@ void ChatCore::setSelf(QSharedPointer me) { &ChatModel::chatMessagesReceived, [this](const std::shared_ptr &chatRoom, const std::list> &eventsLog) { if (mChatModel->getMonitor() != chatRoom) return; - qDebug() << "EVENT LOGS RECEIVED IN CHATROOM" << mChatModel->getTitle(); + qDebug() << "CHAT MESSAGE RECEIVED IN CHATROOM" << mChatModel->getTitle(); QList> list; for (auto &e : eventsLog) { auto event = EventLogCore::create(e); @@ -326,6 +325,15 @@ void ChatCore::setSelf(QSharedPointer 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 { diff --git a/Linphone/core/chat/ChatCore.hpp b/Linphone/core/chat/ChatCore.hpp index 323fd05b..f7e22898 100644 --- a/Linphone/core/chat/ChatCore.hpp +++ b/Linphone/core/chat/ChatCore.hpp @@ -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 create(const std::shared_ptr &chatRoom); @@ -128,8 +130,7 @@ public: QList> buildParticipants(const std::shared_ptr &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; diff --git a/Linphone/core/chat/message/EventLogCore.cpp b/Linphone/core/chat/message/EventLogCore.cpp index 5539942d..d3ab6a5f 100644 --- a/Linphone/core/chat/message/EventLogCore.cpp +++ b/Linphone/core/chat/message/EventLogCore.cpp @@ -39,12 +39,15 @@ EventLogCore::EventLogCore(const std::shared_ptr &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().valueToKey(static_cast(mEventLogType))); + mEventId = type + QString::number(static_cast(eventLog->getCreationTime())); + ; computeEvent(eventLog); } } @@ -55,7 +58,7 @@ EventLogCore::~EventLogCore() { void EventLogCore::setSelf(QSharedPointer me) { } -std::string EventLogCore::getEventLogId() { +QString EventLogCore::getEventLogId() { return mEventId; } @@ -127,11 +130,11 @@ void EventLogCore::computeEvent(const std::shared_ptr 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; diff --git a/Linphone/core/chat/message/EventLogCore.hpp b/Linphone/core/chat/message/EventLogCore.hpp index a29d6d6e..47c4a7e6 100644 --- a/Linphone/core/chat/message/EventLogCore.hpp +++ b/Linphone/core/chat/message/EventLogCore.hpp @@ -53,7 +53,7 @@ public: EventLogCore(const std::shared_ptr &eventLog); ~EventLogCore(); void setSelf(QSharedPointer me); - std::string getEventLogId(); + QString getEventLogId(); QSharedPointer getChatMessageCore(); QSharedPointer getCallHistoryCore(); bool isHandled() const { @@ -62,7 +62,7 @@ public: private: DECLARE_ABSTRACT_OBJECT - std::string mEventId; + QString mEventId; QSharedPointer mChatMessageCore = nullptr; QSharedPointer mCallHistoryCore = nullptr; diff --git a/Linphone/data/languages/de.ts b/Linphone/data/languages/de.ts index 9519f4e0..dcb9cab9 100644 --- a/Linphone/data/languages/de.ts +++ b/Linphone/data/languages/de.ts @@ -3009,9 +3009,9 @@ Error - group_infos_add_participants_title - "Ajouter des participants" - Add Participants + group_infos_manage_participants_title + "Gérer les participants" + Manage Participants @@ -3058,6 +3058,18 @@ Error GroupConversationInfos + + + group_infos_manage_participants + Participants + Participants + + + + group_infos_participants_edit_apply + Apply + Apply + group_infos_call @@ -3914,6 +3926,12 @@ Error "Ajouter des participants" Teilnehmer hinzufügen + + + meeting_schedule_add_participants_apply + Apply + Apply + add diff --git a/Linphone/data/languages/en.ts b/Linphone/data/languages/en.ts index c36532cf..74e3503f 100644 --- a/Linphone/data/languages/en.ts +++ b/Linphone/data/languages/en.ts @@ -2811,7 +2811,7 @@ Only your correspondent can decrypt them. conference_participant_removed_event - %1 has left + %1 is no longer in the conversation @@ -2927,9 +2927,9 @@ Only your correspondent can decrypt them. GroupChatInfoParticipants - group_infos_add_participants_title - "Ajouter des participants" - Add Participants + group_infos_manage_participants_title + "Gérer les participants" + Manage participants @@ -2981,6 +2981,18 @@ Only your correspondent can decrypt them. GroupConversationInfos + + + group_infos_manage_participants + Participants + Participants + + + + group_infos_participants_edit_apply + Apply + Apply + group_infos_call @@ -3832,6 +3844,12 @@ Only your correspondent can decrypt them. "Ajouter des participants" Add participants + + + meeting_schedule_add_participants_apply + Apply + Apply + add diff --git a/Linphone/data/languages/fr_FR.ts b/Linphone/data/languages/fr_FR.ts index 1d0a601c..9d13ef54 100644 --- a/Linphone/data/languages/fr_FR.ts +++ b/Linphone/data/languages/fr_FR.ts @@ -2811,7 +2811,7 @@ en bout. Seul votre correspondant peut les déchiffrer. conference_participant_removed_event - %1 a quitté le groupe + %1 ne fait plus partie du groupe @@ -2927,9 +2927,9 @@ en bout. Seul votre correspondant peut les déchiffrer. GroupChatInfoParticipants - group_infos_add_participants_title - "Ajouter des participants" - Ajouter des Participants + group_infos_manage_participants_title + "Gérer les participants" + Gérer les participants @@ -2981,6 +2981,18 @@ en bout. Seul votre correspondant peut les déchiffrer. GroupConversationInfos + + + group_infos_manage_participants + Participants + Participants + + + + group_infos_participants_edit_apply + Apply + Appliquer + group_infos_call @@ -3832,6 +3844,12 @@ en bout. Seul votre correspondant peut les déchiffrer. "Ajouter des participants" Ajouter des participants + + + meeting_schedule_add_participants_apply + Appliquer + Appliquer + add diff --git a/Linphone/model/chat/ChatModel.cpp b/Linphone/model/chat/ChatModel.cpp index 08c00a87..82f6b1f8 100644 --- a/Linphone/model/chat/ChatModel.cpp +++ b/Linphone/model/chat/ChatModel.cpp @@ -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 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 &chatRoom, diff --git a/Linphone/model/chat/ChatModel.hpp b/Linphone/model/chat/ChatModel.hpp index a2d789e9..1be12dcd 100644 --- a/Linphone/model/chat/ChatModel.hpp +++ b/Linphone/model/chat/ChatModel.hpp @@ -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(); diff --git a/Linphone/view/Page/Form/Meeting/AddParticipantsForm.qml b/Linphone/view/Page/Form/Meeting/AddParticipantsForm.qml index e3295917..0c5e8cac 100644 --- a/Linphone/view/Page/Form/Meeting/AddParticipantsForm.qml +++ b/Linphone/view/Page/Form/Meeting/AddParticipantsForm.qml @@ -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 { diff --git a/Linphone/view/Page/Layout/Chat/GroupChatInfoParticipants.qml b/Linphone/view/Page/Layout/Chat/GroupChatInfoParticipants.qml index 7e2f66d6..03376422 100644 --- a/Linphone/view/Page/Layout/Chat/GroupChatInfoParticipants.qml +++ b/Linphone/view/Page/Layout/Chat/GroupChatInfoParticipants.qml @@ -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) } } diff --git a/Linphone/view/Page/Layout/Chat/GroupConversationInfos.qml b/Linphone/view/Page/Layout/Chat/GroupConversationInfos.qml index c721dc56..62a4121f 100644 --- a/Linphone/view/Page/Layout/Chat/GroupConversationInfos.qml +++ b/Linphone/view/Page/Layout/Chat/GroupConversationInfos.qml @@ -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 { diff --git a/Linphone/view/Page/Main/Meeting/MeetingPage.qml b/Linphone/view/Page/Main/Meeting/MeetingPage.qml index 1602ed7f..61a796ae 100644 --- a/Linphone/view/Page/Main/Meeting/MeetingPage.qml +++ b/Linphone/view/Page/Main/Meeting/MeetingPage.qml @@ -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: {