Add mobile bottom navbar, use Titles toolbar on mobile, and add blurred image background to mobile player

This commit is contained in:
Devin Lin 2021-10-30 17:10:19 +00:00
parent f861f4e802
commit e16c40d57c
8 changed files with 461 additions and 242 deletions

62
src/qml/BottomToolbar.qml Normal file
View File

@ -0,0 +1,62 @@
/*
* Copyright 2021 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.12
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.2
import org.kde.kirigami 2.19 as Kirigami
import org.kde.kasts 1.0
Kirigami.NavigationTabBar {
id: root
property alias toolbarHeight: root.implicitHeight
property bool transparentBackground: false
shadow: false
actions: [
Kirigami.Action {
iconName: "view-media-playlist"
text: i18n("Queue")
checked: "QueuePage" === SettingsManager.lastOpenedPage
onTriggered: {
pushPage("QueuePage");
SettingsManager.lastOpenedPage = "QueuePage"; // for persistency
}
},
Kirigami.Action {
iconName: "bookmarks"
text: i18n("Subscriptions")
checked: "FeedListPage" === SettingsManager.lastOpenedPage
onTriggered: {
pushPage("FeedListPage");
SettingsManager.lastOpenedPage = "FeedListPage"; // for persistency
}
},
Kirigami.Action {
iconName: "rss"
text: i18n("Episodes")
checked: "EpisodeListPage" === SettingsManager.lastOpenedPage
onTriggered: {
pushPage("EpisodeListPage")
SettingsManager.lastOpenedPage = "EpisodeListPage" // for persistency
}
},
Kirigami.Action {
iconName: "settings-configure"
text: i18n("Settings")
checked: "SettingsPage" === SettingsManager.lastOpenedPage
onTriggered: {
applicationWindow().pageStack.clear()
applicationWindow().pageStack.push("qrc:/SettingsPage.qml", {}, {
title: i18n("Settings")
})
}
}
]
}

View File

@ -26,12 +26,21 @@ Kirigami.ScrollablePage {
}
actions.main: Kirigami.Action {
iconName: "download"
text: i18n("Downloads")
onTriggered: {
pushPage("DownloadListPage")
SettingsManager.lastOpenedPage = "DownloadListPage" // for persistency
}
}
actions.left: Kirigami.Action {
iconName: "view-filter"
text: i18n("Filter")
onTriggered: filterTypeOverlay.open();
}
actions.left: Kirigami.Action {
actions.right: Kirigami.Action {
iconName: "view-refresh"
text: i18n("Refresh All Podcasts")
onTriggered: refreshing = true

View File

@ -33,14 +33,22 @@ Kirigami.ScrollablePage {
}
actions.main: Kirigami.Action {
visible: Kirigami.Settings.isMobile
text: i18n("Discover")
iconName: "search"
onTriggered: {
applicationWindow().pageStack.push("qrc:/DiscoverPage.qml");
}
}
contextualActions: [
Kirigami.Action {
text: i18n("Add Podcast")
iconName: "list-add"
onTriggered: {
addSheet.open()
}
}
contextualActions: [
},
Kirigami.Action {
text: i18n("Refresh All Podcasts")
iconName: "view-refresh"

View File

@ -8,6 +8,7 @@
import QtQuick 2.14
import QtQuick.Controls 2.14 as Controls
import QtQuick.Layouts 1.14
import QtGraphicalEffects 1.0
import org.kde.kirigami 2.14 as Kirigami
@ -20,6 +21,8 @@ Flickable {
property bool isMaximized: contentY === contentHeight / 2
property int contentToPlayerSpacing: 0
boundsBehavior: Flickable.StopAtBounds
NumberAnimation on contentY {
@ -51,6 +54,10 @@ Flickable {
onReleased: footerBar.resetToBoundsOnFlick()
}
function close() {
toClose.restart();
}
function resetToBoundsOnFlick() {
if (!atYBeginning || !atYEnd) {
if (footerBar.verticalVelocity > 0) {
@ -90,8 +97,11 @@ Flickable {
// a cover for content underneath the panel
Rectangle {
id: coverUnderneath
color: Kirigami.Theme.backgroundColor
anchors.fill: parent
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
color: Kirigami.Theme.backgroundColor
}
}
@ -101,23 +111,52 @@ Flickable {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: root.height + root.miniplayerSize
height: root.height + root.miniplayerSize + contentToPlayerSpacing
spacing: 0
Controls.Control {
implicitHeight: root.miniplayerSize + contentToPlayerSpacing
Layout.fillWidth: true
padding: 0
background: Image {
opacity: 0.2
source: AudioManager.entry.cachedImage
asynchronous: true
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
layer.enabled: true
layer.effect: HueSaturation {
cached: true
lightness: 0.7
saturation: 0.9
layer.enabled: true
layer.effect: FastBlur {
cached: true
radius: 64
transparentBorder: false
}
}
}
MinimizedPlayerControls {
id: playControlItem
Layout.fillWidth: true
Layout.minimumHeight: root.miniplayerSize
Layout.alignment: Qt.AlignTop
height: root.miniplayerSize
focus: true
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
}
}
PlayerControls {
id: mobileTrackPlayer
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: Kirigami.Units.largeSpacing * 2
}
}
}

View File

@ -1,5 +1,6 @@
/**
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
@ -8,25 +9,20 @@ import QtQuick 2.14
import QtQuick.Controls 2.14 as Controls
import QtQuick.Layouts 1.14
import QtMultimedia 5.15
import QtGraphicalEffects 1.0
import org.kde.kirigami 2.12 as Kirigami
import org.kde.kasts 1.0
Item {
property int miniplayerheight: Kirigami.Units.gridUnit * 3
property int miniplayerheight: Math.round(Kirigami.Units.gridUnit * 3)
property int progressbarheight: Kirigami.Units.gridUnit / 6
property int buttonsize: Kirigami.Units.gridUnit * 2
height: miniplayerheight + progressbarheight
property int buttonsize: Kirigami.Units.gridUnit * 1.5
height: miniplayerheight
visible: AudioManager.entry
// Set background
Rectangle {
anchors.fill: parent
color: Kirigami.Theme.backgroundColor
}
// progress bar for limited width (phones)
Rectangle {
id: miniprogressbar
@ -41,44 +37,50 @@ Item {
RowLayout {
id: footerrowlayout
anchors.topMargin: miniprogressbar.height
anchors.fill: parent
Item {
spacing: 0
Rectangle {
Layout.fillHeight: true
Layout.fillWidth: true
// press feedback
color: (trackClick.pressed || trackClick.containsMouse) ? Qt.rgba(0, 0, 0, 0.05) : "transparent"
RowLayout {
anchors.fill: parent
ImageWithFallback {
imageSource: AudioManager.entry.cachedImage
Layout.preferredHeight: miniplayerheight
Layout.preferredWidth: miniplayerheight
Layout.fillHeight: true
Layout.preferredWidth: height
}
// track information
ColumnLayout {
Layout.fillHeight: true
Layout.maximumHeight: parent.height
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
Controls.Label {
id: mainLabel
text: AudioManager.entry.title
wrapMode: Text.Wrap
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
elide: Text.ElideRight
maximumLineCount: 1
//font.weight: Font.Bold
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1
font.weight: Font.Medium
}
Controls.Label {
id: feedLabel
text: AudioManager.entry.feed.name
wrapMode: Text.Wrap
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
elide: Text.ElideRight
@ -101,9 +103,10 @@ Item {
icon.height: parent.parent.buttonsize
icon.width: parent.parent.buttonsize
flat: true
Layout.fillHeight: true
Layout.maximumHeight: parent.parent.miniplayerheight
Layout.maximumWidth: height
Layout.preferredHeight: parent.parent.miniplayerheight - Kirigami.Units.smallSpacing * 2
Layout.preferredWidth: height
Layout.leftMargin: Kirigami.Units.smallSpacing
Layout.rightMargin: Kirigami.Units.smallSpacing
onClicked: AudioManager.playbackState === Audio.PlayingState ? AudioManager.pause() : AudioManager.play()
Layout.alignment: Qt.AlignVCenter
}

View File

@ -1,5 +1,6 @@
/**
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
@ -21,16 +22,47 @@ Kirigami.Page {
clip: true
Layout.margins: 0
padding: 0
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: Kirigami.Units.gridUnit
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
background: Image {
opacity: 0.2
source: AudioManager.entry.cachedImage
asynchronous: true
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
layer.enabled: true
layer.effect: HueSaturation {
cached: true
lightness: 0.7
saturation: 0.9
layer.enabled: true
layer.effect: FastBlur {
cached: true
radius: 100
transparentBorder: false
}
}
}
ColumnLayout {
id: playerControlsColumn
anchors.fill: parent
anchors.topMargin:0
anchors.topMargin: Kirigami.Units.largeSpacing * 2
anchors.bottomMargin: Kirigami.Units.largeSpacing * 2
Controls.Button {
id: swipeUpButton
property int swipeUpButtonSize: Kirigami.Units.gridUnit * 2
property int swipeUpButtonSize: Kirigami.Units.iconSizes.smallMedium
icon.name: "arrow-down"
icon.height: swipeUpButtonSize
icon.width: swipeUpButtonSize
@ -39,15 +71,21 @@ Kirigami.Page {
Layout.topMargin: 0
onClicked: toClose.restart()
}
Controls.SwipeView {
id: swipeView
currentIndex: 0
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.preferredWidth: parent.width
Layout.preferredHeight: parent.height - media.height - indicator.height - swipeUpButton.height
Layout.preferredHeight: parent.height - media.height - indicator.height - indicator.Layout.bottomMargin - swipeUpButton.height
Layout.margins: 0
Item {
Controls.Control {
leftPadding: Kirigami.Units.largeSpacing * 2
rightPadding: Kirigami.Units.largeSpacing * 2
contentItem: Item {
property int textMargin: Kirigami.Units.gridUnit // margin above and below the text below the image
ImageWithFallback {
id: coverImage
@ -72,6 +110,7 @@ Kirigami.Page {
elide: Text.ElideRight
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: parent.width
font.weight: Font.Medium
}
Controls.Label {
text: AudioManager.entry ? AudioManager.entry.feed.name : i18n("No Podcast Title")
@ -82,9 +121,16 @@ Kirigami.Page {
}
}
}
Item {
}
Controls.Control {
leftPadding: Kirigami.Units.largeSpacing * 2
rightPadding: Kirigami.Units.largeSpacing * 2
contentItem: Item {
Flickable {
anchors.fill: parent
anchors.leftMargin: playerControlsColumn.anchors.margins
clip: true
contentHeight: description.height
ColumnLayout {
@ -110,14 +156,20 @@ Kirigami.Page {
}
}
}
Item {
}
Controls.Control {
leftPadding: Kirigami.Units.largeSpacing * 2
rightPadding: Kirigami.Units.largeSpacing * 2
contentItem: Item {
Kirigami.PlaceholderMessage {
visible: chapterList.count === 0
width: parent.width
anchors.centerIn: parent
text: i18n("No chapter marks found.")
text: i18n("No chapters found.")
}
ListView {
id: chapterList
@ -134,6 +186,7 @@ Kirigami.Page {
}
}
}
}
Controls.PageIndicator {
id: indicator
@ -141,19 +194,20 @@ Kirigami.Page {
count: swipeView.count
currentIndex: swipeView.currentIndex
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: Kirigami.Units.gridUnit
}
Item {
id: media
implicitHeight: mediaControls.height
Layout.leftMargin: Kirigami.Units.largeSpacing * 2
Layout.rightMargin: Kirigami.Units.largeSpacing * 2
Layout.fillWidth: true
Layout.margins: 0
ColumnLayout {
id: mediaControls
//implicitHeight: controls.height
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 0
@ -161,7 +215,8 @@ Kirigami.Page {
Controls.Slider {
enabled: AudioManager.entry
Layout.fillWidth: true
Layout.margins: 0
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
padding: 0
from: 0
to: AudioManager.duration
@ -170,16 +225,20 @@ Kirigami.Page {
}
RowLayout {
id: controls
Layout.margins: 0
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
Controls.Label {
Layout.alignment: Qt.AlignLeft
padding: Kirigami.Units.largeSpacing
text: AudioManager.formattedPosition
font: Kirigami.Theme.smallFont
}
Item {
Layout.fillWidth: true
}
Item {
Layout.alignment: Qt.AlignRight
Layout.preferredHeight: endLabel.implicitHeight
Layout.preferredWidth: endLabel.implicitWidth
Controls.Label {
@ -190,6 +249,7 @@ Kirigami.Page {
text: (SettingsManager.toggleRemainingTime) ?
"-" + AudioManager.formattedLeftDuration
: AudioManager.formattedDuration
font: Kirigami.Theme.smallFont
}
MouseArea {
@ -199,7 +259,11 @@ Kirigami.Page {
}
}
}
RowLayout {
id: bottomRow
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.maximumWidth: Number.POSITIVE_INFINITY //TODO ?
Layout.fillWidth: true
Layout.margins: 0
@ -207,8 +271,9 @@ Kirigami.Page {
// Make button width scale properly on narrow windows instead of overflowing
property int buttonSize: Math.min(playButton.implicitWidth, ((playerControlsColumn.width - 4 * spacing) / 5 - playButton.leftPadding - playButton.rightPadding))
property int iconSize: Kirigami.Units.gridUnit * 2
property int iconSize: Kirigami.Units.gridUnit * 1.5
// left section
Controls.Button {
// Use contentItem and a Label because using plain "text"
// does not rescale automatically if the text changes
@ -221,49 +286,56 @@ Kirigami.Page {
playbackRateDialog.open()
}
flat: true
Layout.alignment: Qt.AlignHCenter
padding: 0
implicitWidth: playButton.width
implicitHeight: playButton.height
}
// middle section
RowLayout {
spacing: Kirigami.Units.largeSpacing
Layout.alignment: Qt.AlignHCenter
Controls.Button {
icon.name: "media-seek-backward"
icon.height: parent.iconSize
icon.width: parent.iconSize
icon.height: bottomRow.iconSize
icon.width: bottomRow.iconSize
flat: true
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: parent.buttonSize
Layout.alignment: Qt.AlignRight
Layout.preferredWidth: bottomRow.buttonSize
onClicked: AudioManager.skipBackward()
enabled: AudioManager.canSkipBackward
}
Controls.Button {
id: playButton
icon.name: AudioManager.playbackState === Audio.PlayingState ? "media-playback-pause" : "media-playback-start"
icon.height: parent.iconSize
icon.width: parent.iconSize
icon.height: bottomRow.iconSize
icon.width: bottomRow.iconSize
flat: true
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: parent.buttonSize
Layout.preferredWidth: bottomRow.buttonSize
onClicked: AudioManager.playbackState === Audio.PlayingState ? AudioManager.pause() : AudioManager.play()
enabled: AudioManager.canPlay
}
Controls.Button {
icon.name: "media-seek-forward"
icon.height: parent.iconSize
icon.width: parent.iconSize
icon.height: bottomRow.iconSize
icon.width: bottomRow.iconSize
flat: true
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: parent.buttonSize
Layout.alignment: Qt.AlignLeft
Layout.preferredWidth: bottomRow.buttonSize
onClicked: AudioManager.skipForward()
enabled: AudioManager.canSkipForward
}
}
// right section
Controls.Button {
icon.name: "media-skip-forward"
icon.height: parent.iconSize
icon.width: parent.iconSize
icon.height: bottomRow.iconSize
icon.width: bottomRow.iconSize
flat: true
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: parent.buttonSize
Layout.alignment: Qt.AlignRight
Layout.preferredWidth: bottomRow.buttonSize
onClicked: AudioManager.next()
enabled: AudioManager.canGoNext
}

View File

@ -8,6 +8,7 @@
import QtQuick 2.14
import QtQuick.Controls 2.14 as Controls
import QtQuick.Layouts 1.14
import QtGraphicalEffects 1.12
import org.kde.kirigami 2.14 as Kirigami
import org.kde.kasts.solidextras 1.0
@ -21,18 +22,33 @@ Kirigami.ApplicationWindow {
minimumWidth: Kirigami.Units.gridUnit * 17
minimumHeight: Kirigami.Units.gridUnit * 20
property var miniplayerSize: Kirigami.Units.gridUnit * 3 + Kirigami.Units.gridUnit / 6
property int bottomMessageSpacing: Kirigami.Settings.isMobile ? Kirigami.Units.largeSpacing * 9 + ( AudioManager.entry ? ( footerLoader.item.contentY == 0 ? miniplayerSize : 0 ) : 0 ) : Kirigami.Units.largeSpacing * 2
property var miniplayerSize: Math.round(Kirigami.Units.gridUnit * 3) + Kirigami.Units.gridUnit / 6
property int bottomMessageSpacing: {
if (Kirigami.Settings.isMobile) {
return Kirigami.Units.largeSpacing + ( AudioManager.entry ? ( footerLoader.item.contentY == 0 ? miniplayerSize : 0 ) : 0 ) + (root.footer.height);
} else {
return Kirigami.Units.largeSpacing * 2;
}
}
property int originalWidth: Kirigami.Units.gridUnit * 10
property var lastFeed: ""
property string currentPage: ""
property bool isWidescreen: root.width >= root.height
onIsWidescreenChanged: {
if (!Kirigami.Settings.isMobile) {
changeNavigation(!isWidescreen);
}
function changeNavigation(isNarrow) {
if (isNarrow) {
globalDrawer.collapsed = true
globalDrawer.width = Layout.implicitWidth
} else {
globalDrawer.collapsed = false
globalDrawer.width = originalWidth
}
}
function getPage(page) {
switch (page) {
case "QueuePage": return "qrc:/QueuePage.qml";
@ -55,8 +71,10 @@ Kirigami.ApplicationWindow {
currentPage = SettingsManager.lastOpenedPage
pageStack.initialPage = getPage(SettingsManager.lastOpenedPage)
// move mobile handles to toolbar
pageStack.globalToolBar.canContainHandles = true;
if (Kirigami.Settings.isMobile) {
pageStack.globalToolBar.style = Kirigami.ApplicationHeaderStyle.ToolBar;
pageStack.globalToolBar.showNavigationButtons = Kirigami.ApplicationHeaderStyle.ShowBackButton;
}
// Delete played enclosures if set in settings
if (SettingsManager.autoDeleteOnPlayed == 2) {
@ -73,25 +91,20 @@ Kirigami.ApplicationWindow {
}
}
globalDrawer: Kirigami.GlobalDrawer {
globalDrawer: sidebar.item
Loader {
id: sidebar
active: !Kirigami.Settings.isMobile || root.isWidescreen
sourceComponent: Kirigami.GlobalDrawer {
width: 200
modal: false
isMenu: false
modal: Kirigami.Settings.isMobile
collapsible: !Kirigami.Settings.isMobile
header: Kirigami.AbstractApplicationHeader {
visible: !Kirigami.Settings.isMobile
}
header: Kirigami.AbstractApplicationHeader {}
Component.onCompleted: {
if (!Kirigami.Settings.isMobile) {
Kirigami.Theme.colorSet = Kirigami.Theme.Window;
Kirigami.Theme.inherit = false;
}
}
Kirigami.Theme.colorSet: Kirigami.Theme.Window
Kirigami.Theme.inherit: false
// make room at the bottom for miniplayer
handle.anchors.bottomMargin: (( AudioManager.entry && Kirigami.Settings.isMobile ) ? (footerLoader.item.contentY == 0 ? miniplayerSize : 0) : 0) + Kirigami.Units.smallSpacing
handleVisible: Kirigami.Settings.isMobile ? !AudioManager.entry || footerLoader.item.contentY === 0 : false
showHeaderWhenCollapsed: true
actions: [
Kirigami.Action {
text: i18n("Queue")
@ -151,16 +164,6 @@ Kirigami.ApplicationWindow {
}
]
}
function changeNavigation(isNarrow) {
if(isNarrow) {
globalDrawer.collapsed = true
globalDrawer.width = Layout.implicitWidth
}
else {
globalDrawer.collapsed = false
globalDrawer.width = originalWidth
}
}
contextDrawer: Kirigami.ContextDrawer {
@ -190,9 +193,7 @@ Kirigami.ApplicationWindow {
active: !Kirigami.Settings.isMobile
visible: active
sourceComponent: HeaderBar {
focus: true
}
sourceComponent: HeaderBar { focus: true }
}
// create space at the bottom to show miniplayer without it hiding stuff
@ -209,8 +210,32 @@ Kirigami.ApplicationWindow {
sourceComponent: FooterBar {
contentHeight: root.height * 2
focus: true
contentToPlayerSpacing: footer.active ? footer.item.height + 1 : 0
}
}
Loader {
id: footerShadowLoader
active: footer.active && !footerLoader.active
anchors.fill: footer
sourceComponent: RectangularGlow {
glowRadius: 5
spread: 0.3
color: Qt.rgba(0.0, 0.0, 0.0, 0.1)
}
}
footer: Loader {
visible: active
active: Kirigami.Settings.isMobile && !root.isWidescreen
sourceComponent: BottomToolbar {
transparentBackground: footerLoader.active
opacity: (!footerLoader.item || footerLoader.item.contentY === 0) ? 1 : 0
Behavior on opacity {
NumberAnimation { duration: Kirigami.Units.shortDuration }
}
}
}
// Notification that shows the progress of feed updates

View File

@ -34,6 +34,7 @@
<file alias="NetworkSettingsPage.qml">qml/Settings/NetworkSettingsPage.qml</file>
<file alias="StorageSettingsPage.qml">qml/Settings/StorageSettingsPage.qml</file>
<file alias="SynchronizationSettingsPage.qml">qml/Settings/SynchronizationSettingsPage.qml</file>
<file alias="BottomToolbar.qml">qml/BottomToolbar.qml</file>
<file>qtquickcontrols2.conf</file>
<file alias="logo.svg">../kasts.svg</file>
</qresource>