From 4788e963a65f0f0b6743be04145f5d3fb2781368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Blin?= <sebastien.blin@savoirfairelinux.com> Date: Fri, 28 Jan 2022 16:54:15 -0500 Subject: [PATCH] swarm: add context menu for members In the members list, a right click allow the user to access some actions such as: + Perform a video or audio call with a member + Open a 1:1 conversation with this member + Block this contact + If allowed, kick a member from the conversation In the future, other actions can be added, such as promote a user to administrator. GitLab: #340 Change-Id: I3824ad4efa8faf89479e99c93b98d3dd9781582d --- resources/icons/block_black_24dp.svg | 14 +++++---- resources/icons/gotoconversation.svg | 13 +++++++++ resources/icons/kick_member.svg | 14 +++++++++ src/contactadapter.cpp | 7 +++++ src/contactadapter.h | 4 ++- src/conversationsadapter.cpp | 19 ++++++++++++ src/conversationsadapter.h | 3 ++ src/mainview/components/SidePanel.qml | 6 ++++ src/mainview/components/SwarmDetailsPanel.qml | 2 ++ .../SwarmParticipantContextMenu.qml | 29 +++++++++++++------ src/utils.cpp | 7 ++--- 11 files changed, 98 insertions(+), 20 deletions(-) create mode 100644 resources/icons/gotoconversation.svg create mode 100644 resources/icons/kick_member.svg diff --git a/resources/icons/block_black_24dp.svg b/resources/icons/block_black_24dp.svg index c24b8807d..3d3b8512e 100644 --- a/resources/icons/block_black_24dp.svg +++ b/resources/icons/block_black_24dp.svg @@ -2,10 +2,12 @@ <!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> -<path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M4.1,8.4c1.4-3,4.5-5.1,7.9-5.1c2.1,0,4.1,0.7,5.6,2.1l-2,2 - c-0.3-1.8-1.7-3.1-3.6-3.1C10,4.4,8.4,6,8.4,8c0,1.7,1.1,3,2.6,3.5c-2.7,0.5-4.9,2.9-5.5,6l-0.1,0.1C3.2,15,2.7,11.4,4.1,8.4z - M12.7,10.3c-0.2,0.1-0.5,0.1-0.7,0.1c-1.4,0-2.4-1-2.4-2.4s1-2.4,2.4-2.4c1.4,0,2.4,1,2.4,2.4c0,0.3,0,0.5-0.1,0.7L12.7,10.3z - M9.8,13.2l-2.3,2.3C8.1,14.5,8.9,13.7,9.8,13.2z M6.6,18.8c0-0.2,0-0.4,0-0.6l5.6-5.6c2.8,0.2,5,2.8,5.1,6.1 - C15.9,20,14,20.7,12,20.7C10,20.7,8.2,20,6.6,18.8z M18.5,17.7c-0.5-3.1-2.5-5.5-5.1-6.1l0.4-0.4c0.5-0.3,1-0.7,1.3-1.3l3.5-3.5 - c2.2,2.6,2.7,6.2,1.3,9.2C19.5,16.4,19.1,17.1,18.5,17.7z"/> +<g> + <path d="M16.9,11.5c-2.8,0-5.1,2.3-5.1,5.1s2.3,5.1,5.1,5.1s5.1-2.3,5.1-5.1S19.7,11.5,16.9,11.5z M16.9,12.7 + c0.8,0,1.6,0.3,2.2,0.7l-5.6,5c-0.3-0.6-0.5-1.2-0.5-1.9C13.1,14.5,14.8,12.7,16.9,12.7z M16.9,20.5c-1,0-2-0.4-2.7-1.1l5.7-5.1 + c0.5,0.6,0.8,1.4,0.8,2.3C20.8,18.7,19.1,20.5,16.9,20.5z"/> + <path d="M14.5,13.1l0.7-0.4c-1-0.7-2.1-1.2-3.3-1.4c1.9-0.6,3.2-2.3,3.2-4.4c0.1-2.6-2-4.6-4.5-4.6C8.1,2.3,6,4.4,6,7 + c0,2.1,1.4,3.8,3.2,4.4C5.1,12.1,2,16,2,20.7c0,0.4,0.3,0.7,0.7,0.7s0.7-0.3,0.7-0.7c0-4.5,3.3-8.2,7.4-8.2c1.3,0,2.5,0.4,3.6,1.1 + C14.4,13.3,14.5,13.1,14.5,13.1z M10.6,10.3c-1.9,0-3.4-1.5-3.4-3.3c0-1.9,1.5-3.4,3.4-3.4S14,5.1,14,7S12.5,10.3,10.6,10.3z"/> +</g> </svg> diff --git a/resources/icons/gotoconversation.svg b/resources/icons/gotoconversation.svg new file mode 100644 index 000000000..11b527139 --- /dev/null +++ b/resources/icons/gotoconversation.svg @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> +<path d="M22,12.5c0-1.9-0.9-3.8-2.6-5.1l0,0c-0.3-0.2-0.6-0.5-1-0.7c-0.4-0.7-1-1.3-1.7-1.8c-1.6-1.3-3.8-2-6.1-2s-4.4,0.7-6.1,2 + C2.9,6.2,2,8,2,10c0,1.9,0.9,3.7,2.5,5l-0.1,3c0,0.3,0.3,0.6,0.6,0.6c0.1,0,0.2,0,0.3,0l2.1-0.9c1.6,1.2,3.7,1.9,6,1.9l0,0 + c0.5,0,1.1,0,1.7-0.1l3.8,1.6c0.1,0,0.2,0,0.2,0c0.1,0,0.2,0,0.2,0c0.2-0.1,0.3-0.2,0.3-0.3c0-0.1,0.1-0.2,0-0.3l-0.1-3 + C21.1,16.1,22,14.4,22,12.5z M18.4,19.6l-3.1-1.3c-0.1,0-0.2,0-0.2,0H15c-0.5,0.1-1.1,0.1-1.6,0.1c-1.7,0-3.3-0.4-4.6-1.3L9,17 + c0.5,0.1,1.1,0.1,1.7,0.1c2.3,0,4.4-0.7,6.1-2c1.7-1.3,2.6-3.2,2.6-5.1c0-0.3,0-0.7-0.1-1c1,1,1.5,2.2,1.5,3.5c0,1.6-0.8,3-2.2,4.1 + l-0.1,0.1c-0.2,0.1-0.2,0.3-0.2,0.5L18.4,19.6z M5.5,14.2L5.5,14.2C4,13,3.2,11.6,3.2,10S4,7,5.4,5.9s3.3-1.7,5.3-1.7 + s3.9,0.6,5.3,1.7s2.2,2.6,2.2,4.1c0,1.6-0.8,3-2.2,4.1c-1.4,1.1-3.3,1.7-5.3,1.7c-0.5,0-1.1,0-1.6-0.1c-0.1,0-0.2,0-0.3,0L5.7,17 + l0.1-2.3C5.7,14.5,5.7,14.3,5.5,14.2z"/> +</svg> diff --git a/resources/icons/kick_member.svg b/resources/icons/kick_member.svg new file mode 100644 index 000000000..00d0f9372 --- /dev/null +++ b/resources/icons/kick_member.svg @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> +<g> + <path d="M14.5,13.1l0.7-0.4c-1-0.7-2.1-1.2-3.3-1.4c1.9-0.6,3.2-2.3,3.2-4.4c0.1-2.6-2-4.6-4.5-4.6C8.1,2.3,6,4.4,6,7 + c0,2.1,1.4,3.8,3.2,4.4C5.1,12.1,2,16,2,20.7c0,0.4,0.3,0.7,0.7,0.7s0.7-0.3,0.7-0.7c0-4.5,3.3-8.2,7.4-8.2c1.3,0,2.5,0.4,3.6,1.1 + C14.4,13.3,14.5,13.1,14.5,13.1z M10.6,10.3c-1.9,0-3.4-1.5-3.4-3.3c0-1.9,1.5-3.4,3.4-3.4S14,5.1,14,7S12.5,10.3,10.6,10.3z"/> + <path d="M16.9,11.5c-2.8,0-5.1,2.3-5.1,5.1c0,2.8,2.3,5.1,5.1,5.1c2.8,0,5.1-2.3,5.1-5.1C22,13.8,19.7,11.5,16.9,11.5z M20.3,18.4 + L18,16.3l2.2-1.9c0.4,0.6,0.7,1.4,0.7,2.2C20.8,17.3,20.6,17.9,20.3,18.4z M19.3,13.5l-2.2,2l-2.3-2.1c0.6-0.4,1.4-0.7,2.2-0.7 + C17.8,12.7,18.6,13,19.3,13.5z M13.9,14.3l2.3,2l-2.5,2.3c-0.3-0.6-0.6-1.3-0.6-2C13.1,15.7,13.4,14.9,13.9,14.3z M14.4,19.5 + l2.7-2.4l2.5,2.3c-0.7,0.7-1.6,1.1-2.7,1.1C16,20.5,15.1,20.1,14.4,19.5z"/> +</g> +</svg> diff --git a/src/contactadapter.cpp b/src/contactadapter.cpp index 49ad99911..6c96c6d1c 100644 --- a/src/contactadapter.cpp +++ b/src/contactadapter.cpp @@ -239,6 +239,13 @@ ContactAdapter::contactSelected(int index) } } +void +ContactAdapter::removeContact(const QString& peerUri, bool banContact) +{ + auto& accInfo = lrcInstance_->getCurrentAccountInfo(); + accInfo.contactModel->removeContact(peerUri, banContact); +} + void ContactAdapter::connectSignals() { diff --git a/src/contactadapter.h b/src/contactadapter.h index c736816ff..d70f140b0 100644 --- a/src/contactadapter.h +++ b/src/contactadapter.h @@ -91,6 +91,7 @@ public: Q_INVOKABLE QVariant getContactSelectableModel(int type); Q_INVOKABLE void setSearchFilter(const QString& filter); Q_INVOKABLE void contactSelected(int index); + Q_INVOKABLE void removeContact(const QString& peerUri, bool banContact); void connectSignals(); @@ -104,7 +105,8 @@ private: QStringList defaultModerators_; - bool hasDifferentMembers(const VectorString& currentMembers, const VectorString& convMembers) const; + bool hasDifferentMembers(const VectorString& currentMembers, + const VectorString& convMembers) const; Q_SIGNALS: void defaultModeratorsUpdated(); diff --git a/src/conversationsadapter.cpp b/src/conversationsadapter.cpp index a08c82af7..509578a2e 100644 --- a/src/conversationsadapter.cpp +++ b/src/conversationsadapter.cpp @@ -381,6 +381,7 @@ ConversationsAdapter::setFilter(const QString& filterString) { convModel_->setFilter(filterString); searchSrcModel_->setFilter(filterString); + Q_EMIT textFilterChanged(filterString); } QVariantMap @@ -499,6 +500,24 @@ ConversationsAdapter::updateConversationDescription(const QString& convId, convModel->updateConversationInfo(convId, details); } +QString +ConversationsAdapter::dialogId(const QString& peerUri) +{ + auto& convInfo = lrcInstance_->getConversationFromPeerUri(peerUri); + if (!convInfo.uid.isEmpty() && convInfo.isCoreDialog()) + return convInfo.uid; + return {}; +} + +void +ConversationsAdapter::openDialogConversationWith(const QString& peerUri) +{ + auto& convInfo = lrcInstance_->getConversationFromPeerUri(peerUri); + if (convInfo.uid.isEmpty() || !convInfo.isCoreDialog()) + return; + lrcInstance_->selectConversation(convInfo.uid); +} + bool ConversationsAdapter::connectConversationModel() { diff --git a/src/conversationsadapter.h b/src/conversationsadapter.h index 1d7e318fe..15189542e 100644 --- a/src/conversationsadapter.h +++ b/src/conversationsadapter.h @@ -59,9 +59,12 @@ public: Q_INVOKABLE void updateConversationDescription(const QString& convId, const QString& newDescription); + Q_INVOKABLE QString dialogId(const QString& peerUri); + Q_INVOKABLE void openDialogConversationWith(const QString& peerUri); Q_SIGNALS: void showConversation(const QString& accountId, const QString& convUid); void showSearchStatus(const QString& status); + void textFilterChanged(const QString& text); void navigateToWelcomePageRequested(); void conversationReady(const QString& convId); diff --git a/src/mainview/components/SidePanel.qml b/src/mainview/components/SidePanel.qml index 9fbe8d0e1..894bb9f60 100644 --- a/src/mainview/components/SidePanel.qml +++ b/src/mainview/components/SidePanel.qml @@ -156,6 +156,12 @@ Rectangle { function onShowSearchStatus(status) { searchStatusText.text = status } + + function onTextFilterChanged(text) { + // In the swarm details, "Go to conversation" can + // change the search bar. Be sure to be synced + contactSearchBar.textContent = text + } } ColumnLayout { diff --git a/src/mainview/components/SwarmDetailsPanel.qml b/src/mainview/components/SwarmDetailsPanel.qml index c92e9a917..f5adf0f10 100644 --- a/src/mainview/components/SwarmDetailsPanel.qml +++ b/src/mainview/components/SwarmDetailsPanel.qml @@ -168,6 +168,7 @@ Rectangle { SwarmParticipantContextMenu { id: contextMenu + role: UtilsAdapter.getParticipantRole(CurrentAccount.id, CurrentConversation.id, CurrentAccount.uri) function openMenuAt(x, y, participantUri) { contextMenu.x = x @@ -187,6 +188,7 @@ Rectangle { MouseArea { anchors.fill: parent + enabled: modelData != CurrentAccount.uri acceptedButtons: Qt.RightButton onClicked: function (mouse) { contextMenu.openMenuAt(x + mouse.x, y + mouse.y, modelData) diff --git a/src/mainview/components/SwarmParticipantContextMenu.qml b/src/mainview/components/SwarmParticipantContextMenu.qml index 4dcc5ce57..d162b7371 100644 --- a/src/mainview/components/SwarmParticipantContextMenu.qml +++ b/src/mainview/components/SwarmParticipantContextMenu.qml @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 by Savoir-faire Linux + * Copyright (C) 2022 by Savoir-faire Linux * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> * * This program is free software; you can redistribute it and/or modify @@ -29,44 +29,55 @@ ContextMenuAutoLoader { id: root property var conversationId: "" property var participantUri: "" - - // TODO get authorization + property var role property list<GeneralMenuItem> menuItems: [ GeneralMenuItem { id: startVideoCallItem itemName: JamiStrings.startVideoCall + canTrigger: ConversationsAdapter.dialogId(participantUri) !== "" + iconSource: JamiResources.videocam_24dp_svg onClicked: { + ConversationsAdapter.openDialogConversationWith(participantUri) + CallAdapter.placeCall() } }, GeneralMenuItem { id: startAudioCall itemName: JamiStrings.startAudioCall + canTrigger: ConversationsAdapter.dialogId(participantUri) !== "" + iconSource: JamiResources.place_audiocall_24dp_svg onClicked: { + ConversationsAdapter.openDialogConversationWith(participantUri) + CallAdapter.placeAudioOnlyCall() } }, GeneralMenuItem { id: goToConversation + iconSource: JamiResources.gotoconversation_svg itemName: JamiStrings.goToConversation onClicked: { + if (ConversationsAdapter.dialogId(participantUri) !== "") + ConversationsAdapter.openDialogConversationWith(participantUri) + else + ConversationsAdapter.setFilter(participantUri) } }, - GeneralMenuItem { - id: promoteAdministrator - canTrigger: false // No API yet - itemName: JamiStrings.promoteAdministrator - }, GeneralMenuItem { id: blockContact itemName: JamiStrings.blockContact iconSource: JamiResources.block_black_24dp_svg + onClicked: { + ContactAdapter.removeContact(participantUri, true) + } }, GeneralMenuItem { id: kickMember itemName: JamiStrings.kickMember + iconSource: JamiResources.kick_member_svg + canTrigger: role === Member.Role.ADMIN - // TODO can trigger (enough permission for self and member accepted) onClicked: { MessagesAdapter.removeConversationMember(conversationId, participantUri) } diff --git a/src/utils.cpp b/src/utils.cpp index 0d7d2b4df..65afcd260 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -376,7 +376,6 @@ Utils::contactPhoto(LRCInstance* instance, photo = Utils::fallbackAvatar("jami:" + contactInfo.profileInfo.uri, avatarName); } } catch (const std::exception& e) { - qDebug() << e.what() << "; Using default avatar"; photo = fallbackAvatar("jami:" + contactUri, QString(), size); } return Utils::scaleAndFrame(photo, size); @@ -401,14 +400,14 @@ Utils::conversationAvatar(LRCInstance* instance, return avatar; if (members.size() == 1) { // Only member in the swarm or 1:1, draw only peer's avatar - auto peerAvatar = Utils::contactPhoto(instance, members[0], size); + auto peerAvatar = Utils::contactPhoto(instance, members[0], size, ""); painter.drawImage(avatar.rect(), peerAvatar); return avatar; } // Else, combine avatars auto idx = 0; - auto peerAAvatar = Utils::contactPhoto(instance, members[0], size); - auto peerBAvatar = Utils::contactPhoto(instance, members[1], size); + auto peerAAvatar = Utils::contactPhoto(instance, members[0], size, ""); + auto peerBAvatar = Utils::contactPhoto(instance, members[1], size, ""); peerAAvatar = Utils::halfCrop(peerAAvatar, true); peerBAvatar = Utils::halfCrop(peerBAvatar, false); painter.drawImage(avatar.rect(), peerAAvatar); -- GitLab