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:
Bart De Vries 2023-01-17 23:25:04 +01:00
parent 03f8c3a544
commit d869358ff5
18 changed files with 1010 additions and 350 deletions

View File

@ -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)

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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
}
}

View File

@ -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})
}
}
}

View File

@ -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;

View File

@ -155,6 +155,7 @@ Kirigami.ScrollablePage {
iconName: "documentinfo"
text: i18n("Show Details")
checkable: true
checked: showMoreInfo
onCheckedChanged: {
showMoreInfo = checked;
}

205
src/qml/FullScreenImage.qml Normal file
View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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
}
}

View File

@ -153,7 +153,7 @@ Flickable {
}
}
PlayerControls {
MobilePlayerControls {
id: mobileTrackPlayer
Layout.fillWidth: true
Layout.fillHeight: true

View File

@ -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 {

View File

@ -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"

View File

@ -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>