1
0
Fork 0
mirror of https://github.com/HenkKalkwater/harbour-sailfin.git synced 2024-12-22 22:15:17 +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:
Chris Josten 2020-10-02 00:13:05 +02:00
parent dc395e9b3c
commit e9d36a690c
10 changed files with 196 additions and 188 deletions

View file

@ -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"

View file

@ -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); }
}
}

View file

@ -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)
}
}
}
}

View file

@ -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

View file

@ -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: {

View file

@ -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)

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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))

View file

@ -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 {