Fix thumbnails for encrypted files and factor upload box out

This commit is contained in:
Nicolas Werner 2022-03-21 05:49:12 +01:00
parent dbd2bebe6c
commit a9486ec896
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
8 changed files with 182 additions and 83 deletions

View file

@ -125,75 +125,7 @@ Item {
} }
Page { UploadBox {
id: uploadPopup
visible: room && room.input.uploads.length > 0
Layout.preferredHeight: 200
clip: true
Layout.fillWidth: true
padding: Nheko.paddingMedium
contentItem: ListView {
id: uploadsList
anchors.horizontalCenter: parent.horizontalCenter
boundsBehavior: Flickable.StopAtBounds
orientation: ListView.Horizontal
width: Math.min(contentWidth, parent.width)
model: room ? room.input.uploads : undefined
spacing: Nheko.paddingMedium
delegate: Pane {
padding: Nheko.paddingSmall
height: uploadPopup.availableHeight - buttons.height
width: uploadPopup.availableHeight - buttons.height
background: Rectangle {
color: Nheko.colors.window
radius: Nheko.paddingMedium
}
contentItem: ColumnLayout {
Image {
Layout.fillHeight: true
Layout.fillWidth: true
sourceSize.height: height
sourceSize.width: width
property string typeStr: switch(modelData.mediaType) {
case MediaUpload.Video: return "video-file";
case MediaUpload.Audio: return "music";
case MediaUpload.Image: return "image";
default: return "zip";
}
source: "image://colorimage/:/icons/icons/ui/"+typeStr+".svg?" + Nheko.colors.buttonText
}
MatrixTextField {
Layout.fillWidth: true
text: modelData.filename
onTextEdited: modelData.filename = text
}
}
}
}
footer: DialogButtonBox {
id: buttons
standardButtons: DialogButtonBox.Cancel
Button {
text: qsTr("Upload %n file(s)", "", (room ? room.input.uploads.length : 0))
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
onAccepted: room.input.acceptUploads()
onRejected: room.input.declineUploads()
}
background: Rectangle {
color: Nheko.colors.base
}
} }
NotificationWarning { NotificationWarning {

View file

@ -0,0 +1,89 @@
// SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "./components"
import "./ui"
import QtQuick 2.9
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3
import im.nheko 1.0
Page {
id: uploadPopup
visible: room && room.input.uploads.length > 0
Layout.preferredHeight: 200
clip: true
Layout.fillWidth: true
padding: Nheko.paddingMedium
contentItem: ListView {
id: uploadsList
anchors.horizontalCenter: parent.horizontalCenter
boundsBehavior: Flickable.StopAtBounds
ScrollBar.horizontal: ScrollBar {
id: scr
}
orientation: ListView.Horizontal
width: Math.min(contentWidth, parent.availableWidth)
model: room ? room.input.uploads : undefined
spacing: Nheko.paddingMedium
delegate: Pane {
padding: Nheko.paddingSmall
height: uploadPopup.availableHeight - buttons.height - (scr.visible? scr.height : 0)
width: uploadPopup.availableHeight - buttons.height
background: Rectangle {
color: Nheko.colors.window
radius: Nheko.paddingMedium
}
contentItem: ColumnLayout {
Image {
Layout.fillHeight: true
Layout.fillWidth: true
sourceSize.height: height
sourceSize.width: width
fillMode: Image.PreserveAspectFit
smooth: true
mipmap: true
property string typeStr: switch(modelData.mediaType) {
case MediaUpload.Video: return "video-file";
case MediaUpload.Audio: return "music";
case MediaUpload.Image: return "image";
default: return "zip";
}
source: (modelData.thumbnail != "") ? modelData.thumbnail : ("image://colorimage/:/icons/icons/ui/"+typeStr+".svg?" + Nheko.colors.buttonText)
}
MatrixTextField {
Layout.fillWidth: true
text: modelData.filename
onTextEdited: modelData.filename = text
}
}
}
}
footer: DialogButtonBox {
id: buttons
standardButtons: DialogButtonBox.Cancel
Button {
text: qsTr("Upload %n file(s)", "", (room ? room.input.uploads.length : 0))
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
onAccepted: room.input.acceptUploads()
onRejected: room.input.declineUploads()
}
background: Rectangle {
color: Nheko.colors.base
}
}

View file

@ -99,6 +99,7 @@
<file>qml/MatrixText.qml</file> <file>qml/MatrixText.qml</file>
<file>qml/MatrixTextField.qml</file> <file>qml/MatrixTextField.qml</file>
<file>qml/ToggleButton.qml</file> <file>qml/ToggleButton.qml</file>
<file>qml/UploadBox.qml</file>
<file>qml/MessageInput.qml</file> <file>qml/MessageInput.qml</file>
<file>qml/MessageView.qml</file> <file>qml/MessageView.qml</file>
<file>qml/NhekoBusyIndicator.qml</file> <file>qml/NhekoBusyIndicator.qml</file>

View file

@ -139,6 +139,19 @@ struct EventFile
} }
}; };
struct EventThumbnailFile
{
template<class Content>
using file_t = decltype(Content::info.thumbnail_file);
template<class T>
std::optional<mtx::crypto::EncryptedFile> operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<file_t, T>::value)
return e.content.info.thumbnail_file;
return std::nullopt;
}
};
struct EventUrl struct EventUrl
{ {
template<class Content> template<class Content>
@ -163,6 +176,8 @@ struct EventThumbnailUrl
std::string operator()(const mtx::events::Event<T> &e) std::string operator()(const mtx::events::Event<T> &e)
{ {
if constexpr (is_detected<thumbnail_url_t, T>::value) { if constexpr (is_detected<thumbnail_url_t, T>::value) {
if (auto file = EventThumbnailFile{}(e))
return file->url;
return e.content.info.thumbnail_url; return e.content.info.thumbnail_url;
} }
return ""; return "";
@ -424,6 +439,12 @@ mtx::accessors::file(const mtx::events::collections::TimelineEvents &event)
return std::visit(EventFile{}, event); return std::visit(EventFile{}, event);
} }
std::optional<mtx::crypto::EncryptedFile>
mtx::accessors::thumbnail_file(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventThumbnailFile{}, event);
}
std::string std::string
mtx::accessors::url(const mtx::events::collections::TimelineEvents &event) mtx::accessors::url(const mtx::events::collections::TimelineEvents &event)
{ {

View file

@ -78,6 +78,8 @@ formattedBodyWithFallback(const mtx::events::collections::TimelineEvents &event)
std::optional<mtx::crypto::EncryptedFile> std::optional<mtx::crypto::EncryptedFile>
file(const mtx::events::collections::TimelineEvents &event); file(const mtx::events::collections::TimelineEvents &event);
std::optional<mtx::crypto::EncryptedFile>
thumbnail_file(const mtx::events::collections::TimelineEvents &event);
std::string std::string
url(const mtx::events::collections::TimelineEvents &event); url(const mtx::events::collections::TimelineEvents &event);

View file

@ -17,7 +17,6 @@
#include <QMimeDatabase> #include <QMimeDatabase>
#include <QStandardPaths> #include <QStandardPaths>
#include <QTextBoundaryFinder> #include <QTextBoundaryFinder>
#include <QUrl>
#include <QRegularExpression> #include <QRegularExpression>
#include <mtx/responses/common.hpp> #include <mtx/responses/common.hpp>
@ -39,6 +38,20 @@
static constexpr size_t INPUT_HISTORY_SIZE = 10; static constexpr size_t INPUT_HISTORY_SIZE = 10;
QUrl
MediaUpload::thumbnailDataUrl() const
{
if (thumbnail_.isNull())
return {};
QByteArray byteArray;
QBuffer buffer(&byteArray);
buffer.open(QIODevice::WriteOnly);
thumbnail_.save(&buffer, "PNG");
QString base64 = QString::fromUtf8(byteArray.toBase64());
return QString("data:image/png;base64,") + base64;
}
bool bool
InputVideoSurface::present(const QVideoFrame &frame) InputVideoSurface::present(const QVideoFrame &frame)
{ {
@ -465,6 +478,10 @@ InputBar::image(const QString &filename,
const QString &mime, const QString &mime,
uint64_t dsize, uint64_t dsize,
const QSize &dimensions, const QSize &dimensions,
const std::optional<mtx::crypto::EncryptedFile> &thumbnailEncryptedFile,
const QString &thumbnailUrl,
uint64_t thumbnailSize,
const QSize &thumbnailDimensions,
const QString &blurhash) const QString &blurhash)
{ {
mtx::events::msg::Image image; mtx::events::msg::Image image;
@ -480,6 +497,18 @@ InputBar::image(const QString &filename,
else else
image.url = url.toStdString(); image.url = url.toStdString();
if (!thumbnailUrl.isEmpty()) {
if (thumbnailEncryptedFile)
image.info.thumbnail_file = thumbnailEncryptedFile;
else
image.info.thumbnail_url = thumbnailUrl.toStdString();
image.info.thumbnail_info.h = thumbnailDimensions.height();
image.info.thumbnail_info.w = thumbnailDimensions.width();
image.info.thumbnail_info.size = thumbnailSize;
image.info.thumbnail_info.mimetype = "image/png";
}
if (!room->reply().isEmpty()) { if (!room->reply().isEmpty()) {
image.relations.relations.push_back( image.relations.relations.push_back(
{mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
@ -566,11 +595,13 @@ InputBar::video(const QString &filename,
const std::optional<mtx::crypto::EncryptedFile> &thumbnailEncryptedFile, const std::optional<mtx::crypto::EncryptedFile> &thumbnailEncryptedFile,
const QString &thumbnailUrl, const QString &thumbnailUrl,
uint64_t thumbnailSize, uint64_t thumbnailSize,
const QSize &thumbnailDimensions) const QSize &thumbnailDimensions,
const QString &blurhash)
{ {
mtx::events::msg::Video video; mtx::events::msg::Video video;
video.info.mimetype = mime.toStdString(); video.info.mimetype = mime.toStdString();
video.info.size = dsize; video.info.size = dsize;
video.info.blurhash = blurhash.toStdString();
video.body = filename.toStdString(); video.body = filename.toStdString();
if (duration > 0) if (duration > 0)
@ -946,7 +977,17 @@ InputBar::finalizeUpload(MediaUpload *upload, QString url)
auto size = upload->size(); auto size = upload->size();
auto encryptedFile = upload->encryptedFile_(); auto encryptedFile = upload->encryptedFile_();
if (mimeClass == u"image") if (mimeClass == u"image")
image(filename, encryptedFile, url, mime, size, upload->dimensions(), upload->blurhash()); image(filename,
encryptedFile,
url,
mime,
size,
upload->dimensions(),
upload->thumbnailEncryptedFile_(),
upload->thumbnailUrl(),
upload->thumbnailSize(),
upload->thumbnailImg().size(),
upload->blurhash());
else if (mimeClass == u"audio") else if (mimeClass == u"audio")
audio(filename, encryptedFile, url, mime, size, upload->duration()); audio(filename, encryptedFile, url, mime, size, upload->duration());
else if (mimeClass == u"video") else if (mimeClass == u"video")
@ -960,7 +1001,8 @@ InputBar::finalizeUpload(MediaUpload *upload, QString url)
upload->thumbnailEncryptedFile_(), upload->thumbnailEncryptedFile_(),
upload->thumbnailUrl(), upload->thumbnailUrl(),
upload->thumbnailSize(), upload->thumbnailSize(),
upload->thumbnailImg().size()); upload->thumbnailImg().size(),
upload->blurhash());
else else
file(filename, encryptedFile, url, mime, size); file(filename, encryptedFile, url, mime, size);

View file

@ -12,6 +12,7 @@
#include <QSize> #include <QSize>
#include <QStringList> #include <QStringList>
#include <QTimer> #include <QTimer>
#include <QUrl>
#include <QVariantList> #include <QVariantList>
#include <deque> #include <deque>
#include <memory> #include <memory>
@ -52,15 +53,11 @@ signals:
class MediaUpload : public QObject class MediaUpload : public QObject
{ {
Q_OBJECT Q_OBJECT
// Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged)
Q_PROPERTY(int mediaType READ type NOTIFY mediaTypeChanged) Q_PROPERTY(int mediaType READ type NOTIFY mediaTypeChanged)
// // https://stackoverflow.com/questions/33422265/pass-qimage-to-qml/68554646#68554646 // https://stackoverflow.com/questions/33422265/pass-qimage-to-qml/68554646#68554646
// Q_PROPERTY(QUrl thumbnail READ thumbnail NOTIFY thumbnailChanged) Q_PROPERTY(QUrl thumbnail READ thumbnailDataUrl NOTIFY thumbnailChanged)
// Q_PROPERTY(QString humanSize READ humanSize NOTIFY huSizeChanged) // Q_PROPERTY(QString humanSize READ humanSize NOTIFY huSizeChanged)
Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged) Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)
// Q_PROPERTY(QString mimetype READ mimetype NOTIFY mimetypeChanged)
// Q_PROPERTY(int height READ height NOTIFY heightChanged)
// Q_PROPERTY(int width READ width NOTIFY widthChanged)
// thumbnail video // thumbnail video
// https://stackoverflow.com/questions/26229633/display-on-screen-using-qabstractvideosurface // https://stackoverflow.com/questions/26229633/display-on-screen-using-qabstractvideosurface
@ -111,6 +108,7 @@ public:
QImage thumbnailImg() const { return thumbnail_; } QImage thumbnailImg() const { return thumbnail_; }
QString thumbnailUrl() const { return thumbnailUrl_; } QString thumbnailUrl() const { return thumbnailUrl_; }
QUrl thumbnailDataUrl() const;
[[nodiscard]] uint64_t thumbnailSize() const { return thumbnailSize_; } [[nodiscard]] uint64_t thumbnailSize() const { return thumbnailSize_; }
void setFilename(QString fn) void setFilename(QString fn)
@ -125,13 +123,18 @@ signals:
void uploadComplete(MediaUpload *self, QString url); void uploadComplete(MediaUpload *self, QString url);
void uploadFailed(MediaUpload *self); void uploadFailed(MediaUpload *self);
void filenameChanged(); void filenameChanged();
void thumbnailChanged();
void mediaTypeChanged(); void mediaTypeChanged();
public slots: public slots:
void startUpload(); void startUpload();
private slots: private slots:
void setThumbnail(QImage img) { this->thumbnail_ = std::move(img); } void setThumbnail(QImage img)
{
this->thumbnail_ = std::move(img);
emit thumbnailChanged();
}
public: public:
// void uploadThumbnail(QImage img); // void uploadThumbnail(QImage img);
@ -225,6 +228,10 @@ private:
const QString &mime, const QString &mime,
uint64_t dsize, uint64_t dsize,
const QSize &dimensions, const QSize &dimensions,
const std::optional<mtx::crypto::EncryptedFile> &thumbnailEncryptedFile,
const QString &thumbnailUrl,
uint64_t thumbnailSize,
const QSize &thumbnailDimensions,
const QString &blurhash); const QString &blurhash);
void file(const QString &filename, void file(const QString &filename,
const std::optional<mtx::crypto::EncryptedFile> &encryptedFile, const std::optional<mtx::crypto::EncryptedFile> &encryptedFile,
@ -245,9 +252,10 @@ private:
uint64_t duration, uint64_t duration,
const QSize &dimensions, const QSize &dimensions,
const std::optional<mtx::crypto::EncryptedFile> &thumbnailEncryptedFile, const std::optional<mtx::crypto::EncryptedFile> &thumbnailEncryptedFile,
const QString &thumnailUrl, const QString &thumbnailUrl,
uint64_t thumnailSize, uint64_t thumbnailSize,
const QSize &thumbnailDimensions); const QSize &thumbnailDimensions,
const QString &blurhash);
void startUploadFromPath(const QString &path); void startUploadFromPath(const QString &path);
void startUploadFromMimeData(const QMimeData &source, const QString &format); void startUploadFromMimeData(const QMimeData &source, const QString &format);

View file

@ -1367,6 +1367,10 @@ struct SendMessageVisitor
if (encInfo) if (encInfo)
emit model_->newEncryptedImage(encInfo.value()); emit model_->newEncryptedImage(encInfo.value());
encInfo = mtx::accessors::thumbnail_file(msg);
if (encInfo)
emit model_->newEncryptedImage(encInfo.value());
model_->sendEncryptedMessage(msg, Event); model_->sendEncryptedMessage(msg, Event);
} else { } else {
msg.type = Event; msg.type = Event;