1
0
Fork 0
mirror of https://github.com/HenkKalkwater/harbour-sailfin.git synced 2025-09-05 10:12:46 +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:
Chris Josten 2020-09-27 16:54:45 +02:00
parent 5ea17070fe
commit 5d395ad7b6
15 changed files with 363 additions and 73 deletions

View 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()
}
}
}
}
}
}

View file

@ -30,13 +30,10 @@ 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] + "&maxHeight" + height
appWindow.backgroundSource = 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
} else if (_parentBackdropImages && _parentBackdropImages.length > 0) {
console.log(parentId)
//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
backdrop.source = ApiClient.baseUrl + "/Items/" + itemData.ParentBackdropItemId + "/Images/Backdrop/0?tag=" + _parentBackdropImages[0]
}
}

View file

@ -16,10 +16,6 @@ Page {
id: page
allowedOrientations: Orientation.All
ViewPlaceholder {
}
SilicaFlickable {
anchors.fill: parent
@ -50,6 +46,20 @@ Page {
MoreSection {
text: qsTr("Resume watching")
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 {
text: qsTr("Next up")
@ -65,45 +75,14 @@ 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: {
if (count > 0) {
if (["tvshows", "movies"].indexOf(collectionType) == -1) {
Constants.libraryDelegateHeight
} else {
Constants.libraryDelegatePosterHeight
}
} else {
0
}
}
Behavior on height {
NumberAnimation { duration: 300 }
}
onHeaderClicked: pageStack.push(Qt.resolvedUrl("CollectionPage.qml"), {"itemId": model.id})
Loader {
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: 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)
sourceComponent: carrouselView
property alias itemModel: userItemModel
property string collectionType: model.collectionType || ""
onClicked: {
pageStack.push(Qt.resolvedUrl("DetailPage.qml"), {"itemId": model.id})
}
}
UserItemLatestModel {
id: userItemModel
apiClient: ApiClient
@ -165,6 +144,49 @@ Page {
if (force || (ApiClient.authenticated && !_modelsLoaded)) {
_modelsLoaded = true;
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})
}
}
}
}
}