From ea98d7b2aed5d5085d0ce1833568ec93ff813b0f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 5 Oct 2019 23:11:20 +0200 Subject: [PATCH] Add simple audio message widget --- resources/qml/TimelineView.qml | 1 + resources/qml/delegates/AudioMessage.qml | 98 ++++++++++++++++++++++++ resources/qml/delegates/FileMessage.qml | 38 ++++----- resources/res.qrc | 1 + src/timeline2/TimelineViewManager.cpp | 59 ++++++++++++++ src/timeline2/TimelineViewManager.h | 2 + 6 files changed, 180 insertions(+), 19 deletions(-) create mode 100644 resources/qml/delegates/AudioMessage.qml diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index c4750ddf..c25e6543 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -113,6 +113,7 @@ Rectangle { case MtxEvent.ImageMessage: return "delegates/ImageMessage.qml" case MtxEvent.FileMessage: return "delegates/FileMessage.qml" //case MtxEvent.VideoMessage: return "delegates/VideoMessage.qml" + case MtxEvent.AudioMessage: return "delegates/AudioMessage.qml" case MtxEvent.Redacted: return "delegates/Redacted.qml" default: return "delegates/placeholder.qml" } diff --git a/resources/qml/delegates/AudioMessage.qml b/resources/qml/delegates/AudioMessage.qml new file mode 100644 index 00000000..f36d22b9 --- /dev/null +++ b/resources/qml/delegates/AudioMessage.qml @@ -0,0 +1,98 @@ +import QtQuick 2.6 +import QtQuick.Layouts 1.6 +import QtMultimedia 5.12 + +Rectangle { + radius: 10 + color: colors.dark + height: row.height + 24 + width: parent.width + + RowLayout { + id: row + + anchors.centerIn: parent + width: parent.width - 24 + + spacing: 15 + + Rectangle { + id: button + color: colors.light + radius: 22 + height: 44 + width: 44 + Image { + id: img + anchors.centerIn: parent + + source: "qrc:/icons/icons/ui/arrow-pointing-down.png" + fillMode: Image.Pad + + } + MouseArea { + anchors.fill: parent + onClicked: { + switch (button.state) { + case "": timelineManager.cacheMedia(eventData.url, eventData.mimetype); break; + case "stopped": + audio.play(); console.log("play"); + button.state = "playing" + break + case "playing": + audio.pause(); console.log("pause"); + button.state = "stopped" + break + } + } + cursorShape: Qt.PointingHandCursor + } + MediaPlayer { + id: audio + onError: console.log(errorString) + } + + Connections { + target: timelineManager + onMediaCached: { + if (mxcUrl == eventData.url) { + audio.source = "file://" + cacheUrl + button.state = "stopped" + console.log("media loaded: " + mxcUrl + " at " + cacheUrl) + } + console.log("media cached: " + mxcUrl + " at " + cacheUrl) + } + } + + states: [ + State { + name: "stopped" + PropertyChanges { target: img; source: "qrc:/icons/icons/ui/play-sign.png" } + }, + State { + name: "playing" + PropertyChanges { target: img; source: "qrc:/icons/icons/ui/pause-symbol.png" } + } + ] + } + ColumnLayout { + id: col + + Text { + Layout.fillWidth: true + text: eventData.body + textFormat: Text.PlainText + elide: Text.ElideRight + color: colors.text + } + Text { + Layout.fillWidth: true + text: eventData.filesize + textFormat: Text.PlainText + elide: Text.ElideRight + color: colors.text + } + } + } +} + diff --git a/resources/qml/delegates/FileMessage.qml b/resources/qml/delegates/FileMessage.qml index 3099acaa..27cd6403 100644 --- a/resources/qml/delegates/FileMessage.qml +++ b/resources/qml/delegates/FileMessage.qml @@ -1,19 +1,22 @@ import QtQuick 2.6 +import QtQuick.Layouts 1.6 -Row { Rectangle { radius: 10 color: colors.dark - height: row.height - width: row.width + height: row.height + 24 + width: parent.width - Row { + RowLayout { id: row + anchors.centerIn: parent + width: parent.width - 24 + spacing: 15 - padding: 12 Rectangle { + id: button color: colors.light radius: 22 height: 44 @@ -32,26 +35,23 @@ Rectangle { cursorShape: Qt.PointingHandCursor } } - Column { - TextEdit { + ColumnLayout { + id: col + + Text { + Layout.fillWidth: true text: eventData.body - textFormat: TextEdit.PlainText - readOnly: true - wrapMode: Text.Wrap - selectByMouse: true + textFormat: Text.PlainText + elide: Text.ElideRight color: colors.text } - TextEdit { + Text { + Layout.fillWidth: true text: eventData.filesize - textFormat: TextEdit.PlainText - readOnly: true - wrapMode: Text.Wrap - selectByMouse: true + textFormat: Text.PlainText + elide: Text.ElideRight color: colors.text } } } } -Rectangle { -} -} diff --git a/resources/res.qrc b/resources/res.qrc index c865200c..1caf378e 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -122,6 +122,7 @@ qml/delegates/TextMessage.qml qml/delegates/NoticeMessage.qml qml/delegates/ImageMessage.qml + qml/delegates/AudioMessage.qml qml/delegates/FileMessage.qml qml/delegates/Redacted.qml qml/delegates/placeholder.qml diff --git a/src/timeline2/TimelineViewManager.cpp b/src/timeline2/TimelineViewManager.cpp index 74e851c4..29c52ac9 100644 --- a/src/timeline2/TimelineViewManager.cpp +++ b/src/timeline2/TimelineViewManager.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "Logging.h" #include "MxcImageProvider.h" @@ -143,6 +144,64 @@ TimelineViewManager::saveMedia(QString mxcUrl, }); } +void +TimelineViewManager::cacheMedia(QString mxcUrl, QString mimeType) +{ + // If the message is a link to a non mxcUrl, don't download it + if (!mxcUrl.startsWith("mxc://")) { + emit mediaCached(mxcUrl, mxcUrl); + return; + } + + QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix(); + + const auto url = mxcUrl.toStdString(); + QFileInfo filename(QString("%1/media_cache/%2.%3") + .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) + .arg(QString(mxcUrl).remove("mxc://")) + .arg(suffix)); + if (QDir::cleanPath(filename.path()) != filename.path()) { + nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url); + return; + } + + QDir().mkpath(filename.path()); + + if (filename.isReadable()) { + emit mediaCached(mxcUrl, filename.filePath()); + return; + } + + http::client()->download( + url, + [this, mxcUrl, filename, url](const std::string &data, + const std::string &, + const std::string &, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve image {}: {} {}", + url, + err->matrix_error.error, + static_cast(err->status_code)); + return; + } + + try { + QFile file(filename.filePath()); + + if (!file.open(QIODevice::WriteOnly)) + return; + + file.write(QByteArray(data.data(), data.size())); + file.close(); + } catch (const std::exception &e) { + nhlog::ui()->warn("Error while saving file to: {}", e.what()); + } + + emit mediaCached(mxcUrl, filename.filePath()); + }); +} + void TimelineViewManager::updateReadReceipts(const QString &room_id, const std::vector &event_ids) diff --git a/src/timeline2/TimelineViewManager.h b/src/timeline2/TimelineViewManager.h index a8fcf7ce..6a6d3c6b 100644 --- a/src/timeline2/TimelineViewManager.h +++ b/src/timeline2/TimelineViewManager.h @@ -42,6 +42,7 @@ public: QString originalFilename, QString mimeType, qml_mtx_events::EventType eventType) const; + Q_INVOKABLE void cacheMedia(QString mxcUrl, QString mimeType); // Qml can only pass enum as int Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString originalFilename, @@ -63,6 +64,7 @@ signals: void clearRoomMessageCount(QString roomid); void updateRoomsLastMessage(QString roomid, const DescInfo &info); void activeTimelineChanged(TimelineModel *timeline); + void mediaCached(QString mxcUrl, QString cacheUrl); public slots: void updateReadReceipts(const QString &room_id, const std::vector &event_ids);