diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7b26602c..80ea628f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -286,7 +286,6 @@ set(SRC_FILES
src/dialogs/Logout.cpp
src/dialogs/PreviewUploadOverlay.cpp
src/dialogs/ReCaptcha.cpp
- src/dialogs/ReadReceipts.cpp
# Emoji
src/emoji/EmojiModel.cpp
@@ -305,7 +304,6 @@ set(SRC_FILES
src/timeline/RoomlistModel.cpp
# UI components
- src/ui/Avatar.cpp
src/ui/Badge.cpp
src/ui/DropShadow.cpp
src/ui/FlatButton.cpp
@@ -352,6 +350,7 @@ set(SRC_FILES
src/MemberList.cpp
src/MxcImageProvider.cpp
src/Olm.cpp
+ src/ReadReceiptsModel.cpp
src/RegisterPage.cpp
src/SSOHandler.cpp
src/CombinedImagePackModel.cpp
@@ -499,7 +498,6 @@ qt5_wrap_cpp(MOC_HEADERS
src/dialogs/PreviewUploadOverlay.h
src/dialogs/RawMessage.h
src/dialogs/ReCaptcha.h
- src/dialogs/ReadReceipts.h
# Emoji
src/emoji/EmojiModel.h
@@ -517,7 +515,6 @@ qt5_wrap_cpp(MOC_HEADERS
src/timeline/RoomlistModel.h
# UI components
- src/ui/Avatar.h
src/ui/Badge.h
src/ui/FlatButton.h
src/ui/FloatingButton.h
@@ -558,6 +555,7 @@ qt5_wrap_cpp(MOC_HEADERS
src/MainWindow.h
src/MemberList.h
src/MxcImageProvider.h
+ src/ReadReceiptsModel.h
src/RegisterPage.h
src/SSOHandler.h
src/CombinedImagePackModel.h
diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
index 07feec8c..b6f2b909 100644
--- a/resources/qml/MessageView.qml
+++ b/resources/qml/MessageView.qml
@@ -580,7 +580,7 @@ ScrollView {
Platform.MenuItem {
text: qsTr("Read receip&ts")
- onTriggered: room.readReceiptsAction(messageContextMenu.eventId)
+ onTriggered: room.showReadReceipts(messageContextMenu.eventId)
}
Platform.MenuItem {
diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml
new file mode 100644
index 00000000..8869d813
--- /dev/null
+++ b/resources/qml/ReadReceipts.qml
@@ -0,0 +1,131 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+import im.nheko 1.0
+
+ApplicationWindow {
+ id: readReceiptsRoot
+
+ property ReadReceiptsProxy readReceipts
+ property Room room
+
+ x: MainWindow.x + (MainWindow.width / 2) - (width / 2)
+ y: MainWindow.y + (MainWindow.height / 2) - (height / 2)
+ height: 380
+ width: 340
+ minimumHeight: 380
+ minimumWidth: headerTitle.width + 2 * Nheko.paddingMedium
+ palette: Nheko.colors
+ color: Nheko.colors.window
+ flags: Qt.Dialog
+
+ Shortcut {
+ sequence: StandardKey.Cancel
+ onActivated: readReceiptsRoot.close()
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: Nheko.paddingMedium
+ spacing: Nheko.paddingMedium
+
+ Label {
+ id: headerTitle
+
+ color: Nheko.colors.text
+ Layout.alignment: Qt.AlignCenter
+ text: qsTr("Read receipts")
+ font.pointSize: fontMetrics.font.pointSize * 1.5
+ }
+
+ ScrollView {
+ palette: Nheko.colors
+ padding: Nheko.paddingMedium
+ ScrollBar.horizontal.visible: false
+ Layout.fillHeight: true
+ Layout.minimumHeight: 200
+ Layout.fillWidth: true
+
+ ListView {
+ id: readReceiptsList
+
+ clip: true
+ spacing: Nheko.paddingMedium
+ boundsBehavior: Flickable.StopAtBounds
+ model: readReceipts
+
+ delegate: RowLayout {
+ spacing: Nheko.paddingMedium
+
+ Avatar {
+ width: Nheko.avatarSize
+ height: Nheko.avatarSize
+ userid: model.mxid
+ url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
+ displayName: model.displayName
+ onClicked: room.openUserProfile(model.mxid)
+ ToolTip.visible: avatarHover.hovered
+ ToolTip.text: model.mxid
+
+ HoverHandler {
+ id: avatarHover
+ }
+
+ }
+
+ ColumnLayout {
+ spacing: Nheko.paddingSmall
+
+ Label {
+ text: model.displayName
+ color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window)
+ font.pointSize: fontMetrics.font.pointSize
+ ToolTip.visible: displayNameHover.hovered
+ ToolTip.text: model.mxid
+
+ TapHandler {
+ onSingleTapped: room.openUserProfile(userId)
+ }
+
+ CursorShape {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ }
+
+ HoverHandler {
+ id: displayNameHover
+ }
+
+ }
+
+ Label {
+ text: model.timestamp
+ color: Nheko.colors.buttonText
+ font.pointSize: fontMetrics.font.pointSize * 0.9
+ }
+
+ Item {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ footer: DialogButtonBox {
+ standardButtons: DialogButtonBox.Ok
+ onAccepted: readReceiptsRoot.close()
+ }
+
+}
diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml
index e80ff764..7d91beae 100644
--- a/resources/qml/Root.qml
+++ b/resources/qml/Root.qml
@@ -96,6 +96,14 @@ Page {
}
+ Component {
+ id: readReceiptsDialog
+
+ ReadReceipts {
+ }
+
+ }
+
Shortcut {
sequence: "Ctrl+K"
onActivated: {
diff --git a/resources/qml/StatusIndicator.qml b/resources/qml/StatusIndicator.qml
index 7e471d69..0af02b3c 100644
--- a/resources/qml/StatusIndicator.qml
+++ b/resources/qml/StatusIndicator.qml
@@ -34,7 +34,7 @@ ImageButton {
}
onClicked: {
if (status == MtxEvent.Read)
- room.readReceiptsAction(eventId);
+ room.showReadReceipts(eventId);
}
image: {
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index c5cc69a6..d19f2cc9 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -249,4 +249,16 @@ Item {
roomid: room ? room.roomId : ""
}
+ Connections {
+ function onOpenReadReceiptsDialog(rr) {
+ var dialog = readReceiptsDialog.createObject(timelineRoot, {
+ "readReceipts": rr,
+ "room": room
+ });
+ dialog.show();
+ }
+
+ target: room
+ }
+
}
diff --git a/resources/res.qrc b/resources/res.qrc
index 5d37c397..2b655b9e 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -112,7 +112,6 @@
qtquickcontrols2.conf
-
qml/Root.qml
qml/ChatPage.qml
qml/CommunitiesList.qml
@@ -177,6 +176,7 @@
qml/components/FlatButton.qml
qml/RoomMembers.qml
qml/InviteDialog.qml
+ qml/ReadReceipts.qml
media/ring.ogg
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index a76756ae..42e3bc7b 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -31,7 +31,6 @@
#include "notifications/Manager.h"
-#include "dialogs/ReadReceipts.h"
#include "timeline/TimelineViewManager.h"
#include "blurhash.hpp"
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index c0486d01..8bc90f29 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -36,7 +36,6 @@
#include "dialogs/JoinRoom.h"
#include "dialogs/LeaveRoom.h"
#include "dialogs/Logout.h"
-#include "dialogs/ReadReceipts.h"
MainWindow *MainWindow::instance_ = nullptr;
@@ -398,27 +397,6 @@ MainWindow::openLogoutDialog()
showDialog(dialog);
}
-void
-MainWindow::openReadReceiptsDialog(const QString &event_id)
-{
- auto dialog = new dialogs::ReadReceipts(this);
-
- const auto room_id = chat_page_->currentRoom();
-
- try {
- dialog->addUsers(cache::readReceipts(event_id, room_id));
- } catch (const lmdb::error &) {
- nhlog::db()->warn("failed to retrieve read receipts for {} {}",
- event_id.toStdString(),
- chat_page_->currentRoom().toStdString());
- dialog->deleteLater();
-
- return;
- }
-
- showDialog(dialog);
-}
-
bool
MainWindow::hasActiveDialogs() const
{
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 6d62545c..d423af9f 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -65,7 +65,6 @@ public:
std::function callback);
void openJoinRoomDialog(std::function callback);
void openLogoutDialog();
- void openReadReceiptsDialog(const QString &event_id);
void hideOverlay();
void showSolidOverlayModal(QWidget *content,
diff --git a/src/MemberList.cpp b/src/MemberList.cpp
index 0ef3b696..196647fe 100644
--- a/src/MemberList.cpp
+++ b/src/MemberList.cpp
@@ -2,16 +2,6 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
#include "MemberList.h"
#include "Cache.h"
@@ -20,7 +10,6 @@
#include "Logging.h"
#include "Utils.h"
#include "timeline/TimelineViewManager.h"
-#include "ui/Avatar.h"
MemberList::MemberList(const QString &room_id, QObject *parent)
: QAbstractListModel{parent}
diff --git a/src/MemberList.h b/src/MemberList.h
index 9932f6a4..e6522694 100644
--- a/src/MemberList.h
+++ b/src/MemberList.h
@@ -4,9 +4,10 @@
#pragma once
-#include "CacheStructs.h"
#include
+#include "CacheStructs.h"
+
class MemberList : public QAbstractListModel
{
Q_OBJECT
diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp
new file mode 100644
index 00000000..25262c59
--- /dev/null
+++ b/src/ReadReceiptsModel.cpp
@@ -0,0 +1,131 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "ReadReceiptsModel.h"
+
+#include
+
+#include "Cache.h"
+#include "Cache_p.h"
+#include "Logging.h"
+#include "Utils.h"
+
+ReadReceiptsModel::ReadReceiptsModel(QString event_id, QString room_id, QObject *parent)
+ : QAbstractListModel{parent}
+ , event_id_{event_id}
+ , room_id_{room_id}
+{
+ try {
+ addUsers(cache::readReceipts(event_id_, room_id_));
+ } catch (const lmdb::error &) {
+ nhlog::db()->warn("failed to retrieve read receipts for {} {}",
+ event_id_.toStdString(),
+ room_id_.toStdString());
+
+ return;
+ }
+
+ connect(cache::client(), &Cache::newReadReceipts, this, &ReadReceiptsModel::update);
+}
+
+void
+ReadReceiptsModel::update()
+{
+ try {
+ addUsers(cache::readReceipts(event_id_, room_id_));
+ } catch (const lmdb::error &) {
+ nhlog::db()->warn("failed to retrieve read receipts for {} {}",
+ event_id_.toStdString(),
+ room_id_.toStdString());
+
+ return;
+ }
+}
+
+QHash
+ReadReceiptsModel::roleNames() const
+{
+ // Note: RawTimestamp is purposely not included here
+ return {
+ {Mxid, "mxid"},
+ {DisplayName, "displayName"},
+ {AvatarUrl, "avatarUrl"},
+ {Timestamp, "timestamp"},
+ };
+}
+
+QVariant
+ReadReceiptsModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid() || index.row() >= (int)readReceipts_.size() || index.row() < 0)
+ return {};
+
+ switch (role) {
+ case Mxid:
+ return readReceipts_[index.row()].first;
+ case DisplayName:
+ return cache::displayName(room_id_, readReceipts_[index.row()].first);
+ case AvatarUrl:
+ return cache::avatarUrl(room_id_, readReceipts_[index.row()].first);
+ case Timestamp:
+ return dateFormat(readReceipts_[index.row()].second);
+ case RawTimestamp:
+ return readReceipts_[index.row()].second;
+ default:
+ return {};
+ }
+}
+
+void
+ReadReceiptsModel::addUsers(
+ const std::multimap> &users)
+{
+ auto newReceipts = users.size() - readReceipts_.size();
+
+ if (newReceipts > 0) {
+ beginInsertRows(
+ QModelIndex{}, readReceipts_.size(), readReceipts_.size() + newReceipts - 1);
+
+ for (const auto &user : users) {
+ QPair item = {
+ QString::fromStdString(user.second),
+ QDateTime::fromMSecsSinceEpoch(user.first)};
+ if (!readReceipts_.contains(item))
+ readReceipts_.push_back(item);
+ }
+
+ endInsertRows();
+ }
+}
+
+QString
+ReadReceiptsModel::dateFormat(const QDateTime &then) const
+{
+ auto now = QDateTime::currentDateTime();
+ auto days = then.daysTo(now);
+
+ if (days == 0)
+ return QLocale::system().toString(then.time(), QLocale::ShortFormat);
+ else if (days < 2)
+ return tr("Yesterday, %1")
+ .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));
+ else if (days < 7)
+ //: %1 is the name of the current day, %2 is the time the read receipt was read. The
+ //: result may look like this: Monday, 7:15
+ return QString("%1, %2")
+ .arg(then.toString("dddd"))
+ .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));
+
+ return QLocale::system().toString(then.time(), QLocale::ShortFormat);
+}
+
+ReadReceiptsProxy::ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent)
+ : QSortFilterProxyModel{parent}
+ , model_{event_id, room_id, this}
+{
+ setSourceModel(&model_);
+ setSortRole(ReadReceiptsModel::RawTimestamp);
+ sort(0, Qt::DescendingOrder);
+ setDynamicSortFilter(true);
+}
diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h
new file mode 100644
index 00000000..3b45716c
--- /dev/null
+++ b/src/ReadReceiptsModel.h
@@ -0,0 +1,73 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef READRECEIPTSMODEL_H
+#define READRECEIPTSMODEL_H
+
+#include
+#include
+#include
+#include
+#include
+
+class ReadReceiptsModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ enum Roles
+ {
+ Mxid,
+ DisplayName,
+ AvatarUrl,
+ Timestamp,
+ RawTimestamp,
+ };
+
+ explicit ReadReceiptsModel(QString event_id, QString room_id, QObject *parent = nullptr);
+
+ QString eventId() const { return event_id_; }
+ QString roomId() const { return room_id_; }
+
+ QHash roleNames() const override;
+ int rowCount(const QModelIndex &parent) const override
+ {
+ Q_UNUSED(parent)
+ return readReceipts_.size();
+ }
+ QVariant data(const QModelIndex &index, int role) const override;
+
+public slots:
+ void addUsers(const std::multimap> &users);
+ void update();
+
+private:
+ QString dateFormat(const QDateTime &then) const;
+
+ QString event_id_;
+ QString room_id_;
+ QVector> readReceipts_;
+};
+
+class ReadReceiptsProxy : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString eventId READ eventId CONSTANT)
+ Q_PROPERTY(QString roomId READ roomId CONSTANT)
+
+public:
+ explicit ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent = nullptr);
+
+ QString eventId() const { return event_id_; }
+ QString roomId() const { return room_id_; }
+
+private:
+ QString event_id_;
+ QString room_id_;
+
+ ReadReceiptsModel model_;
+};
+
+#endif // READRECEIPTSMODEL_H
diff --git a/src/dialogs/ReadReceipts.cpp b/src/dialogs/ReadReceipts.cpp
deleted file mode 100644
index fa7132fd..00000000
--- a/src/dialogs/ReadReceipts.cpp
+++ /dev/null
@@ -1,179 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "dialogs/ReadReceipts.h"
-
-#include "AvatarProvider.h"
-#include "Cache.h"
-#include "ChatPage.h"
-#include "Config.h"
-#include "Utils.h"
-#include "ui/Avatar.h"
-
-using namespace dialogs;
-
-ReceiptItem::ReceiptItem(QWidget *parent,
- const QString &user_id,
- uint64_t timestamp,
- const QString &room_id)
- : QWidget(parent)
-{
- topLayout_ = new QHBoxLayout(this);
- topLayout_->setMargin(0);
-
- textLayout_ = new QVBoxLayout;
- textLayout_->setMargin(0);
- textLayout_->setSpacing(conf::modals::TEXT_SPACING);
-
- QFont nameFont;
- nameFont.setPointSizeF(nameFont.pointSizeF() * 1.1);
-
- auto displayName = cache::displayName(room_id, user_id);
-
- avatar_ = new Avatar(this, 44);
- avatar_->setLetter(utils::firstChar(displayName));
-
- // If it's a matrix id we use the second letter.
- if (displayName.size() > 1 && displayName.at(0) == '@')
- avatar_->setLetter(QChar(displayName.at(1)));
-
- userName_ = new QLabel(displayName, this);
- userName_->setFont(nameFont);
-
- timestamp_ = new QLabel(dateFormat(QDateTime::fromMSecsSinceEpoch(timestamp)), this);
-
- textLayout_->addWidget(userName_);
- textLayout_->addWidget(timestamp_);
-
- topLayout_->addWidget(avatar_);
- topLayout_->addLayout(textLayout_, 1);
-
- avatar_->setImage(ChatPage::instance()->currentRoom(), user_id);
-}
-
-void
-ReceiptItem::paintEvent(QPaintEvent *)
-{
- QStyleOption opt;
- opt.init(this);
- QPainter p(this);
- style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
-}
-
-QString
-ReceiptItem::dateFormat(const QDateTime &then) const
-{
- auto now = QDateTime::currentDateTime();
- auto days = then.daysTo(now);
-
- if (days == 0)
- return tr("Today %1")
- .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));
- else if (days < 2)
- return tr("Yesterday %1")
- .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));
- else if (days < 7)
- return QString("%1 %2")
- .arg(then.toString("dddd"))
- .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));
-
- return QLocale::system().toString(then.time(), QLocale::ShortFormat);
-}
-
-ReadReceipts::ReadReceipts(QWidget *parent)
- : QFrame(parent)
-{
- setAutoFillBackground(true);
- setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
- setWindowModality(Qt::WindowModal);
- setAttribute(Qt::WA_DeleteOnClose, true);
-
- auto layout = new QVBoxLayout(this);
- layout->setSpacing(conf::modals::WIDGET_SPACING);
- layout->setMargin(conf::modals::WIDGET_MARGIN);
-
- userList_ = new QListWidget;
- userList_->setFrameStyle(QFrame::NoFrame);
- userList_->setSelectionMode(QAbstractItemView::NoSelection);
- userList_->setSpacing(conf::modals::TEXT_SPACING);
-
- QFont largeFont;
- largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5);
-
- setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
- setMinimumHeight(userList_->sizeHint().height() * 2);
- setMinimumWidth(std::max(userList_->sizeHint().width() + 4 * conf::modals::WIDGET_MARGIN,
- QFontMetrics(largeFont).averageCharWidth() * 30 -
- 2 * conf::modals::WIDGET_MARGIN));
-
- QFont font;
- font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
-
- topLabel_ = new QLabel(tr("Read receipts"), this);
- topLabel_->setAlignment(Qt::AlignCenter);
- topLabel_->setFont(font);
-
- auto okBtn = new QPushButton(tr("Close"), this);
-
- auto buttonLayout = new QHBoxLayout();
- buttonLayout->setSpacing(15);
- buttonLayout->addStretch(1);
- buttonLayout->addWidget(okBtn);
-
- layout->addWidget(topLabel_);
- layout->addWidget(userList_);
- layout->addLayout(buttonLayout);
-
- auto closeShortcut = new QShortcut(QKeySequence(QKeySequence::Cancel), this);
- connect(closeShortcut, &QShortcut::activated, this, &ReadReceipts::close);
- connect(okBtn, &QPushButton::clicked, this, &ReadReceipts::close);
-}
-
-void
-ReadReceipts::addUsers(const std::multimap> &receipts)
-{
- // We want to remove any previous items that have been set.
- userList_->clear();
-
- for (const auto &receipt : receipts) {
- auto user = new ReceiptItem(this,
- QString::fromStdString(receipt.second),
- receipt.first,
- ChatPage::instance()->currentRoom());
- auto item = new QListWidgetItem(userList_);
-
- item->setSizeHint(user->minimumSizeHint());
- item->setFlags(Qt::NoItemFlags);
- item->setTextAlignment(Qt::AlignCenter);
-
- userList_->setItemWidget(item, user);
- }
-}
-
-void
-ReadReceipts::paintEvent(QPaintEvent *)
-{
- QStyleOption opt;
- opt.init(this);
- QPainter p(this);
- style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
-}
-
-void
-ReadReceipts::hideEvent(QHideEvent *event)
-{
- userList_->clear();
- QFrame::hideEvent(event);
-}
diff --git a/src/dialogs/ReadReceipts.h b/src/dialogs/ReadReceipts.h
deleted file mode 100644
index 5c6c5d2b..00000000
--- a/src/dialogs/ReadReceipts.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#pragma once
-
-#include
-#include
-
-class Avatar;
-class QLabel;
-class QListWidget;
-class QHBoxLayout;
-class QVBoxLayout;
-
-namespace dialogs {
-
-class ReceiptItem : public QWidget
-{
- Q_OBJECT
-
-public:
- ReceiptItem(QWidget *parent,
- const QString &user_id,
- uint64_t timestamp,
- const QString &room_id);
-
-protected:
- void paintEvent(QPaintEvent *) override;
-
-private:
- QString dateFormat(const QDateTime &then) const;
-
- QHBoxLayout *topLayout_;
- QVBoxLayout *textLayout_;
-
- Avatar *avatar_;
-
- QLabel *userName_;
- QLabel *timestamp_;
-};
-
-class ReadReceipts : public QFrame
-{
- Q_OBJECT
-public:
- explicit ReadReceipts(QWidget *parent = nullptr);
-
-public slots:
- void addUsers(const std::multimap> &users);
-
-protected:
- void paintEvent(QPaintEvent *event) override;
- void hideEvent(QHideEvent *event) override;
-
-private:
- QLabel *topLabel_;
-
- QListWidget *userList_;
-};
-} // dialogs
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index ee5564a5..6ae0c4d1 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -28,6 +28,7 @@
#include "MemberList.h"
#include "MxcImageProvider.h"
#include "Olm.h"
+#include "ReadReceiptsModel.h"
#include "TimelineViewManager.h"
#include "Utils.h"
#include "dialogs/RawMessage.h"
@@ -1089,9 +1090,9 @@ TimelineModel::relatedInfo(QString id)
}
void
-TimelineModel::readReceiptsAction(QString id) const
+TimelineModel::showReadReceipts(QString id)
{
- MainWindow::instance()->openReadReceiptsDialog(id);
+ emit openReadReceiptsDialog(new ReadReceiptsProxy{id, roomId(), this});
}
void
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 0e2ce153..0d5f7109 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -20,6 +20,7 @@
#include "InviteesModel.h"
#include "MemberList.h"
#include "Permissions.h"
+#include "ReadReceiptsModel.h"
#include "ui/RoomSettings.h"
#include "ui/UserProfile.h"
@@ -241,7 +242,7 @@ public:
Q_INVOKABLE void openUserProfile(QString userid);
Q_INVOKABLE void editAction(QString id);
Q_INVOKABLE void replyAction(QString id);
- Q_INVOKABLE void readReceiptsAction(QString id) const;
+ Q_INVOKABLE void showReadReceipts(QString id);
Q_INVOKABLE void redactEvent(QString id);
Q_INVOKABLE int idToIndex(QString id) const;
Q_INVOKABLE QString indexToId(int index) const;
@@ -348,6 +349,7 @@ signals:
void typingUsersChanged(std::vector users);
void replyChanged(QString reply);
void editChanged(QString reply);
+ void openReadReceiptsDialog(ReadReceiptsProxy *rr);
void paginationInProgressChanged(const bool);
void newCallEvent(const mtx::events::collections::TimelineEvents &event);
void scrollToIndex(int index);
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index a6922be7..76bc127e 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -26,6 +26,7 @@
#include "MainWindow.h"
#include "MatrixClient.h"
#include "MxcImageProvider.h"
+#include "ReadReceiptsModel.h"
#include "RoomsModel.h"
#include "SingleImagePackModel.h"
#include "UserSettingsPage.h"
@@ -205,6 +206,12 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
0,
"InviteesModel",
"InviteesModel needs to be instantiated on the C++ side");
+ qmlRegisterUncreatableType(
+ "im.nheko",
+ 1,
+ 0,
+ "ReadReceiptsProxy",
+ "ReadReceiptsProxy needs to be instantiated on the C++ side");
static auto self = this;
qmlRegisterSingletonType(
diff --git a/src/ui/Avatar.cpp b/src/ui/Avatar.cpp
deleted file mode 100644
index 154a0e2c..00000000
--- a/src/ui/Avatar.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include
-#include
-#include
-
-#include "AvatarProvider.h"
-#include "Utils.h"
-#include "ui/Avatar.h"
-
-Avatar::Avatar(QWidget *parent, int size)
- : QWidget(parent)
- , size_(size)
-{
- type_ = ui::AvatarType::Letter;
- letter_ = "A";
-
- QFont _font(font());
- _font.setPointSizeF(ui::FontSize);
- setFont(_font);
-
- QSizePolicy policy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
- setSizePolicy(policy);
-}
-
-QColor
-Avatar::textColor() const
-{
- if (!text_color_.isValid())
- return QColor("black");
-
- return text_color_;
-}
-
-QColor
-Avatar::backgroundColor() const
-{
- if (!text_color_.isValid())
- return QColor("white");
-
- return background_color_;
-}
-
-QSize
-Avatar::sizeHint() const
-{
- return QSize(size_ + 2, size_ + 2);
-}
-
-void
-Avatar::setTextColor(const QColor &color)
-{
- text_color_ = color;
-}
-
-void
-Avatar::setBackgroundColor(const QColor &color)
-{
- background_color_ = color;
-}
-
-void
-Avatar::setLetter(const QString &letter)
-{
- letter_ = letter;
- type_ = ui::AvatarType::Letter;
- update();
-}
-
-void
-Avatar::setImage(const QString &avatar_url)
-{
- avatar_url_ = avatar_url;
- AvatarProvider::resolve(avatar_url,
- static_cast(size_ * pixmap_.devicePixelRatio()),
- this,
- [this, requestedRatio = pixmap_.devicePixelRatio()](QPixmap pm) {
- if (pm.isNull())
- return;
- type_ = ui::AvatarType::Image;
- pixmap_ = pm;
- pixmap_.setDevicePixelRatio(requestedRatio);
- update();
- });
-}
-
-void
-Avatar::setImage(const QString &room, const QString &user)
-{
- room_ = room;
- user_ = user;
- AvatarProvider::resolve(room,
- user,
- static_cast(size_ * pixmap_.devicePixelRatio()),
- this,
- [this, requestedRatio = pixmap_.devicePixelRatio()](QPixmap pm) {
- if (pm.isNull())
- return;
- type_ = ui::AvatarType::Image;
- pixmap_ = pm;
- pixmap_.setDevicePixelRatio(requestedRatio);
- update();
- });
-}
-
-void
-Avatar::setDevicePixelRatio(double ratio)
-{
- if (type_ == ui::AvatarType::Image && abs(pixmap_.devicePixelRatio() - ratio) > 0.01) {
- pixmap_ = pixmap_.scaled(QSize(size_, size_) * ratio);
- pixmap_.setDevicePixelRatio(ratio);
-
- if (!avatar_url_.isEmpty())
- setImage(avatar_url_);
- else
- setImage(room_, user_);
- }
-}
-
-void
-Avatar::paintEvent(QPaintEvent *)
-{
- bool rounded = QSettings().value(QStringLiteral("user/avatar_circles"), true).toBool();
-
- QPainter painter(this);
-
- painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform |
- QPainter::TextAntialiasing);
-
- QRectF r = rect();
- const int hs = size_ / 2;
-
- if (type_ != ui::AvatarType::Image) {
- QBrush brush;
- brush.setStyle(Qt::SolidPattern);
- brush.setColor(backgroundColor());
-
- painter.setPen(Qt::NoPen);
- painter.setBrush(brush);
- rounded ? painter.drawEllipse(r) : painter.drawRoundedRect(r, 3, 3);
- } else if (painter.isActive()) {
- setDevicePixelRatio(painter.device()->devicePixelRatioF());
- }
-
- switch (type_) {
- case ui::AvatarType::Image: {
- QPainterPath ppath;
-
- rounded ? ppath.addEllipse(width() / 2 - hs, height() / 2 - hs, size_, size_)
- : ppath.addRoundedRect(r, 3, 3);
-
- painter.setClipPath(ppath);
- painter.drawPixmap(QRect(width() / 2 - hs, height() / 2 - hs, size_, size_),
- pixmap_);
- break;
- }
- case ui::AvatarType::Letter: {
- painter.setPen(textColor());
- painter.setBrush(Qt::NoBrush);
- painter.drawText(r.translated(0, -1), Qt::AlignCenter, letter_);
- break;
- }
- default:
- break;
- }
-}
diff --git a/src/ui/Avatar.h b/src/ui/Avatar.h
deleted file mode 100644
index bbf05be3..00000000
--- a/src/ui/Avatar.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#pragma once
-
-#include
-#include
-#include
-
-#include "Theme.h"
-
-class Avatar : public QWidget
-{
- Q_OBJECT
-
- Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
- Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
-
-public:
- explicit Avatar(QWidget *parent = nullptr, int size = ui::AvatarSize);
-
- void setBackgroundColor(const QColor &color);
- void setImage(const QString &avatar_url);
- void setImage(const QString &room, const QString &user);
- void setLetter(const QString &letter);
- void setTextColor(const QColor &color);
- void setDevicePixelRatio(double ratio);
-
- QColor backgroundColor() const;
- QColor textColor() const;
-
- QSize sizeHint() const override;
-
-protected:
- void paintEvent(QPaintEvent *event) override;
-
-private:
- void init();
-
- ui::AvatarType type_;
- QString letter_;
- QString avatar_url_, room_, user_;
- QColor background_color_;
- QColor text_color_;
- QPixmap pixmap_;
- int size_;
-};