diff --git a/Linphone/core/chat/ChatCore.cpp b/Linphone/core/chat/ChatCore.cpp index fa3b78701..009d4df84 100644 --- a/Linphone/core/chat/ChatCore.cpp +++ b/Linphone/core/chat/ChatCore.cpp @@ -67,6 +67,7 @@ ChatCore::ChatCore(const std::shared_ptr &chatRoom) : QObjec mTitle = Utils::coreStringToAppString(chatRoom->getSubject()); mAvatarUri = Utils::coreStringToAppString(chatRoom->getSubject()); mIsGroupChat = true; + mMeAdmin = chatRoom->getMe() && chatRoom->getMe()->isAdmin(); } } mUnreadMessagesCount = chatRoom->getUnreadMessagesCount(); @@ -102,6 +103,7 @@ ChatCore::ChatCore(const std::shared_ptr &chatRoom) : QObjec connect(this, &ChatCore::eventRemoved, this, &ChatCore::lUpdateLastMessage); mEphemeralEnabled = chatRoom->ephemeralEnabled(); mIsMuted = chatRoom->getMuted(); + mParticipants = buildParticipants(chatRoom); } ChatCore::~ChatCore() { @@ -155,18 +157,23 @@ void ChatCore::setSelf(QSharedPointer me) { }); }); - mChatModelConnection->makeConnectToModel(&ChatModel::chatMessageReceived, // TODO onNewEvent? + // Events (excluding messages) + mChatModelConnection->makeConnectToModel(&ChatModel::newEvent, [this](const std::shared_ptr &chatRoom, const std::shared_ptr &eventLog) { if (mChatModel->getMonitor() != chatRoom) return; qDebug() << "EVENT LOG RECEIVED IN CHATROOM" << mChatModel->getTitle(); auto event = EventLogCore::create(eventLog); - mChatModelConnection->invokeToCore([this, event]() { - appendEventLogToEventLogList(event); - emit lUpdateUnreadCount(); - emit lUpdateLastUpdatedTime(); - }); + if (event->isHandled()) { + mChatModelConnection->invokeToCore([this, event]() { + appendEventLogToEventLogList(event); + emit lUpdateUnreadCount(); + emit lUpdateLastUpdatedTime(); + }); + } }); + + // Chat messages mChatModelConnection->makeConnectToModel( &ChatModel::chatMessagesReceived, [this](const std::shared_ptr &chatRoom, const std::list> &eventsLog) { @@ -196,7 +203,7 @@ void ChatCore::setSelf(QSharedPointer me) { auto lastMessageModel = mLastMessage ? mLastMessage->getModel() : nullptr; mChatModelConnection->invokeToModel([this, lastMessageModel]() { auto linphoneMessage = mChatModel->getLastChatMessage(); - if (!lastMessageModel || lastMessageModel->getMonitor() != linphoneMessage) { + if (linphoneMessage && (!lastMessageModel || lastMessageModel->getMonitor() != linphoneMessage)) { auto chatMessageCore = ChatMessageCore::create(linphoneMessage); mChatModelConnection->invokeToCore([this, chatMessageCore]() { setLastMessage(chatMessageCore); }); } @@ -269,6 +276,47 @@ void ChatCore::setSelf(QSharedPointer me) { } }); }); + + mChatModelConnection->makeConnectToCore(&ChatCore::lSetSubject, [this](QString subject) { + mChatModelConnection->invokeToModel([this, subject]() { mChatModel->setSubject(subject); }); + }); + mChatModelConnection->makeConnectToModel( + &ChatModel::subjectChanged, [this](const std::shared_ptr &chatRoom, + const std::shared_ptr &eventLog) { + QString subject = Utils::coreStringToAppString(chatRoom->getSubject()); + mChatModelConnection->invokeToCore([this, subject]() { setTitle(subject); }); + }); + + mChatModelConnection->makeConnectToModel(&ChatModel::participantAdded, + [this](const std::shared_ptr &chatRoom, + const std::shared_ptr &eventLog) { + auto participants = buildParticipants(chatRoom); + mChatModelConnection->invokeToCore([this, participants]() { + mParticipants = participants; + emit participantsChanged(); + }); + }); + mChatModelConnection->makeConnectToModel(&ChatModel::participantRemoved, + [this](const std::shared_ptr &chatRoom, + const std::shared_ptr &eventLog) { + auto participants = buildParticipants(chatRoom); + mChatModelConnection->invokeToCore([this, participants]() { + mParticipants = participants; + emit participantsChanged(); + }); + }); + mChatModelConnection->makeConnectToModel(&ChatModel::participantAdminStatusChanged, + [this](const std::shared_ptr &chatRoom, + const std::shared_ptr &eventLog) { + auto participants = buildParticipants(chatRoom); + mChatModelConnection->invokeToCore([this, participants]() { + mParticipants = participants; + emit participantsChanged(); + }); + }); + mChatModelConnection->makeConnectToCore(&ChatCore::lRemoveParticipantAtIndex, [this](int index) { + mChatModelConnection->invokeToModel([this, index]() { mChatModel->removeParticipantAtIndex(index); }); + }); } QDateTime ChatCore::getLastUpdatedTime() const { @@ -463,3 +511,34 @@ bool ChatCore::isMuted() const { bool ChatCore::isEphemeralEnabled() const { return mEphemeralEnabled; } + +void ChatCore::setMeAdmin(bool admin) { + if (mMeAdmin != admin) { + mMeAdmin = admin; + emit meAdminChanged(); + } +} + +bool ChatCore::getMeAdmin() const { + return mMeAdmin; +} + +QVariantList ChatCore::getParticipantsGui() const { + QVariantList result; + for (auto participantCore : mParticipants) { + auto participantGui = new ParticipantGui(participantCore); + result.append(QVariant::fromValue(participantGui)); + } + return result; +} + +QList> +ChatCore::buildParticipants(const std::shared_ptr &chatRoom) const { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + QList> result; + for (auto participant : chatRoom->getParticipants()) { + auto participantCore = ParticipantCore::create(participant); + result.append(participantCore); + } + return result; +} diff --git a/Linphone/core/chat/ChatCore.hpp b/Linphone/core/chat/ChatCore.hpp index 37026113b..12568efa9 100644 --- a/Linphone/core/chat/ChatCore.hpp +++ b/Linphone/core/chat/ChatCore.hpp @@ -22,6 +22,7 @@ #define CHAT_CORE_H_ #include "core/chat/message/EventLogGui.hpp" +#include "core/participant/ParticipantCore.hpp" #include "message/ChatMessageGui.hpp" #include "model/chat/ChatModel.hpp" #include "model/search/MagicSearchModel.hpp" @@ -57,6 +58,8 @@ public: Q_PROPERTY(QString sendingText READ getSendingText WRITE setSendingText NOTIFY sendingTextChanged) Q_PROPERTY(bool ephemeralEnabled READ isEphemeralEnabled WRITE lEnableEphemeral NOTIFY ephemeralEnabledChanged) 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) // Should be call from model Thread. Will be automatically in App thread after initialization static QSharedPointer create(const std::shared_ptr &chatRoom); @@ -103,6 +106,9 @@ public: QString getChatRoomAddress() const; QString getPeerAddress() const; + bool getMeAdmin() const; + void setMeAdmin(bool admin); + QList> getEventLogList() const; void resetEventLogList(QList> list); void appendEventLogToEventLogList(QSharedPointer event); @@ -120,6 +126,9 @@ public: std::shared_ptr getModel() const; + QList> buildParticipants(const std::shared_ptr &chatRoom) const; + QVariantList getParticipantsGui() const; + signals: // used to close all the notifications when one is clicked void messageOpen(); @@ -138,6 +147,8 @@ signals: void sendingTextChanged(QString text); void mutedChanged(); void ephemeralEnabledChanged(); + void meAdminChanged(); + void participantsChanged(); void lDeleteMessage(); void lDelete(); @@ -152,6 +163,8 @@ signals: void lLeave(); void lSetMuted(bool muted); void lEnableEphemeral(bool enable); + void lSetSubject(QString subject); + void lRemoveParticipantAtIndex(int index); private: QString id; @@ -170,6 +183,8 @@ private: bool mIsReadOnly = false; bool mEphemeralEnabled = false; bool mIsMuted = false; + bool mMeAdmin = false; + QList> mParticipants; LinphoneEnums::ChatRoomState mChatRoomState; std::shared_ptr mChatModel; QSharedPointer mLastMessage; diff --git a/Linphone/core/chat/message/EventLogCore.hpp b/Linphone/core/chat/message/EventLogCore.hpp index 869479eb1..a29d6d6ee 100644 --- a/Linphone/core/chat/message/EventLogCore.hpp +++ b/Linphone/core/chat/message/EventLogCore.hpp @@ -56,6 +56,9 @@ public: std::string getEventLogId(); QSharedPointer getChatMessageCore(); QSharedPointer getCallHistoryCore(); + bool isHandled() const { + return mHandled; + } private: DECLARE_ABSTRACT_OBJECT diff --git a/Linphone/core/participant/ParticipantCore.cpp b/Linphone/core/participant/ParticipantCore.cpp index 5134763a0..8825cac7c 100644 --- a/Linphone/core/participant/ParticipantCore.cpp +++ b/Linphone/core/participant/ParticipantCore.cpp @@ -50,8 +50,7 @@ ParticipantCore::ParticipantCore(const std::shared_ptr &p mIsMe = ToolModel::isMe(mSipAddress); mCreationTime = QDateTime::fromSecsSinceEpoch(participant->getCreationTime()); mDisplayName = Utils::coreStringToAppString(participant->getAddress()->getDisplayName()); - if (mDisplayName.isEmpty()) - mDisplayName = Utils::coreStringToAppString(participant->getAddress()->getUsername()); + if (mDisplayName.isEmpty()) mDisplayName = ToolModel::getDisplayName(participant->getAddress()->clone()); for (auto &device : participant->getDevices()) { auto name = Utils::coreStringToAppString(device->getName()); auto address = Utils::coreStringToAppString(device->getAddress()->asStringUriOnly()); diff --git a/Linphone/data/languages/de.ts b/Linphone/data/languages/de.ts index 6d709cb2c..7d4a3c26d 100644 --- a/Linphone/data/languages/de.ts +++ b/Linphone/data/languages/de.ts @@ -6245,6 +6245,11 @@ Failed to create 1-1 conversation with %1 ! Meeting Meeting + + group_infos_participants + Participants + Participants (%1) + group_infos_media_docs Medias & documents @@ -6300,6 +6305,64 @@ Failed to create 1-1 conversation with %1 ! All the messages will be removed from the chat room. Do you want to continue ? Alle Nachrichten werden aus dem Chat entfernt. Möchten Sie fortfahren? + + group_infos_group_call_toast_message + "Start a group call ?" + Start a group call ? + + + + GroupChatInfoParticipants + + group_infos_participant_is_admin + Admin + Admin + + + group_infos_add_participants_title + Add Participants + Add Participants + + + menu_see_existing_contact + "Show contact" + Kontakt anzeigen + + + menu_add_address_to_contacts + "Add to contacts" + Zu Kontakten hinzufügen + + + group_infos_give_admin_rights + "Give admin rights" + Give admin rights + + + group_infos_remove_admin_rights + "Remove admin rights" + Remove admin rights + + + group_infos_copy_sip_address + "Copy SIP Address" + Copy SIP Address + + + group_infos_remove_participant + "Remove participant" + Remove participant + + + group_infos_remove_participants_toast_title + "Remove participant ?" + Remove participant ? + + + group_infos_remove_participants_toast_message + "Participant will be removed from chat room." + Participant will be removed from chat room. + diff --git a/Linphone/data/languages/en.ts b/Linphone/data/languages/en.ts index 6afbe1132..418611cf7 100644 --- a/Linphone/data/languages/en.ts +++ b/Linphone/data/languages/en.ts @@ -6224,7 +6224,7 @@ Failed to create 1-1 conversation with %1 ! one_one_infos_open_contact Open contact - Open contact + Show contact one_one_infos_create_contact @@ -6249,6 +6249,11 @@ Failed to create 1-1 conversation with %1 ! Meeting Meeting + + group_infos_participants + Participants + Participants (%1) + group_infos_media_docs Medias & documents @@ -6309,5 +6314,64 @@ Failed to create 1-1 conversation with %1 ! All the messages will be removed from the chat room. Do you want to continue ? All the messages will be removed from the chat room. Do you want to continue ? + + group_infos_group_call_toast_message + "Start a group call ?" + Start a group call ? + + + GroupChatInfoParticipants + + group_infos_add_participants_title + Add Participants + Add Participants + + + group_infos_participant_is_admin + Admin + Admin + + + menu_see_existing_contact + "Show contact" + Show contact + + + menu_add_address_to_contacts + "Add to contacts" + Add to contacts + + + group_infos_give_admin_rights + "Give admin rights" + Give admin rights + + + group_infos_remove_admin_rights + "Remove admin rights" + Remove admin rights + + + group_infos_copy_sip_address + "Copy SIP Address" + Copy SIP Address + + + group_infos_remove_participant + "Remove participant" + Remove participant + + + group_infos_remove_participants_toast_title + "Remove participant ?" + Remove participant ? + + + group_infos_remove_participants_toast_message + "Participant will be removed from chat room." + Participant will be removed from chat room. + + + diff --git a/Linphone/data/languages/fr_FR.ts b/Linphone/data/languages/fr_FR.ts index 0bf78806c..556f38467 100644 --- a/Linphone/data/languages/fr_FR.ts +++ b/Linphone/data/languages/fr_FR.ts @@ -6146,6 +6146,11 @@ Failed to create 1-1 conversation with %1 ! Meeting Réunion + + group_infos_participants + Participants + Participants (%1) + group_infos_media_docs Medias & documents @@ -6206,5 +6211,65 @@ Failed to create 1-1 conversation with %1 ! All the messages will be removed from the chat room. Do you want to continue ? Vous ne recevrez ni pourrez envoyer des messages dans cette conversation, quitter ? + + group_infos_group_call_toast_message + "Start a group call ?" + Démarrer un appel de groupe ? + + + GroupChatInfoParticipants + + group_infos_add_participants_title + Add Participants + Ajouter des Participants + + + group_infos_participant_is_admin + Admin + Admin + + + menu_see_existing_contact + "Show contact" + Voir le contact + + + menu_add_address_to_contacts + "Add to contacts" + Ajouter aux contacts + + + group_infos_give_admin_rights + "Give admin rights" + Donner les droits admins + + + group_infos_remove_admin_rights + "Remove admin rights" + Retirer les droits admins + + + group_infos_copy_sip_address + "Copy SIP Address" + Copier l’adresse SIP + + + group_infos_remove_participant + "Remove participant" + Retirer le participant + + + group_infos_remove_participants_toast_title + "Remove participant ?" + Retirer le participant ? + + + group_infos_remove_participants_toast_message + "Participant will be removed from chat room." + La participant sere retiré de la conversation + + + + diff --git a/Linphone/model/chat/ChatModel.cpp b/Linphone/model/chat/ChatModel.cpp index 807578537..e7bd9d13b 100644 --- a/Linphone/model/chat/ChatModel.cpp +++ b/Linphone/model/chat/ChatModel.cpp @@ -150,6 +150,15 @@ linphone::ChatRoom::State ChatModel::getState() const { return mMonitor->getState(); } +void ChatModel::setSubject(QString subject) const { + return mMonitor->setSubject(Utils::appStringToCoreString(subject)); +} + +void ChatModel::removeParticipantAtIndex(int index) const { + auto participant = *std::next(mMonitor->getParticipants().begin(), index); + mMonitor->removeParticipant(participant); +} + //---------------------------------------------------------------// void ChatModel::onIsComposingReceived(const std::shared_ptr &chatRoom, diff --git a/Linphone/model/chat/ChatModel.hpp b/Linphone/model/chat/ChatModel.hpp index 655a1d573..cc7b60e27 100644 --- a/Linphone/model/chat/ChatModel.hpp +++ b/Linphone/model/chat/ChatModel.hpp @@ -56,6 +56,8 @@ public: linphone::ChatRoom::State getState() const; void setMuted(bool muted); void enableEphemeral(bool enable); + void setSubject(QString subject) const; + void removeParticipantAtIndex(int index) const; signals: void historyDeleted(); diff --git a/Linphone/tool/Utils.cpp b/Linphone/tool/Utils.cpp index 381851ed1..7c6630ebe 100644 --- a/Linphone/tool/Utils.cpp +++ b/Linphone/tool/Utils.cpp @@ -2004,4 +2004,4 @@ void Utils::setGlobalCursor(Qt::CursorShape cursor) { void Utils::restoreGlobalCursor() { App::getInstance()->restoreOverrideCursor(); -} \ No newline at end of file +} diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index b6bda2755..5e909404c 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -154,6 +154,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Page/Layout/Chat/GroupConversationInfos.qml view/Page/Layout/Chat/OneOneConversationInfos.qml view/Page/Layout/Chat/ChatInfoActionsGroup.qml + view/Page/Layout/Chat/GroupChatInfoParticipants.qml view/Page/Main/AbstractMainPage.qml view/Page/Main/Account/AccountListView.qml diff --git a/Linphone/view/Page/Form/Chat/SelectedChatView.qml b/Linphone/view/Page/Form/Chat/SelectedChatView.qml index 4d12c73a3..f70b85e96 100644 --- a/Linphone/view/Page/Form/Chat/SelectedChatView.qml +++ b/Linphone/view/Page/Form/Chat/SelectedChatView.qml @@ -267,28 +267,21 @@ RowLayout { id: contentLoader anchors.top: parent.top anchors.topMargin: Math.round(39 * DefaultStyle.dp) - active: true - property var chat: mainItem.chat - sourceComponent: chat && chat.core.isGroupChat ? groupInfoComponent : oneToOneInfoComponent - - onLoaded: { - if (item && item.hasOwnProperty("chat")) { - item.chat = chat - } - } + sourceComponent: mainItem.chat.core.isGroupChat ? groupInfoComponent : oneToOneInfoComponent + active: detailsPanel.visible } Component { id: oneToOneInfoComponent OneOneConversationInfos { - chat: contentLoader.chat + chatGui: mainItem.chat } } Component { id: groupInfoComponent GroupConversationInfos { - chat: contentLoader.chat + chatGui: mainItem.chat } } } diff --git a/Linphone/view/Page/Layout/Chat/GroupChatInfoParticipants.qml b/Linphone/view/Page/Layout/Chat/GroupChatInfoParticipants.qml new file mode 100644 index 000000000..7e2f66d6d --- /dev/null +++ b/Linphone/view/Page/Layout/Chat/GroupChatInfoParticipants.qml @@ -0,0 +1,206 @@ +import QtCore +import QtQuick +import QtQuick.Controls.Basic as Control +import QtQuick.Dialogs +import QtQuick.Effects +import QtQuick.Layouts +import Linphone +import UtilsCpp +import SettingsCpp +import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle + +ColumnLayout { + + id: mainItem + property var title: String + property var participants + property var chatCore + signal addParticipantRequested() + + function isGroupEditable() { + return chatCore && chatCore.meAdmin && !chatCore.isReadOnly + } + + Text { + font: Typography.h4 + color: DefaultStyle.main2_600 + text: title + Layout.topMargin: Math.round(5 * DefaultStyle.dp) + } + + Rectangle { + Layout.fillWidth: true + Layout.topMargin: Math.round(9 * DefaultStyle.dp) + color: DefaultStyle.grey_100 + radius: Math.round(15 * DefaultStyle.dp) + height: contentColumn.implicitHeight + + ColumnLayout { + id: contentColumn + anchors.fill: parent + spacing: Math.round(16 * DefaultStyle.dp) + + Item { + visible: participants.length > 0 + Layout.preferredHeight: Math.round(1 * DefaultStyle.dp) + } + + Repeater { + model: participants + delegate: RowLayout { + width: parent.width + Layout.leftMargin: Math.round(17 * DefaultStyle.dp) + Layout.rightMargin: Math.round(10 * DefaultStyle.dp) + spacing: Math.round(10 * DefaultStyle.dp) + property var participantGui: modelData + property var participantCore: participantGui.core + property var contactObj: UtilsCpp.findFriendByAddress(participantCore.sipAddress) + property var contact: contactObj?.value || null + Avatar { + contact: contactObj?.value || null + displayNameVal: contact ? "" : participantCore.displayName + Layout.preferredWidth: Math.round(45 * DefaultStyle.dp) + Layout.preferredHeight: Math.round(45 * DefaultStyle.dp) + } + ColumnLayout { + Layout.fillWidth: true + Layout.preferredHeight: Math.round(56 * DefaultStyle.dp) + + ColumnLayout { + spacing: Math.round(2 * DefaultStyle.dp) + Layout.alignment: Qt.AlignVCenter + + Text { + text: participantCore.displayName + font: Typography.p1 + color: DefaultStyle.main2_700 + } + + Text { + visible: participantCore.isAdmin + text: qsTr("group_infos_participant_is_admin") + font: Typography.p3 + color: DefaultStyle.main2_500main + } + } + } + + Item { + Layout.fillWidth: true + } + + PopupButton { + id: detailOptions + popup.x: width + popup.contentItem: FocusScope { + implicitHeight: detailsButtons.implicitHeight + implicitWidth: detailsButtons.implicitWidth + Keys.onPressed: event => { + if (event.key == Qt.Key_Left + || event.key == Qt.Key_Escape) { + detailOptions.popup.close() + event.accepted = true + } + } + ColumnLayout { + id: detailsButtons + anchors.fill: parent + IconLabelButton { + Layout.fillWidth: true + //: "Show contact" + text: contact && contact.core && contact.core.isAppFriend ? qsTr("menu_see_existing_contact") : + //: "Add to contacts" + qsTr("menu_add_address_to_contacts") + icon.source: (contact && contact.core && contact.core.isAppFriend) + ? AppIcons.adressBook + : AppIcons.plusCircle + icon.width: Math.round(32 * DefaultStyle.dp) + icon.height: Math.round(32 * DefaultStyle.dp) + onClicked: { + detailOptions.close() + if (contact && contact.core.isAppFriend) + UtilsCpp.getMainWindow().displayContactPage(participantCore.sipAddress) + else + UtilsCpp.getMainWindow().displayCreateContactPage("",participantCore.sipAddress) + } + } + IconLabelButton { + visible: mainItem.isGroupEditable() + Layout.fillWidth: true + text: participantCore.isAdmin ? qsTr("group_infos_remove_admin_rights") : qsTr("group_infos_give_admin_rights") + icon.source: AppIcons.profile + icon.width: Math.round(32 * DefaultStyle.dp) + icon.height: Math.round(32 * DefaultStyle.dp) + onClicked: { + detailOptions.close() + participantCore.isAdmin = !participantCore.isAdmin + } + } + IconLabelButton { + visible: !contact || (contact.core && !contact.core.isAppFriend) + Layout.fillWidth: true + text: qsTr("group_infos_copy_sip_address") + icon.source: AppIcons.copy + icon.width: Math.round(32 * DefaultStyle.dp) + icon.height: Math.round(32 * DefaultStyle.dp) + onClicked: { + detailOptions.close() + UtilsCpp.copyToClipboard(participantCore.sipAddress) + } + } + Rectangle { + visible: mainItem.isGroupEditable() + color: DefaultStyle.main2_200 + Layout.fillWidth: true + height: Math.round(1 * DefaultStyle.dp) + width: parent.width - Math.round(30 * DefaultStyle.dp) + Layout.leftMargin: Math.round(17 * DefaultStyle.dp) + } + IconLabelButton { + visible: mainItem.isGroupEditable() + Layout.fillWidth: true + text: qsTr("group_infos_remove_participant") + icon.source: AppIcons.trashCan + icon.width: Math.round(32 * DefaultStyle.dp) + icon.height: Math.round(32 * DefaultStyle.dp) + style: ButtonStyle.hoveredBackgroundRed + onClicked: { + detailOptions.close() + UtilsCpp.getMainWindow().showConfirmationLambdaPopup(qsTr("group_infos_remove_participants_toast_title"), + qsTr("group_infos_remove_participants_toast_message"), + "", + function(confirmed) { + if (confirmed) { + mainItem.chatCore.lRemoveParticipantAtIndex(index) + } + }) + } + } + } + } + } + + } + } + + MediumButton { + id: addParticipant + 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") + style: ButtonStyle.secondary + onClicked: mainItem.addParticipantRequested() + Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: Math.round(17 * DefaultStyle.dp) + } + Item { + visible: !addParticipant.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 170228bdb..dafd59631 100644 --- a/Linphone/view/Page/Layout/Chat/GroupConversationInfos.qml +++ b/Linphone/view/Page/Layout/Chat/GroupConversationInfos.qml @@ -1,6 +1,6 @@ import QtCore import QtQuick -import QtQuick.Controls.Basic as Control +import QtQuick.Controls 2.15 import QtQuick.Dialogs import QtQuick.Effects import QtQuick.Layouts @@ -11,28 +11,93 @@ import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle ColumnLayout { id: mainItem - property var chat + property ChatGui chatGui + property var chatCore: chatGui.core spacing: 0 Avatar { Layout.alignment: Qt.AlignHCenter - displayNameVal: mainItem.chat.core.avatarUri + displayNameVal: mainItem.chatCore.avatarUri Layout.preferredWidth: Math.round(100 * DefaultStyle.dp) Layout.preferredHeight: Math.round(100 * DefaultStyle.dp) } - Text { - font: Typography.p1 - color: DefaultStyle.main2_700 - text: mainItem.chat?.core.title || "" + RowLayout { + id: titleMainItem + property bool isEditingSubject: false + property bool canEditSubject: mainItem.chatCore.meAdmin && mainItem.chatCore.isGroupChat Layout.alignment: Qt.AlignHCenter Layout.topMargin: Math.round(11 * DefaultStyle.dp) + + function saveSubject() { + mainItem.chatCore.lSetSubject(title.text) + } + + Item { + visible: titleMainItem.canEditSubject + Layout.preferredWidth: Math.round(18 * DefaultStyle.dp) + } + + Rectangle { + color: "transparent" + border.color: titleMainItem.isEditingSubject ? DefaultStyle.main1_500_main : "transparent" + border.width: 1 + radius: Math.round(4 * DefaultStyle.dp) + Layout.preferredWidth: Math.round(150 * DefaultStyle.dp) + Layout.preferredHeight: title.contentHeight + anchors.margins: Math.round(2 * DefaultStyle.dp) + + TextEdit { + id: title + anchors.fill: parent + anchors.margins: 6 + font: Typography.p1 + color: DefaultStyle.main2_700 + text: mainItem.chatCore.title || "" + enabled: titleMainItem.isEditingSubject + wrapMode: TextEdit.Wrap + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + Keys.onReturnPressed: { + if (titleMainItem.isEditingSubject) + titleMainItem.saveSubject() + titleMainItem.isEditingSubject = !titleMainItem.isEditingSubject + } + } + } + + Item { + visible: titleMainItem.canEditSubject + Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: Math.round(18 * DefaultStyle.dp) + Layout.preferredHeight: Math.round(18 * DefaultStyle.dp) + + EffectImage { + anchors.fill: parent + fillMode: Image.PreserveAspectFit + imageSource: titleMainItem.isEditingSubject ? AppIcons.check : AppIcons.pencil + colorizationColor: titleMainItem.isEditingSubject ? DefaultStyle.main1_500_main : DefaultStyle.main2_500main + } + + MouseArea { + id: editMouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + if (titleMainItem.isEditingSubject) + titleMainItem.saveSubject() + titleMainItem.isEditingSubject = !titleMainItem.isEditingSubject + } + cursorShape: Qt.PointingHandCursor + } + } } + Text { font: Typography.p3 color: DefaultStyle.main2_700 - text: mainItem.chat?.core.peerAddress + text: mainItem.chatCore.peerAddress Layout.alignment: Qt.AlignHCenter Layout.topMargin: Math.round(5 * DefaultStyle.dp) } @@ -46,11 +111,11 @@ ColumnLayout { height: Math.round(56 * DefaultStyle.dp) button.icon.width: Math.round(24 * DefaultStyle.dp) button.icon.height: Math.round(24 * DefaultStyle.dp) - button.icon.source: chat.core.muted ? AppIcons.bell : AppIcons.bellSlash + button.icon.source: chatCore.muted ? AppIcons.bell : AppIcons.bellSlash //: "Sourdine" label: qsTr("group_infos_mute") button.onClicked: { - chat.core.muted = !chat.core.muted + chatCore.muted = !chatCore.muted } } LabelButton { @@ -62,7 +127,21 @@ ColumnLayout { //: "Appel" label: qsTr("group_infos_call") button.onClicked: { - //TODO + mainWindow.showConfirmationLambdaPopup(qsTr("group_infos_call"), + qsTr("group_infos_group_call_toast_message"), + "", + function(confirmed) { + if (confirmed) { + const sourceList = mainItem.chatCore.participants + let addresses = []; + for (let i = 0; i < sourceList.length; ++i) { + const participantGui = sourceList[i] + const participantCore = participantGui.core + addresses.push(participantCore.sipAddress) + } + UtilsCpp.createGroupCall(mainItem.chatCore.title, addresses) + } + }) } } LabelButton { @@ -78,94 +157,112 @@ ColumnLayout { } } } - - ChatInfoActionsGroup { - Layout.leftMargin: Math.round(15 * DefaultStyle.dp) - Layout.rightMargin: Math.round(12 * DefaultStyle.dp) - Layout.topMargin: Math.round(30 * DefaultStyle.dp) - title: qsTr("group_infos_media_docs") - entries: [ - { - icon: AppIcons.photo, - visible: true, - text: qsTr("group_infos_media_docs"), - color: DefaultStyle.main2_600, - showRightArrow: true, - action: function() { - console.log("group_infos_shared_media") - } - }, - { - icon: AppIcons.pdf, - visible: true, - text: qsTr("group_infos_shared_docs"), - color: DefaultStyle.main2_600, - showRightArrow: true, - action: function() { - console.log("Opening shared documents") - } - } - ] - } - ChatInfoActionsGroup { - Layout.leftMargin: Math.round(15 * DefaultStyle.dp) - Layout.rightMargin: Math.round(12 * DefaultStyle.dp) - Layout.topMargin: Math.round(17 * DefaultStyle.dp) - title: qsTr("group_infos_other_actions") - entries: [ - { - icon: AppIcons.clockCountDown, - visible: true, - text: mainItem.chat.core.ephemeralEnabled ? qsTr("group_infos_disable_ephemerals") : qsTr("group_infos_enable_ephemerals"), - color: DefaultStyle.main2_600, - showRightArrow: false, - action: function() { - mainItem.chat.core.ephemeralEnabled = !mainItem.chat.core.ephemeralEnabled - } - }, - { - icon: AppIcons.signOut, - visible: !mainItem.chat.core.isReadOnly, - text: qsTr("group_infos_leave_room"), - color: DefaultStyle.main2_600, - showRightArrow: false, - action: function() { - //: Leave Chat Room ? - mainWindow.showConfirmationLambdaPopup(qsTr("group_infos_leave_room_toast_title"), - //: All the messages will be removed from the chat room. Do you want to continue ? - qsTr("group_infos_leave_room_toast_message"), - "", - function(confirmed) { - if (confirmed) { - mainItem.chat.core.lLeave() - } - }) - } - }, - { - icon: AppIcons.trashCan, - visible: true, - text: qsTr("group_infos_delete_history"), - color: DefaultStyle.danger_500main, - showRightArrow: false, - action: function() { - //: Delete history ? - mainWindow.showConfirmationLambdaPopup(qsTr("group_infos_delete_history_toast_title"), - //: All the messages will be removed from the chat room. Do you want to continue ? - qsTr("group_infos_delete_history_toast_message"), - "", - function(confirmed) { - if (confirmed) { - mainItem.chat.core.lDeleteHistory() - } - }) - } - } - ] - } - - Item { + ScrollView { + id: scrollView 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) + + ColumnLayout { + spacing: 0 + width: scrollView.width + + GroupChatInfoParticipants { + Layout.fillWidth: true + title: qsTr("group_infos_participants").arg(mainItem.chatCore.participants.length) + participants: mainItem.chatCore.participants + chatCore: mainItem.chatCore + } + + ChatInfoActionsGroup { + Layout.topMargin: Math.round(30 * DefaultStyle.dp) + title: qsTr("group_infos_media_docs") + entries: [ + { + icon: AppIcons.photo, + visible: true, + text: qsTr("group_infos_media_docs"), + color: DefaultStyle.main2_600, + showRightArrow: true, + action: function() { + console.log("group_infos_shared_media") + } + }, + { + icon: AppIcons.pdf, + visible: true, + text: qsTr("group_infos_shared_docs"), + color: DefaultStyle.main2_600, + showRightArrow: true, + action: function() { + console.log("Opening shared documents") + } + } + ] + } + + ChatInfoActionsGroup { + Layout.topMargin: Math.round(17 * DefaultStyle.dp) + title: qsTr("group_infos_other_actions") + entries: [ + { + icon: AppIcons.clockCountDown, + visible: !mainItem.chatCore.isReadOnly, + text: mainItem.chatCore.ephemeralEnabled ? qsTr("group_infos_disable_ephemerals") : qsTr("group_infos_enable_ephemerals"), + color: DefaultStyle.main2_600, + showRightArrow: false, + action: function() { + mainItem.chatCore.ephemeralEnabled = !mainItem.chatCore.ephemeralEnabled + } + }, + { + icon: AppIcons.signOut, + visible: !mainItem.chatCore.isReadOnly, + text: qsTr("group_infos_leave_room"), + color: DefaultStyle.main2_600, + showRightArrow: false, + action: function() { + //: Leave Chat Room ? + mainWindow.showConfirmationLambdaPopup(qsTr("group_infos_leave_room_toast_title"), + //: All the messages will be removed from the chat room. Do you want to continue ? + qsTr("group_infos_leave_room_toast_message"), + "", + function(confirmed) { + if (confirmed) { + mainItem.chatCore.lLeave() + } + }) + } + }, + { + icon: AppIcons.trashCan, + visible: true, + text: qsTr("group_infos_delete_history"), + color: DefaultStyle.danger_500main, + showRightArrow: false, + action: function() { + //: Delete history ? + mainWindow.showConfirmationLambdaPopup(qsTr("group_infos_delete_history_toast_title"), + //: All the messages will be removed from the chat room. Do you want to continue ? + qsTr("group_infos_delete_history_toast_message"), + "", + function(confirmed) { + if (confirmed) { + mainItem.chatCore.lDeleteHistory() + } + }) + } + } + ] + } + } + } + Item { + Layout.preferredHeight: Math.round(50 * DefaultStyle.dp) } } diff --git a/Linphone/view/Page/Layout/Chat/OneOneConversationInfos.qml b/Linphone/view/Page/Layout/Chat/OneOneConversationInfos.qml index a40e46993..63b76f80b 100644 --- a/Linphone/view/Page/Layout/Chat/OneOneConversationInfos.qml +++ b/Linphone/view/Page/Layout/Chat/OneOneConversationInfos.qml @@ -11,14 +11,15 @@ import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle ColumnLayout { id: mainItem - property var chat - property var contactObj: chat ? UtilsCpp.findFriendByAddress(chat?.core.peerAddress) : null + property ChatGui chatGui + property var chatCore: chatGui.core + property var contactObj: chat ? UtilsCpp.findFriendByAddress(mainItem.chatCore.peerAddress) : null spacing: 0 Avatar { Layout.alignment: Qt.AlignHCenter contact: contactObj?.value || null - displayNameVal: contact ? "" : mainItem.chat.core.avatarUri + displayNameVal: contact ? "" : mainItem.chatCore.avatarUri Layout.preferredWidth: Math.round(100 * DefaultStyle.dp) Layout.preferredHeight: Math.round(100 * DefaultStyle.dp) } @@ -26,7 +27,7 @@ ColumnLayout { Text { font: Typography.p1 color: DefaultStyle.main2_700 - text: mainItem.chat?.core.title || "" + text: mainItem.chatCore.title || "" Layout.alignment: Qt.AlignHCenter Layout.topMargin: Math.round(11 * DefaultStyle.dp) } @@ -34,20 +35,20 @@ ColumnLayout { Text { font: Typography.p3 color: DefaultStyle.main2_700 - text: mainItem.chat?.core.peerAddress + text: mainItem.chatCore.peerAddress Layout.alignment: Qt.AlignHCenter Layout.topMargin: Math.round(5 * DefaultStyle.dp) } Text { + visible: contactObj?.value font: Typography.p3 - color: contactObj?.value.core.presenceColor - text: contactObj?.value.core.presenceStatus + color: contactObj?.value != null ? contactObj?.value.core.presenceColor : "transparent" + text: contactObj?.value != null ? contactObj?.value.core.presenceStatus : "" Layout.alignment: Qt.AlignHCenter Layout.topMargin: Math.round(5 * DefaultStyle.dp) } - RowLayout { spacing: Math.round(55 * DefaultStyle.dp) Layout.alignment: Qt.AlignHCenter @@ -69,11 +70,11 @@ ColumnLayout { height: Math.round(56 * DefaultStyle.dp) button.icon.width: Math.round(24 * DefaultStyle.dp) button.icon.height: Math.round(24 * DefaultStyle.dp) - button.icon.source: chat.core.muted ? AppIcons.bell : AppIcons.bellSlash + button.icon.source: mainItem.chatCore.muted ? AppIcons.bell : AppIcons.bellSlash //: "Sourdine" label: qsTr("one_one_infos_mute") button.onClicked: { - chat.core.muted = !chat.core.muted + mainItem.chatCore.muted = !mainItem.chatCore.muted } } LabelButton { @@ -126,7 +127,7 @@ ColumnLayout { title: qsTr("one_one_infos_other_actions") entries: [ { - icon: AppIcons.adressBook, + icon: contactObj.value ? AppIcons.adressBook : AppIcons.plusCircle, visible: true, text: contactObj.value ? qsTr("one_one_infos_open_contact") : qsTr("one_one_infos_create_contact"), color: DefaultStyle.main2_600, @@ -136,18 +137,17 @@ ColumnLayout { if (contactObj.value) mainWindow.displayContactPage(contactObj.value.core.defaultAddress) else - mainWindow.displayCreateContactPage("",chat.core.peerAddress) - //mainItem.createContactRequested(contactDetail.contactName, chat.core.peerAddress) + mainWindow.displayCreateContactPage("",mainItem.chatCore.peerAddress) } }, { icon: AppIcons.clockCountDown, visible: true, - text: mainItem.chat.core.ephemeralEnabled ? qsTr("one_one_infos_disable_ephemerals") : qsTr("one_one_infos_enable_ephemerals"), + text: mainItem.chatCore.ephemeralEnabled ? qsTr("one_one_infos_disable_ephemerals") : qsTr("one_one_infos_enable_ephemerals"), color: DefaultStyle.main2_600, showRightArrow: false, action: function() { - mainItem.chat.core.ephemeralEnabled = !mainItem.chat.core.ephemeralEnabled + mainItem.chatCore.ephemeralEnabled = !mainItem.chatCore.ephemeralEnabled } }, { @@ -164,7 +164,7 @@ ColumnLayout { "", function(confirmed) { if (confirmed) { - mainItem.chat.core.lDeleteHistory() + mainItem.chatCore.lDeleteHistory() } }) }