diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9280f7aa..4348e819 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -279,7 +279,6 @@ set(SRC_FILES
src/ui/ThemeManager.cpp
src/ui/UserProfile.cpp
- src/ActiveCallBar.cpp
src/AvatarProvider.cpp
src/BlurhashProvider.cpp
src/Cache.cpp
@@ -492,7 +491,6 @@ qt5_wrap_cpp(MOC_HEADERS
src/notifications/Manager.h
- src/ActiveCallBar.h
src/AvatarProvider.h
src/BlurhashProvider.h
src/Cache_p.h
diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts
new file mode 100644
index 00000000..66e07bb4
--- /dev/null
+++ b/resources/langs/nheko_pt_PT.ts
@@ -0,0 +1,1993 @@
+
+
+
+
+ Cache
+
+
+
+
+
+
+
+ ChatPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ CommunitiesListItem
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tag translation for m.server_notice
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EditModal
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EmojiPicker
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EncryptionIndicator
+
+
+
+
+
+
+
+
+
+
+
+
+ EventStore
+
+
+
+ Placeholder, when the message was not decrypted yet or can't be decrypted.
+
+
+
+
+
+ Placeholder, when the message can't be decrypted, because the DB access failed.
+
+
+
+
+
+ Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1.
+
+
+
+
+
+ Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ InviteeItem
+
+
+
+
+
+
+
+ LoginPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MemberList
+
+
+
+
+
+
+
+
+
+
+
+
+ MessageDelegate
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Placeholder
+
+
+
+
+
+
+
+ QuickSwitcher
+
+
+
+
+
+
+
+ RegisterPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RoomInfo
+
+
+
+
+
+
+
+ RoomInfoListItem
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Standard matrix tag for favourites
+
+
+
+
+
+ Standard matrix tag for low priority rooms
+
+
+
+
+
+ Standard matrix tag for server notices
+
+
+
+
+
+ WhatsThis hint for tag menu actions
+
+
+
+
+
+ Add a new tag to the room
+
+
+
+
+
+ Tag name prompt title
+
+
+
+
+
+ Tag name prompt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SideBarActions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ StatusIndicator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TextInputWidget
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TimelineModel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This is a leave event after the user already left and shouldn't happen apart from state resets
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TimelineRow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TimelineView
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TrayIcon
+
+
+
+
+
+
+
+
+
+
+
+
+ UserInfoWidget
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UserSettingsPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ WelcomePage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ descriptiveTime
+
+
+
+
+
+
+
+ dialogs::AcceptCall
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::CreateRoom
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::FallbackAuth
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::InviteUsers
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::JoinRoom
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::LeaveRoom
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::Logout
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::PlaceCall
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::PreviewUploadOverlay
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::ReCaptcha
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::ReadReceipts
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::ReceiptItem
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::RoomSettings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dialogs::UserProfile
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ emoji::Panel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ message-description sent:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ popups::UserMentions
+
+
+
+
+
+
+
+
+
+
+
+
+ utils
+
+
+
+
+
+
+
diff --git a/resources/qml/ActiveCallBar.qml b/resources/qml/ActiveCallBar.qml
new file mode 100644
index 00000000..61484625
--- /dev/null
+++ b/resources/qml/ActiveCallBar.qml
@@ -0,0 +1,111 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.2
+
+import im.nheko 1.0
+
+Rectangle {
+ id: activeCallBar
+ visible: TimelineManager.callState != WebRTCState.DISCONNECTED
+ color: "#2ECC71"
+ implicitHeight: rowLayout.height + 8
+
+ RowLayout {
+ id: rowLayout
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.leftMargin: 8
+
+ Avatar {
+ width: avatarSize
+ height: avatarSize
+
+ url: TimelineManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
+ displayName: TimelineManager.callPartyName
+ }
+
+ Label {
+ font.pointSize: fontMetrics.font.pointSize * 1.1
+ text: " " + TimelineManager.callPartyName + " "
+ }
+
+ Image {
+ Layout.preferredWidth: 24
+ Layout.preferredHeight: 24
+ source: "qrc:/icons/icons/ui/place-call.png"
+ }
+
+ Label {
+ id: callStateLabel
+ font.pointSize: fontMetrics.font.pointSize * 1.1
+ }
+
+ Connections {
+ target: TimelineManager
+ function onCallStateChanged(state) {
+ switch (state) {
+ case WebRTCState.INITIATING:
+ callStateLabel.text = qsTr("Initiating...")
+ break;
+ case WebRTCState.OFFERSENT:
+ callStateLabel.text = qsTr("Calling...")
+ break;
+ case WebRTCState.CONNECTING:
+ callStateLabel.text = qsTr("Connecting...")
+ break;
+ case WebRTCState.CONNECTED:
+ callStateLabel.text = "00:00"
+ var d = new Date()
+ callTimer.startTime = Math.floor(d.getTime() / 1000)
+ break;
+ case WebRTCState.DISCONNECTED:
+ callStateLabel.text = ""
+ }
+ }
+ }
+
+ Timer {
+ id: callTimer
+ property int startTime
+ interval: 1000
+ running: TimelineManager.callState == WebRTCState.CONNECTED
+ repeat: true
+ onTriggered: {
+ var d = new Date()
+ let seconds = Math.floor(d.getTime() / 1000 - startTime)
+ let s = Math.floor(seconds % 60)
+ let m = Math.floor(seconds / 60) % 60
+ let h = Math.floor(seconds / 3600)
+ callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s)
+ }
+
+ function pad(n) {
+ return (n < 10) ? ("0" + n) : n
+ }
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ ImageButton {
+ width: 24
+ height: 24
+ buttonTextColor: "#000000"
+ image: TimelineManager.isMicMuted ?
+ ":/icons/icons/ui/microphone-unmute.png" :
+ ":/icons/icons/ui/microphone-mute.png"
+
+ hoverEnabled: true
+ ToolTip.visible: hovered
+ ToolTip.text: TimelineManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic")
+
+ onClicked: TimelineManager.toggleMicMute()
+ }
+
+ Item {
+ implicitWidth: 16
+ }
+ }
+}
diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml
index 67c3e008..df3dd08e 100644
--- a/resources/qml/Avatar.qml
+++ b/resources/qml/Avatar.qml
@@ -16,7 +16,7 @@ Rectangle {
Label {
anchors.fill: parent
- text: TimelineManager.escapeEmoji(String.fromCodePoint(displayName.codePointAt(0)))
+ text: TimelineManager.escapeEmoji(displayName ? String.fromCodePoint(displayName.codePointAt(0)) : "")
textFormat: Text.RichText
font.pixelSize: avatar.height/2
verticalAlignment: Text.AlignVCenter
diff --git a/resources/qml/ImageButton.qml b/resources/qml/ImageButton.qml
index dd67d597..54399ae7 100644
--- a/resources/qml/ImageButton.qml
+++ b/resources/qml/ImageButton.qml
@@ -3,6 +3,8 @@ import QtQuick.Controls 2.3
AbstractButton {
property string image: undefined
+ property color highlightColor: colors.highlight
+ property color buttonTextColor: colors.buttonText
width: 16
height: 16
id: button
@@ -11,7 +13,7 @@ AbstractButton {
id: buttonImg
// Workaround, can't get icon.source working for now...
anchors.fill: parent
- source: "image://colorimage/" + image + "?" + (button.hovered ? colors.highlight : colors.buttonText)
+ source: "image://colorimage/" + image + "?" + (button.hovered ? highlightColor : buttonTextColor)
}
MouseArea
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 1dbe7c1a..5c9ca348 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -516,6 +516,11 @@ Page {
}
}
}
+
+ ActiveCallBar {
+ Layout.fillWidth: true
+ z: 3
+ }
}
}
}
diff --git a/resources/res.qrc b/resources/res.qrc
index 64e5b084..87216e30 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -121,6 +121,7 @@
qtquickcontrols2.conf
qml/TimelineView.qml
+ qml/ActiveCallBar.qml
qml/Avatar.qml
qml/ImageButton.qml
qml/MatrixText.qml
diff --git a/src/ActiveCallBar.cpp b/src/ActiveCallBar.cpp
deleted file mode 100644
index c0d2c13a..00000000
--- a/src/ActiveCallBar.cpp
+++ /dev/null
@@ -1,160 +0,0 @@
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "ActiveCallBar.h"
-#include "ChatPage.h"
-#include "Utils.h"
-#include "WebRTCSession.h"
-#include "ui/Avatar.h"
-#include "ui/FlatButton.h"
-
-ActiveCallBar::ActiveCallBar(QWidget *parent)
- : QWidget(parent)
-{
- setAutoFillBackground(true);
- auto p = palette();
- p.setColor(backgroundRole(), QColor(46, 204, 113));
- setPalette(p);
-
- QFont f;
- f.setPointSizeF(f.pointSizeF());
-
- const int fontHeight = QFontMetrics(f).height();
- const int widgetMargin = fontHeight / 3;
- const int contentHeight = fontHeight * 3;
-
- setFixedHeight(contentHeight + widgetMargin);
-
- layout_ = new QHBoxLayout(this);
- layout_->setSpacing(widgetMargin);
- layout_->setContentsMargins(2 * widgetMargin, widgetMargin, 2 * widgetMargin, widgetMargin);
-
- QFont labelFont;
- labelFont.setPointSizeF(labelFont.pointSizeF() * 1.1);
- labelFont.setWeight(QFont::Medium);
-
- avatar_ = new Avatar(this, QFontMetrics(f).height() * 2.5);
-
- callPartyLabel_ = new QLabel(this);
- callPartyLabel_->setFont(labelFont);
-
- stateLabel_ = new QLabel(this);
- stateLabel_->setFont(labelFont);
-
- durationLabel_ = new QLabel(this);
- durationLabel_->setFont(labelFont);
- durationLabel_->hide();
-
- muteBtn_ = new FlatButton(this);
- setMuteIcon(false);
- muteBtn_->setFixedSize(buttonSize_, buttonSize_);
- muteBtn_->setCornerRadius(buttonSize_ / 2);
- connect(muteBtn_, &FlatButton::clicked, this, [this]() {
- if (WebRTCSession::instance().toggleMuteAudioSrc(muted_))
- setMuteIcon(muted_);
- });
-
- layout_->addWidget(avatar_, 0, Qt::AlignLeft);
- 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
-ActiveCallBar::setMuteIcon(bool muted)
-{
- QIcon icon;
- if (muted) {
- muteBtn_->setToolTip("Unmute Mic");
- 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:
- show();
- stateLabel_->setText("Initiating call...");
- break;
- case WebRTCSession::State::INITIATED:
- show();
- stateLabel_->setText("Call initiated...");
- break;
- case WebRTCSession::State::OFFERSENT:
- show();
- stateLabel_->setText("Calling...");
- break;
- case WebRTCSession::State::CONNECTING:
- show();
- stateLabel_->setText("Connecting...");
- break;
- case WebRTCSession::State::CONNECTED:
- show();
- callStartTime_ = QDateTime::currentSecsSinceEpoch();
- timer_->start(1000);
- stateLabel_->setPixmap(
- QIcon(":/icons/icons/ui/place-call.png").pixmap(QSize(buttonSize_, buttonSize_)));
- durationLabel_->setText("00:00");
- durationLabel_->show();
- break;
- case WebRTCSession::State::ICEFAILED:
- case WebRTCSession::State::DISCONNECTED:
- hide();
- timer_->stop();
- callPartyLabel_->setText(QString());
- stateLabel_->setText(QString());
- durationLabel_->setText(QString());
- durationLabel_->hide();
- setMuteIcon(false);
- break;
- default:
- break;
- }
-}
diff --git a/src/ActiveCallBar.h b/src/ActiveCallBar.h
deleted file mode 100644
index 1e940227..00000000
--- a/src/ActiveCallBar.h
+++ /dev/null
@@ -1,40 +0,0 @@
-#pragma once
-
-#include
-
-#include "WebRTCSession.h"
-
-class QHBoxLayout;
-class QLabel;
-class QTimer;
-class Avatar;
-class FlatButton;
-
-class ActiveCallBar : public QWidget
-{
- Q_OBJECT
-
-public:
- ActiveCallBar(QWidget *parent = nullptr);
-
-public slots:
- void update(WebRTCSession::State);
- void setCallParty(const QString &userid,
- const QString &displayName,
- const QString &roomName,
- const QString &avatarUrl);
-
-private:
- QHBoxLayout *layout_ = nullptr;
- Avatar *avatar_ = nullptr;
- QLabel *callPartyLabel_ = nullptr;
- QLabel *stateLabel_ = nullptr;
- QLabel *durationLabel_ = nullptr;
- FlatButton *muteBtn_ = nullptr;
- int buttonSize_ = 22;
- bool muted_ = false;
- qint64 callStartTime_ = 0;
- QTimer *timer_ = nullptr;
-
- void setMuteIcon(bool muted);
-};
diff --git a/src/CallManager.cpp b/src/CallManager.cpp
index 7a8d2ca7..b1d1a75a 100644
--- a/src/CallManager.cpp
+++ b/src/CallManager.cpp
@@ -52,7 +52,7 @@ CallManager::CallManager(QSharedPointer userSettings)
emit newMessage(roomid_, CallInvite{callid_, sdp, 0, timeoutms_});
emit newMessage(roomid_, CallCandidates{callid_, candidates, 0});
QTimer::singleShot(timeoutms_, this, [this]() {
- if (session_.state() == WebRTCSession::State::OFFERSENT) {
+ if (session_.state() == webrtc::State::OFFERSENT) {
hangUp(CallHangUp::Reason::InviteTimeOut);
emit ChatPage::instance()->showNotification(
"The remote side failed to pick up.");
@@ -99,13 +99,13 @@ CallManager::CallManager(QSharedPointer userSettings)
turnServerTimer_.setInterval(ttl * 1000 * 0.9);
});
- connect(&session_, &WebRTCSession::stateChanged, this, [this](WebRTCSession::State state) {
+ connect(&session_, &WebRTCSession::stateChanged, this, [this](webrtc::State state) {
switch (state) {
- case WebRTCSession::State::DISCONNECTED:
+ case webrtc::State::DISCONNECTED:
playRingtone("qrc:/media/media/callend.ogg", false);
clear();
break;
- case WebRTCSession::State::ICEFAILED: {
+ case webrtc::State::ICEFAILED: {
QString error("Call connection failed.");
if (turnURIs_.empty())
error += " Your homeserver has no configured TURN server.";
@@ -155,10 +155,9 @@ CallManager::sendInvite(const QString &roomid)
std::vector members(cache::getMembers(roomid.toStdString()));
const RoomMember &callee =
members.front().user_id == utils::localUser() ? members.back() : members.front();
- emit newCallParty(callee.user_id,
- callee.display_name,
- QString::fromStdString(roomInfo.name),
- QString::fromStdString(roomInfo.avatar_url));
+ callPartyName_ = callee.display_name.isEmpty() ? callee.user_id : callee.display_name;
+ callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
+ emit newCallParty();
playRingtone("qrc:/media/media/ringback.ogg", true);
if (!session_.createOffer()) {
emit ChatPage::instance()->showNotification("Problem setting up call.");
@@ -193,9 +192,9 @@ CallManager::hangUp(CallHangUp::Reason reason)
}
bool
-CallManager::onActiveCall()
+CallManager::onActiveCall() const
{
- return session_.state() != WebRTCSession::State::DISCONNECTED;
+ return session_.state() != webrtc::State::DISCONNECTED;
}
void
@@ -259,11 +258,9 @@ CallManager::handleEvent(const RoomEvent &callInviteEvent)
std::vector members(cache::getMembers(callInviteEvent.room_id));
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));
-
+ callPartyName_ = caller.display_name.isEmpty() ? caller.user_id : caller.display_name;
+ callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
+ emit newCallParty();
auto dialog = new dialogs::AcceptCall(caller.user_id,
caller.display_name,
QString::fromStdString(roomInfo.name),
@@ -376,6 +373,8 @@ void
CallManager::clear()
{
roomid_.clear();
+ callPartyName_.clear();
+ callPartyAvatarUrl_.clear();
callid_.clear();
remoteICECandidates_.clear();
}
diff --git a/src/CallManager.h b/src/CallManager.h
index 3a406438..640230a4 100644
--- a/src/CallManager.h
+++ b/src/CallManager.h
@@ -29,7 +29,9 @@ public:
void sendInvite(const QString &roomid);
void hangUp(
mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User);
- bool onActiveCall();
+ bool onActiveCall() const;
+ QString callPartyName() const { return callPartyName_; }
+ QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; }
void refreshTurnServer();
public slots:
@@ -40,11 +42,8 @@ signals:
void newMessage(const QString &roomid, const mtx::events::msg::CallCandidates &);
void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &);
void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &);
+ void newCallParty();
void turnServerRetrieved(const mtx::responses::TurnServer &);
- void newCallParty(const QString &userid,
- const QString &displayName,
- const QString &roomName,
- const QString &avatarUrl);
private slots:
void retrieveTurnServer();
@@ -52,6 +51,8 @@ private slots:
private:
WebRTCSession &session_;
QString roomid_;
+ QString callPartyName_;
+ QString callPartyAvatarUrl_;
std::string callid_;
const uint32_t timeoutms_ = 120000;
std::vector remoteICECandidates_;
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 87b4c277..8e93c0f4 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -22,7 +22,6 @@
#include
#include
-#include "ActiveCallBar.h"
#include "AvatarProvider.h"
#include "Cache.h"
#include "Cache_p.h"
@@ -41,7 +40,6 @@
#include "UserInfoWidget.h"
#include "UserSettingsPage.h"
#include "Utils.h"
-#include "WebRTCSession.h"
#include "ui/OverlayModal.h"
#include "ui/Theme.h"
@@ -130,12 +128,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
contentLayout_->addWidget(view_manager_->getWidget());
- activeCallBar_ = new ActiveCallBar(this);
- contentLayout_->addWidget(activeCallBar_);
- activeCallBar_->hide();
- connect(
- &callManager_, &CallManager::newCallParty, activeCallBar_, &ActiveCallBar::setCallParty);
-
// Splitter
splitter->addWidget(sideBar_);
splitter->addWidget(content_);
diff --git a/src/ChatPage.h b/src/ChatPage.h
index c37aa915..f0e12ab5 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -43,7 +43,6 @@
#include "notifications/Manager.h"
#include "popups/UserMentions.h"
-class ActiveCallBar;
class OverlayModal;
class QuickSwitcher;
class RoomList;
@@ -259,7 +258,6 @@ private:
SideBarActions *sidebarActions_;
TextInputWidget *text_input_;
- ActiveCallBar *activeCallBar_;
QTimer connectivityTimer_;
std::atomic_bool isConnected_;
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 63722372..b6ad8bbe 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -288,7 +288,7 @@ MainWindow::showChatPage()
void
MainWindow::closeEvent(QCloseEvent *event)
{
- if (WebRTCSession::instance().state() != WebRTCSession::State::DISCONNECTED) {
+ if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
if (QMessageBox::question(this, "nheko", "A call is in progress. Quit?") !=
QMessageBox::Yes) {
event->ignore();
@@ -431,7 +431,7 @@ MainWindow::openLogoutDialog()
{
auto dialog = new dialogs::Logout(this);
connect(dialog, &dialogs::Logout::loggingOut, this, [this]() {
- if (WebRTCSession::instance().state() != WebRTCSession::State::DISCONNECTED) {
+ if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
if (QMessageBox::question(
this, "nheko", "A call is in progress. Log out?") !=
QMessageBox::Yes) {
diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp
index d1be7fb4..22e8aafc 100644
--- a/src/TextInputWidget.cpp
+++ b/src/TextInputWidget.cpp
@@ -560,7 +560,7 @@ TextInputWidget::TextInputWidget(QWidget *parent)
#ifdef GSTREAMER_AVAILABLE
callBtn_ = new FlatButton(this);
- changeCallButtonState(WebRTCSession::State::DISCONNECTED);
+ changeCallButtonState(webrtc::State::DISCONNECTED);
connect(&WebRTCSession::instance(),
&WebRTCSession::stateChanged,
this,
@@ -778,11 +778,10 @@ TextInputWidget::paintEvent(QPaintEvent *)
}
void
-TextInputWidget::changeCallButtonState(WebRTCSession::State state)
+TextInputWidget::changeCallButtonState(webrtc::State state)
{
QIcon icon;
- if (state == WebRTCSession::State::ICEFAILED ||
- state == WebRTCSession::State::DISCONNECTED) {
+ if (state == webrtc::State::ICEFAILED || state == webrtc::State::DISCONNECTED) {
callBtn_->setToolTip(tr("Place a call"));
icon.addFile(":/icons/icons/ui/place-call.png");
} else {
diff --git a/src/TextInputWidget.h b/src/TextInputWidget.h
index 092e0ff2..7cc73e98 100644
--- a/src/TextInputWidget.h
+++ b/src/TextInputWidget.h
@@ -164,7 +164,7 @@ public slots:
void openFileSelection();
void hideUploadSpinner();
void focusLineEdit() { input_->setFocus(); }
- void changeCallButtonState(WebRTCSession::State);
+ void changeCallButtonState(webrtc::State);
private slots:
void addSelectedEmoji(const QString &emoji);
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index f1542ec5..7d81e663 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -511,7 +511,6 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
callsLabel->setFixedHeight(callsLabel->minimumHeight() + LayoutTopMargin);
callsLabel->setAlignment(Qt::AlignBottom);
callsLabel->setFont(font);
- useStunServer_ = new Toggle{this};
auto encryptionLabel_ = new QLabel{tr("ENCRYPTION"), this};
encryptionLabel_->setFixedHeight(encryptionLabel_->minimumHeight() + LayoutTopMargin);
diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp
index 30a27b60..1c11f750 100644
--- a/src/WebRTCSession.cpp
+++ b/src/WebRTCSession.cpp
@@ -1,3 +1,4 @@
+#include
#include
#include "Logging.h"
@@ -14,12 +15,17 @@ extern "C"
}
#endif
-Q_DECLARE_METATYPE(WebRTCSession::State)
+Q_DECLARE_METATYPE(webrtc::State)
+
+using webrtc::State;
WebRTCSession::WebRTCSession()
: QObject()
{
- qRegisterMetaType();
+ qRegisterMetaType();
+ qmlRegisterUncreatableMetaObject(
+ webrtc::staticMetaObject, "im.nheko", 1, 0, "WebRTCState", "Can't instantiate enum");
+
connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState);
init();
}
@@ -246,12 +252,10 @@ iceGatheringStateChanged(GstElement *webrtc,
nhlog::ui()->debug("WebRTC: GstWebRTCICEGatheringState -> Complete");
if (isoffering_) {
emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_);
- emit WebRTCSession::instance().stateChanged(
- WebRTCSession::State::OFFERSENT);
+ emit WebRTCSession::instance().stateChanged(State::OFFERSENT);
} else {
emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_);
- emit WebRTCSession::instance().stateChanged(
- WebRTCSession::State::ANSWERSENT);
+ emit WebRTCSession::instance().stateChanged(State::ANSWERSENT);
}
}
}
@@ -264,10 +268,10 @@ onICEGatheringCompletion(gpointer timerid)
*(guint *)(timerid) = 0;
if (isoffering_) {
emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_);
- emit WebRTCSession::instance().stateChanged(WebRTCSession::State::OFFERSENT);
+ emit WebRTCSession::instance().stateChanged(State::OFFERSENT);
} else {
emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_);
- emit WebRTCSession::instance().stateChanged(WebRTCSession::State::ANSWERSENT);
+ emit WebRTCSession::instance().stateChanged(State::ANSWERSENT);
}
return FALSE;
}
@@ -285,7 +289,7 @@ addLocalICECandidate(GstElement *webrtc G_GNUC_UNUSED,
localcandidates_.push_back({"audio", (uint16_t)mlineIndex, candidate});
return;
#else
- if (WebRTCSession::instance().state() >= WebRTCSession::State::OFFERSENT) {
+ if (WebRTCSession::instance().state() >= State::OFFERSENT) {
emit WebRTCSession::instance().newICECandidate(
{"audio", (uint16_t)mlineIndex, candidate});
return;
@@ -314,11 +318,11 @@ iceConnectionStateChanged(GstElement *webrtc,
switch (newState) {
case GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING:
nhlog::ui()->debug("WebRTC: GstWebRTCICEConnectionState -> Checking");
- emit WebRTCSession::instance().stateChanged(WebRTCSession::State::CONNECTING);
+ emit WebRTCSession::instance().stateChanged(State::CONNECTING);
break;
case GST_WEBRTC_ICE_CONNECTION_STATE_FAILED:
nhlog::ui()->error("WebRTC: GstWebRTCICEConnectionState -> Failed");
- emit WebRTCSession::instance().stateChanged(WebRTCSession::State::ICEFAILED);
+ emit WebRTCSession::instance().stateChanged(State::ICEFAILED);
break;
default:
break;
@@ -355,8 +359,7 @@ linkNewPad(GstElement *decodebin G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe
if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, queuepad)))
nhlog::ui()->error("WebRTC: unable to link new pad");
else {
- emit WebRTCSession::instance().stateChanged(
- WebRTCSession::State::CONNECTED);
+ emit WebRTCSession::instance().stateChanged(State::CONNECTED);
}
gst_object_unref(queuepad);
}
@@ -632,21 +635,30 @@ WebRTCSession::createPipeline(int opusPayloadType)
}
bool
-WebRTCSession::toggleMuteAudioSrc(bool &isMuted)
+WebRTCSession::isMicMuted() const
{
if (state_ < State::INITIATED)
return false;
GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
- if (!srclevel)
+ gboolean muted;
+ g_object_get(srclevel, "mute", &muted, nullptr);
+ gst_object_unref(srclevel);
+ return muted;
+}
+
+bool
+WebRTCSession::toggleMicMute()
+{
+ if (state_ < State::INITIATED)
return false;
+ GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
gboolean muted;
g_object_get(srclevel, "mute", &muted, nullptr);
g_object_set(srclevel, "mute", !muted, nullptr);
gst_object_unref(srclevel);
- isMuted = !muted;
- return true;
+ return !muted;
}
void
@@ -777,7 +789,13 @@ WebRTCSession::createPipeline(int)
}
bool
-WebRTCSession::toggleMuteAudioSrc(bool &)
+WebRTCSession::isMicMuted() const
+{
+ return false;
+}
+
+bool
+WebRTCSession::toggleMicMute()
{
return false;
}
diff --git a/src/WebRTCSession.h b/src/WebRTCSession.h
index 653ec2cf..83cabf5c 100644
--- a/src/WebRTCSession.h
+++ b/src/WebRTCSession.h
@@ -9,23 +9,30 @@
typedef struct _GstElement GstElement;
+namespace webrtc {
+Q_NAMESPACE
+
+enum class State
+{
+ DISCONNECTED,
+ ICEFAILED,
+ INITIATING,
+ INITIATED,
+ OFFERSENT,
+ ANSWERSENT,
+ CONNECTING,
+ CONNECTED
+
+};
+Q_ENUM_NS(State)
+
+}
+
class WebRTCSession : public QObject
{
Q_OBJECT
public:
- enum class State
- {
- DISCONNECTED,
- ICEFAILED,
- INITIATING,
- INITIATED,
- OFFERSENT,
- ANSWERSENT,
- CONNECTING,
- CONNECTED
- };
-
static WebRTCSession &instance()
{
static WebRTCSession instance;
@@ -33,14 +40,15 @@ public:
}
bool init(std::string *errorMessage = nullptr);
- State state() const { return state_; }
+ webrtc::State state() const { return state_; }
bool createOffer();
bool acceptOffer(const std::string &sdp);
bool acceptAnswer(const std::string &sdp);
void acceptICECandidates(const std::vector &);
- bool toggleMuteAudioSrc(bool &isMuted);
+ bool isMicMuted() const;
+ bool toggleMicMute();
void end();
void setStunServer(const std::string &stunServer) { stunServer_ = stunServer; }
@@ -55,16 +63,16 @@ signals:
void answerCreated(const std::string &sdp,
const std::vector &);
void newICECandidate(const mtx::events::msg::CallCandidates::Candidate &);
- void stateChanged(WebRTCSession::State); // explicit qualifier necessary for Qt
+ void stateChanged(webrtc::State);
private slots:
- void setState(State state) { state_ = state; }
+ void setState(webrtc::State state) { state_ = state; }
private:
WebRTCSession();
bool initialised_ = false;
- State state_ = State::DISCONNECTED;
+ webrtc::State state_ = webrtc::State::DISCONNECTED;
GstElement *pipe_ = nullptr;
GstElement *webrtc_ = nullptr;
unsigned int busWatchId_ = 0;
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index ed720056..18151173 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -8,7 +8,6 @@
#include
#include "BlurhashProvider.h"
-#include "CallManager.h"
#include "ChatPage.h"
#include "ColorImageProvider.h"
#include "DelegateChooser.h"
@@ -233,6 +232,12 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin
isInitialSync_ = true;
emit initialSyncChanged(true);
});
+ connect(&WebRTCSession::instance(),
+ &WebRTCSession::stateChanged,
+ this,
+ &TimelineViewManager::callStateChanged);
+ connect(
+ callManager_, &CallManager::newCallParty, this, &TimelineViewManager::callPartyChanged);
}
void
@@ -304,6 +309,13 @@ TimelineViewManager::escapeEmoji(QString str) const
return utils::replaceEmoji(str);
}
+void
+TimelineViewManager::toggleMicMute()
+{
+ WebRTCSession::instance().toggleMicMute();
+ emit micMuteChanged();
+}
+
void
TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) const
{
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index a8bd2e06..9a2a6467 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -10,16 +10,17 @@
#include
#include "Cache.h"
+#include "CallManager.h"
#include "DeviceVerificationFlow.h"
#include "Logging.h"
#include "TimelineModel.h"
#include "Utils.h"
+#include "WebRTCSession.h"
#include "emoji/EmojiModel.h"
#include "emoji/Provider.h"
class MxcImageProvider;
class BlurhashProvider;
-class CallManager;
class ColorImageProvider;
class UserSettings;
class ChatPage;
@@ -34,6 +35,10 @@ class TimelineViewManager : public QObject
bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
Q_PROPERTY(
bool isNarrowView MEMBER isNarrowView_ READ isNarrowView NOTIFY narrowViewChanged)
+ Q_PROPERTY(webrtc::State callState READ callState NOTIFY callStateChanged)
+ Q_PROPERTY(QString callPartyName READ callPartyName NOTIFY callPartyChanged)
+ Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY callPartyChanged)
+ Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged)
public:
TimelineViewManager(QSharedPointer userSettings,
@@ -49,6 +54,11 @@ public:
Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; }
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
bool isNarrowView() const { return isNarrowView_; }
+ webrtc::State callState() const { return WebRTCSession::instance().state(); }
+ QString callPartyName() const { return callManager_->callPartyName(); }
+ QString callPartyAvatarUrl() const { return callManager_->callPartyAvatarUrl(); }
+ bool isMicMuted() const { return WebRTCSession::instance().isMicMuted(); }
+ Q_INVOKABLE void toggleMicMute();
Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId) const;
Q_INVOKABLE QColor userColor(QString id, QColor background);
Q_INVOKABLE QString escapeEmoji(QString str) const;
@@ -78,6 +88,9 @@ signals:
void inviteUsers(QStringList users);
void showRoomList();
void narrowViewChanged();
+ void callStateChanged(webrtc::State);
+ void callPartyChanged();
+ void micMuteChanged();
public slots:
void updateReadReceipts(const QString &room_id, const std::vector &event_ids);