kasts/src/qml/GenericEntryDelegate.qml

361 lines
15 KiB
QML

/**
* SPDX-FileCopyrightText: 2021-2023 Bart De Vries <bart@mogwai.be>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.15
import QtQuick.Controls 2.14 as Controls
import QtQuick.Layouts 1.14
import QtGraphicalEffects 1.15
import QtQml.Models 2.15
import org.kde.kirigami 2.14 as Kirigami
import org.kde.kasts.solidextras 1.0
import org.kde.kmediasession 1.0
import org.kde.kasts 1.0
Kirigami.SwipeListItem {
id: listItem
alwaysVisibleActions: true
separatorVisible: true
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
visible: entry ? true : false
property bool isQueue: false
property bool isDownloads: false
property QtObject listView: undefined
property bool selected: false
property int row: model ? model.row : -1
property bool streamingAllowed: (NetworkStatus.connectivity !== NetworkStatus.No && SettingsManager.prioritizeStreaming && (SettingsManager.allowMeteredStreaming || NetworkStatus.metered !== NetworkStatus.Yes))
property bool showRemoveFromQueueButton: entry ? (!entry.enclosure && entry.queueStatus) : false
property bool showDownloadButton: entry ? ((!isDownloads || entry.enclosure.status === Enclosure.PartiallyDownloaded) && entry.enclosure && (entry.enclosure.status === Enclosure.Downloadable || entry.enclosure.status === Enclosure.PartiallyDownloaded) && (!streamingAllowed || isDownloads) && !(AudioManager.entry === entry && AudioManager.playbackState === KMediaSession.PlayingState)) : false
property bool showCancelDownloadButton: entry ? (entry.enclosure && entry.enclosure.status === Enclosure.Downloading) : false
property bool showDeleteDownloadButton: entry ? (isDownloads && entry.enclosure && entry.enclosure.status === Enclosure.Downloaded) : false
property bool showAddToQueueButton: entry ? (!isDownloads && !entry.queueStatus && entry.enclosure && entry.enclosure.status === Enclosure.Downloaded) : false
property bool showPlayButton: entry ? (!isDownloads && entry.queueStatus && entry.enclosure && (entry.enclosure.status === Enclosure.Downloaded) && (AudioManager.entry !== entry || AudioManager.playbackState !== KMediaSession.PlayingState)) : false
property bool showStreamingPlayButton: entry ? (!isDownloads && entry.enclosure && (entry.enclosure.status !== Enclosure.Downloaded && entry.enclosure.status !== Enclosure.Downloading && streamingAllowed) && (AudioManager.entry !== entry || AudioManager.playbackState !== KMediaSession.PlayingState)) : false
property bool showPauseButton: entry ? (!isDownloads && entry.queueStatus && entry.enclosure && (AudioManager.entry === entry && AudioManager.playbackState === KMediaSession.PlayingState)) : false
highlighted: selected
activeBackgroundColor: Qt.lighter(Kirigami.Theme.highlightColor, 1.3)
Accessible.role: Accessible.Button
Accessible.name: entry ? entry.title : ""
Accessible.onPressAction: {
listItem.clicked();
}
Keys.onReturnPressed: clicked()
// We need to update the "selected" status:
// - if the selected indexes changes
// - if our delegate moves
// - if the model moves and the delegate stays in the same place
function updateIsSelected() {
selected = listView.selectionModel.rowIntersectsSelection(row);
}
onRowChanged: {
updateIsSelected();
}
Component.onCompleted: {
updateIsSelected();
}
contentItem: MouseArea {
id: mouseArea
implicitHeight: rowLayout.implicitHeight
implicitWidth: rowLayout.implicitWidth
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
// Keep track of (currently) selected items
var modelIndex = listItem.listView.model.index(index, 0);
if (listView.selectionModel.isSelected(modelIndex) && mouse.button == Qt.RightButton) {
listView.contextMenu.popup(null, mouse.x+1, mouse.y+1);
} else if (mouse.modifiers & Qt.ShiftModifier) {
// Have to take a detour through c++ since selecting large sets
// in QML is extremely slow
listView.selectionModel.select(listView.model.createSelection(modelIndex.row, listView.selectionModel.currentIndex.row), ItemSelectionModel.ClearAndSelect | ItemSelectionModel.Rows);
} else if (mouse.modifiers & Qt.ControlModifier) {
listView.selectionModel.select(modelIndex, ItemSelectionModel.Toggle | ItemSelectionModel.Rows);
} else if (mouse.button == Qt.LeftButton) {
listView.currentIndex = index;
listView.selectionModel.setCurrentIndex(modelIndex, ItemSelectionModel.ClearAndSelect | ItemSelectionModel.Rows);
listItem.clicked();
} else if (mouse.button == Qt.RightButton) {
// This item is right-clicked, but isn't selected
listView.selectionForContextMenu = [modelIndex];
listView.contextMenu.popup(null, mouse.x+1, mouse.y+1);
}
}
onPressAndHold: {
var modelIndex = listItem.listView.model.index(index, 0);
listView.selectionModel.select(modelIndex, ItemSelectionModel.Toggle | ItemSelectionModel.Rows);
}
Connections {
target: listView.selectionModel
function onSelectionChanged() {
updateIsSelected();
}
}
Connections {
target: listView.model
function onLayoutChanged() {
updateIsSelected();
}
}
RowLayout {
id: rowLayout
anchors.fill: parent
Loader {
property var loaderListView: listView
property var loaderListItem: listItem
sourceComponent: dragHandleComponent
active: isQueue
}
Component {
id: dragHandleComponent
Kirigami.ListItemDragHandle {
listItem: loaderListItem
listView: loaderListView
onMoveRequested: {
DataManager.moveQueueItem(oldIndex, newIndex);
// reset current selection when moving items
var modelIndex = listItem.listView.model.index(newIndex, 0);
listView.currentIndex = newIndex;
listView.selectionModel.setCurrentIndex(modelIndex, ItemSelectionModel.ClearAndSelect | ItemSelectionModel.Rows);
}
}
}
ImageWithFallback {
id: img
imageSource: entry ? entry.cachedImage : "no-image"
property int size: Kirigami.Units.gridUnit * 3
Layout.preferredHeight: size
Layout.preferredWidth: size
Layout.rightMargin: Kirigami.Units.smallSpacing
fractionalRadius: 1.0 / 8.0
}
ColumnLayout {
spacing: Kirigami.Units.smallSpacing
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
RowLayout{
Kirigami.Icon {
Layout.maximumHeight: playedLabel.implicitHeight
Layout.maximumWidth: playedLabel.implicitHeight
source: "checkbox"
visible: entry ? entry.read : false
}
Controls.Label {
id: playedLabel
text: entry ? ((entry.enclosure ? i18n("Played") : i18n("Read")) + " ·") : ""
font: Kirigami.Theme.smallFont
visible: entry ? entry.read : false
opacity: 0.7
}
Controls.Label {
text: entry ? (entry.new ? i18n("New") + " ·" : "") : ""
font.capitalization: Font.AllUppercase
color: Kirigami.Theme.highlightColor
visible: entry ? entry.new : false
opacity: 0.7
}
Kirigami.Icon {
Layout.maximumHeight: 0.8 * supertitle.implicitHeight
Layout.maximumWidth: 0.8 * supertitle.implicitHeight
source: "starred-symbolic"
visible: entry ? (entry.favorite) : false
opacity: 0.7
}
Kirigami.Icon {
Layout.maximumHeight: 0.8 * supertitle.implicitHeight
Layout.maximumWidth: 0.8 * supertitle.implicitHeight
source: "source-playlist"
visible: entry ? (!isQueue && entry.queueStatus) : false
opacity: 0.7
}
Controls.Label {
id: supertitle
text: entry ? (((!isQueue && entry.queueStatus) || entry.favorite ? "· " : "") + entry.updated.toLocaleDateString(Qt.locale(), Locale.NarrowFormat) + (entry.enclosure ? ( entry.enclosure.size !== 0 ? " · " + entry.enclosure.formattedSize : "") : "" )) : ""
Layout.fillWidth: true
elide: Text.ElideRight
font: Kirigami.Theme.smallFont
opacity: 0.7
}
}
Controls.Label {
text: entry ? entry.title : ""
Layout.fillWidth: true
elide: Text.ElideRight
font.weight: Font.Normal
}
Loader {
sourceComponent: entry ? (entry.enclosure && (entry.enclosure.status === Enclosure.Downloading || (isDownloads && entry.enclosure.status === Enclosure.PartiallyDownloaded)) ? downloadProgress : ( entry.enclosure && entry.enclosure.playPosition > 0 ? playProgress : subtitle)) : undefined
Layout.fillWidth: true
}
Component {
id: subtitle
Controls.Label {
text: entry ? (entry.enclosure ? entry.enclosure.formattedDuration : "") : ""
Layout.fillWidth: true
elide: Text.ElideRight
font: Kirigami.Theme.smallFont
opacity: 0.7
visible: !downloadProgress.visible
}
}
Component {
id: downloadProgress
RowLayout {
Controls.Label {
text: entry ? entry.enclosure.formattedDownloadSize : ""
elide: Text.ElideRight
font: Kirigami.Theme.smallFont
opacity: 0.7
}
Controls.ProgressBar {
from: 0
to: 1
value: entry ? entry.enclosure.downloadProgress : 0
Layout.fillWidth: true
}
Controls.Label {
text: entry ? entry.enclosure.formattedSize : ""
elide: Text.ElideRight
font: Kirigami.Theme.smallFont
opacity: 0.7
}
}
}
Component {
id: playProgress
RowLayout {
Controls.Label {
text: entry ? entry.enclosure.formattedPlayPosition : ""
elide: Text.ElideRight
font: Kirigami.Theme.smallFont
opacity: 0.7
}
Controls.ProgressBar {
from: 0
to: entry ? entry.enclosure.duration : 1
value: entry ? entry.enclosure.playPosition / 1000 : 0
Layout.fillWidth: true
}
Controls.Label {
text: entry ? ((SettingsManager.toggleRemainingTime)
? "-" + entry.enclosure.formattedLeftDuration
: entry.enclosure.formattedDuration) : ""
elide: Text.ElideRight
font: Kirigami.Theme.smallFont
opacity: 0.7
}
}
}
}
}
}
onClicked: {
// only mark pure rss feeds as read + not new;
// podcasts should only be marked read once they have been listened to, and only
// marked as non-new once they've been downloaded
if (!entry.enclosure) {
entry.read = true;
entry.new = false;
}
if (isQueue || isDownloads) {
lastEntry = entry.id;
}
if (pageStack.depth > (currentPage === "FeedListPage" ? 2 : 1))
pageStack.pop();
pageStack.push("qrc:/EntryPage.qml", {"entry": entry});
}
actions: [
Kirigami.Action {
text: i18n("Remove from Queue")
icon.name: "list-remove"
onTriggered: {
entry.queueStatus = false;
}
visible: showRemoveFromQueueButton
},
Kirigami.Action {
text: i18n("Download")
icon.name: "download"
onTriggered: {
downloadOverlay.entry = entry;
downloadOverlay.run();
}
visible: showDownloadButton
},
Kirigami.Action {
text: i18n("Cancel Download")
icon.name: "edit-delete-remove"
onTriggered: entry.enclosure.cancelDownload()
visible: showCancelDownloadButton
},
Kirigami.Action {
text: i18n("Delete Download")
icon.name: "delete"
onTriggered: entry.enclosure.deleteFile()
visible: showDeleteDownloadButton
},
Kirigami.Action {
text: i18n("Add to Queue")
icon.name: "media-playlist-append"
visible: showAddToQueueButton
onTriggered: entry.queueStatus = true
},
Kirigami.Action {
text: i18n("Play")
icon.name: "media-playback-start"
visible: showPlayButton
onTriggered: {
AudioManager.entry = entry;
AudioManager.play();
}
},
Kirigami.Action {
text: i18nc("@action:inmenu Action to start playback by streaming the episode rather than downloading it first", "Stream")
icon.name: "media-playback-cloud"
visible: showStreamingPlayButton
onTriggered: {
if (!entry.queueStatus) {
entry.queueStatus = true;
}
AudioManager.entry = entry;
AudioManager.play();
}
},
Kirigami.Action {
text: i18n("Pause")
icon.name: "media-playback-pause"
visible: showPauseButton
onTriggered: AudioManager.pause()
}
]
}