diff --git a/harbour-sailfin.desktop b/harbour-sailfin.desktop index 5a21920..5a7d614 100644 --- a/harbour-sailfin.desktop +++ b/harbour-sailfin.desktop @@ -3,10 +3,10 @@ Type=Application X-Nemo-Application-Type=silica-qt5 Icon=harbour-sailfin Exec=harbour-sailfin -Name=harbour-sailfin +Name=Sailfin # translation example: # your app name in German locale (de) # # Remember to comment out the following line, if you do not want to use # a different app name in German locale (de). -Name[de]=harbour-sailfin +Name[nl] = Sailfin diff --git a/harbour-sailfin.pro b/harbour-sailfin.pro index e635689..64788fb 100644 --- a/harbour-sailfin.pro +++ b/harbour-sailfin.pro @@ -26,6 +26,7 @@ SOURCES += \ src/serverdiscoverymodel.cpp DISTFILES += \ + qml/Constants.qml \ qml/Utils.js \ qml/components/GlassyBackground.qml \ qml/components/LibraryItemDelegate.qml \ @@ -48,12 +49,12 @@ DISTFILES += \ qml/pages/DetailPage.qml \ qml/pages/LegalPage.qml \ qml/pages/MainPage.qml \ - qml/pages/SecondPages.qml \ + qml/pages/AboutPage.qml \ qml/harbour-sailfin.qml \ qml/pages/VideoPage.qml \ qml/pages/setup/AddServerConnectingPage.qml \ qml/pages/setup/LoginDialog.qml \ - qml/pages/setup/a + qml/qmldir SAILFISHAPP_ICONS = 86x86 108x108 128x128 172x172 diff --git a/icons/108x108/harbour-sailfin.png b/icons/108x108/harbour-sailfin.png index ab10628..609c62b 100644 Binary files a/icons/108x108/harbour-sailfin.png and b/icons/108x108/harbour-sailfin.png differ diff --git a/icons/128x128/harbour-sailfin.png b/icons/128x128/harbour-sailfin.png index 54375c5..8965743 100644 Binary files a/icons/128x128/harbour-sailfin.png and b/icons/128x128/harbour-sailfin.png differ diff --git a/icons/172x172/harbour-sailfin.png b/icons/172x172/harbour-sailfin.png index 36eee58..1587486 100644 Binary files a/icons/172x172/harbour-sailfin.png and b/icons/172x172/harbour-sailfin.png differ diff --git a/icons/86x86/harbour-sailfin.png b/icons/86x86/harbour-sailfin.png index ad316d6..e650b09 100644 Binary files a/icons/86x86/harbour-sailfin.png and b/icons/86x86/harbour-sailfin.png differ diff --git a/qml/Constants.qml b/qml/Constants.qml new file mode 100644 index 0000000..96abbd7 --- /dev/null +++ b/qml/Constants.qml @@ -0,0 +1,10 @@ +pragma Singleton +import QtQuick 2.6 +import Sailfish.Silica 1.0 + +QtObject { + readonly property real libraryDelegateWidth: Screen.width / 3 + readonly property real libraryDelegateHeight: Screen.width / 3 + + readonly property real libraryDelegatePosterHeight: Screen.width / 2 +} diff --git a/qml/Utils.js b/qml/Utils.js index 39dd838..c5b40a5 100644 --- a/qml/Utils.js +++ b/qml/Utils.js @@ -17,3 +17,20 @@ function timeToText(time) { function ticksToText(ticks) { return timeToText(ticks / 10000); } + +function itemImageUrl(baseUrl, item, type, options) { + if (!item.ImageTags[type]) { return "" } + return itemModelImageUrl(baseUrl, item.Id, item.ImageTags[type], type, options) + } + +function itemModelImageUrl(baseUrl, itemId, tag, type, options) { + if (tag == undefined) return "" + var extraQuery = ""; + for (var prop in options) { + if (options.hasOwnProperty(prop)) { + extraQuery += "&" + prop + "=" + options[prop]; + } + } + return baseUrl + "/Items/" + itemId + "/Images/" + type + "?tag=" + tag + extraQuery + +} diff --git a/qml/components/LibraryItemDelegate.qml b/qml/components/LibraryItemDelegate.qml index efa99d4..55a2299 100644 --- a/qml/components/LibraryItemDelegate.qml +++ b/qml/components/LibraryItemDelegate.qml @@ -1,6 +1,8 @@ import QtQuick 2.6 import Sailfish.Silica 1.0 +import ".." + /** * Delegate for displaying an item in the library. */ @@ -9,11 +11,12 @@ BackgroundItem { property alias poster: posterImage.source property alias title: titleText.text property bool landscape: false - width: Screen.width / 3 - height: landscape ? width / 4 * 3 : width / 2 * 3 + width: Constants.libraryDelegateWidth + height: landscape ? Constants.libraryDelegateHeight : Constants.libraryDelegatePosterHeight RemoteImage { id: posterImage + clip: true anchors { left: parent.left top: parent.top @@ -23,11 +26,11 @@ BackgroundItem { fillMode: Image.PreserveAspectCrop } - Rectangle { + /*Rectangle { anchors.fill: posterImage color: Theme.rgba(Theme.highlightBackgroundColor, Theme.highlightBackgroundOpacity) visible: root.highlighted - } + }*/ Rectangle { anchors { diff --git a/qml/components/MoreSection.qml b/qml/components/MoreSection.qml index 68a91b2..8d3f340 100644 --- a/qml/components/MoreSection.qml +++ b/qml/components/MoreSection.qml @@ -10,17 +10,34 @@ Item { property alias text: label.text property alias textAlignment: label.horizontalAlignment property bool busy: false + property bool clickable: true property int depth: 0 - readonly property color _color: enabled ? highlighted ? Theme.highlightColor : Theme.primaryColor : Theme.secondaryColor + readonly property color _color: { + if (!clickable) { + Theme.primaryColor + } else if (enabled) { + if (highlighted) { + Theme.highlightColor + } else { + Theme.primaryColor + } + } else { + Theme.secondaryColor + } + } default property alias content: container.data + signal headerClicked() + implicitHeight: backgroundItem.height + container.height width: parent.width BackgroundItem { id: backgroundItem + enabled: parent.enabled && parent.clickable width: parent.width height: Theme.itemSizeMedium + onClicked: root.headerClicked() Rectangle { anchors.fill: parent @@ -51,7 +68,7 @@ Item { verticalCenter: parent.verticalCenter rightMargin: Theme.horizontalPageMargin } - visible: root.enabled && !root.busy + visible: root.enabled && root.clickable && !root.busy source: "image://theme/icon-m-right?" + _color } diff --git a/qml/components/RemoteImage.qml b/qml/components/RemoteImage.qml index 255e212..2cd9bb4 100644 --- a/qml/components/RemoteImage.qml +++ b/qml/components/RemoteImage.qml @@ -4,7 +4,7 @@ import Sailfish.Silica 1.0 /** * An image for "remote" images (loaded over e.g. http), with a spinner and a fallback image */ -Image { +HighlightImage { property string fallbackImage property bool usingFallbackImage @@ -23,7 +23,7 @@ Image { visible: parent.status == Image.Error || parent.status == Image.Null } - Image { + HighlightImage { id: fallbackImageItem anchors.centerIn: parent visible: parent.status == Image.Error || parent.status == Image.Null diff --git a/qml/components/itemdetails/EpisodeDetails.qml b/qml/components/itemdetails/EpisodeDetails.qml index 9c36e13..a5cf7cb 100644 --- a/qml/components/itemdetails/EpisodeDetails.qml +++ b/qml/components/itemdetails/EpisodeDetails.qml @@ -1,5 +1,35 @@ -import QtQuick 2.0 +import QtQuick 2.6 +import Sailfish.Silica 1.0 +import nl.netsoj.chris.Jellyfin 1.0 -Item { +import ".." +import "../../Utils.js" as Utils +Column { + property var itemData + spacing: Theme.paddingMedium + + PlayToolbar { + 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 + } + + PlainLabel { + id: tinyDetails + text: qsTr("Released: %1 — Run time: %2").arg(itemData.ProductionYear).arg(Utils.ticksToText(itemData.RunTimeTicks)) + } + + PlainLabel { + id: overviewText + text: itemData.Overview + font.pixelSize: Theme.fontSizeSmall + color: Theme.secondaryHighlightColor + } } diff --git a/qml/components/itemdetails/FilmDetails.qml b/qml/components/itemdetails/FilmDetails.qml index d80a7f4..b750731 100644 --- a/qml/components/itemdetails/FilmDetails.qml +++ b/qml/components/itemdetails/FilmDetails.qml @@ -20,10 +20,6 @@ Column { tracks: itemData.MediaStreams } - PlainLabel { - text: "sub: %1 dub: %2".arg(trackSelector.subtitleTrack).arg(trackSelector.audioTrack) - } - PlainLabel { id: tinyDetails text: qsTr("Released: %1 — Run time: %2").arg(itemData.ProductionYear).arg(Utils.ticksToText(itemData.RunTimeTicks)) @@ -35,7 +31,4 @@ Column { font.pixelSize: Theme.fontSizeSmall color: Theme.secondaryHighlightColor } - - - } diff --git a/qml/components/itemdetails/PlayToolbar.qml b/qml/components/itemdetails/PlayToolbar.qml index d13b3f9..243c9fe 100644 --- a/qml/components/itemdetails/PlayToolbar.qml +++ b/qml/components/itemdetails/PlayToolbar.qml @@ -20,4 +20,5 @@ Row { icon.source: "image://theme/icon-l-play" onPressed: playPressed() } + } diff --git a/qml/components/itemdetails/SeasonDetails.qml b/qml/components/itemdetails/SeasonDetails.qml index 9c36e13..cff1b49 100644 --- a/qml/components/itemdetails/SeasonDetails.qml +++ b/qml/components/itemdetails/SeasonDetails.qml @@ -1,5 +1,80 @@ -import QtQuick 2.0 +import QtQuick 2.6 +import Sailfish.Silica 1.0 +import nl.netsoj.chris.Jellyfin 1.0 -Item { +import "../../Utils.js" as Utils +import "../.." +import ".." +Column { + property var itemData + + ShowEpisodesModel { + id: episodeModel + apiClient: ApiClient + show: itemData.SeriesId + seasonId: itemData.Id + fields: ["Overview"] + } + + ColumnView { + model: episodeModel + itemHeight: Constants.libraryDelegateHeight + delegate: BackgroundItem { + height: Constants.libraryDelegateHeight + RemoteImage { + id: episodeImage + anchors { + top: parent.top + left: parent.left + leftMargin: Theme.horizontalPageMargin + bottom: parent.bottom + } + width: Constants.libraryDelegateWidth + height: Constants.libraryDelegateHeight + source: Utils.itemModelImageUrl(ApiClient.baseUrl, model.id, model.imageTags["Primary"], "Primary", {"maxHeight": height}) + fillMode: Image.PreserveAspectCrop + clip: true + } + + Label { + id: episodeTitle + anchors { + left: episodeImage.right + leftMargin: Theme.paddingLarge + top: parent.top + right: parent.right + rightMargin: Theme.horizontalPageMargin + } + text: model.name + truncationMode: TruncationMode.Fade + horizontalAlignment: Text.AlignLeft + } + + Label { + id: episodeOverview + anchors { + left: episodeImage.right + leftMargin: Theme.paddingLarge + right: parent.right + rightMargin: Theme.horizontalPageMargin + top: episodeTitle.bottom + bottom: parent.bottom + } + color: highlighted ? Theme.secondaryHighlightColor: Theme.secondaryColor + font.pixelSize: Theme.fontSizeExtraSmall + //: No overview/summary text of an episode available + text: model.overview || qsTr("No overview available") + wrapMode: Text.WordWrap + elide: Text.ElideRight + } + onClicked: pageStack.push(Qt.resolvedUrl("../../pages/DetailPage.qml"), {"itemId": model.id}) + } + } + onItemDataChanged: { + console.log(JSON.stringify(itemData)) + episodeModel.show = itemData.SeriesId + episodeModel.seasonId = itemData.Id + episodeModel.reload() + } } diff --git a/qml/components/itemdetails/SeriesDetails.qml b/qml/components/itemdetails/SeriesDetails.qml index 9c36e13..7bfdbf1 100644 --- a/qml/components/itemdetails/SeriesDetails.qml +++ b/qml/components/itemdetails/SeriesDetails.qml @@ -1,5 +1,49 @@ -import QtQuick 2.0 +import QtQuick 2.6 +import Sailfish.Silica 1.0 +import nl.netsoj.chris.Jellyfin 1.0 -Item { +import "../" +import "../../Utils.js" as Utils +Column { + property var itemData + + PlainLabel { + id: overviewText + text: itemData.Overview + font.pixelSize: Theme.fontSizeSmall + color: Theme.secondaryHighlightColor + } + + SectionHeader { + //: Seasons of a (TV) show + text: qsTr("Seasons") + } + + ShowSeasonsModel { + id: showSeasonsModel + apiClient: ApiClient + show: itemData.Id + } + + SilicaListView { + model: showSeasonsModel + clip: true + width: parent.width + height: Screen.width / 2 + orientation: ListView.Horizontal + spacing: Theme.paddingLarge + leftMargin: Theme.horizontalPageMargin + rightMargin: Theme.horizontalPageMargin + delegate: LibraryItemDelegate { + poster: Utils.itemModelImageUrl(ApiClient.baseUrl, model.id, model.imageTags["Primary"], "Primary", {"maxHeight": height}) + title: model.name + onClicked: pageStack.push(Qt.resolvedUrl("../../pages/DetailPage.qml"), {"itemId": model.id}) + } + } + + onItemDataChanged: { + showSeasonsModel.show = itemData.Id + showSeasonsModel.reload() + } } diff --git a/qml/components/itemdetails/UnsupportedDetails.qml b/qml/components/itemdetails/UnsupportedDetails.qml index 3754d3f..65137fe 100644 --- a/qml/components/itemdetails/UnsupportedDetails.qml +++ b/qml/components/itemdetails/UnsupportedDetails.qml @@ -2,7 +2,9 @@ import QtQuick 2.6 import Sailfish.Silica 1.0 ViewPlaceholder { + property var itemData + enabled: true - text: qsTr("Item type unsupported") + text: qsTr("Item type (%1) unsupported").arg(itemData.Type) hintText: qsTr("This is still an alpha version :)") } diff --git a/qml/cover/CoverPage.qml b/qml/cover/CoverPage.qml index fc562d5..220f649 100644 --- a/qml/cover/CoverPage.qml +++ b/qml/cover/CoverPage.qml @@ -1,22 +1,140 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 +import nl.netsoj.chris.Jellyfin 1.0 + +import "../components" +import "../Utils.js" as Utils + CoverBackground { + id: cover Label { id: label anchors.centerIn: parent - text: qsTr("My Cover") + text: qsTr("Sailfin") + } + property int rowCount: 8 + + UserItemModel { + id: randomItems1 + apiClient: ApiClient + limit: cover.rowCount + imageTypes: ["Primary"] + sortBy: ["IsFavoriteOrLiked", "Random"] + recursive: true + Component.onCompleted: reload() } - CoverActionList { - id: coverAction + UserItemModel { + id: randomItems2 + apiClient: ApiClient + limit: cover.rowCount + imageTypes: ["Primary"] + sortBy: ["IsFavoriteOrLiked", "Random"] + recursive: true + Component.onCompleted: reload() + } - CoverAction { - iconSource: "image://theme/icon-cover-next" + Row { + id: row1 + property bool movingRight: true + property int moveCount: 0 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + transform: [ + Translate { + x: -row1.height// + (row1.width - row1.height) / 2; + }, + Translate { + id: row1Translate; + Behavior on x { NumberAnimation { duration: 500; easing.type: Easing.InOutQuad }} + } + ] + height: parent.height / 2 + width: parent.width + Repeater { + model: randomItems1 + RemoteImage { + clip: true + height: row1.height + width: height + source: Utils.itemModelImageUrl(ApiClient.baseUrl, model.id, model.imageTags["Primary"], "Primary", {"maxHeight": row1.height}) + fillMode: Image.PreserveAspectCrop + Component.onCompleted: console.log(JSON.stringify(model.imageTags)) + } } - CoverAction { - iconSource: "image://theme/icon-cover-pause" + function move() { + if (movingRight) { + row1Translate.x -= row1.height + moveCount++; + } else { + row1Translate.x += row1.height + moveCount--; + } + if (moveCount == 0) movingRight = true; + if (moveCount == rowCount - 3) movingRight = false; } } + + Row { + id: row2 + property bool movingRight: false + property int moveCount: rowCount - 3 + anchors.bottom: parent.bottom + + anchors.left: parent.left + anchors.right: parent.right + transform: [ + Translate { + x: -row2.height * (rowCount - 2) + (row2.width - row2.height); + }, + Translate { + id: row2Translate; + Behavior on x { NumberAnimation { duration: 500; easing.type: Easing.InOutQuad }} + } + ] + height: parent.height / 2 + width: parent.width + Repeater { + model: randomItems2 + RemoteImage { + clip: true + height: row2.height + width: height + source: Utils.itemModelImageUrl(ApiClient.baseUrl, model.id, model.imageTags["Primary"], "Primary", {"maxHeight": row1.height}) + fillMode: Image.PreserveAspectCrop + Component.onCompleted: console.log(JSON.stringify(model.imageTags)) + } + } + + function move() { + if (movingRight) { + row2Translate.x -= row1.height + moveCount++; + } else { + row2Translate.x += row1.height + moveCount--; + } + if (moveCount == 0) movingRight = true; + if (moveCount == rowCount - 3) movingRight = false; + } + } + + Timer { + property bool odd: false + running: true + interval: 5000 + repeat: true + onTriggered: { + if (odd) { + row1.move() + } else { + row2.move() + } + odd = !odd + } + } + } diff --git a/qml/icon.png b/qml/icon.png new file mode 100644 index 0000000..6be8fda Binary files /dev/null and b/qml/icon.png differ diff --git a/qml/licenses/lgpl-2.1.html b/qml/licenses/lgpl-2.1.html new file mode 100644 index 0000000..dce7fa5 --- /dev/null +++ b/qml/licenses/lgpl-2.1.html @@ -0,0 +1,616 @@ + + + + GNU Lesser General Public License v2.1 - GNU Project - Free Software Foundation (FSF) + + + +

GNU LESSER GENERAL PUBLIC LICENSE

+

+Version 2.1, February 1999 +

+ +
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ + +

Preamble

+ +

+ The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. +

+

+ This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. +

+

+ When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. +

+

+ To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. +

+

+ For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. +

+

+ We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. +

+

+ To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. +

+

+ Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. +

+

+ Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. +

+

+ When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. +

+

+ We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. +

+

+ For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. +

+

+ In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. +

+

+ Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. +

+

+ The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. +

+ +

TERMS AND CONDITIONS FOR COPYING, +DISTRIBUTION AND MODIFICATION

+ + +

+0. +This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". +

+

+ A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. +

+

+ The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) +

+

+ "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. +

+

+ Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. +

+

+1. +You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. +

+

+ You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. +

+

+2. +You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: +

+ + + +

+These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, and can be +reasonably considered independent and separate works in themselves, then +this License, and its terms, do not apply to those sections when you +distribute them as separate works. But when you distribute the same +sections as part of a whole which is a work based on the Library, the +distribution of the whole must be on the terms of this License, whose +permissions for other licensees extend to the entire whole, and thus to +each and every part regardless of who wrote it. +

+

+Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works +based on the Library. +

+

+In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of +this License. +

+

+3. +You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. +

+

+ Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. +

+

+ This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. +

+

+4. +You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. +

+

+ If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. +

+

+5. +A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. +

+

+ However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. +

+

+ When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. +

+

+ If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) +

+

+ Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. +

+

+6. +As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. +

+

+ You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: +

+ + + +

+ For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. +

+

+ It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. +

+

+7. You may place library facilities that are a work +based on the Library side-by-side in a single library together with +other library facilities not covered by this License, and distribute +such a combined library, provided that the separate distribution of +the work based on the Library and of the other library facilities is +otherwise permitted, and provided that you do these two things: +

+ + + +

+8. You may not copy, modify, sublicense, link with, +or distribute the Library except as expressly provided under this +License. Any attempt otherwise to copy, modify, sublicense, link +with, or distribute the Library is void, and will automatically +terminate your rights under this License. However, parties who have +received copies, or rights, from you under this License will not have +their licenses terminated so long as such parties remain in full +compliance. +

+

+9. +You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. +

+

+10. +Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. +

+

+11. +If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. +

+

+If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. +

+

+It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. +

+

+This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. +

+

+12. +If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. +

+

+13. +The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. +

+

+Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. +

+

+14. +If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. +

+

+NO WARRANTY +

+

+15. +BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. +

+

+16. +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. +

+ +

END OF TERMS AND CONDITIONS

+ +

How to Apply These Terms to Your New +Libraries

+

+ If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). +

+

+ To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. +

+ +
one line to give the library's name and an idea of what it does.
+Copyright (C) year  name of author
+
+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
+
+ +

+Also add information on how to contact you by electronic and paper mail. +

+

+You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: +

+ +
Yoyodyne, Inc., hereby disclaims all copyright interest in
+the library `Frob' (a library for tweaking knobs) written
+by James Random Hacker.
+
+signature of Ty Coon, 1 April 1990
+Ty Coon, President of Vice
+
+ +

+That's all there is to it!

+ + + diff --git a/qml/pages/AboutPage.qml b/qml/pages/AboutPage.qml new file mode 100644 index 0000000..f2c2a35 --- /dev/null +++ b/qml/pages/AboutPage.qml @@ -0,0 +1,97 @@ +import QtQuick 2.6 +import Sailfish.Silica 1.0 + +import "../components" + +Page { + id: page + + // The effective value will be restricted by ApplicationWindow.allowedOrientations + allowedOrientations: Orientation.All + + SilicaFlickable { + anchors.fill: parent + contentHeight: content.height + Column { + id: content + width: parent.width + PageHeader { + title: qsTr("About Sailfin") + } + Image { + anchors.horizontalCenter: parent.horizontalCenter + source: Qt.resolvedUrl("../icon.png") + } + + Item { width: 1; height: Theme.paddingLarge } + + Label { + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: Theme.horizontalPageMargin + anchors.rightMargin: Theme.horizontalPageMargin + wrapMode: Text.WordWrap + text: "

Sailfin version 0.1.0
" + + "Copyright © Chris Josten 2020

" + + "

Sailfin is Free Software licensed under the LGPL-v2.1 or later, at your choice. " + + "Parts of the code of Sailfin are from other libraries. View their licenses here.

" + textFormat: Text.StyledText + color: Theme.secondaryHighlightColor + linkColor: Theme.primaryColor + onLinkActivated: { + switch(link) { + case "lgpl": + pageStack.push(licensePage) + break; + case "3rdparty": + pageStack.push(Qt.resolvedUrl("LegalPage.qml")) + break; + } + } + } + + } + VerticalScrollDecorator {} + } + + Component { + id: licensePage + + Page { + allowedOrientations: Orientation.All + SilicaFlickable { + anchors.fill: parent + contentHeight: content.height + PullDownMenu { + MenuItem { + text: qsTr("Open externally") + onClicked: Qt.openUrlExternally(Qt.resolvedUrl("../licenses/lgpl-2.1.html")) + } + } + VerticalScrollDecorator {} + Column { + id: content + width: parent.width + PageHeader { + title: qsTr("LGPL 2.1 License") + } + PlainLabel { + id: licenseLabel + + Component.onCompleted: { + var xhr = new XMLHttpRequest; + xhr.open("GET", Qt.resolvedUrl("../licenses/lgpl-2.1.html")); // set Method and File + xhr.onreadystatechange = function () { + if (xhr.readyState === XMLHttpRequest.DONE){ // if request_status == DONE + licenseLabel.text = xhr.responseText; + } + } + xhr.send(); // begin the request + } + } + } + } + + } + } +} diff --git a/qml/pages/DetailPage.qml b/qml/pages/DetailPage.qml index f649e8b..b66167f 100644 --- a/qml/pages/DetailPage.qml +++ b/qml/pages/DetailPage.qml @@ -30,7 +30,7 @@ Page { if (_backdropImages && _backdropImages.length > 0) { var rand = Math.floor(Math.random() * (_backdropImages.length - 0.001)) console.log("Random: ", rand) - backdrop.source = ApiClient.baseUrl + "/Items/" + itemId + "/Images/Backdrop/" + rand + "?tag=" + _backdropImages[rand] + backdrop.source = ApiClient.baseUrl + "/Items/" + itemId + "/Images/Backdrop/" + rand + "?tag=" + _backdropImages[rand] + "&maxHeight" + height } else if (_parentBackdropImages && _parentBackdropImages.length > 0) { console.log(parentId) backdrop.source = ApiClient.baseUrl + "/Items/" + itemData.ParentBackdropItemId + "/Images/Backdrop/0?tag=" + _parentBackdropImages[0] @@ -89,6 +89,12 @@ Page { switch (itemData.Type){ case "Movie": return Qt.resolvedUrl("../components/itemdetails/FilmDetails.qml") + case "Series": + return Qt.resolvedUrl("../components/itemdetails/SeriesDetails.qml") + case "Season": + return Qt.resolvedUrl("../components/itemdetails/SeasonDetails.qml") + case "Episode": + return Qt.resolvedUrl("../components/itemdetails/EpisodeDetails.qml") default: return Qt.resolvedUrl("../components/itemdetails/UnsupportedDetails.qml") } @@ -117,7 +123,10 @@ Page { onStatusChanged: { if (status == PageStatus.Deactivating) { backdrop.clear() - appWindow.itemData = ({}) + //appWindow.itemData = ({}) + } + if (status == PageStatus.Active && itemData) { + appWindow.itemData = itemData } } @@ -128,6 +137,7 @@ Page { //console.log(JSON.stringify(result)) pageRoot.itemData = result pageRoot._loading = false + if (status == PageStatus.Active) appWindow.itemData = result } } diff --git a/qml/pages/MainPage.qml b/qml/pages/MainPage.qml index c04a7f8..7bf2901 100644 --- a/qml/pages/MainPage.qml +++ b/qml/pages/MainPage.qml @@ -4,6 +4,8 @@ import Sailfish.Silica 1.0 import nl.netsoj.chris.Jellyfin 1.0 import "../components" +import "../" +import "../Utils.js" as Utils /** * Main page, which simply shows some content of every library, as well as next items. @@ -21,11 +23,7 @@ Page { PullDownMenu { MenuItem { text: qsTr("About") - onClicked: pageStack.push(Qt.resolvedUrl("LegalPage.qml")) - } - MenuItem { - text: qsTr("Settings") - onClicked: pageStack.push(Qt.resolvedUrl("SecondPage.qml")) + onClicked: pageStack.push(Qt.resolvedUrl("AboutPage.qml")) } } @@ -45,11 +43,12 @@ Page { } MoreSection { - text: "Kijken hervatten" - enabled: false + text: qsTr("Resume watching") + clickable: false } MoreSection { - text: "Volgende" + text: qsTr("Next up") + clickable: false } UserViewModel { @@ -61,29 +60,46 @@ Page { MoreSection { text: model.name busy: userItemModel.status != ApiModel.Ready + property string collectionType: model.collectionType + + onHeaderClicked: pageStack.push(Qt.resolvedUrl("DetailPage.qml"), {"itemId": model.id}) SilicaListView { clip: true - height: count > 0 ? Screen.width / 4 : 0 + height: { + if (count > 0) { + console.log(collectionType) + if (["tvshows", "movies"].indexOf(collectionType) == -1) { + Constants.libraryDelegateHeight + } else { + Constants.libraryDelegatePosterHeight + } + } else { + 0 + } + } Behavior on height { NumberAnimation { duration: 300 } } width: parent.width model: userItemModel orientation: ListView.Horizontal + leftMargin: Theme.horizontalPageMargin + rightMargin: Theme.horizontalPageMargin + spacing: Theme.paddingLarge delegate: LibraryItemDelegate { property string id: model.id title: model.name - poster: model.imageTags["Primary"] ? ApiClient.baseUrl + "/Items/" + model.id + poster: Utils.itemModelImageUrl(ApiClient.baseUrl, model.id, model.imageTags["Primary"], "Primary", {"maxHeight": height}) + /*model.imageTags["Primary"] ? ApiClient.baseUrl + "/Items/" + model.id + "/Images/Primary?maxHeight=" + height + "&tag=" + model.imageTags["Primary"] - : "" - landscape: true + : ""*/ + landscape: ["Series", "Movie"].indexOf(model.type) == -1 onClicked: { pageStack.push(Qt.resolvedUrl("DetailPage.qml"), {"itemId": model.id}) } } - HorizontalScrollDecorator {} UserItemLatestModel { id: userItemModel apiClient: ApiClient @@ -104,6 +120,12 @@ Page { } } + onStatusChanged: { + if (status == PageStatus.Active) { + appWindow.itemData = null + } + } + Connections { target: ApiClient onAuthenticatedChanged: { diff --git a/qml/pages/SecondPage.qml b/qml/pages/SecondPage.qml deleted file mode 100644 index 6dbadf4..0000000 --- a/qml/pages/SecondPage.qml +++ /dev/null @@ -1,30 +0,0 @@ -import QtQuick 2.0 -import Sailfish.Silica 1.0 - -Page { - id: page - - // The effective value will be restricted by ApplicationWindow.allowedOrientations - allowedOrientations: Orientation.All - - SilicaListView { - id: listView - model: 20 - anchors.fill: parent - header: PageHeader { - title: qsTr("Nested Page") - } - delegate: BackgroundItem { - id: delegate - - Label { - x: Theme.horizontalPageMargin - text: qsTr("Item") + " " + index - anchors.verticalCenter: parent.verticalCenter - color: delegate.highlighted ? Theme.highlightColor : Theme.primaryColor - } - onClicked: console.log("Clicked " + index) - } - VerticalScrollDecorator {} - } -} diff --git a/qml/pages/setup/a b/qml/pages/setup/a deleted file mode 100644 index e69de29..0000000 diff --git a/qml/qmldir b/qml/qmldir new file mode 100644 index 0000000..616ac20 --- /dev/null +++ b/qml/qmldir @@ -0,0 +1 @@ +singleton Constants 1.0 Constants.qml diff --git a/rpm/harbour-sailfin.yaml b/rpm/harbour-sailfin.yaml index e03b49d..a11a913 100644 --- a/rpm/harbour-sailfin.yaml +++ b/rpm/harbour-sailfin.yaml @@ -6,7 +6,7 @@ Release: 1 # https://github.com/mer-tools/spectacle/blob/master/data/GROUPS Group: Qt/Qt URL: https://chris.netsoj.nl/projects/harbour-sailfin -License: LICENSE +License: LGPL-2.0-or-later # This must be generated before uploading a package to a remote build service. # Usually this line does not need to be modified. Sources: diff --git a/src/jellyfinapimodel.cpp b/src/jellyfinapimodel.cpp index 183b9a2..aaebb65 100644 --- a/src/jellyfinapimodel.cpp +++ b/src/jellyfinapimodel.cpp @@ -1,10 +1,11 @@ #include "jellyfinapimodel.h" namespace Jellyfin { -ApiModel::ApiModel(QString path, QString subfield, QObject *parent) +ApiModel::ApiModel(QString path, QString subfield, bool addUserId, QObject *parent) : QAbstractListModel (parent), m_path(path), - m_subfield(subfield) { + m_subfield(subfield), + m_addUserId(addUserId){ } void ApiModel::reload() { @@ -13,9 +14,11 @@ void ApiModel::reload() { qWarning() << "Please set the apiClient property before (re)loading"; return; } - if (m_path.contains(":user")) { - qDebug() << "Path contains :user, replacing with" << m_apiClient->userId(); - m_path = m_path.replace(":user", m_apiClient->userId()); + if (m_path.contains("{{user}}")) { + m_path = m_path.replace("{{user}}", m_apiClient->userId()); + } + if (m_path.contains("{{show}}") && !m_show.isEmpty()) { + m_path = m_path.replace("{{show}}", m_show); } QUrlQuery query; if (m_limit >= 0) { @@ -24,8 +27,23 @@ void ApiModel::reload() { if (!m_parentId.isEmpty()) { query.addQueryItem("ParentId", m_parentId); } - if (m_sortBy.empty()) { - query.addQueryItem("SortBy", enumListToString(m_sortBy)); + if (!m_sortBy.empty()) { + query.addQueryItem("SortBy", m_sortBy.join(",")); + } + if (!m_imageTypes.empty()) { + query.addQueryItem("ImageTypes", m_imageTypes.join(",")); + } + if (!m_fields.empty()) { + query.addQueryItem("Fields", m_fields.join(",")); + } + if (!m_seasonId.isEmpty()) { + query.addQueryItem("seasonId", m_seasonId); + } + if (m_addUserId) { + query.addQueryItem("userId", m_apiClient->userId()); + } + if (m_recursive) { + query.addQueryItem("Recursive", "true"); } QNetworkReply *rep = m_apiClient->get(m_path, query); connect(rep, &QNetworkReply::finished, this, [this, rep]() { @@ -112,5 +130,7 @@ void registerModels(const char *URI) { qmlRegisterType(URI, 1, 0, "UserViewModel"); qmlRegisterType(URI, 1, 0, "UserItemModel"); qmlRegisterType(URI, 1, 0, "UserItemLatestModel"); + qmlRegisterType(URI, 1, 0, "ShowSeasonsModel"); + qmlRegisterType(URI, 1, 0, "ShowEpisodesModel"); } } diff --git a/src/jellyfinapimodel.h b/src/jellyfinapimodel.h index 1ec0f5e..157cd14 100644 --- a/src/jellyfinapimodel.h +++ b/src/jellyfinapimodel.h @@ -70,12 +70,6 @@ public: }; Q_ENUM(ModelStatus) - enum MediaType { - MediaUnspecified, - Series - }; - Q_DECLARE_FLAGS(MediaTypes, MediaType) - Q_FLAG(MediaTypes) /** * @brief Creates a new basemodel * @param path The path (relative to the baseUrl of JellyfinApiClient) to make the call to. @@ -99,13 +93,21 @@ public: * @endcode * Subfield should be set to "data" in this example. */ - explicit ApiModel(QString path, QString subfield, QObject *parent = nullptr); + explicit ApiModel(QString path, QString subfield, bool passUserId = false, QObject *parent = nullptr); Q_PROPERTY(ApiClient *apiClient MEMBER m_apiClient) Q_PROPERTY(ModelStatus status READ status NOTIFY statusChanged) + + // Query properties Q_PROPERTY(int limit MEMBER m_limit NOTIFY limitChanged) Q_PROPERTY(QString parentId MEMBER m_parentId NOTIFY parentIdChanged) - Q_PROPERTY(QList sortBy MEMBER m_sortBy NOTIFY sortByChanged) - //Q_PROPERTY(MediaTypes includeTypes MEMBER m_includeTypes NOTIFY includeTypesChanged) + Q_PROPERTY(QList sortBy MEMBER m_sortBy NOTIFY sortByChanged) + Q_PROPERTY(QList fields MEMBER m_fields NOTIFY fieldsChanged) + Q_PROPERTY(QString seasonId MEMBER m_seasonId NOTIFY seasonIdChanged) + Q_PROPERTY(QList imageTypes MEMBER m_imageTypes NOTIFY imageTypesChanged) + Q_PROPERTY(bool recursive MEMBER m_recursive) + + // Path properties + Q_PROPERTY(QString show MEMBER m_show NOTIFY showChanged) int rowCount(const QModelIndex &index) const override { if (!index.isValid()) return m_array.size(); @@ -132,8 +134,11 @@ signals: void statusChanged(ModelStatus newStatus); void limitChanged(int newLimit); void parentIdChanged(QString newParentId); - void sortByChanged(SortOrder::SortBy newSortOrder); - void includeTypesChanged(MediaTypes newTypes); + void sortByChanged(QList newSortOrder); + void showChanged(QString newShow); + void seasonIdChanged(QString newSeasonId); + void fieldsChanged(QList newFields); + void imageTypesChanged(QList newImageTypes); public slots: /** @@ -148,11 +153,18 @@ protected: QString m_subfield; QJsonArray m_array; + // Path properties + QString m_show; + // Query properties int m_limit = -1; + bool m_addUserId = false; QString m_parentId; - QList m_sortBy = {}; - MediaTypes m_includeTypes = MediaUnspecified; + QString m_seasonId; + QList m_fields; + QList m_imageTypes; + QList m_sortBy = {}; + bool m_recursive; QHash m_roles; //QHash m_reverseRoles; @@ -168,7 +180,6 @@ private: */ void generateFields(); QString sortByToString(SortOrder::SortBy sortBy); - QString mediaTypeToString(MediaType mediaType); }; /** @@ -177,30 +188,40 @@ private: class PublicUserModel : public ApiModel { public: explicit PublicUserModel (QObject *parent = nullptr) - : ApiModel ("/users/public", "", parent) { } + : ApiModel ("/users/public", "", false, parent) { } }; class UserViewModel : public ApiModel { public: explicit UserViewModel (QObject *parent = nullptr) - : ApiModel ("/Users/:user/Views", "Items", parent) {} + : ApiModel ("/Users/{{user}}/Views", "Items", false, parent) {} }; class UserItemModel : public ApiModel { public: explicit UserItemModel (QObject *parent = nullptr) - : ApiModel ("/Users/:user/Items", "Items", parent) {} + : ApiModel ("/Users/{{user}}/Items", "Items", false, parent) {} }; class UserItemLatestModel : public ApiModel { public: explicit UserItemLatestModel (QObject *parent = nullptr) - : ApiModel ("/Users/:user/Items/Latest", "", parent) {} + : ApiModel ("/Users/{{user}}/Items/Latest", "", false, parent) {} +}; + +class ShowSeasonsModel : public ApiModel { +public: + explicit ShowSeasonsModel (QObject *parent = nullptr) + : ApiModel ("/Shows/{{show}}/Seasons", "Items", true, parent) {} +}; + +class ShowEpisodesModel : public ApiModel { +public: + explicit ShowEpisodesModel (QObject *parent = nullptr) + : ApiModel ("/Shows/{{show}}/Episodes", "Items", true, parent) {} }; void registerModels(const char *URI); -Q_DECLARE_OPERATORS_FOR_FLAGS(ApiModel::MediaTypes) - } #endif //JELLYFIN_API_MODEL diff --git a/translations/harbour-sailfin.ts b/translations/harbour-sailfin.ts index 08fcd9f..7800e44 100644 --- a/translations/harbour-sailfin.ts +++ b/translations/harbour-sailfin.ts @@ -1,6 +1,21 @@ + + AboutPage + + About Sailfin + + + + Open externally + + + + LGPL 2.1 License + + + AddServerConnectingPage @@ -46,7 +61,14 @@ CoverPage - My Cover + Sailfin + + + + + EpisodeDetails + + Released: %1 — Run time: %2 @@ -106,33 +128,42 @@ MainPage - Settings + About - About + Resume watching + + + + Next up - SecondPage + SeasonDetails - Nested Page + No overview available + No overview/summary text of an episode available + + + SeriesDetails - Item + Seasons + Seasons of a (TV) show UnsupportedDetails - Item type unsupported + This is still an alpha version :) - This is still an alpha version :) + Item type (%1) unsupported