summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--resources/qml/PrivacyScreen.qml7
-rw-r--r--resources/qml/Root.qml23
-rw-r--r--resources/qml/components/FlatButton.qml4
-rw-r--r--resources/qml/pages/WelcomePage.qml76
-rw-r--r--resources/res.qrc1
-rw-r--r--src/Cache.cpp2
-rw-r--r--src/ChatPage.cpp40
-rw-r--r--src/ChatPage.h4
-rw-r--r--src/MainWindow.cpp347
-rw-r--r--src/MainWindow.h30
-rw-r--r--src/TrayIcon.cpp7
-rw-r--r--src/TrayIcon.h2
-rw-r--r--src/UserSettingsPage.cpp24
-rw-r--r--src/main.cpp9
-rw-r--r--src/timeline/InputBar.cpp6
-rw-r--r--src/timeline/RoomlistModel.cpp5
-rw-r--r--src/timeline/TimelineModel.cpp4
-rw-r--r--src/timeline/TimelineViewManager.cpp204
-rw-r--r--src/timeline/TimelineViewManager.h19
-rw-r--r--src/ui/NhekoGlobalObject.cpp2
-rw-r--r--src/ui/UIA.cpp7
21 files changed, 426 insertions, 397 deletions
diff --git a/resources/qml/PrivacyScreen.qml b/resources/qml/PrivacyScreen.qml
index e6286bc6..6ad2a557 100644
--- a/resources/qml/PrivacyScreen.qml
+++ b/resources/qml/PrivacyScreen.qml
@@ -5,6 +5,7 @@
 
 import QtGraphicalEffects 1.0
 import QtQuick 2.12
+import QtQuick.Window 2.2
 import im.nheko 1.0
 
 Item {
@@ -15,7 +16,7 @@ Item {
 
     Connections {
         function onFocusChanged() {
-            if (TimelineManager.isWindowFocused) {
+            if (MainWindow.active) {
                 screenSaverTimer.stop();
                 screenSaver.state = "Invisible";
             } else {
@@ -25,14 +26,14 @@ Item {
             }
         }
 
-        target: TimelineManager
+        target: MainWindow
     }
 
     Timer {
         id: screenSaverTimer
 
         interval: screenTimeout * 1000
-        running: true
+        running: !MainWindow.active
         onTriggered: {
             screenSaver.state = "Visible";
         }
diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml
index e4b164e4..004169e1 100644
--- a/resources/qml/Root.qml
+++ b/resources/qml/Root.qml
@@ -17,10 +17,12 @@ import QtQuick.Window 2.15
 import im.nheko 1.0
 import im.nheko.EmojiModel 1.0
 
-Page {
+Pane {
     id: timelineRoot
 
     palette: Nheko.colors
+    background: null
+    padding: 0
 
     FontMetrics {
         id: fontMetrics
@@ -157,7 +159,6 @@ Page {
         sequence: "Ctrl+K"
         onActivated: {
             var quickSwitch = quickSwitcherComponent.createObject(timelineRoot);
-            TimelineManager.focusTimeline();
             quickSwitch.open();
         }
     }
@@ -165,7 +166,6 @@ Page {
     Shortcut {
         // Add alternative shortcut, because sometimes Alt+A is stolen by the TextEdit
         sequences: ["Alt+A", "Ctrl+Shift+A"]
-        context: Qt.ApplicationShortcut
         onActivated: Rooms.nextRoomWithActivity()
     }
 
@@ -366,9 +366,24 @@ Page {
         id: mainWindow
 
         anchors.fill: parent
-        initialItem: ChatPage {
+        initialItem: WelcomePage {
             //anchors.fill: parent
         }
     }
 
+    Component {
+        id: chatPage
+
+        ChatPage {
+        }
+    }
+
+    Connections {
+        function onSwitchToChatPage() {
+            console.log("AAAA");
+            mainWindow.replace(chatPage);
+        }
+        target: MainWindow
+    }
+
 }
diff --git a/resources/qml/components/FlatButton.qml b/resources/qml/components/FlatButton.qml
index 8ca3f104..72184d28 100644
--- a/resources/qml/components/FlatButton.qml
+++ b/resources/qml/components/FlatButton.qml
@@ -12,7 +12,7 @@ import im.nheko 1.0
 Button {
     id: control
 
-    implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.5)
+    implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.70)
     implicitWidth: Math.ceil(control.contentItem.implicitWidth + control.contentItem.implicitHeight)
     hoverEnabled: true
 
@@ -42,7 +42,7 @@ Button {
     background: Rectangle {
         //height: control.contentItem.implicitHeight * 2
         //width: control.contentItem.implicitWidth * 2
-        radius: height / 6
+        radius: height / 8
         color: Qt.lighter(Nheko.colors.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1))
     }
 
diff --git a/resources/qml/pages/WelcomePage.qml b/resources/qml/pages/WelcomePage.qml
new file mode 100644
index 00000000..d95b6104
--- /dev/null
+++ b/resources/qml/pages/WelcomePage.qml
@@ -0,0 +1,76 @@
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.2
+import QtQuick.Window 2.15
+import im.nheko 1.0
+import "../components/"
+
+ColumnLayout {
+    FontMetrics {
+        id: fontMetrics
+    }
+
+    Shortcut {
+        sequence: StandardKey.Quit
+        onActivated: Qt.quit()
+    }
+
+    Item {
+        Layout.fillHeight: true
+    }
+
+    Image {
+        Layout.alignment: Qt.AlignHCenter
+        source: "qrc:/logos/splash.png"
+        height: 256
+        width: 256
+    }
+
+    Label {
+        Layout.margins: Nheko.paddingLarge
+        Layout.bottomMargin: 0
+        Layout.alignment: Qt.AlignHCenter
+        Layout.fillWidth: true
+        text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.")
+        color: Nheko.colors.text
+        font.pointSize: fontMetrics.font.pointSize*2
+        wrapMode: Text.Wrap
+        horizontalAlignment: Text.AlignHCenter
+    }
+    Label {
+        Layout.margins: Nheko.paddingLarge
+        Layout.alignment: Qt.AlignHCenter
+        Layout.fillWidth: true
+        text: qsTr("Enjoy your stay!")
+        color: Nheko.colors.text
+        font.pointSize: fontMetrics.font.pointSize*1.5
+        wrapMode: Text.Wrap
+        horizontalAlignment: Text.AlignHCenter
+    }
+
+    RowLayout {
+    Item {
+        Layout.fillWidth: true
+    }
+    FlatButton {
+        Layout.margins: Nheko.paddingLarge
+        Layout.alignment: Qt.AlignHCenter
+        text: qsTr("REGISTER")
+        onClicked: {
+        }
+    }
+    FlatButton {
+        Layout.margins: Nheko.paddingLarge
+        Layout.alignment: Qt.AlignHCenter
+        text: qsTr("LOGIN")
+        onClicked: {
+        }
+    }
+    Item {
+        Layout.fillWidth: true
+    }
+}
+    Item {
+        Layout.fillHeight: true
+    }
+}
diff --git a/resources/res.qrc b/resources/res.qrc
index a2ee393f..0222619b 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -110,6 +110,7 @@
         <file>qml/TypingIndicator.qml</file>
         <file>qml/NotificationWarning.qml</file>
         <file>qml/pages/UserSettingsPage.qml</file>
+        <file>qml/pages/WelcomePage.qml</file>
         <file>qml/components/AdaptiveLayout.qml</file>
         <file>qml/components/AdaptiveLayoutElement.qml</file>
         <file>qml/components/AvatarListTile.qml</file>
diff --git a/src/Cache.cpp b/src/Cache.cpp
index d1723d98..c15d2f4b 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -325,7 +325,7 @@ static void
 fatalSecretError()
 {
     QMessageBox::critical(
-      ChatPage::instance(),
+      nullptr,
       QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"),
       QCoreApplication::translate(
         "SecretStorage",
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index bfaa6389..fc90e6c7 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -44,8 +44,8 @@ Q_DECLARE_METATYPE(mtx::presence::PresenceState)
 Q_DECLARE_METATYPE(mtx::secret_storage::AesHmacSha2KeyDescription)
 Q_DECLARE_METATYPE(SecretsToDecrypt)
 
-ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
-  : QWidget(parent)
+ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent)
+  : QObject(parent)
   , isConnected_(true)
   , userSettings_{userSettings}
   , notificationsManager(this)
@@ -61,14 +61,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
     qRegisterMetaType<mtx::secret_storage::AesHmacSha2KeyDescription>();
     qRegisterMetaType<SecretsToDecrypt>();
 
-    topLayout_ = new QHBoxLayout(this);
-    topLayout_->setSpacing(0);
-    topLayout_->setContentsMargins(0, 0, 0, 0);
-
     view_manager_ = new TimelineViewManager(callManager_, this);
 
-    topLayout_->addWidget(view_manager_->getWidget());
-
     connect(this,
             &ChatPage::downloadedSecrets,
             this,
@@ -154,7 +148,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
             [this](const QString &roomid, const QString &eventid) {
                 Q_UNUSED(eventid)
                 view_manager_->rooms()->setCurrentRoom(roomid);
-                activateWindow();
+                MainWindow::instance()->requestActivate();
             });
     connect(&notificationsManager,
             &NotificationsManager::sendNotificationReply,
@@ -162,15 +156,13 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
             [this](const QString &roomid, const QString &eventid, const QString &body) {
                 view_manager_->rooms()->setCurrentRoom(roomid);
                 view_manager_->queueReply(roomid, eventid, body);
-                activateWindow();
+                MainWindow::instance()->requestActivate();
             });
 
     connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() {
         // ensure the qml context is shutdown before we destroy all other singletons
         // Otherwise Qml will try to access the room list or settings, after they have been
         // destroyed
-        topLayout_->removeWidget(view_manager_->getWidget());
-        delete view_manager_->getWidget();
     });
 
     connect(
@@ -201,7 +193,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
         // TODO: Replace this once we have proper pushrules support. This is a horrible hack
         if (prevNotificationCount < notificationCount) {
             if (userSettings_->hasAlertOnNotification())
-                QApplication::alert(this);
+                MainWindow::instance()->alert(0);
         }
         prevNotificationCount = notificationCount;
 
@@ -331,7 +323,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
                     } else if (cacheVersion == cache::CacheVersion::Older) {
                         if (!cache::runMigrations()) {
                             QMessageBox::critical(
-                              this,
+                              nullptr,
                               tr("Cache migration failed!"),
                               tr("Migrating the cache to the current version failed. "
                                  "This can have different reasons. Please open an "
@@ -344,7 +336,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
                         return;
                     } else if (cacheVersion == cache::CacheVersion::Newer) {
                         QMessageBox::critical(
-                          this,
+                          nullptr,
                           tr("Incompatible cache version"),
                           tr("The cache on your disk is newer than this version of Nheko "
                              "supports. Please update Nheko or clear your cache."));
@@ -690,7 +682,7 @@ ChatPage::joinRoomVia(const std::string &room_id,
     if (promptForConfirmation &&
         QMessageBox::Yes !=
           QMessageBox::question(
-            this,
+            nullptr,
             tr("Confirm join"),
             tr("Do you really want to join %1?").arg(QString::fromStdString(room_id))))
         return;
@@ -776,7 +768,7 @@ ChatPage::inviteUser(QString userid, QString reason)
 {
     auto room = currentRoom();
 
-    if (QMessageBox::question(this,
+    if (QMessageBox::question(nullptr,
                               tr("Confirm invite"),
                               tr("Do you really want to invite %1 (%2)?")
                                 .arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes)
@@ -800,7 +792,7 @@ ChatPage::kickUser(QString userid, QString reason)
 {
     auto room = currentRoom();
 
-    if (QMessageBox::question(this,
+    if (QMessageBox::question(nullptr,
                               tr("Confirm kick"),
                               tr("Do you really want to kick %1 (%2)?")
                                 .arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes)
@@ -825,7 +817,7 @@ ChatPage::banUser(QString userid, QString reason)
     auto room = currentRoom();
 
     if (QMessageBox::question(
-          this,
+          nullptr,
           tr("Confirm ban"),
           tr("Do you really want to ban %1 (%2)?").arg(cache::displayName(room, userid), userid)) !=
         QMessageBox::Yes)
@@ -849,7 +841,7 @@ ChatPage::unbanUser(QString userid, QString reason)
 {
     auto room = currentRoom();
 
-    if (QMessageBox::question(this,
+    if (QMessageBox::question(nullptr,
                               tr("Confirm unban"),
                               tr("Do you really want to unban %1 (%2)?")
                                 .arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes)
@@ -1083,7 +1075,7 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio
                                    const SecretsToDecrypt &secrets)
 {
     QString text = QInputDialog::getText(
-      ChatPage::instance(),
+      nullptr,
       QCoreApplication::translate("CrossSigningSecrets", "Decrypt secrets"),
       keyDesc.name.empty()
         ? QCoreApplication::translate(
@@ -1115,7 +1107,7 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio
 
     if (!decryptionKey) {
         QMessageBox::information(
-          ChatPage::instance(),
+          nullptr,
           QCoreApplication::translate("CrossSigningSecrets", "Decryption failed"),
           QCoreApplication::translate("CrossSigningSecrets",
                                       "Failed to decrypt secrets with the "
@@ -1209,7 +1201,7 @@ ChatPage::startChat(QString userid)
 
     if (QMessageBox::Yes !=
         QMessageBox::question(
-          this,
+          nullptr,
           tr("Confirm invite"),
           tr("Do you really want to start a private chat with %1?").arg(userid)))
         return;
@@ -1395,7 +1387,7 @@ ChatPage::handleMatrixUri(const QUrl &uri)
 bool
 ChatPage::isRoomActive(const QString &room_id)
 {
-    return isActiveWindow() && currentRoom() == room_id;
+    return MainWindow::instance()->isActive() && currentRoom() == room_id;
 }
 
 QString
diff --git a/src/ChatPage.h b/src/ChatPage.h
index ae55c923..5e3b509d 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -51,12 +51,12 @@ struct Rooms;
 
 using SecretsToDecrypt = std::map<std::string, mtx::secret_storage::AesHmacSha2EncryptedData>;
 
-class ChatPage : public QWidget
+class ChatPage : public QObject
 {
     Q_OBJECT
 
 public:
-    ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent = nullptr);
+    ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent = nullptr);
 
     // Initialize all the components of the UI.
     void bootstrap(QString userid, QString homeserver, QString token);
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 5bfce89e..5e7fe6ce 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -13,77 +13,108 @@
 #include <mtx/requests.hpp>
 #include <mtx/responses/login.hpp>
 
+#include "BlurhashProvider.h"
 #include "Cache.h"
 #include "Cache_p.h"
 #include "ChatPage.h"
+#include "Clipboard.h"
+#include "ColorImageProvider.h"
+#include "CombinedImagePackModel.h"
+#include "CompletionProxyModel.h"
 #include "Config.h"
+#include "EventAccessors.h"
+#include "ImagePackListModel.h"
+#include "InviteesModel.h"
 #include "JdenticonProvider.h"
 #include "Logging.h"
 #include "LoginPage.h"
 #include "MainWindow.h"
 #include "MatrixClient.h"
 #include "MemberList.h"
+#include "MxcImageProvider.h"
+#include "ReadReceiptsModel.h"
 #include "RegisterPage.h"
+#include "RoomDirectoryModel.h"
+#include "RoomsModel.h"
+#include "SingleImagePackModel.h"
 #include "TrayIcon.h"
 #include "UserSettingsPage.h"
+#include "UsersModel.h"
 #include "Utils.h"
 #include "WelcomePage.h"
+#include "emoji/EmojiModel.h"
+#include "emoji/Provider.h"
+#include "encryption/DeviceVerificationFlow.h"
+#include "encryption/SelfVerificationStatus.h"
+#include "timeline/DelegateChooser.h"
+#include "timeline/TimelineViewManager.h"
 #include "ui/LoadingIndicator.h"
+#include "ui/MxcAnimatedImage.h"
+#include "ui/MxcMediaProxy.h"
+#include "ui/NhekoCursorShape.h"
+#include "ui/NhekoDropArea.h"
+#include "ui/NhekoGlobalObject.h"
 #include "ui/OverlayModal.h"
 #include "ui/SnackBar.h"
+#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>)
+
 MainWindow *MainWindow::instance_ = nullptr;
 
-MainWindow::MainWindow(QWidget *parent)
-  : QMainWindow(parent)
+MainWindow::MainWindow(QWindow *parent)
+  : QQuickView(parent)
   , userSettings_{UserSettings::instance()}
 {
     instance_ = this;
 
-    QMainWindow::setWindowTitle(0);
+    MainWindow::setWindowTitle(0);
     setObjectName(QStringLiteral("MainWindow"));
+    setResizeMode(QQuickView::SizeRootObjectToView);
+    setMinimumHeight(400);
+    setMinimumWidth(400);
+    restoreWindowSize();
 
-    modal_ = new OverlayModal(this);
+    chat_page_ = new ChatPage(userSettings_, this);
+    registerQmlTypes();
 
-    restoreWindowSize();
+    setColor(Theme::paletteFromTheme(userSettings_->theme()).window().color());
+    setSource(QUrl(QStringLiteral("qrc:///qml/Root.qml")));
+    // modal_ = new OverlayModal(this);
 
-    QFont font;
-    font.setStyleStrategy(QFont::PreferAntialias);
-    setFont(font);
 
-    trayIcon_ = new TrayIcon(QStringLiteral(":/logos/nheko.svg"), this);
+    // QFont font;
+    // font.setStyleStrategy(QFont::PreferAntialias);
+    // setFont(font);
 
-    welcome_page_  = new WelcomePage(this);
-    login_page_    = new LoginPage(this);
-    register_page_ = new RegisterPage(this);
-    chat_page_     = new ChatPage(userSettings_, this);
+    trayIcon_ = new TrayIcon(QStringLiteral(":/logos/nheko.svg"), this);
 
-    // Initialize sliding widget manager.
-    pageStack_ = new QStackedWidget(this);
-    pageStack_->addWidget(welcome_page_);
-    pageStack_->addWidget(login_page_);
-    pageStack_->addWidget(register_page_);
-    pageStack_->addWidget(chat_page_);
+    // welcome_page_  = new WelcomePage(this);
+    // login_page_    = new LoginPage(this);
+    // register_page_ = new RegisterPage(this);
 
-    setCentralWidget(pageStack_);
+    //// Initialize sliding widget manager.
 
-    connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage()));
-    connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage()));
+    // connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage()));
+    // connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage()));
 
-    connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
-    connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar);
-    connect(register_page_, &RegisterPage::registering, this, &MainWindow::showOverlayProgressBar);
-    connect(login_page_, &LoginPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); });
-    connect(
-      register_page_, &RegisterPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); });
-    connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
+    // connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
+    // connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar);
+    // connect(register_page_, &RegisterPage::registering, this,
+    // &MainWindow::showOverlayProgressBar); connect(login_page_, &LoginPage::errorOccurred, this,
+    // [this]() { removeOverlayProgressBar(); }); connect(
+    //   register_page_, &RegisterPage::errorOccurred, this, [this]() { removeOverlayProgressBar();
+    //   });
+    // connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
 
     connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage);
-    connect(
-      chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar);
+    // connect(
+    //   chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar);
     connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle);
     connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int)));
     connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) {
@@ -101,15 +132,12 @@ MainWindow::MainWindow(QWidget *parent)
 
     connect(this, &MainWindow::focusChanged, chat_page_, &ChatPage::chatFocusChanged);
 
-    connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) {
-        http::client()->set_user(res.user_id);
-        showChatPage();
-    });
-
-    connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage);
+    // connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) {
+    //     http::client()->set_user(res.user_id);
+    //     showChatPage();
+    // });
 
-    QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
-    connect(quitShortcut, &QShortcut::activated, this, QApplication::quit);
+    // connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage);
 
     trayIcon_->setVisible(userSettings_->tray());
 
@@ -133,12 +161,168 @@ MainWindow::MainWindow(QWidget *parent)
                                       user_id.toStdString());
             }
 
+            nhlog::ui()->info("User already signed in, showing chat page");
             showChatPage();
         }
     });
 }
 
 void
+MainWindow::registerQmlTypes()
+{
+    qRegisterMetaType<mtx::events::msg::KeyVerificationAccept>();
+    qRegisterMetaType<mtx::events::msg::KeyVerificationCancel>();
+    qRegisterMetaType<mtx::events::msg::KeyVerificationDone>();
+    qRegisterMetaType<mtx::events::msg::KeyVerificationKey>();
+    qRegisterMetaType<mtx::events::msg::KeyVerificationMac>();
+    qRegisterMetaType<mtx::events::msg::KeyVerificationReady>();
+    qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>();
+    qRegisterMetaType<mtx::events::msg::KeyVerificationStart>();
+    qRegisterMetaType<CombinedImagePackModel *>();
+    qRegisterMetaType<mtx::events::collections::TimelineEvents>();
+    qRegisterMetaType<std::vector<DeviceInfo>>();
+
+    qRegisterMetaType<std::vector<mtx::responses::PublicRoomsChunk>>();
+
+    qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
+                                     "im.nheko",
+                                     1,
+                                     0,
+                                     "MtxEvent",
+                                     QStringLiteral("Can't instantiate enum!"));
+    qmlRegisterUncreatableMetaObject(
+      olm::staticMetaObject, "im.nheko", 1, 0, "Olm", QStringLiteral("Can't instantiate enum!"));
+    qmlRegisterUncreatableMetaObject(crypto::staticMetaObject,
+                                     "im.nheko",
+                                     1,
+                                     0,
+                                     "Crypto",
+                                     QStringLiteral("Can't instantiate enum!"));
+    qmlRegisterUncreatableMetaObject(verification::staticMetaObject,
+                                     "im.nheko",
+                                     1,
+                                     0,
+                                     "VerificationStatus",
+                                     QStringLiteral("Can't instantiate enum!"));
+
+    qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice");
+    qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser");
+    qmlRegisterType<NhekoDropArea>("im.nheko", 1, 0, "NhekoDropArea");
+    qmlRegisterType<NhekoCursorShape>("im.nheko", 1, 0, "CursorShape");
+    qmlRegisterType<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage");
+    qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia");
+    qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel");
+    qmlRegisterUncreatableType<DeviceVerificationFlow>(
+      "im.nheko",
+      1,
+      0,
+      "DeviceVerificationFlow",
+      QStringLiteral("Can't create verification flow from QML!"));
+    qmlRegisterUncreatableType<UserProfile>(
+      "im.nheko",
+      1,
+      0,
+      "UserProfileModel",
+      QStringLiteral("UserProfile needs to be instantiated on the C++ side"));
+    qmlRegisterUncreatableType<MemberList>(
+      "im.nheko",
+      1,
+      0,
+      "MemberList",
+      QStringLiteral("MemberList needs to be instantiated on the C++ side"));
+    qmlRegisterUncreatableType<RoomSettings>(
+      "im.nheko",
+      1,
+      0,
+      "RoomSettingsModel",
+      QStringLiteral("Room Settings needs to be instantiated on the C++ side"));
+    qmlRegisterUncreatableType<TimelineModel>(
+      "im.nheko", 1, 0, "Room", QStringLiteral("Room needs to be instantiated on the C++ side"));
+    qmlRegisterUncreatableType<ImagePackListModel>(
+      "im.nheko",
+      1,
+      0,
+      "ImagePackListModel",
+      QStringLiteral("ImagePackListModel needs to be instantiated on the C++ side"));
+    qmlRegisterUncreatableType<SingleImagePackModel>(
+      "im.nheko",
+      1,
+      0,
+      "SingleImagePackModel",
+      QStringLiteral("SingleImagePackModel needs to be instantiated on the C++ side"));
+    qmlRegisterUncreatableType<InviteesModel>(
+      "im.nheko",
+      1,
+      0,
+      "InviteesModel",
+      QStringLiteral("InviteesModel needs to be instantiated on the C++ side"));
+    qmlRegisterUncreatableType<ReadReceiptsProxy>(
+      "im.nheko",
+      1,
+      0,
+      "ReadReceiptsProxy",
+      QStringLiteral("ReadReceiptsProxy needs to be instantiated on the C++ side"));
+
+    qmlRegisterSingletonType<Clipboard>(
+      "im.nheko", 1, 0, "Clipboard", [](QQmlEngine *, QJSEngine *) -> QObject * {
+          return new Clipboard();
+      });
+    qmlRegisterSingletonType<Nheko>(
+      "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * {
+          return new Nheko();
+      });
+    qmlRegisterSingletonType<UserSettingsModel>(
+      "im.nheko", 1, 0, "UserSettingsModel", [](QQmlEngine *, QJSEngine *) -> QObject * {
+          return new UserSettingsModel();
+      });
+
+    qmlRegisterSingletonInstance("im.nheko", 1, 0, "Settings", userSettings_.data());
+
+    qRegisterMetaType<mtx::events::collections::TimelineEvents>();
+    qRegisterMetaType<std::vector<DeviceInfo>>();
+
+    qmlRegisterUncreatableType<FilteredCommunitiesModel>(
+      "im.nheko",
+      1,
+      0,
+      "FilteredCommunitiesModel",
+      QStringLiteral("Use Communities.filtered() to create a FilteredCommunitiesModel"));
+
+    qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel");
+    qmlRegisterUncreatableType<emoji::Emoji>(
+      "im.nheko.EmojiModel", 1, 0, "Emoji", QStringLiteral("Used by emoji models"));
+    qmlRegisterUncreatableMetaObject(emoji::staticMetaObject,
+                                     "im.nheko.EmojiModel",
+                                     1,
+                                     0,
+                                     "EmojiCategory",
+                                     QStringLiteral("Error: Only enums"));
+
+    qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel");
+
+    qmlRegisterSingletonType<SelfVerificationStatus>(
+      "im.nheko", 1, 0, "SelfVerificationStatus", [](QQmlEngine *, QJSEngine *) -> QObject * {
+          auto ptr = new SelfVerificationStatus();
+          QObject::connect(ChatPage::instance(),
+                           &ChatPage::initializeEmptyViews,
+                           ptr,
+                           &SelfVerificationStatus::invalidate);
+          return ptr;
+      });
+    qmlRegisterSingletonInstance("im.nheko", 1, 0, "MainWindow", this);
+    qmlRegisterSingletonInstance("im.nheko", 1, 0, "UIA", UIA::instance());
+    qmlRegisterSingletonInstance(
+      "im.nheko", 1, 0, "CallManager", ChatPage::instance()->callManager());
+
+    imgProvider = new MxcImageProvider();
+    engine()->addImageProvider(QStringLiteral("MxcImage"), imgProvider);
+    engine()->addImageProvider(QStringLiteral("colorimage"), new ColorImageProvider());
+    engine()->addImageProvider(QStringLiteral("blurhash"), new BlurhashProvider());
+    if (JdenticonProvider::isAvailable())
+        engine()->addImageProvider(QStringLiteral("jdenticon"), new JdenticonProvider());
+}
+
+void
 MainWindow::setWindowTitle(int notificationCount)
 {
     QString name = QStringLiteral("nheko");
@@ -148,20 +332,23 @@ MainWindow::setWindowTitle(int notificationCount)
     if (notificationCount > 0) {
         name.append(QString{QStringLiteral(" (%1)")}.arg(notificationCount));
     }
-    QMainWindow::setWindowTitle(name);
+    QQuickView::setTitle(name);
 }
 
 bool
 MainWindow::event(QEvent *event)
 {
     auto type = event->type();
-    if (type == QEvent::WindowActivate) {
+
+    if (type == QEvent::Close) {
+        closeEvent(static_cast<QCloseEvent *>(event));
+    } else if (type == QEvent::WindowActivate) {
         emit focusChanged(true);
     } else if (type == QEvent::WindowDeactivate) {
         emit focusChanged(false);
     }
 
-    return QMainWindow::event(event);
+    return QQuickView::event(event);
 }
 
 void
@@ -196,19 +383,13 @@ MainWindow::removeOverlayProgressBar()
 
     connect(timer, &QTimer::timeout, this, [this, timer]() {
         timer->deleteLater();
-
-        if (modal_)
-            modal_->hide();
-
-        if (spinner_)
-            spinner_->stop();
     });
 
     // FIXME:  Snackbar doesn't work if it's initialized in the constructor.
-    QTimer::singleShot(0, this, [this]() {
-        snackBar_ = new SnackBar(this);
-        connect(chat_page_, &ChatPage::showNotification, snackBar_, &SnackBar::showMessage);
-    });
+    // QTimer::singleShot(0, this, [this]() {
+    //    snackBar_ = new SnackBar(this);
+    //    connect(chat_page_, &ChatPage::showNotification, snackBar_, &SnackBar::showMessage);
+    //});
 
     timer->start(50);
 }
@@ -229,17 +410,14 @@ MainWindow::showChatPage()
 
     showOverlayProgressBar();
 
-    pageStack_->setCurrentWidget(chat_page_);
-
-    pageStack_->removeWidget(welcome_page_);
-    pageStack_->removeWidget(login_page_);
-    pageStack_->removeWidget(register_page_);
-
-    login_page_->reset();
+    // login_page_->reset();
     chat_page_->bootstrap(userid, homeserver, token);
     connect(cache::client(), &Cache::databaseReady, this, &MainWindow::secretsChanged);
     connect(cache::client(), &Cache::secretChanged, this, &MainWindow::secretsChanged);
+
     emit reload();
+    nhlog::ui()->info("Switching to chat page");
+    emit switchToChatPage();
 }
 
 void
@@ -247,7 +425,7 @@ MainWindow::closeEvent(QCloseEvent *event)
 {
     if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
         if (QMessageBox::question(
-              this, QStringLiteral("nheko"), QStringLiteral("A call is in progress. Quit?")) !=
+              nullptr, QStringLiteral("nheko"), QStringLiteral("A call is in progress. Quit?")) !=
             QMessageBox::Yes) {
             event->ignore();
             return;
@@ -292,20 +470,13 @@ MainWindow::hasActiveUser()
 void
 MainWindow::showOverlayProgressBar()
 {
-    spinner_ = new LoadingIndicator(this);
-    spinner_->setFixedHeight(100);
-    spinner_->setFixedWidth(100);
-    spinner_->setObjectName(QStringLiteral("ChatPageLoadSpinner"));
-    spinner_->start();
-
-    showSolidOverlayModal(spinner_);
 }
 
 void
 MainWindow::openCreateRoomDialog(
   std::function<void(const mtx::requests::CreateRoom &request)> callback)
 {
-    auto dialog = new dialogs::CreateRoom(this);
+    auto dialog = new dialogs::CreateRoom(nullptr);
     connect(dialog,
             &dialogs::CreateRoom::createRoom,
             this,
@@ -315,50 +486,36 @@ MainWindow::openCreateRoomDialog(
 }
 
 void
-MainWindow::showTransparentOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags)
-{
-    modal_->setWidget(content);
-    modal_->setColor(QColor(30, 30, 30, 150));
-    modal_->setDismissible(true);
-    modal_->setContentAlignment(flags);
-    modal_->raise();
-    modal_->show();
-}
+MainWindow::showTransparentOverlayModal(QWidget *, QFlags<Qt::AlignmentFlag>)
+{}
 
 void
-MainWindow::showSolidOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags)
+MainWindow::showSolidOverlayModal(QWidget *, QFlags<Qt::AlignmentFlag>)
 {
-    modal_->setWidget(content);
-    modal_->setColor(QColor(30, 30, 30));
-    modal_->setDismissible(false);
-    modal_->setContentAlignment(flags);
-    modal_->raise();
-    modal_->show();
 }
 
 bool
 MainWindow::hasActiveDialogs() const
 {
-    return modal_ && modal_->isVisible();
+    return false;
 }
 
 bool
 MainWindow::pageSupportsTray() const
 {
-    return !welcome_page_->isVisible() && !login_page_->isVisible() && !register_page_->isVisible();
+    return false; //! welcome_page_->isVisible() && !login_page_->isVisible() &&
+                  //! !register_page_->isVisible();
 }
 
 void
 MainWindow::hideOverlay()
 {
-    if (modal_)
-        modal_->hide();
 }
 
 inline void
 MainWindow::showDialog(QWidget *dialog)
 {
-    utils::centerWidget(dialog, this);
+    // utils::centerWidget(dialog, this);
     dialog->raise();
     dialog->show();
 }
@@ -367,23 +524,13 @@ void
 MainWindow::showWelcomePage()
 {
     removeOverlayProgressBar();
-    pageStack_->addWidget(welcome_page_);
-    pageStack_->setCurrentWidget(welcome_page_);
 }
 
 void
 MainWindow::showLoginPage()
 {
-    if (modal_)
-        modal_->hide();
-
-    pageStack_->addWidget(login_page_);
-    pageStack_->setCurrentWidget(login_page_);
 }
 
 void
 MainWindow::showRegisterPage()
-{
-    pageStack_->addWidget(register_page_);
-    pageStack_->setCurrentWidget(register_page_);
-}
+{}
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 458eb054..04311e12 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -8,7 +8,7 @@
 
 #include <functional>
 
-#include <QMainWindow>
+#include <QQuickView>
 #include <QSharedPointer>
 #include <QStackedWidget>
 #include <QSystemTrayIcon>
@@ -28,6 +28,7 @@ class OverlayModal;
 class SnackBar;
 class TrayIcon;
 class UserSettings;
+class MxcImageProvider;
 
 namespace mtx {
 namespace requests {
@@ -42,17 +43,12 @@ class MemberList;
 class ReCaptcha;
 }
 
-class MainWindow : public QMainWindow
+class MainWindow : public QQuickView
 {
     Q_OBJECT
 
-    Q_PROPERTY(int x READ x CONSTANT)
-    Q_PROPERTY(int y READ y CONSTANT)
-    Q_PROPERTY(int width READ width CONSTANT)
-    Q_PROPERTY(int height READ height CONSTANT)
-
 public:
-    explicit MainWindow(QWidget *parent = nullptr);
+    explicit MainWindow(QWindow *parent = nullptr);
 
     static MainWindow *instance() { return instance_; }
     void saveCurrentWindowSize();
@@ -67,8 +63,10 @@ public:
     showTransparentOverlayModal(QWidget *content,
                                 QFlags<Qt::AlignmentFlag> flags = Qt::AlignTop | Qt::AlignHCenter);
 
+    MxcImageProvider *imageProvider() { return imgProvider; }
+
 protected:
-    void closeEvent(QCloseEvent *event) override;
+    void closeEvent(QCloseEvent *event);
     bool event(QEvent *event) override;
 
 private slots:
@@ -97,6 +95,9 @@ signals:
     void reload();
     void secretsChanged();
 
+    void switchToChatPage();
+    void switchToWelcomePage();
+
 private:
     void showDialog(QWidget *dialog);
     bool hasActiveUser();
@@ -106,6 +107,8 @@ private:
     //! Check if the current page supports the "minimize to tray" functionality.
     bool pageSupportsTray() const;
 
+    void registerQmlTypes();
+
     static MainWindow *instance_;
 
     //! The initial welcome screen.
@@ -114,16 +117,11 @@ private:
     LoginPage *login_page_;
     //! The register page.
     RegisterPage *register_page_;
-    //! A stacked widget that handles the transitions between widgets.
-    QStackedWidget *pageStack_;
     //! The main chat area.
     ChatPage *chat_page_;
     QSharedPointer<UserSettings> userSettings_;
     //! Tray icon that shows the unread message count.
     TrayIcon *trayIcon_;
-    //! Notifications display.
-    SnackBar *snackBar_ = nullptr;
-    //! Overlay modal used to project other widgets.
-    OverlayModal *modal_       = nullptr;
-    LoadingIndicator *spinner_ = nullptr;
+
+    MxcImageProvider *imgProvider = nullptr;
 };
diff --git a/src/TrayIcon.cpp b/src/TrayIcon.cpp
index d83156a4..28da9558 100644
--- a/src/TrayIcon.cpp
+++ b/src/TrayIcon.cpp
@@ -10,6 +10,7 @@
 #include <QMenu>
 #include <QPainter>
 #include <QTimer>
+#include <QWindow>
 
 #include "TrayIcon.h"
 
@@ -100,7 +101,7 @@ MsgCountComposedIcon::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State s
     return result;
 }
 
-TrayIcon::TrayIcon(const QString &filename, QWidget *parent)
+TrayIcon::TrayIcon(const QString &filename, QWindow *parent)
   : QSystemTrayIcon(parent)
 {
 #if defined(Q_OS_MAC) || defined(Q_OS_WIN)
@@ -110,13 +111,13 @@ TrayIcon::TrayIcon(const QString &filename, QWidget *parent)
     setIcon(QIcon(icon_));
 #endif
 
-    QMenu *menu = new QMenu(parent);
+    QMenu *menu = new QMenu();
     setContextMenu(menu);
 
     viewAction_ = new QAction(tr("Show"), this);
     quitAction_ = new QAction(tr("Quit"), this);
 
-    connect(viewAction_, SIGNAL(triggered()), parent, SLOT(show()));
+    connect(viewAction_, &QAction::triggered, parent, &QWindow::show);
     connect(quitAction_, &QAction::triggered, this, QApplication::quit);
 
     menu->addAction(viewAction_);
diff --git a/src/TrayIcon.h b/src/TrayIcon.h
index 17bf5eff..554a4a0a 100644
--- a/src/TrayIcon.h
+++ b/src/TrayIcon.h
@@ -40,7 +40,7 @@ class TrayIcon : public QSystemTrayIcon
 {
     Q_OBJECT
 public:
-    TrayIcon(const QString &filename, QWidget *parent);
+    TrayIcon(const QString &filename, QWindow *parent);
 
 public slots:
     void setUnreadCount(int count);
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index c43733fb..e973fc1f 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -1518,7 +1518,7 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int
                     QString homeFolder =
                       QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
                     auto filepath = QFileDialog::getOpenFileName(
-                      MainWindow::instance(), tr("Select a file"), homeFolder, tr("All Files (*)"));
+                      nullptr, tr("Select a file"), homeFolder, tr("All Files (*)"));
                     if (!filepath.isEmpty()) {
                         i->setRingtone(filepath);
                         i->setRingtone(filepath);
@@ -1600,11 +1600,11 @@ UserSettingsModel::importSessionKeys()
 {
     const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
     const QString fileName   = QFileDialog::getOpenFileName(
-      MainWindow::instance(), tr("Open Sessions File"), homeFolder, QLatin1String(""));
+      nullptr, tr("Open Sessions File"), homeFolder, QLatin1String(""));
 
     QFile file(fileName);
     if (!file.open(QIODevice::ReadOnly)) {
-        QMessageBox::warning(MainWindow::instance(), tr("Error"), file.errorString());
+        QMessageBox::warning(nullptr, tr("Error"), file.errorString());
         return;
     }
 
@@ -1612,7 +1612,7 @@ UserSettingsModel::importSessionKeys()
     auto payload = std::string(bin.data(), bin.size());
 
     bool ok;
-    auto password = QInputDialog::getText(MainWindow::instance(),
+    auto password = QInputDialog::getText(nullptr,
                                           tr("File Password"),
                                           tr("Enter the passphrase to decrypt the file:"),
                                           QLineEdit::Password,
@@ -1622,8 +1622,7 @@ UserSettingsModel::importSessionKeys()
         return;
 
     if (password.isEmpty()) {
-        QMessageBox::warning(
-          MainWindow::instance(), tr("Error"), tr("The password cannot be empty"));
+        QMessageBox::warning(nullptr, tr("Error"), tr("The password cannot be empty"));
         return;
     }
 
@@ -1631,7 +1630,7 @@ UserSettingsModel::importSessionKeys()
         auto sessions = mtx::crypto::decrypt_exported_sessions(payload, password.toStdString());
         cache::importSessionKeys(std::move(sessions));
     } catch (const std::exception &e) {
-        QMessageBox::warning(MainWindow::instance(), tr("Error"), e.what());
+        QMessageBox::warning(nullptr, tr("Error"), e.what());
     }
 }
 void
@@ -1639,7 +1638,7 @@ UserSettingsModel::exportSessionKeys()
 {
     // Open password dialog.
     bool ok;
-    auto password = QInputDialog::getText(MainWindow::instance(),
+    auto password = QInputDialog::getText(nullptr,
                                           tr("File Password"),
                                           tr("Enter passphrase to encrypt your session keys:"),
                                           QLineEdit::Password,
@@ -1649,19 +1648,18 @@ UserSettingsModel::exportSessionKeys()
         return;
 
     if (password.isEmpty()) {
-        QMessageBox::warning(
-          MainWindow::instance(), tr("Error"), tr("The password cannot be empty"));
+        QMessageBox::warning(nullptr, tr("Error"), tr("The password cannot be empty"));
         return;
     }
 
     // Open file dialog to save the file.
     const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
     const QString fileName   = QFileDialog::getSaveFileName(
-      MainWindow::instance(), tr("File to save the exported session keys"), homeFolder);
+      nullptr, tr("File to save the exported session keys"), homeFolder);
 
     QFile file(fileName);
     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
-        QMessageBox::warning(MainWindow::instance(), tr("Error"), file.errorString());
+        QMessageBox::warning(nullptr, tr("Error"), file.errorString());
         return;
     }
 
@@ -1679,7 +1677,7 @@ UserSettingsModel::exportSessionKeys()
         out << prefix << newline << b64 << newline << suffix << newline;
         file.close();
     } catch (const std::exception &e) {
-        QMessageBox::warning(MainWindow::instance(), tr("Error"), e.what());
+        QMessageBox::warning(nullptr, tr("Error"), e.what());
     }
 }
 void
diff --git a/src/main.cpp b/src/main.cpp
index 2ae631cf..af8a46a9 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -17,6 +17,7 @@
 #include <QLibraryInfo>
 #include <QMessageBox>
 #include <QPoint>
+#include <QQuickView>
 #include <QScreen>
 #include <QStandardPaths>
 #include <QTranslator>
@@ -279,6 +280,7 @@ main(int argc, char *argv[])
     font.setPointSizeF(settings.lock()->fontSize());
 
     app.setFont(font);
+    settings.lock()->applyTheme();
 
     if (QLocale().language() == QLocale::C)
         QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedKingdom));
@@ -296,15 +298,16 @@ main(int argc, char *argv[])
     app.installTranslator(&appTranslator);
 
     MainWindow w;
+    // QQuickView w;
 
     // Move the MainWindow to the center
-    w.move(screenCenter(w.width(), w.height()));
+    // w.move(screenCenter(w.width(), w.height()));
 
     if (!(settings.lock()->startInTray() && settings.lock()->tray()))
         w.show();
 
     QObject::connect(&app, &QApplication::aboutToQuit, &w, [&w]() {
-        w.saveCurrentWindowSize();
+        // w.saveCurrentWindowSize();
         if (http::client() != nullptr) {
             nhlog::net()->debug("shutting down all I/O threads & open connections");
             http::client()->close(true);
@@ -314,7 +317,7 @@ main(int argc, char *argv[])
     QObject::connect(&app, &SingleApplication::instanceStarted, &w, [&w]() {
         w.show();
         w.raise();
-        w.activateWindow();
+        w.requestActivate();
     });
 
     // It seems like handling the message in a blocking manner is a no-go. I have no idea how to
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index 5284ce0e..18e224b2 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -266,8 +266,8 @@ void
 InputBar::openFileSelection()
 {
     const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
-    const auto fileName      = QFileDialog::getOpenFileName(
-      ChatPage::instance(), tr("Select a file"), homeFolder, tr("All Files (*)"));
+    const auto fileName =
+      QFileDialog::getOpenFileName(nullptr, tr("Select a file"), homeFolder, tr("All Files (*)"));
 
     if (fileName.isEmpty())
         return;
@@ -659,7 +659,7 @@ InputBar::command(const QString &command, QString args)
 void
 InputBar::showPreview(const QMimeData &source, const QString &path, const QStringList &formats)
 {
-    auto *previewDialog_ = new dialogs::PreviewUploadOverlay(ChatPage::instance());
+    auto *previewDialog_ = new dialogs::PreviewUploadOverlay(nullptr);
     previewDialog_->setAttribute(Qt::WA_DeleteOnClose);
 
     // Force SVG to _not_ be handled as an image, but as raw data
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index eb453462..aa81f501 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -8,6 +8,7 @@
 #include "Cache_p.h"
 #include "ChatPage.h"
 #include "Logging.h"
+#include "MainWindow.h"
 #include "MatrixClient.h"
 #include "MxcImageProvider.h"
 #include "TimelineModel.h"
@@ -275,7 +276,7 @@ RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification)
 
         connect(newRoom.data(),
                 &TimelineModel::newEncryptedImage,
-                manager->imageProvider(),
+                MainWindow::instance()->imageProvider(),
                 &MxcImageProvider::addEncryptionInfo);
         connect(newRoom.data(),
                 &TimelineModel::forwardToRoom,
@@ -509,7 +510,7 @@ RoomlistModel::sync(const mtx::responses::Sync &sync_)
         // room_model->addEvents(room.timeline);
         connect(room_model.data(),
                 &TimelineModel::newCallEvent,
-                manager->callManager(),
+                ChatPage::instance()->callManager(),
                 &CallManager::syncEvent,
                 Qt::UniqueConnection);
 
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 7c9df403..e769fa40 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -1031,7 +1031,7 @@ TimelineModel::setCurrentIndex(int index)
     if (index != oldIndex)
         emit currentIndexChanged(index);
 
-    if (!ChatPage::instance()->isActiveWindow())
+    if (!MainWindow::instance()->isActive())
         return;
 
     if (!currentId.startsWith('m')) {
@@ -1495,7 +1495,7 @@ TimelineModel::saveMedia(const QString &eventId) const
     const QString openLocation = downloadsFolder + "/" + originalFilename;
 
     const QString filename =
-      QFileDialog::getSaveFileName(manager_->getWidget(), dialogTitle, openLocation, filterString);
+      QFileDialog::getSaveFileName(nullptr, dialogTitle, openLocation, filterString);
 
     if (filename.isEmpty())
         return false;
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index e689e2fa..5d2a4443 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -8,7 +8,6 @@
 #include <QDropEvent>
 #include <QFileDialog>
 #include <QMetaType>
-#include <QPalette>
 #include <QQmlContext>
 #include <QQmlEngine>
 #include <QStandardPaths>
@@ -45,10 +44,6 @@
 #include "ui/NhekoGlobalObject.h"
 #include "ui/UIA.h"
 
-Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
-Q_DECLARE_METATYPE(std::vector<DeviceInfo>)
-Q_DECLARE_METATYPE(std::vector<mtx::responses::PublicRoomsChunk>)
-
 namespace msgs = mtx::events::msg;
 
 namespace {
@@ -102,19 +97,6 @@ void
 TimelineViewManager::updateColorPalette()
 {
     userColors.clear();
-
-    if (ChatPage::instance()->userSettings()->theme() == QLatin1String("light")) {
-        view->rootContext()->setContextProperty(QStringLiteral("currentActivePalette"), QPalette());
-        view->rootContext()->setContextProperty(QStringLiteral("currentInactivePalette"),
-                                                QPalette());
-    } else if (ChatPage::instance()->userSettings()->theme() == QLatin1String("dark")) {
-        view->rootContext()->setContextProperty(QStringLiteral("currentActivePalette"), QPalette());
-        view->rootContext()->setContextProperty(QStringLiteral("currentInactivePalette"),
-                                                QPalette());
-    } else {
-        view->rootContext()->setContextProperty(QStringLiteral("currentActivePalette"), QPalette());
-        view->rootContext()->setContextProperty(QStringLiteral("currentInactivePalette"), nullptr);
-    }
 }
 
 QColor
@@ -126,112 +108,15 @@ TimelineViewManager::userColor(QString id, QColor background)
     return userColors.value(idx);
 }
 
-TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *parent)
+TimelineViewManager::TimelineViewManager(CallManager *, ChatPage *parent)
   : QObject(parent)
-  , imgProvider(new MxcImageProvider())
-  , colorImgProvider(new ColorImageProvider())
-  , blurhashProvider(new BlurhashProvider())
-  , jdenticonProvider(new JdenticonProvider())
   , rooms_(new RoomlistModel(this))
   , communities_(new CommunitiesModel(this))
-  , callManager_(callManager)
   , verificationManager_(new VerificationManager(this))
   , presenceEmitter(new PresenceEmitter(this))
 {
-    qRegisterMetaType<mtx::events::msg::KeyVerificationAccept>();
-    qRegisterMetaType<mtx::events::msg::KeyVerificationCancel>();
-    qRegisterMetaType<mtx::events::msg::KeyVerificationDone>();
-    qRegisterMetaType<mtx::events::msg::KeyVerificationKey>();
-    qRegisterMetaType<mtx::events::msg::KeyVerificationMac>();
-    qRegisterMetaType<mtx::events::msg::KeyVerificationReady>();
-    qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>();
-    qRegisterMetaType<mtx::events::msg::KeyVerificationStart>();
-    qRegisterMetaType<CombinedImagePackModel *>();
-
-    qRegisterMetaType<std::vector<mtx::responses::PublicRoomsChunk>>();
-
-    qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
-                                     "im.nheko",
-                                     1,
-                                     0,
-                                     "MtxEvent",
-                                     QStringLiteral("Can't instantiate enum!"));
-    qmlRegisterUncreatableMetaObject(
-      olm::staticMetaObject, "im.nheko", 1, 0, "Olm", QStringLiteral("Can't instantiate enum!"));
-    qmlRegisterUncreatableMetaObject(crypto::staticMetaObject,
-                                     "im.nheko",
-                                     1,
-                                     0,
-                                     "Crypto",
-                                     QStringLiteral("Can't instantiate enum!"));
-    qmlRegisterUncreatableMetaObject(verification::staticMetaObject,
-                                     "im.nheko",
-                                     1,
-                                     0,
-                                     "VerificationStatus",
-                                     QStringLiteral("Can't instantiate enum!"));
-
-    qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice");
-    qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser");
-    qmlRegisterType<NhekoDropArea>("im.nheko", 1, 0, "NhekoDropArea");
-    qmlRegisterType<NhekoCursorShape>("im.nheko", 1, 0, "CursorShape");
-    qmlRegisterType<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage");
-    qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia");
-    qmlRegisterUncreatableType<DeviceVerificationFlow>(
-      "im.nheko",
-      1,
-      0,
-      "DeviceVerificationFlow",
-      QStringLiteral("Can't create verification flow from QML!"));
-    qmlRegisterUncreatableType<UserProfile>(
-      "im.nheko",
-      1,
-      0,
-      "UserProfileModel",
-      QStringLiteral("UserProfile needs to be instantiated on the C++ side"));
-    qmlRegisterUncreatableType<MemberList>(
-      "im.nheko",
-      1,
-      0,
-      "MemberList",
-      QStringLiteral("MemberList needs to be instantiated on the C++ side"));
-    qmlRegisterUncreatableType<RoomSettings>(
-      "im.nheko",
-      1,
-      0,
-      "RoomSettingsModel",
-      QStringLiteral("Room Settings needs to be instantiated on the C++ side"));
-    qmlRegisterUncreatableType<TimelineModel>(
-      "im.nheko", 1, 0, "Room", QStringLiteral("Room needs to be instantiated on the C++ side"));
-    qmlRegisterUncreatableType<ImagePackListModel>(
-      "im.nheko",
-      1,
-      0,
-      "ImagePackListModel",
-      QStringLiteral("ImagePackListModel needs to be instantiated on the C++ side"));
-    qmlRegisterUncreatableType<SingleImagePackModel>(
-      "im.nheko",
-      1,
-      0,
-      "SingleImagePackModel",
-      QStringLiteral("SingleImagePackModel needs to be instantiated on the C++ side"));
-    qmlRegisterUncreatableType<InviteesModel>(
-      "im.nheko",
-      1,
-      0,
-      "InviteesModel",
-      QStringLiteral("InviteesModel needs to be instantiated on the C++ side"));
-    qmlRegisterUncreatableType<ReadReceiptsProxy>(
-      "im.nheko",
-      1,
-      0,
-      "ReadReceiptsProxy",
-      QStringLiteral("ReadReceiptsProxy needs to be instantiated on the C++ side"));
-
     static auto self = this;
-    qmlRegisterSingletonInstance("im.nheko", 1, 0, "MainWindow", MainWindow::instance());
     qmlRegisterSingletonInstance("im.nheko", 1, 0, "TimelineManager", self);
-    qmlRegisterSingletonInstance("im.nheko", 1, 0, "UIA", UIA::instance());
     qmlRegisterSingletonType<RoomlistModel>(
       "im.nheko", 1, 0, "Rooms", [](QQmlEngine *, QJSEngine *) -> QObject * {
           auto ptr = new FilteredRoomlistModel(self->rooms_);
@@ -247,79 +132,15 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
           return ptr;
       });
     qmlRegisterSingletonInstance("im.nheko", 1, 0, "Communities", self->communities_);
-    qmlRegisterSingletonInstance(
-      "im.nheko", 1, 0, "Settings", ChatPage::instance()->userSettings().data());
-    qmlRegisterSingletonInstance(
-      "im.nheko", 1, 0, "CallManager", ChatPage::instance()->callManager());
-    qmlRegisterSingletonType<Clipboard>(
-      "im.nheko", 1, 0, "Clipboard", [](QQmlEngine *, QJSEngine *) -> QObject * {
-          return new Clipboard();
-      });
-    qmlRegisterSingletonType<Nheko>(
-      "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * {
-          return new Nheko();
-      });
-    qmlRegisterSingletonType<UserSettingsModel>(
-      "im.nheko", 1, 0, "UserSettingsModel", [](QQmlEngine *, QJSEngine *) -> QObject * {
-          return new UserSettingsModel();
-      });
     qmlRegisterSingletonInstance("im.nheko", 1, 0, "VerificationManager", verificationManager_);
     qmlRegisterSingletonInstance("im.nheko", 1, 0, "Presence", presenceEmitter);
-    qmlRegisterSingletonType<SelfVerificationStatus>(
-      "im.nheko", 1, 0, "SelfVerificationStatus", [](QQmlEngine *, QJSEngine *) -> QObject * {
-          auto ptr = new SelfVerificationStatus();
-          QObject::connect(ChatPage::instance(),
-                           &ChatPage::initializeEmptyViews,
-                           ptr,
-                           &SelfVerificationStatus::invalidate);
-          return ptr;
-      });
 
-    qRegisterMetaType<mtx::events::collections::TimelineEvents>();
-    qRegisterMetaType<std::vector<DeviceInfo>>();
-
-    qmlRegisterUncreatableType<FilteredCommunitiesModel>(
-      "im.nheko",
-      1,
-      0,
-      "FilteredCommunitiesModel",
-      QStringLiteral("Use Communities.filtered() to create a FilteredCommunitiesModel"));
-
-    qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel");
-    qmlRegisterUncreatableType<emoji::Emoji>(
-      "im.nheko.EmojiModel", 1, 0, "Emoji", QStringLiteral("Used by emoji models"));
-    qmlRegisterUncreatableMetaObject(emoji::staticMetaObject,
-                                     "im.nheko.EmojiModel",
-                                     1,
-                                     0,
-                                     "EmojiCategory",
-                                     QStringLiteral("Error: Only enums"));
-
-    qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel");
-
-#ifdef USE_QUICK_VIEW
-    view      = new QQuickView(parent);
-    container = QWidget::createWindowContainer(view, parent);
-#else
-    view      = new QQuickWidget(parent);
-    container = view;
-    view->setResizeMode(QQuickWidget::SizeRootObjectToView);
-    container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
-
-    connect(view, &QQuickWidget::statusChanged, this, [](QQuickWidget::Status status) {
-        nhlog::ui()->debug("Status changed to {}", status);
-    });
-#endif
-    container->setMinimumSize(200, 200);
     updateColorPalette();
-    view->engine()->addImageProvider(QStringLiteral("MxcImage"), imgProvider);
-    view->engine()->addImageProvider(QStringLiteral("colorimage"), colorImgProvider);
-    view->engine()->addImageProvider(QStringLiteral("blurhash"), blurhashProvider);
-    if (JdenticonProvider::isAvailable())
-        view->engine()->addImageProvider(QStringLiteral("jdenticon"), jdenticonProvider);
-    view->setSource(QUrl(QStringLiteral("qrc:///qml/Root.qml")));
-
-    connect(parent, &ChatPage::themeChanged, this, &TimelineViewManager::updateColorPalette);
+
+    connect(UserSettings::instance().get(),
+            &UserSettings::themeChanged,
+            this,
+            &TimelineViewManager::updateColorPalette);
     connect(parent,
             &ChatPage::receivedRoomDeviceVerificationRequest,
             verificationManager_,
@@ -379,7 +200,8 @@ void
 TimelineViewManager::setVideoCallItem()
 {
     WebRTCSession::instance().setVideoItem(
-      view->rootObject()->findChild<QQuickItem *>(QStringLiteral("videoCallItem")));
+      MainWindow::instance()->rootObject()->findChild<QQuickItem *>(
+        QStringLiteral("videoCallItem")));
 }
 
 void
@@ -401,7 +223,7 @@ TimelineViewManager::showEvent(const QString &room_id, const QString &event_id)
     if (auto room = rooms_->getRoomById(room_id)) {
         if (rooms_->currentRoom() != room) {
             rooms_->setCurrentRoom(room_id);
-            container->setFocus();
+            MainWindow::instance()->requestActivate();
             nhlog::ui()->info("Activated room {}", room_id.toStdString());
         }
 
@@ -439,7 +261,7 @@ TimelineViewManager::saveMedia(QString mxcUrl)
       QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
     const QString openLocation = downloadsFolder + "/" + mxcUrl.splitRef(u'/').constLast();
 
-    const QString filename = QFileDialog::getSaveFileName(getWidget(), {}, openLocation);
+    const QString filename = QFileDialog::getSaveFileName(nullptr, {}, openLocation);
 
     if (filename.isEmpty())
         return;
@@ -591,12 +413,6 @@ TimelineViewManager::completerFor(QString completerName, QString roomId)
 }
 
 void
-TimelineViewManager::focusTimeline()
-{
-    getWidget()->setFocus();
-}
-
-void
 TimelineViewManager::forwardMessageToRoom(mtx::events::collections::TimelineEvents *e,
                                           QString roomId)
 {
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 455702f4..424d78d6 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -8,8 +8,6 @@
 #include <QHash>
 #include <QQuickItem>
 #include <QQuickTextDocument>
-#include <QQuickView>
-#include <QQuickWidget>
 #include <QWidget>
 
 #include <mtx/common.hpp>
@@ -48,12 +46,9 @@ class TimelineViewManager : public QObject
 
 public:
     TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr);
-    QWidget *getWidget() const { return container; }
 
     void sync(const mtx::responses::Sync &sync_);
 
-    MxcImageProvider *imageProvider() { return imgProvider; }
-    CallManager *callManager() { return callManager_; }
     VerificationManager *verificationManager() { return verificationManager_; }
 
     void clearAll() { rooms_->clear(); }
@@ -105,7 +100,6 @@ public slots:
     }
 
     void showEvent(const QString &room_id, const QString &event_id);
-    void focusTimeline();
 
     void updateColorPalette();
     void queueReply(const QString &roomid, const QString &repliedToEvent, const QString &replyBody);
@@ -122,18 +116,6 @@ public slots:
     RoomlistModel *rooms() { return rooms_; }
 
 private:
-#ifdef USE_QUICK_VIEW
-    QQuickView *view;
-#else
-    QQuickWidget *view;
-#endif
-    QWidget *container;
-
-    MxcImageProvider *imgProvider;
-    ColorImageProvider *colorImgProvider;
-    BlurhashProvider *blurhashProvider;
-    JdenticonProvider *jdenticonProvider;
-
     bool isInitialSync_   = true;
     bool isWindowFocused_ = false;
 
@@ -141,7 +123,6 @@ private:
     CommunitiesModel *communities_ = nullptr;
 
     // don't move this above the rooms_
-    CallManager *callManager_                 = nullptr;
     VerificationManager *verificationManager_ = nullptr;
     PresenceEmitter *presenceEmitter          = nullptr;
 
diff --git a/src/ui/NhekoGlobalObject.cpp b/src/ui/NhekoGlobalObject.cpp
index 450cb0d0..3d8d9959 100644
--- a/src/ui/NhekoGlobalObject.cpp
+++ b/src/ui/NhekoGlobalObject.cpp
@@ -139,5 +139,5 @@ Nheko::openCreateRoomDialog() const
 void
 Nheko::reparent(QWindow *win) const
 {
-    win->setTransientParent(MainWindow::instance()->windowHandle());
+    win->setTransientParent(MainWindow::instance());
 }
diff --git a/src/ui/UIA.cpp b/src/ui/UIA.cpp
index 291d0a9f..9f28ca6a 100644
--- a/src/ui/UIA.cpp
+++ b/src/ui/UIA.cpp
@@ -13,7 +13,6 @@
 #include <mtx/responses/common.hpp>
 
 #include "Logging.h"
-#include "MainWindow.h"
 #include "dialogs/FallbackAuth.h"
 #include "dialogs/ReCaptcha.h"
 
@@ -71,7 +70,7 @@ UIA::genericHandler(QString context)
                 emit phoneNumber();
             } else if (current_stage == mtx::user_interactive::auth_types::recaptcha) {
                 auto captchaDialog =
-                  new dialogs::ReCaptcha(QString::fromStdString(u.session), MainWindow::instance());
+                  new dialogs::ReCaptcha(QString::fromStdString(u.session), nullptr);
                 captchaDialog->setWindowTitle(context);
 
                 connect(
@@ -95,7 +94,7 @@ UIA::genericHandler(QString context)
             } else if (current_stage == mtx::user_interactive::auth_types::registration_token) {
                 bool ok;
                 QString token =
-                  QInputDialog::getText(MainWindow::instance(),
+                  QInputDialog::getText(nullptr,
                                         context,
                                         tr("Please enter a valid registration token."),
                                         QLineEdit::Normal,
@@ -113,7 +112,7 @@ UIA::genericHandler(QString context)
                 // use fallback
                 auto dialog = new dialogs::FallbackAuth(QString::fromStdString(current_stage),
                                                         QString::fromStdString(u.session),
-                                                        MainWindow::instance());
+                                                        nullptr);
                 dialog->setWindowTitle(context);
 
                 connect(dialog, &dialogs::FallbackAuth::confirmation, this, [h, u, dialog]() {