1
0
Fork 0
mirror of https://github.com/HenkKalkwater/harbour-sailfin.git synced 2024-11-22 09:15:18 +00:00

Added SortOrder and fallback pages.

Once again I couldn't stop myself from being sidetracked.
[Collections]: Added: allow specifying sort order (Ascending, Descending)
[General]: Improved: Added video fallback page to allow unknown video types to be played, although without extra metadata.
[General]: Improved: Added folder fallback, so unknown collection types are at least displayed without metadata.
This commit is contained in:
Chris Josten 2020-10-01 11:56:02 +02:00
parent b68da318f2
commit 5057867ade
11 changed files with 147 additions and 20 deletions

View file

@ -67,6 +67,7 @@ DISTFILES += \
qml/pages/itemdetails/SeasonPage.qml \ qml/pages/itemdetails/SeasonPage.qml \
qml/pages/itemdetails/SeriesPage.qml \ qml/pages/itemdetails/SeriesPage.qml \
qml/pages/itemdetails/UnsupportedPage.qml \ qml/pages/itemdetails/UnsupportedPage.qml \
qml/pages/itemdetails/VideoPage.qml \
qml/pages/setup/AddServerConnectingPage.qml \ qml/pages/setup/AddServerConnectingPage.qml \
qml/pages/setup/LoginDialog.qml \ qml/pages/setup/LoginDialog.qml \
qml/qmldir qml/qmldir

View file

@ -27,5 +27,8 @@ QtObject {
readonly property real libraryDelegateHeight: Screen.width / 3 readonly property real libraryDelegateHeight: Screen.width / 3
readonly property real libraryDelegatePosterHeight: Screen.width / 2 readonly property real libraryDelegatePosterHeight: Screen.width / 2
readonly property real libraryProgressHeight: Theme.paddingMedium readonly property real libraryProgressHeight: Theme.paddingMedium
readonly property real horizontalVideoAspectRatio: 1.66666 //itemData.PrimaryImageAspectRatio
} }

View file

@ -60,19 +60,26 @@ function usePortraitCover(itemType) {
/** /**
* Returns the page url for a certain item type. * Returns the page url for a certain item type.
*/ */
function getPageUrl(itemType) { function getPageUrl(mediaType, itemType) {
switch (itemType.toLowerCase()) { switch (itemType.toLowerCase()) {
case "series": case "series":
return Qt.resolvedUrl("pages/itemdetails/SeriesPage.qml") return Qt.resolvedUrl("pages/itemdetails/SeriesPage.qml")
case "movie": case "movie":
return Qt.resolvedUrl("pages/itemdetails/FilmPage.qml") return Qt.resolvedUrl("pages/itemdetails/FilmPage.qml")
case "collection": case "collection":
return Qt.resolvedUrl("pages/itemdetails/ColectionPage.qml") return Qt.resolvedUrl("pages/itemdetails/CollectionPage.qml")
case "season": case "season":
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")
default: default:
return Qt.resolvedUrl("pages/itemdetails/UnsupportedPage.qml") switch (mediaType ? mediaType.toLowerCase() : "folder") {
case "folder":
return Qt.resolvedUrl("pages/itemdetails/CollectionPage.qml")
case "video":
return Qt.resolvedUrl("pages/itemdetails/VideoPage.qml")
default:
return Qt.resolvedUrl("pages/itemdetails/UnsupportedPage.qml")
}
} }
} }

View file

@ -202,7 +202,7 @@ Page {
progress: model.userData.PlayedPercentage / 100 progress: model.userData.PlayedPercentage / 100
onClicked: { onClicked: {
pageStack.push(Utils.getPageUrl(model.type), {"itemId": model.id}) pageStack.push(Utils.getPageUrl(model.mediaType, model.type), {"itemId": model.id})
} }
} }
} }

View file

@ -62,17 +62,13 @@ BaseDetailPage {
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
clip: true clip: true
} }
Rectangle { Shim {
anchors { anchors {
left: parent.left left: parent.left
bottom: parent.bottom bottom: parent.bottom
right: parent.right right: parent.right
} }
height: itemName.height + Theme.paddingSmall * 2 height: itemName.height + Theme.paddingSmall * 2
gradient: Gradient {
GradientStop { position: 0.0; color: "transparent" }
GradientStop { position: 1.0; color: Theme.highlightDimmerColor }
}
visible: itemImage.status !== Image.Null visible: itemImage.status !== Image.Null
} }
Label { Label {
@ -96,7 +92,7 @@ BaseDetailPage {
pageStack.push(Qt.resolvedUrl("CollectionPage.qml"), {"itemId": model.id}) pageStack.push(Qt.resolvedUrl("CollectionPage.qml"), {"itemId": model.id})
break; break;
default: default:
pageStack.push(Utils.getPageUrl(model.type), {"itemId": model.id}) pageStack.push(Utils.getPageUrl(model.mediaType, model.type), {"itemId": model.id})
} }
} }
} }
@ -110,6 +106,8 @@ BaseDetailPage {
VerticalScrollDecorator {} VerticalScrollDecorator {}
} }
// The page for selecting a sort order
Component { Component {
id: sortPageComponent id: sortPageComponent
Page { Page {
@ -139,8 +137,23 @@ BaseDetailPage {
} }
text: model.name text: model.name
} }
onClicked: { menu: ContextMenu {
collectionModel.sortBy = [model.value] MenuItem {
//: Sort order
text: qsTr("Ascending")
onClicked: apply(model.value, ApiModel.Ascending)
}
MenuItem {
//: Sort order
text: qsTr("Descending")
onClicked: apply(model.value, ApiModel.Descending)
}
}
onClicked: openMenu()
function apply(field, order) {
collectionModel.sortBy = [field];
collectionModel.sortOrder = order;
collectionModel.reload() collectionModel.reload()
pageStack.pop() pageStack.pop()
} }

View file

@ -122,7 +122,7 @@ BaseDetailPage {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
elide: Text.ElideRight elide: Text.ElideRight
} }
onClicked: pageStack.push(Utils.getPageUrl(model.type), {"itemId": model.id}) onClicked: pageStack.push(Utils.getPageUrl(model.mediaType, model.type), {"itemId": model.id})
} }
} }

View file

@ -80,7 +80,7 @@ BaseDetailPage {
delegate: LibraryItemDelegate { delegate: LibraryItemDelegate {
poster: Utils.itemModelImageUrl(ApiClient.baseUrl, model.id, model.imageTags["Primary"], "Primary", {"maxHeight": height}) poster: Utils.itemModelImageUrl(ApiClient.baseUrl, model.id, model.imageTags["Primary"], "Primary", {"maxHeight": height})
title: model.name title: model.name
onClicked: pageStack.push(Utils.getPageUrl(model.type), {"itemId": model.id}) onClicked: pageStack.push(Utils.getPageUrl(model.mediaType, model.type), {"itemId": model.id})
} }
} }

View file

@ -0,0 +1,63 @@
/*
Sailfin: a Jellyfin client written using Qt
Copyright (C) 2020 Chris Josten
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
import QtQuick 2.6
import Sailfish.Silica 1.0
import nl.netsoj.chris.Jellyfin 1.0
import "../../components"
import "../.."
/**
* Fallback page for everything that's a video, but we haven't a more specific page for, like
* the FilmPage or EpisodePage.
*/
BaseDetailPage {
SilicaFlickable {
anchors.fill: parent
contentHeight: content.height
Column {
id: content
width: parent.width
spacing: Theme.paddingMedium
PageHeader {
title: itemData.Name
description: qsTr("Run time: %2").arg(Utils.ticksToText(itemData.RunTimeTicks))
}
PlayToolbar {
width: parent.width
imageSource: Utils.itemImageUrl(ApiClient.baseUrl, itemData, "Primary", {"maxWidth": parent.width})
imageAspectRatio: Constants.horizontalVideoAspectRatio
onPlayPressed: pageStack.push(Qt.resolvedUrl("../../pages/VideoPage.qml"),
{"itemId": itemId, "itemData": itemData, "audioTrack": trackSelector.audioTrack,
"subtitleTrack": trackSelector.subtitleTrack })
}
VideoTrackSelector {
id: trackSelector
width: parent.width
tracks: itemData.MediaStreams
}
}
}
}

View file

@ -60,6 +60,9 @@ void ApiModel::load(LoadType type) {
if (!m_sortBy.empty()) { if (!m_sortBy.empty()) {
query.addQueryItem("SortBy", m_sortBy.join(",")); query.addQueryItem("SortBy", m_sortBy.join(","));
} }
if (m_sortOrder != Unspecified) {
query.addQueryItem("SortOrder", m_sortOrder == Ascending ? "Ascending" : "Descending");
}
if (!m_imageTypes.empty()) { if (!m_imageTypes.empty()) {
query.addQueryItem("ImageTypes", m_imageTypes.join(",")); query.addQueryItem("ImageTypes", m_imageTypes.join(","));
} }
@ -208,7 +211,7 @@ void ApiModel::addQueryParameters(QUrlQuery &query) { Q_UNUSED(query)}
void registerModels(const char *URI) { void registerModels(const char *URI) {
qmlRegisterUncreatableType<ApiModel>(URI, 1, 0, "ApiModel", "Is enum and base class"); qmlRegisterUncreatableType<ApiModel>(URI, 1, 0, "ApiModel", "Is enum and base class");
qmlRegisterUncreatableType<SortOrder>(URI, 1, 0, "SortOrder", "Is enum"); qmlRegisterUncreatableType<SortOptions>(URI, 1, 0, "SortOptions", "Is enum");
qmlRegisterType<PublicUserModel>(URI, 1, 0, "PublicUserModel"); qmlRegisterType<PublicUserModel>(URI, 1, 0, "PublicUserModel");
qmlRegisterType<UserViewModel>(URI, 1, 0, "UserViewModel"); qmlRegisterType<UserViewModel>(URI, 1, 0, "UserViewModel");
qmlRegisterType<UserItemModel>(URI, 1, 0, "UserItemModel"); qmlRegisterType<UserItemModel>(URI, 1, 0, "UserItemModel");

View file

@ -31,9 +31,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include "jellyfinapiclient.h" #include "jellyfinapiclient.h"
namespace Jellyfin { namespace Jellyfin {
class SortOrder { class SortOptions : public QObject{
Q_GADGET Q_OBJECT
public: public:
explicit SortOptions (QObject *parent = nullptr) : QObject(parent) {}
enum SortBy { enum SortBy {
Album, Album,
AlbumArtist, AlbumArtist,
@ -54,6 +55,7 @@ public:
Q_ENUM(SortBy) Q_ENUM(SortBy)
}; };
/** /**
* @brief Abstract model for displaying a REST JSON collection. Role names will be based on the fields encountered in the * @brief Abstract model for displaying a REST JSON collection. Role names will be based on the fields encountered in the
* first record. * first record.
@ -90,6 +92,13 @@ public:
}; };
Q_ENUM(ModelStatus) Q_ENUM(ModelStatus)
enum SortOrder {
Unspecified,
Ascending,
Descending
};
Q_ENUM(SortOrder)
/** /**
* @brief Creates a new basemodel * @brief Creates a new basemodel
* @param path The path (relative to the baseUrl of JellyfinApiClient) to make the call to. * @param path The path (relative to the baseUrl of JellyfinApiClient) to make the call to.
@ -130,6 +139,7 @@ public:
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(bool recursive MEMBER m_recursive) Q_PROPERTY(bool recursive MEMBER m_recursive)
Q_PROPERTY(SortOrder sortOrder MEMBER m_sortOrder NOTIFY sortOrderChanged)
// Path properties // Path properties
Q_PROPERTY(QString show MEMBER m_show NOTIFY showChanged) Q_PROPERTY(QString show MEMBER m_show NOTIFY showChanged)
@ -164,6 +174,7 @@ signals:
void limitChanged(int newLimit); void limitChanged(int newLimit);
void parentIdChanged(QString newParentId); void parentIdChanged(QString newParentId);
void sortByChanged(QList<QString> newSortOrder); void sortByChanged(QList<QString> newSortOrder);
void sortOrderChanged(SortOrder newSortOrder);
void showChanged(QString newShow); void showChanged(QString newShow);
void seasonIdChanged(QString newSeasonId); void seasonIdChanged(QString newSeasonId);
void fieldsChanged(QList<QString> newFields); void fieldsChanged(QList<QString> newFields);
@ -214,6 +225,7 @@ protected:
QList<QString> m_fields; QList<QString> m_fields;
QList<QString> m_imageTypes; QList<QString> m_imageTypes;
QList<QString> m_sortBy = {}; QList<QString> m_sortBy = {};
SortOrder m_sortOrder = Unspecified;
bool m_recursive = false; bool m_recursive = false;
QHash<int, QByteArray> m_roles; QHash<int, QByteArray> m_roles;
@ -228,7 +240,7 @@ private:
* @brief Generates roleNames based on the first record in m_array. * @brief Generates roleNames based on the first record in m_array.
*/ */
void generateFields(); void generateFields();
QString sortByToString(SortOrder::SortBy sortBy); QString sortByToString(SortOptions::SortBy sortBy);
}; };
/** /**

View file

@ -93,6 +93,16 @@
<source>Date added</source> <source>Date added</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Ascending</source>
<extracomment>Sort order</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>Descending</source>
<extracomment>Sort order</extracomment>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>CoverPage</name> <name>CoverPage</name>
@ -104,11 +114,15 @@
<context> <context>
<name>EpisodePage</name> <name>EpisodePage</name>
<message> <message>
<source>Episode %1%2 Season %3</source> <source>Episode %1%2 | %3</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Episode %1 Season %2</source> <source>Episode %1 | %2</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Overview</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
@ -118,6 +132,10 @@
<source>Released: %1 Run time: %2</source> <source>Released: %1 Run time: %2</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Overview</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>LegalPage</name> <name>LegalPage</name>
@ -301,6 +319,13 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context>
<name>VideoPage</name>
<message>
<source>Run time: %2</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>VideoTrackSelector</name> <name>VideoTrackSelector</name>
<message> <message>