diff --git a/imports/Spectral/Component/Timeline/AudioDelegate.qml b/imports/Spectral/Component/Timeline/AudioDelegate.qml
new file mode 100644
index 000000000..0b4d64a29
--- /dev/null
+++ b/imports/Spectral/Component/Timeline/AudioDelegate.qml
@@ -0,0 +1,138 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Controls.Material 2.12
+import QtGraphicalEffects 1.0
+import Qt.labs.platform 1.0 as Platform
+import QtMultimedia 5.12
+
+import Spectral 0.1
+import Spectral.Setting 0.1
+
+import Spectral.Component 2.0
+import Spectral.Dialog 2.0
+import Spectral.Menu.Timeline 2.0
+import Spectral.Font 0.1
+import Spectral.Effect 2.0
+
+RowLayout {
+ readonly property bool avatarVisible: !sentByMe && showAuthor
+ readonly property bool sentByMe: author === currentRoom.localUser
+
+ id: root
+
+ spacing: 4
+
+ z: -5
+
+ Avatar {
+ Layout.preferredWidth: 36
+ Layout.preferredHeight: 36
+ Layout.alignment: Qt.AlignBottom
+
+ visible: avatarVisible
+ hint: author.displayName
+ source: author.avatarMediaId
+ color: author.color
+
+ Component {
+ id: userDetailDialog
+
+ UserDetailDialog {}
+ }
+
+ RippleEffect {
+ anchors.fill: parent
+
+ circular: true
+
+ onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": author}).open()
+ }
+ }
+
+ Item {
+ Layout.preferredWidth: 36
+ Layout.preferredHeight: 36
+
+ visible: !(sentByMe || avatarVisible)
+ }
+
+ Control {
+ Layout.maximumWidth: messageListView.width - (!sentByMe ? 36 + root.spacing : 0) - 48
+
+ padding: 12
+
+ Audio {
+ id: audio
+
+ source: currentRoom.urlToMxcUrl(content.url)
+ }
+
+ contentItem: Label {
+ text: content.info.duration || audio.duration || "Unknown audio"
+
+ color: !sentByMe ? "white" : MPalette.foreground
+ }
+
+ background: AutoRectangle {
+ readonly property int minorRadius: 8
+
+ id: bubbleBackground
+
+ color: sentByMe ? MPalette.background : author.color
+ radius: 18
+
+ topLeftVisible: !sentByMe && (bubbleShape == 3 || bubbleShape == 2)
+ topRightVisible: sentByMe && (bubbleShape == 3 || bubbleShape == 2)
+ bottomLeftVisible: !sentByMe && (bubbleShape == 1 || bubbleShape == 2)
+ bottomRightVisible: sentByMe && (bubbleShape == 1 || bubbleShape == 2)
+
+ topLeftRadius: minorRadius
+ topRightRadius: minorRadius
+ bottomLeftRadius: minorRadius
+ bottomRightRadius: minorRadius
+
+ AutoMouseArea {
+ anchors.fill: parent
+
+ id: messageMouseArea
+
+ onPrimaryClicked: {
+ if (audio.playbackState === Audio.PlayingState) {
+ audio.stop()
+ } else {
+ audio.play()
+ }
+ }
+ }
+ }
+ }
+
+ function saveFileAs() {
+ var folderDialog = openFolderDialog.createObject(ApplicationWindow.overlay)
+
+ folderDialog.chosen.connect(function(path) {
+ if (!path) return
+
+ currentRoom.downloadFile(eventId, path + "/" + currentRoom.fileNameToDownload(eventId))
+ })
+
+ folderDialog.open()
+ }
+
+ function downloadAndOpen()
+ {
+ if (downloaded) openSavedFile()
+ else
+ {
+ openOnFinished = true
+ currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
+ }
+ }
+
+ function openSavedFile()
+ {
+ if (Qt.openUrlExternally(progressInfo.localPath)) return;
+ if (Qt.openUrlExternally(progressInfo.localDir)) return;
+ }
+}
diff --git a/imports/Spectral/Component/Timeline/FileDelegate.qml b/imports/Spectral/Component/Timeline/FileDelegate.qml
index 56e362013..b37fe1f6f 100644
--- a/imports/Spectral/Component/Timeline/FileDelegate.qml
+++ b/imports/Spectral/Component/Timeline/FileDelegate.qml
@@ -54,7 +54,7 @@ RowLayout {
}
}
- Label {
+ Item {
Layout.preferredWidth: 36
Layout.preferredHeight: 36
diff --git a/imports/Spectral/Component/Timeline/ImageDelegate.qml b/imports/Spectral/Component/Timeline/ImageDelegate.qml
index 78b8042cb..ab2d92414 100644
--- a/imports/Spectral/Component/Timeline/ImageDelegate.qml
+++ b/imports/Spectral/Component/Timeline/ImageDelegate.qml
@@ -60,7 +60,7 @@ RowLayout {
}
}
- Label {
+ Item {
Layout.preferredWidth: 36
Layout.preferredHeight: 36
@@ -75,15 +75,17 @@ RowLayout {
}
Image {
- Layout.maximumWidth: messageListView.width - (!sentByMe ? 36 + root.spacing : 0) - 48
+ property int maxWidth: messageListView.width - (!sentByMe ? 36 + root.spacing : 0) - 48
+
Layout.minimumWidth: 256
Layout.minimumHeight: 64
+ Layout.preferredWidth: content.info.w > maxWidth ? maxWidth : content.info.w
+ Layout.preferredHeight: content.info.w > maxWidth ? (content.info.h / content.info.w * maxWidth) : content.info.h
+
id: img
- source: "image://mxc/" +
- (content.info && content.info.thumbnail_info ?
- content.thumbnailMediaId : content.mediaId)
+ source: "image://mxc/" + content.mediaId
sourceSize.width: 720
sourceSize.height: 720
diff --git a/imports/Spectral/Component/Timeline/VideoDelegate.qml b/imports/Spectral/Component/Timeline/VideoDelegate.qml
index 6f190814f..aa24acccf 100644
--- a/imports/Spectral/Component/Timeline/VideoDelegate.qml
+++ b/imports/Spectral/Component/Timeline/VideoDelegate.qml
@@ -65,7 +65,7 @@ RowLayout {
}
}
- Label {
+ Item {
Layout.preferredWidth: 36
Layout.preferredHeight: 36
@@ -73,18 +73,22 @@ RowLayout {
}
Video {
- Layout.fillWidth: true
- Layout.preferredHeight: width
+ property int maxWidth: messageListView.width - (!sentByMe ? 36 + root.spacing : 0) - 48
+
+ Layout.preferredWidth: content.info.w > maxWidth ? maxWidth : content.info.w
+ Layout.preferredHeight: content.info.w > maxWidth ? (content.info.h / content.info.w * maxWidth) : content.info.h
id: vid
- source: progressInfo.localPath
+ source: progressInfo.completed ? progressInfo.localPath : ""
loops: MediaPlayer.Infinite
autoPlay: true
fillMode: VideoOutput.PreserveAspectFit
+ onErrorChanged: console.log("Video playback error: " + errorString)
+
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
@@ -94,6 +98,22 @@ RowLayout {
}
}
+ Image {
+ anchors.fill: parent
+
+ visible: content.info && content.info.thumbnail_info && vid.playbackState != MediaPlayer.PlayingState
+
+ source: "image://mxc/" + (content.info && content.info.thumbnail_info ?
+ content.thumbnailMediaId : "")
+
+ sourceSize.width: 720
+ sourceSize.height: 720
+
+ fillMode: Image.PreserveAspectCrop
+
+ Component.onCompleted: console.log(source)
+ }
+
Label {
anchors.centerIn: parent
diff --git a/imports/Spectral/Component/Timeline/qmldir b/imports/Spectral/Component/Timeline/qmldir
index e1f5b7b4e..60b807c93 100644
--- a/imports/Spectral/Component/Timeline/qmldir
+++ b/imports/Spectral/Component/Timeline/qmldir
@@ -6,3 +6,4 @@ ImageDelegate 2.0 ImageDelegate.qml
FileDelegate 2.0 FileDelegate.qml
VideoDelegate 2.0 VideoDelegate.qml
ReactionDelegate 2.0 ReactionDelegate.qml
+AudioDelegate 2.0 AudioDelegate.qml
diff --git a/imports/Spectral/Panel/RoomPanel.qml b/imports/Spectral/Panel/RoomPanel.qml
index ac9b6d8b0..c85ab5b07 100644
--- a/imports/Spectral/Panel/RoomPanel.qml
+++ b/imports/Spectral/Panel/RoomPanel.qml
@@ -392,6 +392,33 @@ Item {
}
}
+ DelegateChoice {
+ roleValue: "audio"
+ delegate: ColumnLayout {
+ width: messageListView.width
+
+ SectionDelegate {
+ Layout.alignment: Qt.AlignHCenter
+ Layout.maximumWidth: parent.width
+
+ visible: showSection
+ }
+
+ AudioDelegate {
+ Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 2
+
+ visible: readMarker
+
+ color: MPalette.primary
+ }
+ }
+ }
+
DelegateChoice {
roleValue: "video"
delegate: ColumnLayout {
diff --git a/res.qrc b/res.qrc
index 0827f905b..f51bcd27b 100644
--- a/res.qrc
+++ b/res.qrc
@@ -58,5 +58,6 @@
imports/Spectral/Component/Timeline/VideoDelegate.qml
imports/Spectral/Component/AutoRectangle.qml
imports/Spectral/Component/Timeline/ReactionDelegate.qml
+ imports/Spectral/Component/Timeline/AudioDelegate.qml