From ebeb1eb7721f357b016f6e914509918b6bee5356 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 7 Sep 2019 22:22:07 +0200 Subject: Implement avatars in qml timeline --- src/MxcImageProvider.cpp | 79 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/MxcImageProvider.cpp (limited to 'src/MxcImageProvider.cpp') 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(); + }); + } +} -- cgit 1.5.1 From 8ebef4eed2134179e5609104eb72fe8f055a35f1 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 9 Oct 2019 00:36:03 +0200 Subject: Size images/videos by timeline width --- resources/qml/delegates/ImageMessage.qml | 4 ++-- resources/qml/delegates/PlayableMediaMessage.qml | 27 ++++++++++++++++++------ src/AvatarProvider.cpp | 4 ++-- src/MxcImageProvider.cpp | 3 ++- src/timeline2/TimelineModel.cpp | 19 +++++++++++++++++ src/timeline2/TimelineModel.h | 1 + 6 files changed, 46 insertions(+), 12 deletions(-) (limited to 'src/MxcImageProvider.cpp') diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index 70d2debe..f1e95e3d 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -3,8 +3,8 @@ import QtQuick 2.6 import com.github.nheko 1.0 Item { - width: 300 - height: 300 * model.proportionalHeight + width: Math.min(parent.width, model.width) + height: width * model.proportionalHeight Image { id: img diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml index c716d21d..3a518617 100644 --- a/resources/qml/delegates/PlayableMediaMessage.qml +++ b/resources/qml/delegates/PlayableMediaMessage.qml @@ -6,26 +6,38 @@ import QtMultimedia 5.6 import com.github.nheko 1.0 Rectangle { + id: bg radius: 10 color: colors.dark height: content.height + 24 width: parent.width - ColumnLayout { + Column { id: content width: parent.width - 24 anchors.centerIn: parent - VideoOutput { + Rectangle { + id: videoContainer visible: model.type == MtxEvent.VideoMessage - Layout.maximumHeight: 300 - Layout.minimumHeight: 300 - Layout.maximumWidth: 500 - fillMode: VideoOutput.PreserveAspectFit - source: media + width: Math.min(parent.width, model.width) + height: width*model.proportionalHeight + Image { + anchors.fill: parent + source: model.thumbnailUrl.replace("mxc://", "image://MxcImage/") + asynchronous: true + fillMode: Image.PreserveAspectFit + + VideoOutput { + anchors.fill: parent + fillMode: VideoOutput.PreserveAspectFit + source: media + } + } } RowLayout { + width: parent.width Text { id: positionText text: "--:--:--" @@ -102,6 +114,7 @@ Rectangle { id: media onError: console.log(errorString) onStatusChanged: if(status == MediaPlayer.Loaded) progress.updatePositionTexts() + onStopped: button.state = "stopped" } Connections { diff --git a/src/AvatarProvider.cpp b/src/AvatarProvider.cpp index c83ffe0f..68b6901e 100644 --- a/src/AvatarProvider.cpp +++ b/src/AvatarProvider.cpp @@ -67,8 +67,8 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca }); mtx::http::ThumbOpts opts; - opts.width = 256; - opts.height = 256; + opts.width = size; + opts.height = size; opts.mxc_url = avatarUrl.toStdString(); http::client()->get_thumbnail( diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index 305439fc..86dbcabc 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -38,7 +38,8 @@ MxcImageResponse::run() 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 = m_image.scaled( + m_requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); m_image.setText("mxc url", "mxc://" + m_id); emit finished(); diff --git a/src/timeline2/TimelineModel.cpp b/src/timeline2/TimelineModel.cpp index b3ddf899..27bd09b6 100644 --- a/src/timeline2/TimelineModel.cpp +++ b/src/timeline2/TimelineModel.cpp @@ -106,6 +106,21 @@ eventUrl(const mtx::events::RoomEvent &e) return QString::fromStdString(e.content.url); } +template +QString +eventThumbnailUrl(const mtx::events::Event &) +{ + return ""; +} +template +auto +eventThumbnailUrl(const mtx::events::RoomEvent &e) + -> std::enable_if_t::value, + QString> +{ + return QString::fromStdString(e.content.info.thumbnail_url); +} + template QString eventFilename(const mtx::events::Event &) @@ -355,6 +370,7 @@ TimelineModel::roleNames() const {UserName, "userName"}, {Timestamp, "timestamp"}, {Url, "url"}, + {ThumbnailUrl, "thumbnailUrl"}, {Filename, "filename"}, {Filesize, "filesize"}, {MimeType, "mimetype"}, @@ -436,6 +452,9 @@ TimelineModel::data(const QModelIndex &index, int role) const case Url: return QVariant(boost::apply_visitor( [](const auto &e) -> QString { return eventUrl(e); }, event)); + case ThumbnailUrl: + return QVariant(boost::apply_visitor( + [](const auto &e) -> QString { return eventThumbnailUrl(e); }, event)); case Filename: return QVariant(boost::apply_visitor( [](const auto &e) -> QString { return eventFilename(e); }, event)); diff --git a/src/timeline2/TimelineModel.h b/src/timeline2/TimelineModel.h index 35ec325d..b7ff546b 100644 --- a/src/timeline2/TimelineModel.h +++ b/src/timeline2/TimelineModel.h @@ -129,6 +129,7 @@ public: UserName, Timestamp, Url, + ThumbnailUrl, Filename, Filesize, MimeType, -- cgit 1.5.1 From 4f7a45a0a6b28255fb4c1ccbb53fcf8a6958843a Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 3 Nov 2019 01:17:40 +0100 Subject: Improve avatar look and layouting Thanks to red_sky for the feedback! --- resources/qml/Avatar.qml | 5 +++++ resources/qml/TimelineRow.qml | 1 - resources/qml/TimelineView.qml | 7 +++++-- resources/qml/delegates/MessageDelegate.qml | 2 -- src/MxcImageProvider.cpp | 6 ++---- 5 files changed, 12 insertions(+), 9 deletions(-) (limited to 'src/MxcImageProvider.cpp') diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index 131e6b46..a53f057b 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -31,6 +31,11 @@ Rectangle { anchors.fill: parent asynchronous: true fillMode: Image.PreserveAspectCrop + mipmap: true + smooth: false + + sourceSize.width: avatar.width + sourceSize.height: avatar.height layer.enabled: true layer.effect: OpacityMask { diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index 66d44622..b9fa6f40 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -12,7 +12,6 @@ RowLayout { property var view: chat anchors.leftMargin: avatarSize + 4 - anchors.rightMargin: scrollbar.width anchors.left: parent.left anchors.right: parent.right diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index e5c1bda6..8f64637e 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -14,7 +14,7 @@ Rectangle { property var colors: currentActivePalette property var systemInactive: SystemPalette { colorGroup: SystemPalette.Disabled } property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive - property int avatarSize: 32 + property int avatarSize: 40 color: colors.window @@ -34,6 +34,9 @@ Rectangle { visible: timelineManager.timeline != null anchors.fill: parent + anchors.leftMargin: 4 + anchors.rightMargin: scrollbar.width + model: timelineManager.timeline onModelChanged: { @@ -54,7 +57,7 @@ Rectangle { ScrollBar.vertical: ScrollBar { id: scrollbar anchors.top: parent.top - anchors.right: parent.right + anchors.left: parent.right anchors.bottom: parent.bottom onPressedChanged: if (!pressed) chat.updatePosition() } diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index 3d892b76..49209f68 100644 --- a/resources/qml/delegates/MessageDelegate.qml +++ b/resources/qml/delegates/MessageDelegate.qml @@ -5,8 +5,6 @@ DelegateChooser { //role: "type" //< not supported in our custom implementation, have to use roleValue roleValue: model.type - width: parent.width - DelegateChoice { roleValue: MtxEvent.TextMessage TextMessage {} diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index 86dbcabc..556b019b 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -6,7 +6,7 @@ void MxcImageResponse::run() { if (m_requestedSize.isValid()) { - QString fileName = QString("%1_%2x%3") + QString fileName = QString("%1_%2x%3_crop") .arg(m_id) .arg(m_requestedSize.width()) .arg(m_requestedSize.height()); @@ -23,7 +23,7 @@ MxcImageResponse::run() 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"; + opts.method = "crop"; http::client()->get_thumbnail( opts, [this, fileName](const std::string &res, mtx::http::RequestErr err) { if (err) { @@ -38,8 +38,6 @@ MxcImageResponse::run() 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, Qt::SmoothTransformation); m_image.setText("mxc url", "mxc://" + m_id); emit finished(); -- cgit 1.5.1 From 5bfdaff7780bc4299c3edab85c688eebf21f7d4e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 3 Dec 2019 23:34:16 +0100 Subject: Implement decryption of images It is a bit of a hack, but it works... --- CMakeLists.txt | 1 + src/MxcImageProvider.cpp | 9 +++++++-- src/MxcImageProvider.h | 30 ++++++++++++++++++++++++++---- src/timeline/TimelineModel.cpp | 13 +++++++++++++ src/timeline/TimelineModel.h | 2 ++ src/timeline/TimelineViewManager.cpp | 11 ++++++++--- 6 files changed, 57 insertions(+), 9 deletions(-) (limited to 'src/MxcImageProvider.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index c918d834..67a1dfb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -365,6 +365,7 @@ qt5_wrap_cpp(MOC_HEADERS src/CommunitiesList.h src/LoginPage.h src/MainWindow.h + src/MxcImageProvider.h src/InviteeItem.h src/QuickSwitcher.h src/RegisterPage.h diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index 556b019b..edf6ceb5 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -5,7 +5,7 @@ void MxcImageResponse::run() { - if (m_requestedSize.isValid()) { + if (m_requestedSize.isValid() && !m_encryptionInfo) { QString fileName = QString("%1_%2x%3_crop") .arg(m_id) .arg(m_requestedSize.width()) @@ -65,7 +65,12 @@ MxcImageResponse::run() return; } - auto data = QByteArray(res.data(), res.size()); + auto temp = res; + if (m_encryptionInfo) + temp = mtx::crypto::to_string( + mtx::crypto::decrypt_file(temp, m_encryptionInfo.value())); + + auto data = QByteArray(temp.data(), temp.size()); m_image.loadFromData(data); m_image.setText("original filename", QString::fromStdString(originalFilename)); diff --git a/src/MxcImageProvider.h b/src/MxcImageProvider.h index 19d8a74e..2c197a13 100644 --- a/src/MxcImageProvider.h +++ b/src/MxcImageProvider.h @@ -6,14 +6,21 @@ #include #include +#include + +#include + class MxcImageResponse : public QQuickImageResponse , public QRunnable { public: - MxcImageResponse(const QString &id, const QSize &requestedSize) + MxcImageResponse(const QString &id, + const QSize &requestedSize, + boost::optional encryptionInfo) : m_id(id) , m_requestedSize(requestedSize) + , m_encryptionInfo(encryptionInfo) { setAutoDelete(false); } @@ -29,19 +36,34 @@ public: QString m_id, m_error; QSize m_requestedSize; QImage m_image; + boost::optional m_encryptionInfo; }; -class MxcImageProvider : public QQuickAsyncImageProvider +class MxcImageProvider + : public QObject + , public QQuickAsyncImageProvider { -public: + Q_OBJECT +public slots: QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override { - MxcImageResponse *response = new MxcImageResponse(id, requestedSize); + boost::optional info; + auto temp = infos.find("mxc://" + id); + if (temp != infos.end()) + info = *temp; + + MxcImageResponse *response = new MxcImageResponse(id, requestedSize, info); pool.start(response); return response; } + void addEncryptionInfo(mtx::crypto::EncryptedFile info) + { + infos.insert(QString::fromStdString(info.url), info); + } + private: QThreadPool pool; + QHash infos; }; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index f606b603..2c58e2f5 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -673,6 +673,19 @@ TimelineModel::internalAddEvents( continue; // don't insert redaction into timeline } + if (auto event = + boost::get>(&e)) { + auto temp = decryptEvent(*event).event; + auto encInfo = boost::apply_visitor( + [](const auto &ev) -> boost::optional { + return eventEncryptionInfo(ev); + }, + temp); + + if (encInfo) + emit newEncryptedImage(encInfo.value()); + } + this->events.insert(id, e); ids.push_back(id); } diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index f52091e6..06c64acf 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -6,6 +6,7 @@ #include #include +#include #include #include "Cache.h" @@ -188,6 +189,7 @@ signals: void nextPendingMessage(); void newMessageToSend(mtx::events::collections::TimelineEvents event); void mediaCached(QString mxcUrl, QString cacheUrl); + void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo); private: DecryptionResult decryptEvent( diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index c44bcbbf..25f72a6d 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -102,9 +102,14 @@ TimelineViewManager::sync(const mtx::responses::Rooms &rooms) void TimelineViewManager::addRoom(const QString &room_id) { - if (!models.contains(room_id)) - models.insert(room_id, - QSharedPointer(new TimelineModel(this, room_id))); + if (!models.contains(room_id)) { + QSharedPointer newRoom(new TimelineModel(this, room_id)); + connect(newRoom.data(), + &TimelineModel::newEncryptedImage, + imgProvider, + &MxcImageProvider::addEncryptionInfo); + models.insert(room_id, std::move(newRoom)); + } } void -- cgit 1.5.1