summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--resources/qml/Avatar.qml45
-rw-r--r--resources/qml/TimelineView.qml5
-rw-r--r--resources/res.qrc1
-rw-r--r--src/MxcImageProvider.cpp79
-rw-r--r--src/MxcImageProvider.h48
-rw-r--r--src/RoomInfoListItem.cpp2
-rw-r--r--src/UserSettingsPage.cpp6
-rw-r--r--src/timeline2/TimelineModel.cpp6
-rw-r--r--src/timeline2/TimelineModel.h1
-rw-r--r--src/timeline2/TimelineViewManager.cpp2
-rw-r--r--src/ui/Avatar.cpp2
12 files changed, 190 insertions, 8 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8013fed9..d386efbf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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
diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml
new file mode 100644
index 00000000..9d7b54fe
--- /dev/null
+++ b/resources/qml/Avatar.qml
@@ -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
+}
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 5f068e57..0151686a 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -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 { 
diff --git a/resources/res.qrc b/resources/res.qrc
index b18835fb..6f6d480a 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -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>
diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp
new file mode 100644
index 00000000..305439fc
--- /dev/null
+++ b/src/MxcImageProvider.cpp
@@ -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();
+                  });
+        }
+}
diff --git a/src/MxcImageProvider.h b/src/MxcImageProvider.h
new file mode 100644
index 00000000..8710171c
--- /dev/null
+++ b/src/MxcImageProvider.h
@@ -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;
+};
+
diff --git a/src/RoomInfoListItem.cpp b/src/RoomInfoListItem.cpp
index 8aadbea2..f135451c 100644
--- a/src/RoomInfoListItem.cpp
+++ b/src/RoomInfoListItem.cpp
@@ -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);
 
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 9fd033e9..1caea449 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -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_);
diff --git a/src/timeline2/TimelineModel.cpp b/src/timeline2/TimelineModel.cpp
index 28820205..310494b4 100644
--- a/src/timeline2/TimelineModel.cpp
+++ b/src/timeline2/TimelineModel.cpp
@@ -326,6 +326,12 @@ TimelineModel::displayName(QString id) const
 }
 
 QString
+TimelineModel::avatarUrl(QString id) const
+{
+        return Cache::avatarUrl(room_id_, id);
+}
+
+QString
 TimelineModel::formatDateSeparator(QDate date) const
 {
         auto now = QDateTime::currentDateTime();
diff --git a/src/timeline2/TimelineModel.h b/src/timeline2/TimelineModel.h
index e37c6542..954da5eb 100644
--- a/src/timeline2/TimelineModel.h
+++ b/src/timeline2/TimelineModel.h
@@ -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;
 
diff --git a/src/timeline2/TimelineViewManager.cpp b/src/timeline2/TimelineViewManager.cpp
index 0e0e74e4..eb9bea54 100644
--- a/src/timeline2/TimelineViewManager.cpp
+++ b/src/timeline2/TimelineViewManager.cpp
@@ -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"));
 }
 
diff --git a/src/ui/Avatar.cpp b/src/ui/Avatar.cpp
index 501a8968..e4a90f81 100644
--- a/src/ui/Avatar.cpp
+++ b/src/ui/Avatar.cpp
@@ -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);