diff --git a/resources/qml/delegates/FileMessage.qml b/resources/qml/delegates/FileMessage.qml
new file mode 100644
index 00000000..2c911c5e
--- /dev/null
+++ b/resources/qml/delegates/FileMessage.qml
@@ -0,0 +1,57 @@
+import QtQuick 2.6
+import QtQuick.Layouts 1.2
+
+Rectangle {
+ radius: 10
+ color: colors.dark
+ height: row.height + 24
+ width: parent ? parent.width : undefined
+
+ 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: timelineManager.timeline.saveMedia(model.id)
+ cursorShape: Qt.PointingHandCursor
+ }
+ }
+ ColumnLayout {
+ id: col
+
+ Text {
+ Layout.fillWidth: true
+ text: model.body
+ textFormat: Text.PlainText
+ elide: Text.ElideRight
+ color: colors.text
+ }
+ Text {
+ Layout.fillWidth: true
+ text: model.filesize
+ textFormat: Text.PlainText
+ elide: Text.ElideRight
+ color: colors.text
+ }
+ }
+ }
+}
diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml
new file mode 100644
index 00000000..1b6e5729
--- /dev/null
+++ b/resources/qml/delegates/ImageMessage.qml
@@ -0,0 +1,23 @@
+import QtQuick 2.6
+
+import im.nheko 1.0
+
+Item {
+ width: Math.min(parent ? parent.width : undefined, model.width)
+ height: width * model.proportionalHeight
+
+ Image {
+ id: img
+ anchors.fill: parent
+
+ source: model.url.replace("mxc://", "image://MxcImage/")
+ asynchronous: true
+ fillMode: Image.PreserveAspectFit
+
+ MouseArea {
+ enabled: model.type == MtxEvent.ImageMessage
+ anchors.fill: parent
+ onClicked: timelineManager.openImageOverlay(model.url, model.id)
+ }
+ }
+}
diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml
new file mode 100644
index 00000000..178dfd86
--- /dev/null
+++ b/resources/qml/delegates/MessageDelegate.qml
@@ -0,0 +1,55 @@
+import QtQuick 2.6
+import im.nheko 1.0
+
+DelegateChooser {
+ //role: "type" //< not supported in our custom implementation, have to use roleValue
+ roleValue: model.type
+
+ DelegateChoice {
+ roleValue: MtxEvent.TextMessage
+ TextMessage {}
+ }
+ DelegateChoice {
+ roleValue: MtxEvent.NoticeMessage
+ NoticeMessage {}
+ }
+ DelegateChoice {
+ roleValue: MtxEvent.EmoteMessage
+ TextMessage {}
+ }
+ DelegateChoice {
+ roleValue: MtxEvent.ImageMessage
+ ImageMessage {}
+ }
+ DelegateChoice {
+ roleValue: MtxEvent.Sticker
+ ImageMessage {}
+ }
+ DelegateChoice {
+ roleValue: MtxEvent.FileMessage
+ FileMessage {}
+ }
+ DelegateChoice {
+ roleValue: MtxEvent.VideoMessage
+ PlayableMediaMessage {}
+ }
+ DelegateChoice {
+ roleValue: MtxEvent.AudioMessage
+ PlayableMediaMessage {}
+ }
+ DelegateChoice {
+ roleValue: MtxEvent.Redacted
+ Pill {
+ text: qsTr("redacted")
+ }
+ }
+ DelegateChoice {
+ roleValue: MtxEvent.Encryption
+ Pill {
+ text: qsTr("Encryption enabled")
+ }
+ }
+ DelegateChoice {
+ Placeholder {}
+ }
+}
diff --git a/resources/qml/delegates/NoticeMessage.qml b/resources/qml/delegates/NoticeMessage.qml
new file mode 100644
index 00000000..a392eb5b
--- /dev/null
+++ b/resources/qml/delegates/NoticeMessage.qml
@@ -0,0 +1,8 @@
+import ".."
+
+MatrixText {
+ text: model.formattedBody
+ width: parent ? parent.width : undefined
+ font.italic: true
+ color: inactiveColors.text
+}
diff --git a/resources/qml/delegates/Pill.qml b/resources/qml/delegates/Pill.qml
new file mode 100644
index 00000000..53a9684e
--- /dev/null
+++ b/resources/qml/delegates/Pill.qml
@@ -0,0 +1,14 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.1
+
+Label {
+ color: inactiveColors.text
+ horizontalAlignment: Text.AlignHCenter
+
+ height: contentHeight * 1.2
+ width: contentWidth * 1.2
+ background: Rectangle {
+ radius: parent.height / 2
+ color: colors.dark
+ }
+}
diff --git a/resources/qml/delegates/Placeholder.qml b/resources/qml/delegates/Placeholder.qml
new file mode 100644
index 00000000..4c0e68c3
--- /dev/null
+++ b/resources/qml/delegates/Placeholder.qml
@@ -0,0 +1,7 @@
+import ".."
+
+MatrixText {
+ text: qsTr("unimplemented event: ") + model.type
+ width: parent ? parent.width : undefined
+ color: inactiveColors.text
+}
diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
new file mode 100644
index 00000000..d0d4d7cb
--- /dev/null
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -0,0 +1,164 @@
+import QtQuick 2.6
+import QtQuick.Layouts 1.2
+import QtQuick.Controls 2.1
+import QtMultimedia 5.6
+
+import im.nheko 1.0
+
+Rectangle {
+ id: bg
+ radius: 10
+ color: colors.dark
+ height: content.height + 24
+ width: parent ? parent.width : undefined
+
+ Column {
+ id: content
+ width: parent.width - 24
+ anchors.centerIn: parent
+
+ Rectangle {
+ id: videoContainer
+ visible: model.type == MtxEvent.VideoMessage
+ width: Math.min(parent.width, model.width ? model.width : 400) // some media has 0 as size...
+ 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: "--:--:--"
+ color: colors.text
+ }
+ Slider {
+ Layout.fillWidth: true
+ id: progress
+ value: media.position
+ from: 0
+ to: media.duration
+
+ onMoved: media.seek(value)
+ //indeterminate: true
+ function updatePositionTexts() {
+ function formatTime(date) {
+ var hh = date.getUTCHours();
+ var mm = date.getUTCMinutes();
+ var ss = date.getSeconds();
+ if (hh < 10) {hh = "0"+hh;}
+ if (mm < 10) {mm = "0"+mm;}
+ if (ss < 10) {ss = "0"+ss;}
+ return hh+":"+mm+":"+ss;
+ }
+ positionText.text = formatTime(new Date(media.position))
+ durationText.text = formatTime(new Date(media.duration))
+ }
+ onValueChanged: updatePositionTexts()
+ }
+ Text {
+ id: durationText
+ text: "--:--:--"
+ color: colors.text
+ }
+ }
+
+ RowLayout {
+ width: parent.width
+
+ 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.timeline.cacheMedia(model.id); break;
+ case "stopped":
+ media.play(); console.log("play");
+ button.state = "playing"
+ break
+ case "playing":
+ media.pause(); console.log("pause");
+ button.state = "stopped"
+ break
+ }
+ }
+ cursorShape: Qt.PointingHandCursor
+ }
+ MediaPlayer {
+ id: media
+ onError: console.log(errorString)
+ onStatusChanged: if(status == MediaPlayer.Loaded) progress.updatePositionTexts()
+ onStopped: button.state = "stopped"
+ }
+
+ Connections {
+ target: timelineManager.timeline
+ onMediaCached: {
+ if (mxcUrl == model.url) {
+ media.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: model.body
+ textFormat: Text.PlainText
+ elide: Text.ElideRight
+ color: colors.text
+ }
+ Text {
+ Layout.fillWidth: true
+ text: model.filesize
+ textFormat: Text.PlainText
+ elide: Text.ElideRight
+ color: colors.text
+ }
+ }
+ }
+ }
+}
+
diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml
new file mode 100644
index 00000000..f984b32f
--- /dev/null
+++ b/resources/qml/delegates/TextMessage.qml
@@ -0,0 +1,6 @@
+import ".."
+
+MatrixText {
+ text: model.formattedBody.replace("<pre>", "<pre style='white-space: pre-wrap'>")
+ width: parent ? parent.width : undefined
+}
|