diff options
author | Loren Burkholder <computersemiexpert@outlook.com> | 2021-07-23 18:11:33 -0400 |
---|---|---|
committer | Loren Burkholder <computersemiexpert@outlook.com> | 2021-07-29 21:07:16 -0400 |
commit | 4dd994ae009b622cd35e292d1170a3f60a26c4d6 (patch) | |
tree | af3a825decdd96963a9cf0c9efcd57a3d8db1ed4 /src | |
parent | Port to explicit connect syntax (diff) | |
download | nheko-4dd994ae009b622cd35e292d1170a3f60a26c4d6.tar.xz |
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
Diffstat (limited to 'src')
-rw-r--r-- | src/ChatPage.cpp | 1 | ||||
-rw-r--r-- | src/MainWindow.cpp | 22 | ||||
-rw-r--r-- | src/MainWindow.h | 1 | ||||
-rw-r--r-- | src/ReadReceiptsModel.cpp | 120 | ||||
-rw-r--r-- | src/ReadReceiptsModel.h | 86 | ||||
-rw-r--r-- | src/dialogs/ReadReceipts.cpp | 179 | ||||
-rw-r--r-- | src/dialogs/ReadReceipts.h | 61 | ||||
-rw-r--r-- | src/timeline/TimelineModel.cpp | 5 | ||||
-rw-r--r-- | src/timeline/TimelineModel.h | 4 | ||||
-rw-r--r-- | src/timeline/TimelineViewManager.cpp | 7 |
10 files changed, 219 insertions, 267 deletions
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<void(const mtx::requests::CreateRoom &request)> callback); void openJoinRoomDialog(std::function<void(const QString &room_id)> 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 <QLocale> + +#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<int, QByteArray> +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<uint64_t, std::string, std::greater<uint64_t>> &users) +{ + std::multimap<uint64_t, std::string, std::greater<uint64_t>> 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 <QAbstractListModel> +#include <QObject> +#include <QString> + +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<int, QByteArray> 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<uint64_t, std::string, std::greater<uint64_t>> &users); + +private: + QString event_id_; + QString room_id_; + QVector<ReadReceipt *> readReceipts_; + std::multimap<uint64_t, std::string, std::greater<uint64_t>> 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 <QDebug> -#include <QIcon> -#include <QLabel> -#include <QListWidgetItem> -#include <QPainter> -#include <QPushButton> -#include <QShortcut> -#include <QStyleOption> -#include <QTimer> -#include <QVBoxLayout> - -#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<uint64_t, std::string, std::greater<uint64_t>> &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 <QDateTime> -#include <QFrame> - -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<uint64_t, std::string, std::greater<uint64_t>> &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<QString> 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<ReadReceiptsModel>( + "im.nheko", + 1, + 0, + "ReadReceiptsModel", + "ReadReceiptsModel needs to be instantiated on the C++ side"); static auto self = this; qmlRegisterSingletonType<MainWindow>( |