Add a tab bar to the room drawer which includes a new media tab in addition to the room information tab. This mr completes the architecture for adding others easily later e.g. message highlights or threads. To put this together I had to make sure things like the menus and the maximize delegate were available to both the room drawer and page so there is some rework there to put it all together. Wide\  Mobile\ 
370 lines
13 KiB
QML
370 lines
13 KiB
QML
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
import QtQuick 2.15
|
|
import QtQuick.Controls 2.15 as QQC2
|
|
import QtQuick.Layouts 1.15
|
|
import QtMultimedia @QTMULTIMEDIA_MODULE_QML_VERSION@
|
|
import Qt.labs.platform 1.1 as Platform
|
|
|
|
import org.kde.kirigami 2.13 as Kirigami
|
|
|
|
import org.kde.neochat 1.0
|
|
|
|
/**
|
|
* @brief A timeline delegate for a video message.
|
|
*
|
|
* @inherit TimelineContainer
|
|
*/
|
|
TimelineContainer {
|
|
id: root
|
|
|
|
/**
|
|
* @brief The media info for the event.
|
|
*
|
|
* This should consist of the following:
|
|
* - source - The mxc URL for the media.
|
|
* - mimeType - The MIME type of the media (should be video/xxx for this delegate).
|
|
* - mimeIcon - The MIME icon name (should be video-xxx).
|
|
* - size - The file size in bytes.
|
|
* - duration - The length in seconds of the audio media.
|
|
* - width - The width in pixels of the audio media.
|
|
* - height - The height in pixels of the audio media.
|
|
* - tempInfo - mediaInfo (with the same properties as this except no tempInfo) for a temporary image while the file downloads.
|
|
*/
|
|
required property var mediaInfo
|
|
|
|
/**
|
|
* @brief Whether the media has been downloaded.
|
|
*/
|
|
readonly property bool downloaded: root.progressInfo && root.progressInfo.completed
|
|
|
|
/**
|
|
* @brief Whether the video should be played when downloaded.
|
|
*/
|
|
property bool playOnFinished: false
|
|
|
|
/**
|
|
* @brief The maximum width of the image.
|
|
*/
|
|
readonly property var maxWidth: Kirigami.Units.gridUnit * 30
|
|
|
|
/**
|
|
* @brief The maximum height of the image.
|
|
*/
|
|
readonly property var maxHeight: Kirigami.Units.gridUnit * 30
|
|
|
|
onOpenContextMenu: openFileContext(vid)
|
|
|
|
onDownloadedChanged: {
|
|
if (downloaded) {
|
|
vid.source = root.progressInfo.localPath
|
|
}
|
|
|
|
if (downloaded && playOnFinished) {
|
|
playSavedFile()
|
|
playOnFinished = false
|
|
}
|
|
}
|
|
|
|
innerObject: Video {
|
|
id: vid
|
|
Layout.preferredWidth: mediaSizeHelper.currentSize.width
|
|
Layout.preferredHeight: mediaSizeHelper.currentSize.height
|
|
|
|
fillMode: VideoOutput.PreserveAspectFit
|
|
@QTMULTIMEDIA_VIDEO_FLUSHMODE@
|
|
|
|
states: [
|
|
State {
|
|
name: "notDownloaded"
|
|
when: !root.progressInfo.completed && !root.progressInfo.active
|
|
PropertyChanges {
|
|
target: noDownloadLabel
|
|
visible: true
|
|
}
|
|
PropertyChanges {
|
|
target: mediaThumbnail
|
|
visible: true
|
|
}
|
|
},
|
|
State {
|
|
name: "downloading"
|
|
when: root.progressInfo.active && !root.progressInfo.completed
|
|
PropertyChanges {
|
|
target: downloadBar
|
|
visible: true
|
|
}
|
|
},
|
|
State {
|
|
name: "paused"
|
|
when: root.progressInfo.completed && (vid.playbackState === MediaPlayer.StoppedState || vid.playbackState === MediaPlayer.PausedState)
|
|
PropertyChanges {
|
|
target: videoControls
|
|
stateVisible: true
|
|
}
|
|
PropertyChanges {
|
|
target: playButton
|
|
icon.name: "media-playback-start"
|
|
onClicked: vid.play()
|
|
}
|
|
},
|
|
State {
|
|
name: "playing"
|
|
when: root.progressInfo.completed && vid.playbackState === MediaPlayer.PlayingState
|
|
PropertyChanges {
|
|
target: videoControls
|
|
stateVisible: true
|
|
}
|
|
PropertyChanges {
|
|
target: playButton
|
|
icon.name: "media-playback-pause"
|
|
onClicked: vid.pause()
|
|
}
|
|
}
|
|
]
|
|
|
|
Image {
|
|
id: mediaThumbnail
|
|
anchors.fill: parent
|
|
visible: false
|
|
|
|
source: root.mediaInfo.tempInfo.source
|
|
fillMode: Image.PreserveAspectFit
|
|
}
|
|
|
|
QQC2.Label {
|
|
id: noDownloadLabel
|
|
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: Kirigami.Theme.backgroundColor
|
|
radius: Kirigami.Units.smallSpacing
|
|
|
|
QQC2.ProgressBar {
|
|
anchors.centerIn: parent
|
|
|
|
width: parent.width * 0.8
|
|
|
|
from: 0
|
|
to: root.progressInfo.total
|
|
value: root.progressInfo.progress
|
|
}
|
|
}
|
|
|
|
QQC2.Control {
|
|
id: videoControls
|
|
property bool stateVisible: false
|
|
|
|
anchors.bottom: vid.bottom
|
|
anchors.left: vid.left
|
|
anchors.right: vid.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: vid.duration
|
|
value: vid.position
|
|
onMoved: vid.seek(value)
|
|
}
|
|
QQC2.Label {
|
|
text: Controller.formatDuration(vid.position) + "/" + Controller.formatDuration(vid.duration)
|
|
}
|
|
QQC2.ToolButton {
|
|
id: volumeButton
|
|
property var unmuteVolume: vid.volume
|
|
|
|
icon.name: vid.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 (vid.volume > 0) {
|
|
vid.volume = 0
|
|
} else {
|
|
if (unmuteVolume === 0) {
|
|
vid.volume = 1
|
|
} else {
|
|
vid.volume = unmuteVolume
|
|
}
|
|
}
|
|
}
|
|
onHoveredChanged: {
|
|
if (!hovered && (vid.state === "paused" || vid.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: vid.volume
|
|
onMoved: {
|
|
vid.volume = value
|
|
volumeButton.unmuteVolume = value
|
|
}
|
|
onHoveredChanged: {
|
|
if (!hovered && (vid.state === "paused" || vid.state === "playing")) {
|
|
videoControlTimer.restart()
|
|
volumePopupTimer.restart()
|
|
}
|
|
}
|
|
}
|
|
Timer {
|
|
id: volumePopupTimer
|
|
interval: 500
|
|
}
|
|
HoverHandler {
|
|
id: volumePopupHoverHandler
|
|
onHoveredChanged: {
|
|
if (!hovered && (vid.state === "paused" || vid.state === "playing")) {
|
|
videoControlTimer.restart()
|
|
volumePopupTimer.restart()
|
|
}
|
|
}
|
|
}
|
|
background: Kirigami.ShadowedRectangle {
|
|
radius: 4
|
|
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.ListView.view.interactive = false
|
|
vid.pause()
|
|
// We need to make sure the index is that of the MediaMessageFilterModel.
|
|
if (root.ListView.view.model instanceof MessageFilterModel) {
|
|
RoomManager.maximizeMedia(RoomManager.mediaMessageFilterModel.getRowForSourceItem(root.index))
|
|
} else {
|
|
RoomManager.maximizeMedia(root.index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
background: Kirigami.ShadowedRectangle {
|
|
radius: 4
|
|
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 && (vid.state === "paused" || vid.state === "playing")) {
|
|
videoControlTimer.restart()
|
|
}
|
|
}
|
|
}
|
|
|
|
TapHandler {
|
|
acceptedButtons: Qt.LeftButton
|
|
gesturePolicy: TapHandler.ReleaseWithinBounds | TapHandler.WithinBounds
|
|
onTapped: if (root.progressInfo.completed) {
|
|
if (vid.playbackState == MediaPlayer.PlayingState) {
|
|
vid.pause()
|
|
} else {
|
|
vid.play()
|
|
}
|
|
} else {
|
|
root.downloadAndPlay()
|
|
}
|
|
}
|
|
|
|
MediaSizeHelper {
|
|
id: mediaSizeHelper
|
|
contentMaxWidth: root.contentMaxWidth
|
|
mediaWidth: root.mediaInfo.width
|
|
mediaHeight: root.mediaInfo.height
|
|
}
|
|
}
|
|
|
|
function downloadAndPlay() {
|
|
if (vid.downloaded) {
|
|
playSavedFile()
|
|
} else {
|
|
playOnFinished = true
|
|
ListView.view.currentRoom.downloadFile(root.eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + root.eventId.replace(":", "_").replace("/", "_").replace("+", "_") + ListView.view.currentRoom.fileNameToDownload(root.eventId))
|
|
}
|
|
}
|
|
|
|
function playSavedFile() {
|
|
vid.stop()
|
|
vid.play()
|
|
}
|
|
}
|