From 4dd994ae009b622cd35e292d1170a3f60a26c4d6 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 23 Jul 2021 18:11:33 -0400 Subject: [PATCH 01/20] QML the read receipts list There are probably a few things wrong with this, but I'm going to call it good enough for an initial commit --- CMakeLists.txt | 4 +- resources/qml/ReadReceipts.qml | 118 ++++++++++++++++++ resources/qml/Root.qml | 19 +++ resources/qml/StatusIndicator.qml | 2 +- resources/res.qrc | 2 +- src/ChatPage.cpp | 1 - src/MainWindow.cpp | 22 ---- src/MainWindow.h | 1 - src/ReadReceiptsModel.cpp | 120 ++++++++++++++++++ src/ReadReceiptsModel.h | 86 +++++++++++++ src/dialogs/ReadReceipts.cpp | 179 --------------------------- src/dialogs/ReadReceipts.h | 61 --------- src/timeline/TimelineModel.cpp | 5 +- src/timeline/TimelineModel.h | 4 +- src/timeline/TimelineViewManager.cpp | 7 ++ 15 files changed, 360 insertions(+), 271 deletions(-) create mode 100644 resources/qml/ReadReceipts.qml create mode 100644 src/ReadReceiptsModel.cpp create mode 100644 src/ReadReceiptsModel.h delete mode 100644 src/dialogs/ReadReceipts.cpp delete mode 100644 src/dialogs/ReadReceipts.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b26602c..e9371579 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 @@ -352,6 +351,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 +499,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 @@ -558,6 +557,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/ReadReceipts.qml b/resources/qml/ReadReceipts.qml new file mode 100644 index 00000000..21b9b15e --- /dev/null +++ b/resources/qml/ReadReceipts.qml @@ -0,0 +1,118 @@ +// 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 ReadReceiptsModel readReceipts + + 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 + + ColumnLayout { + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + spacing: Nheko.paddingMedium + + Label { + id: headerTitle + + 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: Rooms.currentRoom.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: chat.model.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 + } + + } + + } + + } + + } + + } + +} diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index e80ff764..a099b5e6 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: { @@ -164,6 +172,17 @@ Page { target: TimelineManager } + Connections { + function onOpenReadReceiptsDialog() { + var dialog = readReceiptsDialog.createObject(timelineRoot, { + "readReceipts": rr + }); + dialog.show(); + } + + target: Rooms.currentRoom + } + Connections { function onNewInviteState() { if (CallManager.haveCallInvite && Settings.mobileMode) { 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/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/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp new file mode 100644 index 00000000..293733d3 --- /dev/null +++ b/src/ReadReceiptsModel.cpp @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ReadReceiptsModel.h" + +#include + +#include "Cache.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; + } +} + +ReadReceiptsModel::~ReadReceiptsModel() +{ + for (const auto &item : readReceipts_) + item->deleteLater(); +} + +QHash +ReadReceiptsModel::roleNames() const +{ + 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()]->mxid(); + case DisplayName: + return readReceipts_[index.row()]->displayName(); + case AvatarUrl: + return readReceipts_[index.row()]->avatarUrl(); + case Timestamp: + // the uint64_t to QVariant conversion was ambiguous, so... + return readReceipts_[index.row()]->timestamp(); + default: + return {}; + } +} + +void +ReadReceiptsModel::addUsers( + const std::multimap> &users) +{ + std::multimap> unshown; + for (const auto &user : users) { + if (users_.find(user.first) == users_.end()) + unshown.emplace(user); + } + + beginInsertRows( + QModelIndex{}, readReceipts_.length(), readReceipts_.length() + unshown.size() - 1); + + for (const auto &user : unshown) + readReceipts_.push_back( + new ReadReceipt{QString::fromStdString(user.second), room_id_, user.first, this}); + + users_.merge(unshown); + + endInsertRows(); +} + +ReadReceipt::ReadReceipt(QString mxid, QString room_id, uint64_t timestamp, QObject *parent) + : QObject{parent} + , mxid_{mxid} + , room_id_{room_id} + , displayName_{cache::displayName(room_id_, mxid_)} + , avatarUrl_{cache::avatarUrl(room_id_, mxid_)} + , timestamp_{timestamp} +{} + +QString +ReadReceipt::timestamp() const +{ + return dateFormat(QDateTime::fromMSecsSinceEpoch(timestamp_)); +} + +QString +ReadReceipt::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); +} diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h new file mode 100644 index 00000000..d90bf7c1 --- /dev/null +++ b/src/ReadReceiptsModel.h @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef READRECEIPTSMODEL_H +#define READRECEIPTSMODEL_H + +#include +#include +#include + +class ReadReceipt : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString mxid READ mxid CONSTANT) + Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged) + Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged) + Q_PROPERTY(QString timestamp READ timestamp CONSTANT) + +public: + explicit ReadReceipt(QString mxid, + QString room_id, + uint64_t timestamp, + QObject *parent = nullptr); + + QString mxid() const { return mxid_; } + QString displayName() const { return displayName_; } + QString avatarUrl() const { return avatarUrl_; } + QString timestamp() const; + +signals: + void displayNameChanged(); + void avatarUrlChanged(); + +private: + QString dateFormat(const QDateTime &then) const; + + QString mxid_; + QString room_id_; + QString displayName_; + QString avatarUrl_; + uint64_t timestamp_; +}; + +class ReadReceiptsModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(QString eventId READ eventId CONSTANT) + Q_PROPERTY(QString roomId READ roomId CONSTANT) + +public: + enum Roles + { + Mxid, + DisplayName, + AvatarUrl, + Timestamp, + }; + + explicit ReadReceiptsModel(QString event_id, QString room_id, QObject *parent = nullptr); + ~ReadReceiptsModel() override; + + 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); + +private: + QString event_id_; + QString room_id_; + QVector readReceipts_; + std::multimap> users_; +}; + +#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..f5737063 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 ReadReceiptsModel{id, roomId(), this}); } void diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 0e2ce153..82fce257 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(ReadReceiptsModel *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..58b0d5a8 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, + "ReadReceiptsModel", + "ReadReceiptsModel needs to be instantiated on the C++ side"); static auto self = this; qmlRegisterSingletonType( From 774a9fdc3a5dd5221cd8143e2aaa03c7b93737f2 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 23 Jul 2021 21:58:57 -0400 Subject: [PATCH 02/20] Remove outdated comment --- src/ReadReceiptsModel.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index 293733d3..eadb4e74 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -55,7 +55,6 @@ ReadReceiptsModel::data(const QModelIndex &index, int role) const case AvatarUrl: return readReceipts_[index.row()]->avatarUrl(); case Timestamp: - // the uint64_t to QVariant conversion was ambiguous, so... return readReceipts_[index.row()]->timestamp(); default: return {}; From 0d42909e406821b76c32b37af758a3721ea1238d Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 23 Jul 2021 22:19:48 -0400 Subject: [PATCH 03/20] Simplify read receipt storage --- src/ReadReceiptsModel.cpp | 52 +++++++++++---------------------------- src/ReadReceiptsModel.h | 41 +++--------------------------- 2 files changed, 19 insertions(+), 74 deletions(-) diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index eadb4e74..8ee9cf45 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -26,12 +26,6 @@ ReadReceiptsModel::ReadReceiptsModel(QString event_id, QString room_id, QObject } } -ReadReceiptsModel::~ReadReceiptsModel() -{ - for (const auto &item : readReceipts_) - item->deleteLater(); -} - QHash ReadReceiptsModel::roleNames() const { @@ -49,13 +43,13 @@ ReadReceiptsModel::data(const QModelIndex &index, int role) const switch (role) { case Mxid: - return readReceipts_[index.row()]->mxid(); + return readReceipts_[index.row()].first; case DisplayName: - return readReceipts_[index.row()]->displayName(); + return cache::displayName(room_id_, readReceipts_[index.row()].first); case AvatarUrl: - return readReceipts_[index.row()]->avatarUrl(); + return cache::avatarUrl(room_id_, readReceipts_[index.row()].first); case Timestamp: - return readReceipts_[index.row()]->timestamp(); + return dateFormat(readReceipts_[index.row()].second); default: return {}; } @@ -65,41 +59,25 @@ void ReadReceiptsModel::addUsers( const std::multimap> &users) { - std::multimap> unshown; + beginInsertRows(QModelIndex{}, readReceipts_.length(), users.size() - 1); + + readReceipts_.clear(); for (const auto &user : users) { - if (users_.find(user.first) == users_.end()) - unshown.emplace(user); + readReceipts_.push_back({QString::fromStdString(user.second), + QDateTime::fromMSecsSinceEpoch(user.first)}); } - beginInsertRows( - QModelIndex{}, readReceipts_.length(), readReceipts_.length() + unshown.size() - 1); - - for (const auto &user : unshown) - readReceipts_.push_back( - new ReadReceipt{QString::fromStdString(user.second), room_id_, user.first, this}); - - users_.merge(unshown); + std::sort(readReceipts_.begin(), + readReceipts_.end(), + [](const QPair &a, const QPair &b) { + return a.second > b.second; + }); endInsertRows(); } -ReadReceipt::ReadReceipt(QString mxid, QString room_id, uint64_t timestamp, QObject *parent) - : QObject{parent} - , mxid_{mxid} - , room_id_{room_id} - , displayName_{cache::displayName(room_id_, mxid_)} - , avatarUrl_{cache::avatarUrl(room_id_, mxid_)} - , timestamp_{timestamp} -{} - QString -ReadReceipt::timestamp() const -{ - return dateFormat(QDateTime::fromMSecsSinceEpoch(timestamp_)); -} - -QString -ReadReceipt::dateFormat(const QDateTime &then) const +ReadReceiptsModel::dateFormat(const QDateTime &then) const { auto now = QDateTime::currentDateTime(); auto days = then.daysTo(now); diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h index d90bf7c1..98e41f8f 100644 --- a/src/ReadReceiptsModel.h +++ b/src/ReadReceiptsModel.h @@ -8,40 +8,7 @@ #include #include #include - -class ReadReceipt : public QObject -{ - Q_OBJECT - - Q_PROPERTY(QString mxid READ mxid CONSTANT) - Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged) - Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged) - Q_PROPERTY(QString timestamp READ timestamp CONSTANT) - -public: - explicit ReadReceipt(QString mxid, - QString room_id, - uint64_t timestamp, - QObject *parent = nullptr); - - QString mxid() const { return mxid_; } - QString displayName() const { return displayName_; } - QString avatarUrl() const { return avatarUrl_; } - QString timestamp() const; - -signals: - void displayNameChanged(); - void avatarUrlChanged(); - -private: - QString dateFormat(const QDateTime &then) const; - - QString mxid_; - QString room_id_; - QString displayName_; - QString avatarUrl_; - uint64_t timestamp_; -}; +#include class ReadReceiptsModel : public QAbstractListModel { @@ -60,7 +27,6 @@ public: }; explicit ReadReceiptsModel(QString event_id, QString room_id, QObject *parent = nullptr); - ~ReadReceiptsModel() override; QString eventId() const { return event_id_; } QString roomId() const { return room_id_; } @@ -77,10 +43,11 @@ public slots: void addUsers(const std::multimap> &users); private: + QString dateFormat(const QDateTime &then) const; + QString event_id_; QString room_id_; - QVector readReceipts_; - std::multimap> users_; + QVector> readReceipts_; }; #endif // READRECEIPTSMODEL_H From 8a329d65174d091a5c4d1542d0e74c7d576ee3c6 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 24 Jul 2021 09:16:29 -0400 Subject: [PATCH 04/20] Remove Avatar class RIP --- CMakeLists.txt | 2 - src/MemberList.cpp | 1 - src/MemberList.h | 3 +- src/ui/Avatar.cpp | 168 --------------------------------------------- src/ui/Avatar.h | 48 ------------- 5 files changed, 2 insertions(+), 220 deletions(-) delete mode 100644 src/ui/Avatar.cpp delete mode 100644 src/ui/Avatar.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e9371579..8fc8e19d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -304,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 @@ -516,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 diff --git a/src/MemberList.cpp b/src/MemberList.cpp index 0ef3b696..fb4f5ac2 100644 --- a/src/MemberList.cpp +++ b/src/MemberList.cpp @@ -20,7 +20,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/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_; -}; From 9c7bde22d10eef049c4fc267b3149db1b2f22343 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 24 Jul 2021 09:16:58 -0400 Subject: [PATCH 05/20] Remove unused headers Why didn't I see these earlier? --- src/MemberList.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/MemberList.cpp b/src/MemberList.cpp index fb4f5ac2..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" From 2be91b591dde67377cdd0c6125b8faf5a6b79cdd Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 24 Jul 2021 09:17:06 -0400 Subject: [PATCH 06/20] make lint --- src/ReadReceiptsModel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h index 98e41f8f..d7ff5fb8 100644 --- a/src/ReadReceiptsModel.h +++ b/src/ReadReceiptsModel.h @@ -6,9 +6,9 @@ #define READRECEIPTSMODEL_H #include +#include #include #include -#include class ReadReceiptsModel : public QAbstractListModel { From b03a1df19da3e7e6732cff7b21743d214336d00d Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 24 Jul 2021 12:51:45 -0400 Subject: [PATCH 07/20] Add close button at footer --- resources/qml/ReadReceipts.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml index 21b9b15e..b3bca9db 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/ReadReceipts.qml @@ -115,4 +115,10 @@ ApplicationWindow { } + footer: DialogButtonBox { + standardButtons: DialogButtonBox.Ok + onAccepted: readReceiptsRoot.close() + + } + } From 3ce7fdd63fda4fa31ee444448c829debe5e408f2 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 24 Jul 2021 14:42:40 -0400 Subject: [PATCH 08/20] Fix incorrect function name --- resources/qml/MessageView.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 { From 2fe010c04a90bb232f077a513a7ef6e31a97621a Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 24 Jul 2021 15:35:28 -0400 Subject: [PATCH 09/20] Dynamically update read receipts --- resources/qml/ReadReceipts.qml | 1 - src/ReadReceiptsModel.cpp | 27 ++++++++++++++++++++++++--- src/ReadReceiptsModel.h | 1 + 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml index b3bca9db..0756a2e7 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/ReadReceipts.qml @@ -118,7 +118,6 @@ ApplicationWindow { footer: DialogButtonBox { standardButtons: DialogButtonBox.Ok onAccepted: readReceiptsRoot.close() - } } diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index 8ee9cf45..8a371922 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -7,6 +7,7 @@ #include #include "Cache.h" +#include "Cache_p.h" #include "Logging.h" #include "Utils.h" @@ -16,10 +17,26 @@ ReadReceiptsModel::ReadReceiptsModel(QString event_id, QString room_id, QObject , room_id_{room_id} { try { - addUsers(cache::readReceipts(event_id, room_id)); + addUsers(cache::readReceipts(event_id_, room_id_)); } catch (const lmdb::error &) { nhlog::db()->warn("failed to retrieve read receipts for {} {}", - event_id.toStdString(), + 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; @@ -59,7 +76,9 @@ void ReadReceiptsModel::addUsers( const std::multimap> &users) { - beginInsertRows(QModelIndex{}, readReceipts_.length(), users.size() - 1); + auto oldLen = readReceipts_.length(); + + beginInsertRows(QModelIndex{}, oldLen, users.size() - 1); readReceipts_.clear(); for (const auto &user : users) { @@ -74,6 +93,8 @@ ReadReceiptsModel::addUsers( }); endInsertRows(); + + emit dataChanged(index(0), index(oldLen - 1)); } QString diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h index d7ff5fb8..f2e39f88 100644 --- a/src/ReadReceiptsModel.h +++ b/src/ReadReceiptsModel.h @@ -41,6 +41,7 @@ public: public slots: void addUsers(const std::multimap> &users); + void update(); private: QString dateFormat(const QDateTime &then) const; From 9dc9152e075804200158a1f6c6fb8f6e10961221 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 24 Jul 2021 18:38:22 -0400 Subject: [PATCH 10/20] Close dialog on escape --- resources/qml/ReadReceipts.qml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml index 0756a2e7..da2a5f66 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/ReadReceipts.qml @@ -21,6 +21,11 @@ ApplicationWindow { palette: Nheko.colors color: Nheko.colors.window + Shortcut { + sequence: StandardKey.Cancel + onActivated: readReceiptsRoot.close() + } + ColumnLayout { anchors.fill: parent anchors.margins: Nheko.paddingMedium From 5d38b96bbba01369e3b0238579bab64f74079fc5 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Wed, 28 Jul 2021 17:50:49 -0400 Subject: [PATCH 11/20] Use Dialog flag to make tiling WMs happy --- resources/qml/ReadReceipts.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml index da2a5f66..84dc5666 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/ReadReceipts.qml @@ -20,6 +20,7 @@ ApplicationWindow { minimumWidth: headerTitle.width + 2 * Nheko.paddingMedium palette: Nheko.colors color: Nheko.colors.window + flags: Qt.Dialog Shortcut { sequence: StandardKey.Cancel From 1777a1b52ffcb4e2d3fa0c394b14b3282ef6f3d5 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Wed, 28 Jul 2021 18:20:23 -0400 Subject: [PATCH 12/20] Reset model instead of doing weird convoluted updates --- src/ReadReceiptsModel.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index 8a371922..936c6d61 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -76,9 +76,7 @@ void ReadReceiptsModel::addUsers( const std::multimap> &users) { - auto oldLen = readReceipts_.length(); - - beginInsertRows(QModelIndex{}, oldLen, users.size() - 1); + beginResetModel(); readReceipts_.clear(); for (const auto &user : users) { @@ -92,9 +90,7 @@ ReadReceiptsModel::addUsers( return a.second > b.second; }); - endInsertRows(); - - emit dataChanged(index(0), index(oldLen - 1)); + endResetModel(); } QString From 7e538851d6e3779434722e56a968e9f8b8a9da0d Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Wed, 28 Jul 2021 21:31:37 -0400 Subject: [PATCH 13/20] Use a QSortFilterProxyModel instead of resetting the model --- CMakeLists.txt | 4 +- resources/qml/ReadReceipts.qml | 4 +- src/ReadReceiptsModel.cpp | 55 +++++++++++++++++++--------- src/ReadReceiptsModel.h | 27 ++++++++++++-- src/timeline/TimelineModel.cpp | 2 +- src/timeline/TimelineModel.h | 2 +- src/timeline/TimelineViewManager.cpp | 6 +-- 7 files changed, 71 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fc8e19d..80ea628f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -350,7 +350,7 @@ set(SRC_FILES src/MemberList.cpp src/MxcImageProvider.cpp src/Olm.cpp - src/ReadReceiptsModel.cpp + src/ReadReceiptsModel.cpp src/RegisterPage.cpp src/SSOHandler.cpp src/CombinedImagePackModel.cpp @@ -555,7 +555,7 @@ qt5_wrap_cpp(MOC_HEADERS src/MainWindow.h src/MemberList.h src/MxcImageProvider.h - src/ReadReceiptsModel.h + src/ReadReceiptsModel.h src/RegisterPage.h src/SSOHandler.h src/CombinedImagePackModel.h diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml index 84dc5666..5f213328 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/ReadReceipts.qml @@ -10,7 +10,7 @@ import im.nheko 1.0 ApplicationWindow { id: readReceiptsRoot - property ReadReceiptsModel readReceipts + property ReadReceiptsProxy readReceipts x: MainWindow.x + (MainWindow.width / 2) - (width / 2) y: MainWindow.y + (MainWindow.height / 2) - (height / 2) @@ -86,7 +86,7 @@ ApplicationWindow { ToolTip.text: model.mxid TapHandler { - onSingleTapped: chat.model.openUserProfile(userId) + onSingleTapped: Rooms.currentRoom.openUserProfile(userId) } CursorShape { diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index 936c6d61..0be22be2 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -46,10 +46,13 @@ ReadReceiptsModel::update() QHash ReadReceiptsModel::roleNames() const { - return {{Mxid, "mxid"}, - {DisplayName, "displayName"}, - {AvatarUrl, "avatarUrl"}, - {Timestamp, "timestamp"}}; + // Note: RawTimestamp is purposely not included here + return { + {Mxid, "mxid"}, + {DisplayName, "displayName"}, + {AvatarUrl, "avatarUrl"}, + {Timestamp, "timestamp"}, + }; } QVariant @@ -67,6 +70,8 @@ ReadReceiptsModel::data(const QModelIndex &index, int role) const 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 {}; } @@ -76,21 +81,22 @@ void ReadReceiptsModel::addUsers( const std::multimap> &users) { - beginResetModel(); + auto newReceipts = users.size() - readReceipts_.size(); - readReceipts_.clear(); - for (const auto &user : users) { - readReceipts_.push_back({QString::fromStdString(user.second), - QDateTime::fromMSecsSinceEpoch(user.first)}); + 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(); } - - std::sort(readReceipts_.begin(), - readReceipts_.end(), - [](const QPair &a, const QPair &b) { - return a.second > b.second; - }); - - endResetModel(); } QString @@ -112,3 +118,18 @@ ReadReceiptsModel::dateFormat(const QDateTime &then) const 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); +} + +bool +ReadReceiptsProxy::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const +{ + // since we are sorting from greatest to least timestamp, return something that looks totally backwards! + return source_left.data().toULongLong() > source_right.data().toULongLong(); +} diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h index f2e39f88..9e26bcd5 100644 --- a/src/ReadReceiptsModel.h +++ b/src/ReadReceiptsModel.h @@ -8,15 +8,13 @@ #include #include #include +#include #include class ReadReceiptsModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY(QString eventId READ eventId CONSTANT) - Q_PROPERTY(QString roomId READ roomId CONSTANT) - public: enum Roles { @@ -24,6 +22,7 @@ public: DisplayName, AvatarUrl, Timestamp, + RawTimestamp, }; explicit ReadReceiptsModel(QString event_id, QString room_id, QObject *parent = nullptr); @@ -51,4 +50,26 @@ private: 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_; } + + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; + +private: + QString event_id_; + QString room_id_; + + ReadReceiptsModel model_; +}; + #endif // READRECEIPTSMODEL_H diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index f5737063..6ae0c4d1 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1092,7 +1092,7 @@ TimelineModel::relatedInfo(QString id) void TimelineModel::showReadReceipts(QString id) { - emit openReadReceiptsDialog(new ReadReceiptsModel{id, roomId(), this}); + emit openReadReceiptsDialog(new ReadReceiptsProxy{id, roomId(), this}); } void diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 82fce257..0d5f7109 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -349,7 +349,7 @@ signals: void typingUsersChanged(std::vector users); void replyChanged(QString reply); void editChanged(QString reply); - void openReadReceiptsDialog(ReadReceiptsModel *rr); + 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 58b0d5a8..76bc127e 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -206,12 +206,12 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par 0, "InviteesModel", "InviteesModel needs to be instantiated on the C++ side"); - qmlRegisterUncreatableType( + qmlRegisterUncreatableType( "im.nheko", 1, 0, - "ReadReceiptsModel", - "ReadReceiptsModel needs to be instantiated on the C++ side"); + "ReadReceiptsProxy", + "ReadReceiptsProxy needs to be instantiated on the C++ side"); static auto self = this; qmlRegisterSingletonType( From 368e13fac38e22b5c0ce4669de63dcdadbb31116 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Thu, 29 Jul 2021 20:49:37 -0400 Subject: [PATCH 14/20] Use built-in sorting so that dynamic updates work --- src/ReadReceiptsModel.cpp | 9 ++------- src/ReadReceiptsModel.h | 2 -- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index 0be22be2..d8b7141f 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -125,11 +125,6 @@ ReadReceiptsProxy::ReadReceiptsProxy(QString event_id, QString room_id, QObject { setSourceModel(&model_); setSortRole(ReadReceiptsModel::RawTimestamp); -} - -bool -ReadReceiptsProxy::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const -{ - // since we are sorting from greatest to least timestamp, return something that looks totally backwards! - return source_left.data().toULongLong() > source_right.data().toULongLong(); + sort(0, Qt::DescendingOrder); + setDynamicSortFilter(true); } diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h index 9e26bcd5..3b45716c 100644 --- a/src/ReadReceiptsModel.h +++ b/src/ReadReceiptsModel.h @@ -63,8 +63,6 @@ public: QString eventId() const { return event_id_; } QString roomId() const { return room_id_; } - bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; - private: QString event_id_; QString room_id_; From 135622e14e8ff3bba32becce722d986e0abf11f5 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Thu, 29 Jul 2021 21:29:09 -0400 Subject: [PATCH 15/20] Don't switch room that read receipt-related stuff is opened in --- resources/qml/ReadReceipts.qml | 5 +++-- resources/qml/Root.qml | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml index 5f213328..db5d2e36 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/ReadReceipts.qml @@ -11,6 +11,7 @@ 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) @@ -65,7 +66,7 @@ ApplicationWindow { userid: model.mxid url: model.avatarUrl.replace("mxc://", "image://MxcImage/") displayName: model.displayName - onClicked: Rooms.currentRoom.openUserProfile(model.mxid) + onClicked: room.openUserProfile(model.mxid) ToolTip.visible: avatarHover.hovered ToolTip.text: model.mxid @@ -86,7 +87,7 @@ ApplicationWindow { ToolTip.text: model.mxid TapHandler { - onSingleTapped: Rooms.currentRoom.openUserProfile(userId) + onSingleTapped: room.openUserProfile(userId) } CursorShape { diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index a099b5e6..a7684af5 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -173,9 +173,10 @@ Page { } Connections { - function onOpenReadReceiptsDialog() { + function onOpenReadReceiptsDialog(rr) { var dialog = readReceiptsDialog.createObject(timelineRoot, { - "readReceipts": rr + "readReceipts": rr, + "room": Rooms.currentRoom }); dialog.show(); } From 330b9d62a580fbd4c79925f511ab4c2e2200ad60 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 30 Jul 2021 07:24:48 -0400 Subject: [PATCH 16/20] Move read receipts connection to allow for future pop-out room views --- resources/qml/Root.qml | 12 ------------ resources/qml/TimelineView.qml | 12 ++++++++++++ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index a7684af5..7d91beae 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -172,18 +172,6 @@ Page { target: TimelineManager } - Connections { - function onOpenReadReceiptsDialog(rr) { - var dialog = readReceiptsDialog.createObject(timelineRoot, { - "readReceipts": rr, - "room": Rooms.currentRoom - }); - dialog.show(); - } - - target: Rooms.currentRoom - } - Connections { function onNewInviteState() { if (CallManager.haveCallInvite && Settings.mobileMode) { 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 + } + } From 3cb4209d7b3c6a0b8455f49b991b897adf302572 Mon Sep 17 00:00:00 2001 From: Loren Burkholder <55629213+LorenDB@users.noreply.github.com> Date: Fri, 30 Jul 2021 07:56:25 -0400 Subject: [PATCH 17/20] Reformat dates Co-authored-by: DeepBlueV7.X --- src/ReadReceiptsModel.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index d8b7141f..562353a7 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -106,13 +106,14 @@ ReadReceiptsModel::dateFormat(const QDateTime &then) const auto days = then.daysTo(now); if (days == 0) - return tr("Today %1") + return tr("Today, %1") .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); else if (days < 2) - return tr("Yesterday %1") + return tr("Yesterday, %1") .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); else if (days < 7) - return QString("%1 %2") + //: %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)); From b398454409e3197ebf1b7d501e106bbb46523073 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 30 Jul 2021 08:14:44 -0400 Subject: [PATCH 18/20] Use an explicit color for the label --- resources/qml/ReadReceipts.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml index db5d2e36..8869d813 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/ReadReceipts.qml @@ -36,6 +36,7 @@ ApplicationWindow { Label { id: headerTitle + color: Nheko.colors.text Layout.alignment: Qt.AlignCenter text: qsTr("Read receipts") font.pointSize: fontMetrics.font.pointSize * 1.5 From 7dcdd51a8b329178152526dee875ba4980e4993d Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 30 Jul 2021 08:19:05 -0400 Subject: [PATCH 19/20] make lint --- src/ReadReceiptsModel.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index 562353a7..059f5d53 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -112,7 +112,8 @@ ReadReceiptsModel::dateFormat(const QDateTime &then) const 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 + //: %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)); From f48f244dcbfa319c5b8092791231fe56ac70bb8d Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 30 Jul 2021 08:44:07 -0400 Subject: [PATCH 20/20] Use correct date format --- src/ReadReceiptsModel.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index 059f5d53..25262c59 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -106,8 +106,7 @@ ReadReceiptsModel::dateFormat(const QDateTime &then) const auto days = then.daysTo(now); if (days == 0) - return tr("Today, %1") - .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); + return QLocale::system().toString(then.time(), QLocale::ShortFormat); else if (days < 2) return tr("Yesterday, %1") .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));