From 656fcac91cc3da305cca2e9ee7abd0a8557d176e Mon Sep 17 00:00:00 2001
From: Nicolas Werner <nicolas.werner@hotmail.de>
Date: Wed, 30 Mar 2022 06:45:31 +0200
Subject: [PATCH] Don't use a modal to edit room name and topic

---
 CMakeLists.txt                         |   2 -
 resources/qml/dialogs/RoomSettings.qml |  99 +++++--
 src/ui/RoomSettings.cpp                | 267 +++++++----------
 src/ui/RoomSettings.h                  |  54 +---
 src/ui/TextField.cpp                   | 382 -------------------------
 src/ui/TextField.h                     | 201 -------------
 src/ui/Theme.h                         |  48 ----
 src/ui/ThemeManager.cpp                |   2 -
 8 files changed, 187 insertions(+), 868 deletions(-)
 delete mode 100644 src/ui/TextField.cpp
 delete mode 100644 src/ui/TextField.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 704e8e9f0..332c9e925 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -338,7 +338,6 @@ set(SRC_FILES
 	src/ui/NhekoDropArea.cpp
 	src/ui/NhekoGlobalObject.cpp
 	src/ui/RoomSettings.cpp
-	src/ui/TextField.cpp
 	src/ui/Theme.cpp
 	src/ui/ThemeManager.cpp
 	src/ui/UIA.cpp
@@ -532,7 +531,6 @@ qt5_wrap_cpp(MOC_HEADERS
 	src/ui/NhekoDropArea.h
 	src/ui/NhekoGlobalObject.h
 	src/ui/RoomSettings.h
-	src/ui/TextField.h
 	src/ui/Theme.h
 	src/ui/ThemeManager.h
 	src/ui/UIA.h
diff --git a/resources/qml/dialogs/RoomSettings.qml b/resources/qml/dialogs/RoomSettings.qml
index a5ca0b475..110475c7f 100644
--- a/resources/qml/dialogs/RoomSettings.qml
+++ b/resources/qml/dialogs/RoomSettings.qml
@@ -107,17 +107,58 @@ ApplicationWindow {
                     hideErrorAnimation.restart();
                 }
             }
-                Label {
-                    text: roomSettings.roomName
-                    Layout.alignment: Qt.AlignHCenter
-                    font.pixelSize: fontMetrics.font.pixelSize * 2
-                    Layout.fillWidth: true
-                    horizontalAlignment: TextEdit.AlignHCenter
-                    color: Nheko.colors.text
-                    wrapMode: Text.Wrap
-                    textFormat: Text.RichText
+
+            TextEdit {
+                id: roomName
+
+                property bool isNameEditingAllowed: false
+
+                readOnly: !isNameEditingAllowed
+                textFormat: isNameEditingAllowed ? TextEdit.PlainText : TextEdit.RichText
+                text: isNameEditingAllowed ? roomSettings.plainRoomName : roomSettings.roomName
+                font.pixelSize: fontMetrics.font.pixelSize * 2
+                color: Nheko.colors.text
+
+                Layout.alignment: Qt.AlignHCenter
+                Layout.maximumWidth: parent.width - (Nheko.paddingSmall * 2) - nameChangeButton.anchors.leftMargin - (nameChangeButton.width * 2)
+                horizontalAlignment: TextEdit.AlignHCenter
+                wrapMode: TextEdit.Wrap
+                selectByMouse: true
+
+                Keys.onShortcutOverride: event.key === Qt.Key_Enter
+                Keys.onPressed: {
+                    if (event.matches(StandardKey.InsertLineSeparator) || event.matches(StandardKey.InsertParagraphSeparator)) {
+                        roomSettings.changeName(roomName.text);
+                        roomName.isNameEditingAllowed = false;
+                        event.accepted = true;
+                    }
+                }
+
+                ImageButton {
+                    id: nameChangeButton
+                    visible: roomSettings.canChangeName
+                    anchors.leftMargin: Nheko.paddingSmall
+                    anchors.left: roomName.right
+                    anchors.verticalCenter: roomName.verticalCenter
+                    hoverEnabled: true
+                    ToolTip.visible: hovered
+                    ToolTip.text: qsTr("Change name of this room")
+                    ToolTip.delay: Nheko.tooltipDelay
+                    image: roomName.isNameEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
+                    onClicked: {
+                        if (roomName.isNameEditingAllowed) {
+                            roomSettings.changeName(roomName.text);
+                            roomName.isNameEditingAllowed = false;
+                        } else {
+                            roomName.isNameEditingAllowed = true;
+                            roomName.focus = true;
+                            roomName.selectAll();
+                        }
+                    }
                 }
 
+            }
+
                 Label {
                     text: qsTr("%n member(s)", "", roomSettings.memberCount)
                     Layout.alignment: Qt.AlignHCenter
@@ -134,17 +175,10 @@ ApplicationWindow {
 
                 }
 
-            ImageButton {
-                Layout.alignment: Qt.AlignHCenter
-                image: ":/icons/icons/ui/edit.svg"
-                visible: roomSettings.canChangeNameAndTopic
-                onClicked: roomSettings.openEditModal()
-            }
-
             TextArea {
                 id: roomTopic
                 property bool cut: implicitHeight > 100
-                property bool showMore
+                property bool showMore: false
                 clip: true
                 Layout.maximumHeight: showMore? Number.POSITIVE_INFINITY : 100
                 Layout.preferredHeight: implicitHeight
@@ -153,10 +187,12 @@ ApplicationWindow {
                 Layout.leftMargin: Nheko.paddingLarge
                 Layout.rightMargin: Nheko.paddingLarge
 
-                text: TimelineManager.escapeEmoji(roomSettings.roomTopic)
+                property bool isTopicEditingAllowed: false
+
+                readOnly: !isTopicEditingAllowed
+                textFormat: isTopicEditingAllowed ? TextEdit.PlainText : TextEdit.RichText
+                text: isTopicEditingAllowed ? roomSettings.plainRoomTopic : roomSettings.roomTopic
                 wrapMode: TextEdit.WordWrap
-                textFormat: TextEdit.RichText
-                readOnly: true
                 background: null
                 selectByMouse: !Settings.mobileMode
                 color: Nheko.colors.text
@@ -169,6 +205,29 @@ ApplicationWindow {
                 }
 
             }
+
+            ImageButton {
+                id: topicChangeButton
+                Layout.alignment: Qt.AlignHCenter
+                visible: roomSettings.canChangeTopic
+                hoverEnabled: true
+                ToolTip.visible: hovered
+                ToolTip.text: qsTr("Change topic of this room")
+                ToolTip.delay: Nheko.tooltipDelay
+                image: roomTopic.isTopicEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
+                onClicked: {
+                    if (roomTopic.isTopicEditingAllowed) {
+                        roomSettings.changeTopic(roomTopic.text);
+                        roomTopic.isTopicEditingAllowed = false;
+                    } else {
+                        roomTopic.isTopicEditingAllowed = true;
+                        roomTopic.showMore = true;
+                        roomTopic.focus = true;
+                        //roomTopic.selectAll();
+                    }
+                }
+            }
+
             Item {
                 Layout.alignment: Qt.AlignHCenter
                 id: showMorePlaceholder
diff --git a/src/ui/RoomSettings.cpp b/src/ui/RoomSettings.cpp
index 43a11b7f9..42db19557 100644
--- a/src/ui/RoomSettings.cpp
+++ b/src/ui/RoomSettings.cpp
@@ -5,13 +5,10 @@
 
 #include "RoomSettings.h"
 
-#include <QApplication>
 #include <QFileDialog>
-#include <QHBoxLayout>
 #include <QImageReader>
 #include <QMimeDatabase>
 #include <QStandardPaths>
-#include <QVBoxLayout>
 #include <mtx/events/event_type.hpp>
 #include <mtx/responses/common.hpp>
 #include <mtx/responses/media.hpp>
@@ -23,153 +20,9 @@
 #include "Logging.h"
 #include "MatrixClient.h"
 #include "Utils.h"
-#include "ui/TextField.h"
 
 using namespace mtx::events;
 
-EditModal::EditModal(const QString &roomId, QWidget *parent)
-  : QWidget(parent)
-  , roomId_{roomId}
-{
-    setAutoFillBackground(true);
-    setAttribute(Qt::WA_DeleteOnClose, true);
-    setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
-    setWindowModality(Qt::WindowModal);
-
-    QFont largeFont;
-    largeFont.setPointSizeF(largeFont.pointSizeF() * 1.4);
-    setMinimumWidth(conf::window::minModalWidth);
-    setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
-
-    auto layout = new QVBoxLayout(this);
-
-    applyBtn_  = new QPushButton(tr("Apply"), this);
-    cancelBtn_ = new QPushButton(tr("Cancel"), this);
-    cancelBtn_->setDefault(true);
-
-    auto btnLayout = new QHBoxLayout;
-    btnLayout->addStretch(1);
-    btnLayout->setSpacing(15);
-    btnLayout->addWidget(cancelBtn_);
-    btnLayout->addWidget(applyBtn_);
-
-    nameInput_ = new TextField(this);
-    nameInput_->setLabel(tr("Name").toUpper());
-    topicInput_ = new TextField(this);
-    topicInput_->setLabel(tr("Topic").toUpper());
-
-    errorField_ = new QLabel(this);
-    errorField_->setWordWrap(true);
-    errorField_->hide();
-
-    layout->addWidget(nameInput_);
-    layout->addWidget(topicInput_);
-    layout->addLayout(btnLayout, 1);
-
-    auto labelLayout = new QHBoxLayout;
-    labelLayout->setAlignment(Qt::AlignHCenter);
-    labelLayout->addWidget(errorField_);
-    layout->addLayout(labelLayout);
-
-    connect(applyBtn_, &QPushButton::clicked, this, &EditModal::applyClicked);
-    connect(cancelBtn_, &QPushButton::clicked, this, &EditModal::close);
-
-    auto window = QApplication::activeWindow();
-
-    if (window != nullptr) {
-        auto center = window->frameGeometry().center();
-        move(center.x() - (width() * 0.5), center.y() - (height() * 0.5));
-    }
-}
-
-void
-EditModal::topicEventSent(const QString &topic)
-{
-    errorField_->hide();
-    emit topicChanged(topic);
-    close();
-}
-
-void
-EditModal::nameEventSent(const QString &name)
-{
-    errorField_->hide();
-    emit nameChanged(name);
-    close();
-}
-
-void
-EditModal::error(const QString &msg)
-{
-    errorField_->setText(msg);
-    errorField_->show();
-}
-
-void
-EditModal::applyClicked()
-{
-    // Check if the values are changed from the originals.
-    auto newName  = nameInput_->text().trimmed();
-    auto newTopic = topicInput_->text().trimmed();
-
-    errorField_->hide();
-
-    if (newName == initialName_ && newTopic == initialTopic_) {
-        close();
-        return;
-    }
-
-    using namespace mtx::events;
-    auto proxy = std::make_shared<ThreadProxy>();
-    connect(proxy.get(), &ThreadProxy::topicEventSent, this, &EditModal::topicEventSent);
-    connect(proxy.get(), &ThreadProxy::nameEventSent, this, &EditModal::nameEventSent);
-    connect(proxy.get(), &ThreadProxy::error, this, &EditModal::error);
-
-    if (newName != initialName_ && !newName.isEmpty()) {
-        state::Name body;
-        body.name = newName.toStdString();
-
-        http::client()->send_state_event(
-          roomId_.toStdString(),
-          body,
-          [proxy, newName](const mtx::responses::EventId &, mtx::http::RequestErr err) {
-              if (err) {
-                  emit proxy->error(QString::fromStdString(err->matrix_error.error));
-                  return;
-              }
-
-              emit proxy->nameEventSent(newName);
-          });
-    }
-
-    if (newTopic != initialTopic_ && !newTopic.isEmpty()) {
-        state::Topic body;
-        body.topic = newTopic.toStdString();
-
-        http::client()->send_state_event(
-          roomId_.toStdString(),
-          body,
-          [proxy, newTopic](const mtx::responses::EventId &, mtx::http::RequestErr err) {
-              if (err) {
-                  emit proxy->error(QString::fromStdString(err->matrix_error.error));
-                  return;
-              }
-
-              emit proxy->topicEventSent(newTopic);
-          });
-    }
-}
-
-void
-EditModal::setFields(const QString &roomName, const QString &roomTopic)
-{
-    initialName_  = roomName;
-    initialTopic_ = roomTopic;
-
-    nameInput_->setText(roomName);
-    topicInput_->setText(roomTopic);
-}
-
 RoomSettings::RoomSettings(QString roomid, QObject *parent)
   : QObject(parent)
   , roomid_{std::move(roomid)}
@@ -244,6 +97,18 @@ RoomSettings::roomTopic() const
                               .replace(QLatin1String("\n"), QLatin1String("<br>"))));
 }
 
+QString
+RoomSettings::plainRoomName() const
+{
+    return QString::fromStdString(info_.name);
+}
+
+QString
+RoomSettings::plainRoomTopic() const
+{
+    return QString::fromStdString(info_.topic);
+}
+
 QString
 RoomSettings::roomId() const
 {
@@ -340,12 +205,24 @@ RoomSettings::canChangeJoinRules() const
 }
 
 bool
-RoomSettings::canChangeNameAndTopic() const
+RoomSettings::canChangeName() const
 {
     try {
-        return cache::hasEnoughPowerLevel({EventType::RoomName, EventType::RoomTopic},
-                                          roomid_.toStdString(),
-                                          utils::localUser().toStdString());
+        return cache::hasEnoughPowerLevel(
+          {EventType::RoomName}, roomid_.toStdString(), utils::localUser().toStdString());
+    } catch (const lmdb::error &e) {
+        nhlog::db()->warn("lmdb error: {}", e.what());
+    }
+
+    return false;
+}
+
+bool
+RoomSettings::canChangeTopic() const
+{
+    try {
+        return cache::hasEnoughPowerLevel(
+          {EventType::RoomTopic}, roomid_.toStdString(), utils::localUser().toStdString());
     } catch (const lmdb::error &e) {
         nhlog::db()->warn("lmdb error: {}", e.what());
     }
@@ -387,26 +264,6 @@ RoomSettings::supportsRestricted() const
            info_.version != "6" && info_.version != "7";
 }
 
-void
-RoomSettings::openEditModal()
-{
-    retrieveRoomInfo();
-
-    auto modal = new EditModal(roomid_);
-    modal->setFields(QString::fromStdString(info_.name), QString::fromStdString(info_.topic));
-    modal->raise();
-    modal->show();
-    connect(modal, &EditModal::nameChanged, this, [this](const QString &newName) {
-        info_.name = newName.toStdString();
-        emit roomNameChanged();
-    });
-
-    connect(modal, &EditModal::topicChanged, this, [this](const QString &newTopic) {
-        info_.topic = newTopic.toStdString();
-        emit roomTopicChanged();
-    });
-}
-
 void
 RoomSettings::changeNotifications(int currentIndex)
 {
@@ -502,6 +359,74 @@ RoomSettings::changeAccessRules(int index)
     updateAccessRules(roomid_.toStdString(), join_rule, guest_access);
 }
 
+void
+RoomSettings::changeName(QString name)
+{
+    // Check if the values are changed from the originals.
+    auto newName = name.trimmed().toStdString();
+
+    if (newName == info_.name) {
+        return;
+    }
+
+    using namespace mtx::events;
+    auto proxy = std::make_shared<ThreadProxy>();
+    connect(proxy.get(), &ThreadProxy::nameEventSent, this, [this](QString newRoomName) {
+        this->info_.name = newRoomName.toStdString();
+        emit roomNameChanged();
+    });
+    connect(proxy.get(), &ThreadProxy::error, this, &RoomSettings::displayError);
+
+    state::Name body;
+    body.name = newName;
+
+    http::client()->send_state_event(
+      roomid_.toStdString(),
+      body,
+      [proxy, newName](const mtx::responses::EventId &, mtx::http::RequestErr err) {
+          if (err) {
+              emit proxy->error(QString::fromStdString(err->matrix_error.error));
+              return;
+          }
+
+          emit proxy->nameEventSent(QString::fromStdString(newName));
+      });
+}
+
+void
+RoomSettings::changeTopic(QString topic)
+{
+    // Check if the values are changed from the originals.
+    auto newTopic = topic.trimmed().toStdString();
+
+    if (newTopic == info_.topic) {
+        return;
+    }
+
+    using namespace mtx::events;
+    auto proxy = std::make_shared<ThreadProxy>();
+    connect(proxy.get(), &ThreadProxy::topicEventSent, this, [this](QString newRoomTopic) {
+        this->info_.topic = newRoomTopic.toStdString();
+        emit roomTopicChanged();
+    });
+    connect(proxy.get(), &ThreadProxy::error, this, &RoomSettings::displayError);
+
+    state::Topic body;
+    body.topic = newTopic;
+
+    http::client()->send_state_event(
+      roomid_.toStdString(),
+      body,
+      [proxy, newTopic](const mtx::responses::EventId &, mtx::http::RequestErr err) {
+          if (err) {
+              emit proxy->error(QString::fromStdString(err->matrix_error.error));
+              return;
+          }
+
+          emit proxy->topicEventSent(QString::fromStdString(newTopic));
+      });
+}
+
 void
 RoomSettings::updateAccessRules(const std::string &room_id,
                                 const mtx::events::state::JoinRules &join_rule,
diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h
index f79aa3f76..9912cfd6b 100644
--- a/src/ui/RoomSettings.h
+++ b/src/ui/RoomSettings.h
@@ -5,9 +5,7 @@
 
 #pragma once
 
-#include <QLabel>
 #include <QObject>
-#include <QPushButton>
 #include <QSet>
 #include <QString>
 
@@ -16,8 +14,6 @@
 
 #include "CacheStructs.h"
 
-class TextField;
-
 /// Convenience class which connects events emmited from threads
 /// outside of main with the UI code.
 class ThreadProxy : public QObject
@@ -31,40 +27,6 @@ signals:
     void stopLoading();
 };
 
-class EditModal : public QWidget
-{
-    Q_OBJECT
-
-public:
-    EditModal(const QString &roomId, QWidget *parent = nullptr);
-
-    void setFields(const QString &roomName, const QString &roomTopic);
-
-signals:
-    void nameChanged(const QString &roomName);
-    void topicChanged(const QString &topic);
-
-private slots:
-    void topicEventSent(const QString &topic);
-    void nameEventSent(const QString &name);
-    void error(const QString &msg);
-
-    void applyClicked();
-
-private:
-    QString roomId_;
-    QString initialName_;
-    QString initialTopic_;
-
-    QLabel *errorField_;
-
-    TextField *nameInput_;
-    TextField *topicInput_;
-
-    QPushButton *applyBtn_;
-    QPushButton *cancelBtn_;
-};
-
 class RoomSettings : public QObject
 {
     Q_OBJECT
@@ -72,6 +34,8 @@ class RoomSettings : public QObject
     Q_PROPERTY(QString roomVersion READ roomVersion CONSTANT)
     Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
     Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged)
+    Q_PROPERTY(QString plainRoomName READ plainRoomName NOTIFY roomNameChanged)
+    Q_PROPERTY(QString plainRoomTopic READ plainRoomTopic NOTIFY roomTopicChanged)
     Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY avatarUrlChanged)
     Q_PROPERTY(int memberCount READ memberCount CONSTANT)
     Q_PROPERTY(int notifications READ notifications NOTIFY notificationsChanged)
@@ -79,7 +43,8 @@ class RoomSettings : public QObject
     Q_PROPERTY(bool isLoading READ isLoading NOTIFY loadingChanged)
     Q_PROPERTY(bool canChangeAvatar READ canChangeAvatar CONSTANT)
     Q_PROPERTY(bool canChangeJoinRules READ canChangeJoinRules CONSTANT)
-    Q_PROPERTY(bool canChangeNameAndTopic READ canChangeNameAndTopic CONSTANT)
+    Q_PROPERTY(bool canChangeName READ canChangeName CONSTANT)
+    Q_PROPERTY(bool canChangeTopic READ canChangeTopic CONSTANT)
     Q_PROPERTY(bool isEncryptionEnabled READ isEncryptionEnabled NOTIFY encryptionChanged)
     Q_PROPERTY(bool supportsKnocking READ supportsKnocking CONSTANT)
     Q_PROPERTY(bool supportsRestricted READ supportsRestricted CONSTANT)
@@ -90,6 +55,8 @@ public:
     QString roomId() const;
     QString roomName() const;
     QString roomTopic() const;
+    QString plainRoomName() const;
+    QString plainRoomTopic() const;
     QString roomVersion() const;
     QString roomAvatarUrl();
     int memberCount() const;
@@ -98,8 +65,10 @@ public:
     bool isLoading() const;
     //! Whether the user has enough power level to send m.room.join_rules events.
     bool canChangeJoinRules() const;
-    //! Whether the user has enough power level to send m.room.name & m.room.topic events.
-    bool canChangeNameAndTopic() const;
+    //! Whether the user has enough power level to send m.room.name.
+    bool canChangeName() const;
+    //! Whether the user has enough power level to send m.room.topic events.
+    bool canChangeTopic() const;
     //! Whether the user has enough power level to send m.room.avatar event.
     bool canChangeAvatar() const;
     bool isEncryptionEnabled() const;
@@ -108,9 +77,10 @@ public:
 
     Q_INVOKABLE void enableEncryption();
     Q_INVOKABLE void updateAvatar();
-    Q_INVOKABLE void openEditModal();
     Q_INVOKABLE void changeAccessRules(int index);
     Q_INVOKABLE void changeNotifications(int currentIndex);
+    Q_INVOKABLE void changeTopic(QString topic);
+    Q_INVOKABLE void changeName(QString name);
 
 signals:
     void loadingChanged();
diff --git a/src/ui/TextField.cpp b/src/ui/TextField.cpp
deleted file mode 100644
index f09237a0b..000000000
--- a/src/ui/TextField.cpp
+++ /dev/null
@@ -1,382 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-// SPDX-FileCopyrightText: 2022 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include "TextField.h"
-
-#include <QCoreApplication>
-#include <QEventTransition>
-#include <QFontDatabase>
-#include <QPaintEvent>
-#include <QPainter>
-#include <QPropertyAnimation>
-#include <QRegularExpressionValidator>
-
-TextField::TextField(QWidget *parent)
-  : QLineEdit(parent)
-{
-    // Get rid of the focus border on macOS.
-    setAttribute(Qt::WA_MacShowFocusRect, 0);
-
-    QPalette pal;
-
-    state_machine_    = new TextFieldStateMachine(this);
-    label_            = nullptr;
-    label_font_size_  = 15;
-    show_label_       = false;
-    background_color_ = pal.color(QPalette::Window);
-    is_valid_         = true;
-
-    setFrame(false);
-    setAttribute(Qt::WA_Hover);
-    setMouseTracking(true);
-    setTextMargins(0, 4, 0, 6);
-
-    state_machine_->start();
-    QCoreApplication::processEvents();
-}
-
-void
-TextField::setBackgroundColor(const QColor &color)
-{
-    background_color_ = color;
-    emit backgroundColorChanged();
-}
-
-QColor
-TextField::backgroundColor() const
-{
-    return background_color_;
-}
-
-void
-TextField::setShowLabel(bool value)
-{
-    if (show_label_ == value) {
-        return;
-    }
-
-    show_label_ = value;
-
-    if (!label_ && value) {
-        label_ = new TextFieldLabel(this);
-        state_machine_->setLabel(label_);
-    }
-
-    if (value) {
-        setContentsMargins(0, 23, 0, 0);
-    } else {
-        setContentsMargins(0, 0, 0, 0);
-    }
-}
-
-bool
-TextField::hasLabel() const
-{
-    return show_label_;
-}
-
-void
-TextField::setValid(bool valid)
-{
-    is_valid_ = valid;
-}
-
-bool
-TextField::isValid() const
-{
-    QString s = text();
-    int pos   = 0;
-    if (regexp_.pattern().isEmpty()) {
-        return is_valid_;
-    }
-    QRegularExpressionValidator v(QRegularExpression(regexp_), 0);
-    return v.validate(s, pos) == QValidator::Acceptable;
-}
-
-void
-TextField::setLabelFontSize(qreal size)
-{
-    label_font_size_ = size;
-
-    if (label_) {
-        QFont font(label_->font());
-        font.setPointSizeF(size);
-        label_->setFont(font);
-        label_->update();
-    }
-}
-
-qreal
-TextField::labelFontSize() const
-{
-    return label_font_size_;
-}
-
-void
-TextField::setLabel(const QString &label)
-{
-    label_text_ = label;
-    setShowLabel(true);
-    label_->update();
-}
-
-QString
-TextField::label() const
-{
-    return label_text_;
-}
-
-void
-TextField::setLabelColor(const QColor &color)
-{
-    label_color_ = color;
-    emit labelColorChanged();
-    update();
-}
-
-QColor
-TextField::labelColor() const
-{
-    if (!label_color_.isValid()) {
-        return QPalette().color(QPalette::Text);
-    }
-
-    return label_color_;
-}
-
-void
-TextField::setInkColor(const QColor &color)
-{
-    ink_color_ = color;
-    emit inkColorChanged();
-    update();
-}
-
-QColor
-TextField::inkColor() const
-{
-    if (!ink_color_.isValid()) {
-        return QPalette().color(QPalette::Text);
-    }
-
-    return ink_color_;
-}
-
-void
-TextField::setUnderlineColor(const QColor &color)
-{
-    underline_color_ = color;
-    emit underlineColorChanged();
-    update();
-}
-
-void
-TextField::setRegexp(const QRegularExpression &regexp)
-{
-    regexp_ = regexp;
-}
-
-QColor
-TextField::underlineColor() const
-{
-    if (!underline_color_.isValid()) {
-        if ((hasAcceptableInput() && isValid()) || !isModified())
-            return QPalette().color(QPalette::Highlight);
-        else
-            return Qt::red;
-    }
-
-    return underline_color_;
-}
-
-bool
-TextField::event(QEvent *event)
-{
-    switch (event->type()) {
-    case QEvent::Resize:
-    case QEvent::Move: {
-        if (label_)
-            label_->setGeometry(rect());
-        break;
-    }
-    default:
-        break;
-    }
-
-    return QLineEdit::event(event);
-}
-
-void
-TextField::paintEvent(QPaintEvent *event)
-{
-    QLineEdit::paintEvent(event);
-
-    QPainter painter(this);
-
-    if (text().isEmpty()) {
-        painter.setOpacity(1 - state_machine_->progress());
-        painter.fillRect(rect(), backgroundColor());
-    }
-
-    const int y  = height() - 1;
-    const int wd = width() - 5;
-
-    QPen pen;
-    pen.setWidth(1);
-    pen.setColor(underlineColor());
-    painter.setPen(pen);
-    painter.setOpacity(1);
-    painter.drawLine(2, y, wd, y);
-
-    QBrush brush;
-    brush.setStyle(Qt::SolidPattern);
-    brush.setColor(inkColor());
-
-    const qreal progress = state_machine_->progress();
-
-    if (progress > 0) {
-        painter.setPen(Qt::NoPen);
-        painter.setBrush(brush);
-        const int w = (1 - progress) * static_cast<qreal>(wd / 2);
-        painter.drawRect(w + 2.5, height() - 2, wd - 2 * w, 2);
-    }
-}
-
-TextFieldStateMachine::TextFieldStateMachine(TextField *parent)
-  : QStateMachine(parent)
-  , text_field_(parent)
-{
-    normal_state_  = new QState;
-    focused_state_ = new QState;
-
-    label_       = nullptr;
-    offset_anim_ = nullptr;
-    color_anim_  = nullptr;
-    progress_    = 0.0;
-
-    addState(normal_state_);
-    addState(focused_state_);
-
-    setInitialState(normal_state_);
-
-    QEventTransition *transition;
-    QPropertyAnimation *animation;
-
-    transition = new QEventTransition(parent, QEvent::FocusIn);
-    transition->setTargetState(focused_state_);
-    normal_state_->addTransition(transition);
-
-    animation = new QPropertyAnimation(this, "progress", this);
-    animation->setEasingCurve(QEasingCurve::InCubic);
-    animation->setDuration(310);
-    transition->addAnimation(animation);
-
-    transition = new QEventTransition(parent, QEvent::FocusOut);
-    transition->setTargetState(normal_state_);
-    focused_state_->addTransition(transition);
-
-    animation = new QPropertyAnimation(this, "progress", this);
-    animation->setEasingCurve(QEasingCurve::OutCubic);
-    animation->setDuration(310);
-    transition->addAnimation(animation);
-
-    normal_state_->assignProperty(this, "progress", 0);
-    focused_state_->assignProperty(this, "progress", 1);
-
-    setupProperties();
-
-    connect(text_field_, SIGNAL(textChanged(QString)), this, SLOT(setupProperties()));
-}
-
-void
-TextFieldStateMachine::setLabel(TextFieldLabel *label)
-{
-    if (label_) {
-        delete label_;
-    }
-
-    if (offset_anim_) {
-        removeDefaultAnimation(offset_anim_);
-        delete offset_anim_;
-    }
-
-    if (color_anim_) {
-        removeDefaultAnimation(color_anim_);
-        delete color_anim_;
-    }
-
-    label_ = label;
-
-    if (label_) {
-        offset_anim_ = new QPropertyAnimation(label_, "offset", this);
-        offset_anim_->setDuration(210);
-        offset_anim_->setEasingCurve(QEasingCurve::OutCubic);
-        addDefaultAnimation(offset_anim_);
-
-        color_anim_ = new QPropertyAnimation(label_, "color", this);
-        color_anim_->setDuration(210);
-        addDefaultAnimation(color_anim_);
-    }
-
-    setupProperties();
-}
-
-void
-TextFieldStateMachine::setupProperties()
-{
-    if (label_) {
-        const int m = text_field_->textMargins().top();
-
-        if (text_field_->text().isEmpty()) {
-            normal_state_->assignProperty(label_, "offset", QPointF(0, 26));
-        } else {
-            normal_state_->assignProperty(label_, "offset", QPointF(0, 0 - m));
-        }
-
-        focused_state_->assignProperty(label_, "offset", QPointF(0, 0 - m));
-        focused_state_->assignProperty(label_, "color", text_field_->inkColor());
-        normal_state_->assignProperty(label_, "color", text_field_->labelColor());
-
-        if (0 != label_->offset().y() && !text_field_->text().isEmpty()) {
-            label_->setOffset(QPointF(0, 0 - m));
-        } else if (!text_field_->hasFocus() && label_->offset().y() <= 0 &&
-                   text_field_->text().isEmpty()) {
-            label_->setOffset(QPointF(0, 26));
-        }
-    }
-
-    text_field_->update();
-}
-
-TextFieldLabel::TextFieldLabel(TextField *parent)
-  : QWidget(parent)
-  , text_field_(parent)
-{
-    x_     = 0;
-    y_     = 26;
-    scale_ = 1;
-    color_ = parent->labelColor();
-
-    QFont font;
-    font.setWeight(60);
-    font.setLetterSpacing(QFont::PercentageSpacing, 102);
-    setFont(font);
-}
-
-void
-TextFieldLabel::paintEvent(QPaintEvent *)
-{
-    if (!text_field_->hasLabel())
-        return;
-
-    QPainter painter(this);
-    painter.setRenderHint(QPainter::Antialiasing);
-    painter.scale(scale_, scale_);
-    painter.setPen(color_);
-    painter.setOpacity(1);
-
-    QPointF pos(2 + x_, height() - 36 + y_);
-    painter.drawText(pos.x(), pos.y(), text_field_->label());
-}
diff --git a/src/ui/TextField.h b/src/ui/TextField.h
deleted file mode 100644
index 6bc5c1608..000000000
--- a/src/ui/TextField.h
+++ /dev/null
@@ -1,201 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-// SPDX-FileCopyrightText: 2022 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#pragma once
-
-#include <QColor>
-#include <QLineEdit>
-#include <QPaintEvent>
-#include <QPropertyAnimation>
-#include <QRegularExpression>
-#include <QStateMachine>
-#include <QtGlobal>
-
-class TextField;
-class TextFieldLabel;
-class TextFieldStateMachine;
-
-class TextField : public QLineEdit
-{
-    Q_OBJECT
-
-    Q_PROPERTY(QColor inkColor WRITE setInkColor READ inkColor NOTIFY inkColorChanged)
-    Q_PROPERTY(QColor labelColor WRITE setLabelColor READ labelColor NOTIFY labelColorChanged)
-    Q_PROPERTY(QColor underlineColor WRITE setUnderlineColor READ underlineColor NOTIFY
-                 underlineColorChanged)
-    Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor NOTIFY
-                 backgroundColorChanged)
-
-public:
-    explicit TextField(QWidget *parent = nullptr);
-
-    void setInkColor(const QColor &color);
-    void setBackgroundColor(const QColor &color);
-    void setLabel(const QString &label);
-    void setLabelColor(const QColor &color);
-    void setLabelFontSize(qreal size);
-    void setShowLabel(bool value);
-    void setUnderlineColor(const QColor &color);
-    void setRegexp(const QRegularExpression &regexp);
-    void setValid(bool valid);
-
-    QColor inkColor() const;
-    QColor labelColor() const;
-    QColor underlineColor() const;
-    QColor backgroundColor() const;
-    QString label() const;
-    bool hasLabel() const;
-    bool isValid() const;
-    qreal labelFontSize() const;
-
-protected:
-    bool event(QEvent *event) override;
-    void paintEvent(QPaintEvent *event) override;
-
-signals:
-    void inkColorChanged();
-    void labelColorChanged();
-    void underlineColorChanged();
-    void backgroundColorChanged();
-
-private:
-    void init();
-
-    QColor ink_color_;
-    QColor background_color_;
-    QColor label_color_;
-    QColor underline_color_;
-    QString label_text_;
-    TextFieldLabel *label_;
-    TextFieldStateMachine *state_machine_;
-    bool show_label_;
-    QRegularExpression regexp_;
-    bool is_valid_;
-    qreal label_font_size_;
-};
-
-class TextFieldLabel : public QWidget
-{
-    Q_OBJECT
-
-    Q_PROPERTY(qreal scale WRITE setScale READ scale NOTIFY scaleChanged)
-    Q_PROPERTY(QPointF offset WRITE setOffset READ offset NOTIFY offsetChanged)
-    Q_PROPERTY(QColor color WRITE setColor READ color NOTIFY colorChanged)
-
-public:
-    TextFieldLabel(TextField *parent);
-
-    inline void setColor(const QColor &color);
-    inline void setOffset(QPointF pos);
-    inline void setScale(qreal scale);
-
-    inline QColor color() const;
-    inline QPointF offset() const;
-    inline qreal scale() const;
-
-protected:
-    void paintEvent(QPaintEvent *event) override;
-
-signals:
-    void scaleChanged();
-    void offsetChanged();
-    void colorChanged();
-
-private:
-    TextField *const text_field_;
-
-    QColor color_;
-    qreal scale_;
-    qreal x_;
-    qreal y_;
-};
-
-inline void
-TextFieldLabel::setColor(const QColor &color)
-{
-    color_ = color;
-    emit colorChanged();
-    update();
-}
-
-inline void
-TextFieldLabel::setOffset(QPointF pos)
-{
-    x_ = pos.x();
-    y_ = pos.y();
-    emit offsetChanged();
-    update();
-}
-
-inline void
-TextFieldLabel::setScale(qreal scale)
-{
-    scale_ = scale;
-    emit scaleChanged();
-    update();
-}
-
-inline QPointF
-TextFieldLabel::offset() const
-{
-    return QPointF(x_, y_);
-}
-inline qreal
-TextFieldLabel::scale() const
-{
-    return scale_;
-}
-inline QColor
-TextFieldLabel::color() const
-{
-    return color_;
-}
-
-class TextFieldStateMachine : public QStateMachine
-{
-    Q_OBJECT
-
-    Q_PROPERTY(qreal progress WRITE setProgress READ progress NOTIFY progressChanged)
-
-public:
-    TextFieldStateMachine(TextField *parent);
-
-    inline void setProgress(qreal progress);
-    void setLabel(TextFieldLabel *label);
-
-    inline qreal progress() const;
-
-public slots:
-    void setupProperties();
-
-signals:
-    void progressChanged();
-
-private:
-    QPropertyAnimation *color_anim_;
-    QPropertyAnimation *offset_anim_;
-
-    QState *focused_state_;
-    QState *normal_state_;
-
-    TextField *text_field_;
-    TextFieldLabel *label_;
-
-    qreal progress_;
-};
-
-inline void
-TextFieldStateMachine::setProgress(qreal progress)
-{
-    progress_ = progress;
-    emit progressChanged();
-    text_field_->update();
-}
-
-inline qreal
-TextFieldStateMachine::progress() const
-{
-    return progress_;
-}
diff --git a/src/ui/Theme.h b/src/ui/Theme.h
index 2ad781c66..2c85c61e1 100644
--- a/src/ui/Theme.h
+++ b/src/ui/Theme.h
@@ -8,54 +8,6 @@
 #include <QColor>
 #include <QPalette>
 
-namespace ui {
-// Default font size.
-const int FontSize = 16;
-
-// Default avatar size. Width and height.
-const int AvatarSize = 40;
-
-enum class ButtonPreset
-{
-    FlatPreset,
-    CheckablePreset
-};
-
-enum class RippleStyle
-{
-    CenteredRipple,
-    PositionedRipple,
-    NoRipple
-};
-
-enum class OverlayStyle
-{
-    NoOverlay,
-    TintedOverlay,
-    GrayOverlay
-};
-
-enum class Role
-{
-    Default,
-    Primary,
-    Secondary
-};
-
-enum class ButtonIconPlacement
-{
-    LeftIcon,
-    RightIcon
-};
-
-enum class ProgressType
-{
-    DeterminateProgress,
-    IndeterminateProgress
-};
-
-} // namespace ui
-
 class Theme : public QPalette
 {
     Q_GADGET
diff --git a/src/ui/ThemeManager.cpp b/src/ui/ThemeManager.cpp
index d9b599ffc..e275fa906 100644
--- a/src/ui/ThemeManager.cpp
+++ b/src/ui/ThemeManager.cpp
@@ -3,8 +3,6 @@
 //
 // SPDX-License-Identifier: GPL-3.0-or-later
 
-#include <QFontDatabase>
-
 #include "ThemeManager.h"
 
 QColor
-- 
GitLab