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

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
This commit is contained in:
Sébastien Blin 2022-01-28 16:54:15 -05:00
parent 5bfe0851cd
commit 4788e963a6
No known key found for this signature in database
GPG key ID: C894BB01EEB2A9A9
11 changed files with 98 additions and 20 deletions

View file

@ -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>

Before

Width:  |  Height:  |  Size: 1,020 B

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -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>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -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>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -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()
{

View file

@ -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();

View file

@ -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()
{

View file

@ -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);

View file

@ -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 {

View file

@ -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)

View file

@ -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)
}

View file

@ -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);