summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--resources/qml/TimelineView.qml6
-rw-r--r--src/ChatPage.cpp37
-rw-r--r--src/ChatPage.h6
-rw-r--r--src/TextInputWidget.cpp69
-rw-r--r--src/TextInputWidget.h27
-rw-r--r--src/notifications/ManagerLinux.cpp36
-rw-r--r--src/notifications/ManagerWin.cpp1
-rw-r--r--src/popups/ReplyPopup.cpp103
-rw-r--r--src/popups/ReplyPopup.h44
-rw-r--r--src/timeline/TimelineModel.cpp13
-rw-r--r--src/timeline/TimelineModel.h21
-rw-r--r--src/timeline/TimelineViewManager.cpp76
-rw-r--r--src/timeline/TimelineViewManager.h30
14 files changed, 135 insertions, 336 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9742c48e..6f769a26 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -303,7 +303,6 @@ set(SRC_FILES
 	src/Utils.cpp
 	src/WelcomePage.cpp
 	src/popups/PopupItem.cpp
-	src/popups/ReplyPopup.cpp
 	src/popups/SuggestionsPopup.cpp
 	src/popups/UserMentions.cpp
 	src/main.cpp
@@ -501,7 +500,6 @@ qt5_wrap_cpp(MOC_HEADERS
 	src/UserSettingsPage.h
 	src/WelcomePage.h
 	src/popups/PopupItem.h
-	src/popups/ReplyPopup.h
 	src/popups/SuggestionsPopup.h
 	src/popups/UserMentions.h
 	)
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 5b051235..86b511b2 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -285,7 +285,7 @@ Page {
 
 					id: replyPopup
 
-					visible: timelineManager.replyingEvent && chat.model
+					visible: chat.model && chat.model.reply
 					// Height of child, plus margins, plus border
 					height: replyPreview.height + 10
 					color: colors.base
@@ -300,7 +300,7 @@ Page {
 						anchors.rightMargin: 20
 						anchors.bottom: parent.bottom
 
-						modelData: chat.model ? chat.model.getDump(timelineManager.replyingEvent) : {}
+						modelData: chat.model ? chat.model.getDump(chat.model.reply) : {}
 						userColor: timelineManager.userColor(modelData.userId, colors.window)
 					}
 
@@ -318,7 +318,7 @@ Page {
 						ToolTip.visible: closeReplyButton.hovered
 						ToolTip.text: qsTr("Close")
 
-						onClicked: timelineManager.closeReply()
+						onClicked: chat.model.reply = undefined
 					}
 				}
 			}
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index d7d11a12..c9afeb75 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -303,10 +303,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
           text_input_,
           &TextInputWidget::uploadMedia,
           this,
-          [this](QSharedPointer<QIODevice> dev,
-                 QString mimeClass,
-                 const QString &fn,
-                 const std::optional<RelatedInfo> &related) {
+          [this](QSharedPointer<QIODevice> dev, QString mimeClass, const QString &fn) {
                   if (!dev->open(QIODevice::ReadOnly)) {
                           emit uploadFailed(
                             QString("Error while reading media: %1").arg(dev->errorString()));
@@ -358,8 +355,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
                      mime = mime.name(),
                      size = payload.size(),
                      dimensions,
-                     blurhash,
-                     related](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) {
+                     blurhash](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) {
                             if (err) {
                                     emit uploadFailed(
                                       tr("Failed to upload media. Please try again."));
@@ -378,8 +374,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
                                                mime,
                                                size,
                                                dimensions,
-                                               blurhash,
-                                               related);
+                                               blurhash);
                     });
           });
 
@@ -398,8 +393,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
                        QString mime,
                        qint64 dsize,
                        QSize dimensions,
-                       QString blurhash,
-                       const std::optional<RelatedInfo> &related) {
+                       QString blurhash) {
                         text_input_->hideUploadSpinner();
 
                         if (encryptedFile)
@@ -413,17 +407,16 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
                                                                  mime,
                                                                  dsize,
                                                                  dimensions,
-                                                                 blurhash,
-                                                                 related);
+                                                                 blurhash);
                         else if (mimeClass == "audio")
                                 view_manager_->queueAudioMessage(
-                                  roomid, filename, encryptedFile, url, mime, dsize, related);
+                                  roomid, filename, encryptedFile, url, mime, dsize);
                         else if (mimeClass == "video")
                                 view_manager_->queueVideoMessage(
-                                  roomid, filename, encryptedFile, url, mime, dsize, related);
+                                  roomid, filename, encryptedFile, url, mime, dsize);
                         else
                                 view_manager_->queueFileMessage(
-                                  roomid, filename, encryptedFile, url, mime, dsize, related);
+                                  roomid, filename, encryptedFile, url, mime, dsize);
                 });
 
         connect(room_list_, &RoomList::roomAvatarChanged, this, &ChatPage::updateTopBarAvatar);
@@ -548,14 +541,6 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
         });
 
         connect(this, &ChatPage::dropToLoginPageCb, this, &ChatPage::dropToLoginPage);
-        connect(this, &ChatPage::messageReply, text_input_, &TextInputWidget::addReply);
-        connect(this, &ChatPage::messageReply, this, [this](const RelatedInfo &related) {
-                view_manager_->updateReplyingEvent(QString::fromStdString(related.related_event));
-        });
-        connect(view_manager_,
-                &TimelineViewManager::replyClosed,
-                text_input_,
-                &TextInputWidget::closeReplyPopup);
 
         instance_ = this;
 }
@@ -597,6 +582,12 @@ ChatPage::resetUI()
 }
 
 void
+ChatPage::focusMessageInput()
+{
+        this->text_input_->focusLineEdit();
+}
+
+void
 ChatPage::deleteConfigs()
 {
         QSettings settings;
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 02c19ba7..5182ab99 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -85,6 +85,7 @@ public:
         //! Show the room/group list (if it was visible).
         void showSideBars();
         void initiateLogout();
+        void focusMessageInput();
 
 public slots:
         void leaveRoom(const QString &room_id);
@@ -99,8 +100,6 @@ signals:
         void connectionLost();
         void connectionRestored();
 
-        void messageReply(const RelatedInfo &related);
-
         void notificationsRetrieved(const mtx::responses::Notifications &);
         void highlightedNotifsRetrieved(const mtx::responses::Notifications &,
                                         const QPoint widgetPos);
@@ -114,8 +113,7 @@ signals:
                            const QString &mime,
                            qint64 dsize,
                            const QSize &dimensions,
-                           const QString &blurhash,
-                           const std::optional<RelatedInfo> &related);
+                           const QString &blurhash);
 
         void contentLoaded();
         void closing();
diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp
index 11f7ddda..af5c278e 100644
--- a/src/TextInputWidget.cpp
+++ b/src/TextInputWidget.cpp
@@ -46,7 +46,6 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent)
   : QTextEdit{parent}
   , history_index_{0}
   , suggestionsPopup_{parent}
-  , replyPopup_{parent}
   , previewDialog_{parent}
 {
         setFrameStyle(QFrame::NoFrame);
@@ -73,10 +72,6 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent)
                 &FilteredTextEdit::uploadData);
 
         connect(this, &FilteredTextEdit::resultsRetrieved, this, &FilteredTextEdit::showResults);
-        connect(&replyPopup_, &ReplyPopup::userSelected, this, [](const QString &text) {
-                // TODO: Show user avatar window.
-                nhlog::ui()->info("User selected: " + text.toStdString());
-        });
         connect(
           &suggestionsPopup_, &SuggestionsPopup::itemSelected, this, [this](const QString &text) {
                   suggestionsPopup_.hide();
@@ -90,8 +85,6 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent)
                   cursor.insertText(text);
           });
 
-        connect(&replyPopup_, &ReplyPopup::cancel, this, [this]() { closeReply(); });
-
         // For cycling through the suggestions by hitting tab.
         connect(this,
                 &FilteredTextEdit::selectNextSuggestion,
@@ -174,17 +167,6 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event)
                 }
         }
 
-        if (replyPopup_.isVisible()) {
-                switch (event->key()) {
-                case Qt::Key_Escape:
-                        closeReply();
-                        return;
-
-                default:
-                        break;
-                }
-        }
-
         switch (event->key()) {
         case Qt::Key_At:
                 atTriggerPosition_ = textCursor().position();
@@ -218,7 +200,6 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event)
                 if (!(event->modifiers() & Qt::ShiftModifier)) {
                         stopTyping();
                         submit();
-                        closeReply();
                 } else {
                         QTextEdit::keyPressEvent(event);
                 }
@@ -416,31 +397,18 @@ FilteredTextEdit::submit()
                 auto name = text.mid(1, command_end - 1);
                 auto args = text.mid(command_end + 1);
                 if (name.isEmpty() || name == "/") {
-                        message(args, related);
+                        message(args);
                 } else {
                         command(name, args);
                 }
         } else {
-                message(std::move(text), std::move(related));
+                message(std::move(text));
         }
 
-        related = {};
-
         clear();
 }
 
 void
-FilteredTextEdit::showReplyPopup(const RelatedInfo &related_)
-{
-        QPoint pos = viewport()->mapToGlobal(this->pos());
-
-        replyPopup_.setReplyContent(related_);
-        replyPopup_.move(pos.x(), pos.y() - replyPopup_.height() - 10);
-        replyPopup_.setFixedWidth(this->parentWidget()->width());
-        replyPopup_.show();
-}
-
-void
 FilteredTextEdit::textChanged()
 {
         working_history_[history_index_] = toPlainText();
@@ -456,9 +424,7 @@ FilteredTextEdit::uploadData(const QByteArray data,
 
         emit startedUpload();
 
-        emit media(buffer, mediaType, filename, related);
-        related = {};
-        closeReply();
+        emit media(buffer, mediaType, filename);
 }
 
 void
@@ -599,7 +565,7 @@ void
 TextInputWidget::command(QString command, QString args)
 {
         if (command == "me") {
-                sendEmoteMessage(args, input_->related);
+                sendEmoteMessage(args);
         } else if (command == "join") {
                 sendJoinRoomRequest(args);
         } else if (command == "invite") {
@@ -611,16 +577,14 @@ TextInputWidget::command(QString command, QString args)
         } else if (command == "unban") {
                 sendUnbanRoomRequest(args.section(' ', 0, 0), args.section(' ', 1, -1));
         } else if (command == "shrug") {
-                sendTextMessage("¯\\_(ツ)_/¯", input_->related);
+                sendTextMessage("¯\\_(ツ)_/¯");
         } else if (command == "fliptable") {
-                sendTextMessage("(╯°□°)╯︵ ┻━┻", input_->related);
+                sendTextMessage("(╯°□°)╯︵ ┻━┻");
         } else if (command == "unfliptable") {
-                sendTextMessage(" ┯━┯╭( º _ º╭)", input_->related);
+                sendTextMessage(" ┯━┯╭( º _ º╭)");
         } else if (command == "sovietflip") {
-                sendTextMessage("ノ┬─┬ノ ︵ ( \\o°o)\\", input_->related);
+                sendTextMessage("ノ┬─┬ノ ︵ ( \\o°o)\\");
         }
-
-        input_->related = std::nullopt;
 }
 
 void
@@ -640,9 +604,7 @@ TextInputWidget::openFileSelection()
 
         QSharedPointer<QFile> file{new QFile{fileName, this}};
 
-        emit uploadMedia(file, format, QFileInfo(fileName).fileName(), input_->related);
-        input_->related = {};
-        input_->closeReply();
+        emit uploadMedia(file, format, QFileInfo(fileName).fileName());
 
         showUploadSpinner();
 }
@@ -687,16 +649,3 @@ TextInputWidget::paintEvent(QPaintEvent *)
 
         style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
 }
-
-void
-TextInputWidget::addReply(const RelatedInfo &related)
-{
-        // input_->setText(QString("> %1: %2\n\n").arg(username).arg(msg));
-        input_->setFocus();
-
-        // input_->showReplyPopup(related);
-        auto cursor = input_->textCursor();
-        cursor.movePosition(QTextCursor::End);
-        input_->setTextCursor(cursor);
-        input_->related = related;
-}
diff --git a/src/TextInputWidget.h b/src/TextInputWidget.h
index 77d77e44..addb61ec 100644
--- a/src/TextInputWidget.h
+++ b/src/TextInputWidget.h
@@ -28,7 +28,6 @@
 
 #include "dialogs/PreviewUploadOverlay.h"
 #include "emoji/PickButton.h"
-#include "popups/ReplyPopup.h"
 #include "popups/SuggestionsPopup.h"
 
 struct SearchResult;
@@ -49,27 +48,15 @@ public:
         QSize minimumSizeHint() const override;
 
         void submit();
-        void showReplyPopup(const RelatedInfo &related_);
-        void closeReply()
-        {
-                replyPopup_.hide();
-                related = {};
-        }
-
-        // Used for replies
-        std::optional<RelatedInfo> related;
 
 signals:
         void heightChanged(int height);
         void startedTyping();
         void stoppedTyping();
         void startedUpload();
-        void message(QString, const std::optional<RelatedInfo> &);
+        void message(QString msg);
         void command(QString name, QString args);
-        void media(QSharedPointer<QIODevice> data,
-                   QString mimeClass,
-                   const QString &filename,
-                   const std::optional<RelatedInfo> &related);
+        void media(QSharedPointer<QIODevice> data, QString mimeClass, const QString &filename);
 
         //! Trigger the suggestion popup.
         void showSuggestions(const QString &query);
@@ -97,7 +84,6 @@ private:
         QTimer *typingTimer_;
 
         SuggestionsPopup suggestionsPopup_;
-        ReplyPopup replyPopup_;
 
         enum class AnchorType
         {
@@ -163,21 +149,18 @@ public slots:
         void openFileSelection();
         void hideUploadSpinner();
         void focusLineEdit() { input_->setFocus(); }
-        void addReply(const RelatedInfo &related);
-        void closeReplyPopup() { input_->closeReply(); }
 
 private slots:
         void addSelectedEmoji(const QString &emoji);
 
 signals:
-        void sendTextMessage(const QString &msg, const std::optional<RelatedInfo> &related);
-        void sendEmoteMessage(QString msg, const std::optional<RelatedInfo> &related);
+        void sendTextMessage(const QString &msg);
+        void sendEmoteMessage(QString msg);
         void heightChanged(int height);
 
         void uploadMedia(const QSharedPointer<QIODevice> data,
                          QString mimeClass,
-                         const QString &filename,
-                         const std::optional<RelatedInfo> &related);
+                         const QString &filename);
 
         void sendJoinRoomRequest(const QString &room);
         void sendInviteRoomRequest(const QString &userid, const QString &reason);
diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp
index 8b7b41bb..b9eca1a8 100644
--- a/src/notifications/ManagerLinux.cpp
+++ b/src/notifications/ManagerLinux.cpp
@@ -97,24 +97,24 @@ NotificationsManager::closeNotification(uint id)
 
 void
 NotificationsManager::removeNotification(const QString &roomId, const QString &eventId)
-
-  roomEventId reId = {roomId, eventId};
-for (auto elem = notificationIds.begin(); elem != notificationIds.end(); ++elem) {
-        if (elem.value().roomId != roomId)
-                continue;
-
-        // close all notifications matching the eventId or having a lower
-        // notificationId
-        // This relies on the notificationId not wrapping around. This allows for
-        // approximately 2,147,483,647 notifications, so it is a bit unlikely.
-        // Otherwise we would need to store a 64bit counter instead.
-        closeNotification(elem.key());
-
-        // FIXME: compare index of event id of the read receipt and the notification instead of just
-        // the id to prevent read receipts of events without notification clearing all notifications
-        // in that room!
-        if (elem.value() == reId)
-                break;
+{
+        roomEventId reId = {roomId, eventId};
+        for (auto elem = notificationIds.begin(); elem != notificationIds.end(); ++elem) {
+                if (elem.value().roomId != roomId)
+                        continue;
+
+                // close all notifications matching the eventId or having a lower
+                // notificationId
+                // This relies on the notificationId not wrapping around. This allows for
+                // approximately 2,147,483,647 notifications, so it is a bit unlikely.
+                // Otherwise we would need to store a 64bit counter instead.
+                closeNotification(elem.key());
+
+                // FIXME: compare index of event id of the read receipt and the notification instead
+                // of just the id to prevent read receipts of events without notification clearing
+                // all notifications in that room!
+                if (elem.value() == reId)
+                        break;
         }
 }
 
diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp
index b00bac2e..5e209ee6 100644
--- a/src/notifications/ManagerWin.cpp
+++ b/src/notifications/ManagerWin.cpp
@@ -67,4 +67,3 @@ void NotificationsManager::notificationClosed(uint, uint) {}
 void
 NotificationsManager::removeNotification(const QString &roomId, const QString &eventId)
 {}
-
diff --git a/src/popups/ReplyPopup.cpp b/src/popups/ReplyPopup.cpp
deleted file mode 100644
index 5058c039..00000000
--- a/src/popups/ReplyPopup.cpp
+++ /dev/null
@@ -1,103 +0,0 @@
-#include <QLabel>
-#include <QPaintEvent>
-#include <QPainter>
-#include <QStyleOption>
-
-#include "../Config.h"
-#include "../Utils.h"
-#include "../ui/Avatar.h"
-#include "../ui/DropShadow.h"
-#include "../ui/TextLabel.h"
-#include "PopupItem.h"
-#include "ReplyPopup.h"
-
-ReplyPopup::ReplyPopup(QWidget *parent)
-  : QWidget(parent)
-  , userItem_{nullptr}
-  , msgLabel_{nullptr}
-  , eventLabel_{nullptr}
-{
-        setAttribute(Qt::WA_ShowWithoutActivating, true);
-        setWindowFlags(Qt::ToolTip | Qt::NoDropShadowWindowHint);
-
-        mainLayout_ = new QVBoxLayout(this);
-        mainLayout_->setMargin(0);
-        mainLayout_->setSpacing(0);
-
-        topLayout_ = new QHBoxLayout();
-        topLayout_->setSpacing(0);
-        topLayout_->setContentsMargins(13, 1, 13, 0);
-
-        userItem_ = new UserItem(this);
-        connect(userItem_, &UserItem::clicked, this, &ReplyPopup::userSelected);
-        topLayout_->addWidget(userItem_);
-
-        buttonLayout_ = new QHBoxLayout();
-        buttonLayout_->setSpacing(0);
-        buttonLayout_->setMargin(0);
-
-        topLayout_->addLayout(buttonLayout_);
-        QFont f;
-        f.setPointSizeF(f.pointSizeF());
-        const int fontHeight = QFontMetrics(f).height();
-        buttonSize_          = std::min(fontHeight, 20);
-
-        closeBtn_ = new FlatButton(this);
-        closeBtn_->setToolTip(tr("Logout"));
-        closeBtn_->setCornerRadius(buttonSize_ / 4);
-        closeBtn_->setText("X");
-
-        QIcon icon;
-        icon.addFile(":/icons/icons/ui/remove-symbol.png");
-
-        closeBtn_->setIcon(icon);
-        closeBtn_->setIconSize(QSize(buttonSize_, buttonSize_));
-        connect(closeBtn_, &FlatButton::clicked, this, [this]() { emit cancel(); });
-
-        buttonLayout_->addWidget(closeBtn_);
-
-        topLayout_->addLayout(buttonLayout_);
-
-        mainLayout_->addLayout(topLayout_);
-        msgLabel_ = new TextLabel(this);
-        msgLabel_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
-        mainLayout_->addWidget(msgLabel_);
-        eventLabel_ = new QLabel(this);
-        mainLayout_->addWidget(eventLabel_);
-
-        setLayout(mainLayout_);
-}
-
-void
-ReplyPopup::setReplyContent(const RelatedInfo &related)
-{
-        // Update the current widget with the new data.
-        userItem_->updateItem(related.quoted_user);
-
-        msgLabel_->setText(utils::getFormattedQuoteBody(related, "")
-                             .replace("<mx-reply>", "")
-                             .replace("</mx-reply>", ""));
-
-        // eventLabel_->setText(srcEvent);
-
-        adjustSize();
-}
-
-void
-ReplyPopup::paintEvent(QPaintEvent *)
-{
-        QStyleOption opt;
-        opt.init(this);
-        QPainter p(this);
-        style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
-}
-
-void
-ReplyPopup::mousePressEvent(QMouseEvent *event)
-{
-        if (event->buttons() != Qt::RightButton) {
-                emit clicked(eventLabel_->text());
-        }
-
-        QWidget::mousePressEvent(event);
-}
diff --git a/src/popups/ReplyPopup.h b/src/popups/ReplyPopup.h
deleted file mode 100644
index 1fa3bb83..00000000
--- a/src/popups/ReplyPopup.h
+++ /dev/null
@@ -1,44 +0,0 @@
-#pragma once
-
-#include <QHBoxLayout>
-#include <QLabel>
-#include <QVBoxLayout>
-#include <QWidget>
-
-#include "../ui/FlatButton.h"
-#include "../ui/TextLabel.h"
-
-struct RelatedInfo;
-class UserItem;
-
-class ReplyPopup : public QWidget
-{
-        Q_OBJECT
-
-public:
-        explicit ReplyPopup(QWidget *parent = nullptr);
-
-public slots:
-        void setReplyContent(const RelatedInfo &related);
-
-protected:
-        void paintEvent(QPaintEvent *event) override;
-        void mousePressEvent(QMouseEvent *event) override;
-
-signals:
-        void userSelected(const QString &user);
-        void clicked(const QString &text);
-        void cancel();
-
-private:
-        QHBoxLayout *topLayout_;
-        QVBoxLayout *mainLayout_;
-        QHBoxLayout *buttonLayout_;
-
-        UserItem *userItem_;
-        FlatButton *closeBtn_;
-        TextLabel *msgLabel_;
-        QLabel *eventLabel_;
-
-        int buttonSize_;
-};
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 84dd9885..d54677f7 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -800,6 +800,16 @@ TimelineModel::decryptEvent(const mtx::events::EncryptedEvent<mtx::events::msg::
 void
 TimelineModel::replyAction(QString id)
 {
+        setReply(id);
+        ChatPage::instance()->focusMessageInput();
+}
+
+RelatedInfo
+TimelineModel::relatedInfo(QString id)
+{
+        if (!events.contains(id))
+                return {};
+
         auto event = events.value(id);
         if (auto e =
               std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
@@ -815,10 +825,9 @@ TimelineModel::replyAction(QString id)
         related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(event);
         related.quoted_formatted_body.remove(QRegularExpression(
           "<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption));
-        nhlog::ui()->debug("after replacement: {}", related.quoted_body.toStdString());
         related.room = room_id_;
 
-        ChatPage::instance()->messageReply(related);
+        return related;
 }
 
 void
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 02f5527a..0b181583 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -18,6 +18,7 @@ struct Timeline;
 struct Messages;
 struct ClaimKeys;
 }
+struct RelatedInfo;
 
 namespace qml_mtx_events {
 Q_NAMESPACE
@@ -124,6 +125,7 @@ class TimelineModel : public QAbstractListModel
           int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
         Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY
                      typingUsersChanged)
+        Q_PROPERTY(QString reply READ reply WRITE setReply NOTIFY replyChanged RESET resetReply)
 
 public:
         explicit TimelineModel(TimelineViewManager *manager,
@@ -191,6 +193,7 @@ public:
         void addEvents(const mtx::responses::Timeline &events);
         template<class T>
         void sendMessage(const T &msg);
+        RelatedInfo relatedInfo(QString id);
 
 public slots:
         void setCurrentIndex(int index);
@@ -206,6 +209,22 @@ public slots:
         }
         std::vector<QString> typingUsers() const { return typingUsers_; }
 
+        QString reply() const { return reply_; }
+        void setReply(QString newReply)
+        {
+                if (reply_ != newReply) {
+                        reply_ = newReply;
+                        emit replyChanged(reply_);
+                }
+        }
+        void resetReply()
+        {
+                if (!reply_.isEmpty()) {
+                        reply_ = "";
+                        emit replyChanged(reply_);
+                }
+        }
+
 private slots:
         // Add old events at the top of the timeline.
         void addBackwardsEvents(const mtx::responses::Messages &msgs);
@@ -225,6 +244,7 @@ signals:
         void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo);
         void eventFetched(QString requestingEvent, mtx::events::collections::TimelineEvents event);
         void typingUsersChanged(std::vector<QString> users);
+        void replyChanged(QString reply);
 
 private:
         DecryptionResult decryptEvent(
@@ -254,6 +274,7 @@ private:
         bool isProcessingPending  = false;
 
         QString currentId;
+        QString reply_;
         std::vector<QString> typingUsers_;
 
         TimelineViewManager *manager_;
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 794c13aa..0a339825 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -188,8 +188,11 @@ TimelineViewManager::initWithMessages(const std::map<QString, mtx::responses::Ti
 }
 
 void
-TimelineViewManager::queueTextMessage(const QString &msg, const std::optional<RelatedInfo> &related)
+TimelineViewManager::queueTextMessage(const QString &msg)
 {
+        if (!timeline_)
+                return;
+
         mtx::events::msg::Text text = {};
         text.body                   = msg.trimmed().toStdString();
 
@@ -203,13 +206,15 @@ TimelineViewManager::queueTextMessage(const QString &msg, const std::optional<Re
                         text.format = "org.matrix.custom.html";
         }
 
-        if (related) {
+        if (!timeline_->reply().isEmpty()) {
+                auto related = timeline_->relatedInfo(timeline_->reply());
+
                 QString body;
                 bool firstLine = true;
-                for (const auto &line : related->quoted_body.split("\n")) {
+                for (const auto &line : related.quoted_body.split("\n")) {
                         if (firstLine) {
                                 firstLine = false;
-                                body = QString("> <%1> %2\n").arg(related->quoted_user).arg(line);
+                                body = QString("> <%1> %2\n").arg(related.quoted_user).arg(line);
                         } else {
                                 body = QString("%1\n> %2\n").arg(body).arg(line);
                         }
@@ -221,17 +226,17 @@ TimelineViewManager::queueTextMessage(const QString &msg, const std::optional<Re
                 text.format = "org.matrix.custom.html";
                 if (settings->isMarkdownEnabled())
                         text.formatted_body =
-                          utils::getFormattedQuoteBody(*related, utils::markdownToHtml(msg))
+                          utils::getFormattedQuoteBody(related, utils::markdownToHtml(msg))
                             .toStdString();
                 else
                         text.formatted_body =
-                          utils::getFormattedQuoteBody(*related, msg.toHtmlEscaped()).toStdString();
+                          utils::getFormattedQuoteBody(related, msg.toHtmlEscaped()).toStdString();
 
-                text.relates_to.in_reply_to.event_id = related->related_event;
+                text.relates_to.in_reply_to.event_id = related.related_event;
+                timeline_->resetReply();
         }
 
-        if (timeline_)
-                timeline_->sendMessage(text);
+        timeline_->sendMessage(text);
 }
 
 void
@@ -247,6 +252,11 @@ TimelineViewManager::queueEmoteMessage(const QString &msg)
                 emote.format         = "org.matrix.custom.html";
         }
 
+        if (!timeline_->reply().isEmpty()) {
+                emote.relates_to.in_reply_to.event_id = timeline_->reply().toStdString();
+                timeline_->resetReply();
+        }
+
         if (timeline_)
                 timeline_->sendMessage(emote);
 }
@@ -259,8 +269,7 @@ TimelineViewManager::queueImageMessage(const QString &roomid,
                                        const QString &mime,
                                        uint64_t dsize,
                                        const QSize &dimensions,
-                                       const QString &blurhash,
-                                       const std::optional<RelatedInfo> &related)
+                                       const QString &blurhash)
 {
         mtx::events::msg::Image image;
         image.info.mimetype = mime.toStdString();
@@ -272,10 +281,13 @@ TimelineViewManager::queueImageMessage(const QString &roomid,
         image.info.w        = dimensions.width();
         image.file          = file;
 
-        if (related)
-                image.relates_to.in_reply_to.event_id = related->related_event;
+        auto model = models.value(roomid);
+        if (!model->reply().isEmpty()) {
+                image.relates_to.in_reply_to.event_id = model->reply().toStdString();
+                model->resetReply();
+        }
 
-        models.value(roomid)->sendMessage(image);
+        model->sendMessage(image);
 }
 
 void
@@ -285,8 +297,7 @@ TimelineViewManager::queueFileMessage(
   const std::optional<mtx::crypto::EncryptedFile> &encryptedFile,
   const QString &url,
   const QString &mime,
-  uint64_t dsize,
-  const std::optional<RelatedInfo> &related)
+  uint64_t dsize)
 {
         mtx::events::msg::File file;
         file.info.mimetype = mime.toStdString();
@@ -295,10 +306,13 @@ TimelineViewManager::queueFileMessage(
         file.url           = url.toStdString();
         file.file          = encryptedFile;
 
-        if (related)
-                file.relates_to.in_reply_to.event_id = related->related_event;
+        auto model = models.value(roomid);
+        if (!model->reply().isEmpty()) {
+                file.relates_to.in_reply_to.event_id = model->reply().toStdString();
+                model->resetReply();
+        }
 
-        models.value(roomid)->sendMessage(file);
+        model->sendMessage(file);
 }
 
 void
@@ -307,8 +321,7 @@ TimelineViewManager::queueAudioMessage(const QString &roomid,
                                        const std::optional<mtx::crypto::EncryptedFile> &file,
                                        const QString &url,
                                        const QString &mime,
-                                       uint64_t dsize,
-                                       const std::optional<RelatedInfo> &related)
+                                       uint64_t dsize)
 {
         mtx::events::msg::Audio audio;
         audio.info.mimetype = mime.toStdString();
@@ -317,10 +330,13 @@ TimelineViewManager::queueAudioMessage(const QString &roomid,
         audio.url           = url.toStdString();
         audio.file          = file;
 
-        if (related)
-                audio.relates_to.in_reply_to.event_id = related->related_event;
+        auto model = models.value(roomid);
+        if (!model->reply().isEmpty()) {
+                audio.relates_to.in_reply_to.event_id = model->reply().toStdString();
+                model->resetReply();
+        }
 
-        models.value(roomid)->sendMessage(audio);
+        model->sendMessage(audio);
 }
 
 void
@@ -329,8 +345,7 @@ TimelineViewManager::queueVideoMessage(const QString &roomid,
                                        const std::optional<mtx::crypto::EncryptedFile> &file,
                                        const QString &url,
                                        const QString &mime,
-                                       uint64_t dsize,
-                                       const std::optional<RelatedInfo> &related)
+                                       uint64_t dsize)
 {
         mtx::events::msg::Video video;
         video.info.mimetype = mime.toStdString();
@@ -339,8 +354,11 @@ TimelineViewManager::queueVideoMessage(const QString &roomid,
         video.url           = url.toStdString();
         video.file          = file;
 
-        if (related)
-                video.relates_to.in_reply_to.event_id = related->related_event;
+        auto model = models.value(roomid);
+        if (!model->reply().isEmpty()) {
+                video.relates_to.in_reply_to.event_id = model->reply().toStdString();
+                model->resetReply();
+        }
 
-        models.value(roomid)->sendMessage(video);
+        model->sendMessage(video);
 }
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 0c516e7f..122e4aec 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -26,8 +26,6 @@ class TimelineViewManager : public QObject
           TimelineModel *timeline MEMBER timeline_ READ activeTimeline NOTIFY activeTimelineChanged)
         Q_PROPERTY(
           bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
-        Q_PROPERTY(QString replyingEvent READ getReplyingEvent WRITE updateReplyingEvent NOTIFY
-                     replyingEventChanged)
 
 public:
         TimelineViewManager(QSharedPointer<UserSettings> userSettings, QWidget *parent = nullptr);
@@ -52,26 +50,13 @@ signals:
         void replyClosed();
 
 public slots:
-        void updateReplyingEvent(const QString &replyingEvent)
-        {
-                if (this->replyingEvent_ != replyingEvent) {
-                        this->replyingEvent_ = replyingEvent;
-                        emit replyingEventChanged(replyingEvent_);
-                }
-        }
-        void closeReply()
-        {
-                this->updateReplyingEvent(nullptr);
-                emit replyClosed();
-        }
-        QString getReplyingEvent() const { return replyingEvent_; }
         void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
         void initWithMessages(const std::map<QString, mtx::responses::Timeline> &msgs);
 
         void setHistoryView(const QString &room_id);
         void updateColorPalette();
 
-        void queueTextMessage(const QString &msg, const std::optional<RelatedInfo> &related);
+        void queueTextMessage(const QString &msg);
         void queueEmoteMessage(const QString &msg);
         void queueImageMessage(const QString &roomid,
                                const QString &filename,
@@ -80,29 +65,25 @@ public slots:
                                const QString &mime,
                                uint64_t dsize,
                                const QSize &dimensions,
-                               const QString &blurhash,
-                               const std::optional<RelatedInfo> &related);
+                               const QString &blurhash);
         void queueFileMessage(const QString &roomid,
                               const QString &filename,
                               const std::optional<mtx::crypto::EncryptedFile> &file,
                               const QString &url,
                               const QString &mime,
-                              uint64_t dsize,
-                              const std::optional<RelatedInfo> &related);
+                              uint64_t dsize);
         void queueAudioMessage(const QString &roomid,
                                const QString &filename,
                                const std::optional<mtx::crypto::EncryptedFile> &file,
                                const QString &url,
                                const QString &mime,
-                               uint64_t dsize,
-                               const std::optional<RelatedInfo> &related);
+                               uint64_t dsize);
         void queueVideoMessage(const QString &roomid,
                                const QString &filename,
                                const std::optional<mtx::crypto::EncryptedFile> &file,
                                const QString &url,
                                const QString &mime,
-                               uint64_t dsize,
-                               const std::optional<RelatedInfo> &related);
+                               uint64_t dsize);
 
 private:
 #ifdef USE_QUICK_VIEW
@@ -119,7 +100,6 @@ private:
         QHash<QString, QSharedPointer<TimelineModel>> models;
         TimelineModel *timeline_ = nullptr;
         bool isInitialSync_      = true;
-        QString replyingEvent_;
 
         QSharedPointer<UserSettings> settings;
         QHash<QString, QColor> userColors;