Commit 76972cec authored by Tranter Madi's avatar Tranter Madi 🌧 Committed by Nate Graham
Browse files

Dynamically switch between album-style and standalone-song-style playlist items

Now the style of playlist items will be dynamically changed according to whether
they are part of an album with other songs from the same album, or if they are
alone and their neighbors are both from different albums.

CCBUG: 434437
parent 0f950ef1
Pipeline #159257 passed with stage
in 4 minutes and 21 seconds
......@@ -49,22 +49,11 @@ Item {
property int viewSelectorSmallSizeThreshold: 800
readonly property alias sectionHeight: sectionSizer.implicitHeight
readonly property alias toolButtonHeight: button.height
readonly property alias trackNumberWidth: trackNumber.width
readonly property alias durationWidth: duration.width
readonly property int playListEntryMinWidth: button.width * 6 + duration.width + trackNumber.width * 2
// calculate a fixed height for playlist's section delegates
// workaround for QTBUG-52595
Column {
id: sectionSizer
visible: false
spacing: Kirigami.Units.smallSpacing
LabelWithToolTip { text: "M\nM"; level: 2 }
LabelWithToolTip { text: "M" }
}
readonly property int coverArtSize: Kirigami.Units.gridUnit * 2
// get height of buttons inside loaders
FlatButtonWithToolTip {
......
......@@ -19,19 +19,29 @@ Kirigami.ListSectionHeader {
property string albumArtist: headerData[1]
property url imageUrl: headerData[2]
height: contentLayout.implicitHeight
padding: 0
property bool simpleMode: false
RowLayout {
// keep section's elements aligned with playlistEntry's ones
leftPadding: 0
backgroundColor: simpleMode ? "transparent" : Kirigami.Theme.backgroundColor
contentItem: RowLayout {
id: contentLayout
width: parent.width
spacing: Kirigami.Units.smallSpacing
// keep section's elements aligned with playlistEntry's ones
Item {
visible: !simpleMode
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
}
ImageWithFallback {
Layout.preferredWidth: height
Layout.fillHeight: true
Layout.margins: Kirigami.Units.largeSpacing
Layout.preferredWidth: elisaTheme.coverArtSize
Layout.preferredHeight: elisaTheme.coverArtSize
source: imageUrl
fallback: elisaTheme.defaultAlbumImage
......@@ -47,14 +57,7 @@ Kirigami.ListSectionHeader {
id: albumHeaderTextColumn
Layout.fillWidth: true
Layout.preferredHeight: elisaTheme.sectionHeight
Layout.leftMargin: !LayoutMirroring.enabled ? - Kirigami.Units.smallSpacing : 0
Layout.rightMargin: LayoutMirroring.enabled ? - Kirigami.Units.smallSpacing : 0
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
spacing: 0
LabelWithToolTip {
id: mainLabel
......@@ -64,11 +67,6 @@ Kirigami.ListSectionHeader {
text: album
level: 2
font.weight: Font.Bold
elide: Text.ElideRight
wrapMode: Text.WordWrap
maximumLineCount: 2
}
LabelWithToolTip {
......@@ -78,10 +76,7 @@ Kirigami.ListSectionHeader {
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
text: albumArtist
elide: Text.ElideRight
wrapMode: Text.WordWrap
maximumLineCount: 3 - mainLabel.lineCount
type: Kirigami.Heading.Type.Secondary
}
}
}
......
......@@ -103,6 +103,7 @@ Kirigami.Page {
id: playListView
signal moveItemRequested(int oldIndex, int newIndex)
property bool dragging: false
focus: true
clip: true
......@@ -116,11 +117,6 @@ Kirigami.Page {
section.property: 'albumSection'
section.criteria: ViewSection.FullString
section.labelPositioning: ViewSection.InlineLabels
section.delegate: BasicPlayListAlbumHeader {
headerData: JSON.parse(section)
width: playListView.width
}
model: ElisaApplication.mediaPlayListProxyModel
......@@ -130,40 +126,100 @@ Kirigami.Page {
}
}
delegate: Item {
delegate: ColumnLayout {
id: playListDelegate
property alias entry: entry
property bool nextDelegateHasSection
width: entry.width
height: entry.height
width: playListView.width
spacing: 0
Loader {
id: albumSection
active: entry.sectionVisible
visible: active
Layout.fillWidth: true
sourceComponent: BasicPlayListAlbumHeader {
headerData: JSON.parse(playListDelegate.ListView.section)
}
}
// ListItemDragHandle requires listItem
// to be a child of delegate
PlayListEntry {
id: entry
// entry's placeholder
// otherwise the layout is broken while dragging
Item {
implicitWidth: entry.width
// the height must be set because entry's parent
// will be changed while dragging
implicitHeight: entry.height
// ListItemDragHandle requires listItem
// to be a child of delegate
PlayListEntry {
id: entry
width: playListView.width
index: model.index
isSelected: playListView.currentIndex === index
databaseId: model.databaseId ? model.databaseId : 0
entryType: model.entryType ? model.entryType : ElisaUtils.Unknown
title: model.title ? model.title : ''
artist: model.artist ? model.artist : ''
album: model.album ? model.album : ''
albumArtist: model.albumArtist ? model.albumArtist : ''
duration: model.duration ? model.duration : ''
fileName: model.trackResource ? model.trackResource : ''
imageUrl: model.imageUrl ? model.imageUrl : ''
trackNumber: model.trackNumber ? model.trackNumber : -1
discNumber: model.discNumber ? model.discNumber : -1
rating: model.rating ? model.rating : 0
isSingleDiscAlbum: model.isSingleDiscAlbum !== undefined ? model.isSingleDiscAlbum : true
isValid: model.isValid
isPlaying: model.isPlaying
metadataModifiableRole: model && model.metadataModifiableRole ? model.metadataModifiableRole : false
listView: playListView
listDelegate: playListDelegate
showDragHandle: playListView.count > 1
}
}
width: playListView.width
// separator
Rectangle {
readonly property bool largeSeparator: (entry.previousAlbum === entry.currentAlbum) && (entry.nextAlbum !== entry.currentAlbum) && !playListDelegate.nextDelegateHasSection
index: model.index
isSelected: playListView.currentIndex === index
implicitWidth: playListView.width - (largeSeparator ? 0 : Kirigami.Units.iconSizes.smallMedium)
implicitHeight: largeSeparator ? Kirigami.Units.largeSpacing : 1
databaseId: model.databaseId ? model.databaseId : 0
entryType: model.entryType ? model.entryType : ElisaUtils.Unknown
title: model.title ? model.title : ''
artist: model.artist ? model.artist : ''
album: model.album ? model.album : ''
albumArtist: model.albumArtist ? model.albumArtist : ''
duration: model.duration ? model.duration : ''
fileName: model.trackResource ? model.trackResource : ''
imageUrl: model.imageUrl ? model.imageUrl : ''
trackNumber: model.trackNumber ? model.trackNumber : -1
discNumber: model.discNumber ? model.discNumber : -1
rating: model.rating ? model.rating : 0
isSingleDiscAlbum: model.isSingleDiscAlbum !== undefined ? model.isSingleDiscAlbum : true
isValid: model.isValid
isPlaying: model.isPlaying
metadataModifiableRole: model && model.metadataModifiableRole ? model.metadataModifiableRole : false
listView: playListView
showDragHandle: playListView.count > 1
Layout.alignment: Qt.AlignHCenter
// the last item should never have the separator
visible: index < playListView.count - 1
// same color with Kirigami.ListSectionHeader
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
color: Kirigami.Theme.backgroundColor
Connections {
target: entry
function onSectionVisibleChanged() {
var previousDelegate = playListView.itemAtIndex(model.index - 1)
if (previousDelegate) {
previousDelegate.nextDelegateHasSection = entry.sectionVisible
}
}
}
Connections {
target: entry
function onNextAlbumChanged() {
var nextDelegate = playListView.itemAtIndex(model.index + 1)
if (nextDelegate) {
playListDelegate.nextDelegateHasSection = nextDelegate.entry.sectionVisible
}
}
}
}
}
......@@ -359,7 +415,7 @@ Kirigami.Page {
onTriggered: {
playListView.positionViewAtIndex(ElisaApplication.mediaPlayListProxyModel.currentTrackRow, ListView.Contain)
playListView.currentIndex = ElisaApplication.mediaPlayListProxyModel.currentTrackRow
playListView.currentItem.forceActiveFocus()
playListView.currentItem.entry.forceActiveFocus()
}
},
Kirigami.Action {
......
......@@ -22,6 +22,15 @@ BasePlayListDelegate {
// otherwise display a menu button
readonly property bool wideMode: width >= elisaTheme.playListEntryMinWidth
property var listDelegate
readonly property string previousAlbum: listDelegate.ListView.previousSection ? JSON.parse(listDelegate.ListView.previousSection)[0] : null
readonly property string currentAlbum: model.album || null
readonly property string nextAlbum: listDelegate.ListView.nextSection ? JSON.parse(listDelegate.ListView.nextSection)[0] : null
readonly property bool grouped: (previousAlbum === currentAlbum || nextAlbum === currentAlbum)
readonly property bool sectionVisible: (previousAlbum !== currentAlbum && nextAlbum === currentAlbum)
Accessible.role: Accessible.ListItem
Accessible.name: title + ' ' + album + ' ' + artist
......@@ -30,13 +39,16 @@ BasePlayListDelegate {
playListEntry.startPlayback()
}
padding: 0
topPadding: grouped ? 0 : Kirigami.Units.smallSpacing
bottomPadding: grouped ? 0 : Kirigami.Units.smallSpacing
leftPadding: 0
rightPadding: Kirigami.Units.smallSpacing
alternatingBackground: false
separatorVisible: !simpleMode
separatorVisible: false
contentItem: Item {
implicitWidth: playListEntry.width
implicitHeight: Math.max(Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2, elisaTheme.toolButtonHeight)
implicitHeight: childrenRect.height
Loader {
id: metadataLoader
......@@ -129,25 +141,48 @@ BasePlayListDelegate {
RowLayout {
id: trackRow
anchors.fill: parent
width: parent.width
height: Math.max((playListEntry.grouped ? Kirigami.Units.gridUnit : Kirigami.Units.gridUnit * 2), elisaTheme.toolButtonHeight)
spacing: Kirigami.Units.smallSpacing / 2
spacing: Kirigami.Units.smallSpacing
Loader {
active: !simpleMode && playListEntry.showDragHandle
sourceComponent: Kirigami.ListItemDragHandle {
listItem: playListEntry
listView: playListEntry.listView
onMoveRequested: ElisaApplication.mediaPlayListProxyModel.moveRow(oldIndex, newIndex)
onMoveRequested: {
playListEntry.listView.dragging = true
ElisaApplication.mediaPlayListProxyModel.moveRow(oldIndex, newIndex)
}
onDropped: {
playListEntry.listView.dragging = false
}
}
}
// Container for the play/pause icon and the track/disc label
Item {
Layout.preferredWidth: elisaTheme.trackNumberWidth
Layout.preferredHeight: parent.height
Layout.leftMargin: !LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0
Layout.rightMargin: LayoutMirroring.enabled ? Kirigami.Units.smallSpacing : 0
Layout.preferredWidth: Math.max(elisaTheme.trackNumberWidth, elisaTheme.coverArtSize)
Layout.fillHeight: true
Loader {
active: !playListEntry.grouped
opacity: playIcon.visible ? 0.2 : 1
anchors.fill: parent
sourceComponent: ImageWithFallback {
source: imageUrl
fallback: elisaTheme.defaultAlbumImage
sourceSize.width: height
sourceSize.height: height
fillMode: Image.PreserveAspectFit
asynchronous: true
}
}
Kirigami.Icon {
id: playIcon
......@@ -166,41 +201,55 @@ BasePlayListDelegate {
color: Kirigami.Theme.textColor
}
Label {
id: trackAndDiscNumberLabel
Loader {
active: playListEntry.grouped && isValid && !playIcon.visible
anchors.fill: parent
anchors.rightMargin: LayoutMirroring.enabled ? 0 : Kirigami.Units.largeSpacing
anchors.leftMargin: !LayoutMirroring.enabled ? 0 : Kirigami.Units.largeSpacing
horizontalAlignment: Text.AlignRight
text: {
var trackNumberString;
if (trackNumber !== -1) {
trackNumberString = Number(trackNumber).toLocaleString(Qt.locale(), 'f', 0);
} else {
trackNumberString = ''
}
if (!isSingleDiscAlbum && discNumber !== 0 ) {
return trackNumberString + "/" + Number(discNumber).toLocaleString(Qt.locale(), 'f', 0)
} else {
return trackNumberString
sourceComponent: Label {
//trackAndDiscNumberLabel
horizontalAlignment: Text.AlignRight
text: {
var trackNumberString;
if (trackNumber !== -1) {
trackNumberString = Number(trackNumber).toLocaleString(Qt.locale(), 'f', 0);
} else {
trackNumberString = ''
}
if (!isSingleDiscAlbum && discNumber !== 0 ) {
return trackNumberString + "/" + Number(discNumber).toLocaleString(Qt.locale(), 'f', 0)
} else {
return trackNumberString
}
}
textFormat: Text.PlainText
font.weight: (isPlaying ? Font.Bold : Font.Normal)
}
textFormat: Text.PlainText
font.weight: (isPlaying ? Font.Bold : Font.Normal)
visible: isValid && !playIcon.visible
}
}
LabelWithToolTip {
text: title
font.weight: isPlaying ? Font.Bold : Font.Normal
visible: isValid
ColumnLayout {
spacing: 0
Layout.fillWidth: true
Layout.preferredHeight: playListEntry.height
LabelWithToolTip {
text: title
font.weight: isPlaying ? Font.Bold : Font.Normal
visible: isValid
Layout.fillWidth: true
}
Loader {
active: !playListEntry.grouped
visible: active
Layout.fillWidth: true
sourceComponent: LabelWithToolTip {
id: artistLabel
text: artist + " - " + album
type: Kirigami.Heading.Type.Secondary
}
}
}
// button row
......@@ -288,9 +337,7 @@ BasePlayListDelegate {
LabelWithToolTip {
id: durationLabel
text: duration
font.weight: (isPlaying ? Font.Bold : Font.Normal)
Layout.leftMargin: Kirigami.Units.smallSpacing
Layout.rightMargin: Kirigami.Units.smallSpacing
font.weight: isPlaying ? Font.Bold : Font.Normal
}
Loader {
......@@ -337,6 +384,10 @@ BasePlayListDelegate {
}
states: [
State {
name: "dragging"
when: playListEntry.listView.dragging
},
State {
name: "menuVisible"
when: menuLoader.menuVisible && !playListEntry.wideMode
......
......@@ -54,40 +54,58 @@ ScrollView {
section.property: 'albumSection'
section.criteria: ViewSection.FullString
section.labelPositioning: ViewSection.InlineLabels
section.delegate: BasicPlayListAlbumHeader {
headerData: JSON.parse(section)
delegate: Column {
id: playListDelegate
width: playListView.width
Kirigami.Theme.inherit: true
backgroundColor: "transparent"
}
delegate: PlayListEntry {
id: entry
// album seperator
Item {
width: playListView.width
height: Kirigami.Units.smallSpacing
visible: entry.previousAlbum && entry.previousAlbum !== entry.currentAlbum
}
focus: true
width: playListView.width
Loader {
id: albumSection
active: entry.sectionVisible
visible: active
sourceComponent: BasicPlayListAlbumHeader {
headerData: JSON.parse(playListDelegate.ListView.section)
width: playListView.width
simpleMode: true
Kirigami.Theme.textColor: "#eff0f1"
}
}
index: model.index
simpleMode: true
listView: playListView
databaseId: model.databaseId ? model.databaseId : 0
entryType: model.entryType ? model.entryType : ElisaUtils.Unknown
title: model.title ? model.title : ''
artist: model.artist ? model.artist : ''
album: model.album ? model.album : ''
albumArtist: model.albumArtist ? model.albumArtist : ''
duration: model.duration ? model.duration : ''
fileName: model.trackResource ? model.trackResource : ''
imageUrl: model.imageUrl ? model.imageUrl : ''
trackNumber: model.trackNumber ? model.trackNumber : -1
discNumber: model.discNumber ? model.discNumber : -1
rating: model.rating ? model.rating : 0
isSingleDiscAlbum: model.isSingleDiscAlbum !== undefined ? model.isSingleDiscAlbum : true
isValid: model.isValid
isPlaying: model.isPlaying
metadataModifiableRole: model.metadataModifiableRole ? model.metadataModifiableRole : false
PlayListEntry {
id: entry
focus: true
width: playListView.width
index: model.index
simpleMode: true
listView: playListView
listDelegate: playListDelegate
databaseId: model.databaseId ? model.databaseId : 0
entryType: model.entryType ? model.entryType : ElisaUtils.Unknown
title: model.title ? model.title : ''
artist: model.artist ? model.artist : ''
album: model.album ? model.album : ''
albumArtist: model.albumArtist ? model.albumArtist : ''
duration: model.duration ? model.duration : ''
fileName: model.trackResource ? model.trackResource : ''
imageUrl: model.imageUrl ? model.imageUrl : ''
trackNumber: model.trackNumber ? model.trackNumber : -1
discNumber: model.discNumber ? model.discNumber : -1
rating: model.rating ? model.rating : 0
isSingleDiscAlbum: model.isSingleDiscAlbum !== undefined ? model.isSingleDiscAlbum : true
isValid: model.isValid
isPlaying: model.isPlaying
metadataModifiableRole: model.metadataModifiableRole ? model.metadataModifiableRole : false
}
}
add: Transition {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment