diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index e596d8e2..fd08f0ca 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -52,6 +52,13 @@ Page {
}
+ Component {
+ id: mobileCallInviteDialog
+
+ CallInvite {
+ }
+ }
+
Menu {
id: messageContextMenu
@@ -151,6 +158,16 @@ Page {
}
}
+ Connections {
+ target: CallManager
+ onNewInviteState: {
+ if (CallManager.haveCallInvite && Settings.mobileMode) {
+ var dialog = mobileCallInviteDialog.createObject(msgView);
+ dialog.open();
+ }
+ }
+ }
+
Label {
visible: !TimelineManager.timeline && !TimelineManager.isInitialSync
anchors.centerIn: parent
@@ -184,6 +201,7 @@ Page {
}
Rectangle {
+ id: msgView
Layout.fillWidth: true
Layout.fillHeight: true
color: colors.base
diff --git a/resources/qml/voip/CallInvite.qml b/resources/qml/voip/CallInvite.qml
new file mode 100644
index 00000000..1e9a31c2
--- /dev/null
+++ b/resources/qml/voip/CallInvite.qml
@@ -0,0 +1,182 @@
+import "../"
+import QtQuick 2.9
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.2
+import im.nheko 1.0
+
+Popup {
+ closePolicy: Popup.NoAutoClose
+ width: parent.width
+ height: parent.height
+ palette: colors
+ background: Rectangle {
+ color: colors.window
+ border.color: colors.windowText
+ }
+
+ Component {
+ id: deviceError
+ DeviceError {
+ }
+ }
+
+ Connections {
+ target: CallManager
+ onNewInviteState: {
+ if (!CallManager.haveCallInvite) {
+ close();
+ }
+ }
+ }
+
+ ColumnLayout {
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Label {
+ Layout.alignment: Qt.AlignCenter
+ Layout.topMargin: msgView.height / 25
+ text: CallManager.callParty
+ font.pointSize: fontMetrics.font.pointSize * 2
+ color: colors.windowText
+ }
+
+ Avatar {
+ Layout.alignment: Qt.AlignCenter
+ width: msgView.height / 5
+ height: msgView.height / 5
+ url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
+ displayName: CallManager.callParty
+ }
+
+ ColumnLayout {
+ Layout.alignment: Qt.AlignCenter
+ Layout.bottomMargin: msgView.height / 25
+
+ Image {
+ property string image: CallManager.isVideo ? ":/icons/icons/ui/video-call.png" : ":/icons/icons/ui/place-call.png"
+ Layout.alignment: Qt.AlignCenter
+ Layout.preferredWidth: msgView.height / 10
+ Layout.preferredHeight: msgView.height / 10
+ source: "image://colorimage/" + image + "?" + colors.windowText
+ }
+
+ Label {
+ Layout.alignment: Qt.AlignCenter
+ text: CallManager.isVideo ? qsTr("Video Call") : qsTr("Voice Call")
+ font.pointSize: fontMetrics.font.pointSize * 2
+ color: colors.windowText
+ }
+ }
+
+ ColumnLayout {
+ id: deviceCombos
+
+ property int imageSize: msgView.height / 20
+ Layout.alignment: Qt.AlignCenter
+ Layout.bottomMargin: msgView.height / 25
+
+ RowLayout {
+
+ Layout.alignment: Qt.AlignCenter
+
+ Image {
+ Layout.preferredWidth: deviceCombos.imageSize
+ Layout.preferredHeight: deviceCombos.imageSize
+ source: "image://colorimage/:/icons/icons/ui/microphone-unmute.png?" + colors.windowText
+ }
+
+ ComboBox {
+ id: micCombo
+ Layout.fillWidth: true
+ model: CallManager.mics
+ }
+ }
+
+ RowLayout {
+
+ visible: CallManager.isVideo && CallManager.cameras.length > 0
+ Layout.alignment: Qt.AlignCenter
+
+ Image {
+ Layout.preferredWidth: deviceCombos.imageSize
+ Layout.preferredHeight: deviceCombos.imageSize
+ source: "image://colorimage/:/icons/icons/ui/video-call.png?" + colors.windowText
+ }
+
+ ComboBox {
+ id: cameraCombo
+ Layout.fillWidth: true
+ model: CallManager.cameras
+ }
+ }
+ }
+
+ RowLayout {
+ id: buttonLayout
+
+ property int buttonSize: msgView.height / 8
+ Layout.alignment: Qt.AlignCenter
+ spacing: msgView.height / 6
+
+ function validateMic() {
+ if (CallManager.mics.length == 0) {
+ var dialog = deviceError.createObject(timelineRoot, {
+ "errorString": qsTr("No microphone found."),
+ "image": ":/icons/icons/ui/place-call.png"
+ });
+ dialog.open();
+ return false;
+ }
+ return true;
+ }
+
+ RoundButton {
+ implicitWidth: buttonLayout.buttonSize
+ implicitHeight: buttonLayout.buttonSize
+
+ background: Rectangle {
+ radius: buttonLayout.buttonSize / 2
+ color: "#ff0000"
+ }
+
+ contentItem : Image {
+ source: "image://colorimage/:/icons/icons/ui/end-call.png?#ffffff"
+ }
+
+ onClicked: {
+ CallManager.hangUp();
+ close();
+ }
+ }
+
+ RoundButton {
+ id: acceptButton
+
+ property string image: CallManager.isVideo ? ":/icons/icons/ui/video-call.png" : ":/icons/icons/ui/place-call.png"
+ implicitWidth: buttonLayout.buttonSize
+ implicitHeight: buttonLayout.buttonSize
+
+ background: Rectangle {
+ radius: buttonLayout.buttonSize / 2
+ color: "#00ff00"
+ }
+
+ contentItem : Image {
+ source: "image://colorimage/" + acceptButton.image + "?#ffffff"
+ }
+
+ onClicked: {
+ if (buttonLayout.validateMic()) {
+ Settings.microphone = micCombo.currentText;
+ if (cameraCombo.visible)
+ Settings.camera = cameraCombo.currentText;
+ CallManager.acceptInvite();
+ close();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml
index 5c4b8f32..cc3f9005 100644
--- a/resources/qml/voip/CallInviteBar.qml
+++ b/resources/qml/voip/CallInviteBar.qml
@@ -5,7 +5,7 @@ import QtQuick.Layouts 1.2
import im.nheko 1.0
Rectangle {
- visible: CallManager.haveCallInvite
+ visible: CallManager.haveCallInvite && !Settings.mobileMode
color: "#2ECC71"
implicitHeight: visible ? rowLayout.height + 8 : 0
diff --git a/resources/res.qrc b/resources/res.qrc
index 71e8b997..e3998bd1 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -160,6 +160,7 @@
qml/ui/Ripple.qml
qml/voip/ActiveCallBar.qml
qml/voip/CallDevices.qml
+ qml/voip/CallInvite.qml
qml/voip/CallInviteBar.qml
qml/voip/DeviceError.qml
qml/voip/PlaceCall.qml
diff --git a/src/CallManager.cpp b/src/CallManager.cpp
index f725d49f..0841a079 100644
--- a/src/CallManager.cpp
+++ b/src/CallManager.cpp
@@ -45,8 +45,9 @@ CallManager::CallManager(QObject *parent)
nhlog::ui()->debug("WebRTC: call id: {} - sending offer", callid_);
emit newMessage(roomid_, CallInvite{callid_, sdp, 0, timeoutms_});
emit newMessage(roomid_, CallCandidates{callid_, candidates, 0});
- QTimer::singleShot(timeoutms_, this, [this]() {
- if (session_.state() == webrtc::State::OFFERSENT) {
+ std::string callid(callid_);
+ QTimer::singleShot(timeoutms_, this, [this, callid]() {
+ if (session_.state() == webrtc::State::OFFERSENT && callid == callid_) {
hangUp(CallHangUp::Reason::InviteTimeOut);
emit ChatPage::instance()->showNotification(
"The remote side failed to pick up.");