diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml
new file mode 100644
index 00000000..a53f057b
--- /dev/null
+++ b/resources/qml/Avatar.qml
@@ -0,0 +1,51 @@
+import QtQuick 2.6
+import QtGraphicalEffects 1.0
+import Qt.labs.settings 1.0
+
+Rectangle {
+ id: avatar
+ width: 48
+ height: 48
+ radius: settings.avatar_circles ? height/2 : 3
+
+ Settings {
+ id: settings
+ category: "user"
+ property bool avatar_circles: true
+ }
+
+ property alias url: img.source
+ property string displayName
+
+ Text {
+ anchors.fill: parent
+ text: String.fromCodePoint(displayName.codePointAt(0))
+ color: colors.text
+ font.pixelSize: avatar.height/2
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ Image {
+ id: img
+ anchors.fill: parent
+ asynchronous: true
+ fillMode: Image.PreserveAspectCrop
+ mipmap: true
+ smooth: false
+
+ sourceSize.width: avatar.width
+ sourceSize.height: avatar.height
+
+ layer.enabled: true
+ layer.effect: OpacityMask {
+ maskSource: Rectangle {
+ anchors.fill: parent
+ width: avatar.width
+ height: avatar.height
+ radius: settings.avatar_circles ? height/2 : 3
+ }
+ }
+ }
+ color: colors.dark
+}
diff --git a/resources/qml/EncryptionIndicator.qml b/resources/qml/EncryptionIndicator.qml
new file mode 100644
index 00000000..905cf934
--- /dev/null
+++ b/resources/qml/EncryptionIndicator.qml
@@ -0,0 +1,24 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.1
+import im.nheko 1.0
+
+Rectangle {
+ id: indicator
+ color: "transparent"
+ width: 16
+ height: 16
+ ToolTip.visible: ma.containsMouse && indicator.visible
+ ToolTip.text: qsTr("Encrypted")
+ MouseArea{
+ id: ma
+ anchors.fill: parent
+ hoverEnabled: true
+ }
+
+ Image {
+ id: stateImg
+ anchors.fill: parent
+ source: "image://colorimage/:/icons/icons/ui/lock.png?"+colors.buttonText
+ }
+}
+
diff --git a/resources/qml/ImageButton.qml b/resources/qml/ImageButton.qml
new file mode 100644
index 00000000..dc576e18
--- /dev/null
+++ b/resources/qml/ImageButton.qml
@@ -0,0 +1,29 @@
+import QtQuick 2.3
+import QtQuick.Controls 2.3
+
+Button {
+ property string image: undefined
+
+ id: button
+
+ flat: true
+
+ // disable background, because we don't want a border on hover
+ background: Item {
+ }
+
+ Image {
+ id: buttonImg
+ // Workaround, can't get icon.source working for now...
+ anchors.fill: parent
+ source: "image://colorimage/" + image + "?" + (button.hovered ? colors.highlight : colors.buttonText)
+ }
+
+ MouseArea
+ {
+ id: mouseArea
+ anchors.fill: parent
+ onPressed: mouse.accepted = false
+ cursorShape: Qt.PointingHandCursor
+ }
+}
diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml
new file mode 100644
index 00000000..46e74711
--- /dev/null
+++ b/resources/qml/MatrixText.qml
@@ -0,0 +1,33 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.3
+
+TextEdit {
+ textFormat: TextEdit.RichText
+ readOnly: true
+ wrapMode: Text.Wrap
+ selectByMouse: true
+ color: colors.text
+
+ onLinkActivated: {
+ if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) chat.model.openUserProfile(/^https:\/\/matrix.to\/#\/(@.*)$/.exec(link)[1])
+ else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) timelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1])
+ else if (/^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.test(link)) {
+ var match = /^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.exec(link)
+ timelineManager.setHistoryView(match[1])
+ chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain)
+ }
+ else Qt.openUrlExternally(link)
+ }
+ MouseArea
+ {
+ anchors.fill: parent
+ onPressed: mouse.accepted = false
+ cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
+ }
+
+ ToolTip {
+ visible: parent.hoveredLink
+ text: parent.hoveredLink
+ palette: colors
+ }
+}
diff --git a/resources/qml/StatusIndicator.qml b/resources/qml/StatusIndicator.qml
new file mode 100644
index 00000000..91e8f769
--- /dev/null
+++ b/resources/qml/StatusIndicator.qml
@@ -0,0 +1,38 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.1
+import im.nheko 1.0
+
+Rectangle {
+ id: indicator
+ property int state: 0
+ color: "transparent"
+ width: 16
+ height: 16
+ ToolTip.visible: ma.containsMouse && state != MtxEvent.Empty
+ ToolTip.text: switch (state) {
+ case MtxEvent.Failed: return qsTr("Failed")
+ case MtxEvent.Sent: return qsTr("Sent")
+ case MtxEvent.Received: return qsTr("Received")
+ case MtxEvent.Read: return qsTr("Read")
+ default: return ""
+ }
+ MouseArea{
+ id: ma
+ anchors.fill: parent
+ hoverEnabled: true
+ }
+
+ Image {
+ id: stateImg
+ // Workaround, can't get icon.source working for now...
+ anchors.fill: parent
+ source: switch (indicator.state) {
+ case MtxEvent.Failed: return "image://colorimage/:/icons/icons/ui/remove-symbol.png?" + colors.buttonText
+ case MtxEvent.Sent: return "image://colorimage/:/icons/icons/ui/clock.png?" + colors.buttonText
+ case MtxEvent.Received: return "image://colorimage/:/icons/icons/ui/checkmark.png?" + colors.buttonText
+ case MtxEvent.Read: return "image://colorimage/:/icons/icons/ui/double-tick-indicator.png?" + colors.buttonText
+ default: return ""
+ }
+ }
+}
+
diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml
new file mode 100644
index 00000000..2c2ed02a
--- /dev/null
+++ b/resources/qml/TimelineRow.qml
@@ -0,0 +1,122 @@
+import QtQuick 2.6
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.2
+import QtQuick.Window 2.2
+
+import im.nheko 1.0
+
+import "./delegates"
+
+RowLayout {
+ property var view: chat
+
+ anchors.leftMargin: avatarSize + 4
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ height: Math.max(contentItem.height, 16)
+
+ Column {
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignTop
+
+ //property var replyTo: model.replyTo
+
+ //Text {
+ // property int idx: timelineManager.timeline.idToIndex(replyTo)
+ // text: "" + (idx != -1 ? timelineManager.timeline.data(timelineManager.timeline.index(idx, 0), 2) : "nothing")
+ //}
+ MessageDelegate {
+ id: contentItem
+
+ width: parent.width
+ height: childrenRect.height
+ }
+ }
+
+ StatusIndicator {
+ state: model.state
+ Layout.alignment: Qt.AlignRight | Qt.AlignTop
+ Layout.preferredHeight: 16
+ }
+
+ EncryptionIndicator {
+ visible: model.isEncrypted
+ Layout.alignment: Qt.AlignRight | Qt.AlignTop
+ Layout.preferredHeight: 16
+ }
+
+ ImageButton {
+ Layout.alignment: Qt.AlignRight | Qt.AlignTop
+ Layout.preferredHeight: 16
+ id: replyButton
+
+ image: ":/icons/icons/ui/mail-reply.png"
+ ToolTip {
+ visible: replyButton.hovered
+ text: qsTr("Reply")
+ palette: colors
+ }
+
+ onClicked: view.model.replyAction(model.id)
+ }
+ ImageButton {
+ Layout.alignment: Qt.AlignRight | Qt.AlignTop
+ Layout.preferredHeight: 16
+ id: optionsButton
+
+ image: ":/icons/icons/ui/vertical-ellipsis.png"
+ ToolTip {
+ visible: optionsButton.hovered
+ text: qsTr("Options")
+ palette: colors
+ }
+
+ onClicked: contextMenu.open()
+
+ Menu {
+ y: optionsButton.height
+ id: contextMenu
+ palette: colors
+
+ MenuItem {
+ text: qsTr("Read receipts")
+ onTriggered: view.model.readReceiptsAction(model.id)
+ }
+ MenuItem {
+ text: qsTr("Mark as read")
+ }
+ MenuItem {
+ text: qsTr("View raw message")
+ onTriggered: view.model.viewRawMessage(model.id)
+ }
+ MenuItem {
+ text: qsTr("Redact message")
+ onTriggered: view.model.redactEvent(model.id)
+ }
+ MenuItem {
+ visible: model.type == MtxEvent.ImageMessage || model.type == MtxEvent.VideoMessage || model.type == MtxEvent.AudioMessage || model.type == MtxEvent.FileMessage || model.type == MtxEvent.Sticker
+ text: qsTr("Save as")
+ onTriggered: timelineManager.timeline.saveMedia(model.id)
+ }
+ }
+ }
+
+ Text {
+ Layout.alignment: Qt.AlignRight | Qt.AlignTop
+ text: model.timestamp.toLocaleTimeString("HH:mm")
+ color: inactiveColors.text
+
+ MouseArea{
+ id: ma
+ anchors.fill: parent
+ hoverEnabled: true
+ }
+
+ ToolTip {
+ visible: ma.containsMouse
+ text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate)
+ palette: colors
+ }
+ }
+}
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
new file mode 100644
index 00000000..1a1900ad
--- /dev/null
+++ b/resources/qml/TimelineView.qml
@@ -0,0 +1,185 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.2
+import QtGraphicalEffects 1.0
+import QtQuick.Window 2.2
+
+import im.nheko 1.0
+
+import "./delegates"
+
+Item {
+ property var colors: currentActivePalette
+ property var systemInactive: SystemPalette { colorGroup: SystemPalette.Disabled }
+ property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive
+ property int avatarSize: 40
+
+ Rectangle {
+ anchors.fill: parent
+ color: colors.window
+
+ Text {
+ visible: !timelineManager.timeline && !timelineManager.isInitialSync
+ anchors.centerIn: parent
+ text: qsTr("No room open")
+ font.pointSize: 24
+ color: colors.windowText
+ }
+
+ BusyIndicator {
+ anchors.centerIn: parent
+ running: timelineManager.isInitialSync
+ height: 200
+ width: 200
+ }
+
+ ListView {
+ id: chat
+
+ cacheBuffer: 2000
+
+ visible: timelineManager.timeline != null
+ anchors.fill: parent
+
+ anchors.leftMargin: 4
+ anchors.rightMargin: scrollbar.width
+
+ model: timelineManager.timeline
+
+ boundsBehavior: Flickable.StopAtBounds
+
+ onVerticalOvershootChanged: contentY = contentY - verticalOvershoot
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.NoButton
+ propagateComposedEvents: true
+ z: -1
+ onWheel: {
+ if (wheel.angleDelta != 0) {
+ chat.contentY = chat.contentY - wheel.angleDelta.y
+ wheel.accepted = true
+ chat.forceLayout()
+ chat.updatePosition()
+ }
+ }
+ }
+
+ onModelChanged: {
+ if (model) {
+ currentIndex = model.currentIndex
+ if (model.currentIndex == count - 1) {
+ positionViewAtEnd()
+ } else {
+ positionViewAtIndex(model.currentIndex, ListView.End)
+ }
+ }
+ }
+
+ ScrollBar.vertical: ScrollBar {
+ id: scrollbar
+ parent: chat.parent
+ anchors.top: chat.top
+ anchors.left: chat.right
+ anchors.bottom: chat.bottom
+ onPressedChanged: if (!pressed) chat.updatePosition()
+ }
+
+ property bool atBottom: false
+ onCountChanged: {
+ if (atBottom) {
+ var newIndex = count - 1 // last index
+ positionViewAtEnd()
+ currentIndex = newIndex
+ model.currentIndex = newIndex
+ }
+
+ if (contentHeight < height && model) {
+ model.fetchHistory();
+ }
+ }
+
+ onAtYBeginningChanged: if (atYBeginning) { chat.model.currentIndex = 0; chat.currentIndex = 0; model.fetchHistory(); }
+
+ function updatePosition() {
+ for (var y = chat.contentY + chat.height; y > chat.height; y -= 9) {
+ var i = chat.itemAt(100, y);
+ if (!i) continue;
+ if (!i.isFullyVisible()) continue;
+ chat.model.currentIndex = i.getIndex();
+ chat.currentIndex = i.getIndex()
+ atBottom = i.getIndex() == count - 1;
+ break;
+ }
+ }
+ onMovementEnded: updatePosition()
+
+ spacing: 4
+ delegate: TimelineRow {
+ function isFullyVisible() {
+ return height > 1 && (y - chat.contentY - 1) + height < chat.height
+ }
+ function getIndex() {
+ return index;
+ }
+ }
+
+ section {
+ property: "section"
+ delegate: Column {
+ topPadding: 4
+ bottomPadding: 4
+ spacing: 8
+
+ width: parent.width
+ height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
+
+ Label {
+ id: dateBubble
+ anchors.horizontalCenter: parent.horizontalCenter
+ visible: section.includes(" ")
+ text: chat.model.formatDateSeparator(new Date(Number(section.split(" ")[1])))
+ color: colors.windowText
+
+ height: contentHeight * 1.2
+ width: contentWidth * 1.2
+ horizontalAlignment: Text.AlignHCenter
+ background: Rectangle {
+ radius: parent.height / 2
+ color: colors.dark
+ }
+ }
+ Row {
+ height: userName.height
+ spacing: 4
+ Avatar {
+ width: avatarSize
+ height: avatarSize
+ url: chat.model.avatarUrl(section.split(" ")[0]).replace("mxc://", "image://MxcImage/")
+ displayName: chat.model.displayName(section.split(" ")[0])
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: chat.model.openUserProfile(section.split(" ")[0])
+ cursorShape: Qt.PointingHandCursor
+ }
+ }
+
+ Text {
+ id: userName
+ text: chat.model.escapeEmoji(chat.model.displayName(section.split(" ")[0]))
+ color: chat.model.userColor(section.split(" ")[0], colors.window)
+ textFormat: Text.RichText
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: chat.model.openUserProfile(section.split(" ")[0])
+ cursorShape: Qt.PointingHandCursor
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
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
+}
|