1
0
Fork 0
mirror of https://github.com/HenkKalkwater/harbour-sailfin.git synced 2024-05-08 23:22:42 +00:00

Added MusicAlbumPage

- [UI]: Added specialized view for music albums

- Prepared RemoteImageView for fading in image when loaded
This commit is contained in:
Chris Josten 2020-10-26 22:29:07 +01:00
parent a29ab3dff4
commit 040aeb1a40
10 changed files with 330 additions and 22 deletions

3
.gitignore vendored
View file

@ -6,4 +6,5 @@ build/
build-*/ build-*/
# IDE files # IDE files
harbour-sailfin.pro.user *.user
CMakeLists.txt.user.*

View file

@ -140,6 +140,7 @@ public:
Q_PROPERTY(QList<QString> fields MEMBER m_fields NOTIFY fieldsChanged) Q_PROPERTY(QList<QString> fields MEMBER m_fields NOTIFY fieldsChanged)
Q_PROPERTY(QString seasonId MEMBER m_seasonId NOTIFY seasonIdChanged) Q_PROPERTY(QString seasonId MEMBER m_seasonId NOTIFY seasonIdChanged)
Q_PROPERTY(QList<QString> imageTypes MEMBER m_imageTypes NOTIFY imageTypesChanged) Q_PROPERTY(QList<QString> imageTypes MEMBER m_imageTypes NOTIFY imageTypesChanged)
Q_PROPERTY(QList<QString> includeItemTypes MEMBER m_includeItemTypes NOTIFY includeItemTypesChanged)
Q_PROPERTY(bool recursive MEMBER m_recursive) Q_PROPERTY(bool recursive MEMBER m_recursive)
Q_PROPERTY(SortOrder sortOrder MEMBER m_sortOrder NOTIFY sortOrderChanged) Q_PROPERTY(SortOrder sortOrder MEMBER m_sortOrder NOTIFY sortOrderChanged)
@ -182,6 +183,7 @@ signals:
void seasonIdChanged(QString newSeasonId); void seasonIdChanged(QString newSeasonId);
void fieldsChanged(QList<QString> newFields); void fieldsChanged(QList<QString> newFields);
void imageTypesChanged(QList<QString> newImageTypes); void imageTypesChanged(QList<QString> newImageTypes);
void includeItemTypesChanged(const QList<QString> &newIncludeItemTypes);
public slots: public slots:
/** /**
@ -225,9 +227,10 @@ protected:
bool m_addUserId = false; bool m_addUserId = false;
QString m_parentId; QString m_parentId;
QString m_seasonId; QString m_seasonId;
QList<QString> m_fields; QList<QString> m_fields = {};
QList<QString> m_imageTypes; QList<QString> m_imageTypes = {};
QList<QString> m_sortBy = {}; QList<QString> m_sortBy = {};
QList<QString> m_includeItemTypes = {};
SortOrder m_sortOrder = Unspecified; SortOrder m_sortOrder = Unspecified;
bool m_recursive = false; bool m_recursive = false;

View file

@ -169,6 +169,21 @@ private:
QString m_errorString; QString m_errorString;
}; };
class NameGuidPair : public JsonSerializable {
Q_OBJECT
public:
Q_INVOKABLE NameGuidPair(QObject *parent = nullptr);
Q_PROPERTY(QString name MEMBER m_name NOTIFY nameChanged)
// Once again the Jellyfin id workaround
Q_PROPERTY(QString jellyfinId MEMBER m_id NOTIFY jellyfinIdChanged)
signals:
void nameChanged(const QString &newName);
void jellyfinIdChanged(const QString &newJellyfinId);
private:
QString m_name;
QString m_id;
};
class User : public RemoteData { class User : public RemoteData {
Q_OBJECT Q_OBJECT
public: public:
@ -333,10 +348,16 @@ public:
Q_PROPERTY(bool isFolder READ isFolder WRITE setIsFolder NOTIFY isFolderChanged) Q_PROPERTY(bool isFolder READ isFolder WRITE setIsFolder NOTIFY isFolderChanged)
Q_PROPERTY(QString type MEMBER m_type NOTIFY typeChanged) Q_PROPERTY(QString type MEMBER m_type NOTIFY typeChanged)
Q_PROPERTY(UserData *userData MEMBER m_userData NOTIFY userDataChanged) Q_PROPERTY(UserData *userData MEMBER m_userData NOTIFY userDataChanged)
Q_PROPERTY(int recursiveItemCount READ recursiveItemCount WRITE setRecursiveItemCount NOTIFY recursiveItemCountChanged)
Q_PROPERTY(int childCount READ childCount WRITE setChildCount NOTIFY childCountChanged)
Q_PROPERTY(QString albumArtist MEMBER m_albumArtist NOTIFY albumArtistChanged)
Q_PROPERTY(QList<NameGuidPair *> __list__albumArtists MEMBER __list__m_albumArtists NOTIFY albumArtistsChanged)
Q_PROPERTY(QVariantList albumArtists MEMBER m_albumArtists NOTIFY albumArtistsChanged STORED false)
Q_PROPERTY(QString seriesName MEMBER m_seriesName NOTIFY seriesNameChanged) Q_PROPERTY(QString seriesName MEMBER m_seriesName NOTIFY seriesNameChanged)
Q_PROPERTY(QString seasonName MEMBER m_seasonName NOTIFY seasonNameChanged) Q_PROPERTY(QString seasonName MEMBER m_seasonName NOTIFY seasonNameChanged)
Q_PROPERTY(QList<MediaStream *> __list__mediaStreams MEMBER __list__m_mediaStreams NOTIFY mediaStreamsChanged) Q_PROPERTY(QList<MediaStream *> __list__mediaStreams MEMBER __list__m_mediaStreams NOTIFY mediaStreamsChanged)
Q_PROPERTY(QVariantList mediaStreams MEMBER m_mediaStreams NOTIFY mediaStreamsChanged STORED false) Q_PROPERTY(QVariantList mediaStreams MEMBER m_mediaStreams NOTIFY mediaStreamsChanged STORED false)
Q_PROPERTY(QStringList artists MEMBER m_artists NOTIFY artistsChanged)
// Why is this a QJsonObject? Well, because I couldn't be bothered to implement the deserialisations of // Why is this a QJsonObject? Well, because I couldn't be bothered to implement the deserialisations of
// a QHash at the moment. // a QHash at the moment.
Q_PROPERTY(QJsonObject imageTags MEMBER m_imageTags NOTIFY imageTagsChanged) Q_PROPERTY(QJsonObject imageTags MEMBER m_imageTags NOTIFY imageTagsChanged)
@ -374,6 +395,10 @@ public:
void setIndexNumberEnd(int newIndexNumberEnd) { m_indexNumberEnd = std::optional<int>(newIndexNumberEnd); emit indexNumberEndChanged(newIndexNumberEnd); } void setIndexNumberEnd(int newIndexNumberEnd) { m_indexNumberEnd = std::optional<int>(newIndexNumberEnd); emit indexNumberEndChanged(newIndexNumberEnd); }
bool isFolder() const { return m_isFolder.value_or(false); } bool isFolder() const { return m_isFolder.value_or(false); }
void setIsFolder(bool newIsFolder) { m_isFolder = newIsFolder; emit isFolderChanged(newIsFolder); } void setIsFolder(bool newIsFolder) { m_isFolder = newIsFolder; emit isFolderChanged(newIsFolder); }
int recursiveItemCount() const { return m_recursiveItemCount.value_or(-1); }
void setRecursiveItemCount(int newRecursiveItemCount) { m_recursiveItemCount = newRecursiveItemCount; emit recursiveItemCountChanged(newRecursiveItemCount); }
int childCount() const { return m_childCount.value_or(-1); }
void setChildCount(int newChildCount) { m_childCount = newChildCount; emit childCountChanged(newChildCount); }
//QQmlListProperty<MediaStream> mediaStreams() { return toReadOnlyQmlListProperty<MediaStream>(m_mediaStreams); } //QQmlListProperty<MediaStream> mediaStreams() { return toReadOnlyQmlListProperty<MediaStream>(m_mediaStreams); }
//QList<QObject *> mediaStreams() { return *reinterpret_cast<QList<QObject *> *>(&m_mediaStreams); } //QList<QObject *> mediaStreams() { return *reinterpret_cast<QList<QObject *> *>(&m_mediaStreams); }
@ -415,9 +440,14 @@ signals:
void isFolderChanged(bool newIsFolder); void isFolderChanged(bool newIsFolder);
void typeChanged(const QString &newType); void typeChanged(const QString &newType);
void userDataChanged(UserData *newUserData); void userDataChanged(UserData *newUserData);
void recursiveItemCountChanged(int newRecursiveItemCount);
void childCountChanged(int newChildCount);
void albumArtistChanged(const QString &newAlbumArtist);
void albumArtistsChanged(NameGuidPair *newAlbumArtists);
void seriesNameChanged(const QString &newSeriesName); void seriesNameChanged(const QString &newSeriesName);
void seasonNameChanged(const QString &newSeasonName); void seasonNameChanged(const QString &newSeasonName);
void mediaStreamsChanged(/*const QList<MediaStream *> &newMediaStreams*/); void mediaStreamsChanged(/*const QList<MediaStream *> &newMediaStreams*/);
void artistsChanged(const QStringList &newArtists);
void imageTagsChanged(); void imageTagsChanged();
void imageBlurHashesChanged(); void imageBlurHashesChanged();
@ -463,10 +493,16 @@ protected:
std::optional<bool> m_isFolder = std::nullopt; std::optional<bool> m_isFolder = std::nullopt;
QString m_type; QString m_type;
UserData *m_userData = nullptr; UserData *m_userData = nullptr;
std::optional<int> m_recursiveItemCount = std::nullopt;
std::optional<int> m_childCount = std::nullopt;
QString m_albumArtist;
QList<NameGuidPair *> __list__m_albumArtists;
QVariantList m_albumArtists;
QString m_seriesName; QString m_seriesName;
QString m_seasonName; QString m_seasonName;
QList<MediaStream *> __list__m_mediaStreams; QList<MediaStream *> __list__m_mediaStreams;
QVariantList m_mediaStreams; QVariantList m_mediaStreams;
QStringList m_artists;
QJsonObject m_imageTags; QJsonObject m_imageTags;
QJsonObject m_imageBlurHashes; QJsonObject m_imageBlurHashes;

View file

@ -66,6 +66,9 @@ void ApiModel::load(LoadType type) {
if (!m_imageTypes.empty()) { if (!m_imageTypes.empty()) {
query.addQueryItem("ImageTypes", m_imageTypes.join(",")); query.addQueryItem("ImageTypes", m_imageTypes.join(","));
} }
if (!m_includeItemTypes.empty()) {
query.addQueryItem("IncludeItemTypes", m_includeItemTypes.join(","));
}
if (!m_fields.empty()) { if (!m_fields.empty()) {
query.addQueryItem("Fields", m_fields.join(",")); query.addQueryItem("Fields", m_fields.join(","));
} }
@ -191,6 +194,7 @@ bool ApiModel::canFetchMore(const QModelIndex &parent) const {
switch(m_status) { switch(m_status) {
case Uninitialised: case Uninitialised:
case Loading: case Loading:
case LoadingMore:
return false; return false;
default: default:
break; break;

View file

@ -254,6 +254,8 @@ void RemoteData::reload() {
}); });
} }
NameGuidPair::NameGuidPair(QObject *parent) : JsonSerializable (parent) {}
// User // User
User::User(QObject *parent) : RemoteData (parent) {} User::User(QObject *parent) : RemoteData (parent) {}
@ -351,6 +353,7 @@ void Item::onUserDataChanged(const QString &itemId, QSharedPointer<UserData> use
void registerSerializableJsonTypes(const char* URI) { void registerSerializableJsonTypes(const char* URI) {
qmlRegisterType<MediaStream>(URI, 1, 0, "MediaStream"); qmlRegisterType<MediaStream>(URI, 1, 0, "MediaStream");
qmlRegisterType<NameGuidPair>(URI, 1, 0, "NameGuidPair");
qmlRegisterType<User>(URI, 1, 0, "User"); qmlRegisterType<User>(URI, 1, 0, "User");
qmlRegisterType<UserData>(URI, 1, 0, "UserData"); qmlRegisterType<UserData>(URI, 1, 0, "UserData");
qmlRegisterType<Item>(URI, 1, 0, "JellyfinItem"); qmlRegisterType<Item>(URI, 1, 0, "JellyfinItem");

View file

@ -20,8 +20,7 @@ set(sailfin_QML_SOURCES
qml/components/Shim.qml qml/components/Shim.qml
qml/components/UserGridDelegate.qml qml/components/UserGridDelegate.qml
qml/components/VideoPlayer.qml qml/components/VideoPlayer.qml
qml/components/VideoTrackSelector.qml qml/components/VideoTrackSelector.qml
qml/components/itemdetails/SeasonDetails.qml
qml/components/videoplayer/VideoError.qml qml/components/videoplayer/VideoError.qml
qml/components/videoplayer/VideoHud.qml qml/components/videoplayer/VideoHud.qml
qml/cover/CoverPage.qml qml/cover/CoverPage.qml
@ -46,14 +45,12 @@ set(sailfin_QML_SOURCES
qml/pages/setup/LoginDialog.qml qml/pages/setup/LoginDialog.qml
qml/qmldir) qml/qmldir)
add_executable(harbour-sailfin ${harbour-sailfin_SOURCES}) add_executable(harbour-sailfin ${harbour-sailfin_SOURCES} ${sailfin_QML_SOURCES})
target_link_libraries(harbour-sailfin PRIVATE Qt5::Gui Qt5::Qml Qt5::Quick SailfishApp::SailfishApp target_link_libraries(harbour-sailfin PRIVATE Qt5::Gui Qt5::Qml Qt5::Quick SailfishApp::SailfishApp
# Note: this may break when the compiler changes. -rdynamic and -pie seem to be needed for the # Note: this may break when the compiler changes. -rdynamic and -pie seem to be needed for the
# invoker/booster to work # invoker/booster to work
jellyfin-qt "-Wl,-rpath,${CMAKE_INSTALL_LIBDIR} -rdynamic -pie") jellyfin-qt "-Wl,-rpath,${CMAKE_INSTALL_LIBDIR} -rdynamic -pie")
add_custom_target(harbour-sailfin-qml ${sailfin_QML_SOURCES})
install(TARGETS harbour-sailfin install(TARGETS harbour-sailfin
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
@ -79,3 +76,19 @@ install(FILES icons/128x128/harbour-sailfin.png
install(FILES icons/172x172/harbour-sailfin.png install(FILES icons/172x172/harbour-sailfin.png
DESTINATION share/icons/hicolor/172x172/apps DESTINATION share/icons/hicolor/172x172/apps
) )
# Tell Qt Creator where the application executable(s) would be located on the
# device.
#
# It is not necessary to list other deployables than executables (runtime
# targets) here. The deployment process of Sailfish OS projects is opaque to
# Qt Creator and the information contained in QtCreatorDeployment.txt is only
# used to locate the executable associated with the active run configuration
# on the device in order to run it.
#
# Search the Qt Creator Manual to learn about the QtCreatorDeployment.txt file
# format.
file(WRITE "${CMAKE_BINARY_DIR}/QtCreatorDeployment.txt"
"${CMAKE_INSTALL_PREFIX}
sailfish/harbour-sailfin:bin
")

View file

@ -22,15 +22,17 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
/** /**
* Converts miliseconds to a h:mm:ss format * Converts miliseconds to a h:mm:ss format
*/ */
function timeToText(time) { function timeToText(time, showHours) {
var mShowHours = showHours === undefined ? true : showHours
if (time < 0) return "??:??:??" if (time < 0) return "??:??:??"
var hours = Math.floor(time / (60 * 60 * 1000)) var hours = Math.floor(time / (60 * 60 * 1000))
var left = time % (60 * 60 * 1000) var left = time % (60 * 60 * 1000)
var minutes = Math.floor(left / (60 * 1000)) var minutes = Math.floor(left / (60 * 1000))
left = time % (60 * 1000) left = time % (60 * 1000)
var seconds = Math.floor(left / 1000) var seconds = Math.floor(left / 1000)
return (hours > 0 ? hours + ":" : "")
return hours + ":" + (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "")+ seconds + (minutes < 10 ? "0" : "")
+ minutes + ":" + (seconds < 10 ? "0" : "")+ seconds
} }
function msToTicks(ms) { function msToTicks(ms) {
@ -41,8 +43,8 @@ function ticksToMs(ticks) {
return ticks / 10000; return ticks / 10000;
} }
function ticksToText(ticks) { function ticksToText(ticks, showHours) {
return timeToText(ticks / 10000); return timeToText(ticks / 10000, showHours);
} }
function itemImageUrl(baseUrl, item, type, options) { function itemImageUrl(baseUrl, item, type, options) {
@ -80,6 +82,8 @@ function getPageUrl(mediaType, itemType) {
return Qt.resolvedUrl("pages/itemdetails/SeasonPage.qml") return Qt.resolvedUrl("pages/itemdetails/SeasonPage.qml")
case "episode": case "episode":
return Qt.resolvedUrl("pages/itemdetails/EpisodePage.qml") return Qt.resolvedUrl("pages/itemdetails/EpisodePage.qml")
case "musicalbum":
return Qt.resolvedUrl("pages/itemdetails/MusicAlbumPage.qml")
default: default:
switch (mediaType ? mediaType.toLowerCase() : "folder") { switch (mediaType ? mediaType.toLowerCase() : "folder") {
case "folder": case "folder":

View file

@ -45,6 +45,7 @@ BackgroundItem {
} }
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
fallbackColor: Utils.colorFromString(title) fallbackColor: Utils.colorFromString(title)
highlighted: root.highlighted
} }
/*Rectangle { /*Rectangle {

View file

@ -23,11 +23,22 @@ import Sailfish.Silica 1.0
/** /**
* An image for "remote" images (loaded over e.g. http), with a spinner and a fallback image * An image for "remote" images (loaded over e.g. http), with a spinner and a fallback image
*/ */
HighlightImage { SilicaItem {
property string fallbackImage property string fallbackImage
property bool usingFallbackImage property bool usingFallbackImage
property color fallbackColor: Theme.highlightColor property color fallbackColor: Theme.highlightColor
asynchronous: true
property alias source: realImage.source
property alias sourceSize: realImage.sourceSize
property alias fillMode: realImage.fillMode
implicitHeight: realImage.implicitHeight
implicitWidth: realImage.implicitWidth
Image {
id: realImage
anchors.fill: parent
asynchronous: true
}
Rectangle { Rectangle {
id: fallbackBackground id: fallbackBackground
@ -36,18 +47,25 @@ HighlightImage {
GradientStop { position: 0.0; color: fallbackColor; } GradientStop { position: 0.0; color: fallbackColor; }
GradientStop { position: 1.0; color: Theme.highlightDimmerFromColor(fallbackColor, Theme.colorScheme); } GradientStop { position: 1.0; color: Theme.highlightDimmerFromColor(fallbackColor, Theme.colorScheme); }
} }
visible: parent.status == Image.Error || parent.status == Image.Null || parent.status == Image.Loading visible: realImage.status === Image.Error || realImage.status === Image.Null || realImage.status === Image.Loading
}
Rectangle {
id: highlightOverlay
anchors.fill: parent
color: Theme.rgba(Theme.highlightColor, Theme.opacityOverlay)
visible: parent.highlighted
} }
BusyIndicator { BusyIndicator {
anchors.centerIn: parent anchors.centerIn: parent
running: parent.status == Image.Loading running: realImage.status === Image.Loading
} }
HighlightImage { HighlightImage {
id: fallbackImageItem id: fallbackImageItem
anchors.centerIn: parent anchors.centerIn: parent
visible: parent.status == Image.Error || parent.status == Image.Null visible: realImage.status === Image.Error || realImage.status === Image.Null
source: fallbackImage ? fallbackImage : "image://theme/icon-m-question" source: fallbackImage ? fallbackImage : "image://theme/icon-m-question"
} }
} }

View file

@ -1,5 +1,230 @@
import QtQuick 2.0 import QtQuick 2.6
import Sailfish.Silica 1.0
Item { import nl.netsoj.chris.Jellyfin 1.0
import "../../components"
import "../.."
BaseDetailPage {
readonly property int _songIndexWidth: 100
property string _albumArtistText: itemData.albumArtist
UserItemModel {
id: collectionModel
apiClient: ApiClient
sortBy: ["SortName"]
fields: ["ItemCounts","PrimaryImageAspectRatio","BasicSyncInfo","CanDelete","MediaSourceCount"]
parentId: itemData.jellyfinId
onParentIdChanged: reload()
}
SilicaListView {
id: list
anchors.fill: parent
model: collectionModel
header: Item {
property string stateIfArt: "largeArt"
property alias albumArt: albumArt
id: listHeader
width: parent.width
//spacing: Theme.paddingLarge
state: albumArt.source != "" ? stateIfArt : "noArt"
MouseArea {
anchors.fill: parent
onClicked: {
if (listHeader.stateIfArt == "largeArt") {
listHeader.stateIfArt = "details"
} else {
listHeader.stateIfArt = "largeArt"
}
}
}
RemoteImage {
id: albumArt
anchors {
top: parent.top
right: parent.right
}
source: Utils.itemImageUrl(ApiClient.baseUrl, itemData, "Primary", {"maxWidth": parent.width})
sourceSize.width: listHeader.width
sourceSize.height: listHeader.width
fillMode: Image.PreserveAspectFit
opacity: 1
clip: true
}
PageHeader {
id: albumHeader
width: parent.width - Theme.horizontalPageMargin - height
title: itemData.name
description: qsTr("%1\n%2 songs | %3 | %4")
.arg(_albumArtistText)
.arg(itemData.childCount)
.arg(Utils.ticksToText(itemData.runTimeTicks))
.arg(itemData.productionYear > 0 ? itemData.productionYear : qsTr("Unknown year"))
}
states: [
State {
name: "largeArt"
PropertyChanges {
target: albumArt
width: parent.width
height: width
}
PropertyChanges {
target: listHeader
height: width
}
PropertyChanges {
target: albumHeader
opacity: 0
}
PropertyChanges {
target: list
contentY: -list.width
}
AnchorChanges {
target: albumHeader
anchors.left: undefined
anchors.right: albumArt.left
}
},
State {
name: "details"
PropertyChanges {
target: albumArt
width: height
height: albumHeader.height
}
PropertyChanges {
target: listHeader
height: albumHeader.height
}
PropertyChanges {
target: albumHeader
opacity: 1
}
PropertyChanges {
target: list
contentY: -albumHeader.height
}
AnchorChanges {
target: albumHeader
anchors.left: undefined
anchors.right: albumArt.left
}
},
State {
name: "noArt"
extend: "details"
PropertyChanges {
target: albumArt
opacity: 0
}
PropertyChanges {
target: albumHeader
width: parent.width - Theme.horizontalPageMargin * 2
}
AnchorChanges {
target: albumHeader
anchors.left: parent.left
anchors.right: parent.right
}
}
]
transitions: Transition {
OpacityAnimator { target: albumHeader}
OpacityAnimator { target: albumArt}
NumberAnimation {
properties: "width,height,contentY"
//velocity: 1600
duration: 300
easing.type: Easing.OutQuad
}
AnchorAnimation {}
}
}
section {
property: "parentIndexNumber"
delegate: SectionHeader {
text: qsTr("Disc %1").arg(section)
}
}
delegate: ListItem {
contentHeight: songName.height + songArtists.height + 2 * Theme.paddingMedium
width: parent.width
Label {
id: songIndex
anchors {
top: parent.top
topMargin: Theme.paddingMedium
left: parent.left
leftMargin: Theme.horizontalPageMargin
}
text: model.indexNumber
horizontalAlignment: Text.AlignRight
font.pixelSize: Theme.fontSizeExtraLarge
width: _songIndexWidth
}
Label {
id: songName
anchors {
left: songIndex.right
leftMargin: Theme.paddingLarge
top: parent.top
topMargin: Theme.paddingMedium
right: duration.left
rightMargin: Theme.paddingLarge
}
text: model.name
font.pixelSize: Theme.fontSizeMedium
truncationMode: TruncationMode.Fade
}
Label {
id: songArtists
anchors {
top: songName.bottom
left: songIndex.right
leftMargin: Theme.paddingLarge
right: parent.right
rightMargin: Theme.horizontalPageMargin
}
text: model.artists.join(", ")
font.pixelSize: Theme.fontSizeSmall
truncationMode: TruncationMode.Fade
color: highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor
}
Label {
id: duration
anchors {
right: parent.right
rightMargin: Theme.horizontalPageMargin
baseline: songName.baseline
}
width: contentWidth
text: Utils.ticksToText(model.runTimeTicks)
font.pixelSize: Theme.fontSizeSmall
color: highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor
}
}
VerticalScrollDecorator {}
}
Connections {
target: itemData
onAlbumArtistsChanged: {
console.log(itemData.albumArtists)
_albumArtistText = ""
for (var i = 0; i < itemData.albumArtists.length; i++) {
_albumArtistText += itemData.albumArtists[i]["name"]
}
}
}
} }