mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 11:00:48 +03:00
Implement avatars in qml timeline
This commit is contained in:
parent
aae295cb02
commit
ebeb1eb772
12 changed files with 190 additions and 8 deletions
|
@ -231,6 +231,7 @@ set(SRC_FILES
|
||||||
src/Logging.cpp
|
src/Logging.cpp
|
||||||
src/MainWindow.cpp
|
src/MainWindow.cpp
|
||||||
src/MatrixClient.cpp
|
src/MatrixClient.cpp
|
||||||
|
src/MxcImageProvider.cpp
|
||||||
src/QuickSwitcher.cpp
|
src/QuickSwitcher.cpp
|
||||||
src/Olm.cpp
|
src/Olm.cpp
|
||||||
src/RegisterPage.cpp
|
src/RegisterPage.cpp
|
||||||
|
|
45
resources/qml/Avatar.qml
Normal file
45
resources/qml/Avatar.qml
Normal 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
|
||||||
|
}
|
|
@ -181,10 +181,11 @@ Rectangle {
|
||||||
Row {
|
Row {
|
||||||
height: userName.height
|
height: userName.height
|
||||||
spacing: 4
|
spacing: 4
|
||||||
Rectangle {
|
Avatar {
|
||||||
width: 48
|
width: 48
|
||||||
height: 48
|
height: 48
|
||||||
color: "green"
|
url: chat.model.avatarUrl(section.split(" ")[0]).replace("mxc://", "image://MxcImage/")
|
||||||
|
displayName: chat.model.displayName(section.split(" ")[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
|
|
@ -116,6 +116,7 @@
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="/">
|
<qresource prefix="/">
|
||||||
<file>qml/TimelineView.qml</file>
|
<file>qml/TimelineView.qml</file>
|
||||||
|
<file>qml/Avatar.qml</file>
|
||||||
<file>qml/delegates/TextMessage.qml</file>
|
<file>qml/delegates/TextMessage.qml</file>
|
||||||
<file>qml/delegates/NoticeMessage.qml</file>
|
<file>qml/delegates/NoticeMessage.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
|
|
79
src/MxcImageProvider.cpp
Normal file
79
src/MxcImageProvider.cpp
Normal 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
48
src/MxcImageProvider.h
Normal 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;
|
||||||
|
};
|
||||||
|
|
|
@ -142,7 +142,7 @@ RoomInfoListItem::resizeEvent(QResizeEvent *)
|
||||||
void
|
void
|
||||||
RoomInfoListItem::paintEvent(QPaintEvent *event)
|
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);
|
Q_UNUSED(event);
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ UserSettings::load()
|
||||||
isReadReceiptsEnabled_ = settings.value("user/read_receipts", true).toBool();
|
isReadReceiptsEnabled_ = settings.value("user/read_receipts", true).toBool();
|
||||||
theme_ = settings.value("user/theme", defaultTheme_).toString();
|
theme_ = settings.value("user/theme", defaultTheme_).toString();
|
||||||
font_ = settings.value("user/font_family", "default").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();
|
emojiFont_ = settings.value("user/emoji_font_family", "default").toString();
|
||||||
baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble();
|
baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble();
|
||||||
|
|
||||||
|
@ -119,9 +119,7 @@ UserSettings::save()
|
||||||
settings.setValue("start_in_tray", isStartInTrayEnabled_);
|
settings.setValue("start_in_tray", isStartInTrayEnabled_);
|
||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
|
|
||||||
settings.beginGroup("avatar");
|
settings.setValue("avatar_circles", avatarCircles_);
|
||||||
settings.setValue("circles", avatarCircles_);
|
|
||||||
settings.endGroup();
|
|
||||||
|
|
||||||
settings.setValue("font_size", baseFontSize_);
|
settings.setValue("font_size", baseFontSize_);
|
||||||
settings.setValue("typing_notifications", isTypingNotificationsEnabled_);
|
settings.setValue("typing_notifications", isTypingNotificationsEnabled_);
|
||||||
|
|
|
@ -325,6 +325,12 @@ TimelineModel::displayName(QString id) const
|
||||||
return Cache::displayName(room_id_, id);
|
return Cache::displayName(room_id_, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString
|
||||||
|
TimelineModel::avatarUrl(QString id) const
|
||||||
|
{
|
||||||
|
return Cache::avatarUrl(room_id_, id);
|
||||||
|
}
|
||||||
|
|
||||||
QString
|
QString
|
||||||
TimelineModel::formatDateSeparator(QDate date) const
|
TimelineModel::formatDateSeparator(QDate date) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -90,6 +90,7 @@ public:
|
||||||
|
|
||||||
Q_INVOKABLE QColor userColor(QString id, QColor background);
|
Q_INVOKABLE QColor userColor(QString id, QColor background);
|
||||||
Q_INVOKABLE QString displayName(QString id) const;
|
Q_INVOKABLE QString displayName(QString id) const;
|
||||||
|
Q_INVOKABLE QString avatarUrl(QString id) const;
|
||||||
Q_INVOKABLE QString formatDateSeparator(QDate date) const;
|
Q_INVOKABLE QString formatDateSeparator(QDate date) const;
|
||||||
Q_INVOKABLE QString escapeEmoji(QString str) const;
|
Q_INVOKABLE QString escapeEmoji(QString str) const;
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
|
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
|
#include "MxcImageProvider.h"
|
||||||
|
|
||||||
TimelineViewManager::TimelineViewManager(QWidget *parent)
|
TimelineViewManager::TimelineViewManager(QWidget *parent)
|
||||||
{
|
{
|
||||||
|
@ -18,6 +19,7 @@ TimelineViewManager::TimelineViewManager(QWidget *parent)
|
||||||
container = QWidget::createWindowContainer(view, parent);
|
container = QWidget::createWindowContainer(view, parent);
|
||||||
container->setMinimumSize(200, 200);
|
container->setMinimumSize(200, 200);
|
||||||
view->rootContext()->setContextProperty("timelineManager", this);
|
view->rootContext()->setContextProperty("timelineManager", this);
|
||||||
|
view->engine()->addImageProvider("MxcImage", new MxcImageProvider());
|
||||||
view->setSource(QUrl("qrc:///qml/TimelineView.qml"));
|
view->setSource(QUrl("qrc:///qml/TimelineView.qml"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,7 @@ Avatar::setIcon(const QIcon &icon)
|
||||||
void
|
void
|
||||||
Avatar::paintEvent(QPaintEvent *)
|
Avatar::paintEvent(QPaintEvent *)
|
||||||
{
|
{
|
||||||
bool rounded = QSettings().value("user/avatar/circles", true).toBool();
|
bool rounded = QSettings().value("user/avatar_circles", true).toBool();
|
||||||
|
|
||||||
QPainter painter(this);
|
QPainter painter(this);
|
||||||
painter.setRenderHint(QPainter::Antialiasing);
|
painter.setRenderHint(QPainter::Antialiasing);
|
||||||
|
|
Loading…
Reference in a new issue