mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-04-21 13:42:05 +02:00
videocallpage: handle conference infos
Change-Id: I4cfc7c9c525c66f4e483089864bec059c388a1bd
This commit is contained in:
parent
8940f3c46e
commit
6607e0e9cf
14 changed files with 629 additions and 20 deletions
|
@ -1,6 +1,6 @@
|
|||
[gerrit]
|
||||
host=gerrit-ring.savoirfairelinux.com
|
||||
port=29420
|
||||
project=ring-client-window.git
|
||||
project=jami-client-qt.git
|
||||
defaultremote=origin
|
||||
defaultbranch=master
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<svg fill="#ffffff" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 9c-1.6 0-3.15.25-4.6.72v3.1c0 .39-.23.74-.56.9-.98.49-1.87 1.12-2.66 1.85-.18.18-.43.28-.7.28-.28 0-.53-.11-.71-.29L.29 13.08c-.18-.17-.29-.42-.29-.7 0-.28.11-.53.29-.71C3.34 8.78 7.46 7 12 7s8.66 1.78 11.71 4.67c.18.18.29.43.29.71 0 .28-.11.53-.29.71l-2.48 2.48c-.18.18-.43.29-.71.29-.27 0-.52-.11-.7-.28-.79-.74-1.69-1.36-2.67-1.85-.33-.16-.56-.5-.56-.9v-3.1C15.15 9.25 13.6 9 12 9z"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 553 B After Width: | Height: | Size: 538 B |
2
qml.qrc
2
qml.qrc
|
@ -67,6 +67,7 @@
|
|||
<file>src/mainview/components/ContactSearchBar.qml</file>
|
||||
<file>src/mainview/components/VideoCallPage.qml</file>
|
||||
<file>src/mainview/components/CallAdvancedOptions.qml</file>
|
||||
<file>src/mainview/components/ParticipantOverlay.qml</file>
|
||||
<file>src/mainview/components/ChangeLogScrollView.qml</file>
|
||||
<file>src/mainview/components/ProjectCreditsScrollView.qml</file>
|
||||
<file>src/mainview/components/AccountComboBoxPopup.qml</file>
|
||||
|
@ -77,6 +78,7 @@
|
|||
<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>
|
||||
|
|
|
@ -37,7 +37,7 @@ CallAdapter::~CallAdapter() {}
|
|||
void
|
||||
CallAdapter::initQmlObject()
|
||||
{
|
||||
connectCallStatusChanged(LRCInstance::getCurrAccId());
|
||||
connectCallModel(LRCInstance::getCurrAccId());
|
||||
|
||||
connect(&LRCInstance::behaviorController(),
|
||||
&BehaviorController::showIncomingCallView,
|
||||
|
@ -233,12 +233,91 @@ CallAdapter::shouldShowPreview(bool force)
|
|||
return shouldShowPreview;
|
||||
}
|
||||
|
||||
QVariantList
|
||||
CallAdapter::getConferencesInfos()
|
||||
{
|
||||
QVariantList map;
|
||||
auto convInfo = LRCInstance::getConversationFromConvUid(convUid_, accountId_);
|
||||
if (convInfo.uid.isEmpty())
|
||||
return map;
|
||||
auto callId = convInfo.confId.isEmpty()? convInfo.callId : convInfo.confId;
|
||||
if (!callId.isEmpty()) {
|
||||
try {
|
||||
auto call = LRCInstance::getCurrentCallModel()->getCall(callId);
|
||||
for (const auto& participant: call.participantsInfos) {
|
||||
QJsonObject data;
|
||||
data["x"] = participant["x"].toInt();
|
||||
data["y"] = participant["y"].toInt();
|
||||
data["w"] = participant["w"].toInt();
|
||||
data["h"] = participant["h"].toInt();
|
||||
data["active"] = participant["active"] == "true";
|
||||
auto bestName = participant["uri"];
|
||||
auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId_);
|
||||
data["isLocal"] = false;
|
||||
if (bestName == accInfo.profileInfo.uri) {
|
||||
bestName = tr("me");
|
||||
data["isLocal"] = true;
|
||||
} else {
|
||||
try {
|
||||
auto &contact = LRCInstance::getCurrentAccountInfo().contactModel->getContact(participant["uri"]);
|
||||
bestName = Utils::bestNameForContact(contact);
|
||||
} catch (...) {}
|
||||
}
|
||||
data["bestName"] = bestName;
|
||||
|
||||
map.push_back(QVariant(data));
|
||||
}
|
||||
return map;
|
||||
} catch (...) {}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
void
|
||||
CallAdapter::connectCallStatusChanged(const QString &accountId)
|
||||
CallAdapter::connectCallModel(const QString &accountId)
|
||||
{
|
||||
auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId);
|
||||
|
||||
QObject::disconnect(callStatusChangedConnection_);
|
||||
QObject::disconnect(onParticipantsChangedConnection_);
|
||||
|
||||
onParticipantsChangedConnection_ = QObject::connect(
|
||||
accInfo.callModel.get(),
|
||||
&lrc::api::NewCallModel::onParticipantsChanged,
|
||||
[this, accountId](const QString &confId) {
|
||||
auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId);
|
||||
auto &callModel = accInfo.callModel;
|
||||
auto call = callModel->getCall(confId);
|
||||
auto convInfo = LRCInstance::getConversationFromCallId(confId);
|
||||
if (!convInfo.uid.isEmpty()) {
|
||||
// Convert to QML
|
||||
QVariantList map;
|
||||
for (const auto& participant: call.participantsInfos) {
|
||||
QJsonObject data;
|
||||
data["x"] = participant["x"].toInt();
|
||||
data["y"] = participant["y"].toInt();
|
||||
data["w"] = participant["w"].toInt();
|
||||
data["h"] = participant["h"].toInt();
|
||||
data["uri"] = participant["uri"];
|
||||
data["active"] = participant["active"] == "true";
|
||||
auto bestName = participant["uri"];
|
||||
data["isLocal"] = false;
|
||||
auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId_);
|
||||
if (bestName == accInfo.profileInfo.uri) {
|
||||
bestName = tr("me");
|
||||
data["isLocal"] = true;
|
||||
} else {
|
||||
try {
|
||||
auto &contact = LRCInstance::getCurrentAccountInfo().contactModel->getContact(participant["uri"]);
|
||||
bestName = Utils::bestNameForContact(contact);
|
||||
} catch (...) {}
|
||||
}
|
||||
data["bestName"] = bestName;
|
||||
map.push_back(QVariant(data));
|
||||
}
|
||||
emit updateParticipantsInfos(map, accountId, confId);
|
||||
}
|
||||
});
|
||||
|
||||
callStatusChangedConnection_ = QObject::connect(
|
||||
accInfo.callModel.get(),
|
||||
|
@ -345,7 +424,7 @@ CallAdapter::updateCallOverlay(const lrc::api::conversation::Info &convInfo)
|
|||
bool isAudioOnly = call->isAudioOnly && !isPaused;
|
||||
bool isAudioMuted = call->audioMuted && (call->status != lrc::api::call::Status::PAUSED);
|
||||
bool isVideoMuted = call->videoMuted && !isPaused && !call->isAudioOnly;
|
||||
bool isRecording = accInfo.callModel->isRecording(convInfo.callId);
|
||||
bool isRecording = isRecordingThisCall();
|
||||
|
||||
emit updateOverlay(isPaused,
|
||||
isAudioOnly,
|
||||
|
@ -359,16 +438,16 @@ CallAdapter::updateCallOverlay(const lrc::api::conversation::Info &convInfo)
|
|||
}
|
||||
|
||||
void
|
||||
CallAdapter::hangUpThisCall()
|
||||
CallAdapter::hangupCall(const QString& uri)
|
||||
{
|
||||
auto convInfo = LRCInstance::getConversationFromConvUid(convUid_, accountId_);
|
||||
auto convInfo = LRCInstance::getConversationFromPeerUri(uri, accountId_);
|
||||
if (!convInfo.uid.isEmpty()) {
|
||||
auto callModel = LRCInstance::getAccountInfo(accountId_).callModel.get();
|
||||
if (callModel->hasCall(convInfo.callId)) {
|
||||
/*
|
||||
* Store the last remaining participant of the conference,
|
||||
* so we can switch the smartlist index after termination.
|
||||
*/
|
||||
* Store the last remaining participant of the conference,
|
||||
* so we can switch the smartlist index after termination.
|
||||
*/
|
||||
if (!convInfo.confId.isEmpty()) {
|
||||
auto callList = LRCInstance::getAPI().getConferenceSubcalls(convInfo.confId);
|
||||
if (callList.size() == 2) {
|
||||
|
@ -379,6 +458,73 @@ CallAdapter::hangUpThisCall()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
callModel->hangUp(convInfo.callId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CallAdapter::maximizeParticipant(const QString& uri, bool isActive)
|
||||
{
|
||||
auto callModel = LRCInstance::getAccountInfo(accountId_).callModel.get();
|
||||
auto confId = LRCInstance::getCurrentConversation().confId;
|
||||
QString callId;
|
||||
if (LRCInstance::getCurrentAccountInfo().profileInfo.uri != uri) {
|
||||
auto convInfo = LRCInstance::getConversationFromPeerUri(uri, accountId_);
|
||||
if (!convInfo.uid.isEmpty()) {
|
||||
callId = convInfo.callId;
|
||||
}
|
||||
}
|
||||
try {
|
||||
auto call = callModel->getCall(confId);
|
||||
switch (call.layout) {
|
||||
case lrc::api::call::Layout::GRID:
|
||||
callModel->setActiveParticipant(confId, callId);
|
||||
callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL);
|
||||
break;
|
||||
case lrc::api::call::Layout::ONE_WITH_SMALL:
|
||||
callModel->setActiveParticipant(confId, callId);
|
||||
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, callId);
|
||||
callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID);
|
||||
break;
|
||||
};
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
void
|
||||
CallAdapter::minimizeParticipant()
|
||||
{
|
||||
auto callModel = LRCInstance::getAccountInfo(accountId_).callModel.get();
|
||||
auto confId = LRCInstance::getCurrentConversation().confId;
|
||||
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;
|
||||
};
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
void
|
||||
CallAdapter::hangUpThisCall()
|
||||
{
|
||||
auto convInfo = LRCInstance::getConversationFromConvUid(convUid_, accountId_);
|
||||
if (!convInfo.uid.isEmpty()) {
|
||||
auto callModel = LRCInstance::getAccountInfo(accountId_).callModel.get();
|
||||
if (!convInfo.confId.isEmpty() && callModel->hasCall(convInfo.confId)) {
|
||||
callModel->hangUp(convInfo.confId);
|
||||
} else if (callModel->hasCall(convInfo.callId)) {
|
||||
callModel->hangUp(convInfo.callId);
|
||||
}
|
||||
}
|
||||
|
@ -393,6 +539,38 @@ CallAdapter::isRecordingThisCall()
|
|||
|| accInfo.callModel->isRecording(convInfo.callId);
|
||||
}
|
||||
|
||||
bool
|
||||
CallAdapter::isCurrentMaster() const
|
||||
{
|
||||
auto convInfo = LRCInstance::getConversationFromConvUid(convUid_, accountId_);
|
||||
if (!convInfo.uid.isEmpty()) {
|
||||
auto callModel = LRCInstance::getAccountInfo(accountId_).callModel.get();
|
||||
try {
|
||||
if (!convInfo.confId.isEmpty() && callModel->hasCall(convInfo.confId)) {
|
||||
return true;
|
||||
} else {
|
||||
auto call = callModel->getCall(convInfo.callId);
|
||||
return call.participantsInfos.size() == 0;
|
||||
}
|
||||
} catch (...) {}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
CallAdapter::getCurrentLayoutType() const
|
||||
{
|
||||
auto convInfo = LRCInstance::getConversationFromConvUid(convUid_, accountId_);
|
||||
if (!convInfo.uid.isEmpty()) {
|
||||
auto callModel = LRCInstance::getAccountInfo(accountId_).callModel.get();
|
||||
try {
|
||||
auto call = callModel->getCall(convInfo.confId);
|
||||
return Utils::toUnderlyingValue(call.layout);
|
||||
} catch (...) {}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
CallAdapter::holdThisCallToggle()
|
||||
{
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
|
||||
class CallAdapter : public QmlAdapterBase
|
||||
{
|
||||
|
@ -44,17 +45,23 @@ public:
|
|||
Q_INVOKABLE void refuseACall(const QString &accountId, const QString &convUid);
|
||||
Q_INVOKABLE void acceptACall(const QString &accountId, const QString &convUid);
|
||||
|
||||
Q_INVOKABLE void connectCallStatusChanged(const QString &accountId);
|
||||
Q_INVOKABLE void connectCallModel(const QString &accountId);
|
||||
|
||||
/*
|
||||
* 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 hangUpThisCall();
|
||||
Q_INVOKABLE bool isCurrentMaster() const;
|
||||
Q_INVOKABLE int getCurrentLayoutType() const;
|
||||
Q_INVOKABLE void holdThisCallToggle();
|
||||
Q_INVOKABLE void muteThisCallToggle();
|
||||
Q_INVOKABLE void recordThisCallToggle();
|
||||
Q_INVOKABLE void videoPauseThisCallToggle();
|
||||
Q_INVOKABLE bool isRecordingThisCall();
|
||||
Q_INVOKABLE QVariantList getConferencesInfos();
|
||||
|
||||
signals:
|
||||
void showOutgoingCallPage(const QString &accountId, const QString &convUid);
|
||||
|
@ -66,6 +73,7 @@ signals:
|
|||
void closePotentialIncomingCallPageWindow(const QString &accountId, const QString &convUid);
|
||||
void callStatusChanged(const QString &status, const QString &accountId, const QString &convUid);
|
||||
void updateConversationSmartList();
|
||||
void updateParticipantsInfos(const QVariantList& infos, const QString &accountId, const QString &callId);
|
||||
|
||||
void incomingCallNeedToSetupMainView(const QString &accountId, const QString &convUid);
|
||||
void previewVisibilityNeedToChange(bool visible);
|
||||
|
@ -103,6 +111,7 @@ private:
|
|||
QString convUid_;
|
||||
|
||||
QMetaObject::Connection callStatusChangedConnection_;
|
||||
QMetaObject::Connection onParticipantsChangedConnection_;
|
||||
QMetaObject::Connection closeIncomingCallPageConnection_;
|
||||
|
||||
/*
|
||||
|
|
|
@ -51,15 +51,48 @@ DistantRenderer::setRendererId(const QString &id)
|
|||
update(QRect(0, 0, width(), height()));
|
||||
}
|
||||
|
||||
int
|
||||
DistantRenderer::getXOffset() const
|
||||
{
|
||||
return xOffset_;
|
||||
}
|
||||
|
||||
int
|
||||
DistantRenderer::getYOffset() const
|
||||
{
|
||||
return yOffset_;
|
||||
}
|
||||
|
||||
double
|
||||
DistantRenderer::getScaledWidth() const
|
||||
{
|
||||
return scaledWidth_;
|
||||
}
|
||||
|
||||
double
|
||||
DistantRenderer::getScaledHeight() const
|
||||
{
|
||||
return scaledHeight_;
|
||||
}
|
||||
|
||||
void
|
||||
DistantRenderer::paint(QPainter *painter)
|
||||
{
|
||||
auto distantImage = LRCInstance::renderer()->getFrame(distantRenderId_);
|
||||
if (distantImage) {
|
||||
auto scaledDistant = distantImage->scaled(size().toSize(), Qt::KeepAspectRatio);
|
||||
auto xDiff = (width() - scaledDistant.width()) / 2;
|
||||
auto yDiff = (height() - scaledDistant.height()) / 2;
|
||||
painter->drawImage(QRect(xDiff, yDiff, scaledDistant.width(), scaledDistant.height()),
|
||||
auto tempScaledWidth = static_cast<int>(scaledWidth_*1000);
|
||||
auto tempScaledHeight = static_cast<int>(scaledHeight_*1000);
|
||||
auto tempXOffset = xOffset_;
|
||||
auto tempYOffset = yOffset_;
|
||||
scaledWidth_ = static_cast<double>(scaledDistant.width())/static_cast<double>(distantImage->width());
|
||||
scaledHeight_ = static_cast<double>(scaledDistant.height())/static_cast<double>(distantImage->height());
|
||||
xOffset_ = (width() - scaledDistant.width()) / 2;
|
||||
yOffset_ = (height() - scaledDistant.height()) / 2;
|
||||
if (tempXOffset != xOffset_ or tempYOffset != yOffset_ or static_cast<int>(scaledWidth_*1000) != tempScaledWidth or static_cast<int>(scaledHeight_*1000) != tempScaledHeight) {
|
||||
emit offsetChanged();
|
||||
}
|
||||
painter->drawImage(QRect(xOffset_, yOffset_, scaledDistant.width(), scaledDistant.height()),
|
||||
scaledDistant);
|
||||
}
|
||||
}
|
|
@ -34,6 +34,13 @@ public:
|
|||
~DistantRenderer();
|
||||
|
||||
Q_INVOKABLE void setRendererId(const QString &id);
|
||||
Q_INVOKABLE int getXOffset() const;
|
||||
Q_INVOKABLE int getYOffset() const;
|
||||
Q_INVOKABLE double getScaledWidth() const;
|
||||
Q_INVOKABLE double getScaledHeight() const;
|
||||
|
||||
signals:
|
||||
void offsetChanged();
|
||||
|
||||
private:
|
||||
void paint(QPainter *painter);
|
||||
|
@ -42,4 +49,8 @@ private:
|
|||
* Unique DistantRenderId for each call.
|
||||
*/
|
||||
QString distantRenderId_;
|
||||
int xOffset_ {0};
|
||||
int yOffset_ {0};
|
||||
double scaledWidth_ {0};
|
||||
double scaledHeight_ {0};
|
||||
};
|
|
@ -229,7 +229,7 @@ public:
|
|||
{
|
||||
return getConversation(
|
||||
!accountId.isEmpty() ? accountId : getCurrAccId(),
|
||||
[&](const conversation::Info &conv) -> bool { return callId == conv.callId; },
|
||||
[&](const conversation::Info &conv) -> bool { return callId == conv.callId or callId == conv.confId; },
|
||||
filtered);
|
||||
}
|
||||
static const conversation::Info &
|
||||
|
|
|
@ -35,6 +35,9 @@ Rectangle {
|
|||
|
||||
signal overlayChatButtonClicked
|
||||
|
||||
property var participantOverlays: []
|
||||
property var participantComponent: Qt.createComponent("ParticipantOverlay.qml")
|
||||
|
||||
function setRecording(isRecording) {
|
||||
callViewContextMenu.isRecording = isRecording
|
||||
recordingRect.visible = isRecording
|
||||
|
@ -52,6 +55,10 @@ Rectangle {
|
|||
isConferenceCall)
|
||||
}
|
||||
|
||||
function updateMaster() {
|
||||
callOverlayButtonGroup.updateMaster()
|
||||
}
|
||||
|
||||
function showOnHoldImage(visible) {
|
||||
onHoldImage.visible = visible
|
||||
}
|
||||
|
@ -60,6 +67,41 @@ Rectangle {
|
|||
ContactPickerCreation.closeContactPicker()
|
||||
}
|
||||
|
||||
function handleParticipantsInfo(infos) {
|
||||
videoCallOverlay.updateMaster()
|
||||
var isMaster = CallAdapter.isCurrentMaster()
|
||||
for (var p in participantOverlays) {
|
||||
if (participantOverlays[p])
|
||||
participantOverlays[p].destroy()
|
||||
}
|
||||
participantOverlays = []
|
||||
if (infos.length == 0) {
|
||||
previewRenderer.visible = true
|
||||
} 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
|
||||
}
|
||||
hover.setParticipantName(infos[infoVariant].bestName)
|
||||
hover.active = infos[infoVariant].active;
|
||||
hover.isLocal = infos[infoVariant].isLocal;
|
||||
hover.setMenuVisible(isMaster)
|
||||
hover.uri = infos[infoVariant].uri
|
||||
hover.injectedContextMenu = participantContextMenu
|
||||
participantOverlays.push(hover)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
|
||||
|
@ -313,10 +355,9 @@ Rectangle {
|
|||
id: callOverlayRectMouseArea
|
||||
|
||||
anchors.top: callOverlayRect.top
|
||||
anchors.topMargin: 50
|
||||
|
||||
width: callOverlayRect.width
|
||||
height: callOverlayRect.height - callOverlayButtonGroup.height - 50
|
||||
height: callOverlayRect.height
|
||||
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
|
@ -370,4 +411,8 @@ Rectangle {
|
|||
ContactPickerCreation.openContactPicker()
|
||||
}
|
||||
}
|
||||
|
||||
ParticipantContextMenu {
|
||||
id: participantContextMenu
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,13 +34,24 @@ Rectangle {
|
|||
* since no other methods can make buttons at the layout center.
|
||||
*/
|
||||
property int buttonPreferredSize: 24
|
||||
property var isMaster: true
|
||||
property var isSip: false
|
||||
|
||||
signal chatButtonClicked
|
||||
signal addToConferenceButtonClicked
|
||||
|
||||
function updateMaster() {
|
||||
root.isMaster = CallAdapter.isCurrentMaster()
|
||||
addToConferenceButton.visible = !root.isSip && root.isMaster
|
||||
}
|
||||
|
||||
function setButtonStatus(isPaused, isAudioOnly, isAudioMuted, isVideoMuted, isRecording, isSIP, isConferenceCall) {
|
||||
root.isMaster = CallAdapter.isCurrentMaster()
|
||||
root.isSip = isSIP
|
||||
noVideoButton.visible = !isAudioOnly
|
||||
addToConferenceButton.visible = !isSIP
|
||||
addToConferenceButton.visible = !isSIP && isMaster
|
||||
transferCallButton.visible = isSIP
|
||||
sipInputPanelButton.visible = isSIP
|
||||
|
||||
noMicButton.checked = isAudioMuted
|
||||
noVideoButton.checked = isVideoMuted
|
||||
|
@ -148,6 +159,7 @@ Rectangle {
|
|||
|
||||
Layout.preferredWidth: buttonPreferredSize * 2
|
||||
Layout.preferredHeight: buttonPreferredSize * 2
|
||||
visible: !isMaster
|
||||
|
||||
backgroundColor: Qt.rgba(0, 0, 0, 0.75)
|
||||
onEnterColor: Qt.rgba(0, 0, 0, 0.6)
|
||||
|
|
|
@ -157,11 +157,17 @@ Rectangle {
|
|||
}
|
||||
|
||||
function onCallStatusChanged(status, accountId, convUid) {
|
||||
if (responsibleConvUid === convUid
|
||||
&& responsibleAccountId === accountId) {
|
||||
if (responsibleConvUid === convUid && responsibleAccountId === accountId) {
|
||||
outgoingCallPage.callStatusPresentation = status
|
||||
}
|
||||
}
|
||||
|
||||
function onUpdateParticipantsInfos(infos, accountId, callId) {
|
||||
var responsibleCallId = ClientWrapper.utilsAdaptor.getCallId(responsibleAccountId, responsibleConvUid)
|
||||
if (responsibleCallId === callId) {
|
||||
videoCallPage.handleParticipantsInfo(infos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AudioCallPage {
|
||||
|
@ -198,6 +204,8 @@ Rectangle {
|
|||
videoCallPage.parent = callStackMainView
|
||||
VideoCallFullScreenWindowContainerCreation.closeVideoCallFullScreenWindowContainer()
|
||||
}
|
||||
|
||||
videoCallPage.handleParticipantsInfo(CallAdapter.getConferencesInfos())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
127
src/mainview/components/ParticipantContextMenu.qml
Normal file
127
src/mainview/components/ParticipantContextMenu.qml
Normal file
|
@ -0,0 +1,127 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) 2020 by Savoir-faire Linux
|
||||
* Author: Sébastien Blin <sebastien.blin@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.12
|
||||
import net.jami.Models 1.0
|
||||
|
||||
import "../../commoncomponents"
|
||||
|
||||
import "../js/videodevicecontextmenuitemcreation.js" as VideoDeviceContextMenuItemCreation
|
||||
import "../js/selectscreenwindowcreation.js" as SelectScreenWindowCreation
|
||||
|
||||
Menu {
|
||||
id: root
|
||||
|
||||
property int generalMenuSeparatorCount: 0
|
||||
property int commonBorderWidth: 1
|
||||
font.pointSize: JamiTheme.textFontSize + 3
|
||||
property var uri: ""
|
||||
property var maximized: true
|
||||
property var active: true
|
||||
|
||||
function showHangup(show) {
|
||||
if (show) {
|
||||
hangupItem.visible = true
|
||||
hangupItem.height = hangupItem.preferredHeight
|
||||
} else {
|
||||
hangupItem.visible = false
|
||||
hangupItem.height = 0
|
||||
}
|
||||
}
|
||||
|
||||
function showMaximize(show) {
|
||||
if (show) {
|
||||
maximizeItem.visible = true
|
||||
maximizeItem.height = hangupItem.preferredHeight
|
||||
} else {
|
||||
maximizeItem.visible = false
|
||||
maximizeItem.height = 0
|
||||
}
|
||||
}
|
||||
|
||||
function showMinimize(show) {
|
||||
if (show) {
|
||||
minimizeItem.visible = true
|
||||
minimizeItem.height = hangupItem.preferredHeight
|
||||
} else {
|
||||
minimizeItem.visible = false
|
||||
minimizeItem.height = 0
|
||||
}
|
||||
}
|
||||
|
||||
function setHeight(visibleItems) {
|
||||
root.height = hangupItem.preferredHeight * visibleItems;
|
||||
}
|
||||
|
||||
/*
|
||||
* All GeneralMenuItems should remain the same width / height.
|
||||
*/
|
||||
GeneralMenuItem {
|
||||
id: hangupItem
|
||||
|
||||
itemName: qsTr("Hangup")
|
||||
iconSource: "qrc:/images/icons/ic_call_end_white_24px.svg"
|
||||
icon.color: "black"
|
||||
leftBorderWidth: commonBorderWidth
|
||||
rightBorderWidth: commonBorderWidth
|
||||
|
||||
onClicked: {
|
||||
CallAdapter.hangupCall(uri)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
GeneralMenuItem {
|
||||
id: maximizeItem
|
||||
|
||||
itemName: qsTr("Maximize participant")
|
||||
iconSource: "qrc:/images/icons/open_in_full-24px.svg"
|
||||
leftBorderWidth: commonBorderWidth
|
||||
rightBorderWidth: commonBorderWidth
|
||||
visible: !maximized
|
||||
|
||||
onClicked: {
|
||||
CallAdapter.maximizeParticipant(uri, active)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
GeneralMenuItem {
|
||||
id: minimizeItem
|
||||
|
||||
itemName: qsTr("Minimize participant")
|
||||
iconSource: "qrc:/images/icons/close_fullscreen-24px.svg"
|
||||
leftBorderWidth: commonBorderWidth
|
||||
rightBorderWidth: commonBorderWidth
|
||||
visible: maximized
|
||||
|
||||
onClicked: {
|
||||
CallAdapter.minimizeParticipant()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: hangupItem.preferredWidth
|
||||
implicitHeight: hangupItem.preferredHeight * 3
|
||||
|
||||
border.width: commonBorderWidth
|
||||
border.color: JamiTheme.tabbarBorderColor
|
||||
}
|
||||
}
|
||||
|
169
src/mainview/components/ParticipantOverlay.qml
Normal file
169
src/mainview/components/ParticipantOverlay.qml
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright (C) 2020 by Savoir-faire Linux
|
||||
* Author: Sébastien Blin <sebastien.blin@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 QtQuick.Layouts 1.14
|
||||
import QtQuick.Controls.Universal 2.12
|
||||
import QtGraphicalEffects 1.14
|
||||
import net.jami.Models 1.0
|
||||
|
||||
import "../../commoncomponents"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property int buttonPreferredSize: 12
|
||||
property var uri: ""
|
||||
property var active: true
|
||||
property var isLocal: true
|
||||
property var injectedContextMenu: null
|
||||
|
||||
function setParticipantName(name) {
|
||||
participantName.text = name
|
||||
}
|
||||
|
||||
function setMenuVisible(isVisible) {
|
||||
optionsButton.visible = isVisible
|
||||
}
|
||||
|
||||
border.width: 1
|
||||
opacity: 0
|
||||
color: "transparent"
|
||||
z: 1
|
||||
|
||||
MouseArea {
|
||||
id: mouseAreaHover
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
acceptedButtons: Qt.LeftButton
|
||||
|
||||
RowLayout {
|
||||
id: bottomLabel
|
||||
|
||||
height: 24
|
||||
width: parent.width
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
Rectangle {
|
||||
color: "black"
|
||||
opacity: 0.8
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: parent.height
|
||||
|
||||
Text {
|
||||
id: participantName
|
||||
anchors.fill: parent
|
||||
leftPadding: 8.0
|
||||
|
||||
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))
|
||||
injectedContextMenu.showHangup(!root.isLocal)
|
||||
injectedContextMenu.showMaximize(showMaximized)
|
||||
injectedContextMenu.showMinimize(showMinimized)
|
||||
injectedContextMenu.setHeight(
|
||||
(root.isLocal ? 0 : 1)
|
||||
+ (showMaximized ? 1 : 0)
|
||||
+ (showMinimized ? 1 : 0))
|
||||
injectedContextMenu.uri = uri
|
||||
injectedContextMenu.active = active
|
||||
injectedContextMenu.x = mousePos.x
|
||||
injectedContextMenu.y = mousePos.y - injectedContextMenu.height
|
||||
injectedContextMenu.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
CallAdapter.maximizeParticipant(uri, active)
|
||||
}
|
||||
|
||||
onEntered: {
|
||||
root.state = "entered"
|
||||
}
|
||||
|
||||
onExited: {
|
||||
root.state = "exited"
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "entered"
|
||||
PropertyChanges {
|
||||
target: root
|
||||
opacity: 1
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "exited"
|
||||
PropertyChanges {
|
||||
target: root
|
||||
opacity: 0
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: Transition {
|
||||
PropertyAnimation {
|
||||
target: root
|
||||
property: "opacity"
|
||||
duration: 500
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,6 +42,8 @@ Rectangle {
|
|||
signal needToShowInFullScreen
|
||||
|
||||
function updateUI(accountId, convUid) {
|
||||
videoCallOverlay.handleParticipantsInfo(CallAdapter.getConferencesInfos())
|
||||
|
||||
bestName = ClientWrapper.utilsAdaptor.getBestName(accountId, convUid)
|
||||
|
||||
var id = ClientWrapper.utilsAdaptor.getBestId(accountId, convUid)
|
||||
|
@ -74,6 +76,15 @@ Rectangle {
|
|||
videoCallOverlay.closePotentialContactPicker()
|
||||
}
|
||||
|
||||
function handleParticipantsInfo(infos) {
|
||||
if (infos.length === 0) {
|
||||
bestName = ClientWrapper.utilsAdaptor.getBestName(accountId, convUid)
|
||||
} else {
|
||||
bestName = ""
|
||||
}
|
||||
videoCallOverlay.handleParticipantsInfo(infos)
|
||||
}
|
||||
|
||||
function previewMagneticSnap() {
|
||||
|
||||
|
||||
|
@ -225,6 +236,10 @@ Rectangle {
|
|||
|
||||
width: videoCallPageMainRect.width
|
||||
height: videoCallPageMainRect.height
|
||||
|
||||
onOffsetChanged: {
|
||||
videoCallOverlay.handleParticipantsInfo(CallAdapter.getConferencesInfos())
|
||||
}
|
||||
}
|
||||
|
||||
VideoCallPreviewRenderer {
|
||||
|
|
Loading…
Add table
Reference in a new issue