mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-07-24 09:25:33 +02:00
conference: change UI for moderator
- set UI according to sketch prototype - add moderation overlay menu instead of context menu - only update participants overlay when necessary - avoid minimum size for ResponsiveImage Gitlab: #207, #208 Change-Id: I65c9932319e55840518cbb0ce3cfa1a46e2275f0
This commit is contained in:
parent
18abbce09d
commit
0fa4fe6fe4
14 changed files with 646 additions and 365 deletions
9
images/icons/moderator.svg
Normal file
9
images/icons/moderator.svg
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Moderateur</title>
|
||||
<g id="Icones_Outline" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Moderateur" fill-rule="nonzero" stroke="#000000" stroke-width="1.3">
|
||||
<path d="M12.0856077,6.17414866 L15.2188905,10.399333 L21.4126996,6.23175137 L21.4126996,18.3907977 L2.73757526,18.3907977 L2.73757526,6.23175137 L8.92952352,10.3980809 L12.0856077,6.17414866 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 643 B |
2
qml.qrc
2
qml.qrc
|
@ -102,7 +102,6 @@
|
|||
<file>src/commoncomponents/GeneralMenuItem.qml</file>
|
||||
<file>src/mainview/components/ConversationSmartListContextMenu.qml</file>
|
||||
<file>src/mainview/components/CallViewContextMenu.qml</file>
|
||||
<file>src/mainview/components/ParticipantContextMenu.qml</file>
|
||||
<file>src/commoncomponents/GeneralMenuSeparator.qml</file>
|
||||
<file>src/mainview/components/UserProfile.qml</file>
|
||||
<file>src/mainview/js/videodevicecontextmenuitemcreation.js</file>
|
||||
|
@ -137,5 +136,6 @@
|
|||
<file>src/commoncomponents/ResponsiveImage.qml</file>
|
||||
<file>src/commoncomponents/PresenceIndicator.qml</file>
|
||||
<file>src/commoncomponents/AvatarImage.qml</file>
|
||||
<file>src/mainview/components/ParticipantOverlayMenu.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -135,5 +135,6 @@
|
|||
<file>images/icons/settings_backup_restore-24px.svg</file>
|
||||
<file>images/logo-jami-standard-coul.svg</file>
|
||||
<file>images/logo-jami-standard-coul-white.svg</file>
|
||||
<file>images/icons/moderator.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -399,6 +399,7 @@ CallAdapter::connectCallModel(const QString& accountId)
|
|||
const auto convInfo = LRCInstance::getConversationFromCallId(callId);
|
||||
if (!convInfo.uid.isEmpty()) {
|
||||
emit callStatusChanged(static_cast<int>(call.status), accountId, convInfo.uid);
|
||||
updateCallOverlay(convInfo);
|
||||
}
|
||||
|
||||
switch (call.status) {
|
||||
|
@ -531,45 +532,13 @@ CallAdapter::hangupCall(const QString& uri)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
callModel->hangUp(convInfo.callId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CallAdapter::maximizeParticipant(const QString& uri, bool isActive)
|
||||
{
|
||||
auto* callModel = LRCInstance::getAccountInfo(accountId_).callModel.get();
|
||||
auto* convModel = LRCInstance::getCurrentConversationModel();
|
||||
const auto conversation = convModel->getConversationForUID(LRCInstance::getCurrentConvUid());
|
||||
auto confId = conversation.confId;
|
||||
if (confId.isEmpty())
|
||||
confId = conversation.callId;
|
||||
try {
|
||||
const auto call = callModel->getCall(confId);
|
||||
switch (call.layout) {
|
||||
case lrc::api::call::Layout::GRID:
|
||||
callModel->setActiveParticipant(confId, uri);
|
||||
callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL);
|
||||
break;
|
||||
case lrc::api::call::Layout::ONE_WITH_SMALL:
|
||||
callModel->setActiveParticipant(confId, uri);
|
||||
callModel->setConferenceLayout(confId,
|
||||
isActive ? lrc::api::call::Layout::ONE
|
||||
: lrc::api::call::Layout::ONE_WITH_SMALL);
|
||||
break;
|
||||
case lrc::api::call::Layout::ONE:
|
||||
callModel->setActiveParticipant(confId, uri);
|
||||
callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID);
|
||||
break;
|
||||
};
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CallAdapter::minimizeParticipant()
|
||||
CallAdapter::maximizeParticipant(const QString& uri)
|
||||
{
|
||||
auto* callModel = LRCInstance::getAccountInfo(accountId_).callModel.get();
|
||||
auto* convModel = LRCInstance::getCurrentConversationModel();
|
||||
|
@ -579,16 +548,52 @@ CallAdapter::minimizeParticipant()
|
|||
confId = conversation.callId;
|
||||
try {
|
||||
auto call = callModel->getCall(confId);
|
||||
switch (call.layout) {
|
||||
case lrc::api::call::Layout::GRID:
|
||||
break;
|
||||
case lrc::api::call::Layout::ONE_WITH_SMALL:
|
||||
callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID);
|
||||
break;
|
||||
case lrc::api::call::Layout::ONE:
|
||||
callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL);
|
||||
break;
|
||||
};
|
||||
if (call.participantsInfos.size() > 0) {
|
||||
for (const auto& participant : call.participantsInfos) {
|
||||
if (participant["uri"] == uri) {
|
||||
if (participant["active"] == "false") {
|
||||
callModel->setActiveParticipant(confId, uri);
|
||||
callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL);
|
||||
} else if (participant["y"].toInt() != 0) {
|
||||
callModel->setActiveParticipant(confId, uri);
|
||||
callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE);
|
||||
} else {
|
||||
callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CallAdapter::minimizeParticipant(const QString& uri)
|
||||
{
|
||||
auto* callModel = LRCInstance::getAccountInfo(accountId_).callModel.get();
|
||||
auto* convModel = LRCInstance::getCurrentConversationModel();
|
||||
const auto conversation = convModel->getConversationForUID(LRCInstance::getCurrentConvUid());
|
||||
auto confId = conversation.confId;
|
||||
|
||||
if (confId.isEmpty())
|
||||
confId = conversation.callId;
|
||||
try {
|
||||
auto call = callModel->getCall(confId);
|
||||
if (call.participantsInfos.size() > 0) {
|
||||
for (const auto& participant : call.participantsInfos) {
|
||||
if (participant["uri"] == uri) {
|
||||
if (participant["active"] == "true") {
|
||||
if (participant["y"].toInt() == 0) {
|
||||
callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL);
|
||||
} else {
|
||||
callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
@ -626,7 +631,10 @@ CallAdapter::isCurrentHost() const
|
|||
if (!convInfo.uid.isEmpty()) {
|
||||
auto* callModel = LRCInstance::getAccountInfo(accountId_).callModel.get();
|
||||
try {
|
||||
auto call = callModel->getCall(convInfo.callId);
|
||||
auto confId = convInfo.confId;
|
||||
if (confId.isEmpty())
|
||||
confId = convInfo.callId;
|
||||
auto call = callModel->getCall(confId);
|
||||
if (call.participantsInfos.size() == 0) {
|
||||
return true;
|
||||
} else {
|
||||
|
@ -647,13 +655,12 @@ CallAdapter::participantIsHost(const QString& uri) const
|
|||
auto& accInfo = LRCInstance::getAccountInfo(accountId_);
|
||||
auto* callModel = accInfo.callModel.get();
|
||||
try {
|
||||
auto call = callModel->getCall(convInfo.callId);
|
||||
if (call.participantsInfos.size() == 0) {
|
||||
return (uri.isEmpty() || uri == accInfo.profileInfo.uri);
|
||||
if (isCurrentHost()) {
|
||||
return uri == accInfo.profileInfo.uri;
|
||||
} else {
|
||||
return !convInfo.confId.isEmpty()
|
||||
&& callModel->hasCall(convInfo.confId)
|
||||
&& (uri.isEmpty() || uri == accInfo.profileInfo.uri);
|
||||
auto call = callModel->getCall(convInfo.callId);
|
||||
auto peer = call.peerUri.remove("ring:");
|
||||
return (uri == peer);
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
|
@ -778,22 +785,6 @@ CallAdapter::isCurrentMuted() const
|
|||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
CallAdapter::getCurrentLayoutType() const
|
||||
{
|
||||
auto* convModel = LRCInstance::getCurrentConversationModel();
|
||||
const auto convInfo = convModel->getConversationForUID(convUid_);
|
||||
if (!convInfo.uid.isEmpty()) {
|
||||
auto* callModel = LRCInstance::getAccountInfo(accountId_).callModel.get();
|
||||
try {
|
||||
auto call = callModel->getCall(convInfo.confId);
|
||||
return static_cast<int>(call.layout);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
CallAdapter::holdThisCallToggle()
|
||||
{
|
||||
|
|
|
@ -53,15 +53,14 @@ public:
|
|||
* For Call Overlay
|
||||
*/
|
||||
Q_INVOKABLE void hangupCall(const QString& uri);
|
||||
Q_INVOKABLE void maximizeParticipant(const QString& uri, bool isActive);
|
||||
Q_INVOKABLE void minimizeParticipant();
|
||||
Q_INVOKABLE void maximizeParticipant(const QString& uri);
|
||||
Q_INVOKABLE void minimizeParticipant(const QString& uri);
|
||||
Q_INVOKABLE void hangUpThisCall();
|
||||
Q_INVOKABLE void setModerator(const QString& uri, const bool state);
|
||||
Q_INVOKABLE bool isCurrentHost() const;
|
||||
Q_INVOKABLE bool participantIsHost(const QString& uri = {}) const;
|
||||
Q_INVOKABLE bool participantIsHost(const QString& uri) const;
|
||||
Q_INVOKABLE void setModerator(const QString& uri, const bool state);
|
||||
Q_INVOKABLE bool isModerator(const QString& uri = {}) const;
|
||||
Q_INVOKABLE bool isCurrentModerator() const;
|
||||
Q_INVOKABLE int getCurrentLayoutType() const;
|
||||
Q_INVOKABLE void holdThisCallToggle();
|
||||
Q_INVOKABLE void muteThisCallToggle();
|
||||
Q_INVOKABLE void recordThisCallToggle();
|
||||
|
|
|
@ -62,8 +62,8 @@ Image {
|
|||
|
||||
function setSourceSize() {
|
||||
if (isSvg) {
|
||||
sourceSize.width = Math.max(24, width)
|
||||
sourceSize.height = Math.max(24, height)
|
||||
sourceSize.width = width
|
||||
sourceSize.height = height
|
||||
} else
|
||||
sourceSize = undefined
|
||||
}
|
||||
|
|
|
@ -397,4 +397,14 @@ Item {
|
|||
// Generic dialog options
|
||||
property string optionOk: qsTr("Ok")
|
||||
property string optionCancel: qsTr("Cancel")
|
||||
|
||||
// Conference moderation
|
||||
property string setModerator: qsTr("Set moderator")
|
||||
property string unsetModerator: qsTr("Unset moderator")
|
||||
property string muteParticipant: qsTr("Mute")
|
||||
property string unmuteParticipant: qsTr("Unmute")
|
||||
property string maximizeParticipant: qsTr("Maximize")
|
||||
property string minimizeParticipant: qsTr("Minimize")
|
||||
property string hangupParticipant: qsTr("Hangup")
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ Item {
|
|||
// General
|
||||
property color blackColor: "#000000"
|
||||
property color whiteColor: "#ffffff"
|
||||
property color darkGreyColor: "#272727"
|
||||
property color darkGreyColorOpacity: "#4D272727" // 77%
|
||||
property color transparentColor: "transparent"
|
||||
property color primaryForegroundColor: darkTheme? whiteColor : blackColor
|
||||
property color primaryBackgroundColor: darkTheme? bgDarkMode_ : whiteColor
|
||||
|
@ -91,6 +93,10 @@ Item {
|
|||
property color sipInputButtonHoverColor: "#4477aa"
|
||||
property color sipInputButtonPressColor: "#5588bb"
|
||||
|
||||
property string buttonConference: "#110000"
|
||||
property string buttonConferenceHovered: "#66cfff"
|
||||
property string buttonConferencePressed: "#66cfff"
|
||||
|
||||
// Wizard / account manager
|
||||
property color accountCreationOtherStepColor: "grey"
|
||||
property color accountCreationCurrentStepColor: "#28b1ed"
|
||||
|
@ -151,6 +157,7 @@ Item {
|
|||
property int textFontSize: 9
|
||||
property int settingsFontSize: 9
|
||||
property int buttonFontSize: 9
|
||||
property int participantFontSize: 10
|
||||
property int menuFontSize: 12
|
||||
property int headerFontSize: 13
|
||||
property int titleFontSize: 16
|
||||
|
|
|
@ -47,7 +47,8 @@ Rectangle {
|
|||
recordingRect.visible = isRecording
|
||||
}
|
||||
|
||||
function updateButtonStatus(isPaused, isAudioOnly, isAudioMuted, isVideoMuted, isRecording, isSIP, isConferenceCall) {
|
||||
function updateButtonStatus(isPaused, isAudioOnly, isAudioMuted, isVideoMuted,
|
||||
isRecording, isSIP, isConferenceCall) {
|
||||
callViewContextMenu.isSIP = isSIP
|
||||
callViewContextMenu.isPaused = isPaused
|
||||
callViewContextMenu.isAudioOnly = isAudioOnly
|
||||
|
@ -75,46 +76,116 @@ Rectangle {
|
|||
MediaHandlerPickerCreation.closeMediaHandlerPicker()
|
||||
}
|
||||
|
||||
// returns true if participant is not fully maximized
|
||||
function showMaximize(pX, pY, pW, pH) {
|
||||
// Hack: -1 offset added to avoid problems with odd sizes
|
||||
return (pX - distantRenderer.getXOffset() !== 0
|
||||
|| pY - distantRenderer.getYOffset() !== 0
|
||||
|| pW < (distantRenderer.width - distantRenderer.getXOffset() * 2 - 1)
|
||||
|| pH < (distantRenderer.height - distantRenderer.getYOffset() * 2 - 1))
|
||||
}
|
||||
|
||||
// returns true if participant takes renderer's width
|
||||
function showMinimize(pX, pW) {
|
||||
return (pX - distantRenderer.getXOffset() === 0
|
||||
&& pW >= distantRenderer.width - distantRenderer.getXOffset() * 2 - 1)
|
||||
}
|
||||
|
||||
|
||||
function handleParticipantsInfo(infos) {
|
||||
// TODO: in the future the conference layout should be entirely managed by the client
|
||||
videoCallOverlay.updateMenu()
|
||||
var isModerator = CallAdapter.isCurrentModerator()
|
||||
var isHost = CallAdapter.isCurrentHost()
|
||||
var showMax = false
|
||||
var showMin = false
|
||||
|
||||
var deletedUris = []
|
||||
var currentUris = []
|
||||
for (var p in participantOverlays) {
|
||||
if (participantOverlays[p])
|
||||
participantOverlays[p].destroy()
|
||||
if (participantOverlays[p]) {
|
||||
var participant = infos.find(e => e.uri === participantOverlays[p].uri);
|
||||
if (participant) {
|
||||
// Update participant's information
|
||||
var newX = distantRenderer.getXOffset()
|
||||
+ participant.x * distantRenderer.getScaledWidth()
|
||||
var newY = distantRenderer.getYOffset()
|
||||
+ participant.y * distantRenderer.getScaledHeight()
|
||||
var newWidth = participant.w * distantRenderer.getScaledWidth()
|
||||
var newHeight = participant.h * distantRenderer.getScaledHeight()
|
||||
var newVisible = participant.w !== 0 && participant.h !== 0
|
||||
|
||||
if (participantOverlays[p].x !== newX)
|
||||
participantOverlays[p].x = newX
|
||||
if (participantOverlays[p].y !== newY)
|
||||
participantOverlays[p].y = newY
|
||||
if (participantOverlays[p].width !== newWidth)
|
||||
participantOverlays[p].width = newWidth
|
||||
if (participantOverlays[p].height !== newHeight)
|
||||
participantOverlays[p].height = newHeight
|
||||
if (participantOverlays[p].visible !== newVisible)
|
||||
participantOverlays[p].visible = newVisible
|
||||
|
||||
showMax = showMaximize(participantOverlays[p].x,
|
||||
participantOverlays[p].y,
|
||||
participantOverlays[p].width,
|
||||
participantOverlays[p].height)
|
||||
showMin = showMinimize(participantOverlays[p].x,
|
||||
participantOverlays[p].width)
|
||||
|
||||
participantOverlays[p].setMenu(participant.uri, participant.bestName,
|
||||
participant.isLocal, showMax, showMin)
|
||||
if (participant.videoMuted)
|
||||
participantOverlays[p].setAvatar(participant.avatar)
|
||||
else
|
||||
participantOverlays[p].setAvatar("")
|
||||
currentUris.push(participantOverlays[p].uri)
|
||||
} else {
|
||||
// Participant is no longer in conference
|
||||
deletedUris.push(participantOverlays[p].uri)
|
||||
participantOverlays[p].destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
participantOverlays = []
|
||||
if (infos.length == 0) {
|
||||
participantOverlays = participantOverlays.filter(part => !deletedUris.includes(part.uri))
|
||||
|
||||
if (infos.length === 0) { // Return to normal call
|
||||
previewRenderer.visible = true
|
||||
for (var part in participantOverlays) {
|
||||
if (participantOverlays[part]) {
|
||||
participantOverlays[part].destroy()
|
||||
}
|
||||
}
|
||||
participantOverlays = []
|
||||
} else {
|
||||
previewRenderer.visible = false
|
||||
for (var infoVariant in infos) {
|
||||
var hover = participantComponent.createObject(callOverlayRectMouseArea, {
|
||||
x: distantRenderer.getXOffset() + infos[infoVariant].x * distantRenderer.getScaledWidth(),
|
||||
y: distantRenderer.getYOffset() + infos[infoVariant].y * distantRenderer.getScaledHeight(),
|
||||
width: infos[infoVariant].w * distantRenderer.getScaledWidth(),
|
||||
height: infos[infoVariant].h * distantRenderer.getScaledHeight(),
|
||||
visible: infos[infoVariant].w != 0 && infos[infoVariant].h != 0
|
||||
})
|
||||
if (!hover) {
|
||||
console.log("Error when creating the hover")
|
||||
return
|
||||
}
|
||||
// Only create overlay for new participants
|
||||
if (!currentUris.includes(infos[infoVariant].uri)) {
|
||||
var hover = participantComponent.createObject(callOverlayRectMouseArea, {
|
||||
x: distantRenderer.getXOffset() + infos[infoVariant].x * distantRenderer.getScaledWidth(),
|
||||
y: distantRenderer.getYOffset() + infos[infoVariant].y * distantRenderer.getScaledHeight(),
|
||||
width: infos[infoVariant].w * distantRenderer.getScaledWidth(),
|
||||
height: infos[infoVariant].h * distantRenderer.getScaledHeight(),
|
||||
visible: infos[infoVariant].w !== 0 && infos[infoVariant].h !== 0
|
||||
})
|
||||
if (!hover) {
|
||||
console.log("Error when creating the hover")
|
||||
return
|
||||
}
|
||||
|
||||
hover.setParticipantName(infos[infoVariant].bestName)
|
||||
hover.active = infos[infoVariant].active;
|
||||
hover.isLocal = infos[infoVariant].isLocal;
|
||||
hover.setMenuVisible(isModerator)
|
||||
hover.setEndCallVisible(isHost)
|
||||
hover.uri = infos[infoVariant].uri
|
||||
if (infos[infoVariant].videoMuted)
|
||||
hover.setAvatar(infos[infoVariant].avatar)
|
||||
else
|
||||
hover.setAvatar("")
|
||||
hover.injectedContextMenu = participantContextMenu
|
||||
participantOverlays.push(hover)
|
||||
showMax = showMaximize(hover.x, hover.y, hover.width, hover.height)
|
||||
showMin = showMinimize(hover.x, hover.width)
|
||||
|
||||
hover.setMenu(infos[infoVariant].uri, infos[infoVariant].bestName,
|
||||
infos[infoVariant].isLocal, showMax, showMin)
|
||||
if (infos[infoVariant].videoMuted)
|
||||
hover.setAvatar(infos[infoVariant].avatar)
|
||||
else
|
||||
hover.setAvatar("")
|
||||
participantOverlays.push(hover)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// x, y position does not need to be translated
|
||||
|
@ -477,8 +548,4 @@ Rectangle {
|
|||
MediaHandlerPickerCreation.openMediaHandlerPicker()
|
||||
}
|
||||
}
|
||||
|
||||
ParticipantContextMenu {
|
||||
id: participantContextMenu
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,22 +33,23 @@ Rectangle {
|
|||
// ButtonCounts here is to make sure that flow layout margin is calculated correctly,
|
||||
// since no other methods can make buttons at the layout center.
|
||||
property int buttonPreferredSize: 48
|
||||
property var isHost: true
|
||||
property var isModerator: true
|
||||
property var isSip: false
|
||||
|
||||
signal chatButtonClicked
|
||||
signal addToConferenceButtonClicked
|
||||
|
||||
function updateMenu() {
|
||||
root.isHost = CallAdapter.isCurrentHost()
|
||||
addToConferenceButton.visible = !root.isSip && root.isHost
|
||||
root.isModerator = CallAdapter.isCurrentModerator()
|
||||
addToConferenceButton.visible = !root.isSip && root.isModerator
|
||||
}
|
||||
|
||||
function setButtonStatus(isPaused, isAudioOnly, isAudioMuted, isVideoMuted, isRecording, isSIP, isConferenceCall) {
|
||||
root.isHost = CallAdapter.isCurrentModerator()
|
||||
function setButtonStatus(isPaused, isAudioOnly, isAudioMuted, isVideoMuted,
|
||||
isRecording, isSIP, isConferenceCall) {
|
||||
root.isModerator = CallAdapter.isCurrentModerator()
|
||||
root.isSip = isSIP
|
||||
noVideoButton.visible = !isAudioOnly
|
||||
addToConferenceButton.visible = !isSIP && isHost
|
||||
addToConferenceButton.visible = !root.isSIP && root.isModerator
|
||||
|
||||
noMicButton.checked = isAudioMuted
|
||||
noVideoButton.checked = isVideoMuted
|
||||
|
@ -150,7 +151,7 @@ Rectangle {
|
|||
|
||||
Layout.preferredWidth: buttonPreferredSize
|
||||
Layout.preferredHeight: buttonPreferredSize
|
||||
visible: !isHost
|
||||
visible: !isModerator
|
||||
|
||||
pressedColor: JamiTheme.invertedPressedButtonColor
|
||||
hoveredColor: JamiTheme.invertedHoveredButtonColor
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2020 by Savoir-faire Linux
|
||||
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14
|
||||
import QtGraphicalEffects 1.14
|
||||
import net.jami.Models 1.0
|
||||
import net.jami.Constants 1.0
|
||||
|
||||
import "../../commoncomponents"
|
||||
import "../../commoncomponents/js/contextmenugenerator.js" as ContextMenuGenerator
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var uri: ""
|
||||
property var maximized: true
|
||||
property var active: true
|
||||
property var showHangup: false
|
||||
property var showMaximize: false
|
||||
property var showMinimize: false
|
||||
property var showSetModerator: false
|
||||
property var showUnsetModerator: false
|
||||
property var showMute: false
|
||||
property var showUnmute: false
|
||||
|
||||
function openMenu(){
|
||||
ContextMenuGenerator.initMenu()
|
||||
if (showHangup)
|
||||
ContextMenuGenerator.addMenuItem(JamiStrings.hangup,
|
||||
"qrc:/images/icons/ic_call_end_white_24px.svg",
|
||||
function (){
|
||||
CallAdapter.hangupCall(uri)
|
||||
})
|
||||
|
||||
if (showMaximize)
|
||||
ContextMenuGenerator.addMenuItem(qsTr("Maximize participant"),
|
||||
"qrc:/images/icons/open_in_full-24px.svg",
|
||||
function (){
|
||||
CallAdapter.maximizeParticipant(uri, active)
|
||||
})
|
||||
if (showMinimize)
|
||||
ContextMenuGenerator.addMenuItem(qsTr("Minimize participant"),
|
||||
"qrc:/images/icons/close_fullscreen-24px.svg",
|
||||
function (){
|
||||
CallAdapter.minimizeParticipant()
|
||||
})
|
||||
|
||||
if (showSetModerator)
|
||||
ContextMenuGenerator.addMenuItem(qsTr("Set moderator"),
|
||||
"qrc:/images/icons/person_add-24px.svg",
|
||||
function (){
|
||||
CallAdapter.setModerator(uri, true)
|
||||
})
|
||||
|
||||
if (showUnsetModerator)
|
||||
ContextMenuGenerator.addMenuItem(qsTr("Unset moderator"),
|
||||
"qrc:/images/icons/round-close-24px.svg",
|
||||
function (){
|
||||
CallAdapter.setModerator(uri, false)
|
||||
})
|
||||
|
||||
if (showMute)
|
||||
ContextMenuGenerator.addMenuItem(qsTr("Mute participant"),
|
||||
"qrc:/images/icons/mic_off-24px.svg",
|
||||
function (){
|
||||
CallAdapter.muteParticipant(uri, true)
|
||||
})
|
||||
|
||||
if (showUnmute)
|
||||
ContextMenuGenerator.addMenuItem(qsTr("Unmute participant"),
|
||||
"qrc:/images/icons/mic-24px.svg",
|
||||
function (){
|
||||
CallAdapter.muteParticipant(uri, false)
|
||||
})
|
||||
|
||||
|
||||
root.height = ContextMenuGenerator.getMenu().height
|
||||
root.width = ContextMenuGenerator.getMenu().width
|
||||
ContextMenuGenerator.getMenu().open()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
ContextMenuGenerator.createBaseContextMenuObjects(root)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Copyright (C) 2020 by Savoir-faire Linux
|
||||
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
* Author: Albert Babí <albert.babi@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -19,6 +20,7 @@
|
|||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
import QtQuick.Shapes 1.14
|
||||
import QtQuick.Controls.Universal 2.14
|
||||
import QtGraphicalEffects 1.14
|
||||
import net.jami.Models 1.0
|
||||
|
@ -29,190 +31,215 @@ import "../../commoncomponents"
|
|||
Rectangle {
|
||||
id: root
|
||||
|
||||
property int buttonPreferredSize: 12
|
||||
property var uri: ""
|
||||
property var active: true
|
||||
property var isLocal: true
|
||||
property var showEndCall: true
|
||||
property var injectedContextMenu: null
|
||||
// svg path for the background participant shape (width is offset dependant)
|
||||
property int offset: indicatorsRowLayout.width
|
||||
property int shapeHeight: 16
|
||||
property string pathShape: "M 0.0,%8
|
||||
C 0.0,%8 %1,%8 %1,%8 %2,%8 %3,%9 %4,10.0 %5,5.0 %5,0.0 %6,0.0 %7,0.0 %4,0.0
|
||||
0.0,0.0 0.0,0.0 0.0,%8 0.0,%8 Z".arg(offset).arg(4.0+offset).arg(7+offset)
|
||||
.arg(9+offset).arg(11+offset).arg(15+offset).arg(18+offset).arg(shapeHeight)
|
||||
.arg(shapeHeight-2)
|
||||
|
||||
function setParticipantName(name) {
|
||||
participantName.text = name
|
||||
}
|
||||
// TODO: properties should be
|
||||
property string uri: overlayMenu.uri
|
||||
property bool participantIsModerator: false
|
||||
property bool participantIsMuted: false
|
||||
|
||||
// TODO: try to use AvatarImage as well
|
||||
function setAvatar(avatar) {
|
||||
if (avatar === "") {
|
||||
opacity = 0
|
||||
contactImage.source = ""
|
||||
} else {
|
||||
opacity = 1
|
||||
contactImage.source = "data:image/png;base64," + avatar
|
||||
}
|
||||
}
|
||||
|
||||
function setMenuVisible(isVisible) {
|
||||
optionsButton.visible = isVisible
|
||||
function setMenu(newUri, bestName, isLocal, showMax, showMin) {
|
||||
|
||||
overlayMenu.uri = newUri
|
||||
overlayMenu.bestName = bestName
|
||||
|
||||
var isHost = CallAdapter.isCurrentHost()
|
||||
var isModerator = CallAdapter.isCurrentModerator()
|
||||
var participantIsHost = CallAdapter.participantIsHost(overlayMenu.uri)
|
||||
participantIsModerator = CallAdapter.isModerator(overlayMenu.uri)
|
||||
overlayMenu.showSetModerator = isHost && !isLocal && !participantIsModerator
|
||||
overlayMenu.showUnsetModerator = isHost && !isLocal && participantIsModerator
|
||||
|
||||
participantIsMuted = CallAdapter.isMuted(overlayMenu.uri)
|
||||
overlayMenu.showMute = isModerator && !participantIsMuted
|
||||
overlayMenu.showUnmute = isModerator && participantIsMuted && isLocal
|
||||
overlayMenu.showMaximize = isModerator && showMax
|
||||
overlayMenu.showMinimize = isModerator && showMin
|
||||
overlayMenu.showHangup = isModerator && !isLocal && !participantIsHost
|
||||
}
|
||||
|
||||
function setEndCallVisible(isVisible) {
|
||||
showEndCall = isVisible
|
||||
}
|
||||
|
||||
border.width: 1
|
||||
opacity: 0
|
||||
color: "transparent"
|
||||
z: 1
|
||||
|
||||
MouseArea {
|
||||
id: mouseAreaHover
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
acceptedButtons: Qt.LeftButton
|
||||
// Participant header with moderator / mute indicators
|
||||
Rectangle {
|
||||
id: participantIndicators
|
||||
width: indicatorsRowLayout.width
|
||||
height: shapeHeight
|
||||
visible: participantIsModerator || participantIsMuted
|
||||
color: "transparent"
|
||||
|
||||
Image {
|
||||
id: contactImage
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
height: Math.min(parent.width / 2, parent.height / 2)
|
||||
width: Math.min(parent.width / 2, parent.height / 2)
|
||||
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: ""
|
||||
asynchronous: true
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle{
|
||||
width: contactImage.width
|
||||
height: contactImage.height
|
||||
radius: {
|
||||
var size = ((contactImage.width <= contactImage.height)? contactImage.width:contactImage.height)
|
||||
return size /2
|
||||
}
|
||||
}
|
||||
Shape {
|
||||
id: myShape
|
||||
ShapePath {
|
||||
id: backgroundShape
|
||||
strokeColor: "transparent"
|
||||
fillColor: JamiTheme.darkGreyColorOpacity
|
||||
capStyle: ShapePath.RoundCap
|
||||
PathSvg { path: pathShape }
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: bottomLabel
|
||||
id: indicatorsRowLayout
|
||||
height: parent.height
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
height: 24
|
||||
width: parent.width
|
||||
anchors.bottom: parent.bottom
|
||||
ResponsiveImage {
|
||||
id: isModeratorIndicator
|
||||
|
||||
Rectangle {
|
||||
color: "black"
|
||||
opacity: 0.8
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: parent.height
|
||||
visible: participantIsModerator
|
||||
|
||||
Text {
|
||||
id: participantName
|
||||
anchors.fill: parent
|
||||
leftPadding: 8.0
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: 6
|
||||
containerHeight: 12
|
||||
containerWidth: 12
|
||||
|
||||
TextMetrics {
|
||||
id: participantMetrics
|
||||
elide: Text.ElideRight
|
||||
elideWidth: bottomLabel.width - 8
|
||||
}
|
||||
|
||||
text: participantMetrics.elidedText
|
||||
|
||||
color: "white"
|
||||
font.pointSize: JamiTheme.textFontSize
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
Button {
|
||||
id: optionsButton
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
|
||||
icon.color: "white"
|
||||
icon.height: buttonPreferredSize
|
||||
icon.width: buttonPreferredSize
|
||||
icon.source: "qrc:/images/icons/more_vert-24px.svg"
|
||||
|
||||
onClicked: {
|
||||
if (!injectedContextMenu) {
|
||||
console.log("Participant's overlay don't have any injected context menu")
|
||||
return
|
||||
}
|
||||
var mousePos = mapToItem(videoCallPageRect, parent.x, parent.y)
|
||||
var layout = CallAdapter.getCurrentLayoutType()
|
||||
var showMaximized = layout !== 2
|
||||
var showMinimized = !(layout === 0 || (layout === 1 && !active))
|
||||
var isModerator = CallAdapter.isModerator(uri)
|
||||
var isHost = CallAdapter.isCurrentHost()
|
||||
var participantIsHost = CallAdapter.participantIsHost(uri)
|
||||
var isMuted = CallAdapter.isMuted(uri)
|
||||
injectedContextMenu.showHangup = !root.isLocal && showEndCall
|
||||
injectedContextMenu.showMaximize = showMaximized
|
||||
injectedContextMenu.showMinimize = showMinimized
|
||||
injectedContextMenu.uri = uri
|
||||
injectedContextMenu.active = active
|
||||
injectedContextMenu.x = mousePos.x
|
||||
injectedContextMenu.y = mousePos.y - injectedContextMenu.height
|
||||
injectedContextMenu.showSetModerator = (isHost && !participantIsHost && !isModerator)
|
||||
injectedContextMenu.showUnsetModerator = (isHost && !participantIsHost && isModerator)
|
||||
injectedContextMenu.showMute = !isMuted
|
||||
injectedContextMenu.showUnmute = isMuted && root.isLocal
|
||||
injectedContextMenu.openMenu()
|
||||
}
|
||||
source: "qrc:/images/icons/moderator.svg"
|
||||
layer {
|
||||
enabled: true
|
||||
effect: ColorOverlay { color: JamiTheme.whiteColor }
|
||||
mipmap: false
|
||||
smooth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
CallAdapter.maximizeParticipant(uri, active)
|
||||
}
|
||||
ResponsiveImage {
|
||||
id: isMutedIndicator
|
||||
|
||||
onEntered: {
|
||||
if (contactImage.status === Image.Null)
|
||||
root.state = "entered"
|
||||
}
|
||||
visible: participantIsMuted
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: 6
|
||||
containerHeight: 12
|
||||
containerWidth: 12
|
||||
|
||||
onExited: {
|
||||
if (contactImage.status === Image.Null)
|
||||
root.state = "exited"
|
||||
source: "qrc:/images/icons/mic_off-24px.svg"
|
||||
layer {
|
||||
enabled: true
|
||||
effect: ColorOverlay { color: JamiTheme.whiteColor }
|
||||
mipmap: false
|
||||
smooth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "entered"
|
||||
PropertyChanges {
|
||||
target: root
|
||||
opacity: 1
|
||||
// Participant background, mousearea, hover and buttons for moderation
|
||||
Rectangle {
|
||||
id: participantRect
|
||||
|
||||
anchors.fill: parent
|
||||
opacity: 0
|
||||
color: JamiTheme.darkGreyColorOpacity
|
||||
z: 1
|
||||
|
||||
MouseArea {
|
||||
id: mouseAreaHover
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: false
|
||||
acceptedButtons: Qt.LeftButton
|
||||
|
||||
Image {
|
||||
id: contactImage
|
||||
|
||||
anchors.centerIn: parent
|
||||
height: Math.min(parent.width / 2, parent.height / 2)
|
||||
width: Math.min(parent.width / 2, parent.height / 2)
|
||||
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: ""
|
||||
asynchronous: true
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle{
|
||||
width: contactImage.width
|
||||
height: contactImage.height
|
||||
radius: {
|
||||
var size = ((contactImage.width <= contactImage.height)?
|
||||
contactImage.width : contactImage.height)
|
||||
return size / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
layer.mipmap: false
|
||||
layer.smooth: true
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "exited"
|
||||
PropertyChanges {
|
||||
target: root
|
||||
opacity: 0
|
||||
|
||||
ParticipantOverlayMenu {
|
||||
id: overlayMenu
|
||||
visible: participantRect.opacity !== 0
|
||||
anchors.centerIn: parent
|
||||
hasMinimumSize: root.width > minimumWidth && root.height > minimumHeight
|
||||
|
||||
onMouseAreaExited: {
|
||||
if (contactImage.status === Image.Null) {
|
||||
root.z = 1
|
||||
participantRect.state = "exited"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
CallAdapter.maximizeParticipant(uri)
|
||||
}
|
||||
|
||||
onEntered: {
|
||||
if (contactImage.status === Image.Null) {
|
||||
root.z = 2
|
||||
participantRect.state = "entered"
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
if (contactImage.status === Image.Null) {
|
||||
root.z = 1
|
||||
participantRect.state = "exited"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: Transition {
|
||||
PropertyAnimation {
|
||||
target: root
|
||||
property: "opacity"
|
||||
duration: 500
|
||||
states: [
|
||||
State {
|
||||
name: "entered"
|
||||
PropertyChanges {
|
||||
target: participantRect
|
||||
opacity: 1
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "exited"
|
||||
PropertyChanges {
|
||||
target: participantRect
|
||||
opacity: 0
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: Transition {
|
||||
PropertyAnimation {
|
||||
target: participantRect
|
||||
property: "opacity"
|
||||
duration: 500
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
271
src/mainview/components/ParticipantOverlayMenu.qml
Normal file
271
src/mainview/components/ParticipantOverlayMenu.qml
Normal file
|
@ -0,0 +1,271 @@
|
|||
/*
|
||||
* Copyright (C) 2020 by Savoir-faire Linux
|
||||
* Author: Albert Babí <albert.babi@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14
|
||||
import QtGraphicalEffects 1.14
|
||||
import QtQuick.Layouts 1.14
|
||||
import net.jami.Models 1.0
|
||||
import net.jami.Constants 1.0
|
||||
|
||||
import "../../commoncomponents"
|
||||
|
||||
// Overlay menu for conference moderation
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool hasMinimumSize: true
|
||||
property int buttonPreferredSize: 30
|
||||
property int minimumWidth: Math.max(114, visibleButtons * 37 + 21 * 2)
|
||||
property int minimumHeight: 114
|
||||
property int visibleButtons: toggleModerator.visible
|
||||
+ toggleMute.visible
|
||||
+ maximizeParticipant.visible
|
||||
+ minimizeParticipant.visible
|
||||
+ hangupParticipant.visible
|
||||
|
||||
property string uri: ""
|
||||
property string bestName: ""
|
||||
property bool showSetModerator: false
|
||||
property bool showUnsetModerator: false
|
||||
property bool showMute: false
|
||||
property bool showUnmute: false
|
||||
property bool showMaximize: false
|
||||
property bool showMinimize: false
|
||||
property bool showHangup: false
|
||||
|
||||
signal mouseAreaExited
|
||||
|
||||
// values taken from sketch
|
||||
width: hasMinimumSize? parent.width : minimumWidth
|
||||
height: hasMinimumSize? parent.height: minimumHeight
|
||||
|
||||
color: hasMinimumSize? "transparent" : JamiTheme.darkGreyColorOpacity
|
||||
radius: 10
|
||||
|
||||
MouseArea {
|
||||
id: mouseAreaHover
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
acceptedButtons: Qt.LeftButton
|
||||
|
||||
onExited: mouseAreaExited()
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
id: participantName
|
||||
|
||||
TextMetrics {
|
||||
id: participantMetrics
|
||||
text: bestName
|
||||
elide: Text.ElideRight
|
||||
elideWidth: root.width - JamiTheme.preferredMarginSize * 2
|
||||
}
|
||||
|
||||
text: participantMetrics.elidedText
|
||||
color: JamiTheme.whiteColor
|
||||
font.pointSize: JamiTheme.participantFontSize
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: rowLayoutButtons
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
Layout.fillWidth: true
|
||||
spacing: 7
|
||||
|
||||
PushButton {
|
||||
id: toggleModerator
|
||||
|
||||
visible: (showSetModerator || showUnsetModerator)
|
||||
Layout.preferredWidth: buttonPreferredSize
|
||||
Layout.preferredHeight: buttonPreferredSize
|
||||
preferredSize: 16
|
||||
normalColor: JamiTheme.buttonConference
|
||||
hoveredColor: JamiTheme.buttonConferenceHovered
|
||||
pressedColor: JamiTheme.buttonConferencePressed
|
||||
|
||||
source: "qrc:/images/icons/moderator.svg"
|
||||
imageColor: hovered? JamiTheme.darkGreyColor
|
||||
: JamiTheme.whiteColor
|
||||
|
||||
onClicked: CallAdapter.setModerator(uri, showSetModerator)
|
||||
onHoveredChanged: toggleModeratorToolTip.visible = hovered
|
||||
|
||||
Text {
|
||||
id: toggleModeratorToolTip
|
||||
|
||||
visible: false
|
||||
width: parent.width
|
||||
text: showSetModerator? JamiStrings.setModerator
|
||||
: JamiStrings.unsetModerator
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.top: parent.bottom
|
||||
anchors.topMargin: 6
|
||||
color: JamiTheme.whiteColor
|
||||
font.pointSize: JamiTheme.tinyFontSize
|
||||
}
|
||||
}
|
||||
|
||||
PushButton {
|
||||
id: toggleMute
|
||||
|
||||
visible: showMute || showUnmute
|
||||
Layout.preferredWidth: buttonPreferredSize
|
||||
Layout.preferredHeight: buttonPreferredSize
|
||||
preferredSize: 16
|
||||
|
||||
normalColor: JamiTheme.buttonConference
|
||||
hoveredColor: JamiTheme.buttonConferenceHovered
|
||||
pressedColor: JamiTheme.buttonConferencePressed
|
||||
|
||||
source: showMute? "qrc:/images/icons/mic-24px.svg"
|
||||
: "qrc:/images/icons/mic_off-24px.svg"
|
||||
imageColor: hovered? JamiTheme.darkGreyColor
|
||||
: JamiTheme.whiteColor
|
||||
|
||||
onClicked: CallAdapter.muteParticipant(uri, showMute)
|
||||
onHoveredChanged: toggleParticipantToolTip.visible = hovered
|
||||
|
||||
Text {
|
||||
id: toggleParticipantToolTip
|
||||
|
||||
visible: false
|
||||
width: parent.width
|
||||
text: showMute? JamiStrings.muteParticipant
|
||||
: JamiStrings.unmuteParticipant
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignTop
|
||||
|
||||
anchors.top: parent.bottom
|
||||
anchors.topMargin: 6
|
||||
color: JamiTheme.whiteColor
|
||||
font.pointSize: JamiTheme.tinyFontSize
|
||||
}
|
||||
}
|
||||
|
||||
PushButton {
|
||||
id: maximizeParticipant
|
||||
|
||||
visible: showMaximize
|
||||
Layout.preferredWidth: buttonPreferredSize
|
||||
Layout.preferredHeight: buttonPreferredSize
|
||||
preferredSize: 16
|
||||
|
||||
normalColor: JamiTheme.buttonConference
|
||||
hoveredColor: JamiTheme.buttonConferenceHovered
|
||||
pressedColor: JamiTheme.buttonConferencePressed
|
||||
|
||||
source: "qrc:/images/icons/open_in_full-24px.svg"
|
||||
imageColor: hovered? JamiTheme.darkGreyColor
|
||||
: JamiTheme.whiteColor
|
||||
|
||||
onClicked: CallAdapter.maximizeParticipant(uri)
|
||||
onHoveredChanged: maximizeParticipantToolTip.visible = hovered
|
||||
|
||||
Text {
|
||||
id: maximizeParticipantToolTip
|
||||
|
||||
visible: false
|
||||
width: parent.width
|
||||
text: JamiStrings.maximizeParticipant
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.top: parent.bottom
|
||||
anchors.topMargin: 6
|
||||
color: JamiTheme.whiteColor
|
||||
font.pointSize: JamiTheme.tinyFontSize
|
||||
}
|
||||
}
|
||||
|
||||
PushButton {
|
||||
id: minimizeParticipant
|
||||
|
||||
visible: showMinimize
|
||||
Layout.preferredWidth: buttonPreferredSize
|
||||
Layout.preferredHeight: buttonPreferredSize
|
||||
preferredSize: 16
|
||||
|
||||
normalColor: JamiTheme.buttonConference
|
||||
hoveredColor: JamiTheme.buttonConferenceHovered
|
||||
pressedColor: JamiTheme.buttonConferencePressed
|
||||
|
||||
source: "qrc:/images/icons/close_fullscreen-24px.svg"
|
||||
imageColor: hovered? JamiTheme.darkGreyColor
|
||||
: JamiTheme.whiteColor
|
||||
onClicked: CallAdapter.minimizeParticipant(uri)
|
||||
onHoveredChanged: minimizeParticipantToolTip.visible = hovered
|
||||
|
||||
Text {
|
||||
id: minimizeParticipantToolTip
|
||||
|
||||
visible: false
|
||||
width: parent.width
|
||||
text: JamiStrings.minimizeParticipant
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.top: parent.bottom
|
||||
anchors.topMargin: 6
|
||||
color: JamiTheme.whiteColor
|
||||
font.pointSize: JamiTheme.tinyFontSize
|
||||
}
|
||||
}
|
||||
|
||||
PushButton {
|
||||
id: hangupParticipant
|
||||
|
||||
visible: showHangup
|
||||
Layout.preferredWidth: buttonPreferredSize
|
||||
Layout.preferredHeight: buttonPreferredSize
|
||||
preferredSize: 16
|
||||
|
||||
normalColor: JamiTheme.buttonConference
|
||||
hoveredColor: JamiTheme.buttonConferenceHovered
|
||||
pressedColor: JamiTheme.buttonConferencePressed
|
||||
|
||||
source: "qrc:/images/icons/ic_block_24px.svg"
|
||||
imageColor: hovered? JamiTheme.darkGreyColor
|
||||
: JamiTheme.whiteColor
|
||||
onClicked: CallAdapter.hangupCall(uri)
|
||||
onHoveredChanged: hangupParticipantToolTip.visible = hovered
|
||||
|
||||
Text {
|
||||
id: hangupParticipantToolTip
|
||||
|
||||
visible: false
|
||||
width: parent.width
|
||||
text: JamiStrings.hangupParticipant
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.top: parent.bottom
|
||||
anchors.topMargin: 6
|
||||
color: JamiTheme.whiteColor
|
||||
font.pointSize: JamiTheme.tinyFontSize
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -191,6 +191,7 @@ Rectangle {
|
|||
isRecording, isSIP,
|
||||
isConferenceCall)
|
||||
videoCallPageRect.bestName = bestName
|
||||
videoCallOverlay.handleParticipantsInfo(CallAdapter.getConferencesInfos())
|
||||
}
|
||||
|
||||
function onShowOnHoldLabel(isPaused) {
|
||||
|
|
Loading…
Add table
Reference in a new issue