diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index e596d8e29007cb110b5acd2f37df31d71fa6ff97..fd08f0ca8d51d1182a33f86d296c91ccddc21f22 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 0000000000000000000000000000000000000000..1e9a31c286e7857710e4251d60dda1b3dccb28cb
--- /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 5c4b8f32bcba3e97ab17c7d8d220a6ae7a2accbc..cc3f9005644171b4678506b4a05b3cb43d74217c 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 71e8b9971695f778b3a9f0895c0592516b9f7341..e3998bd1f35b079ca9b8e1a82feb89c9c1817ea0 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -160,6 +160,7 @@
         <file>qml/ui/Ripple.qml</file>
         <file>qml/voip/ActiveCallBar.qml</file>
         <file>qml/voip/CallDevices.qml</file>
+        <file>qml/voip/CallInvite.qml</file>
         <file>qml/voip/CallInviteBar.qml</file>
         <file>qml/voip/DeviceError.qml</file>
         <file>qml/voip/PlaceCall.qml</file>
diff --git a/src/CallManager.cpp b/src/CallManager.cpp
index f725d49f8e240fc6d5196e2e2e268cd86ba73cad..0841a079d9d70c6e46052e9f047fc7642ad30090 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.");