diff --git a/resources/icons/ui/image.svg b/resources/icons/ui/image.svg
new file mode 100644
index 00000000..ca73a76e
--- /dev/null
+++ b/resources/icons/ui/image.svg
@@ -0,0 +1 @@
+
diff --git a/resources/icons/ui/music.svg b/resources/icons/ui/music.svg
new file mode 100644
index 00000000..5f72b736
--- /dev/null
+++ b/resources/icons/ui/music.svg
@@ -0,0 +1 @@
+
diff --git a/resources/icons/ui/video-file.svg b/resources/icons/ui/video-file.svg
new file mode 100644
index 00000000..08c0a6bb
--- /dev/null
+++ b/resources/icons/ui/video-file.svg
@@ -0,0 +1 @@
+
diff --git a/resources/icons/ui/zip.svg b/resources/icons/ui/zip.svg
new file mode 100644
index 00000000..e22534d6
--- /dev/null
+++ b/resources/icons/ui/zip.svg
@@ -0,0 +1 @@
+
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 0b3a6427..7421d594 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -124,10 +124,76 @@ Item {
color: Nheko.theme.separator
}
- Button {
- text: "Send files " + (room ? room.input.uploads.length : 0)
+
+ Page {
+ id: uploadPopup
visible: room && room.input.uploads.length > 0
- onClicked: room.input.acceptUploads()
+ 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 {
diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index 40572704..4828843c 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -58,7 +58,7 @@ Item {
Image {
anchors.fill: parent
- source: thumbnailUrl.replace("mxc://", "image://MxcImage/") + "?scale"
+ source: thumbnailUrl ? thumbnailUrl.replace("mxc://", "image://MxcImage/") + "?scale" : ""
asynchronous: true
fillMode: Image.PreserveAspectFit
diff --git a/resources/res.qrc b/resources/res.qrc
index a383f805..ad86c88d 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -46,6 +46,10 @@
icons/ui/volume-off-indicator.svg
icons/ui/volume-up.svg
icons/ui/world.svg
+ icons/ui/music.svg
+ icons/ui/image.svg
+ icons/ui/zip.svg
+ icons/ui/video-file.svg
icons/emoji-categories/activity.svg
icons/emoji-categories/flags.svg
icons/emoji-categories/foods.svg
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 73e556f7..c4af7f0c 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -251,6 +251,8 @@ MainWindow::registerQmlTypes()
qmlRegisterType("im.nheko.EmojiModel", 1, 0, "EmojiModel");
qmlRegisterUncreatableType(
"im.nheko.EmojiModel", 1, 0, "Emoji", QStringLiteral("Used by emoji models"));
+ qmlRegisterUncreatableType(
+ "im.nheko", 1, 0, "MediaUpload", QStringLiteral("MediaUploads can not be created in Qml"));
qmlRegisterUncreatableMetaObject(emoji::staticMetaObject,
"im.nheko.EmojiModel",
1,
diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index 46563bbb..4472fe84 100644
--- a/src/timeline/InputBar.h
+++ b/src/timeline/InputBar.h
@@ -53,11 +53,11 @@ class MediaUpload : public QObject
{
Q_OBJECT
// Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged)
- // Q_PROPERTY(MediaType mediaType READ type NOTIFY mediaTypeChanged)
+ Q_PROPERTY(int mediaType READ type NOTIFY mediaTypeChanged)
// // https://stackoverflow.com/questions/33422265/pass-qimage-to-qml/68554646#68554646
// Q_PROPERTY(QUrl thumbnail READ thumbnail NOTIFY thumbnailChanged)
// Q_PROPERTY(QString humanSize READ humanSize NOTIFY huSizeChanged)
- // Q_PROPERTY(QString filename READ filename 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)
@@ -73,7 +73,7 @@ public:
Video,
Audio,
};
- Q_ENUM(MediaType);
+ Q_ENUM(MediaType)
explicit MediaUpload(std::unique_ptr data,
QString mimetype,
@@ -81,6 +81,17 @@ public:
bool encrypt,
QObject *parent = nullptr);
+ [[nodiscard]] int type() const
+ {
+ if (mimeClass_ == u"video")
+ return MediaType::Video;
+ else if (mimeClass_ == u"audio")
+ return MediaType::Audio;
+ else if (mimeClass_ == u"image")
+ return MediaType::Image;
+ else
+ return MediaType::File;
+ }
[[nodiscard]] QString url() const { return url_; }
[[nodiscard]] QString mimetype() const { return mimetype_; }
[[nodiscard]] QString mimeClass() const { return mimeClass_; }
@@ -102,9 +113,19 @@ public:
QString thumbnailUrl() const { return thumbnailUrl_; }
[[nodiscard]] uint64_t thumbnailSize() const { return thumbnailSize_; }
+ void setFilename(QString fn)
+ {
+ if (fn != originalFilename_) {
+ originalFilename_ = std::move(fn);
+ emit filenameChanged();
+ }
+ }
+
signals:
void uploadComplete(MediaUpload *self, QString url);
void uploadFailed(MediaUpload *self);
+ void filenameChanged();
+ void mediaTypeChanged();
public slots:
void startUpload();