mirror of
https://github.com/HenkKalkwater/harbour-sailfin.git
synced 2024-11-22 09:15:18 +00:00
Minor UI improvements
* EpisodePage and FilmPage are now based off VideoPage, since they share a lot of components in common. * The overlay over the thumbnail in PlayToolbar is less obtrusive * Cover shows now the title of the item, and total play time, if applicable * The resume playing section forces all delegates to be square. * The Season page now uses a ListView instead of a ColumnView.
This commit is contained in:
parent
dc395e9b3c
commit
e9d36a690c
|
@ -34,9 +34,12 @@ Column {
|
|||
id: playImage
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
color: Theme.overlayBackgroundColor
|
||||
clip: true
|
||||
}
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.rgba(Theme.overlayBackgroundColor, Theme.opacityLow)
|
||||
}
|
||||
Icon {
|
||||
id: playButton
|
||||
source: "image://theme/icon-l-play"
|
||||
|
|
|
@ -23,9 +23,10 @@ import Sailfish.Silica 1.0
|
|||
Rectangle {
|
||||
property real shimOpacity: 1.0
|
||||
property color shimColor: Theme.overlayBackgroundColor
|
||||
property bool upsideDown: false
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: Theme.rgba(shimColor, 0.0); }
|
||||
GradientStop { position: 1.0; color: Theme.rgba(shimColor, shimOpacity); }
|
||||
GradientStop { position: upsideDown ? 1.0 : 0.0; color: Theme.rgba(shimColor, 0.0); }
|
||||
GradientStop { position: upsideDown ? 0.0 : 1.0; color: Theme.rgba(shimColor, shimOpacity); }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import Sailfish.Silica 1.0
|
|||
import nl.netsoj.chris.Jellyfin 1.0
|
||||
|
||||
import "../components"
|
||||
import ".."
|
||||
|
||||
CoverBackground {
|
||||
property var mData: appWindow.itemData
|
||||
|
@ -34,4 +35,52 @@ CoverBackground {
|
|||
fillMode: Image.PreserveAspectCrop
|
||||
}
|
||||
|
||||
Shim {
|
||||
// Movies usually show their name on the poster,
|
||||
// so showing it here as well is a bit double
|
||||
visible: itemData.Type !== "Movie"
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
upsideDown: true
|
||||
height: parent.height / 2
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
}
|
||||
width: itemData.UserData.PlayedPercentage / 100 * parent.width
|
||||
height: Theme.paddingSmall
|
||||
color: Theme.highlightColor
|
||||
}
|
||||
|
||||
Column {
|
||||
id: infoColumn
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
anchors.margins: Theme.paddingMedium
|
||||
Label {
|
||||
id: itemName
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
color: Theme.primaryColor
|
||||
text: itemData.Name
|
||||
truncationMode: TruncationMode.Fade
|
||||
}
|
||||
Label {
|
||||
visible: typeof itemData.RunTimeTicks !== "undefined"
|
||||
color: Theme.secondaryColor
|
||||
text: Utils.ticksToText(itemData.RunTimeTicks)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -50,6 +50,15 @@ CoverBackground {
|
|||
fillMode: Image.PreserveAspectCrop
|
||||
}
|
||||
|
||||
Shim {
|
||||
anchors {
|
||||
left: parent.left
|
||||
bottom: parent.bottom
|
||||
right: parent.right
|
||||
}
|
||||
height: Theme.iconSizeLarge
|
||||
}
|
||||
|
||||
CoverActionList {
|
||||
CoverAction {
|
||||
id: playPause
|
||||
|
|
|
@ -43,6 +43,10 @@ Page {
|
|||
text: qsTr("Settings")
|
||||
onClicked: pageStack.push(Qt.resolvedUrl("SettingsPage.qml"))
|
||||
}
|
||||
MenuItem {
|
||||
text: qsTr("Refresh")
|
||||
onClicked: loadModels(true)
|
||||
}
|
||||
busy: mediaLibraryModel.status == ApiModel.Loading
|
||||
}
|
||||
|
||||
|
@ -198,7 +202,7 @@ Page {
|
|||
/*model.imageTags["Primary"] ? ApiClient.baseUrl + "/Items/" + model.id
|
||||
+ "/Images/Primary?maxHeight=" + height + "&tag=" + model.imageTags["Primary"]
|
||||
: ""*/
|
||||
landscape: !Utils.usePortraitCover(model.type)
|
||||
landscape: !Utils.usePortraitCover(collectionType)
|
||||
progress: (typeof model.userData !== "undefined") ? model.userData.PlayedPercentage / 100 : 0.0
|
||||
|
||||
onClicked: {
|
||||
|
|
|
@ -44,6 +44,7 @@ Page {
|
|||
on_ParentBackdropImagesChanged: updateBackdrop()
|
||||
|
||||
function updateBackdrop() {
|
||||
return;
|
||||
if (_backdropImages && _backdropImages.length > 0) {
|
||||
var rand = Math.floor(Math.random() * (_backdropImages.length - 0.001))
|
||||
console.log("Random: ", rand)
|
||||
|
|
|
@ -24,56 +24,25 @@ import nl.netsoj.chris.Jellyfin 1.0
|
|||
import "../../components"
|
||||
import "../../"
|
||||
|
||||
BaseDetailPage {
|
||||
SilicaFlickable {
|
||||
anchors.fill: parent
|
||||
contentHeight: content.height
|
||||
Column {
|
||||
id: content
|
||||
width: parent.width
|
||||
|
||||
PageHeader {
|
||||
title: itemData.Name
|
||||
description: {
|
||||
if (typeof itemData.IndexNumberEnd !== "undefined") {
|
||||
qsTr("Episode %1–%2 | %3").arg(itemData.IndexNumber)
|
||||
.arg(itemData.IndexNumberEnd)
|
||||
.arg(itemData.SeasonName)
|
||||
} else {
|
||||
qsTr("Episode %1 | %2").arg(itemData.IndexNumber).arg(itemData.SeasonName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PlayToolbar {
|
||||
imageSource: Utils.itemImageUrl(ApiClient.baseUrl, itemData, "Primary", {"maxWidth": parent.width})
|
||||
imageAspectRatio: itemData.PrimaryImageAspectRatio || 1.0
|
||||
playProgress: itemData.UserData.PlayedPercentage / 100
|
||||
onPlayPressed: pageStack.push(Qt.resolvedUrl("../VideoPage.qml"),
|
||||
{"itemId": itemId, "itemData": itemData,
|
||||
"audioTrack": trackSelector.audioTrack,
|
||||
"subtitleTrack": trackSelector.subtitleTrack,
|
||||
"startTicks": startFromBeginning ? 0.0
|
||||
: itemData.UserData.PlaybackPositionTicks })
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
VideoTrackSelector {
|
||||
id: trackSelector
|
||||
width: parent.width
|
||||
tracks: itemData.MediaStreams
|
||||
}
|
||||
|
||||
SectionHeader {
|
||||
text: qsTr("Overview")
|
||||
}
|
||||
|
||||
PlainLabel {
|
||||
id: overviewText
|
||||
text: itemData.Overview || qsTr("No overview available")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.secondaryHighlightColor
|
||||
}
|
||||
VideoPage {
|
||||
subtitle: {
|
||||
if (typeof itemData.IndexNumberEnd !== "undefined") {
|
||||
qsTr("Episode %1–%2 | %3").arg(itemData.IndexNumber)
|
||||
.arg(itemData.IndexNumberEnd)
|
||||
.arg(itemData.SeasonName)
|
||||
} else {
|
||||
qsTr("Episode %1 | %2").arg(itemData.IndexNumber).arg(itemData.SeasonName)
|
||||
}
|
||||
}
|
||||
|
||||
SectionHeader {
|
||||
text: qsTr("Overview")
|
||||
}
|
||||
|
||||
PlainLabel {
|
||||
id: overviewText
|
||||
text: itemData.Overview || qsTr("No overview available")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.secondaryHighlightColor
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,46 +25,17 @@ import nl.netsoj.chris.Jellyfin 1.0
|
|||
import "../../components"
|
||||
import "../.."
|
||||
|
||||
BaseDetailPage {
|
||||
SilicaFlickable {
|
||||
anchors.fill: parent
|
||||
contentHeight: content.height
|
||||
VideoPage {
|
||||
subtitle: qsTr("Released: %1 — Run time: %2").arg(itemData.ProductionYear).arg(Utils.ticksToText(itemData.RunTimeTicks))
|
||||
|
||||
Column {
|
||||
id: content
|
||||
width: parent.width
|
||||
spacing: Theme.paddingMedium
|
||||
SectionHeader {
|
||||
text: qsTr("Overview")
|
||||
}
|
||||
|
||||
PageHeader {
|
||||
title: itemData.Name
|
||||
description: qsTr("Released: %1 — Run time: %2").arg(itemData.ProductionYear).arg(Utils.ticksToText(itemData.RunTimeTicks))
|
||||
}
|
||||
|
||||
PlayToolbar {
|
||||
width: parent.width
|
||||
imageSource: Utils.itemImageUrl(ApiClient.baseUrl, itemData, "Primary", {"maxWidth": parent.width})
|
||||
imageAspectRatio: 1.66666 //itemData.PrimaryImageAspectRatio
|
||||
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
|
||||
}
|
||||
|
||||
SectionHeader {
|
||||
text: qsTr("Overview")
|
||||
}
|
||||
|
||||
PlainLabel {
|
||||
id: overviewText
|
||||
text: itemData.Overview
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.secondaryHighlightColor
|
||||
}
|
||||
}
|
||||
PlainLabel {
|
||||
id: overviewText
|
||||
text: itemData.Overview
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.secondaryHighlightColor
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,107 +26,98 @@ import "../../components"
|
|||
import ".."
|
||||
|
||||
BaseDetailPage {
|
||||
SilicaFlickable {
|
||||
ShowEpisodesModel {
|
||||
id: episodeModel
|
||||
apiClient: ApiClient
|
||||
show: itemData.SeriesId
|
||||
seasonId: itemData.Id
|
||||
fields: ["Overview"]
|
||||
}
|
||||
|
||||
SilicaListView {
|
||||
anchors.fill: parent
|
||||
contentHeight: content.height
|
||||
header: PageHeader {
|
||||
title: itemData.Name
|
||||
description: itemData.SeriesName
|
||||
}
|
||||
model: episodeModel
|
||||
delegate: BackgroundItem {
|
||||
height: Constants.libraryDelegateHeight
|
||||
RemoteImage {
|
||||
id: episodeImage
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
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
|
||||
|
||||
Column {
|
||||
id: content
|
||||
width: parent.width
|
||||
|
||||
PageHeader {
|
||||
title: itemData.Name
|
||||
description: itemData.SeriesName
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
// 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
Rectangle {
|
||||
id: episodeProgress
|
||||
anchors {
|
||||
left: parent.left
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
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(Utils.getPageUrl(model.mediaType, model.type), {"itemId": model.id})
|
||||
height: Theme.paddingMedium
|
||||
width: model.userData.PlayedPercentage * parent.width / 100
|
||||
color: Theme.highlightColor
|
||||
}
|
||||
}
|
||||
|
||||
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(Utils.getPageUrl(model.mediaType, model.type), {"itemId": model.id})
|
||||
}
|
||||
|
||||
VerticalScrollDecorator {}
|
||||
}
|
||||
onItemDataChanged: {
|
||||
console.log(JSON.stringify(itemData))
|
||||
|
|
|
@ -30,9 +30,13 @@ import "../.."
|
|||
* the FilmPage or EpisodePage.
|
||||
*/
|
||||
BaseDetailPage {
|
||||
property alias subtitle: pageHeader.description
|
||||
default property alias _data: content.data
|
||||
SilicaFlickable {
|
||||
anchors.fill: parent
|
||||
contentHeight: content.height
|
||||
contentHeight: content.height + Theme.paddingLarge
|
||||
|
||||
VerticalScrollDecorator {}
|
||||
|
||||
Column {
|
||||
id: content
|
||||
|
@ -40,17 +44,23 @@ BaseDetailPage {
|
|||
spacing: Theme.paddingMedium
|
||||
|
||||
PageHeader {
|
||||
id: pageHeader
|
||||
title: itemData.Name
|
||||
description: qsTr("Run time: %2").arg(Utils.ticksToText(itemData.RunTimeTicks))
|
||||
}
|
||||
|
||||
PlayToolbar {
|
||||
id: toolbar
|
||||
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 })
|
||||
playProgress: itemData.UserData.PlayedPercentage / 100
|
||||
onPlayPressed: pageStack.push(Qt.resolvedUrl("../VideoPage.qml"),
|
||||
{"itemId": itemId, "itemData": itemData,
|
||||
"audioTrack": trackSelector.audioTrack,
|
||||
"subtitleTrack": trackSelector.subtitleTrack,
|
||||
"startTicks": startFromBeginning ? 0.0
|
||||
: itemData.UserData.PlaybackPositionTicks })
|
||||
}
|
||||
|
||||
VideoTrackSelector {
|
||||
|
|
Loading…
Reference in a new issue