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 d63a7dc..4573481 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";