summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt4
-rw-r--r--io.github.NhekoReborn.Nheko.yaml4
-rw-r--r--resources/qml/RoomList.qml29
-rw-r--r--resources/qml/dialogs/CreateDirect.qml116
-rw-r--r--resources/qml/dialogs/CreateRoom.qml157
-rw-r--r--resources/res.qrc2
-rw-r--r--src/ChatPage.cpp10
-rw-r--r--src/ChatPage.h3
-rw-r--r--src/MainWindow.cpp15
-rw-r--r--src/MainWindow.h2
-rw-r--r--src/dialogs/CreateRoom.cpp159
-rw-r--r--src/dialogs/CreateRoom.h50
-rw-r--r--src/timeline/TimelineViewManager.cpp8
-rw-r--r--src/timeline/TimelineViewManager.h1
-rw-r--r--src/ui/NhekoGlobalObject.cpp31
-rw-r--r--src/ui/NhekoGlobalObject.h3
-rw-r--r--src/ui/UserProfile.cpp13
-rw-r--r--src/ui/UserProfile.h1
18 files changed, 366 insertions, 242 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7594d22b..5f822ac1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -309,7 +309,6 @@ configure_file(cmake/nheko.h config/nheko.h)
 #
 set(SRC_FILES
 	# Dialogs
-	src/dialogs/CreateRoom.cpp
 	src/dialogs/FallbackAuth.cpp
 	src/dialogs/ReCaptcha.cpp
 
@@ -404,7 +403,7 @@ if(USE_BUNDLED_MTXCLIENT)
 	FetchContent_Declare(
 		MatrixClient
 		GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
-		GIT_TAG        v0.7.0
+		GIT_TAG        817ae6e110829cfec39cd367a133a628ab923bb4
 		)
 	set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
 	set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
@@ -506,7 +505,6 @@ feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAG
 
 qt5_wrap_cpp(MOC_HEADERS
 	# Dialogs
-	src/dialogs/CreateRoom.h
 	src/dialogs/FallbackAuth.h
 	src/dialogs/ReCaptcha.h
 
diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml
index 9aefc04c..4832ea53 100644
--- a/io.github.NhekoReborn.Nheko.yaml
+++ b/io.github.NhekoReborn.Nheko.yaml
@@ -176,8 +176,8 @@ modules:
     buildsystem: cmake-ninja
     name: mtxclient
     sources:
-      - commit: 9eb9c152faf3461237d4b97ffe12503e367c8809
-        tag: v0.7.0
+      - commit: 817ae6e110829cfec39cd367a133a628ab923bb4
+        #tag: v0.7.0
         type: git
         url: https://github.com/Nheko-Reborn/mtxclient.git
   - config-opts:
diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml
index 702d77e5..078baede 100644
--- a/resources/qml/RoomList.qml
+++ b/resources/qml/RoomList.qml
@@ -26,6 +26,20 @@ Page {
 
     }
 
+    Component {
+        id: createRoomComponent
+
+        CreateRoom {
+        }
+    }
+
+    Component {
+        id: createDirectComponent
+
+        CreateDirect {
+        }
+    }
+
     ListView {
         id: roomlist
 
@@ -648,7 +662,20 @@ Page {
 
                         Platform.MenuItem {
                             text: qsTr("Create a new room")
-                            onTriggered: Nheko.openCreateRoomDialog()
+                            onTriggered: {
+                                var createRoom = createRoomComponent.createObject(timelineRoot);
+                                createRoom.show();
+                                timelineRoot.destroyOnClose(createRoom);
+                            }
+                        }
+
+                        Platform.MenuItem {
+                            text: qsTr("Start a direct chat")
+                            onTriggered: {
+                                var createDirect = createDirectComponent.createObject(timelineRoot);
+                                createDirect.show();
+                                timelineRoot.destroyOnClose(createDirect);
+                            }
                         }
 
                     }
diff --git a/resources/qml/dialogs/CreateDirect.qml b/resources/qml/dialogs/CreateDirect.qml
new file mode 100644
index 00000000..85768cad
--- /dev/null
+++ b/resources/qml/dialogs/CreateDirect.qml
@@ -0,0 +1,116 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import ".."
+import QtQuick 2.15
+import QtQuick.Window 2.13
+import QtQuick.Layouts 1.3
+import QtQuick.Controls 2.3
+import QtQml.Models 2.15
+import im.nheko 1.0
+
+ApplicationWindow {
+    id: createDirectRoot
+    title: qsTr("Create Direct Chat")
+    property var profile
+    property bool otherUserHasE2ee: profile? profile.deviceList.rowCount() > 0 : true
+    minimumHeight: layout.implicitHeight + footer.implicitHeight + Nheko.paddingLarge*2
+    minimumWidth: Math.max(footer.implicitWidth, layout.implicitWidth)
+    modality: Qt.NonModal
+    flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
+
+    onVisibilityChanged: {
+        userID.forceActiveFocus();
+    }
+
+    Shortcut {
+        sequence: StandardKey.Cancel
+        onActivated: createDirectRoot.close()
+    }
+
+    ColumnLayout {
+        id: layout
+        anchors.fill: parent
+        anchors.margins: Nheko.paddingLarge
+        spacing: userID.height/4
+
+        GridLayout {
+            Layout.fillWidth: true
+            rows: 2
+            columns: 2
+            rowSpacing: Nheko.paddingSmall
+            columnSpacing: Nheko.paddingMedium
+
+            Avatar {
+                Layout.rowSpan: 2
+                Layout.preferredWidth: Nheko.avatarSize
+                Layout.preferredHeight: Nheko.avatarSize
+                Layout.alignment: Qt.AlignLeft
+                userid: profile? profile.userid : ""
+                url: profile? profile.avatarUrl.replace("mxc://", "image://MxcImage/") : null
+                displayName: profile? profile.displayName : ""
+                enabled: false
+            }
+            Label {
+                Layout.fillWidth: true
+                text: profile? profile.displayName : ""
+                color: TimelineManager.userColor(userID.text, Nheko.colors.window)
+                font.pointSize: fontMetrics.font.pointSize
+            }
+
+            Label {
+                Layout.fillWidth: true
+                text: userID.text
+                color: Nheko.colors.buttonText
+                font.pointSize: fontMetrics.font.pointSize * 0.9
+            }
+        }
+
+        MatrixTextField {
+            id: userID
+            property bool isValidMxid: text.match("@.+?:.{3,}")
+            Layout.fillWidth: true
+            focus: true
+            label: qsTr("User to invite")
+            placeholderText: qsTr("@user:server.tld")
+            onTextChanged: {
+                if(isValidMxid) {
+                    profile = TimelineManager.getGlobalUserProfile(text);
+                } else
+                    profile = null;
+            }
+        }
+
+        RowLayout {
+            Layout.fillWidth: true
+            Label {
+                Layout.fillWidth: true
+                Layout.alignment: Qt.AlignLeft
+                text: qsTr("Encryption")
+                color: Nheko.colors.text
+            }
+            ToggleButton {
+                Layout.alignment: Qt.AlignRight
+                id: encryption
+                checked: otherUserHasE2ee
+            }
+        }
+
+        Item {Layout.fillHeight: true}
+    }
+    footer: DialogButtonBox {
+        standardButtons: DialogButtonBox.Cancel
+        Button {
+            text: "Start Direct Chat"
+            DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
+            enabled: userID.isValidMxid
+        }
+        onRejected: createDirectRoot.close();
+        onAccepted: {
+            profile.startChat(encryption.checked)
+            createDirectRoot.close()
+        }
+    }
+}
diff --git a/resources/qml/dialogs/CreateRoom.qml b/resources/qml/dialogs/CreateRoom.qml
new file mode 100644
index 00000000..5d224885
--- /dev/null
+++ b/resources/qml/dialogs/CreateRoom.qml
@@ -0,0 +1,157 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import ".."
+import QtQuick 2.15
+import QtQuick.Window 2.13
+import QtQuick.Layouts 1.3
+import QtQuick.Controls 2.3
+import im.nheko 1.0
+
+ApplicationWindow {
+    id: createRoomRoot
+    title: qsTr("Create Room")
+    minimumWidth: Math.max(rootLayout.implicitWidth+2*rootLayout.anchors.margins, footer.implicitWidth + Nheko.paddingLarge)
+    minimumHeight: rootLayout.implicitHeight+footer.implicitHeight+2*rootLayout.anchors.margins
+    modality: Qt.NonModal
+    flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
+
+    onVisibilityChanged: {
+        newRoomName.forceActiveFocus();
+    }
+
+    Shortcut {
+        sequence: StandardKey.Cancel
+        onActivated: createRoomRoot.close()
+    }
+    GridLayout {
+        id: rootLayout
+        anchors.fill: parent
+        anchors.margins: Nheko.paddingLarge
+        columns: 2
+        rowSpacing: Nheko.paddingMedium
+
+        MatrixTextField {
+            id: newRoomName
+            Layout.columnSpan: 2
+            Layout.fillWidth: true
+
+            focus: true
+            label: qsTr("Name")
+            placeholderText: qsTr("No name")
+        }
+        MatrixTextField {
+            id: newRoomTopic
+            Layout.columnSpan: 2
+            Layout.fillWidth: true
+
+            focus: true
+            label: qsTr("Topic")
+            placeholderText: qsTr("No topic")
+        }
+
+        Item {
+            Layout.preferredHeight: newRoomName.height / 2
+        }
+
+        RowLayout {
+            Layout.columnSpan: 2
+            Layout.fillWidth: true
+            Label {
+                Layout.preferredWidth: implicitWidth
+                text: qsTr("#")
+                color: Nheko.colors.text
+            }
+            MatrixTextField {
+                id: newRoomAlias
+                focus: true
+                placeholderText: qsTr("Alias")
+            }
+            Label {
+                Layout.preferredWidth: implicitWidth
+                property string userName: userInfoGrid.profile.userid
+                text: userName.substring(userName.indexOf(":"))
+                color: Nheko.colors.text
+            }
+        }
+        Label {
+            Layout.preferredWidth: implicitWidth
+            Layout.alignment: Qt.AlignLeft
+            text: qsTr("Public")
+            color: Nheko.colors.text
+            HoverHandler {
+                id: privateHover
+            }
+            ToolTip.visible: privateHover.hovered
+            ToolTip.text: qsTr("Public rooms can be joined by anyone, private rooms need explicit invites.")
+            ToolTip.delay: Nheko.tooltipDelay
+        }
+        ToggleButton {
+            Layout.alignment: Qt.AlignRight
+            Layout.preferredWidth: implicitWidth
+            id: isPublic
+            checked: false
+        }
+        Label {
+            Layout.preferredWidth: implicitWidth
+            Layout.alignment: Qt.AlignLeft
+            text: qsTr("Trusted")
+            color: Nheko.colors.text
+            HoverHandler {
+                id: trustedHover
+            }
+            ToolTip.visible: trustedHover.hovered
+            ToolTip.text: qsTr("All invitees are given the same power level as the creator")
+            ToolTip.delay: Nheko.tooltipDelay
+        }
+        ToggleButton {
+            Layout.alignment: Qt.AlignRight
+            Layout.preferredWidth: implicitWidth
+            id: isTrusted
+            checked: false
+            enabled: !isPublic.checked
+        }
+        Label {
+            Layout.preferredWidth: implicitWidth
+            Layout.alignment: Qt.AlignLeft
+            text: qsTr("Encryption")
+            color: Nheko.colors.text
+            HoverHandler {
+                id: encryptionHover
+            }
+            ToolTip.visible: encryptionHover.hovered
+            ToolTip.text: qsTr("Caution: Encryption cannot be disabled")
+            ToolTip.delay: Nheko.tooltipDelay
+        }
+        ToggleButton {
+            Layout.alignment: Qt.AlignRight
+            Layout.preferredWidth: implicitWidth
+            id: isEncrypted
+            checked: false
+        }
+
+        Item {Layout.fillHeight: true}
+    }
+    footer: DialogButtonBox {
+        standardButtons: DialogButtonBox.Cancel
+        Button {
+            text: qsTr("Create Room")
+            DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
+        }
+        onRejected: createRoomRoot.close();
+        onAccepted: {
+            var preset = 0;
+
+            if (isPublic.checked) {
+                preset = 1;
+            }
+            else {
+                preset = isTrusted.checked ? 2 : 0;
+            }
+            Nheko.createRoom(newRoomName.text, newRoomTopic.text, newRoomAlias.text, isEncrypted.checked, preset)
+            createRoomRoot.close();
+        }
+    }
+}
diff --git a/resources/res.qrc b/resources/res.qrc
index 3b762d20..3ce63f42 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -143,6 +143,8 @@
         <file>qml/device-verification/NewVerificationRequest.qml</file>
         <file>qml/device-verification/Success.qml</file>
         <file>qml/device-verification/Waiting.qml</file>
+        <file>qml/dialogs/CreateDirect.qml</file>
+        <file>qml/dialogs/CreateRoom.qml</file>
         <file>qml/dialogs/ImageOverlay.qml</file>
         <file>qml/dialogs/ImagePackEditorDialog.qml</file>
         <file>qml/dialogs/ImagePackSettingsDialog.qml</file>
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 3743eae0..a355a5b2 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -1187,7 +1187,7 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio
 }
 
 void
-ChatPage::startChat(QString userid)
+ChatPage::startChat(QString userid, std::optional<bool> encryptionEnabled)
 {
     auto joined_rooms = cache::joinedRooms();
     auto room_infos   = cache::getRoomInfo(joined_rooms);
@@ -1213,6 +1213,14 @@ ChatPage::startChat(QString userid)
     mtx::requests::CreateRoom req;
     req.preset     = mtx::requests::Preset::PrivateChat;
     req.visibility = mtx::common::RoomVisibility::Private;
+
+    if (encryptionEnabled.value_or(false)) {
+        mtx::events::StrippedEvent<mtx::events::state::Encryption> enc;
+        enc.type              = mtx::events::EventType::RoomEncryption;
+        enc.content.algorithm = mtx::crypto::MEGOLM_ALGO;
+        req.initial_state.emplace_back(std::move(enc));
+    }
+
     if (utils::localUser() != userid) {
         req.invite    = {userid.toStdString()};
         req.is_direct = true;
diff --git a/src/ChatPage.h b/src/ChatPage.h
index e4b9e4e8..f43a008d 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -74,12 +74,13 @@ public:
 
     // TODO(Nico): Get rid of this!
     QString currentRoom() const;
+    void startChat(QString userid, std::optional<bool> encryptionEnabled);
 
 public slots:
     bool handleMatrixUri(QString uri);
     bool handleMatrixUri(const QUrl &uri);
 
-    void startChat(QString userid);
+    void startChat(QString userid) { startChat(userid, std::nullopt); }
     void leaveRoom(const QString &room_id);
     void createRoom(const mtx::requests::CreateRoom &req);
     void joinRoom(const QString &room);
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index c4af7f0c..7235f93d 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -54,8 +54,6 @@
 #include "ui/UIA.h"
 #include "voip/WebRTCSession.h"
 
-#include "dialogs/CreateRoom.h"
-
 Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
 Q_DECLARE_METATYPE(std::vector<DeviceInfo>)
 Q_DECLARE_METATYPE(std::vector<mtx::responses::PublicRoomsChunk>)
@@ -404,19 +402,6 @@ MainWindow::hasActiveUser()
            settings->contains(prefix + "auth/user_id");
 }
 
-void
-MainWindow::openCreateRoomDialog(
-  std::function<void(const mtx::requests::CreateRoom &request)> callback)
-{
-    auto dialog = new dialogs::CreateRoom(nullptr);
-    connect(dialog,
-            &dialogs::CreateRoom::createRoom,
-            this,
-            [callback](const mtx::requests::CreateRoom &request) { callback(request); });
-
-    showDialog(dialog);
-}
-
 bool
 MainWindow::pageSupportsTray() const
 {
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 7bc94328..e8c6fafd 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -47,8 +47,6 @@ public:
     static MainWindow *instance() { return instance_; }
     void saveCurrentWindowSize();
 
-    void
-    openCreateRoomDialog(std::function<void(const mtx::requests::CreateRoom &request)> callback);
     void openJoinRoomDialog(std::function<void(const QString &room_id)> callback);
 
     MxcImageProvider *imageProvider() { return imgProvider; }
diff --git a/src/dialogs/CreateRoom.cpp b/src/dialogs/CreateRoom.cpp
deleted file mode 100644
index e828ae7c..00000000
--- a/src/dialogs/CreateRoom.cpp
+++ /dev/null
@@ -1,159 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-// SPDX-FileCopyrightText: 2022 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include <QComboBox>
-#include <QLabel>
-#include <QPushButton>
-#include <QVBoxLayout>
-
-#include "dialogs/CreateRoom.h"
-
-#include "Config.h"
-#include "ui/TextField.h"
-#include "ui/ToggleButton.h"
-
-using namespace dialogs;
-
-CreateRoom::CreateRoom(QWidget *parent)
-  : QFrame(parent)
-{
-    setAutoFillBackground(true);
-    setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
-    setWindowModality(Qt::WindowModal);
-    setAttribute(Qt::WA_DeleteOnClose, true);
-
-    QFont largeFont;
-    largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5);
-
-    setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
-    setMinimumHeight(conf::window::minHeight);
-    setMinimumWidth(conf::window::minModalWidth);
-
-    auto layout = new QVBoxLayout(this);
-    layout->setSpacing(conf::modals::WIDGET_SPACING);
-    layout->setContentsMargins(conf::modals::WIDGET_MARGIN,
-                               conf::modals::WIDGET_MARGIN,
-                               conf::modals::WIDGET_MARGIN,
-                               conf::modals::WIDGET_MARGIN);
-
-    buttonBox_  = new QDialogButtonBox(QDialogButtonBox::Cancel);
-    confirmBtn_ = new QPushButton(tr("Create room"), this);
-    confirmBtn_->setDefault(true);
-    buttonBox_->addButton(confirmBtn_, QDialogButtonBox::AcceptRole);
-
-    QFont font;
-    font.setPointSizeF(font.pointSizeF() * 1.3);
-
-    nameInput_ = new TextField(this);
-    nameInput_->setLabel(tr("Name"));
-
-    topicInput_ = new TextField(this);
-    topicInput_->setLabel(tr("Topic"));
-
-    aliasInput_ = new TextField(this);
-    aliasInput_->setLabel(tr("Alias"));
-
-    auto visibilityLayout = new QHBoxLayout;
-    visibilityLayout->setContentsMargins(0, 10, 0, 10);
-
-    auto presetLayout = new QHBoxLayout;
-    presetLayout->setContentsMargins(0, 10, 0, 10);
-
-    auto visibilityLabel = new QLabel(tr("Room Visibility"), this);
-    visibilityCombo_     = new QComboBox(this);
-    visibilityCombo_->addItem(tr("Private"));
-    visibilityCombo_->addItem(tr("Public"));
-
-    visibilityLayout->addWidget(visibilityLabel);
-    visibilityLayout->addWidget(visibilityCombo_, 0, Qt::AlignBottom | Qt::AlignRight);
-
-    auto presetLabel = new QLabel(tr("Room Preset"), this);
-    presetCombo_     = new QComboBox(this);
-    presetCombo_->addItem(tr("Private Chat"));
-    presetCombo_->addItem(tr("Public Chat"));
-    presetCombo_->addItem(tr("Trusted Private Chat"));
-
-    presetLayout->addWidget(presetLabel);
-    presetLayout->addWidget(presetCombo_, 0, Qt::AlignBottom | Qt::AlignRight);
-
-    auto directLabel_ = new QLabel(tr("Direct Chat"), this);
-    directToggle_     = new Toggle(this);
-    directToggle_->setActiveColor(QColor(0x38, 0xA3, 0xD8));
-    directToggle_->setInactiveColor(QColor("gray"));
-    directToggle_->setState(false);
-
-    auto directLayout = new QHBoxLayout;
-    directLayout->setContentsMargins(0, 10, 0, 10);
-    directLayout->addWidget(directLabel_);
-    directLayout->addWidget(directToggle_, 0, Qt::AlignBottom | Qt::AlignRight);
-
-    layout->addWidget(nameInput_);
-    layout->addWidget(topicInput_);
-    layout->addWidget(aliasInput_);
-    layout->addLayout(visibilityLayout);
-    layout->addLayout(presetLayout);
-    layout->addLayout(directLayout);
-    layout->addWidget(buttonBox_);
-
-    connect(buttonBox_, &QDialogButtonBox::accepted, this, [this]() {
-        request_.name            = nameInput_->text().toStdString();
-        request_.topic           = topicInput_->text().toStdString();
-        request_.room_alias_name = aliasInput_->text().toStdString();
-
-        emit createRoom(request_);
-
-        clearFields();
-        emit close();
-    });
-
-    connect(buttonBox_, &QDialogButtonBox::rejected, this, [this]() {
-        clearFields();
-        emit close();
-    });
-
-    connect(visibilityCombo_,
-            static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
-            this,
-            [this](int idx) {
-                if (idx == 0) {
-                    request_.visibility = mtx::common::RoomVisibility::Private;
-                } else {
-                    request_.visibility = mtx::common::RoomVisibility::Public;
-                }
-            });
-
-    connect(presetCombo_,
-            static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
-            this,
-            [this](int idx) {
-                if (idx == 0) {
-                    request_.preset = mtx::requests::Preset::PrivateChat;
-                } else if (idx == 1) {
-                    request_.preset = mtx::requests::Preset::PublicChat;
-                } else {
-                    request_.preset = mtx::requests::Preset::TrustedPrivateChat;
-                }
-            });
-
-    connect(directToggle_, &Toggle::toggled, this, [this](bool isEnabled) {
-        request_.is_direct = isEnabled;
-    });
-}
-
-void
-CreateRoom::clearFields()
-{
-    nameInput_->clear();
-    topicInput_->clear();
-    aliasInput_->clear();
-}
-
-void
-CreateRoom::showEvent(QShowEvent *event)
-{
-    nameInput_->setFocus();
-
-    QFrame::showEvent(event);
-}
diff --git a/src/dialogs/CreateRoom.h b/src/dialogs/CreateRoom.h
deleted file mode 100644
index c395941d..00000000
--- a/src/dialogs/CreateRoom.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-// SPDX-FileCopyrightText: 2022 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#pragma once
-
-#include <QDialogButtonBox>
-#include <QFrame>
-
-#include <mtx/requests.hpp>
-
-class QPushButton;
-class TextField;
-class QComboBox;
-class Toggle;
-
-namespace dialogs {
-
-class CreateRoom : public QFrame
-{
-    Q_OBJECT
-public:
-    CreateRoom(QWidget *parent = nullptr);
-
-signals:
-    void createRoom(const mtx::requests::CreateRoom &request);
-
-protected:
-    void showEvent(QShowEvent *event) override;
-
-private:
-    void clearFields();
-
-    QComboBox *visibilityCombo_;
-    QComboBox *presetCombo_;
-
-    Toggle *directToggle_;
-
-    QPushButton *confirmBtn_;
-    QDialogButtonBox *buttonBox_;
-
-    TextField *nameInput_;
-    TextField *topicInput_;
-    TextField *aliasInput_;
-
-    mtx::requests::CreateRoom request_;
-};
-
-} // dialogs
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index a18f3ee2..3bccd8f3 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -199,6 +199,14 @@ TimelineViewManager::openGlobalUserProfile(QString userId)
     emit openProfile(profile);
 }
 
+UserProfile *
+TimelineViewManager::getGlobalUserProfile(QString userId)
+{
+    UserProfile *profile = new UserProfile{QString{}, userId, this};
+    QQmlEngine::setObjectOwnership(profile, QQmlEngine::JavaScriptOwnership);
+    return (profile);
+}
+
 void
 TimelineViewManager::setVideoCallItem()
 {
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 393b1479..07ebfe79 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -67,6 +67,7 @@ public:
     Q_INVOKABLE void openRoomSettings(QString room_id);
     Q_INVOKABLE void openInviteUsers(QString roomId);
     Q_INVOKABLE void openGlobalUserProfile(QString userId);
+    Q_INVOKABLE UserProfile *getGlobalUserProfile(QString userId);
 
     Q_INVOKABLE void focusMessageInput();
 
diff --git a/src/ui/NhekoGlobalObject.cpp b/src/ui/NhekoGlobalObject.cpp
index 3abcdf08..2e1aadf0 100644
--- a/src/ui/NhekoGlobalObject.cpp
+++ b/src/ui/NhekoGlobalObject.cpp
@@ -13,7 +13,6 @@
 #include "Cache_p.h"
 #include "ChatPage.h"
 #include "Logging.h"
-#include "MainWindow.h"
 #include "UserSettingsPage.h"
 #include "Utils.h"
 #include "voip/WebRTCSession.h"
@@ -129,8 +128,32 @@ Nheko::logout() const
 }
 
 void
-Nheko::openCreateRoomDialog() const
+Nheko::createRoom(QString name, QString topic, QString aliasLocalpart, bool isEncrypted, int preset)
 {
-    MainWindow::instance()->openCreateRoomDialog(
-      [](const mtx::requests::CreateRoom &req) { ChatPage::instance()->createRoom(req); });
+    mtx::requests::CreateRoom req;
+
+    switch (preset) {
+    case 1:
+        req.preset = mtx::requests::Preset::PublicChat;
+        break;
+    case 2:
+        req.preset = mtx::requests::Preset::TrustedPrivateChat;
+        break;
+    case 0:
+    default:
+        req.preset = mtx::requests::Preset::PrivateChat;
+    }
+
+    req.name            = name.toStdString();
+    req.topic           = topic.toStdString();
+    req.room_alias_name = aliasLocalpart.toStdString();
+
+    if (isEncrypted) {
+        mtx::events::StrippedEvent<mtx::events::state::Encryption> enc;
+        enc.type              = mtx::events::EventType::RoomEncryption;
+        enc.content.algorithm = mtx::crypto::MEGOLM_ALGO;
+        req.initial_state.emplace_back(std::move(enc));
+    }
+
+    emit ChatPage::instance()->createRoom(req);
 }
diff --git a/src/ui/NhekoGlobalObject.h b/src/ui/NhekoGlobalObject.h
index 24493873..1139cf31 100644
--- a/src/ui/NhekoGlobalObject.h
+++ b/src/ui/NhekoGlobalObject.h
@@ -52,7 +52,8 @@ public:
     Q_INVOKABLE void setStatusMessage(QString msg) const;
     Q_INVOKABLE void showUserSettingsPage() const;
     Q_INVOKABLE void logout() const;
-    Q_INVOKABLE void openCreateRoomDialog() const;
+    Q_INVOKABLE void
+    createRoom(QString name, QString topic, QString aliasLocalpart, bool isEncrypted, int preset);
 
 public slots:
     void updateUserProfile();
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 898b56fd..db50b050 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -53,6 +53,9 @@ UserProfile::UserProfile(QString roomid,
 
           emit verificationStatiChanged();
       });
+    connect(this, &UserProfile::devicesChanged, [this]() {
+        nhlog::net()->critical("Device list: {}", deviceList_.rowCount());
+    });
     fetchDeviceList(this->userid_);
 }
 
@@ -187,7 +190,6 @@ UserProfile::fetchDeviceList(const QString &userID)
               nhlog::net()->warn("failed to query device keys: {},{}",
                                  mtx::errors::to_string(err->matrix_error.errcode),
                                  static_cast<int>(err->status_code));
-              return;
           }
 
           // Ensure local key cache is up to date
@@ -201,7 +203,6 @@ UserProfile::fetchDeviceList(const QString &userID)
                     nhlog::net()->warn("failed to query device keys: {},{}",
                                        mtx::errors::to_string(err->matrix_error.errcode),
                                        static_cast<int>(err->status_code));
-                    return;
                 }
 
                 emit verificationStatiChanged();
@@ -313,9 +314,15 @@ UserProfile::kickUser()
 }
 
 void
+UserProfile::startChat(bool encryption)
+{
+    ChatPage::instance()->startChat(this->userid_, encryption);
+}
+
+void
 UserProfile::startChat()
 {
-    ChatPage::instance()->startChat(this->userid_);
+    ChatPage::instance()->startChat(this->userid_, std::nullopt);
 }
 
 void
diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h
index 0f03e537..4652a72e 100644
--- a/src/ui/UserProfile.h
+++ b/src/ui/UserProfile.h
@@ -143,6 +143,7 @@ public:
     // Q_INVOKABLE void ignoreUser();
     Q_INVOKABLE void kickUser();
     Q_INVOKABLE void startChat();
+    Q_INVOKABLE void startChat(bool encryptionEnabled);
     Q_INVOKABLE void changeUsername(QString username);
     Q_INVOKABLE void changeDeviceName(QString deviceID, QString deviceName);
     Q_INVOKABLE void changeAvatar();