mirror of
https://github.com/HenkKalkwater/harbour-sailfin.git
synced 2025-09-01 08:52:45 +00:00
Added MusicAlbumPage
- [UI]: Added specialized view for music albums - Prepared RemoteImageView for fading in image when loaded
This commit is contained in:
parent
a29ab3dff4
commit
040aeb1a40
10 changed files with 330 additions and 22 deletions
|
@ -20,8 +20,7 @@ set(sailfin_QML_SOURCES
|
|||
qml/components/Shim.qml
|
||||
qml/components/UserGridDelegate.qml
|
||||
qml/components/VideoPlayer.qml
|
||||
qml/components/VideoTrackSelector.qml
|
||||
qml/components/itemdetails/SeasonDetails.qml
|
||||
qml/components/VideoTrackSelector.qml
|
||||
qml/components/videoplayer/VideoError.qml
|
||||
qml/components/videoplayer/VideoHud.qml
|
||||
qml/cover/CoverPage.qml
|
||||
|
@ -46,14 +45,12 @@ set(sailfin_QML_SOURCES
|
|||
qml/pages/setup/LoginDialog.qml
|
||||
qml/qmldir)
|
||||
|
||||
add_executable(harbour-sailfin ${harbour-sailfin_SOURCES})
|
||||
add_executable(harbour-sailfin ${harbour-sailfin_SOURCES} ${sailfin_QML_SOURCES})
|
||||
target_link_libraries(harbour-sailfin PRIVATE Qt5::Gui Qt5::Qml Qt5::Quick SailfishApp::SailfishApp
|
||||
# Note: this may break when the compiler changes. -rdynamic and -pie seem to be needed for the
|
||||
# invoker/booster to work
|
||||
jellyfin-qt "-Wl,-rpath,${CMAKE_INSTALL_LIBDIR} -rdynamic -pie")
|
||||
|
||||
add_custom_target(harbour-sailfin-qml ${sailfin_QML_SOURCES})
|
||||
|
||||
install(TARGETS harbour-sailfin
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
|
@ -79,3 +76,19 @@ install(FILES icons/128x128/harbour-sailfin.png
|
|||
install(FILES icons/172x172/harbour-sailfin.png
|
||||
DESTINATION share/icons/hicolor/172x172/apps
|
||||
)
|
||||
|
||||
# Tell Qt Creator where the application executable(s) would be located on the
|
||||
# device.
|
||||
#
|
||||
# It is not necessary to list other deployables than executables (runtime
|
||||
# targets) here. The deployment process of Sailfish OS projects is opaque to
|
||||
# Qt Creator and the information contained in QtCreatorDeployment.txt is only
|
||||
# used to locate the executable associated with the active run configuration
|
||||
# on the device in order to run it.
|
||||
#
|
||||
# Search the Qt Creator Manual to learn about the QtCreatorDeployment.txt file
|
||||
# format.
|
||||
file(WRITE "${CMAKE_BINARY_DIR}/QtCreatorDeployment.txt"
|
||||
"${CMAKE_INSTALL_PREFIX}
|
||||
sailfish/harbour-sailfin:bin
|
||||
")
|
||||
|
|
|
@ -22,15 +22,17 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
/**
|
||||
* Converts miliseconds to a h:mm:ss format
|
||||
*/
|
||||
function timeToText(time) {
|
||||
function timeToText(time, showHours) {
|
||||
var mShowHours = showHours === undefined ? true : showHours
|
||||
if (time < 0) return "??:??:??"
|
||||
var hours = Math.floor(time / (60 * 60 * 1000))
|
||||
var left = time % (60 * 60 * 1000)
|
||||
var minutes = Math.floor(left / (60 * 1000))
|
||||
left = time % (60 * 1000)
|
||||
var seconds = Math.floor(left / 1000)
|
||||
|
||||
return hours + ":" + (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "")+ seconds
|
||||
return (hours > 0 ? hours + ":" : "")
|
||||
+ (minutes < 10 ? "0" : "")
|
||||
+ minutes + ":" + (seconds < 10 ? "0" : "")+ seconds
|
||||
}
|
||||
|
||||
function msToTicks(ms) {
|
||||
|
@ -41,8 +43,8 @@ function ticksToMs(ticks) {
|
|||
return ticks / 10000;
|
||||
}
|
||||
|
||||
function ticksToText(ticks) {
|
||||
return timeToText(ticks / 10000);
|
||||
function ticksToText(ticks, showHours) {
|
||||
return timeToText(ticks / 10000, showHours);
|
||||
}
|
||||
|
||||
function itemImageUrl(baseUrl, item, type, options) {
|
||||
|
@ -80,6 +82,8 @@ function getPageUrl(mediaType, itemType) {
|
|||
return Qt.resolvedUrl("pages/itemdetails/SeasonPage.qml")
|
||||
case "episode":
|
||||
return Qt.resolvedUrl("pages/itemdetails/EpisodePage.qml")
|
||||
case "musicalbum":
|
||||
return Qt.resolvedUrl("pages/itemdetails/MusicAlbumPage.qml")
|
||||
default:
|
||||
switch (mediaType ? mediaType.toLowerCase() : "folder") {
|
||||
case "folder":
|
||||
|
|
|
@ -45,6 +45,7 @@ BackgroundItem {
|
|||
}
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
fallbackColor: Utils.colorFromString(title)
|
||||
highlighted: root.highlighted
|
||||
}
|
||||
|
||||
/*Rectangle {
|
||||
|
|
|
@ -23,11 +23,22 @@ import Sailfish.Silica 1.0
|
|||
/**
|
||||
* An image for "remote" images (loaded over e.g. http), with a spinner and a fallback image
|
||||
*/
|
||||
HighlightImage {
|
||||
property string fallbackImage
|
||||
property bool usingFallbackImage
|
||||
SilicaItem {
|
||||
property string fallbackImage
|
||||
property bool usingFallbackImage
|
||||
property color fallbackColor: Theme.highlightColor
|
||||
asynchronous: true
|
||||
|
||||
property alias source: realImage.source
|
||||
property alias sourceSize: realImage.sourceSize
|
||||
property alias fillMode: realImage.fillMode
|
||||
implicitHeight: realImage.implicitHeight
|
||||
implicitWidth: realImage.implicitWidth
|
||||
|
||||
Image {
|
||||
id: realImage
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: fallbackBackground
|
||||
|
@ -36,18 +47,25 @@ HighlightImage {
|
|||
GradientStop { position: 0.0; color: fallbackColor; }
|
||||
GradientStop { position: 1.0; color: Theme.highlightDimmerFromColor(fallbackColor, Theme.colorScheme); }
|
||||
}
|
||||
visible: parent.status == Image.Error || parent.status == Image.Null || parent.status == Image.Loading
|
||||
visible: realImage.status === Image.Error || realImage.status === Image.Null || realImage.status === Image.Loading
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: highlightOverlay
|
||||
anchors.fill: parent
|
||||
color: Theme.rgba(Theme.highlightColor, Theme.opacityOverlay)
|
||||
visible: parent.highlighted
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
running: parent.status == Image.Loading
|
||||
running: realImage.status === Image.Loading
|
||||
}
|
||||
|
||||
HighlightImage {
|
||||
id: fallbackImageItem
|
||||
anchors.centerIn: parent
|
||||
visible: parent.status == Image.Error || parent.status == Image.Null
|
||||
visible: realImage.status === Image.Error || realImage.status === Image.Null
|
||||
source: fallbackImage ? fallbackImage : "image://theme/icon-m-question"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,230 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick 2.6
|
||||
import Sailfish.Silica 1.0
|
||||
|
||||
Item {
|
||||
import nl.netsoj.chris.Jellyfin 1.0
|
||||
|
||||
import "../../components"
|
||||
import "../.."
|
||||
|
||||
BaseDetailPage {
|
||||
readonly property int _songIndexWidth: 100
|
||||
property string _albumArtistText: itemData.albumArtist
|
||||
|
||||
UserItemModel {
|
||||
id: collectionModel
|
||||
apiClient: ApiClient
|
||||
sortBy: ["SortName"]
|
||||
fields: ["ItemCounts","PrimaryImageAspectRatio","BasicSyncInfo","CanDelete","MediaSourceCount"]
|
||||
parentId: itemData.jellyfinId
|
||||
onParentIdChanged: reload()
|
||||
}
|
||||
|
||||
SilicaListView {
|
||||
id: list
|
||||
anchors.fill: parent
|
||||
model: collectionModel
|
||||
header: Item {
|
||||
property string stateIfArt: "largeArt"
|
||||
property alias albumArt: albumArt
|
||||
id: listHeader
|
||||
width: parent.width
|
||||
//spacing: Theme.paddingLarge
|
||||
state: albumArt.source != "" ? stateIfArt : "noArt"
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (listHeader.stateIfArt == "largeArt") {
|
||||
listHeader.stateIfArt = "details"
|
||||
} else {
|
||||
listHeader.stateIfArt = "largeArt"
|
||||
}
|
||||
}
|
||||
}
|
||||
RemoteImage {
|
||||
id: albumArt
|
||||
anchors {
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
}
|
||||
source: Utils.itemImageUrl(ApiClient.baseUrl, itemData, "Primary", {"maxWidth": parent.width})
|
||||
sourceSize.width: listHeader.width
|
||||
sourceSize.height: listHeader.width
|
||||
fillMode: Image.PreserveAspectFit
|
||||
opacity: 1
|
||||
clip: true
|
||||
}
|
||||
PageHeader {
|
||||
id: albumHeader
|
||||
width: parent.width - Theme.horizontalPageMargin - height
|
||||
title: itemData.name
|
||||
description: qsTr("%1\n%2 songs | %3 | %4")
|
||||
.arg(_albumArtistText)
|
||||
.arg(itemData.childCount)
|
||||
.arg(Utils.ticksToText(itemData.runTimeTicks))
|
||||
.arg(itemData.productionYear > 0 ? itemData.productionYear : qsTr("Unknown year"))
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "largeArt"
|
||||
PropertyChanges {
|
||||
target: albumArt
|
||||
width: parent.width
|
||||
height: width
|
||||
}
|
||||
PropertyChanges {
|
||||
target: listHeader
|
||||
height: width
|
||||
}
|
||||
PropertyChanges {
|
||||
target: albumHeader
|
||||
opacity: 0
|
||||
}
|
||||
PropertyChanges {
|
||||
target: list
|
||||
contentY: -list.width
|
||||
}
|
||||
AnchorChanges {
|
||||
target: albumHeader
|
||||
anchors.left: undefined
|
||||
anchors.right: albumArt.left
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "details"
|
||||
PropertyChanges {
|
||||
target: albumArt
|
||||
width: height
|
||||
height: albumHeader.height
|
||||
}
|
||||
PropertyChanges {
|
||||
target: listHeader
|
||||
height: albumHeader.height
|
||||
}
|
||||
PropertyChanges {
|
||||
target: albumHeader
|
||||
opacity: 1
|
||||
}
|
||||
PropertyChanges {
|
||||
target: list
|
||||
contentY: -albumHeader.height
|
||||
}
|
||||
AnchorChanges {
|
||||
target: albumHeader
|
||||
anchors.left: undefined
|
||||
anchors.right: albumArt.left
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "noArt"
|
||||
extend: "details"
|
||||
PropertyChanges {
|
||||
target: albumArt
|
||||
opacity: 0
|
||||
}
|
||||
PropertyChanges {
|
||||
target: albumHeader
|
||||
width: parent.width - Theme.horizontalPageMargin * 2
|
||||
}
|
||||
AnchorChanges {
|
||||
target: albumHeader
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}
|
||||
]
|
||||
transitions: Transition {
|
||||
OpacityAnimator { target: albumHeader}
|
||||
OpacityAnimator { target: albumArt}
|
||||
NumberAnimation {
|
||||
properties: "width,height,contentY"
|
||||
//velocity: 1600
|
||||
duration: 300
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
AnchorAnimation {}
|
||||
}
|
||||
}
|
||||
section {
|
||||
property: "parentIndexNumber"
|
||||
delegate: SectionHeader {
|
||||
text: qsTr("Disc %1").arg(section)
|
||||
}
|
||||
}
|
||||
delegate: ListItem {
|
||||
contentHeight: songName.height + songArtists.height + 2 * Theme.paddingMedium
|
||||
width: parent.width
|
||||
|
||||
Label {
|
||||
id: songIndex
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: Theme.paddingMedium
|
||||
left: parent.left
|
||||
leftMargin: Theme.horizontalPageMargin
|
||||
}
|
||||
text: model.indexNumber
|
||||
horizontalAlignment: Text.AlignRight
|
||||
font.pixelSize: Theme.fontSizeExtraLarge
|
||||
width: _songIndexWidth
|
||||
}
|
||||
|
||||
Label {
|
||||
id: songName
|
||||
anchors {
|
||||
left: songIndex.right
|
||||
leftMargin: Theme.paddingLarge
|
||||
top: parent.top
|
||||
topMargin: Theme.paddingMedium
|
||||
right: duration.left
|
||||
rightMargin: Theme.paddingLarge
|
||||
}
|
||||
text: model.name
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
truncationMode: TruncationMode.Fade
|
||||
}
|
||||
Label {
|
||||
id: songArtists
|
||||
anchors {
|
||||
top: songName.bottom
|
||||
left: songIndex.right
|
||||
leftMargin: Theme.paddingLarge
|
||||
right: parent.right
|
||||
rightMargin: Theme.horizontalPageMargin
|
||||
}
|
||||
text: model.artists.join(", ")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
truncationMode: TruncationMode.Fade
|
||||
color: highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor
|
||||
}
|
||||
|
||||
Label {
|
||||
id: duration
|
||||
anchors {
|
||||
right: parent.right
|
||||
rightMargin: Theme.horizontalPageMargin
|
||||
baseline: songName.baseline
|
||||
}
|
||||
width: contentWidth
|
||||
text: Utils.ticksToText(model.runTimeTicks)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
VerticalScrollDecorator {}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: itemData
|
||||
onAlbumArtistsChanged: {
|
||||
console.log(itemData.albumArtists)
|
||||
_albumArtistText = ""
|
||||
for (var i = 0; i < itemData.albumArtists.length; i++) {
|
||||
_albumArtistText += itemData.albumArtists[i]["name"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue