mirror of https://github.com/KDE/kasts.git
Header bar redesign
New scalable header bar design which should scale nicely with height and width changes by collapsing several elements (putting them into popups and overflow menus). The height scaling of the header is similar to Elisa, where it will use the regular background color when fully collapsed. Titles are clickable and will open the relevant pages. Images are also clickable and that will open a fullscreen view. This new design also exposes volume controls both for desktop and mobile layout. BUG: 457846 CCBUG: 458331
This commit is contained in:
parent
03f8c3a544
commit
d869358ff5
|
@ -219,7 +219,8 @@ if(ANDROID)
|
|||
kaccounts-nextcloud
|
||||
clock
|
||||
viewimage
|
||||
media-playback-start-cloud
|
||||
player-volume-muted
|
||||
player-volume
|
||||
)
|
||||
else()
|
||||
target_link_libraries(kasts PRIVATE Qt::Widgets)
|
||||
|
|
|
@ -35,7 +35,7 @@ Kirigami.BasicListItem {
|
|||
}
|
||||
|
||||
trailing: Controls.ToolButton {
|
||||
icon.name: streamingButtonVisible ? ":/media-playback-start-cloud" : "media-playback-start"
|
||||
icon.name: streamingButtonVisible ? "qrc:/media-playback-start-cloud" : "media-playback-start"
|
||||
text: i18n("Play")
|
||||
enabled: entry != undefined && entry.enclosure && (entry.enclosure.status === Enclosure.Downloaded || streamingButtonVisible)
|
||||
display: Controls.Button.IconOnly
|
||||
|
|
|
@ -0,0 +1,438 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 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.14
|
||||
import QtQuick.Controls 2.14 as Controls
|
||||
import QtQuick.Layouts 1.14
|
||||
import QtQml.Models 2.15
|
||||
|
||||
import org.kde.kirigami 2.19 as Kirigami
|
||||
import org.kde.kmediasession 1.0
|
||||
|
||||
import org.kde.kasts 1.0
|
||||
|
||||
FocusScope {
|
||||
id: desktopPlayerControls
|
||||
implicitHeight: playerControlToolBar.implicitHeight + Kirigami.Units.largeSpacing * 2
|
||||
|
||||
property alias chapterModel: chapterModel
|
||||
/*
|
||||
* Emmited when User uses the Item as a handle to resize the layout.
|
||||
* y: difference to previous position
|
||||
* offset: cursor offset (y coordinate relative to this Item, where dragging
|
||||
* begun)
|
||||
*/
|
||||
signal handlePositionChanged(int y, int offset)
|
||||
|
||||
Rectangle {
|
||||
id: toolbarBackground
|
||||
anchors.fill: parent
|
||||
|
||||
opacity: 0.7
|
||||
|
||||
//set background color
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Header
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
property int dragStartOffset: 0
|
||||
|
||||
cursorShape: Qt.SizeVerCursor
|
||||
|
||||
onPressed: {
|
||||
dragStartOffset = mouse.y
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
desktopPlayerControls.handlePositionChanged(mouse.y, dragStartOffset)
|
||||
}
|
||||
|
||||
drag.axis: Drag.YAxis
|
||||
drag.threshold: 1
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: playerControlToolBar
|
||||
property int iconSize: Kirigami.Units.gridUnit
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Kirigami.Units.largeSpacing
|
||||
anchors.bottomMargin: Kirigami.Units.largeSpacing
|
||||
anchors.rightMargin: Kirigami.Units.smallSpacing
|
||||
anchors.leftMargin: Kirigami.Units.smallSpacing
|
||||
|
||||
property int audioSliderNiceMinimumWidth: 300
|
||||
property int audioSliderAbsoluteMinimumWidth: 200
|
||||
|
||||
// size of volumeButton serves as size of extra buttons too
|
||||
// this is chosen because the volumeButton is always visible
|
||||
property bool tooNarrowExtra: playerControlToolBar.width - (audioButtons.width + 4 * volumeButton.width + (chapterAction.visible ? chapterTextMetric.width : 0) + extraButtonsTextMetric.width) < audioSliderNiceMinimumWidth + 40
|
||||
|
||||
property bool tooNarrowChapter: playerControlToolBar.width - (audioButtons.width + 4 * volumeButton.width + (chapterAction.visible ? chapterTextMetric.width : 0)) < audioSliderNiceMinimumWidth + 20
|
||||
|
||||
property bool tooNarrowOverflow: playerControlToolBar.width - (audioButtons.width + 4 * volumeButton.width) < audioSliderNiceMinimumWidth
|
||||
|
||||
property bool tooNarrowAudioLabels: playerControlToolBar.width - (audioButtons.width + 2 * volumeButton.width) < audioSliderAbsoluteMinimumWidth
|
||||
|
||||
clip: true
|
||||
|
||||
Loader {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: height
|
||||
active: headerBar.handlePosition === 0
|
||||
visible: active
|
||||
sourceComponent: imageComponent
|
||||
}
|
||||
|
||||
Component {
|
||||
id: imageComponent
|
||||
ImageWithFallback {
|
||||
id: frontImage
|
||||
imageSource: headerMetaData.image
|
||||
absoluteRadius: Kirigami.Units.smallSpacing
|
||||
visible: headerBar.handlePosition === 0
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
headerBar.openFullScreenImage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: audioButtons
|
||||
Controls.ToolButton {
|
||||
icon.name: "media-seek-backward"
|
||||
onClicked: AudioManager.skipBackward()
|
||||
enabled: AudioManager.canSkipBackward
|
||||
|
||||
Controls.ToolTip.visible: hovered
|
||||
Controls.ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
Controls.ToolTip.text: i18n("Seek Backward")
|
||||
}
|
||||
Controls.ToolButton {
|
||||
id: playButton
|
||||
icon.name: AudioManager.playbackState === KMediaSession.PlayingState ? "media-playback-pause" : "media-playback-start"
|
||||
onClicked: AudioManager.playbackState === KMediaSession.PlayingState ? AudioManager.pause() : AudioManager.play()
|
||||
enabled: AudioManager.canPlay
|
||||
|
||||
Controls.ToolTip.visible: hovered
|
||||
Controls.ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
Controls.ToolTip.text: AudioManager.playbackState === KMediaSession.PlayingState ? i18n("Pause") : i18n("Play")
|
||||
}
|
||||
Controls.ToolButton {
|
||||
icon.name: "media-seek-forward"
|
||||
onClicked: AudioManager.skipForward()
|
||||
enabled: AudioManager.canSkipForward
|
||||
|
||||
Controls.ToolTip.visible: hovered
|
||||
Controls.ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
Controls.ToolTip.text: i18n("Seek Forward")
|
||||
}
|
||||
Controls.ToolButton {
|
||||
icon.name: "media-skip-forward"
|
||||
onClicked: AudioManager.next()
|
||||
enabled: AudioManager.canGoNext
|
||||
|
||||
Controls.ToolTip.visible: hovered
|
||||
Controls.ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
Controls.ToolTip.text: i18n("Skip Forward")
|
||||
}
|
||||
Controls.ToolButton {
|
||||
contentItem: Controls.Label {
|
||||
text: AudioManager.playbackRate + "x"
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
onClicked: playbackRateDialog.open()
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
padding: 0
|
||||
implicitWidth: playButton.width * 1.5
|
||||
implicitHeight: playButton.height
|
||||
|
||||
Controls.ToolTip.visible: hovered
|
||||
Controls.ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
Controls.ToolTip.text: i18n("Playback Rate: ") + AudioManager.playbackRate + "x"
|
||||
}
|
||||
}
|
||||
|
||||
Controls.Label {
|
||||
id: currentPositionLabel
|
||||
text: AudioManager.formattedPosition
|
||||
visible: !playerControlToolBar.tooNarrowAudioLabels
|
||||
}
|
||||
|
||||
Controls.Slider {
|
||||
id: durationSlider
|
||||
enabled: AudioManager.entry
|
||||
Layout.fillWidth: true
|
||||
padding: 0
|
||||
from: 0
|
||||
to: AudioManager.duration / 1000
|
||||
value: AudioManager.position / 1000
|
||||
onMoved: AudioManager.seek(value * 1000)
|
||||
handle.implicitWidth: implicitHeight // workaround to make slider handle position itself exactly at the location of the click
|
||||
}
|
||||
|
||||
Item {
|
||||
id: durationLabel
|
||||
visible: !playerControlToolBar.tooNarrowAudioLabels
|
||||
Layout.preferredHeight: endLabel.implicitHeight
|
||||
Layout.preferredWidth: endLabel.implicitWidth
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
Controls.Label {
|
||||
id: endLabel
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: (SettingsManager.toggleRemainingTime) ?
|
||||
"-" + AudioManager.formattedLeftDuration
|
||||
: AudioManager.formattedDuration
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
SettingsManager.toggleRemainingTime = !SettingsManager.toggleRemainingTime;
|
||||
SettingsManager.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: extraButtons
|
||||
visible: !playerControlToolBar.tooNarrowOverflow
|
||||
Controls.ToolButton {
|
||||
id: chapterButton
|
||||
action: chapterAction
|
||||
display: playerControlToolBar.tooNarrowChapter ? Controls.AbstractButton.IconOnly : Controls.AbstractButton.TextBesideIcon
|
||||
visible: chapterAction.visible && !playerControlToolBar.tooNarrowOverflow
|
||||
Controls.ToolTip {
|
||||
visible: parent.hovered
|
||||
delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
text: i18nc("@action:button", "Show Chapter List")
|
||||
}
|
||||
}
|
||||
|
||||
Controls.ToolButton {
|
||||
id: infoButton
|
||||
action: infoAction
|
||||
display: playerControlToolBar.tooNarrowExtra ? Controls.AbstractButton.IconOnly : Controls.AbstractButton.TextBesideIcon
|
||||
visible: infoAction.visible && !playerControlToolBar.tooNarrowOverflow
|
||||
Controls.ToolTip {
|
||||
visible: parent.hovered
|
||||
delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
text: i18nc("@action:button", "Show Episode Info")
|
||||
}
|
||||
}
|
||||
|
||||
Controls.ToolButton {
|
||||
id: sleepButton
|
||||
action: sleepAction
|
||||
display: playerControlToolBar.tooNarrowExtra ? Controls.AbstractButton.IconOnly : Controls.AbstractButton.TextBesideIcon
|
||||
visible: !playerControlToolBar.tooNarrowOverflow
|
||||
Controls.ToolTip {
|
||||
visible: parent.hovered
|
||||
delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
text: i18nc("@action:button", "Open Sleep Timer Settings")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: volumeControls
|
||||
Controls.ToolButton {
|
||||
id: volumeButton
|
||||
icon.name: AudioManager.muted ? "player-volume-muted" : "player-volume"
|
||||
enabled: AudioManager.PlaybackState != AudioManager.StoppedState && AudioManager.canPlay
|
||||
checked: volumePopup.visible
|
||||
Controls.ToolTip {
|
||||
visible: parent.hovered
|
||||
delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
text: i18nc("@action:button", "Open Volume Settings")
|
||||
}
|
||||
onClicked: {
|
||||
if (volumePopup.visible) {
|
||||
volumePopup.close();
|
||||
} else {
|
||||
volumePopup.open();
|
||||
}
|
||||
}
|
||||
|
||||
Controls.Popup {
|
||||
id: volumePopup
|
||||
x: -padding
|
||||
y: volumeButton.height
|
||||
|
||||
focus: true
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
contentWidth: volumeButtonVertical.implicitWidth
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
Controls.Slider {
|
||||
id: volumeSliderVertical
|
||||
height: Kirigami.Units.gridUnit * 7
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredHeight: height
|
||||
Layout.maximumHeight: height
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
orientation: Qt.Vertical
|
||||
padding: 0
|
||||
enabled: !AudioManager.muted && AudioManager.PlaybackState != AudioManager.StoppedState && AudioManager.canPlay
|
||||
from: 0
|
||||
to: 100
|
||||
value: AudioManager.volume
|
||||
onMoved: AudioManager.volume = value
|
||||
handle.implicitWidth: implicitHeight // workaround to make slider handle position itself exactly at the location of the click
|
||||
}
|
||||
|
||||
Controls.ToolButton {
|
||||
id: volumeButtonVertical
|
||||
enabled: AudioManager.PlaybackState != AudioManager.StoppedState && AudioManager.canPlay
|
||||
icon.name: AudioManager.muted ? "player-volume-muted" : "player-volume"
|
||||
onClicked: AudioManager.muted = !AudioManager.muted
|
||||
Controls.ToolTip {
|
||||
visible: parent.hovered
|
||||
delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
text: i18nc("@action:button", "Toggle Mute")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Controls.ToolButton {
|
||||
id: overflowButton
|
||||
icon.name: "overflow-menu"
|
||||
display: Controls.AbstractButton.IconOnly
|
||||
visible: playerControlToolBar.tooNarrowOverflow
|
||||
checked: overflowMenu.visible
|
||||
Controls.ToolTip {
|
||||
visible: parent.hovered
|
||||
delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
text: i18nc("@action:button", "Show More")
|
||||
}
|
||||
onClicked: {
|
||||
if (overflowMenu.visible) {
|
||||
overflowMenu.dismiss();
|
||||
} else {
|
||||
overflowMenu.popup(0, overflowButton.height)
|
||||
}
|
||||
}
|
||||
|
||||
Controls.Menu {
|
||||
id: overflowMenu
|
||||
contentData: extraActions
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
for (var i in contentData) {
|
||||
overflowMenu.contentData[i].visible = overflowMenu.contentData[i].action.visible;
|
||||
overflowMenu.contentData[i].height =
|
||||
(overflowMenu.contentData[i].action.visible) ? overflowMenu.contentData[i].implicitHeight : 0 // workaround for qqc2-breeze-style
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actions which will be used to create buttons on toolbar or in overflow menu
|
||||
Kirigami.Action {
|
||||
id: chapterAction
|
||||
property bool visible: AudioManager.entry && chapterList.count !== 0
|
||||
text: i18nc("@action:button", "Chapters")
|
||||
icon.name: "view-media-playlist"
|
||||
onTriggered: chapterOverlay.open();
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
id: infoAction
|
||||
property bool visible: AudioManager.entry
|
||||
text: i18nc("@action:button", "Show Info")
|
||||
icon.name: "documentinfo"
|
||||
onTriggered: entryDetailsOverlay.open();
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
id: sleepAction
|
||||
checkable: true
|
||||
checked: AudioManager.remainingSleepTime > 0
|
||||
property bool visible: true
|
||||
text: i18nc("@action:button", "Sleep Timer")
|
||||
icon.name: "clock"
|
||||
onTriggered: {
|
||||
toggle(); // only set the on/off state based on sleep timer state
|
||||
sleepTimerDialog.open();
|
||||
}
|
||||
}
|
||||
|
||||
property var extraActions: [ chapterAction,
|
||||
infoAction,
|
||||
sleepAction ]
|
||||
|
||||
TextMetrics {
|
||||
id: chapterTextMetric
|
||||
text: chapterAction.text
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: extraButtonsTextMetric
|
||||
text: infoAction.text + sleepAction.text
|
||||
}
|
||||
|
||||
ChapterModel {
|
||||
id: chapterModel
|
||||
entry: AudioManager.entry ? AudioManager.entry : null
|
||||
}
|
||||
|
||||
Kirigami.Dialog {
|
||||
id: chapterOverlay
|
||||
preferredWidth: Kirigami.Units.gridUnit * 30
|
||||
preferredHeight: Kirigami.Units.gridUnit * 25
|
||||
|
||||
showCloseButton: false
|
||||
|
||||
title: i18n("Chapters")
|
||||
|
||||
ListView {
|
||||
id: chapterList
|
||||
model: chapterModel
|
||||
delegate: ChapterListDelegate {
|
||||
id: chapterDelegate
|
||||
width: chapterList.width
|
||||
entry: AudioManager.entry ? AudioManager.entry : null
|
||||
overlay: chapterOverlay
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Dialog {
|
||||
id: entryDetailsOverlay
|
||||
preferredWidth: Kirigami.Units.gridUnit * 30
|
||||
|
||||
showCloseButton: false
|
||||
|
||||
title: AudioManager.entry ? AudioManager.entry.title : i18n("No Track Title")
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
|
||||
Controls.Label {
|
||||
id: text
|
||||
text: AudioManager.entry ? AudioManager.entry.content : i18n("No Track Loaded")
|
||||
verticalAlignment: Text.AlignTop
|
||||
baseUrl: AudioManager.entry ? AudioManager.entry.baseUrl : ""
|
||||
textFormat: Text.RichText
|
||||
wrapMode: Text.WordWrap
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2021 Swapnil Tripathi <swapnil06.st@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2021-2023 Bart De Vries <bart@mogwai.be>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.14
|
||||
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.19 as Kirigami
|
||||
import org.kde.kmediasession 1.0
|
||||
|
||||
import org.kde.kasts 1.0
|
||||
|
||||
FocusScope {
|
||||
id: headerBar
|
||||
height: headerMetaData.implicitHeight + desktopPlayerControls.implicitHeight
|
||||
|
||||
property int handlePosition: settings.headerSize
|
||||
property int maximumHeight: Kirigami.Units.gridUnit * 8
|
||||
property int minimumImageSize: Kirigami.Units.gridUnit * 1.5
|
||||
property int subtitleCollapseHeight: Kirigami.Units.gridUnit * 2.5
|
||||
property int authorCollapseHeight: Kirigami.Units.gridUnit * 4
|
||||
property int disappearHeight: Kirigami.Units.gridUnit * 1.0
|
||||
|
||||
function openEntry() {
|
||||
if (AudioManager.entry) {
|
||||
pushPage("QueuePage");
|
||||
pageStack.push("qrc:/EntryPage.qml", {"entry": AudioManager.entry});
|
||||
SettingsManager.lastOpenedPage = "QueuePage";
|
||||
SettingsManager.save();
|
||||
pageStack.get(0).lastEntry = AudioManager.entry.id;
|
||||
var model = pageStack.get(0).queueList.model;
|
||||
for (var i = 0; i < model.rowCount(); i++) {
|
||||
var index = model.index(i, 0);
|
||||
if (AudioManager.entry == model.data(index, EpisodeModel.EntryRole)) {
|
||||
pageStack.get(0).queueList.currentIndex = i;
|
||||
pageStack.get(0).queueList.selectionModel.setCurrentIndex(index, ItemSelectionModel.ClearAndSelect | ItemSelectionModel.Rows);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openFeed() {
|
||||
if (AudioManager.entry) {
|
||||
pushPage("FeedListPage");
|
||||
pageStack.push("qrc:/FeedDetailsPage.qml", {"feed": AudioManager.entry.feed});
|
||||
SettingsManager.lastOpenedPage = "FeedListPage";
|
||||
SettingsManager.save();
|
||||
}
|
||||
}
|
||||
|
||||
function openFullScreenImage() {
|
||||
const dialog = fullScreenImage.createObject(parent, {
|
||||
"image": headerMetaData.image,
|
||||
"description": headerMetaData.title
|
||||
});
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
//set background color
|
||||
anchors.fill: parent
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Header
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: backgroundImageLoader
|
||||
active: handlePosition > 0
|
||||
anchors.fill: parent
|
||||
sourceComponent: backgroundImageComponent
|
||||
}
|
||||
|
||||
Component {
|
||||
id: backgroundImageComponent
|
||||
ImageWithFallback {
|
||||
id: backgroundImage
|
||||
|
||||
imageSource: headerMetaData.blurredImage
|
||||
imageResize: false // no "stuttering" on resizing the window
|
||||
|
||||
opacity: handlePosition > 0 ? 1 : 0
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: HueSaturation {
|
||||
cached: true
|
||||
|
||||
lightness: -0.6
|
||||
saturation: 0.9
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: FastBlur {
|
||||
cached: true
|
||||
radius: 64
|
||||
transparentBorder: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: headerMetaData
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
opacity: height - headerBar.disappearHeight > Kirigami.Units.largeSpacing ? 1 : (height - headerBar.disappearHeight < 0 ? 0 : (height - headerBar.disappearHeight) / Kirigami.Units.largeSpacing)
|
||||
|
||||
visible: opacity === 0 ? false : true
|
||||
|
||||
property string image: AudioManager.entry ? ((desktopPlayerControls.chapterModel.currentChapter && desktopPlayerControls.chapterModel.currentChapter !== undefined) ? desktopPlayerControls.chapterModel.currentChapter.cachedImage : AudioManager.entry.cachedImage) : "no-image"
|
||||
property string blurredImage: AudioManager.entry ? AudioManager.entry.cachedImage : "no-image"
|
||||
property string title: AudioManager.entry ? AudioManager.entry.title : i18n("No Track Title")
|
||||
property string feed: AudioManager.entry ? AudioManager.entry.feed.name : i18n("No Track Loaded")
|
||||
property string authors: AudioManager.entry ? (AudioManager.entry.feed.authors.length !== 0 ? AudioManager.entry.feed.authors[0].name : undefined) : undefined
|
||||
|
||||
implicitHeight: headerBar.handlePosition
|
||||
implicitWidth: parent.width
|
||||
|
||||
RowLayout {
|
||||
property int margin: Kirigami.Units.gridUnit * 1
|
||||
anchors.fill: parent
|
||||
anchors.margins: margin
|
||||
anchors.topMargin: parent.height > headerBar.minimumImageSize + 2 * margin ? margin : (parent.height - headerBar.minimumImageSize) / 2
|
||||
anchors.bottomMargin: parent.height > headerBar.minimumImageSize + 2 * margin ? margin : (parent.height - headerBar.minimumImageSize) / 2
|
||||
|
||||
ImageWithFallback {
|
||||
id: frontImage
|
||||
imageSource: headerMetaData.image
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: height
|
||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 1.5
|
||||
absoluteRadius: Kirigami.Units.smallSpacing
|
||||
imageResize: false
|
||||
mipmap: true
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
openFullScreenImage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: labelLayout
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: parent.margin / 2
|
||||
|
||||
Controls.Label {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
text: headerMetaData.title
|
||||
fontSizeMode: Text.Fit
|
||||
font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 1.4)
|
||||
minimumPointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 1.1)
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignBottom
|
||||
color: "#eff0f1" // breeze light text color
|
||||
opacity: 1
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.WordWrap
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
openEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Controls.Label {
|
||||
visible: labelLayout.height > headerBar.subtitleCollapseHeight
|
||||
Layout.fillWidth: true
|
||||
text: headerMetaData.feed
|
||||
fontSizeMode: Text.Fit
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.1
|
||||
minimumPointSize: Kirigami.Theme.defaultFont.pointSize
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
color: "#eff0f1" // breeze light text color
|
||||
elide: Text.ElideRight
|
||||
opacity: 1
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
openFeed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Controls.Label {
|
||||
visible: (headerMetaData.authors) && labelLayout.height > headerBar.authorCollapseHeight
|
||||
Layout.fillWidth: true
|
||||
text: headerMetaData.authors
|
||||
fontSizeMode: Text.Fit
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.1
|
||||
minimumPointSize: Kirigami.Theme.defaultFont.pointSize
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
color: "#eff0f1" // breeze light text color
|
||||
elide: Text.ElideRight
|
||||
opacity: 1
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
openFeed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DesktopPlayerControls {
|
||||
id: desktopPlayerControls
|
||||
|
||||
anchors.top: headerMetaData.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
onHandlePositionChanged: {
|
||||
handlePosition = Math.max(0, Math.min(headerBar.maximumHeight, headerBar.height - implicitHeight - offset + y));
|
||||
settings.headerSize = handlePosition;
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
width: parent.width
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
}
|
|
@ -74,7 +74,7 @@ Kirigami.ScrollablePage {
|
|||
}
|
||||
]
|
||||
onClicked: {
|
||||
pageStack.push("qrc:/FeedDetailsPage.qml", {"feed": subscribeAction.enabled ? model : DataManager.getFeed(model.url), "isSubscribed": !subscribeAction.enabled, "subscribeAction": subscribeAction})
|
||||
pageStack.push("qrc:/FeedDetailsPage.qml", {"feed": subscribeAction.enabled ? model : DataManager.getFeed(model.url), "isSubscribed": !subscribeAction.enabled, "subscribeAction": subscribeAction, "showMoreInfo": true})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -163,7 +163,7 @@ Kirigami.ScrollablePage {
|
|||
Kirigami.Action {
|
||||
text: i18nc("Action to start playback by streaming the episode rather than downloading it first", "Stream")
|
||||
visible: entry.enclosure && entry.enclosure.status !== Enclosure.Downloaded && (AudioManager.entry !== entry || AudioManager.playbackState !== KMediaSession.PlayingState)
|
||||
icon.name: ":/media-playback-start-cloud"
|
||||
icon.name: "qrc:/media-playback-start-cloud"
|
||||
onTriggered: {
|
||||
if (!entry.queueStatus) {
|
||||
entry.queueStatus = true;
|
||||
|
|
|
@ -155,6 +155,7 @@ Kirigami.ScrollablePage {
|
|||
iconName: "documentinfo"
|
||||
text: i18n("Show Details")
|
||||
checkable: true
|
||||
checked: showMoreInfo
|
||||
onCheckedChanged: {
|
||||
showMoreInfo = checked;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
* SPDX-FileCopyrightText: 2023 Bart De Vries <bart@mogwai.be>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14 as Controls
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import org.kde.kirigami 2.19 as Kirigami
|
||||
|
||||
Controls.Popup {
|
||||
id: root
|
||||
|
||||
required property var image
|
||||
property string description: undefined
|
||||
|
||||
property int imageWidth: -1
|
||||
property int imageHeight: -1
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
parent: Controls.Overlay.overlay
|
||||
closePolicy: Controls.Popup.CloseOnEscape
|
||||
modal: true
|
||||
padding: 0
|
||||
background: null
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Controls.Control {
|
||||
Layout.fillWidth: true
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.ActionToolBar {
|
||||
Layout.fillWidth: true
|
||||
alignment: Qt.AlignRight
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
text: i18n("Zoom in")
|
||||
icon.name: "zoom-in"
|
||||
displayHint: Kirigami.DisplayHint.IconOnly
|
||||
onTriggered: {
|
||||
imageItem.scaleFactor = imageItem.scaleFactor + 0.25
|
||||
if (imageItem.scaleFactor > 3) {
|
||||
imageItem.scaleFactor = 3
|
||||
}
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Zoom out")
|
||||
icon.name: "zoom-out"
|
||||
displayHint: Kirigami.DisplayHint.IconOnly
|
||||
onTriggered: {
|
||||
imageItem.scaleFactor = imageItem.scaleFactor - 0.25
|
||||
if (imageItem.scaleFactor < 0.25) {
|
||||
imageItem.scaleFactor = 0.25
|
||||
}
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Rotate left")
|
||||
icon.name: "image-rotate-left-symbolic"
|
||||
displayHint: Kirigami.DisplayHint.IconOnly
|
||||
onTriggered: imageItem.rotationAngle = imageItem.rotationAngle - 90
|
||||
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Rotate right")
|
||||
icon.name: "image-rotate-right-symbolic"
|
||||
displayHint: Kirigami.DisplayHint.IconOnly
|
||||
onTriggered: imageItem.rotationAngle = imageItem.rotationAngle + 90
|
||||
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Close")
|
||||
icon.name: "dialog-close"
|
||||
displayHint: Kirigami.DisplayHint.IconOnly
|
||||
onTriggered: root.close()
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.alternateBackgroundColor
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
height: 1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: imageContainer
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
focus: true
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
AnimatedImage {
|
||||
id: imageItem
|
||||
|
||||
property var scaleFactor: 1
|
||||
property int rotationAngle: 0
|
||||
property var rotationInsensitiveWidth: Math.min(root.imageWidth > 0 ? root.imageWidth : sourceSize.width, imageContainer.width - Kirigami.Units.largeSpacing * 2)
|
||||
property var rotationInsensitiveHeight: Math.min(root.imageHeight > 0 ? root.imageHeight : sourceSize.height, imageContainer.height - Kirigami.Units.largeSpacing * 2)
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: rotationAngle % 180 === 0 ? rotationInsensitiveWidth : rotationInsensitiveHeight
|
||||
height: rotationAngle % 180 === 0 ? rotationInsensitiveHeight : rotationInsensitiveWidth
|
||||
fillMode: Image.PreserveAspectFit
|
||||
clip: true
|
||||
source: root.image
|
||||
|
||||
MouseArea {
|
||||
anchors.centerIn: parent
|
||||
width: parent.paintedWidth
|
||||
height: parent.paintedHeight
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
|
||||
}
|
||||
Behavior on height {
|
||||
NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
|
||||
}
|
||||
|
||||
transform: [
|
||||
Rotation {
|
||||
origin.x: imageItem.width / 2
|
||||
origin.y: imageItem.height / 2
|
||||
angle: imageItem.rotationAngle
|
||||
|
||||
Behavior on angle {
|
||||
RotationAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
|
||||
}
|
||||
},
|
||||
Scale {
|
||||
origin.x: imageItem.width / 2
|
||||
origin.y: imageItem.height / 2
|
||||
xScale: imageItem.scaleFactor
|
||||
yScale: imageItem.scaleFactor
|
||||
|
||||
Behavior on xScale {
|
||||
NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
|
||||
}
|
||||
Behavior on yScale {
|
||||
NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Controls.Control {
|
||||
Layout.fillWidth: true
|
||||
visible: root.description
|
||||
|
||||
contentItem: Controls.Label {
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
text: root.description
|
||||
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.alternateBackgroundColor
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.top
|
||||
}
|
||||
height: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
imageItem.scaleFactor = 1;
|
||||
imageItem.rotationAngle = 0;
|
||||
}
|
||||
}
|
|
@ -329,7 +329,7 @@ Kirigami.SwipeListItem {
|
|||
},
|
||||
Kirigami.Action {
|
||||
text: i18nc("Action to start playback by streaming the episode rather than downloading it first", "Stream")
|
||||
icon.name: ":/media-playback-start-cloud"
|
||||
icon.name: "qrc:/media-playback-start-cloud"
|
||||
visible: showStreamingPlayButton
|
||||
onTriggered: {
|
||||
if (!entry.queueStatus) {
|
||||
|
|
|
@ -19,7 +19,7 @@ Item {
|
|||
required property string title
|
||||
|
||||
property string subtitle: ""
|
||||
property var headerHeight: Kirigami.Units.gridUnit * 8
|
||||
property var headerHeight: Kirigami.Units.gridUnit * 5
|
||||
|
||||
property bool clickable: false
|
||||
signal clicked()
|
||||
|
@ -31,19 +31,22 @@ Item {
|
|||
id: backgroundImage
|
||||
anchors.fill: parent
|
||||
imageSource: image
|
||||
}
|
||||
GaussianBlur {
|
||||
id: blur
|
||||
anchors.fill: backgroundImage
|
||||
source: backgroundImage
|
||||
radius: 12
|
||||
samples: 16
|
||||
deviation: 6
|
||||
}
|
||||
ColorOverlay {
|
||||
anchors.fill: blur
|
||||
source: blur
|
||||
color:"#87000000" //RGBA, but first value is actually the alpha channel
|
||||
imageResize: false // no "stuttering" on resizing the window
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: HueSaturation {
|
||||
cached: true
|
||||
|
||||
lightness: -0.3
|
||||
saturation: 0.9
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: FastBlur {
|
||||
cached: true
|
||||
radius: 64
|
||||
transparentBorder: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
@ -56,7 +59,7 @@ Item {
|
|||
}
|
||||
|
||||
RowLayout {
|
||||
property int size: Kirigami.Units.gridUnit * 6
|
||||
property int size: root.headerHeight - 2 * margin
|
||||
property int margin: Kirigami.Units.gridUnit * 1
|
||||
height: size
|
||||
anchors.bottom: parent.bottom
|
||||
|
@ -84,11 +87,11 @@ Item {
|
|||
Layout.fillHeight: true
|
||||
text: title
|
||||
fontSizeMode: Text.Fit
|
||||
font.pointSize: 18
|
||||
minimumPointSize: 12
|
||||
font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 1.4)
|
||||
minimumPointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 1.2)
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignBottom
|
||||
color: "white"
|
||||
color: "#eff0f1" // breeze light text color
|
||||
opacity: 1
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.WordWrap
|
||||
|
@ -98,10 +101,11 @@ Item {
|
|||
visible: subtitle !== ""
|
||||
text: subtitle
|
||||
fontSizeMode: Text.Fit
|
||||
font.pointSize: 12
|
||||
minimumPointSize: 10
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize
|
||||
minimumPointSize: Kirigami.Theme.defaultFont.pointSize
|
||||
font.bold: true
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
color: "white"
|
||||
color: "#eff0f1" // breeze light text color
|
||||
elide: Text.ElideRight
|
||||
opacity: 1
|
||||
}
|
||||
|
|
|
@ -1,314 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2021 Swapnil Tripathi <swapnil06.st@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2021-2023 Bart De Vries <bart@mogwai.be>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.14
|
||||
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.19 as Kirigami
|
||||
import org.kde.kmediasession 1.0
|
||||
|
||||
import org.kde.kasts 1.0
|
||||
|
||||
Rectangle {
|
||||
id: headerBar
|
||||
implicitHeight: headerRowLayout.implicitHeight
|
||||
implicitWidth: headerRowLayout.implicitWidth
|
||||
|
||||
//set background color
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Header
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
|
||||
function openEntry() {
|
||||
if (AudioManager.entry) {
|
||||
pushPage("QueuePage");
|
||||
pageStack.push("qrc:/EntryPage.qml", {"entry": AudioManager.entry});
|
||||
SettingsManager.lastOpenedPage = "QueuePage";
|
||||
SettingsManager.save();
|
||||
pageStack.get(0).lastEntry = AudioManager.entry.id;
|
||||
var model = pageStack.get(0).queueList.model;
|
||||
for (var i = 0; i < model.rowCount(); i++) {
|
||||
var index = model.index(i, 0);
|
||||
if (AudioManager.entry == model.data(index, EpisodeModel.EntryRole)) {
|
||||
pageStack.get(0).queueList.currentIndex = i;
|
||||
pageStack.get(0).queueList.selectionModel.setCurrentIndex(index, ItemSelectionModel.ClearAndSelect | ItemSelectionModel.Rows);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: headerRowLayout
|
||||
anchors.fill: parent
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
ImageWithFallback {
|
||||
id: mainImage
|
||||
imageSource: AudioManager.entry ? ((chapterModel.currentChapter && chapterModel.currentChapter !== undefined) ? chapterModel.currentChapter.cachedImage : AudioManager.entry.cachedImage) : "no-image"
|
||||
height: controlsLayout.height
|
||||
width: height
|
||||
absoluteRadius: 5
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
MouseArea {
|
||||
anchors.fill: mainImage
|
||||
onClicked: {
|
||||
headerBar.openEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
ColumnLayout {
|
||||
id: controlsLayout
|
||||
Layout.fillWidth: true
|
||||
Item {
|
||||
id: titlesAndButtons
|
||||
Layout.fillWidth: true
|
||||
height: Math.max(titleLabels.implicitHeight, controlButtons.implicitHeight)
|
||||
clip: true
|
||||
ColumnLayout {
|
||||
id: titleLabels
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: controlButtons.left
|
||||
Kirigami.Heading {
|
||||
text: AudioManager.entry ? AudioManager.entry.title : i18n("No Track Title")
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
level: 3
|
||||
font.bold: true
|
||||
}
|
||||
Controls.Label {
|
||||
text: AudioManager.entry ? AudioManager.entry.feed.name : i18n("No Track Loaded")
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
opacity: 0.6
|
||||
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: titleLabels
|
||||
onClicked: {
|
||||
openEntry();
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
id: controlButtons
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Kirigami.Units.largeSpacing
|
||||
clip: true
|
||||
|
||||
property int optionalButtonCollapseWidth: Kirigami.Units.gridUnit * 12
|
||||
|
||||
Controls.ToolButton {
|
||||
id: chapterButton
|
||||
visible: AudioManager.entry && chapterList.count !== 0 && (titlesAndButtons.width > essentialButtons.width + infoButton.implicitWidth + implicitWidth + 2 * infoButton.implicitWidth + parent.optionalButtonCollapseWidth)
|
||||
text: i18n("Chapters")
|
||||
icon.name: "view-media-playlist"
|
||||
icon.height: essentialButtons.iconSize
|
||||
icon.width: essentialButtons.iconSize
|
||||
Layout.preferredHeight: essentialButtons.buttonSize
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: chapterOverlay.open();
|
||||
}
|
||||
Controls.ToolButton {
|
||||
id: infoButton
|
||||
visible: AudioManager.entry && (titlesAndButtons.width > essentialButtons.width + 2 * implicitWidth + parent.optionalButtonCollapseWidth)
|
||||
icon.name: "documentinfo"
|
||||
icon.height: essentialButtons.iconSize
|
||||
icon.width: essentialButtons.iconSize
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: essentialButtons.buttonSize
|
||||
onClicked: entryDetailsOverlay.open();
|
||||
}
|
||||
Controls.ToolButton {
|
||||
id: sleepButton
|
||||
checkable: true
|
||||
checked: AudioManager.remainingSleepTime > 0
|
||||
visible: titlesAndButtons.width > essentialButtons.width + implicitWidth + parent.optionalButtonCollapseWidth
|
||||
icon.name: "clock"
|
||||
icon.height: essentialButtons.iconSize
|
||||
icon.width: essentialButtons.iconSize
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: essentialButtons.buttonSize
|
||||
onClicked: {
|
||||
toggle(); // only set the on/off state based on sleep timer state
|
||||
sleepTimerDialog.open();
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
id: essentialButtons
|
||||
property int iconSize: Kirigami.Units.gridUnit
|
||||
property int buttonSize: playButton.implicitWidth
|
||||
Layout.margins: 0
|
||||
clip: true
|
||||
|
||||
Controls.ToolButton {
|
||||
contentItem: Controls.Label {
|
||||
text: AudioManager.playbackRate + "x"
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
onClicked: playbackRateDialog.open()
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
padding: 0
|
||||
implicitWidth: playButton.width * 1.5
|
||||
implicitHeight: playButton.height
|
||||
|
||||
Controls.ToolTip.visible: hovered
|
||||
Controls.ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
Controls.ToolTip.text: i18n("Playback Rate: ") + AudioManager.playbackRate + "x"
|
||||
}
|
||||
Controls.ToolButton {
|
||||
icon.name: "media-seek-backward"
|
||||
icon.height: parent.iconSize
|
||||
icon.width: parent.iconSize
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: parent.buttonSize
|
||||
onClicked: AudioManager.skipBackward()
|
||||
enabled: AudioManager.canSkipBackward
|
||||
|
||||
Controls.ToolTip.visible: hovered
|
||||
Controls.ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
Controls.ToolTip.text: i18n("Seek Backward")
|
||||
}
|
||||
Controls.ToolButton {
|
||||
id: playButton
|
||||
icon.name: AudioManager.playbackState === KMediaSession.PlayingState ? "media-playback-pause" : "media-playback-start"
|
||||
icon.height: parent.iconSize
|
||||
icon.width: parent.iconSize
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: parent.buttonSize
|
||||
onClicked: AudioManager.playbackState === KMediaSession.PlayingState ? AudioManager.pause() : AudioManager.play()
|
||||
enabled: AudioManager.canPlay
|
||||
|
||||
Controls.ToolTip.visible: hovered
|
||||
Controls.ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
Controls.ToolTip.text: AudioManager.playbackState === KMediaSession.PlayingState ? i18n("Pause") : i18n("Play")
|
||||
}
|
||||
Controls.ToolButton {
|
||||
icon.name: "media-seek-forward"
|
||||
icon.height: parent.iconSize
|
||||
icon.width: parent.iconSize
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: parent.buttonSize
|
||||
onClicked: AudioManager.skipForward()
|
||||
enabled: AudioManager.canSkipForward
|
||||
|
||||
Controls.ToolTip.visible: hovered
|
||||
Controls.ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
Controls.ToolTip.text: i18n("Seek Forward")
|
||||
}
|
||||
Controls.ToolButton {
|
||||
icon.name: "media-skip-forward"
|
||||
icon.height: parent.iconSize
|
||||
icon.width: parent.iconSize
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: parent.buttonSize
|
||||
onClicked: AudioManager.next()
|
||||
enabled: AudioManager.canGoNext
|
||||
|
||||
Controls.ToolTip.visible: hovered
|
||||
Controls.ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
Controls.ToolTip.text: i18n("Skip Forward")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Controls.Label {
|
||||
text: AudioManager.formattedPosition
|
||||
}
|
||||
Controls.Slider {
|
||||
id: durationSlider
|
||||
enabled: AudioManager.entry
|
||||
Layout.fillWidth: true
|
||||
padding: 0
|
||||
from: 0
|
||||
to: AudioManager.duration / 1000
|
||||
value: AudioManager.position / 1000
|
||||
onMoved: AudioManager.seek(value * 1000)
|
||||
handle.implicitWidth: implicitHeight // workaround to make slider handle position itself exactly at the location of the click
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: endLabel.implicitHeight
|
||||
Layout.preferredWidth: endLabel.implicitWidth
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
Controls.Label {
|
||||
id: endLabel
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: (SettingsManager.toggleRemainingTime) ?
|
||||
"-" + AudioManager.formattedLeftDuration
|
||||
: AudioManager.formattedDuration
|
||||
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
SettingsManager.toggleRemainingTime = !SettingsManager.toggleRemainingTime;
|
||||
SettingsManager.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Dialog {
|
||||
id: chapterOverlay
|
||||
preferredWidth: Kirigami.Units.gridUnit * 25
|
||||
preferredHeight: Kirigami.Units.gridUnit * 20
|
||||
|
||||
showCloseButton: true
|
||||
|
||||
title: i18n("Chapters")
|
||||
|
||||
ListView {
|
||||
id: chapterList
|
||||
model: ChapterModel {
|
||||
id: chapterModel
|
||||
entry: AudioManager.entry ? AudioManager.entry : null
|
||||
}
|
||||
delegate: ChapterListDelegate {
|
||||
id: chapterDelegate
|
||||
width: chapterList.width
|
||||
entry: AudioManager.entry ? AudioManager.entry : null
|
||||
overlay: chapterOverlay
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Dialog {
|
||||
id: entryDetailsOverlay
|
||||
preferredWidth: Kirigami.Units.gridUnit * 25
|
||||
|
||||
showCloseButton: true
|
||||
|
||||
title: AudioManager.entry ? AudioManager.entry.title : i18n("No Track Title")
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
|
||||
Controls.Label {
|
||||
id: text
|
||||
text: AudioManager.entry ? AudioManager.entry.content : i18n("No Track Loaded")
|
||||
verticalAlignment: Text.AlignTop
|
||||
baseUrl: AudioManager.entry ? AudioManager.entry.baseUrl : ""
|
||||
textFormat: Text.RichText
|
||||
wrapMode: Text.WordWrap
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,6 +22,8 @@ Item {
|
|||
property string imageTitle: ""
|
||||
property bool isLoading: false
|
||||
property int imageFillMode: Image.PreserveAspectCrop
|
||||
property bool imageResize: true
|
||||
property bool mipmap: false
|
||||
|
||||
Loader {
|
||||
id: imageLoader
|
||||
|
@ -49,9 +51,10 @@ Item {
|
|||
anchors.fill: parent
|
||||
source: root.imageSource
|
||||
fillMode: root.imageFillMode
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
sourceSize.width: root.imageResize ? width : undefined
|
||||
sourceSize.height: root.imageResize ? height : undefined
|
||||
asynchronous: true
|
||||
mipmap: root.mipmap
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -153,7 +153,7 @@ Flickable {
|
|||
}
|
||||
}
|
||||
|
||||
PlayerControls {
|
||||
MobilePlayerControls {
|
||||
id: mobileTrackPlayer
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
|
@ -152,6 +152,7 @@ Kirigami.Page {
|
|||
Layout.maximumWidth: parent.width
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Controls.Label {
|
||||
text: AudioManager.entry ? AudioManager.entry.feed.name : i18n("No Podcast Title")
|
||||
elide: Text.ElideRight
|
||||
|
@ -181,6 +182,7 @@ Kirigami.Page {
|
|||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
||||
}
|
||||
|
||||
Controls.Label {
|
||||
id: text
|
||||
text: AudioManager.entry ? AudioManager.entry.content : i18n("No Track Loaded")
|
||||
|
@ -209,6 +211,7 @@ Kirigami.Page {
|
|||
|
||||
text: i18n("No chapters found.")
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: chapterList
|
||||
model: ChapterModel {
|
||||
|
@ -269,6 +272,7 @@ Kirigami.Page {
|
|||
swipeView.currentIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Controls.ToolButton {
|
||||
visible: AudioManager.entry
|
||||
Layout.maximumHeight: parent.height
|
||||
|
@ -282,6 +286,7 @@ Kirigami.Page {
|
|||
swipeView.currentIndex = 1;
|
||||
}
|
||||
}
|
||||
|
||||
Controls.ToolButton {
|
||||
visible: AudioManager.entry && chapterList.count !== 0
|
||||
Layout.maximumHeight: parent.height
|
||||
|
@ -295,9 +300,11 @@ Kirigami.Page {
|
|||
swipeView.currentIndex = 2;
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Controls.ToolButton {
|
||||
checkable: true
|
||||
checked: AudioManager.remainingSleepTime > 0
|
||||
|
@ -313,6 +320,67 @@ Kirigami.Page {
|
|||
sleepTimerDialog.open()
|
||||
}
|
||||
}
|
||||
Controls.ToolButton {
|
||||
id: volumeButton
|
||||
icon.name: AudioManager.muted ? "player-volume-muted" : "player-volume"
|
||||
enabled: AudioManager.PlaybackState != AudioManager.StoppedState && AudioManager.canPlay
|
||||
checked: volumePopup.visible
|
||||
Controls.ToolTip {
|
||||
visible: parent.hovered
|
||||
delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
text: i18nc("@action:button", "Open Volume Settings")
|
||||
}
|
||||
onClicked: {
|
||||
if (volumePopup.visible) {
|
||||
volumePopup.close();
|
||||
} else {
|
||||
volumePopup.open();
|
||||
}
|
||||
}
|
||||
|
||||
Controls.Popup {
|
||||
id: volumePopup
|
||||
x: -volumePopup.width + volumeButton.width
|
||||
y: -volumePopup.height
|
||||
|
||||
focus: true
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
contentHeight: muteButton.implicitHeight
|
||||
|
||||
contentItem: RowLayout {
|
||||
id: popupContent
|
||||
|
||||
Controls.ToolButton {
|
||||
id: muteButton
|
||||
enabled: AudioManager.PlaybackState != AudioManager.StoppedState && AudioManager.canPlay
|
||||
icon.name: AudioManager.muted ? "player-volume-muted" : "player-volume"
|
||||
onClicked: AudioManager.muted = !AudioManager.muted
|
||||
Controls.ToolTip {
|
||||
visible: parent.hovered
|
||||
delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
text: i18nc("@action:button", "Toggle Mute")
|
||||
}
|
||||
}
|
||||
|
||||
Controls.Slider {
|
||||
id: volumeSlider
|
||||
width: Kirigami.Units.gridUnit * 7
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: width
|
||||
Layout.maximumWidth: width
|
||||
Layout.rightMargin: Kirigami.Units.smallSpacing
|
||||
padding: 0
|
||||
enabled: !AudioManager.muted && AudioManager.PlaybackState != AudioManager.StoppedState && AudioManager.canPlay
|
||||
from: 0
|
||||
to: 100
|
||||
value: AudioManager.volume
|
||||
onMoved: AudioManager.volume = value
|
||||
handle.implicitWidth: implicitHeight // workaround to make slider handle position itself exactly at the location of the click
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
|
@ -74,6 +74,7 @@ Kirigami.ApplicationWindow {
|
|||
property var mobileHeight
|
||||
property var desktopWidth
|
||||
property var desktopHeight
|
||||
property int headerSize: Kirigami.Units.gridUnit * 5
|
||||
}
|
||||
|
||||
function saveWindowLayout() {
|
||||
|
@ -128,7 +129,7 @@ Kirigami.ApplicationWindow {
|
|||
id: drawer
|
||||
modal: false
|
||||
|
||||
readonly property real listViewThreshold: Kirigami.Units.gridUnit * 22
|
||||
readonly property real listViewThreshold: Kirigami.Settings.isMobile ? Kirigami.Units.gridUnit * 22 : Kirigami.Units.gridUnit * 20
|
||||
|
||||
readonly property real pinnedWidth: Kirigami.Units.gridUnit * 3
|
||||
readonly property real widescreenSmallWidth: Kirigami.Units.gridUnit * 6
|
||||
|
@ -148,7 +149,12 @@ Kirigami.ApplicationWindow {
|
|||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
Kirigami.AbstractApplicationHeader { Layout.fillWidth: true }
|
||||
Loader {
|
||||
active: Kirigami.Settings.isMobile
|
||||
visible: active
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: Kirigami.AbstractApplicationHeader { }
|
||||
}
|
||||
|
||||
Controls.ScrollView {
|
||||
id: scrollView
|
||||
|
@ -472,6 +478,11 @@ Kirigami.ApplicationWindow {
|
|||
id: syncPasswordOverlay
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fullScreenImage
|
||||
FullScreenImage { }
|
||||
}
|
||||
|
||||
//Global Shortcuts
|
||||
Shortcut {
|
||||
sequence: "space"
|
||||
|
|
|
@ -10,9 +10,10 @@
|
|||
<file alias="FeedDetailsPage.qml">qml/FeedDetailsPage.qml</file>
|
||||
<file alias="AddFeedSheet.qml">qml/AddFeedSheet.qml</file>
|
||||
<file alias="FeedListDelegate.qml">qml/FeedListDelegate.qml</file>
|
||||
<file alias="MinimizedPlayerControls.qml">qml/MinimizedPlayerControls.qml</file>
|
||||
<file alias="PlayerControls.qml">qml/PlayerControls.qml</file>
|
||||
<file alias="FooterBar.qml">qml/FooterBar.qml</file>
|
||||
<file alias="MinimizedPlayerControls.qml">qml/Mobile/MinimizedPlayerControls.qml</file>
|
||||
<file alias="MobilePlayerControls.qml">qml/Mobile/MobilePlayerControls.qml</file>
|
||||
<file alias="FooterBar.qml">qml/Mobile/FooterBar.qml</file>
|
||||
<file alias="BottomToolbar.qml">qml/Mobile/BottomToolbar.qml</file>
|
||||
<file alias="QueuePage.qml">qml/QueuePage.qml</file>
|
||||
<file alias="EpisodeListPage.qml">qml/EpisodeListPage.qml</file>
|
||||
<file alias="DownloadListPage.qml">qml/DownloadListPage.qml</file>
|
||||
|
@ -24,7 +25,8 @@
|
|||
<file alias="DiscoverPage.qml">qml/DiscoverPage.qml</file>
|
||||
<file alias="ImageWithFallback.qml">qml/ImageWithFallback.qml</file>
|
||||
<file alias="UpdateNotification.qml">qml/UpdateNotification.qml</file>
|
||||
<file alias="HeaderBar.qml">qml/HeaderBar.qml</file>
|
||||
<file alias="HeaderBar.qml">qml/Desktop/HeaderBar.qml</file>
|
||||
<file alias="DesktopPlayerControls.qml">qml/Desktop/DesktopPlayerControls.qml</file>
|
||||
<file alias="PlaybackRateDialog.qml">qml/PlaybackRateDialog.qml</file>
|
||||
<file alias="ErrorNotification.qml">qml/ErrorNotification.qml</file>
|
||||
<file alias="ConnectionCheckAction.qml">qml/ConnectionCheckAction.qml</file>
|
||||
|
@ -36,8 +38,8 @@
|
|||
<file alias="StorageSettingsPage.qml">qml/Settings/StorageSettingsPage.qml</file>
|
||||
<file alias="SynchronizationSettingsPage.qml">qml/Settings/SynchronizationSettingsPage.qml</file>
|
||||
<file alias="ErrorListPage.qml">qml/Settings/ErrorListPage.qml</file>
|
||||
<file alias="BottomToolbar.qml">qml/BottomToolbar.qml</file>
|
||||
<file alias="SleepTimerDialog.qml">qml/SleepTimerDialog.qml</file>
|
||||
<file alias="FullScreenImage.qml">qml/FullScreenImage.qml</file>
|
||||
<file>qtquickcontrols2.conf</file>
|
||||
<file alias="logo.svg">../kasts.svg</file>
|
||||
<file alias="media-playback-start-cloud">../icons/media-playback-start-cloud.svg</file>
|
||||
|
|
Loading…
Reference in New Issue