Separate out a base `MessageContentModel` that can be extended to get the component types from different places. This is used currently for `EventMessageContentModel` but will be used later as part of the rich chat bar. All display text is now in the text component so it never needs special casing. This also cleans up some of the model parameters so more things come from attributes including location and file data (which was already a qvariantmap anyway). Also cleaned up the itinerary and file enhancement views,
450 lines
14 KiB
QML
450 lines
14 KiB
QML
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
|
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
import QtCore as Core
|
|
import QtQuick
|
|
import QtQuick.Controls as QQC2
|
|
import QtQuick.Layouts
|
|
import QtMultimedia
|
|
|
|
import org.kde.coreaddons
|
|
import org.kde.kirigami as Kirigami
|
|
|
|
import org.kde.neochat
|
|
|
|
/**
|
|
* @brief A component to show a video from a message.
|
|
*/
|
|
Video {
|
|
id: root
|
|
|
|
/**
|
|
* @brief The matrix ID of the message event.
|
|
*/
|
|
required property string eventId
|
|
|
|
/**
|
|
* @brief The display text of the message.
|
|
*/
|
|
required property string display
|
|
|
|
/**
|
|
* @brief The attributes of the component.
|
|
*/
|
|
required property var componentAttributes
|
|
|
|
/**
|
|
* @brief FileTransferInfo for any downloading files.
|
|
*/
|
|
required property var fileTransferInfo
|
|
|
|
/**
|
|
* @brief Whether the media has been downloaded.
|
|
*/
|
|
readonly property bool downloaded: root.fileTransferInfo && root.fileTransferInfo.completed
|
|
onDownloadedChanged: {
|
|
if (downloaded) {
|
|
root.source = root.fileTransferInfo.localPath;
|
|
}
|
|
if (downloaded && playOnFinished) {
|
|
playSavedFile();
|
|
playOnFinished = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Whether the video should be played when downloaded.
|
|
*/
|
|
property bool playOnFinished: false
|
|
|
|
Layout.preferredWidth: mediaSizeHelper.currentSize.width
|
|
Layout.preferredHeight: mediaSizeHelper.currentSize.height
|
|
|
|
fillMode: VideoOutput.PreserveAspectFit
|
|
|
|
Component.onDestruction: root.stop()
|
|
Component.onCompleted: {
|
|
if (NeoChatConfig.hideImages && !Controller.isImageShown(root.eventId)) {
|
|
root.state = "hidden";
|
|
}
|
|
}
|
|
|
|
states: [
|
|
State {
|
|
name: "notDownloaded"
|
|
when: !root.fileTransferInfo.completed && !root.fileTransferInfo.active
|
|
PropertyChanges {
|
|
target: videoLabel
|
|
visible: true
|
|
}
|
|
PropertyChanges {
|
|
target: mediaThumbnail
|
|
visible: true
|
|
}
|
|
},
|
|
State {
|
|
name: "downloading"
|
|
when: root.fileTransferInfo.active && !root.fileTransferInfo.completed && (Controller.isImageShown(root.eventId) || !NeoChatConfig.hideImages)
|
|
PropertyChanges {
|
|
target: downloadBar
|
|
visible: true
|
|
}
|
|
PropertyChanges {
|
|
target: mediaThumbnail
|
|
visible: true
|
|
}
|
|
},
|
|
State {
|
|
name: "paused"
|
|
when: root.fileTransferInfo.completed && root.playbackState === MediaPlayer.PausedState && (Controller.isImageShown(root.eventId) || !NeoChatConfig.hideImages)
|
|
PropertyChanges {
|
|
target: videoControls
|
|
stateVisible: true
|
|
}
|
|
PropertyChanges {
|
|
target: playButton
|
|
icon.name: "media-playback-start"
|
|
onClicked: {
|
|
MediaManager.startPlayback();
|
|
root.play();
|
|
}
|
|
}
|
|
},
|
|
State {
|
|
name: "playing"
|
|
when: root.fileTransferInfo.completed && root.playbackState === MediaPlayer.PlayingState && (Controller.isImageShown(root.eventId) || !NeoChatConfig.hideImages)
|
|
PropertyChanges {
|
|
target: videoControls
|
|
stateVisible: true
|
|
}
|
|
PropertyChanges {
|
|
target: playButton
|
|
icon.name: "media-playback-pause"
|
|
onClicked: root.pause()
|
|
}
|
|
},
|
|
State {
|
|
name: "stopped"
|
|
when: root.fileTransferInfo.completed && root.playbackState === MediaPlayer.StoppedState && (Controller.isImageShown(root.eventId) || !NeoChatConfig.hideImages)
|
|
PropertyChanges {
|
|
target: videoControls
|
|
stateVisible: true
|
|
}
|
|
PropertyChanges {
|
|
target: mediaThumbnail
|
|
visible: true
|
|
}
|
|
PropertyChanges {
|
|
target: videoLabel
|
|
visible: true
|
|
}
|
|
PropertyChanges {
|
|
target: playButton
|
|
icon.name: "media-playback-start"
|
|
onClicked: {
|
|
MediaManager.startPlayback();
|
|
root.play();
|
|
}
|
|
}
|
|
},
|
|
State {
|
|
name: "hidden"
|
|
PropertyChanges {
|
|
target: mediaThumbnail
|
|
visible: false
|
|
}
|
|
PropertyChanges {
|
|
target: videoControls
|
|
visible: false
|
|
}
|
|
PropertyChanges {
|
|
target: hidden
|
|
visible: true
|
|
}
|
|
}
|
|
]
|
|
|
|
Connections {
|
|
target: MediaManager
|
|
function onPlaybackStarted() {
|
|
if (root.playbackState === MediaPlayer.PlayingState) {
|
|
root.pause();
|
|
}
|
|
}
|
|
}
|
|
|
|
QQC2.Button {
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
visible: root.state !== "hidden"
|
|
icon.name: "view-hidden"
|
|
text: i18nc("@action:button", "Hide Image")
|
|
display: QQC2.Button.IconOnly
|
|
z: 10
|
|
onClicked: {
|
|
root.state = "hidden"
|
|
Controller.markImageHidden(root.eventId)
|
|
}
|
|
|
|
QQC2.ToolTip.text: text
|
|
QQC2.ToolTip.visible: hovered
|
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
|
}
|
|
|
|
Image {
|
|
id: mediaThumbnail
|
|
anchors.fill: parent
|
|
visible: false
|
|
|
|
source: visible ? root.componentAttributes.tempInfo.source : ""
|
|
fillMode: Image.PreserveAspectFit
|
|
}
|
|
|
|
QQC2.Label {
|
|
id: videoLabel
|
|
anchors.centerIn: parent
|
|
|
|
visible: false
|
|
color: "white"
|
|
text: i18n("Video")
|
|
font.pixelSize: 16
|
|
|
|
padding: 8
|
|
|
|
background: Rectangle {
|
|
radius: Kirigami.Units.smallSpacing
|
|
color: "black"
|
|
opacity: 0.3
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: downloadBar
|
|
anchors.fill: parent
|
|
visible: false
|
|
|
|
color: Qt.alpha(Kirigami.Theme.backgroundColor, 0.25)
|
|
|
|
QQC2.ProgressBar {
|
|
anchors.centerIn: parent
|
|
|
|
width: parent.width * 0.8
|
|
|
|
from: 0
|
|
to: root.fileTransferInfo.total
|
|
value: root.fileTransferInfo.progress
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: hidden
|
|
anchors.fill: parent
|
|
|
|
visible: false
|
|
color: "#BB000000"
|
|
|
|
QQC2.Button {
|
|
anchors.centerIn: parent
|
|
text: i18nc("@action:button", "Show Video")
|
|
onClicked: {
|
|
root.state = "notDownloaded";
|
|
Controller.markImageShown(root.eventId);
|
|
}
|
|
}
|
|
}
|
|
|
|
QQC2.Control {
|
|
id: videoControls
|
|
property bool stateVisible: false
|
|
|
|
anchors.bottom: root.bottom
|
|
anchors.left: root.left
|
|
anchors.right: root.right
|
|
visible: stateVisible && (videoHoverHandler.hovered || volumePopupHoverHandler.hovered || volumeSlider.hovered || videoControlTimer.running)
|
|
|
|
contentItem: RowLayout {
|
|
id: controlRow
|
|
QQC2.ToolButton {
|
|
id: playButton
|
|
}
|
|
QQC2.Slider {
|
|
Layout.fillWidth: true
|
|
from: 0
|
|
to: root.duration
|
|
value: root.position
|
|
onMoved: root.seek(value)
|
|
}
|
|
QQC2.Label {
|
|
text: Format.formatDuration(root.position) + "/" + Format.formatDuration(root.duration)
|
|
}
|
|
QQC2.ToolButton {
|
|
id: volumeButton
|
|
property var unmuteVolume: root.volume
|
|
|
|
icon.name: root.volume <= 0 ? "player-volume-muted" : "player-volume"
|
|
|
|
QQC2.ToolTip.visible: hovered
|
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
|
QQC2.ToolTip.timeout: Kirigami.Units.toolTipDelay
|
|
QQC2.ToolTip.text: i18nc("@action:button", "Volume")
|
|
|
|
onClicked: {
|
|
if (root.volume > 0) {
|
|
root.volume = 0;
|
|
} else {
|
|
if (unmuteVolume === 0) {
|
|
root.volume = 1;
|
|
} else {
|
|
root.volume = unmuteVolume;
|
|
}
|
|
}
|
|
}
|
|
onHoveredChanged: {
|
|
if (!hovered && (root.state === "paused" || root.state === "stopped" || root.state === "playing")) {
|
|
videoControlTimer.restart();
|
|
volumePopupTimer.restart();
|
|
}
|
|
}
|
|
|
|
QQC2.Popup {
|
|
id: volumePopup
|
|
y: -height
|
|
width: volumeButton.width
|
|
visible: videoControls.stateVisible && (volumeButton.hovered || volumePopupHoverHandler.hovered || volumeSlider.hovered || volumePopupTimer.running)
|
|
|
|
focus: true
|
|
padding: Kirigami.Units.smallSpacing
|
|
closePolicy: QQC2.Popup.NoAutoClose
|
|
|
|
QQC2.Slider {
|
|
id: volumeSlider
|
|
anchors.centerIn: parent
|
|
implicitHeight: Kirigami.Units.gridUnit * 7
|
|
orientation: Qt.Vertical
|
|
padding: 0
|
|
from: 0
|
|
to: 1
|
|
value: root.volume
|
|
onMoved: {
|
|
root.volume = value;
|
|
volumeButton.unmuteVolume = value;
|
|
}
|
|
onHoveredChanged: {
|
|
if (!hovered && (root.state === "paused" || root.state === "stopped" || root.state === "playing")) {
|
|
videoControlTimer.restart();
|
|
volumePopupTimer.restart();
|
|
}
|
|
}
|
|
}
|
|
Timer {
|
|
id: volumePopupTimer
|
|
interval: 500
|
|
}
|
|
HoverHandler {
|
|
id: volumePopupHoverHandler
|
|
onHoveredChanged: {
|
|
if (!hovered && (root.state === "paused" || root.state === "stopped" || root.state === "playing")) {
|
|
videoControlTimer.restart();
|
|
volumePopupTimer.restart();
|
|
}
|
|
}
|
|
}
|
|
background: Kirigami.ShadowedRectangle {
|
|
radius: Kirigami.Units.cornerRadius
|
|
color: Kirigami.Theme.backgroundColor
|
|
opacity: 0.8
|
|
|
|
property color borderColor: Kirigami.Theme.textColor
|
|
border.color: Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0.3)
|
|
border.width: 1
|
|
|
|
shadow.xOffset: 0
|
|
shadow.yOffset: 4
|
|
shadow.color: Qt.rgba(0, 0, 0, 0.3)
|
|
shadow.size: 8
|
|
}
|
|
}
|
|
}
|
|
QQC2.ToolButton {
|
|
id: maximizeButton
|
|
display: QQC2.AbstractButton.IconOnly
|
|
|
|
action: Kirigami.Action {
|
|
text: i18n("Maximize")
|
|
icon.name: "view-fullscreen"
|
|
onTriggered: {
|
|
root.Message.timeline.interactive = false;
|
|
root.pause();
|
|
RoomManager.maximizeMedia(root.eventId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
background: Kirigami.ShadowedRectangle {
|
|
radius: Kirigami.Units.cornerRadius
|
|
color: Kirigami.Theme.backgroundColor
|
|
opacity: 0.8
|
|
|
|
property color borderColor: Kirigami.Theme.textColor
|
|
border.color: Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0.3)
|
|
border.width: 1
|
|
|
|
shadow.xOffset: 0
|
|
shadow.yOffset: 4
|
|
shadow.color: Qt.rgba(0, 0, 0, 0.3)
|
|
shadow.size: 8
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: videoControlTimer
|
|
interval: 1000
|
|
}
|
|
HoverHandler {
|
|
id: videoHoverHandler
|
|
onHoveredChanged: {
|
|
if (!hovered && (root.state === "paused" || root.state === "stopped" || root.state === "playing")) {
|
|
videoControlTimer.restart();
|
|
}
|
|
}
|
|
}
|
|
|
|
TapHandler {
|
|
acceptedButtons: Qt.LeftButton
|
|
gesturePolicy: TapHandler.ReleaseWithinBounds | TapHandler.WithinBounds
|
|
onTapped: if (root.fileTransferInfo.completed) {
|
|
if (root.playbackState == MediaPlayer.PlayingState) {
|
|
root.pause();
|
|
} else {
|
|
MediaManager.startPlayback();
|
|
root.play();
|
|
}
|
|
} else {
|
|
root.downloadAndPlay();
|
|
}
|
|
}
|
|
|
|
MediaSizeHelper {
|
|
id: mediaSizeHelper
|
|
contentMaxWidth: root.Message.maxContentWidth
|
|
mediaWidth: root.componentAttributes.width
|
|
mediaHeight: root.componentAttributes.height
|
|
}
|
|
|
|
function downloadAndPlay() {
|
|
if (root.downloaded) {
|
|
playSavedFile();
|
|
} else {
|
|
playOnFinished = true;
|
|
Message.room.downloadFile(root.eventId, Core.StandardPaths.writableLocation(Core.StandardPaths.CacheLocation) + "/" + root.eventId.replace(":", "_").replace("/", "_").replace("+", "_") + Message.room.fileNameToDownload(root.eventId));
|
|
}
|
|
}
|
|
|
|
function playSavedFile() {
|
|
root.stop();
|
|
MediaManager.startPlayback();
|
|
root.play();
|
|
}
|
|
}
|