mirror of
https://github.com/HenkKalkwater/harbour-sailfin.git
synced 2024-11-22 09:15:18 +00:00
Implemented collections + misc UI improvements
* There is a basic collection page, allowing the user to browse through collections. It has a sort function, that sort of works * Item cards now show a bar indicating play time * Item cards now have a black/white (depending on theme) shim, improving readability. * The resume watching section now actually loads items
This commit is contained in:
parent
5ea17070fe
commit
5d395ad7b6
|
@ -37,6 +37,7 @@ DISTFILES += \
|
||||||
qml/components/MoreSection.qml \
|
qml/components/MoreSection.qml \
|
||||||
qml/components/PlainLabel.qml \
|
qml/components/PlainLabel.qml \
|
||||||
qml/components/RemoteImage.qml \
|
qml/components/RemoteImage.qml \
|
||||||
|
qml/components/Shim.qml \
|
||||||
qml/components/UserGridDelegate.qml \
|
qml/components/UserGridDelegate.qml \
|
||||||
qml/components/VideoPlayer.qml \
|
qml/components/VideoPlayer.qml \
|
||||||
qml/components/itemdetails/CollectionFolder.qml \
|
qml/components/itemdetails/CollectionFolder.qml \
|
||||||
|
@ -51,6 +52,7 @@ DISTFILES += \
|
||||||
qml/cover/CoverPage.qml \
|
qml/cover/CoverPage.qml \
|
||||||
qml/cover/PosterCover.qml \
|
qml/cover/PosterCover.qml \
|
||||||
qml/cover/VideoCover.qml \
|
qml/cover/VideoCover.qml \
|
||||||
|
qml/pages/CollectionPage.qml \
|
||||||
qml/pages/DetailPage.qml \
|
qml/pages/DetailPage.qml \
|
||||||
qml/pages/LegalPage.qml \
|
qml/pages/LegalPage.qml \
|
||||||
qml/pages/MainPage.qml \
|
qml/pages/MainPage.qml \
|
||||||
|
|
|
@ -7,4 +7,5 @@ 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
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,5 +35,5 @@ function itemModelImageUrl(baseUrl, itemId, tag, type, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function usePortraitCover(itemType) {
|
function usePortraitCover(itemType) {
|
||||||
return ["Series", "Movie"].indexOf(itemType) >= 0
|
return ["Series", "Movie", "tvshows", "movies"].indexOf(itemType) >= 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ BackgroundItem {
|
||||||
property alias poster: posterImage.source
|
property alias poster: posterImage.source
|
||||||
property alias title: titleText.text
|
property alias title: titleText.text
|
||||||
property bool landscape: false
|
property bool landscape: false
|
||||||
|
property real progress: 0.0
|
||||||
|
|
||||||
width: Constants.libraryDelegateWidth
|
width: Constants.libraryDelegateWidth
|
||||||
height: landscape ? Constants.libraryDelegateHeight : Constants.libraryDelegatePosterHeight
|
height: landscape ? Constants.libraryDelegateHeight : Constants.libraryDelegatePosterHeight
|
||||||
|
|
||||||
|
@ -32,17 +34,14 @@ BackgroundItem {
|
||||||
visible: root.highlighted
|
visible: root.highlighted
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
Rectangle {
|
Shim {
|
||||||
anchors {
|
anchors {
|
||||||
left: parent.left
|
left: parent.left
|
||||||
right: parent.right
|
right: parent.right
|
||||||
bottom: parent.bottom
|
bottom: parent.bottom
|
||||||
}
|
}
|
||||||
height: titleText.height * 1.5 + Theme.paddingSmall * 2
|
height: titleText.height * 1.5 + Theme.paddingSmall * 2
|
||||||
gradient: Gradient {
|
|
||||||
GradientStop { position: 0.0; color: "transparent"; }
|
|
||||||
GradientStop { position: 1.0; color: Theme.highlightDimmerColor }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
@ -58,4 +57,15 @@ BackgroundItem {
|
||||||
truncationMode: TruncationMode.Fade
|
truncationMode: TruncationMode.Fade
|
||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: progress
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
height: Theme.paddingSmall
|
||||||
|
color: Theme.highlightColor
|
||||||
|
width: root.progress * parent.width
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
12
qml/components/Shim.qml
Normal file
12
qml/components/Shim.qml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import QtQuick 2.6
|
||||||
|
import Sailfish.Silica 1.0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
property real shimOpacity: 1.0
|
||||||
|
property color shimColor: Theme.overlayBackgroundColor
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0.0; color: Theme.rgba(shimColor, 0.0); }
|
||||||
|
GradientStop { position: 1.0; color: Theme.rgba(shimColor, shimOpacity); }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import QtQuick 2.0
|
import QtQuick 2.6
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ Column {
|
||||||
anchors {
|
anchors {
|
||||||
top: parent.top
|
top: parent.top
|
||||||
left: parent.left
|
left: parent.left
|
||||||
leftMargin: Theme.horizontalPageMargin
|
|
||||||
bottom: parent.bottom
|
bottom: parent.bottom
|
||||||
}
|
}
|
||||||
width: Constants.libraryDelegateWidth
|
width: Constants.libraryDelegateWidth
|
||||||
|
@ -35,6 +34,31 @@ Column {
|
||||||
source: Utils.itemModelImageUrl(ApiClient.baseUrl, model.id, model.imageTags["Primary"], "Primary", {"maxHeight": height})
|
source: Utils.itemModelImageUrl(ApiClient.baseUrl, model.id, model.imageTags["Primary"], "Primary", {"maxHeight": height})
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
|
// Makes the progress bar stand out more
|
||||||
|
Shim {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
bottom: parent.bottom
|
||||||
|
right: parent.right
|
||||||
|
}
|
||||||
|
height: parent.height / 3
|
||||||
|
shimColor: Theme.overlayBackgroundColor
|
||||||
|
shimOpacity: Theme.opacityOverlay
|
||||||
|
//width: model.userData.PlayedPercentage * parent.width / 100
|
||||||
|
visible: episodeProgress.width > 0 // It doesn't look nice when it's visible on every image
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: episodeProgress
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
height: Theme.paddingMedium
|
||||||
|
width: model.userData.PlayedPercentage * parent.width / 100
|
||||||
|
color: Theme.highlightColor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
|
|
@ -22,6 +22,7 @@ CoverBackground {
|
||||||
imageTypes: ["Primary"]
|
imageTypes: ["Primary"]
|
||||||
sortBy: ["IsFavoriteOrLiked", "Random"]
|
sortBy: ["IsFavoriteOrLiked", "Random"]
|
||||||
recursive: true
|
recursive: true
|
||||||
|
parentId: appWindow.collectionId
|
||||||
Component.onCompleted: reload()
|
Component.onCompleted: reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ CoverBackground {
|
||||||
imageTypes: ["Primary"]
|
imageTypes: ["Primary"]
|
||||||
sortBy: ["IsFavoriteOrLiked", "Random"]
|
sortBy: ["IsFavoriteOrLiked", "Random"]
|
||||||
recursive: true
|
recursive: true
|
||||||
|
parentId: appWindow.collectionId
|
||||||
Component.onCompleted: reload()
|
Component.onCompleted: reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +61,8 @@ CoverBackground {
|
||||||
clip: true
|
clip: true
|
||||||
height: row1.height
|
height: row1.height
|
||||||
width: height
|
width: height
|
||||||
source: Utils.itemModelImageUrl(ApiClient.baseUrl, model.id, model.imageTags["Primary"], "Primary", {"maxHeight": row1.height})
|
source: model.id ? Utils.itemModelImageUrl(ApiClient.baseUrl, model.id, model.imageTags["Primary"], "Primary", {"maxHeight": row1.height})
|
||||||
|
: ""
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,6 +123,16 @@ CoverBackground {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: appWindow
|
||||||
|
onCollectionIdChanged: {
|
||||||
|
randomItems1.parentId = collectionId
|
||||||
|
randomItems2.parentId = collectionId
|
||||||
|
randomItems1.reload()
|
||||||
|
randomItems2.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
property bool odd: false
|
property bool odd: false
|
||||||
running: true
|
running: true
|
||||||
|
|
|
@ -12,16 +12,11 @@ ApplicationWindow {
|
||||||
property bool _hasInitialized: false
|
property bool _hasInitialized: false
|
||||||
// The global mediaPlayer instance
|
// The global mediaPlayer instance
|
||||||
readonly property MediaPlayer mediaPlayer: _mediaPlayer
|
readonly property MediaPlayer mediaPlayer: _mediaPlayer
|
||||||
property url backgroundSource
|
|
||||||
onBackgroundSourceChanged: {
|
|
||||||
if (backgroundSource) {
|
|
||||||
appWindow._overlayBackgroundSource.backgroundItem.source = backgroundSource
|
|
||||||
} else {
|
|
||||||
appWindow._overlayBackgroundSource.backgroundItem.source = Theme.backgroundImage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Data of the currently selected item. For use on the cover.
|
// Data of the currently selected item. For use on the cover.
|
||||||
property var itemData
|
property var itemData
|
||||||
|
// Id of the collection currently browsing. For use on the cover.
|
||||||
|
property string collectionId
|
||||||
|
|
||||||
//FIXME: proper error handling
|
//FIXME: proper error handling
|
||||||
Connections {
|
Connections {
|
||||||
|
|
179
qml/pages/CollectionPage.qml
Normal file
179
qml/pages/CollectionPage.qml
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
import QtQuick 2.6
|
||||||
|
import Sailfish.Silica 1.0
|
||||||
|
|
||||||
|
import nl.netsoj.chris.Jellyfin 1.0
|
||||||
|
|
||||||
|
import ".."
|
||||||
|
import "../components"
|
||||||
|
import "../Utils.js" as Utils
|
||||||
|
|
||||||
|
Page {
|
||||||
|
id: pageRoot
|
||||||
|
property var itemId
|
||||||
|
property var itemData
|
||||||
|
property bool _loading: true
|
||||||
|
|
||||||
|
UserItemModel {
|
||||||
|
id: collectionModel
|
||||||
|
apiClient: ApiClient
|
||||||
|
parentId: itemData.Id || ""
|
||||||
|
sortBy: ["SortName"]
|
||||||
|
}
|
||||||
|
|
||||||
|
SilicaGridView {
|
||||||
|
id: gridView
|
||||||
|
anchors.fill: parent
|
||||||
|
model: collectionModel
|
||||||
|
cellWidth: Constants.libraryDelegateWidth
|
||||||
|
cellHeight: Utils.usePortraitCover(itemData.CollectionType) ? Constants.libraryDelegatePosterHeight
|
||||||
|
: Constants.libraryDelegateHeight
|
||||||
|
header: PageHeader {
|
||||||
|
title: itemData.Name || qsTr("Loading")
|
||||||
|
}
|
||||||
|
PullDownMenu {
|
||||||
|
id: downMenu
|
||||||
|
MenuItem {
|
||||||
|
//: Menu item for selecting the sort order of a collection
|
||||||
|
text: qsTr("Sort by")
|
||||||
|
onClicked: pageStack.push(sortPageComponent)
|
||||||
|
}
|
||||||
|
busy: collectionModel.status == ApiModel.Loading
|
||||||
|
}
|
||||||
|
delegate: GridItem {
|
||||||
|
RemoteImage {
|
||||||
|
id: itemImage
|
||||||
|
anchors.fill: parent
|
||||||
|
source: Utils.itemModelImageUrl(ApiClient.baseUrl, model.id, model.imageTags["Primary"], "Primary", {"maxWidth": width})
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
clip: true
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
bottom: parent.bottom
|
||||||
|
right: parent.right
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
id: itemName
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: Theme.paddingMedium
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: Theme.paddingMedium
|
||||||
|
bottom: parent.bottom
|
||||||
|
bottomMargin: Theme.paddingSmall
|
||||||
|
}
|
||||||
|
text: model.name
|
||||||
|
truncationMode: TruncationMode.Fade
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
}
|
||||||
|
onClicked: {
|
||||||
|
switch(model.type) {
|
||||||
|
case "Folder":
|
||||||
|
pageStack.push(Qt.resolvedUrl("CollectionPage.qml"), {"itemId": model.id})
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pageStack.push(Qt.resolvedUrl("DetailPage.qml"), {"itemId": model.id})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewPlaceholder {
|
||||||
|
enabled: gridView.count == 0 && !pageRoot._loading
|
||||||
|
text: qsTr("Empty collection")
|
||||||
|
hintText: qsTr("Add some items to this collection!")
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalScrollDecorator {}
|
||||||
|
}
|
||||||
|
|
||||||
|
PageBusyIndicator {
|
||||||
|
running: pageRoot._loading
|
||||||
|
}
|
||||||
|
|
||||||
|
onItemIdChanged: {
|
||||||
|
itemData = {}
|
||||||
|
if (itemId.length && PageStatus.Active) {
|
||||||
|
pageRoot._loading = true
|
||||||
|
ApiClient.fetchItem(itemId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onStatusChanged: {
|
||||||
|
if (status == PageStatus.Deactivating) {
|
||||||
|
backdrop.clear()
|
||||||
|
}
|
||||||
|
if (status == PageStatus.Active) {
|
||||||
|
if (itemId && !itemData) {
|
||||||
|
ApiClient.fetchItem(itemId)
|
||||||
|
appWindow.collectionId = itemId
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ApiClient
|
||||||
|
onItemFetched: {
|
||||||
|
if (itemId === pageRoot.itemId) {
|
||||||
|
pageRoot.itemData = result
|
||||||
|
pageRoot._loading = false
|
||||||
|
console.log(JSON.stringify(result))
|
||||||
|
collectionModel.parentId = result.Id
|
||||||
|
collectionModel.reload()
|
||||||
|
if (status == PageStatus.Active) {
|
||||||
|
appWindow.itemData = null
|
||||||
|
appWindow.collectionId = itemId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: sortPageComponent
|
||||||
|
Page {
|
||||||
|
id: sortPage
|
||||||
|
|
||||||
|
ListModel {
|
||||||
|
id: sortOptions
|
||||||
|
ListElement { name: qsTr("Name"); value: "SortName"; }
|
||||||
|
ListElement { name: qsTr("Play count"); value: "PlayCount"; }
|
||||||
|
ListElement { name: qsTr("Date added"); value: "DateCreated"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
SilicaListView {
|
||||||
|
anchors.fill: parent
|
||||||
|
model: sortOptions
|
||||||
|
header: PageHeader {
|
||||||
|
title: qsTr("Sort by")
|
||||||
|
}
|
||||||
|
delegate: ListItem {
|
||||||
|
Label {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: Theme.horizontalPageMargin
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: Theme.horizontalPageMargin
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
text: model.name
|
||||||
|
}
|
||||||
|
onClicked: {
|
||||||
|
collectionModel.sortBy = [model.value]
|
||||||
|
collectionModel.reload()
|
||||||
|
pageStack.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -30,13 +30,10 @@ Page {
|
||||||
if (_backdropImages && _backdropImages.length > 0) {
|
if (_backdropImages && _backdropImages.length > 0) {
|
||||||
var rand = Math.floor(Math.random() * (_backdropImages.length - 0.001))
|
var rand = Math.floor(Math.random() * (_backdropImages.length - 0.001))
|
||||||
console.log("Random: ", rand)
|
console.log("Random: ", rand)
|
||||||
//backdrop.source = ApiClient.baseUrl + "/Items/" + itemId + "/Images/Backdrop/" + rand + "?tag=" + _backdropImages[rand] + "&maxHeight" + height
|
backdrop.source = ApiClient.baseUrl + "/Items/" + itemId + "/Images/Backdrop/" + rand + "?tag=" + _backdropImages[rand] + "&maxHeight" + height
|
||||||
appWindow.backgroundSource = ApiClient.baseUrl + "/Items/" + itemId + "/Images/Backdrop/" + rand + "?tag=" + _backdropImages[rand] + "&maxHeight" + height
|
|
||||||
} else if (_parentBackdropImages && _parentBackdropImages.length > 0) {
|
} else if (_parentBackdropImages && _parentBackdropImages.length > 0) {
|
||||||
console.log(parentId)
|
console.log(parentId)
|
||||||
//backdrop.source = ApiClient.baseUrl + "/Items/" + itemData.ParentBackdropItemId + "/Images/Backdrop/0?tag=" + _parentBackdropImages[0]
|
backdrop.source = ApiClient.baseUrl + "/Items/" + itemData.ParentBackdropItemId + "/Images/Backdrop/0?tag=" + _parentBackdropImages[0]
|
||||||
appWindow.backgroundSource = ApiClient.baseUrl + "/Items/" + itemData.ParentBackdropItemId + "/Images/Backdrop/0?tag=" + _parentBackdropImages[0]
|
|
||||||
Theme.backgroundGlowColor
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,6 @@ Page {
|
||||||
id: page
|
id: page
|
||||||
allowedOrientations: Orientation.All
|
allowedOrientations: Orientation.All
|
||||||
|
|
||||||
ViewPlaceholder {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SilicaFlickable {
|
SilicaFlickable {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
|
@ -50,6 +46,20 @@ Page {
|
||||||
MoreSection {
|
MoreSection {
|
||||||
text: qsTr("Resume watching")
|
text: qsTr("Resume watching")
|
||||||
clickable: false
|
clickable: false
|
||||||
|
busy: userResumeModel.status == ApiModel.Loading
|
||||||
|
Loader {
|
||||||
|
width: parent.width
|
||||||
|
sourceComponent: carrouselView
|
||||||
|
property alias itemModel: userResumeModel
|
||||||
|
property string collectionType: "series"
|
||||||
|
|
||||||
|
UserItemResumeModel {
|
||||||
|
id: userResumeModel
|
||||||
|
apiClient: ApiClient
|
||||||
|
limit: 12
|
||||||
|
recursive: true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MoreSection {
|
MoreSection {
|
||||||
text: qsTr("Next up")
|
text: qsTr("Next up")
|
||||||
|
@ -65,45 +75,14 @@ Page {
|
||||||
MoreSection {
|
MoreSection {
|
||||||
text: model.name
|
text: model.name
|
||||||
busy: userItemModel.status != ApiModel.Ready
|
busy: userItemModel.status != ApiModel.Ready
|
||||||
property string collectionType: model.collectionType || ""
|
|
||||||
|
|
||||||
onHeaderClicked: pageStack.push(Qt.resolvedUrl("DetailPage.qml"), {"itemId": model.id})
|
onHeaderClicked: pageStack.push(Qt.resolvedUrl("CollectionPage.qml"), {"itemId": model.id})
|
||||||
|
Loader {
|
||||||
SilicaListView {
|
|
||||||
clip: true
|
|
||||||
height: {
|
|
||||||
if (count > 0) {
|
|
||||||
if (["tvshows", "movies"].indexOf(collectionType) == -1) {
|
|
||||||
Constants.libraryDelegateHeight
|
|
||||||
} else {
|
|
||||||
Constants.libraryDelegatePosterHeight
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation { duration: 300 }
|
|
||||||
}
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
model: userItemModel
|
sourceComponent: carrouselView
|
||||||
orientation: ListView.Horizontal
|
property alias itemModel: userItemModel
|
||||||
leftMargin: Theme.horizontalPageMargin
|
property string collectionType: model.collectionType || ""
|
||||||
rightMargin: Theme.horizontalPageMargin
|
|
||||||
spacing: Theme.paddingLarge
|
|
||||||
delegate: LibraryItemDelegate {
|
|
||||||
property string id: model.id
|
|
||||||
title: model.name
|
|
||||||
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: !Utils.usePortraitCover(model.type)
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
pageStack.push(Qt.resolvedUrl("DetailPage.qml"), {"itemId": model.id})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UserItemLatestModel {
|
UserItemLatestModel {
|
||||||
id: userItemModel
|
id: userItemModel
|
||||||
apiClient: ApiClient
|
apiClient: ApiClient
|
||||||
|
@ -165,6 +144,49 @@ Page {
|
||||||
if (force || (ApiClient.authenticated && !_modelsLoaded)) {
|
if (force || (ApiClient.authenticated && !_modelsLoaded)) {
|
||||||
_modelsLoaded = true;
|
_modelsLoaded = true;
|
||||||
mediaLibraryModel.reload()
|
mediaLibraryModel.reload()
|
||||||
|
userResumeModel.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: carrouselView
|
||||||
|
SilicaListView {
|
||||||
|
id: list
|
||||||
|
clip: true
|
||||||
|
height: {
|
||||||
|
if (count > 0) {
|
||||||
|
if (["tvshows", "movies"].indexOf(collectionType) == -1) {
|
||||||
|
Constants.libraryDelegateHeight
|
||||||
|
} else {
|
||||||
|
Constants.libraryDelegatePosterHeight
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation { duration: 300 }
|
||||||
|
}
|
||||||
|
model: itemModel
|
||||||
|
width: parent.width
|
||||||
|
orientation: ListView.Horizontal
|
||||||
|
leftMargin: Theme.horizontalPageMargin
|
||||||
|
rightMargin: Theme.horizontalPageMargin
|
||||||
|
spacing: Theme.paddingLarge
|
||||||
|
delegate: LibraryItemDelegate {
|
||||||
|
property string id: model.id
|
||||||
|
title: model.name
|
||||||
|
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: !Utils.usePortraitCover(model.type)
|
||||||
|
progress: model.userData.PlayedPercentage / 100
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
pageStack.push(Qt.resolvedUrl("DetailPage.qml"), {"itemId": model.id})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,19 +9,13 @@ ApiModel::ApiModel(QString path, bool hasRecordResponse, bool addUserId, QObject
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApiModel::reload() {
|
void ApiModel::reload() {
|
||||||
|
this->setStatus(Loading);
|
||||||
|
m_startIndex = 0;
|
||||||
load(RELOAD);
|
load(RELOAD);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApiModel::load(LoadType type) {
|
void ApiModel::load(LoadType type) {
|
||||||
qDebug() << (type == RELOAD ? "RELOAD" : "LOAD_MORE");
|
qDebug() << (type == RELOAD ? "RELOAD" : "LOAD_MORE");
|
||||||
switch(type) {
|
|
||||||
case RELOAD:
|
|
||||||
this->setStatus(Loading);
|
|
||||||
break;
|
|
||||||
case LOAD_MORE:
|
|
||||||
this->setStatus(LoadingMore);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (m_apiClient == nullptr) {
|
if (m_apiClient == nullptr) {
|
||||||
qWarning() << "Please set the apiClient property before (re)loading";
|
qWarning() << "Please set the apiClient property before (re)loading";
|
||||||
return;
|
return;
|
||||||
|
@ -187,6 +181,7 @@ bool ApiModel::canFetchMore(const QModelIndex &parent) const {
|
||||||
|
|
||||||
void ApiModel::fetchMore(const QModelIndex &parent) {
|
void ApiModel::fetchMore(const QModelIndex &parent) {
|
||||||
if (parent.isValid()) return;
|
if (parent.isValid()) return;
|
||||||
|
this->setStatus(LoadingMore);
|
||||||
load(LOAD_MORE);
|
load(LOAD_MORE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,6 +195,7 @@ void registerModels(const char *URI) {
|
||||||
qmlRegisterType<UserViewModel>(URI, 1, 0, "UserViewModel");
|
qmlRegisterType<UserViewModel>(URI, 1, 0, "UserViewModel");
|
||||||
qmlRegisterType<UserItemModel>(URI, 1, 0, "UserItemModel");
|
qmlRegisterType<UserItemModel>(URI, 1, 0, "UserItemModel");
|
||||||
qmlRegisterType<UserItemLatestModel>(URI, 1, 0, "UserItemLatestModel");
|
qmlRegisterType<UserItemLatestModel>(URI, 1, 0, "UserItemLatestModel");
|
||||||
|
qmlRegisterType<UserItemResumeModel>(URI, 1, 0, "UserItemResumeModel");
|
||||||
qmlRegisterType<ShowSeasonsModel>(URI, 1, 0, "ShowSeasonsModel");
|
qmlRegisterType<ShowSeasonsModel>(URI, 1, 0, "ShowSeasonsModel");
|
||||||
qmlRegisterType<ShowEpisodesModel>(URI, 1, 0, "ShowEpisodesModel");
|
qmlRegisterType<ShowEpisodesModel>(URI, 1, 0, "ShowEpisodesModel");
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,7 +195,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 = {};
|
||||||
bool m_recursive;
|
bool m_recursive = false;
|
||||||
|
|
||||||
QHash<int, QByteArray> m_roles;
|
QHash<int, QByteArray> m_roles;
|
||||||
|
|
||||||
|
@ -218,13 +218,13 @@ private:
|
||||||
class PublicUserModel : public ApiModel {
|
class PublicUserModel : public ApiModel {
|
||||||
public:
|
public:
|
||||||
explicit PublicUserModel (QObject *parent = nullptr)
|
explicit PublicUserModel (QObject *parent = nullptr)
|
||||||
: ApiModel ("/users/public", "", false, parent) { }
|
: ApiModel ("/users/public", false, false, parent) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
class UserViewModel : public ApiModel {
|
class UserViewModel : public ApiModel {
|
||||||
public:
|
public:
|
||||||
explicit UserViewModel (QObject *parent = nullptr)
|
explicit UserViewModel (QObject *parent = nullptr)
|
||||||
: ApiModel ("/Users/{{user}}/Views", "Items", false, parent) {}
|
: ApiModel ("/Users/{{user}}/Views", true, false, parent) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class UserItemModel : public ApiModel {
|
class UserItemModel : public ApiModel {
|
||||||
|
@ -232,6 +232,13 @@ public:
|
||||||
explicit UserItemModel (QObject *parent = nullptr)
|
explicit UserItemModel (QObject *parent = nullptr)
|
||||||
: ApiModel ("/Users/{{user}}/Items", true, false, parent) {}
|
: ApiModel ("/Users/{{user}}/Items", true, false, parent) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class UserItemResumeModel : public ApiModel {
|
||||||
|
public:
|
||||||
|
explicit UserItemResumeModel (QObject *parent = nullptr)
|
||||||
|
: ApiModel ("/Users/{{user}}/Items/Resume", true, false, parent) {}
|
||||||
|
};
|
||||||
|
|
||||||
class UserItemLatestModel : public ApiModel {
|
class UserItemLatestModel : public ApiModel {
|
||||||
public:
|
public:
|
||||||
explicit UserItemLatestModel (QObject *parent = nullptr)
|
explicit UserItemLatestModel (QObject *parent = nullptr)
|
||||||
|
|
|
@ -58,6 +58,38 @@
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>CollectionPage</name>
|
||||||
|
<message>
|
||||||
|
<source>Loading</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Sort by</source>
|
||||||
|
<extracomment>Menu item for selecting the sort order of a collection</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Empty collection</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Add some items to this collection!</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Name</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Play count</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Date added</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>CoverPage</name>
|
<name>CoverPage</name>
|
||||||
<message>
|
<message>
|
||||||
|
|
Loading…
Reference in a new issue