diff --git a/imports/Spectral/Component/Timeline/FileDelegate.qml b/imports/Spectral/Component/Timeline/FileDelegate.qml
index f8bd0d7..1fa994c 100644
--- a/imports/Spectral/Component/Timeline/FileDelegate.qml
+++ b/imports/Spectral/Component/Timeline/FileDelegate.qml
@@ -14,7 +14,7 @@ import Spectral.Menu.Timeline 2.0
import Spectral.Font 0.1
import Spectral.Effect 2.0
-ColumnLayout {
+RowLayout {
readonly property bool avatarVisible: !sentByMe && showAuthor
readonly property bool sentByMe: author === currentRoom.localUser
@@ -23,196 +23,175 @@ ColumnLayout {
id: root
- spacing: 0
+ spacing: 4
onDownloadedChanged: if (downloaded && openOnFinished) openSavedFile()
- Label {
- Layout.leftMargin: 48
+ z: -5
- text: author.displayName
+ Avatar {
+ Layout.preferredWidth: 36
+ Layout.preferredHeight: 36
+ Layout.alignment: Qt.AlignBottom
visible: avatarVisible
+ hint: author.displayName
+ source: author.avatarMediaId
- font.pixelSize: 13
- verticalAlignment: Text.AlignVCenter
+ Component {
+ id: userDetailDialog
+
+ UserDetailDialog {}
+ }
+
+ RippleEffect {
+ anchors.fill: parent
+
+ circular: true
+
+ onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": author}).open()
+ }
}
- RowLayout {
- z: -5
+ Label {
+ Layout.preferredWidth: 36
+ Layout.preferredHeight: 36
- id: messageRow
+ visible: !(sentByMe || avatarVisible)
+ }
- spacing: 4
+ Control {
+ Layout.maximumWidth: messageListView.width - (!sentByMe ? 36 + root.spacing : 0) - 48
- Avatar {
- Layout.preferredWidth: 32
- Layout.preferredHeight: 32
- Layout.alignment: Qt.AlignTop
+ padding: 12
- visible: avatarVisible
- hint: author.displayName
- source: author.avatarMediaId
+ contentItem: RowLayout {
+ ToolButton {
+ contentItem: MaterialIcon {
+ icon: progressInfo.completed ? "\ue5ca" : "\ue2c4"
+ }
- Component {
- id: userDetailDialog
-
- UserDetailDialog {}
+ onClicked: progressInfo.completed ? openSavedFile() : saveFileAs()
}
- RippleEffect {
+ ColumnLayout {
+ Label {
+ Layout.fillWidth: true
+
+ text: display
+ wrapMode: Label.Wrap
+ font.pixelSize: 18
+ font.weight: Font.Medium
+ font.capitalization: Font.AllUppercase
+ }
+
+ Label {
+ Layout.fillWidth: true
+
+ text: progressInfo.active ? (humanSize(progressInfo.progress) + "/" + humanSize(progressInfo.total)) : humanSize(content.info ? content.info.size : 0)
+ color: MPalette.lighter
+ wrapMode: Label.Wrap
+ }
+ }
+ }
+
+ background: Rectangle {
+ color: MPalette.background
+ radius: 18
+
+ Rectangle {
+ anchors.top: parent.top
+ anchors.left: parent.left
+
+ width: parent.width / 2
+ height: parent.height / 2
+
+ visible: !sentByMe && (bubbleShape == 3 || bubbleShape == 2)
+
+ color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
+ radius: 2
+ }
+
+ Rectangle {
+ anchors.top: parent.top
+ anchors.right: parent.right
+
+ width: parent.width / 2
+ height: parent.height / 2
+
+ visible: sentByMe && (bubbleShape == 3 || bubbleShape == 2)
+
+ color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
+ radius: 2
+ }
+
+ Rectangle {
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+
+ width: parent.width / 2
+ height: parent.height / 2
+
+ visible: !sentByMe && (bubbleShape == 1 || bubbleShape == 2)
+
+ color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
+ radius: 2
+ }
+
+ Rectangle {
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+
+ width: parent.width / 2
+ height: parent.height / 2
+
+ visible: sentByMe && (bubbleShape == 1 || bubbleShape == 2)
+
+ color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
+ radius: 2
+ }
+
+ AutoMouseArea {
anchors.fill: parent
- circular: true
+ id: messageMouseArea
- onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": author}).open()
- }
- }
-
- Label {
- Layout.preferredWidth: 32
- Layout.preferredHeight: 32
- Layout.alignment: Qt.AlignTop
-
- visible: !(sentByMe || avatarVisible)
-
- text: Qt.formatTime(time, "hh:mm AP")
- color: "#5B7480"
-
- font.pixelSize: 10
- horizontalAlignment: Label.AlignHCenter
- verticalAlignment: Label.AlignVCenter
- }
-
- Control {
- Layout.maximumWidth: messageListView.width - (!sentByMe ? 32 + messageRow.spacing : 0) - 48
-
- padding: 12
-
- contentItem: RowLayout {
- ToolButton {
- contentItem: MaterialIcon {
- icon: progressInfo.completed ? "\ue5ca" : "\ue2c4"
- }
-
- onClicked: progressInfo.completed ? openSavedFile() : saveFileAs()
+ onSecondaryClicked: {
+ var contextMenu = fileDelegateContextMenu.createObject(ApplicationWindow.overlay)
+ contextMenu.viewSource.connect(function() {
+ messageSourceDialog.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
+ })
+ contextMenu.downloadAndOpen.connect(downloadAndOpen)
+ contextMenu.saveFileAs.connect(saveFileAs)
+ contextMenu.reply.connect(function() {
+ roomPanelInput.replyUser = author
+ roomPanelInput.replyEventID = eventId
+ roomPanelInput.replyContent = message
+ roomPanelInput.isReply = true
+ roomPanelInput.focus()
+ })
+ contextMenu.redact.connect(function() {
+ currentRoom.redactEvent(eventId)
+ })
+ contextMenu.popup()
}
- ColumnLayout {
- Label {
- Layout.alignment: Qt.AlignVCenter
+ Component {
+ id: messageSourceDialog
- text: display
- font.pixelSize: 18
- font.weight: Font.Medium
- font.capitalization: Font.AllUppercase
- }
-
- Label {
- text: progressInfo.active ? (humanSize(progressInfo.progress) + "/" + humanSize(progressInfo.total)) : humanSize(content.info ? content.info.size : 0)
- color: MPalette.lighter
- }
- }
- }
-
- background: Rectangle {
- color: MPalette.background
- radius: 18
-
- Rectangle {
- anchors.top: parent.top
- anchors.left: parent.left
-
- width: parent.width / 2
- height: parent.height / 2
-
- visible: !sentByMe && (bubbleShape == 3 || bubbleShape == 2)
-
- color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
- radius: 2
+ MessageSourceDialog {}
}
- Rectangle {
- anchors.top: parent.top
- anchors.right: parent.right
+ Component {
+ id: openFolderDialog
- width: parent.width / 2
- height: parent.height / 2
-
- visible: sentByMe && (bubbleShape == 3 || bubbleShape == 2)
-
- color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
- radius: 2
+ OpenFolderDialog {}
}
- Rectangle {
- anchors.bottom: parent.bottom
- anchors.left: parent.left
+ Component {
+ id: fileDelegateContextMenu
- width: parent.width / 2
- height: parent.height / 2
-
- visible: !sentByMe && (bubbleShape == 1 || bubbleShape == 2)
-
- color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
- radius: 2
- }
-
- Rectangle {
- anchors.bottom: parent.bottom
- anchors.right: parent.right
-
- width: parent.width / 2
- height: parent.height / 2
-
- visible: sentByMe && (bubbleShape == 1 || bubbleShape == 2)
-
- color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
- radius: 2
- }
-
- AutoMouseArea {
- anchors.fill: parent
-
- id: messageMouseArea
-
- onSecondaryClicked: {
- var contextMenu = fileDelegateContextMenu.createObject(ApplicationWindow.overlay)
- contextMenu.viewSource.connect(function() {
- messageSourceDialog.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
- })
- contextMenu.downloadAndOpen.connect(downloadAndOpen)
- contextMenu.saveFileAs.connect(saveFileAs)
- contextMenu.reply.connect(function() {
- roomPanelInput.replyUser = author
- roomPanelInput.replyEventID = eventId
- roomPanelInput.replyContent = message
- roomPanelInput.isReply = true
- roomPanelInput.focus()
- })
- contextMenu.redact.connect(function() {
- currentRoom.redactEvent(eventId)
- })
- contextMenu.popup()
- }
-
- Component {
- id: messageSourceDialog
-
- MessageSourceDialog {}
- }
-
- Component {
- id: openFolderDialog
-
- OpenFolderDialog {}
- }
-
- Component {
- id: fileDelegateContextMenu
-
- FileDelegateContextMenu {}
- }
+ FileDelegateContextMenu {}
}
}
}
@@ -246,7 +225,6 @@ ColumnLayout {
if (Qt.openUrlExternally(progressInfo.localDir)) return;
}
-
function humanSize(bytes)
{
if (!bytes)
diff --git a/imports/Spectral/Component/Timeline/ImageDelegate.qml b/imports/Spectral/Component/Timeline/ImageDelegate.qml
index d46ede4..3ee9713 100644
--- a/imports/Spectral/Component/Timeline/ImageDelegate.qml
+++ b/imports/Spectral/Component/Timeline/ImageDelegate.qml
@@ -35,8 +35,8 @@ RowLayout {
}
Avatar {
- Layout.preferredWidth: 32
- Layout.preferredHeight: 32
+ Layout.preferredWidth: 36
+ Layout.preferredHeight: 36
Layout.alignment: Qt.AlignBottom
visible: avatarVisible
@@ -59,8 +59,8 @@ RowLayout {
}
Label {
- Layout.preferredWidth: 32
- Layout.preferredHeight: 32
+ Layout.preferredWidth: 36
+ Layout.preferredHeight: 36
visible: !(sentByMe || avatarVisible)
}
@@ -73,7 +73,7 @@ RowLayout {
}
Image {
- Layout.maximumWidth: messageListView.width - (!sentByMe ? 32 + root.spacing : 0) - 48
+ Layout.maximumWidth: messageListView.width - (!sentByMe ? 36 + root.spacing : 0) - 48
id: img
diff --git a/imports/Spectral/Component/Timeline/MessageDelegate.qml b/imports/Spectral/Component/Timeline/MessageDelegate.qml
index 7f74742..2219993 100644
--- a/imports/Spectral/Component/Timeline/MessageDelegate.qml
+++ b/imports/Spectral/Component/Timeline/MessageDelegate.qml
@@ -34,8 +34,8 @@ ColumnLayout {
spacing: 4
Avatar {
- Layout.preferredWidth: 32
- Layout.preferredHeight: 32
+ Layout.preferredWidth: 36
+ Layout.preferredHeight: 36
Layout.alignment: Qt.AlignBottom
visible: avatarVisible
@@ -58,14 +58,14 @@ ColumnLayout {
}
Item {
- Layout.preferredWidth: 32
- Layout.preferredHeight: 32
+ Layout.preferredWidth: 36
+ Layout.preferredHeight: 36
visible: !(sentByMe || avatarVisible)
}
Control {
- Layout.maximumWidth: messageListView.width - (!sentByMe ? 32 + messageRow.spacing : 0) - 48
+ Layout.maximumWidth: messageListView.width - (!sentByMe ? 36 + messageRow.spacing : 0) - 48
verticalPadding: 8
horizontalPadding: 16
@@ -259,7 +259,7 @@ ColumnLayout {
RowLayout {
Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft
- Layout.leftMargin: sentByMe ? undefined : 32 + messageRow.spacing + 12
+ Layout.leftMargin: sentByMe ? undefined : 36 + messageRow.spacing + 12
Layout.rightMargin: sentByMe ? 12 : undefined
Layout.bottomMargin: 4
diff --git a/imports/Spectral/Component/Timeline/VideoDelegate.qml b/imports/Spectral/Component/Timeline/VideoDelegate.qml
new file mode 100644
index 0000000..0dcc456
--- /dev/null
+++ b/imports/Spectral/Component/Timeline/VideoDelegate.qml
@@ -0,0 +1,251 @@
+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 QtMultimedia 5.12
+import Qt.labs.platform 1.0 as Platform
+
+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.Effect 2.0
+import Spectral.Font 0.1
+
+RowLayout {
+ readonly property bool avatarVisible: showAuthor && !sentByMe
+ readonly property bool sentByMe: author === currentRoom.localUser
+
+ property bool openOnFinished: false
+ property bool playOnFinished: false
+ readonly property bool downloaded: progressInfo && progressInfo.completed
+
+ id: root
+
+ spacing: 4
+
+ z: -5
+
+ onDownloadedChanged: {
+ if (downloaded && openOnFinished) {
+ openSavedFile()
+ openOnFinished = false
+ }
+ if (downloaded && playOnFinished) {
+ playSavedFile()
+ playOnFinished = false
+ }
+ }
+
+ Avatar {
+ Layout.preferredWidth: 36
+ Layout.preferredHeight: 36
+ Layout.alignment: Qt.AlignBottom
+
+ visible: avatarVisible
+ hint: author.displayName
+ source: author.avatarMediaId
+
+ Component {
+ id: userDetailDialog
+
+ UserDetailDialog {}
+ }
+
+ RippleEffect {
+ anchors.fill: parent
+
+ circular: true
+
+ onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": author}).open()
+ }
+ }
+
+ Label {
+ Layout.preferredWidth: 36
+ Layout.preferredHeight: 36
+
+ visible: !(sentByMe || avatarVisible)
+ }
+
+ Video {
+ Layout.fillWidth: true
+ Layout.preferredHeight: width
+
+ id: vid
+
+ source: progressInfo.localPath
+
+ loops: MediaPlayer.Infinite
+ autoPlay: true
+
+ fillMode: VideoOutput.PreserveAspectFit
+
+ layer.enabled: true
+ layer.effect: OpacityMask {
+ maskSource: Rectangle {
+ width: vid.width
+ height: vid.height
+ radius: 18
+ }
+ }
+
+ Label {
+ anchors.centerIn: parent
+
+ visible: vid.playbackState != MediaPlayer.PlayingState
+ color: "white"
+ text: "Video"
+ font.pixelSize: 16
+
+ padding: 8
+
+ background: Rectangle {
+ radius: height / 2
+ color: "black"
+ opacity: 0.3
+ }
+ }
+
+ Control {
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 8
+ anchors.right: parent.right
+ anchors.rightMargin: 8
+
+ horizontalPadding: 8
+ verticalPadding: 4
+
+ contentItem: RowLayout {
+ Label {
+ text: Qt.formatTime(time, "hh:mm AP")
+ color: "white"
+ font.pixelSize: 12
+ }
+
+ Label {
+ text: author.displayName
+ color: "white"
+ font.pixelSize: 12
+ }
+ }
+
+ background: Rectangle {
+ radius: height / 2
+ color: "black"
+ opacity: 0.3
+ }
+ }
+
+ Rectangle {
+ anchors.fill: parent
+
+ visible: progressInfo.active && !downloaded
+
+ color: "#BB000000"
+
+ ProgressBar {
+ anchors.centerIn: parent
+
+ width: parent.width * 0.8
+
+ from: 0
+ to: progressInfo.total
+ value: progressInfo.progress
+ }
+ }
+
+ RippleEffect {
+ anchors.fill: parent
+
+ id: messageMouseArea
+
+ onPrimaryClicked: downloadAndPlay()
+
+ onSecondaryClicked: {
+ var contextMenu = imageDelegateContextMenu.createObject(ApplicationWindow.overlay)
+ contextMenu.viewSource.connect(function() {
+ messageSourceDialog.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
+ })
+ contextMenu.downloadAndOpen.connect(downloadAndOpen)
+ contextMenu.saveFileAs.connect(saveFileAs)
+ contextMenu.reply.connect(function() {
+ roomPanelInput.replyUser = author
+ roomPanelInput.replyEventID = eventId
+ roomPanelInput.replyContent = message
+ roomPanelInput.isReply = true
+ roomPanelInput.focus()
+ })
+ contextMenu.redact.connect(function() {
+ currentRoom.redactEvent(eventId)
+ })
+ contextMenu.popup()
+ }
+
+ Component {
+ id: messageSourceDialog
+
+ MessageSourceDialog {}
+ }
+
+ Component {
+ id: openFolderDialog
+
+ OpenFolderDialog {}
+ }
+
+ Component {
+ id: imageDelegateContextMenu
+
+ FileDelegateContextMenu {}
+ }
+ }
+ }
+
+ 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 downloadAndPlay()
+ {
+ if (downloaded) playSavedFile()
+ else
+ {
+ playOnFinished = 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;
+ }
+
+ function playSavedFile()
+ {
+ vid.stop()
+ vid.play()
+ }
+}
diff --git a/imports/Spectral/Component/Timeline/qmldir b/imports/Spectral/Component/Timeline/qmldir
index 527b337..03ed875 100644
--- a/imports/Spectral/Component/Timeline/qmldir
+++ b/imports/Spectral/Component/Timeline/qmldir
@@ -4,3 +4,4 @@ StateDelegate 2.0 StateDelegate.qml
SectionDelegate 2.0 SectionDelegate.qml
ImageDelegate 2.0 ImageDelegate.qml
FileDelegate 2.0 FileDelegate.qml
+VideoDelegate 2.0 VideoDelegate.qml
diff --git a/imports/Spectral/Panel/RoomPanel.qml b/imports/Spectral/Panel/RoomPanel.qml
index ae31075..a142495 100644
--- a/imports/Spectral/Panel/RoomPanel.qml
+++ b/imports/Spectral/Panel/RoomPanel.qml
@@ -219,6 +219,24 @@ Item {
}
}
+ DelegateChoice {
+ roleValue: "video"
+ delegate: ColumnLayout {
+ width: messageListView.width
+
+ SectionDelegate {
+ Layout.alignment: Qt.AlignHCenter
+ Layout.maximumWidth: parent.width
+
+ visible: showSection
+ }
+
+ VideoDelegate {
+ Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft
+ }
+ }
+ }
+
DelegateChoice {
roleValue: "file"
delegate: ColumnLayout {
diff --git a/res.qrc b/res.qrc
index 033f2d6..a07c611 100644
--- a/res.qrc
+++ b/res.qrc
@@ -57,5 +57,6 @@
imports/Spectral/Dialog/AccountDetailDialog.qml
imports/Spectral/Dialog/OpenFileDialog.qml
imports/Spectral/Dialog/OpenFolderDialog.qml
+ imports/Spectral/Component/Timeline/VideoDelegate.qml
diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp
index a90067b..3ce3ed7 100644
--- a/src/messageeventmodel.cpp
+++ b/src/messageeventmodel.cpp
@@ -289,6 +289,8 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
return "image";
case MessageEventType::Audio:
return "audio";
+ case MessageEventType::Video:
+ return "video";
}
if (e->hasFileContent())
return "file";