diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 12e1e2bf..ac77cbc2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/qml/ChapterListDelegate.qml b/src/qml/ChapterListDelegate.qml index 32876e51..d662c27e 100644 --- a/src/qml/ChapterListDelegate.qml +++ b/src/qml/ChapterListDelegate.qml @@ -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 diff --git a/src/qml/Desktop/DesktopPlayerControls.qml b/src/qml/Desktop/DesktopPlayerControls.qml new file mode 100644 index 00000000..769a4734 --- /dev/null +++ b/src/qml/Desktop/DesktopPlayerControls.qml @@ -0,0 +1,438 @@ +/** + * SPDX-FileCopyrightText: 2023 Bart De Vries + * + * 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) + } + } +} diff --git a/src/qml/Desktop/HeaderBar.qml b/src/qml/Desktop/HeaderBar.qml new file mode 100644 index 00000000..ab8d822f --- /dev/null +++ b/src/qml/Desktop/HeaderBar.qml @@ -0,0 +1,241 @@ +/** + * SPDX-FileCopyrightText: 2021 Swapnil Tripathi + * SPDX-FileCopyrightText: 2021-2023 Bart De Vries + * + * 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 + } +} diff --git a/src/qml/DiscoverPage.qml b/src/qml/DiscoverPage.qml index b276f379..c0fecd2b 100644 --- a/src/qml/DiscoverPage.qml +++ b/src/qml/DiscoverPage.qml @@ -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}) } } } diff --git a/src/qml/EntryPage.qml b/src/qml/EntryPage.qml index cdc8a110..8ea48f9b 100644 --- a/src/qml/EntryPage.qml +++ b/src/qml/EntryPage.qml @@ -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; diff --git a/src/qml/FeedDetailsPage.qml b/src/qml/FeedDetailsPage.qml index 03c69b5a..e9980ae9 100644 --- a/src/qml/FeedDetailsPage.qml +++ b/src/qml/FeedDetailsPage.qml @@ -155,6 +155,7 @@ Kirigami.ScrollablePage { iconName: "documentinfo" text: i18n("Show Details") checkable: true + checked: showMoreInfo onCheckedChanged: { showMoreInfo = checked; } diff --git a/src/qml/FullScreenImage.qml b/src/qml/FullScreenImage.qml new file mode 100644 index 00000000..8bff1412 --- /dev/null +++ b/src/qml/FullScreenImage.qml @@ -0,0 +1,205 @@ +/** + * SPDX-FileCopyrightText: 2019 Black Hat + * SPDX-FileCopyrightText: 2023 Bart De Vries + * + * 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; + } +} diff --git a/src/qml/GenericEntryDelegate.qml b/src/qml/GenericEntryDelegate.qml index f4e126af..0b14eea0 100644 --- a/src/qml/GenericEntryDelegate.qml +++ b/src/qml/GenericEntryDelegate.qml @@ -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) { diff --git a/src/qml/GenericHeader.qml b/src/qml/GenericHeader.qml index dc956118..415c8988 100644 --- a/src/qml/GenericHeader.qml +++ b/src/qml/GenericHeader.qml @@ -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 } diff --git a/src/qml/HeaderBar.qml b/src/qml/HeaderBar.qml deleted file mode 100644 index 672d0615..00000000 --- a/src/qml/HeaderBar.qml +++ /dev/null @@ -1,314 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 Swapnil Tripathi - * SPDX-FileCopyrightText: 2021-2023 Bart De Vries - * - * 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) - } - } -} diff --git a/src/qml/ImageWithFallback.qml b/src/qml/ImageWithFallback.qml index 01333f8d..2e875788 100644 --- a/src/qml/ImageWithFallback.qml +++ b/src/qml/ImageWithFallback.qml @@ -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 } } diff --git a/src/qml/BottomToolbar.qml b/src/qml/Mobile/BottomToolbar.qml similarity index 100% rename from src/qml/BottomToolbar.qml rename to src/qml/Mobile/BottomToolbar.qml diff --git a/src/qml/FooterBar.qml b/src/qml/Mobile/FooterBar.qml similarity index 99% rename from src/qml/FooterBar.qml rename to src/qml/Mobile/FooterBar.qml index 80ec99af..d0c9d213 100644 --- a/src/qml/FooterBar.qml +++ b/src/qml/Mobile/FooterBar.qml @@ -153,7 +153,7 @@ Flickable { } } - PlayerControls { + MobilePlayerControls { id: mobileTrackPlayer Layout.fillWidth: true Layout.fillHeight: true diff --git a/src/qml/MinimizedPlayerControls.qml b/src/qml/Mobile/MinimizedPlayerControls.qml similarity index 100% rename from src/qml/MinimizedPlayerControls.qml rename to src/qml/Mobile/MinimizedPlayerControls.qml diff --git a/src/qml/PlayerControls.qml b/src/qml/Mobile/MobilePlayerControls.qml similarity index 85% rename from src/qml/PlayerControls.qml rename to src/qml/Mobile/MobilePlayerControls.qml index 115e32b8..fcc78794 100644 --- a/src/qml/PlayerControls.qml +++ b/src/qml/Mobile/MobilePlayerControls.qml @@ -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 { diff --git a/src/qml/main.qml b/src/qml/main.qml index edb27b0e..53a3c915 100644 --- a/src/qml/main.qml +++ b/src/qml/main.qml @@ -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" diff --git a/src/resources.qrc b/src/resources.qrc index fac2a444..92bc3fb6 100755 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -10,9 +10,10 @@ qml/FeedDetailsPage.qml qml/AddFeedSheet.qml qml/FeedListDelegate.qml - qml/MinimizedPlayerControls.qml - qml/PlayerControls.qml - qml/FooterBar.qml + qml/Mobile/MinimizedPlayerControls.qml + qml/Mobile/MobilePlayerControls.qml + qml/Mobile/FooterBar.qml + qml/Mobile/BottomToolbar.qml qml/QueuePage.qml qml/EpisodeListPage.qml qml/DownloadListPage.qml @@ -24,7 +25,8 @@ qml/DiscoverPage.qml qml/ImageWithFallback.qml qml/UpdateNotification.qml - qml/HeaderBar.qml + qml/Desktop/HeaderBar.qml + qml/Desktop/DesktopPlayerControls.qml qml/PlaybackRateDialog.qml qml/ErrorNotification.qml qml/ConnectionCheckAction.qml @@ -36,8 +38,8 @@ qml/Settings/StorageSettingsPage.qml qml/Settings/SynchronizationSettingsPage.qml qml/Settings/ErrorListPage.qml - qml/BottomToolbar.qml qml/SleepTimerDialog.qml + qml/FullScreenImage.qml qtquickcontrols2.conf ../kasts.svg ../icons/media-playback-start-cloud.svg