mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-25 20:48:52 +03:00
Polish voice call UI
This commit is contained in:
parent
da9995fc3d
commit
88cfa3a8fa
18 changed files with 348 additions and 146 deletions
BIN
resources/icons/ui/end-call.png
Normal file
BIN
resources/icons/ui/end-call.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 643 B |
BIN
resources/icons/ui/microphone-mute.png
Normal file
BIN
resources/icons/ui/microphone-mute.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
resources/icons/ui/microphone-unmute.png
Normal file
BIN
resources/icons/ui/microphone-unmute.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
resources/icons/ui/place-call.png
Normal file
BIN
resources/icons/ui/place-call.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 759 B |
|
@ -70,6 +70,11 @@
|
||||||
|
|
||||||
<file>icons/ui/mail-reply.png</file>
|
<file>icons/ui/mail-reply.png</file>
|
||||||
|
|
||||||
|
<file>icons/ui/place-call.png</file>
|
||||||
|
<file>icons/ui/end-call.png</file>
|
||||||
|
<file>icons/ui/microphone-mute.png</file>
|
||||||
|
<file>icons/ui/microphone-unmute.png</file>
|
||||||
|
|
||||||
<file>icons/emoji-categories/people.png</file>
|
<file>icons/emoji-categories/people.png</file>
|
||||||
<file>icons/emoji-categories/people@2x.png</file>
|
<file>icons/emoji-categories/people@2x.png</file>
|
||||||
<file>icons/emoji-categories/nature.png</file>
|
<file>icons/emoji-categories/nature.png</file>
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
#include "ActiveCallBar.h"
|
#include "ActiveCallBar.h"
|
||||||
|
#include "ChatPage.h"
|
||||||
|
#include "Utils.h"
|
||||||
#include "WebRTCSession.h"
|
#include "WebRTCSession.h"
|
||||||
|
#include "ui/Avatar.h"
|
||||||
#include "ui/FlatButton.h"
|
#include "ui/FlatButton.h"
|
||||||
|
|
||||||
ActiveCallBar::ActiveCallBar(QWidget *parent)
|
ActiveCallBar::ActiveCallBar(QWidget *parent)
|
||||||
|
@ -12,7 +19,7 @@ ActiveCallBar::ActiveCallBar(QWidget *parent)
|
||||||
{
|
{
|
||||||
setAutoFillBackground(true);
|
setAutoFillBackground(true);
|
||||||
auto p = palette();
|
auto p = palette();
|
||||||
p.setColor(backgroundRole(), Qt::green);
|
p.setColor(backgroundRole(), QColorConstants::Svg::limegreen);
|
||||||
setPalette(p);
|
setPalette(p);
|
||||||
|
|
||||||
QFont f;
|
QFont f;
|
||||||
|
@ -24,51 +31,126 @@ ActiveCallBar::ActiveCallBar(QWidget *parent)
|
||||||
|
|
||||||
setFixedHeight(contentHeight + widgetMargin);
|
setFixedHeight(contentHeight + widgetMargin);
|
||||||
|
|
||||||
topLayout_ = new QHBoxLayout(this);
|
layout_ = new QHBoxLayout(this);
|
||||||
topLayout_->setSpacing(widgetMargin);
|
layout_->setSpacing(widgetMargin);
|
||||||
topLayout_->setContentsMargins(
|
layout_->setContentsMargins(
|
||||||
2 * widgetMargin, widgetMargin, 2 * widgetMargin, widgetMargin);
|
2 * widgetMargin, widgetMargin, 2 * widgetMargin, widgetMargin);
|
||||||
topLayout_->setSizeConstraint(QLayout::SetMinimumSize);
|
|
||||||
|
|
||||||
QFont labelFont;
|
QFont labelFont;
|
||||||
labelFont.setPointSizeF(labelFont.pointSizeF() * 1.2);
|
labelFont.setPointSizeF(labelFont.pointSizeF() * 1.1);
|
||||||
labelFont.setWeight(QFont::Medium);
|
labelFont.setWeight(QFont::Medium);
|
||||||
|
|
||||||
|
avatar_ = new Avatar(this, QFontMetrics(f).height() * 2.5);
|
||||||
|
|
||||||
callPartyLabel_ = new QLabel(this);
|
callPartyLabel_ = new QLabel(this);
|
||||||
callPartyLabel_->setFont(labelFont);
|
callPartyLabel_->setFont(labelFont);
|
||||||
|
|
||||||
// TODO microphone mute/unmute icons
|
stateLabel_ = new QLabel(this);
|
||||||
|
stateLabel_->setFont(labelFont);
|
||||||
|
|
||||||
|
durationLabel_ = new QLabel(this);
|
||||||
|
durationLabel_->setFont(labelFont);
|
||||||
|
durationLabel_->hide();
|
||||||
|
|
||||||
muteBtn_ = new FlatButton(this);
|
muteBtn_ = new FlatButton(this);
|
||||||
QIcon muteIcon;
|
setMuteIcon(false);
|
||||||
muteIcon.addFile(":/icons/icons/ui/do-not-disturb-rounded-sign.png");
|
|
||||||
muteBtn_->setIcon(muteIcon);
|
|
||||||
muteBtn_->setIconSize(QSize(buttonSize_ / 2, buttonSize_ / 2));
|
|
||||||
muteBtn_->setToolTip(tr("Mute Mic"));
|
|
||||||
muteBtn_->setFixedSize(buttonSize_, buttonSize_);
|
muteBtn_->setFixedSize(buttonSize_, buttonSize_);
|
||||||
muteBtn_->setCornerRadius(buttonSize_ / 2);
|
muteBtn_->setCornerRadius(buttonSize_ / 2);
|
||||||
connect(muteBtn_, &FlatButton::clicked, this, [this]() {
|
connect(muteBtn_, &FlatButton::clicked, this, [this](){
|
||||||
if (WebRTCSession::instance().toggleMuteAudioSrc(muted_)) {
|
if (WebRTCSession::instance().toggleMuteAudioSrc(muted_))
|
||||||
QIcon icon;
|
setMuteIcon(muted_);
|
||||||
if (muted_) {
|
|
||||||
muteBtn_->setToolTip("Unmute Mic");
|
|
||||||
icon.addFile(":/icons/icons/ui/round-remove-button.png");
|
|
||||||
} else {
|
|
||||||
muteBtn_->setToolTip("Mute Mic");
|
|
||||||
icon.addFile(":/icons/icons/ui/do-not-disturb-rounded-sign.png");
|
|
||||||
}
|
|
||||||
muteBtn_->setIcon(icon);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
topLayout_->addWidget(callPartyLabel_, 0, Qt::AlignLeft);
|
layout_->addWidget(avatar_, 0, Qt::AlignLeft);
|
||||||
topLayout_->addWidget(muteBtn_, 0, Qt::AlignRight);
|
layout_->addWidget(callPartyLabel_, 0, Qt::AlignLeft);
|
||||||
|
layout_->addWidget(stateLabel_, 0, Qt::AlignLeft);
|
||||||
|
layout_->addWidget(durationLabel_, 0, Qt::AlignLeft);
|
||||||
|
layout_->addStretch();
|
||||||
|
layout_->addWidget(muteBtn_, 0, Qt::AlignCenter);
|
||||||
|
layout_->addSpacing(18);
|
||||||
|
|
||||||
|
timer_ = new QTimer(this);
|
||||||
|
connect(timer_, &QTimer::timeout, this,
|
||||||
|
[this](){
|
||||||
|
auto seconds = QDateTime::currentSecsSinceEpoch() - callStartTime_;
|
||||||
|
int s = seconds % 60;
|
||||||
|
int m = (seconds / 60) % 60;
|
||||||
|
int h = seconds / 3600;
|
||||||
|
char buf[12];
|
||||||
|
if (h)
|
||||||
|
snprintf(buf, sizeof(buf), "%.2d:%.2d:%.2d", h, m, s);
|
||||||
|
else
|
||||||
|
snprintf(buf, sizeof(buf), "%.2d:%.2d", m, s);
|
||||||
|
durationLabel_->setText(buf);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(&WebRTCSession::instance(), &WebRTCSession::stateChanged, this, &ActiveCallBar::update);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ActiveCallBar::setCallParty(const QString &userid, const QString &displayName)
|
ActiveCallBar::setMuteIcon(bool muted)
|
||||||
{
|
{
|
||||||
if (!displayName.isEmpty() && displayName != userid)
|
QIcon icon;
|
||||||
callPartyLabel_->setText("Active Call: " + displayName + " (" + userid + ")");
|
if (muted) {
|
||||||
else
|
muteBtn_->setToolTip("Unmute Mic");
|
||||||
callPartyLabel_->setText("Active Call: " + userid);
|
icon.addFile(":/icons/icons/ui/microphone-unmute.png");
|
||||||
|
} else {
|
||||||
|
muteBtn_->setToolTip("Mute Mic");
|
||||||
|
icon.addFile(":/icons/icons/ui/microphone-mute.png");
|
||||||
|
}
|
||||||
|
muteBtn_->setIcon(icon);
|
||||||
|
muteBtn_->setIconSize(QSize(buttonSize_, buttonSize_));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ActiveCallBar::setCallParty(
|
||||||
|
const QString &userid,
|
||||||
|
const QString &displayName,
|
||||||
|
const QString &roomName,
|
||||||
|
const QString &avatarUrl)
|
||||||
|
{
|
||||||
|
callPartyLabel_->setText(
|
||||||
|
(displayName.isEmpty() ? userid : displayName) + " -");
|
||||||
|
|
||||||
|
if (!avatarUrl.isEmpty())
|
||||||
|
avatar_->setImage(avatarUrl);
|
||||||
|
else
|
||||||
|
avatar_->setLetter(utils::firstChar(roomName));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ActiveCallBar::update(WebRTCSession::State state)
|
||||||
|
{
|
||||||
|
switch (state) {
|
||||||
|
case WebRTCSession::State::INITIATING:
|
||||||
|
stateLabel_->setText("Initiating call...");
|
||||||
|
break;
|
||||||
|
case WebRTCSession::State::INITIATED:
|
||||||
|
stateLabel_->setText("Call initiated...");
|
||||||
|
break;
|
||||||
|
case WebRTCSession::State::OFFERSENT:
|
||||||
|
stateLabel_->setText("Calling...");
|
||||||
|
break;
|
||||||
|
case WebRTCSession::State::CONNECTING:
|
||||||
|
stateLabel_->setText("Connecting...");
|
||||||
|
break;
|
||||||
|
case WebRTCSession::State::CONNECTED:
|
||||||
|
callStartTime_ = QDateTime::currentSecsSinceEpoch();
|
||||||
|
timer_->start(1000);
|
||||||
|
stateLabel_->setText("Active call:");
|
||||||
|
durationLabel_->setText("00:00");
|
||||||
|
durationLabel_->show();
|
||||||
|
muteBtn_->show();
|
||||||
|
break;
|
||||||
|
case WebRTCSession::State::DISCONNECTED:
|
||||||
|
timer_->stop();
|
||||||
|
callPartyLabel_->setText(QString());
|
||||||
|
stateLabel_->setText(QString());
|
||||||
|
durationLabel_->setText(QString());
|
||||||
|
durationLabel_->hide();
|
||||||
|
setMuteIcon(false);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,12 @@
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "WebRTCSession.h"
|
||||||
|
|
||||||
class QHBoxLayout;
|
class QHBoxLayout;
|
||||||
class QLabel;
|
class QLabel;
|
||||||
class QString;
|
class QTimer;
|
||||||
|
class Avatar;
|
||||||
class FlatButton;
|
class FlatButton;
|
||||||
|
|
||||||
class ActiveCallBar : public QWidget
|
class ActiveCallBar : public QWidget
|
||||||
|
@ -15,12 +18,24 @@ public:
|
||||||
ActiveCallBar(QWidget *parent = nullptr);
|
ActiveCallBar(QWidget *parent = nullptr);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setCallParty(const QString &userid, const QString &displayName);
|
void update(WebRTCSession::State);
|
||||||
|
void setCallParty(
|
||||||
|
const QString &userid,
|
||||||
|
const QString &displayName,
|
||||||
|
const QString &roomName,
|
||||||
|
const QString &avatarUrl);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QHBoxLayout *topLayout_ = nullptr;
|
QHBoxLayout *layout_ = nullptr;
|
||||||
|
Avatar *avatar_ = nullptr;
|
||||||
QLabel *callPartyLabel_ = nullptr;
|
QLabel *callPartyLabel_ = nullptr;
|
||||||
|
QLabel *stateLabel_ = nullptr;
|
||||||
|
QLabel *durationLabel_ = nullptr;
|
||||||
FlatButton *muteBtn_ = nullptr;
|
FlatButton *muteBtn_ = nullptr;
|
||||||
int buttonSize_ = 32;
|
int buttonSize_ = 22;
|
||||||
bool muted_ = false;
|
bool muted_ = false;
|
||||||
|
qint64 callStartTime_ = 0;
|
||||||
|
QTimer *timer_ = nullptr;
|
||||||
|
|
||||||
|
void setMuteIcon(bool muted);
|
||||||
};
|
};
|
||||||
|
|
|
@ -68,9 +68,9 @@ CallManager::CallManager(QSharedPointer<UserSettings> userSettings)
|
||||||
turnServerTimer_.setInterval(res.ttl * 1000 * 0.9);
|
turnServerTimer_.setInterval(res.ttl * 1000 * 0.9);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(&session_, &WebRTCSession::pipelineChanged, this,
|
connect(&session_, &WebRTCSession::stateChanged, this,
|
||||||
[this](bool started) {
|
[this](WebRTCSession::State state) {
|
||||||
if (!started)
|
if (state == WebRTCSession::State::DISCONNECTED)
|
||||||
playRingtone("qrc:/media/media/callend.ogg", false);
|
playRingtone("qrc:/media/media/callend.ogg", false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -87,9 +87,9 @@ CallManager::sendInvite(const QString &roomid)
|
||||||
if (onActiveCall())
|
if (onActiveCall())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::vector<RoomMember> members(cache::getMembers(roomid.toStdString()));
|
auto roomInfo = cache::singleRoomInfo(roomid.toStdString());
|
||||||
if (members.size() != 2) {
|
if (roomInfo.member_count != 2) {
|
||||||
emit ChatPage::instance()->showNotification("Voice/Video calls are limited to 1:1 rooms");
|
emit ChatPage::instance()->showNotification("Voice calls are limited to 1:1 rooms.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,11 +105,13 @@ CallManager::sendInvite(const QString &roomid)
|
||||||
|
|
||||||
// TODO Add invite timeout
|
// TODO Add invite timeout
|
||||||
generateCallID();
|
generateCallID();
|
||||||
|
std::vector<RoomMember> members(cache::getMembers(roomid.toStdString()));
|
||||||
const RoomMember &callee = members.front().user_id == utils::localUser() ? members.back() : members.front();
|
const RoomMember &callee = members.front().user_id == utils::localUser() ? members.back() : members.front();
|
||||||
emit newCallParty(callee.user_id, callee.display_name);
|
emit newCallParty(callee.user_id, callee.display_name,
|
||||||
|
QString::fromStdString(roomInfo.name), QString::fromStdString(roomInfo.avatar_url));
|
||||||
playRingtone("qrc:/media/media/ringback.ogg", true);
|
playRingtone("qrc:/media/media/ringback.ogg", true);
|
||||||
if (!session_.createOffer()) {
|
if (!session_.createOffer()) {
|
||||||
emit ChatPage::instance()->showNotification("Problem setting up call");
|
emit ChatPage::instance()->showNotification("Problem setting up call.");
|
||||||
endCall();
|
endCall();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +129,7 @@ CallManager::hangUp()
|
||||||
bool
|
bool
|
||||||
CallManager::onActiveCall()
|
CallManager::onActiveCall()
|
||||||
{
|
{
|
||||||
return session_.isActive();
|
return session_.state() != WebRTCSession::State::DISCONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CallManager::syncEvent(const mtx::events::collections::TimelineEvents &event)
|
void CallManager::syncEvent(const mtx::events::collections::TimelineEvents &event)
|
||||||
|
@ -156,8 +158,8 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
|
||||||
if (callInviteEvent.content.call_id.empty())
|
if (callInviteEvent.content.call_id.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::vector<RoomMember> members(cache::getMembers(callInviteEvent.room_id));
|
auto roomInfo = cache::singleRoomInfo(callInviteEvent.room_id);
|
||||||
if (onActiveCall() || members.size() != 2) {
|
if (onActiveCall() || roomInfo.member_count != 2) {
|
||||||
emit newMessage(QString::fromStdString(callInviteEvent.room_id),
|
emit newMessage(QString::fromStdString(callInviteEvent.room_id),
|
||||||
CallHangUp{callInviteEvent.content.call_id, 0, CallHangUp::Reason::InviteTimeOut});
|
CallHangUp{callInviteEvent.content.call_id, 0, CallHangUp::Reason::InviteTimeOut});
|
||||||
return;
|
return;
|
||||||
|
@ -168,10 +170,18 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
|
||||||
callid_ = callInviteEvent.content.call_id;
|
callid_ = callInviteEvent.content.call_id;
|
||||||
remoteICECandidates_.clear();
|
remoteICECandidates_.clear();
|
||||||
|
|
||||||
const RoomMember &caller = members.front().user_id == utils::localUser() ? members.back() : members.front();
|
std::vector<RoomMember> members(cache::getMembers(callInviteEvent.room_id));
|
||||||
emit newCallParty(caller.user_id, caller.display_name);
|
const RoomMember &caller =
|
||||||
|
members.front().user_id == utils::localUser() ? members.back() : members.front();
|
||||||
|
emit newCallParty(caller.user_id, caller.display_name,
|
||||||
|
QString::fromStdString(roomInfo.name), QString::fromStdString(roomInfo.avatar_url));
|
||||||
|
|
||||||
auto dialog = new dialogs::AcceptCall(caller.user_id, caller.display_name, MainWindow::instance());
|
auto dialog = new dialogs::AcceptCall(
|
||||||
|
caller.user_id,
|
||||||
|
caller.display_name,
|
||||||
|
QString::fromStdString(roomInfo.name),
|
||||||
|
QString::fromStdString(roomInfo.avatar_url),
|
||||||
|
MainWindow::instance());
|
||||||
connect(dialog, &dialogs::AcceptCall::accept, this,
|
connect(dialog, &dialogs::AcceptCall::accept, this,
|
||||||
[this, callInviteEvent](){
|
[this, callInviteEvent](){
|
||||||
MainWindow::instance()->hideOverlay();
|
MainWindow::instance()->hideOverlay();
|
||||||
|
@ -198,7 +208,7 @@ CallManager::answerInvite(const CallInvite &invite)
|
||||||
session_.setStunServer(settings_->useStunServer() ? STUN_SERVER : "");
|
session_.setStunServer(settings_->useStunServer() ? STUN_SERVER : "");
|
||||||
|
|
||||||
if (!session_.acceptOffer(invite.sdp)) {
|
if (!session_.acceptOffer(invite.sdp)) {
|
||||||
emit ChatPage::instance()->showNotification("Problem setting up call");
|
emit ChatPage::instance()->showNotification("Problem setting up call.");
|
||||||
hangUp();
|
hangUp();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -232,6 +242,7 @@ CallManager::handleEvent(const RoomEvent<CallAnswer> &callAnswerEvent)
|
||||||
|
|
||||||
if (!onActiveCall() && callAnswerEvent.sender == utils::localUser().toStdString() &&
|
if (!onActiveCall() && callAnswerEvent.sender == utils::localUser().toStdString() &&
|
||||||
callid_ == callAnswerEvent.content.call_id) {
|
callid_ == callAnswerEvent.content.call_id) {
|
||||||
|
emit ChatPage::instance()->showNotification("Call answered on another device.");
|
||||||
stopRingtone();
|
stopRingtone();
|
||||||
MainWindow::instance()->hideOverlay();
|
MainWindow::instance()->hideOverlay();
|
||||||
return;
|
return;
|
||||||
|
@ -240,7 +251,7 @@ CallManager::handleEvent(const RoomEvent<CallAnswer> &callAnswerEvent)
|
||||||
if (onActiveCall() && callid_ == callAnswerEvent.content.call_id) {
|
if (onActiveCall() && callid_ == callAnswerEvent.content.call_id) {
|
||||||
stopRingtone();
|
stopRingtone();
|
||||||
if (!session_.acceptAnswer(callAnswerEvent.content.sdp)) {
|
if (!session_.acceptAnswer(callAnswerEvent.content.sdp)) {
|
||||||
emit ChatPage::instance()->showNotification("Problem setting up call");
|
emit ChatPage::instance()->showNotification("Problem setting up call.");
|
||||||
hangUp();
|
hangUp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,11 @@ signals:
|
||||||
void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer&);
|
void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer&);
|
||||||
void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp&);
|
void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp&);
|
||||||
void turnServerRetrieved(const mtx::responses::TurnServer&);
|
void turnServerRetrieved(const mtx::responses::TurnServer&);
|
||||||
void newCallParty(const QString &userid, const QString& displayName);
|
void newCallParty(
|
||||||
|
const QString &userid,
|
||||||
|
const QString &displayName,
|
||||||
|
const QString &roomName,
|
||||||
|
const QString &avatarUrl);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void retrieveTurnServer();
|
void retrieveTurnServer();
|
||||||
|
|
|
@ -138,13 +138,13 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||||
connect(
|
connect(
|
||||||
&callManager_, &CallManager::newCallParty, activeCallBar_, &ActiveCallBar::setCallParty);
|
&callManager_, &CallManager::newCallParty, activeCallBar_, &ActiveCallBar::setCallParty);
|
||||||
connect(&WebRTCSession::instance(),
|
connect(&WebRTCSession::instance(),
|
||||||
&WebRTCSession::pipelineChanged,
|
&WebRTCSession::stateChanged,
|
||||||
this,
|
this,
|
||||||
[this](bool callStarted) {
|
[this](WebRTCSession::State state) {
|
||||||
if (callStarted)
|
if (state == WebRTCSession::State::DISCONNECTED)
|
||||||
activeCallBar_->show();
|
|
||||||
else
|
|
||||||
activeCallBar_->hide();
|
activeCallBar_->hide();
|
||||||
|
else
|
||||||
|
activeCallBar_->show();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Splitter
|
// Splitter
|
||||||
|
@ -469,22 +469,28 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||||
if (callManager_.onActiveCall()) {
|
if (callManager_.onActiveCall()) {
|
||||||
callManager_.hangUp();
|
callManager_.hangUp();
|
||||||
} else {
|
} else {
|
||||||
if (cache::singleRoomInfo(current_room_.toStdString()).member_count != 2) {
|
if (auto roomInfo =
|
||||||
showNotification("Voice/Video calls are limited to 1:1 rooms");
|
cache::singleRoomInfo(current_room_.toStdString());
|
||||||
|
roomInfo.member_count != 2) {
|
||||||
|
showNotification("Voice calls are limited to 1:1 rooms.");
|
||||||
} else {
|
} else {
|
||||||
std::vector<RoomMember> members(
|
std::vector<RoomMember> members(
|
||||||
cache::getMembers(current_room_.toStdString()));
|
cache::getMembers(current_room_.toStdString()));
|
||||||
const RoomMember &callee =
|
const RoomMember &callee =
|
||||||
members.front().user_id == utils::localUser() ? members.back()
|
members.front().user_id == utils::localUser() ? members.back()
|
||||||
: members.front();
|
: members.front();
|
||||||
auto dialog =
|
auto dialog = new dialogs::PlaceCall(
|
||||||
new dialogs::PlaceCall(callee.user_id, callee.display_name, MainWindow::instance());
|
callee.user_id,
|
||||||
|
callee.display_name,
|
||||||
|
QString::fromStdString(roomInfo.name),
|
||||||
|
QString::fromStdString(roomInfo.avatar_url),
|
||||||
|
MainWindow::instance());
|
||||||
connect(dialog, &dialogs::PlaceCall::voice, this, [this]() {
|
connect(dialog, &dialogs::PlaceCall::voice, this, [this]() {
|
||||||
callManager_.sendInvite(current_room_);
|
callManager_.sendInvite(current_room_);
|
||||||
});
|
});
|
||||||
connect(dialog, &dialogs::PlaceCall::video, this, [this]() {
|
/*connect(dialog, &dialogs::PlaceCall::video, this, [this]() {
|
||||||
showNotification("Video calls not yet implemented");
|
showNotification("Video calls not yet implemented.");
|
||||||
});
|
});*/
|
||||||
utils::centerWidget(dialog, MainWindow::instance());
|
utils::centerWidget(dialog, MainWindow::instance());
|
||||||
dialog->show();
|
dialog->show();
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "TextInputWidget.h"
|
#include "TextInputWidget.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "WebRTCSession.h"
|
|
||||||
#include "ui/FlatButton.h"
|
#include "ui/FlatButton.h"
|
||||||
#include "ui/LoadingIndicator.h"
|
#include "ui/LoadingIndicator.h"
|
||||||
|
|
||||||
|
@ -455,9 +454,9 @@ TextInputWidget::TextInputWidget(QWidget *parent)
|
||||||
topLayout_->setContentsMargins(13, 1, 13, 0);
|
topLayout_->setContentsMargins(13, 1, 13, 0);
|
||||||
|
|
||||||
callBtn_ = new FlatButton(this);
|
callBtn_ = new FlatButton(this);
|
||||||
changeCallButtonState(false);
|
changeCallButtonState(WebRTCSession::State::DISCONNECTED);
|
||||||
connect(&WebRTCSession::instance(),
|
connect(&WebRTCSession::instance(),
|
||||||
&WebRTCSession::pipelineChanged,
|
&WebRTCSession::stateChanged,
|
||||||
this,
|
this,
|
||||||
&TextInputWidget::changeCallButtonState);
|
&TextInputWidget::changeCallButtonState);
|
||||||
|
|
||||||
|
@ -664,17 +663,16 @@ TextInputWidget::paintEvent(QPaintEvent *)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
TextInputWidget::changeCallButtonState(bool callStarted)
|
TextInputWidget::changeCallButtonState(WebRTCSession::State state)
|
||||||
{
|
{
|
||||||
// TODO Telephone and HangUp icons - co-opt the ones below for now
|
|
||||||
QIcon icon;
|
QIcon icon;
|
||||||
if (callStarted) {
|
if (state == WebRTCSession::State::DISCONNECTED) {
|
||||||
callBtn_->setToolTip(tr("Hang up"));
|
|
||||||
icon.addFile(":/icons/icons/ui/remove-symbol.png");
|
|
||||||
} else {
|
|
||||||
callBtn_->setToolTip(tr("Place a call"));
|
callBtn_->setToolTip(tr("Place a call"));
|
||||||
icon.addFile(":/icons/icons/ui/speech-bubbles-comment-option.png");
|
icon.addFile(":/icons/icons/ui/place-call.png");
|
||||||
|
} else {
|
||||||
|
callBtn_->setToolTip(tr("Hang up"));
|
||||||
|
icon.addFile(":/icons/icons/ui/end-call.png");
|
||||||
}
|
}
|
||||||
callBtn_->setIcon(icon);
|
callBtn_->setIcon(icon);
|
||||||
callBtn_->setIconSize(QSize(ButtonHeight, ButtonHeight));
|
callBtn_->setIconSize(QSize(ButtonHeight * 1.1, ButtonHeight * 1.1));
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include <QTextEdit>
|
#include <QTextEdit>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "WebRTCSession.h"
|
||||||
#include "dialogs/PreviewUploadOverlay.h"
|
#include "dialogs/PreviewUploadOverlay.h"
|
||||||
#include "emoji/PickButton.h"
|
#include "emoji/PickButton.h"
|
||||||
#include "popups/SuggestionsPopup.h"
|
#include "popups/SuggestionsPopup.h"
|
||||||
|
@ -149,7 +150,7 @@ public slots:
|
||||||
void openFileSelection();
|
void openFileSelection();
|
||||||
void hideUploadSpinner();
|
void hideUploadSpinner();
|
||||||
void focusLineEdit() { input_->setFocus(); }
|
void focusLineEdit() { input_->setFocus(); }
|
||||||
void changeCallButtonState(bool callStarted);
|
void changeCallButtonState(WebRTCSession::State);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void addSelectedEmoji(const QString &emoji);
|
void addSelectedEmoji(const QString &emoji);
|
||||||
|
|
|
@ -11,6 +11,8 @@ extern "C" {
|
||||||
#include "gst/webrtc/webrtc.h"
|
#include "gst/webrtc/webrtc.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(WebRTCSession::State)
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
bool gisoffer;
|
bool gisoffer;
|
||||||
std::string glocalsdp;
|
std::string glocalsdp;
|
||||||
|
@ -29,6 +31,12 @@ std::string::const_iterator findName(const std::string &sdp, const std::string
|
||||||
int getPayloadType(const std::string &sdp, const std::string &name);
|
int getPayloadType(const std::string &sdp, const std::string &name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WebRTCSession::WebRTCSession() : QObject()
|
||||||
|
{
|
||||||
|
qRegisterMetaType<WebRTCSession::State>();
|
||||||
|
connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState);
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
WebRTCSession::init(std::string *errorMessage)
|
WebRTCSession::init(std::string *errorMessage)
|
||||||
{
|
{
|
||||||
|
@ -54,14 +62,14 @@ WebRTCSession::init(std::string *errorMessage)
|
||||||
nhlog::ui()->info("Initialised " + gstVersion);
|
nhlog::ui()->info("Initialised " + gstVersion);
|
||||||
|
|
||||||
// GStreamer Plugins:
|
// GStreamer Plugins:
|
||||||
// Base: audioconvert, audioresample, opus, playback, videoconvert, volume
|
// Base: audioconvert, audioresample, opus, playback, volume
|
||||||
// Good: autodetect, rtpmanager, vpx
|
// Good: autodetect, rtpmanager, vpx
|
||||||
// Bad: dtls, srtp, webrtc
|
// Bad: dtls, srtp, webrtc
|
||||||
// libnice [GLib]: nice
|
// libnice [GLib]: nice
|
||||||
initialised_ = true;
|
initialised_ = true;
|
||||||
std::string strError = gstVersion + ": Missing plugins: ";
|
std::string strError = gstVersion + ": Missing plugins: ";
|
||||||
const gchar *needed[] = {"audioconvert", "audioresample", "autodetect", "dtls", "nice",
|
const gchar *needed[] = {"audioconvert", "audioresample", "autodetect", "dtls", "nice",
|
||||||
"opus", "playback", "rtpmanager", "srtp", "videoconvert", "vpx", "volume", "webrtc", nullptr};
|
"opus", "playback", "rtpmanager", "srtp", "vpx", "volume", "webrtc", nullptr};
|
||||||
GstRegistry *registry = gst_registry_get();
|
GstRegistry *registry = gst_registry_get();
|
||||||
for (guint i = 0; i < g_strv_length((gchar**)needed); i++) {
|
for (guint i = 0; i < g_strv_length((gchar**)needed); i++) {
|
||||||
GstPlugin *plugin = gst_registry_find_plugin(registry, needed[i]);
|
GstPlugin *plugin = gst_registry_find_plugin(registry, needed[i]);
|
||||||
|
@ -91,17 +99,19 @@ WebRTCSession::createOffer()
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
WebRTCSession::acceptOffer(const std::string& sdp)
|
WebRTCSession::acceptOffer(const std::string &sdp)
|
||||||
{
|
{
|
||||||
nhlog::ui()->debug("Received offer:\n{}", sdp);
|
nhlog::ui()->debug("Received offer:\n{}", sdp);
|
||||||
|
if (state_ != State::DISCONNECTED)
|
||||||
|
return false;
|
||||||
|
|
||||||
gisoffer = false;
|
gisoffer = false;
|
||||||
glocalsdp.clear();
|
glocalsdp.clear();
|
||||||
gcandidates.clear();
|
gcandidates.clear();
|
||||||
|
|
||||||
int opusPayloadType = getPayloadType(sdp, "opus");
|
int opusPayloadType = getPayloadType(sdp, "opus");
|
||||||
if (opusPayloadType == -1) {
|
if (opusPayloadType == -1)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
GstWebRTCSessionDescription *offer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_OFFER);
|
GstWebRTCSessionDescription *offer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_OFFER);
|
||||||
if (!offer)
|
if (!offer)
|
||||||
|
@ -120,9 +130,11 @@ WebRTCSession::acceptOffer(const std::string& sdp)
|
||||||
bool
|
bool
|
||||||
WebRTCSession::startPipeline(int opusPayloadType)
|
WebRTCSession::startPipeline(int opusPayloadType)
|
||||||
{
|
{
|
||||||
if (isActive())
|
if (state_ != State::DISCONNECTED)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
emit stateChanged(State::INITIATING);
|
||||||
|
|
||||||
if (!createPipeline(opusPayloadType))
|
if (!createPipeline(opusPayloadType))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -132,7 +144,12 @@ WebRTCSession::startPipeline(int opusPayloadType)
|
||||||
nhlog::ui()->info("WebRTC: Setting STUN server: {}", stunServer_);
|
nhlog::ui()->info("WebRTC: Setting STUN server: {}", stunServer_);
|
||||||
g_object_set(webrtc_, "stun-server", stunServer_.c_str(), nullptr);
|
g_object_set(webrtc_, "stun-server", stunServer_.c_str(), nullptr);
|
||||||
}
|
}
|
||||||
addTurnServers();
|
|
||||||
|
for (const auto &uri : turnServers_) {
|
||||||
|
nhlog::ui()->info("WebRTC: Setting TURN server: {}", uri);
|
||||||
|
gboolean udata;
|
||||||
|
g_signal_emit_by_name(webrtc_, "add-turn-server", uri.c_str(), (gpointer)(&udata));
|
||||||
|
}
|
||||||
|
|
||||||
// generate the offer when the pipeline goes to PLAYING
|
// generate the offer when the pipeline goes to PLAYING
|
||||||
if (gisoffer)
|
if (gisoffer)
|
||||||
|
@ -152,16 +169,14 @@ WebRTCSession::startPipeline(int opusPayloadType)
|
||||||
GstStateChangeReturn ret = gst_element_set_state(pipe_, GST_STATE_PLAYING);
|
GstStateChangeReturn ret = gst_element_set_state(pipe_, GST_STATE_PLAYING);
|
||||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||||
nhlog::ui()->error("WebRTC: unable to start pipeline");
|
nhlog::ui()->error("WebRTC: unable to start pipeline");
|
||||||
gst_object_unref(pipe_);
|
end();
|
||||||
pipe_ = nullptr;
|
|
||||||
webrtc_ = nullptr;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe_));
|
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe_));
|
||||||
gst_bus_add_watch(bus, newBusMessage, this);
|
gst_bus_add_watch(bus, newBusMessage, this);
|
||||||
gst_object_unref(bus);
|
gst_object_unref(bus);
|
||||||
emit pipelineChanged(true);
|
emit stateChanged(State::INITIATED);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,10 +195,7 @@ WebRTCSession::createPipeline(int opusPayloadType)
|
||||||
if (error) {
|
if (error) {
|
||||||
nhlog::ui()->error("WebRTC: Failed to parse pipeline: {}", error->message);
|
nhlog::ui()->error("WebRTC: Failed to parse pipeline: {}", error->message);
|
||||||
g_error_free(error);
|
g_error_free(error);
|
||||||
if (pipe_) {
|
end();
|
||||||
gst_object_unref(pipe_);
|
|
||||||
pipe_ = nullptr;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -193,7 +205,7 @@ bool
|
||||||
WebRTCSession::acceptAnswer(const std::string &sdp)
|
WebRTCSession::acceptAnswer(const std::string &sdp)
|
||||||
{
|
{
|
||||||
nhlog::ui()->debug("WebRTC: Received sdp:\n{}", sdp);
|
nhlog::ui()->debug("WebRTC: Received sdp:\n{}", sdp);
|
||||||
if (!isActive())
|
if (state_ != State::OFFERSENT)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
GstWebRTCSessionDescription *answer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_ANSWER);
|
GstWebRTCSessionDescription *answer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_ANSWER);
|
||||||
|
@ -206,18 +218,20 @@ WebRTCSession::acceptAnswer(const std::string &sdp)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
WebRTCSession::acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate>& candidates)
|
WebRTCSession::acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate> &candidates)
|
||||||
{
|
{
|
||||||
if (isActive()) {
|
if (state_ >= State::INITIATED) {
|
||||||
for (const auto& c : candidates)
|
for (const auto &c : candidates)
|
||||||
g_signal_emit_by_name(webrtc_, "add-ice-candidate", c.sdpMLineIndex, c.candidate.c_str());
|
g_signal_emit_by_name(webrtc_, "add-ice-candidate", c.sdpMLineIndex, c.candidate.c_str());
|
||||||
}
|
}
|
||||||
|
if (state_ < State::CONNECTED)
|
||||||
|
emit stateChanged(State::CONNECTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
WebRTCSession::toggleMuteAudioSrc(bool &isMuted)
|
WebRTCSession::toggleMuteAudioSrc(bool &isMuted)
|
||||||
{
|
{
|
||||||
if (!isActive())
|
if (state_ < State::INITIATED)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
|
GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
|
||||||
|
@ -241,20 +255,7 @@ WebRTCSession::end()
|
||||||
pipe_ = nullptr;
|
pipe_ = nullptr;
|
||||||
}
|
}
|
||||||
webrtc_ = nullptr;
|
webrtc_ = nullptr;
|
||||||
emit pipelineChanged(false);
|
emit stateChanged(State::DISCONNECTED);
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
WebRTCSession::addTurnServers()
|
|
||||||
{
|
|
||||||
if (!webrtc_)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (const auto &uri : turnServers_) {
|
|
||||||
nhlog::ui()->info("WebRTC: Setting TURN server: {}", uri);
|
|
||||||
gboolean udata;
|
|
||||||
g_signal_emit_by_name(webrtc_, "add-turn-server", uri.c_str(), (gpointer)(&udata));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -373,8 +374,10 @@ gboolean
|
||||||
onICEGatheringCompletion(gpointer timerid)
|
onICEGatheringCompletion(gpointer timerid)
|
||||||
{
|
{
|
||||||
*(guint*)(timerid) = 0;
|
*(guint*)(timerid) = 0;
|
||||||
if (gisoffer)
|
if (gisoffer) {
|
||||||
emit WebRTCSession::instance().offerCreated(glocalsdp, gcandidates);
|
emit WebRTCSession::instance().offerCreated(glocalsdp, gcandidates);
|
||||||
|
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::OFFERSENT);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
emit WebRTCSession::instance().answerCreated(glocalsdp, gcandidates);
|
emit WebRTCSession::instance().answerCreated(glocalsdp, gcandidates);
|
||||||
|
|
||||||
|
@ -445,6 +448,9 @@ linkNewPad(GstElement *decodebin G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe
|
||||||
if (queuepad) {
|
if (queuepad) {
|
||||||
if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, queuepad)))
|
if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, queuepad)))
|
||||||
nhlog::ui()->error("WebRTC: Unable to link new pad");
|
nhlog::ui()->error("WebRTC: Unable to link new pad");
|
||||||
|
else {
|
||||||
|
emit WebRTCSession::instance().stateChanged(WebRTCSession::State::CONNECTED);
|
||||||
|
}
|
||||||
gst_object_unref(queuepad);
|
gst_object_unref(queuepad);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,15 @@ class WebRTCSession : public QObject
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
enum class State {
|
||||||
|
DISCONNECTED,
|
||||||
|
INITIATING,
|
||||||
|
INITIATED,
|
||||||
|
OFFERSENT,
|
||||||
|
CONNECTING,
|
||||||
|
CONNECTED
|
||||||
|
};
|
||||||
|
|
||||||
static WebRTCSession& instance()
|
static WebRTCSession& instance()
|
||||||
{
|
{
|
||||||
static WebRTCSession instance;
|
static WebRTCSession instance;
|
||||||
|
@ -27,7 +36,7 @@ public:
|
||||||
bool acceptAnswer(const std::string &sdp);
|
bool acceptAnswer(const std::string &sdp);
|
||||||
void acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate>&);
|
void acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate>&);
|
||||||
|
|
||||||
bool isActive() { return pipe_ != nullptr; }
|
State state() const {return state_;}
|
||||||
bool toggleMuteAudioSrc(bool &isMuted);
|
bool toggleMuteAudioSrc(bool &isMuted);
|
||||||
void end();
|
void end();
|
||||||
|
|
||||||
|
@ -37,12 +46,16 @@ public:
|
||||||
signals:
|
signals:
|
||||||
void offerCreated(const std::string &sdp, const std::vector<mtx::events::msg::CallCandidates::Candidate>&);
|
void offerCreated(const std::string &sdp, const std::vector<mtx::events::msg::CallCandidates::Candidate>&);
|
||||||
void answerCreated(const std::string &sdp, const std::vector<mtx::events::msg::CallCandidates::Candidate>&);
|
void answerCreated(const std::string &sdp, const std::vector<mtx::events::msg::CallCandidates::Candidate>&);
|
||||||
void pipelineChanged(bool started);
|
void stateChanged(WebRTCSession::State); // explicit qualifier necessary for Qt
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void setState(State state) {state_ = state;}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WebRTCSession() : QObject() {}
|
WebRTCSession();
|
||||||
|
|
||||||
bool initialised_ = false;
|
bool initialised_ = false;
|
||||||
|
State state_ = State::DISCONNECTED;
|
||||||
GstElement *pipe_ = nullptr;
|
GstElement *pipe_ = nullptr;
|
||||||
GstElement *webrtc_ = nullptr;
|
GstElement *webrtc_ = nullptr;
|
||||||
std::string stunServer_;
|
std::string stunServer_;
|
||||||
|
@ -50,7 +63,6 @@ private:
|
||||||
|
|
||||||
bool startPipeline(int opusPayloadType);
|
bool startPipeline(int opusPayloadType);
|
||||||
bool createPipeline(int opusPayloadType);
|
bool createPipeline(int opusPayloadType);
|
||||||
void addTurnServers();
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WebRTCSession(WebRTCSession const&) = delete;
|
WebRTCSession(WebRTCSession const&) = delete;
|
||||||
|
|
|
@ -1,43 +1,83 @@
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
#include <QString>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
#include "Utils.h"
|
||||||
#include "dialogs/AcceptCall.h"
|
#include "dialogs/AcceptCall.h"
|
||||||
|
#include "ui/Avatar.h"
|
||||||
|
|
||||||
namespace dialogs {
|
namespace dialogs {
|
||||||
|
|
||||||
AcceptCall::AcceptCall(const QString &caller, const QString &displayName, QWidget *parent)
|
AcceptCall::AcceptCall(
|
||||||
: QWidget(parent)
|
const QString &caller,
|
||||||
|
const QString &displayName,
|
||||||
|
const QString &roomName,
|
||||||
|
const QString &avatarUrl,
|
||||||
|
QWidget *parent) : QWidget(parent)
|
||||||
{
|
{
|
||||||
setAutoFillBackground(true);
|
setAutoFillBackground(true);
|
||||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||||
setWindowModality(Qt::WindowModal);
|
setWindowModality(Qt::WindowModal);
|
||||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||||
|
|
||||||
|
setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
|
||||||
|
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||||
|
|
||||||
auto layout = new QVBoxLayout(this);
|
auto layout = new QVBoxLayout(this);
|
||||||
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||||
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||||
|
|
||||||
auto buttonLayout = new QHBoxLayout();
|
QFont f;
|
||||||
buttonLayout->setSpacing(15);
|
f.setPointSizeF(f.pointSizeF());
|
||||||
buttonLayout->setMargin(0);
|
|
||||||
|
|
||||||
|
QFont labelFont;
|
||||||
|
labelFont.setWeight(QFont::Medium);
|
||||||
|
|
||||||
|
QLabel *displayNameLabel = nullptr;
|
||||||
|
if (!displayName.isEmpty() && displayName != caller) {
|
||||||
|
displayNameLabel = new QLabel(displayName, this);
|
||||||
|
labelFont.setPointSizeF(f.pointSizeF() * 2);
|
||||||
|
displayNameLabel ->setFont(labelFont);
|
||||||
|
displayNameLabel ->setAlignment(Qt::AlignCenter);
|
||||||
|
}
|
||||||
|
|
||||||
|
QLabel *callerLabel = new QLabel(caller, this);
|
||||||
|
labelFont.setPointSizeF(f.pointSizeF() * 1.2);
|
||||||
|
callerLabel->setFont(labelFont);
|
||||||
|
callerLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
|
||||||
|
QLabel *voiceCallLabel = new QLabel("Voice Call", this);
|
||||||
|
labelFont.setPointSizeF(f.pointSizeF() * 1.1);
|
||||||
|
voiceCallLabel->setFont(labelFont);
|
||||||
|
voiceCallLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
|
||||||
|
auto avatar = new Avatar(this, QFontMetrics(f).height() * 6);
|
||||||
|
if (!avatarUrl.isEmpty())
|
||||||
|
avatar->setImage(avatarUrl);
|
||||||
|
else
|
||||||
|
avatar->setLetter(utils::firstChar(roomName));
|
||||||
|
|
||||||
|
const int iconSize = 24;
|
||||||
|
auto buttonLayout = new QHBoxLayout();
|
||||||
|
buttonLayout->setSpacing(20);
|
||||||
acceptBtn_ = new QPushButton(tr("Accept"), this);
|
acceptBtn_ = new QPushButton(tr("Accept"), this);
|
||||||
acceptBtn_->setDefault(true);
|
acceptBtn_->setDefault(true);
|
||||||
rejectBtn_ = new QPushButton(tr("Reject"), this);
|
acceptBtn_->setIcon(QIcon(":/icons/icons/ui/place-call.png"));
|
||||||
|
acceptBtn_->setIconSize(QSize(iconSize, iconSize));
|
||||||
|
|
||||||
buttonLayout->addStretch(1);
|
rejectBtn_ = new QPushButton(tr("Reject"), this);
|
||||||
|
rejectBtn_->setIcon(QIcon(":/icons/icons/ui/end-call.png"));
|
||||||
|
rejectBtn_->setIconSize(QSize(iconSize, iconSize));
|
||||||
buttonLayout->addWidget(acceptBtn_);
|
buttonLayout->addWidget(acceptBtn_);
|
||||||
buttonLayout->addWidget(rejectBtn_);
|
buttonLayout->addWidget(rejectBtn_);
|
||||||
|
|
||||||
QLabel *label;
|
if (displayNameLabel)
|
||||||
if (!displayName.isEmpty() && displayName != caller)
|
layout->addWidget(displayNameLabel, 0, Qt::AlignCenter);
|
||||||
label = new QLabel("Accept call from " + displayName + " (" + caller + ")?", this);
|
layout->addWidget(callerLabel, 0, Qt::AlignCenter);
|
||||||
else
|
layout->addWidget(voiceCallLabel, 0, Qt::AlignCenter);
|
||||||
label = new QLabel("Accept call from " + caller + "?", this);
|
layout->addWidget(avatar, 0, Qt::AlignCenter);
|
||||||
|
|
||||||
layout->addWidget(label);
|
|
||||||
layout->addLayout(buttonLayout);
|
layout->addLayout(buttonLayout);
|
||||||
|
|
||||||
connect(acceptBtn_, &QPushButton::clicked, this, [this]() {
|
connect(acceptBtn_, &QPushButton::clicked, this, [this]() {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
class QPushButton;
|
class QPushButton;
|
||||||
|
class QString;
|
||||||
|
|
||||||
namespace dialogs {
|
namespace dialogs {
|
||||||
|
|
||||||
|
@ -12,7 +12,12 @@ class AcceptCall : public QWidget
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AcceptCall(const QString &caller, const QString &displayName, QWidget *parent = nullptr);
|
AcceptCall(
|
||||||
|
const QString &caller,
|
||||||
|
const QString &displayName,
|
||||||
|
const QString &roomName,
|
||||||
|
const QString &avatarUrl,
|
||||||
|
QWidget *parent = nullptr);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void accept();
|
void accept();
|
||||||
|
|
|
@ -4,12 +4,18 @@
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
#include "Utils.h"
|
||||||
#include "dialogs/PlaceCall.h"
|
#include "dialogs/PlaceCall.h"
|
||||||
|
#include "ui/Avatar.h"
|
||||||
|
|
||||||
namespace dialogs {
|
namespace dialogs {
|
||||||
|
|
||||||
PlaceCall::PlaceCall(const QString &callee, const QString &displayName, QWidget *parent)
|
PlaceCall::PlaceCall(
|
||||||
: QWidget(parent)
|
const QString &callee,
|
||||||
|
const QString &displayName,
|
||||||
|
const QString &roomName,
|
||||||
|
const QString &avatarUrl,
|
||||||
|
QWidget *parent) : QWidget(parent)
|
||||||
{
|
{
|
||||||
setAutoFillBackground(true);
|
setAutoFillBackground(true);
|
||||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||||
|
@ -20,25 +26,31 @@ PlaceCall::PlaceCall(const QString &callee, const QString &displayName, QWidget
|
||||||
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||||
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||||
|
|
||||||
auto buttonLayout = new QHBoxLayout();
|
auto buttonLayout = new QHBoxLayout(this);
|
||||||
buttonLayout->setSpacing(15);
|
buttonLayout->setSpacing(15);
|
||||||
buttonLayout->setMargin(0);
|
buttonLayout->setMargin(0);
|
||||||
|
|
||||||
|
QFont f;
|
||||||
|
f.setPointSizeF(f.pointSizeF());
|
||||||
|
auto avatar = new Avatar(this, QFontMetrics(f).height() * 3);
|
||||||
|
if (!avatarUrl.isEmpty())
|
||||||
|
avatar->setImage(avatarUrl);
|
||||||
|
else
|
||||||
|
avatar->setLetter(utils::firstChar(roomName));
|
||||||
|
|
||||||
voiceBtn_ = new QPushButton(tr("Voice Call"), this);
|
voiceBtn_ = new QPushButton(tr("Voice Call"), this);
|
||||||
voiceBtn_->setDefault(true);
|
voiceBtn_->setDefault(true);
|
||||||
videoBtn_ = new QPushButton(tr("Video Call"), this);
|
//videoBtn_ = new QPushButton(tr("Video Call"), this);
|
||||||
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
||||||
|
|
||||||
buttonLayout->addStretch(1);
|
buttonLayout->addStretch(1);
|
||||||
|
buttonLayout->addWidget(avatar);
|
||||||
buttonLayout->addWidget(voiceBtn_);
|
buttonLayout->addWidget(voiceBtn_);
|
||||||
buttonLayout->addWidget(videoBtn_);
|
//buttonLayout->addWidget(videoBtn_);
|
||||||
buttonLayout->addWidget(cancelBtn_);
|
buttonLayout->addWidget(cancelBtn_);
|
||||||
|
|
||||||
QLabel *label;
|
QString name = displayName.isEmpty() ? callee : displayName;
|
||||||
if (!displayName.isEmpty() && displayName != callee)
|
QLabel *label = new QLabel("Place a call to " + name + "?", this);
|
||||||
label = new QLabel("Place a call to " + displayName + " (" + callee + ")?", this);
|
|
||||||
else
|
|
||||||
label = new QLabel("Place a call to " + callee + "?", this);
|
|
||||||
|
|
||||||
layout->addWidget(label);
|
layout->addWidget(label);
|
||||||
layout->addLayout(buttonLayout);
|
layout->addLayout(buttonLayout);
|
||||||
|
@ -47,10 +59,10 @@ PlaceCall::PlaceCall(const QString &callee, const QString &displayName, QWidget
|
||||||
emit voice();
|
emit voice();
|
||||||
emit close();
|
emit close();
|
||||||
});
|
});
|
||||||
connect(videoBtn_, &QPushButton::clicked, this, [this]() {
|
/*connect(videoBtn_, &QPushButton::clicked, this, [this]() {
|
||||||
emit video();
|
emit video();
|
||||||
emit close();
|
emit close();
|
||||||
});
|
});*/
|
||||||
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
|
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
|
||||||
emit cancel();
|
emit cancel();
|
||||||
emit close();
|
emit close();
|
||||||
|
|
|
@ -12,16 +12,21 @@ class PlaceCall : public QWidget
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PlaceCall(const QString &callee, const QString &displayName, QWidget *parent = nullptr);
|
PlaceCall(
|
||||||
|
const QString &callee,
|
||||||
|
const QString &displayName,
|
||||||
|
const QString &roomName,
|
||||||
|
const QString &avatarUrl,
|
||||||
|
QWidget *parent = nullptr);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void voice();
|
void voice();
|
||||||
void video();
|
// void video();
|
||||||
void cancel();
|
void cancel();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPushButton *voiceBtn_;
|
QPushButton *voiceBtn_;
|
||||||
QPushButton *videoBtn_;
|
// QPushButton *videoBtn_;
|
||||||
QPushButton *cancelBtn_;
|
QPushButton *cancelBtn_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue