mirror of
https://github.com/HenkKalkwater/harbour-sailfin.git
synced 2024-11-22 09:15:18 +00:00
Added MusicAlbumPage
- [UI]: Added specialized view for music albums - Prepared RemoteImageView for fading in image when loaded
This commit is contained in:
parent
a29ab3dff4
commit
040aeb1a40
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -6,4 +6,5 @@ build/
|
||||||
build-*/
|
build-*/
|
||||||
|
|
||||||
# IDE files
|
# IDE files
|
||||||
harbour-sailfin.pro.user
|
*.user
|
||||||
|
CMakeLists.txt.user.*
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -21,7 +21,6 @@ set(sailfin_QML_SOURCES
|
||||||
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
|
||||||
|
")
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -45,6 +45,7 @@ BackgroundItem {
|
||||||
}
|
}
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
fallbackColor: Utils.colorFromString(title)
|
fallbackColor: Utils.colorFromString(title)
|
||||||
|
highlighted: root.highlighted
|
||||||
}
|
}
|
||||||
|
|
||||||
/*Rectangle {
|
/*Rectangle {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
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
|
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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue