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:
parent
5bfe0851cd
commit
4788e963a6
11 changed files with 98 additions and 20 deletions
|
@ -2,10 +2,12 @@
|
||||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<!-- 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"
|
<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">
|
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
|
<g>
|
||||||
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
|
<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
|
||||||
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
|
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
|
||||||
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
|
c0.5,0.6,0.8,1.4,0.8,2.3C20.8,18.7,19.1,20.5,16.9,20.5z"/>
|
||||||
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
|
<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
|
||||||
c2.2,2.6,2.7,6.2,1.3,9.2C19.5,16.4,19.1,17.1,18.5,17.7z"/>
|
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>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 1,020 B After Width: | Height: | Size: 1 KiB |
13
resources/icons/gotoconversation.svg
Normal file
13
resources/icons/gotoconversation.svg
Normal 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 |
14
resources/icons/kick_member.svg
Normal file
14
resources/icons/kick_member.svg
Normal 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 |
|
@ -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
|
void
|
||||||
ContactAdapter::connectSignals()
|
ContactAdapter::connectSignals()
|
||||||
{
|
{
|
||||||
|
|
|
@ -91,6 +91,7 @@ public:
|
||||||
Q_INVOKABLE QVariant getContactSelectableModel(int type);
|
Q_INVOKABLE QVariant getContactSelectableModel(int type);
|
||||||
Q_INVOKABLE void setSearchFilter(const QString& filter);
|
Q_INVOKABLE void setSearchFilter(const QString& filter);
|
||||||
Q_INVOKABLE void contactSelected(int index);
|
Q_INVOKABLE void contactSelected(int index);
|
||||||
|
Q_INVOKABLE void removeContact(const QString& peerUri, bool banContact);
|
||||||
|
|
||||||
void connectSignals();
|
void connectSignals();
|
||||||
|
|
||||||
|
@ -104,7 +105,8 @@ private:
|
||||||
|
|
||||||
QStringList defaultModerators_;
|
QStringList defaultModerators_;
|
||||||
|
|
||||||
bool hasDifferentMembers(const VectorString& currentMembers, const VectorString& convMembers) const;
|
bool hasDifferentMembers(const VectorString& currentMembers,
|
||||||
|
const VectorString& convMembers) const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void defaultModeratorsUpdated();
|
void defaultModeratorsUpdated();
|
||||||
|
|
|
@ -381,6 +381,7 @@ ConversationsAdapter::setFilter(const QString& filterString)
|
||||||
{
|
{
|
||||||
convModel_->setFilter(filterString);
|
convModel_->setFilter(filterString);
|
||||||
searchSrcModel_->setFilter(filterString);
|
searchSrcModel_->setFilter(filterString);
|
||||||
|
Q_EMIT textFilterChanged(filterString);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap
|
QVariantMap
|
||||||
|
@ -499,6 +500,24 @@ ConversationsAdapter::updateConversationDescription(const QString& convId,
|
||||||
convModel->updateConversationInfo(convId, details);
|
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
|
bool
|
||||||
ConversationsAdapter::connectConversationModel()
|
ConversationsAdapter::connectConversationModel()
|
||||||
{
|
{
|
||||||
|
|
|
@ -59,9 +59,12 @@ public:
|
||||||
Q_INVOKABLE void updateConversationDescription(const QString& convId,
|
Q_INVOKABLE void updateConversationDescription(const QString& convId,
|
||||||
const QString& newDescription);
|
const QString& newDescription);
|
||||||
|
|
||||||
|
Q_INVOKABLE QString dialogId(const QString& peerUri);
|
||||||
|
Q_INVOKABLE void openDialogConversationWith(const QString& peerUri);
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void showConversation(const QString& accountId, const QString& convUid);
|
void showConversation(const QString& accountId, const QString& convUid);
|
||||||
void showSearchStatus(const QString& status);
|
void showSearchStatus(const QString& status);
|
||||||
|
void textFilterChanged(const QString& text);
|
||||||
|
|
||||||
void navigateToWelcomePageRequested();
|
void navigateToWelcomePageRequested();
|
||||||
void conversationReady(const QString& convId);
|
void conversationReady(const QString& convId);
|
||||||
|
|
|
@ -156,6 +156,12 @@ Rectangle {
|
||||||
function onShowSearchStatus(status) {
|
function onShowSearchStatus(status) {
|
||||||
searchStatusText.text = 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 {
|
ColumnLayout {
|
||||||
|
|
|
@ -168,6 +168,7 @@ Rectangle {
|
||||||
|
|
||||||
SwarmParticipantContextMenu {
|
SwarmParticipantContextMenu {
|
||||||
id: contextMenu
|
id: contextMenu
|
||||||
|
role: UtilsAdapter.getParticipantRole(CurrentAccount.id, CurrentConversation.id, CurrentAccount.uri)
|
||||||
|
|
||||||
function openMenuAt(x, y, participantUri) {
|
function openMenuAt(x, y, participantUri) {
|
||||||
contextMenu.x = x
|
contextMenu.x = x
|
||||||
|
@ -187,6 +188,7 @@ Rectangle {
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
enabled: modelData != CurrentAccount.uri
|
||||||
acceptedButtons: Qt.RightButton
|
acceptedButtons: Qt.RightButton
|
||||||
onClicked: function (mouse) {
|
onClicked: function (mouse) {
|
||||||
contextMenu.openMenuAt(x + mouse.x, y + mouse.y, modelData)
|
contextMenu.openMenuAt(x + mouse.x, y + mouse.y, modelData)
|
||||||
|
|
|
@ -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>
|
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
@ -29,44 +29,55 @@ ContextMenuAutoLoader {
|
||||||
id: root
|
id: root
|
||||||
property var conversationId: ""
|
property var conversationId: ""
|
||||||
property var participantUri: ""
|
property var participantUri: ""
|
||||||
|
property var role
|
||||||
// TODO get authorization
|
|
||||||
|
|
||||||
property list<GeneralMenuItem> menuItems: [
|
property list<GeneralMenuItem> menuItems: [
|
||||||
GeneralMenuItem {
|
GeneralMenuItem {
|
||||||
id: startVideoCallItem
|
id: startVideoCallItem
|
||||||
itemName: JamiStrings.startVideoCall
|
itemName: JamiStrings.startVideoCall
|
||||||
|
canTrigger: ConversationsAdapter.dialogId(participantUri) !== ""
|
||||||
|
iconSource: JamiResources.videocam_24dp_svg
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
ConversationsAdapter.openDialogConversationWith(participantUri)
|
||||||
|
CallAdapter.placeCall()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GeneralMenuItem {
|
GeneralMenuItem {
|
||||||
id: startAudioCall
|
id: startAudioCall
|
||||||
itemName: JamiStrings.startAudioCall
|
itemName: JamiStrings.startAudioCall
|
||||||
|
canTrigger: ConversationsAdapter.dialogId(participantUri) !== ""
|
||||||
|
iconSource: JamiResources.place_audiocall_24dp_svg
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
ConversationsAdapter.openDialogConversationWith(participantUri)
|
||||||
|
CallAdapter.placeAudioOnlyCall()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GeneralMenuItem {
|
GeneralMenuItem {
|
||||||
id: goToConversation
|
id: goToConversation
|
||||||
|
|
||||||
|
iconSource: JamiResources.gotoconversation_svg
|
||||||
itemName: JamiStrings.goToConversation
|
itemName: JamiStrings.goToConversation
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
if (ConversationsAdapter.dialogId(participantUri) !== "")
|
||||||
|
ConversationsAdapter.openDialogConversationWith(participantUri)
|
||||||
|
else
|
||||||
|
ConversationsAdapter.setFilter(participantUri)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GeneralMenuItem {
|
|
||||||
id: promoteAdministrator
|
|
||||||
canTrigger: false // No API yet
|
|
||||||
itemName: JamiStrings.promoteAdministrator
|
|
||||||
},
|
|
||||||
GeneralMenuItem {
|
GeneralMenuItem {
|
||||||
id: blockContact
|
id: blockContact
|
||||||
itemName: JamiStrings.blockContact
|
itemName: JamiStrings.blockContact
|
||||||
iconSource: JamiResources.block_black_24dp_svg
|
iconSource: JamiResources.block_black_24dp_svg
|
||||||
|
onClicked: {
|
||||||
|
ContactAdapter.removeContact(participantUri, true)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
GeneralMenuItem {
|
GeneralMenuItem {
|
||||||
id: kickMember
|
id: kickMember
|
||||||
itemName: JamiStrings.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: {
|
onClicked: {
|
||||||
MessagesAdapter.removeConversationMember(conversationId, participantUri)
|
MessagesAdapter.removeConversationMember(conversationId, participantUri)
|
||||||
}
|
}
|
||||||
|
|
|
@ -376,7 +376,6 @@ Utils::contactPhoto(LRCInstance* instance,
|
||||||
photo = Utils::fallbackAvatar("jami:" + contactInfo.profileInfo.uri, avatarName);
|
photo = Utils::fallbackAvatar("jami:" + contactInfo.profileInfo.uri, avatarName);
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
qDebug() << e.what() << "; Using default avatar";
|
|
||||||
photo = fallbackAvatar("jami:" + contactUri, QString(), size);
|
photo = fallbackAvatar("jami:" + contactUri, QString(), size);
|
||||||
}
|
}
|
||||||
return Utils::scaleAndFrame(photo, size);
|
return Utils::scaleAndFrame(photo, size);
|
||||||
|
@ -401,14 +400,14 @@ Utils::conversationAvatar(LRCInstance* instance,
|
||||||
return avatar;
|
return avatar;
|
||||||
if (members.size() == 1) {
|
if (members.size() == 1) {
|
||||||
// Only member in the swarm or 1:1, draw only peer's avatar
|
// 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);
|
painter.drawImage(avatar.rect(), peerAvatar);
|
||||||
return avatar;
|
return avatar;
|
||||||
}
|
}
|
||||||
// Else, combine avatars
|
// Else, combine avatars
|
||||||
auto idx = 0;
|
auto idx = 0;
|
||||||
auto peerAAvatar = Utils::contactPhoto(instance, members[0], size);
|
auto peerAAvatar = Utils::contactPhoto(instance, members[0], size, "");
|
||||||
auto peerBAvatar = Utils::contactPhoto(instance, members[1], size);
|
auto peerBAvatar = Utils::contactPhoto(instance, members[1], size, "");
|
||||||
peerAAvatar = Utils::halfCrop(peerAAvatar, true);
|
peerAAvatar = Utils::halfCrop(peerAAvatar, true);
|
||||||
peerBAvatar = Utils::halfCrop(peerBAvatar, false);
|
peerBAvatar = Utils::halfCrop(peerBAvatar, false);
|
||||||
painter.drawImage(avatar.rect(), peerAAvatar);
|
painter.drawImage(avatar.rect(), peerAAvatar);
|
||||||
|
|
Loading…
Add table
Reference in a new issue