mirror of https://github.com/Nheko-Reborn/nheko
There are probably a few things wrong with this, but I'm going to call it good enough for an initial commitpull/655/head
parent
d955444dc1
commit
4dd994ae00
@ -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 |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
@ -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
|
@ -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); |
||||
} |
@ -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
|
Loading…
Reference in new issue