Implement avatars in qml timeline

This commit is contained in:
Nicolas Werner 2019-09-07 22:22:07 +02:00
parent aae295cb02
commit ebeb1eb772
12 changed files with 190 additions and 8 deletions

View file

@ -231,6 +231,7 @@ set(SRC_FILES
src/Logging.cpp
src/MainWindow.cpp
src/MatrixClient.cpp
src/MxcImageProvider.cpp
src/QuickSwitcher.cpp
src/Olm.cpp
src/RegisterPage.cpp

45
resources/qml/Avatar.qml Normal file
View file

@ -0,0 +1,45 @@
import QtQuick 2.6
import QtGraphicalEffects 1.0
import Qt.labs.settings 1.0
Rectangle {
id: avatar
width: 48
height: 48
radius: settings.avatar_circles ? height/2 : 3
Settings {
id: settings
category: "user"
property bool avatar_circles: true
}
property alias url: img.source
property string displayName
Text {
anchors.fill: parent
text: String.fromCodePoint(displayName.codePointAt(0))
color: colors.text
font.pixelSize: avatar.height/2
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
Image {
id: img
anchors.fill: parent
asynchronous: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
anchors.fill: parent
width: avatar.width
height: avatar.height
radius: settings.avatar_circles ? height/2 : 3
}
}
}
color: colors.dark
}

View file

@ -181,10 +181,11 @@ Rectangle {
Row {
height: userName.height
spacing: 4
Rectangle {
Avatar {
width: 48
height: 48
color: "green"
url: chat.model.avatarUrl(section.split(" ")[0]).replace("mxc://", "image://MxcImage/")
displayName: chat.model.displayName(section.split(" ")[0])
}
Text {

View file

@ -116,6 +116,7 @@
</qresource>
<qresource prefix="/">
<file>qml/TimelineView.qml</file>
<file>qml/Avatar.qml</file>
<file>qml/delegates/TextMessage.qml</file>
<file>qml/delegates/NoticeMessage.qml</file>
</qresource>

79
src/MxcImageProvider.cpp Normal file
View file

@ -0,0 +1,79 @@
#include "MxcImageProvider.h"
#include "Cache.h"
void
MxcImageResponse::run()
{
if (m_requestedSize.isValid()) {
QString fileName = QString("%1_%2x%3")
.arg(m_id)
.arg(m_requestedSize.width())
.arg(m_requestedSize.height());
auto data = cache::client()->image(fileName);
if (!data.isNull() && m_image.loadFromData(data)) {
m_image = m_image.scaled(m_requestedSize, Qt::KeepAspectRatio);
m_image.setText("mxc url", "mxc://" + m_id);
emit finished();
return;
}
mtx::http::ThumbOpts opts;
opts.mxc_url = "mxc://" + m_id.toStdString();
opts.width = m_requestedSize.width() > 0 ? m_requestedSize.width() : -1;
opts.height = m_requestedSize.height() > 0 ? m_requestedSize.height() : -1;
opts.method = "scale";
http::client()->get_thumbnail(
opts, [this, fileName](const std::string &res, mtx::http::RequestErr err) {
if (err) {
nhlog::net()->error("Failed to download image {}",
m_id.toStdString());
m_error = "Failed download";
emit finished();
return;
}
auto data = QByteArray(res.data(), res.size());
cache::client()->saveImage(fileName, data);
m_image.loadFromData(data);
m_image = m_image.scaled(m_requestedSize, Qt::KeepAspectRatio);
m_image.setText("mxc url", "mxc://" + m_id);
emit finished();
});
} else {
auto data = cache::client()->image(m_id);
if (!data.isNull() && m_image.loadFromData(data)) {
m_image.setText("mxc url", "mxc://" + m_id);
emit finished();
return;
}
http::client()->download(
"mxc://" + m_id.toStdString(),
[this](const std::string &res,
const std::string &,
const std::string &originalFilename,
mtx::http::RequestErr err) {
if (err) {
nhlog::net()->error("Failed to download image {}",
m_id.toStdString());
m_error = "Failed download";
emit finished();
return;
}
auto data = QByteArray(res.data(), res.size());
m_image.loadFromData(data);
m_image.setText("original filename",
QString::fromStdString(originalFilename));
m_image.setText("mxc url", "mxc://" + m_id);
cache::client()->saveImage(m_id, data);
emit finished();
});
}
}

48
src/MxcImageProvider.h Normal file
View file

@ -0,0 +1,48 @@
#pragma once
#include <QQuickAsyncImageProvider>
#include <QQuickImageResponse>
#include <QImage>
#include <QThreadPool>
class MxcImageResponse
: public QQuickImageResponse
, public QRunnable
{
public:
MxcImageResponse(const QString &id, const QSize &requestedSize)
: m_id(id)
, m_requestedSize(requestedSize)
{
setAutoDelete(false);
}
QQuickTextureFactory *textureFactory() const override
{
return QQuickTextureFactory::textureFactoryForImage(m_image);
}
QString errorString() const override { return m_error; }
void run() override;
QString m_id, m_error;
QSize m_requestedSize;
QImage m_image;
};
class MxcImageProvider : public QQuickAsyncImageProvider
{
public:
QQuickImageResponse *requestImageResponse(const QString &id,
const QSize &requestedSize) override
{
MxcImageResponse *response = new MxcImageResponse(id, requestedSize);
pool.start(response);
return response;
}
private:
QThreadPool pool;
};

View file

@ -142,7 +142,7 @@ RoomInfoListItem::resizeEvent(QResizeEvent *)
void
RoomInfoListItem::paintEvent(QPaintEvent *event)
{
bool rounded = QSettings().value("user/avatar/circles", true).toBool();
bool rounded = QSettings().value("user/avatar_circles", true).toBool();
Q_UNUSED(event);

View file

@ -53,7 +53,7 @@ UserSettings::load()
isReadReceiptsEnabled_ = settings.value("user/read_receipts", true).toBool();
theme_ = settings.value("user/theme", defaultTheme_).toString();
font_ = settings.value("user/font_family", "default").toString();
avatarCircles_ = settings.value("user/avatar/circles", true).toBool();
avatarCircles_ = settings.value("user/avatar_circles", true).toBool();
emojiFont_ = settings.value("user/emoji_font_family", "default").toString();
baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble();
@ -119,9 +119,7 @@ UserSettings::save()
settings.setValue("start_in_tray", isStartInTrayEnabled_);
settings.endGroup();
settings.beginGroup("avatar");
settings.setValue("circles", avatarCircles_);
settings.endGroup();
settings.setValue("avatar_circles", avatarCircles_);
settings.setValue("font_size", baseFontSize_);
settings.setValue("typing_notifications", isTypingNotificationsEnabled_);

View file

@ -325,6 +325,12 @@ TimelineModel::displayName(QString id) const
return Cache::displayName(room_id_, id);
}
QString
TimelineModel::avatarUrl(QString id) const
{
return Cache::avatarUrl(room_id_, id);
}
QString
TimelineModel::formatDateSeparator(QDate date) const
{

View file

@ -90,6 +90,7 @@ public:
Q_INVOKABLE QColor userColor(QString id, QColor background);
Q_INVOKABLE QString displayName(QString id) const;
Q_INVOKABLE QString avatarUrl(QString id) const;
Q_INVOKABLE QString formatDateSeparator(QDate date) const;
Q_INVOKABLE QString escapeEmoji(QString str) const;

View file

@ -4,6 +4,7 @@
#include <QQmlContext>
#include "Logging.h"
#include "MxcImageProvider.h"
TimelineViewManager::TimelineViewManager(QWidget *parent)
{
@ -18,6 +19,7 @@ TimelineViewManager::TimelineViewManager(QWidget *parent)
container = QWidget::createWindowContainer(view, parent);
container->setMinimumSize(200, 200);
view->rootContext()->setContextProperty("timelineManager", this);
view->engine()->addImageProvider("MxcImage", new MxcImageProvider());
view->setSource(QUrl("qrc:///qml/TimelineView.qml"));
}

View file

@ -101,7 +101,7 @@ Avatar::setIcon(const QIcon &icon)
void
Avatar::paintEvent(QPaintEvent *)
{
bool rounded = QSettings().value("user/avatar/circles", true).toBool();
bool rounded = QSettings().value("user/avatar_circles", true).toBool();
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);