Merge pull request #655 from LorenDB/qml-all-the-things

QML all the things, part 2: Read receipts dialog
This commit is contained in:
DeepBlueV7.X 2021-07-30 22:08:51 +00:00 committed by GitHub
commit 5b5a89b522
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 375 additions and 502 deletions

View File

@ -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

View File

@ -580,7 +580,7 @@ ScrollView {
Platform.MenuItem {
text: qsTr("Read receip&ts")
onTriggered: room.readReceiptsAction(messageContextMenu.eventId)
onTriggered: room.showReadReceipts(messageContextMenu.eventId)
}
Platform.MenuItem {

View File

@ -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()
}
}

View File

@ -96,6 +96,14 @@ Page {
}
Component {
id: readReceiptsDialog
ReadReceipts {
}
}
Shortcut {
sequence: "Ctrl+K"
onActivated: {

View File

@ -34,7 +34,7 @@ ImageButton {
}
onClicked: {
if (status == MtxEvent.Read)
room.readReceiptsAction(eventId);
room.showReadReceipts(eventId);
}
image: {

View File

@ -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
}
}

View File

@ -112,7 +112,6 @@
</qresource>
<qresource prefix="/">
<file>qtquickcontrols2.conf</file>
<file>qml/Root.qml</file>
<file>qml/ChatPage.qml</file>
<file>qml/CommunitiesList.qml</file>
@ -177,6 +176,7 @@
<file>qml/components/FlatButton.qml</file>
<file>qml/RoomMembers.qml</file>
<file>qml/InviteDialog.qml</file>
<file>qml/ReadReceipts.qml</file>
</qresource>
<qresource prefix="/media">
<file>media/ring.ogg</file>

View File

@ -31,7 +31,6 @@
#include "notifications/Manager.h"
#include "dialogs/ReadReceipts.h"
#include "timeline/TimelineViewManager.h"
#include "blurhash.hpp"

View File

@ -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
{

View File

@ -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,

View File

@ -2,16 +2,6 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include <QAbstractSlider>
#include <QLabel>
#include <QListWidgetItem>
#include <QPainter>
#include <QPushButton>
#include <QScrollBar>
#include <QShortcut>
#include <QStyleOption>
#include <QVBoxLayout>
#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}

View File

@ -4,9 +4,10 @@
#pragma once
#include "CacheStructs.h"
#include <QAbstractListModel>
#include "CacheStructs.h"
class MemberList : public QAbstractListModel
{
Q_OBJECT

131
src/ReadReceiptsModel.cpp Normal file
View File

@ -0,0 +1,131 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "ReadReceiptsModel.h"
#include <QLocale>
#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<int, QByteArray>
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<uint64_t, std::string, std::greater<uint64_t>> &users)
{
auto newReceipts = users.size() - readReceipts_.size();
if (newReceipts > 0) {
beginInsertRows(
QModelIndex{}, readReceipts_.size(), readReceipts_.size() + newReceipts - 1);
for (const auto &user : users) {
QPair<QString, QDateTime> 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);
}

73
src/ReadReceiptsModel.h Normal file
View File

@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef READRECEIPTSMODEL_H
#define READRECEIPTSMODEL_H
#include <QAbstractListModel>
#include <QDateTime>
#include <QObject>
#include <QSortFilterProxyModel>
#include <QString>
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<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);
void update();
private:
QString dateFormat(const QDateTime &then) const;
QString event_id_;
QString room_id_;
QVector<QPair<QString, QDateTime>> 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

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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(ReadReceiptsProxy *rr);
void paginationInProgressChanged(const bool);
void newCallEvent(const mtx::events::collections::TimelineEvents &event);
void scrollToIndex(int index);

View File

@ -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<ReadReceiptsProxy>(
"im.nheko",
1,
0,
"ReadReceiptsProxy",
"ReadReceiptsProxy needs to be instantiated on the C++ side");
static auto self = this;
qmlRegisterSingletonType<MainWindow>(

View File

@ -1,168 +0,0 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include <QPainter>
#include <QPainterPath>
#include <QSettings>
#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<int>(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<int>(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;
}
}

View File

@ -1,48 +0,0 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QImage>
#include <QPixmap>
#include <QWidget>
#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_;
};