diff --git a/qml/components/TidalApi.qml b/qml/components/TidalApi.qml
index b8e3f74..2cab971 100644
--- a/qml/components/TidalApi.qml
+++ b/qml/components/TidalApi.qml
@@ -38,6 +38,10 @@ Item {
signal cacheTrack(var track_info)
signal cacheAlbum(var album_info)
signal cacheArtist(var artist_info)
+ signal albumofArtist(var album_info)
+ signal topTracksofArtist(var track_info)
+ signal similarArtist(var artist_info)
+ signal noSimilarArtists()
signal playlistTrackAdded(var track_info)
signal albumTrackAdded(var track_info)
@@ -117,6 +121,21 @@ Item {
tidalApi.cacheAlbum(album_info)
})
+ setHandler('TopTrackofArtist', function(track_info) {
+ tidalApi.topTracksofArtist(track_info)
+ })
+
+ setHandler('AlbumofArtist', function(album_info) {
+ tidalApi.albumofArtist(album_info)
+ })
+
+ setHandler('SimilarArtist', function(artist_info) {
+ tidalApi.similarArtist(artist_info)
+ })
+
+ setHandler('noSimilarArtists', function() {
+ tidalApi.noSimilarArtists()
+ })
// Search Handler
setHandler('addTrack', function(id, title, album, artist, image, duration) {
@@ -374,7 +393,17 @@ Item {
pythonTidal.call('tidal.Tidaler.get_favorite_tracks', [])
}
+ function getAlbumsofArtist(artistid) {
+ pythonTidal.call('tidal.Tidaler.getAlbumsofArtist', [artistid])
+ }
+ function getTopTracksofArtist(artistid) {
+ pythonTidal.call('tidal.Tidaler.getTopTracksofArtist', [artistid])
+ }
+
+ function getSimiliarArtist(artistid) {
+ pythonTidal.call('tidal.Tidaler.getSimiliarArtist', [artistid])
+ }
}
diff --git a/qml/components/TidalCache.qml b/qml/components/TidalCache.qml
index 49693c0..29f37e7 100644
--- a/qml/components/TidalCache.qml
+++ b/qml/components/TidalCache.qml
@@ -19,10 +19,13 @@ id: root
target: tidalApi
// Bestehende Connections
+ /*
onTrackChanged: {
// id, title, album, artist, image, duration
saveTrackToCache({
- id: id,
+ trackid: trackid,
+ albumid: albumid,
+ artistid: artistid,
title: title,
album: album,
artist: artist,
@@ -35,9 +38,10 @@ id: root
onAlbumChanged: {
// id, title, artist, image
saveAlbumToCache({
- id: id,
+ albumid: albumid,
title: title,
artist: artist,
+ artistid: artistid,
image: image,
timestamp: Date.now()
})
@@ -46,25 +50,27 @@ id: root
onArtistChanged: {
// id, name, img
saveArtistToCache({
- id: id,
+ artistid: artistid,
name: name,
image: img,
timestamp: Date.now()
})
}
-
+ */
// Neue Connections für Suchergebnisse
onCacheTrack: {
//track_info
saveTrackToCache({
- id: track_info.id,
+ trackid: track_info.trackid,
title: track_info.title,
- album: track_info.album,
artist: track_info.artist,
- image: track_info.image,
- duration: track_info.duration,
+ artistid:track_info.artistid,
+ album: track_info.album,
albumid: track_info.albumid,
+ duration: track_info.duration,
+ image: track_info.image,
+ track_num : track_info.track_num,
timestamp: Date.now(),
fromSearch: true // Optional: markiert Einträge aus der Suche
})
@@ -73,10 +79,10 @@ id: root
onCacheArtist: {
//artist_info
saveArtistToCache({
- id: artist_info.id,
+ artistid: artist_info.artistid,
name: artist_info.name,
- bio: artist_info.bio,
image: artist_info.image,
+ bio: artist_info.bio,
timestamp: Date.now(),
fromSearch: true // Optional: markiert Einträge aus der Suche
})
@@ -85,19 +91,25 @@ id: root
onCacheAlbum: {
//album_info
saveAlbumToCache({
- id: album_info.id,
+ albumid: album_info.albumid,
title: album_info.title,
artist: album_info.artist,
+ artistid: album_info.artistid,
image: album_info.image,
duration: album_info.duration,
+ num_tracks : album_info.num_tracks,
+ year : album_info.year,
timestamp: Date.now(),
fromSearch: true // Optional: markiert Einträge aus der Suche
})
}
+ /*
onTrackAdded: {
// id, title, album, artist, image, duration
saveTrackToCache({
- id: id,
+ trackid: trackid,
+ albumid: albumid,
+ artistid: artistid,
title: title,
album: album,
artist: artist,
@@ -111,7 +123,8 @@ id: root
onAlbumAdded: {
// id, title, artist, image, duration
saveAlbumToCache({
- id: id,
+ albumid: albumid,
+ artistid: artistid,
title: title,
artist: artist,
image: image,
@@ -130,7 +143,7 @@ id: root
timestamp: Date.now(),
fromSearch: true
})
- }
+ }*/
}
// Optional: Erweiterte Such-spezifische Funktionen
@@ -150,7 +163,7 @@ id: root
function addSearchTrack(id) {
if (!searchResults.tracks.includes(id)) {
searchResults.tracks.push(id)
- }d, title, artist, image, duration
+ }
}
function addSearchAlbum(id) {
@@ -207,7 +220,9 @@ id: root
var result = tidalApi.getTrackInfo(id)
if (result) {
var trackData = {
- id: id,
+ trackid: id,
+ albumid: result.albumid,
+ artistid: result.artistid,
title: result.title,
artist: result.artist,
album: result.album,
@@ -222,9 +237,73 @@ id: root
return null
}
+ function getAlbumInfo(id) {
+ // Erst im Cache nachsehen
+ var cachedTrack = getAlbum(id)
+ if (cachedTrack) {
+ if (Date.now() - cachedTrack.timestamp < maxCacheAge) {
+ return cachedTrack
+ } else {
+ console.log("Cache entry too old, refreshing...")
+ }
+ }
+
+ // Wenn nicht im Cache oder zu alt, von Python holen
+
+ var result = tidalApi.getAlbumInfo(id)
+ if (result) {
+ var trackData = {
+ albumid: id,
+ title: result.title,
+ artist: result.artist,
+ artistid: result.artistid,
+ image : result.image,
+ duration: result.duration,
+ num_tracks : result.num_tracks,
+ year : result.year,
+ timestamp: Date.now()
+ }
+
+ saveAlbumToCache(trackData)
+ return trackData
+ }
+
+ return null
+ }
+
+ function getArtistInfo(id) {
+ // Erst im Cache nachsehen
+ var cachedTrack = getArtist(id)
+ if (cachedTrack) {
+ if (Date.now() - cachedTrack.timestamp < maxCacheAge) {
+ return cachedTrack
+ } else {
+ console.log("Cache entry too old, refreshing...")
+ }
+ }
+
+ // Wenn nicht im Cache oder zu alt, von Python holen
+
+ var result = tidalApi.getArtistInfo(id)
+ if (result) {
+ var trackData = {
+ artistid: id,
+ name: result.name,
+ image : result.image,
+ bio : result.bio,
+ timestamp: Date.now()
+ }
+
+ saveAlbumToCache(trackData)
+ return trackData
+ }
+
+ return null
+ }
+
// Datenbank initialisieren
function initDatabase() {
- db = LocalStorage.openDatabaseSync("TidalCache", "1.1", "Cache for Tidal data", 1000000)
+ db = LocalStorage.openDatabaseSync("TidalCache", "1.2", "Cache for Tidal data", 1000000)
db.transaction(function(tx) {
// Tracks Tabelle
tx.executeSql('CREATE TABLE IF NOT EXISTS tracks(id TEXT PRIMARY KEY, data TEXT, timestamp INTEGER)')
@@ -268,6 +347,7 @@ id: root
for(i = 0; i < rs.rows.length; i++) {
try {
artistCache[rs.rows.item(i).id] = JSON.parse(rs.rows.item(i).data)
+ //console.log(artistCache[rs.rows.item(i).id].artistid, artistCache[rs.rows.item(i).id].name)
} catch(e) {
console.error("Error parsing artist data:", e)
}
@@ -278,26 +358,26 @@ id: root
// Cache-Speicherfunktionen
function saveTrackToCache(trackData) {
- trackCache[trackData.id] = trackData
+ trackCache[trackData.trackid] = trackData
db.transaction(function(tx) {
tx.executeSql('INSERT OR REPLACE INTO tracks(id, data, timestamp) VALUES(?, ?, ?)',
- [trackData.id, JSON.stringify(trackData), trackData.timestamp])
+ [trackData.trackid, JSON.stringify(trackData), trackData.timestamp])
})
}
function saveAlbumToCache(albumData) {
- albumCache[albumData.id] = albumData
+ albumCache[albumData.albumid] = albumData
db.transaction(function(tx) {
tx.executeSql('INSERT OR REPLACE INTO albums(id, data, timestamp) VALUES(?, ?, ?)',
- [albumData.id, JSON.stringify(albumData), albumData.timestamp])
+ [albumData.albumid, JSON.stringify(albumData), albumData.timestamp])
})
}
function saveArtistToCache(artistData) {
- artistCache[artistData.id] = artistData
+ artistCache[artistData.artistidid] = artistData
db.transaction(function(tx) {
tx.executeSql('INSERT OR REPLACE INTO artists(id, data, timestamp) VALUES(?, ?, ?)',
- [artistData.id, JSON.stringify(artistData), artistData.timestamp])
+ [artistData.artistid, JSON.stringify(artistData), artistData.timestamp])
})
}
diff --git a/qml/pages/AlbumPage.qml b/qml/pages/AlbumPage.qml
index 987aa16..b3271e9 100644
--- a/qml/pages/AlbumPage.qml
+++ b/qml/pages/AlbumPage.qml
@@ -84,14 +84,45 @@ Page {
spacing: Theme.paddingSmall
anchors.verticalCenter: parent.verticalCenter
- Label {
- id: artistName
- width: parent.width
- text: albumData ? albumData.artist : ""
- truncationMode: TruncationMode.Fade
- color: Theme.highlightColor
- font.pixelSize: Theme.fontSizeLarge
- }
+BackgroundItem {
+ width: parent.width
+ height: artistRow.height + Theme.paddingMedium * 2
+
+ Rectangle {
+ anchors.fill: parent
+ color: Theme.rgba(Theme.highlightBackgroundColor, parent.pressed ? 0.3 : 0.1)
+ radius: Theme.paddingSmall
+ }
+
+ Row {
+ id: artistRow
+ anchors.centerIn: parent
+ spacing: Theme.paddingMedium
+
+ Image {
+ id: artistIcon
+ source: "image://theme/icon-s-person"
+ width: Theme.iconSizeSmall
+ height: width
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Label {
+ id: artistName
+ text: albumData ? albumData.artist : ""
+ truncationMode: TruncationMode.Fade
+ color: parent.parent.pressed ? Theme.highlightColor : Theme.primaryColor
+ font.pixelSize: Theme.fontSizeLarge
+ }
+ }
+
+ onClicked: {
+ if (albumData && albumData.artistid) {
+ pageStack.push(Qt.resolvedUrl("ArtistPage.qml"),
+ { artistId: albumData.artistid })
+ }
+ }
+}
Label {
width: parent.width
@@ -102,7 +133,7 @@ Page {
Label {
width: parent.width
- text: albumData ? qsTr("Released: ") + albumData.releaseDate : ""
+ text: albumData ? qsTr("Released: ") + albumData.year : ""
color: Theme.secondaryColor
font.pixelSize: Theme.fontSizeSmall
opacity: isHeaderCollapsed ? 0.0 : 1.0
@@ -112,7 +143,7 @@ Page {
Label {
width: parent.width
- text: albumData ? qsTr("Tracks: ") + albumData.numberOfTracks : ""
+ text: albumData ? qsTr("Tracks: ") + albumData.num_tracks : ""
color: Theme.secondaryColor
font.pixelSize: Theme.fontSizeSmall
opacity: isHeaderCollapsed ? 0.0 : 1.0
@@ -172,7 +203,7 @@ Page {
}
Label {
- text: albumData ? qsTr("%n tracks", "", albumData.numberOfTracks) : ""
+ text: albumData ? qsTr("%n tracks", "", albumData.num_tracks) : ""
color: Theme.secondaryColor
font.pixelSize: Theme.fontSizeExtraSmall
anchors.verticalCenter: parent.verticalCenter
diff --git a/qml/pages/ArtistPage.qml b/qml/pages/ArtistPage.qml
index d661336..0813597 100644
--- a/qml/pages/ArtistPage.qml
+++ b/qml/pages/ArtistPage.qml
@@ -2,103 +2,395 @@ import QtQuick 2.0
import Sailfish.Silica 1.0
import QtMultimedia 5.6
import Sailfish.Media 1.0
-
import "widgets"
-
Page {
id: artistPage
- property int track_id
+ property int artistId : -1
+ property var artistData: null
+ property bool isHeaderCollapsed: false
+
+function processWimpLinks(text) {
+ if (!text) return ""
+
+ // Text in Teile zerlegen
+ var parts = text.split("[wimpLink")
+ var result = parts[0] // Start mit dem ersten Teil ohne Link
+
+ // Durch alle weiteren Teile gehen
+ for (var i = 1; i < parts.length; i++) {
+ var part = parts[i]
+ try {
+ // Artist Link
+ if (part.indexOf('artistId="') >= 0) {
+ var idMatch = part.match(/artistId="(\d+)"/)
+ var textMatch = part.match(/](.*?)\[/)
+ if (idMatch && textMatch) {
+ result += '' + textMatch[1] + ''
+ result += part.split("[/wimpLink]")[1] || ""
+ }
+ }
+ // Album Link
+ else if (part.indexOf('albumId="') >= 0) {
+ var idMatch = part.match(/albumId="(\d+)"/)
+ var textMatch = part.match(/](.*?)\[/)
+ if (idMatch && textMatch) {
+ result += '' + textMatch[1] + ''
+ result += part.split("[/wimpLink]")[1] || ""
+ }
+ }
+ else {
+ // Falls kein Match, original Text behalten
+ result += "[wimpLink" + part
+ }
+ } catch (e) {
+ console.log("Fehler beim Verarbeiten eines Links:", e)
+ // Bei Fehler original Text behalten
+ result += "[wimpLink" + part
+ }
+ }
+ return result
+}
- // The effective value will be restricted by ApplicationWindow.allowedOrientations
allowedOrientations: Orientation.All
- // To enable PullDownMenu, place our content in a SilicaFlickable
SilicaFlickable {
-
- width: parent.width
+ id: flickable
anchors {
fill: parent
bottomMargin: minPlayerPanel.margin
}
+ contentHeight: mainColumn.height
+
PullDownMenu {
- MenuItem {
- text: qsTr("Show Playlist")
- onClicked:
- {
- onClicked: pageStack.push(Qt.resolvedUrl("PlaylistPage.qml"))
- }
- }
MenuItem {
text: minPlayerPanel.open ? "Hide player" : "Show player"
onClicked: minPlayerPanel.open = !minPlayerPanel.open
- anchors.horizontalCenter: parent.horizontalCenter
}
}
+
Column {
- id: infoCoulumn
+ id: mainColumn
+ width: parent.width
+ spacing: Theme.paddingMedium
+
PageHeader {
id: header
- title: qsTr("Artist Info")
+ title: qsTr("Artist Info")
}
- spacing: 10 // Abstand zwischen den Elementen in der Column
- width: parent.width // Die Column nimmt die volle Breite des Eltern-Elements (Item) ein
- Image {
- id: coverImage
- anchors {
- top: header.bottom
- horizontalCenter: albumPage.isPortrait ? parent.horizontalCenter : undefined
+ // Artist Info Section
+ Item {
+ id: artistInfoContainer
+ width: parent.width
+ height: isHeaderCollapsed ? Theme.itemSizeLarge : width * 0.4
+ clip: true
+
+ Behavior on height {
+ NumberAnimation { duration: 200 }
}
- sourceSize.width: {
- var maxImageWidth = Screen.width
- var leftMargin = Theme.horizontalPageMargin
- var rightMargin = artistPage.isPortrait ? Theme.horizontalPageMargin : 0
- return (maxImageWidth - leftMargin - rightMargin)*3/2
+ Row {
+ width: parent.width
+ height: parent.height
+ spacing: Theme.paddingMedium
+ anchors.margins: Theme.paddingMedium
+
+ Image {
+ id: coverImage
+ width: parent.height
+ height: width
+ fillMode: Image.PreserveAspectFit
+
+ Rectangle {
+ color: Theme.rgba(Theme.highlightBackgroundColor, 0.1)
+ anchors.fill: parent
+ visible: coverImage.status !== Image.Ready
+ }
+ }
+
+ Column {
+ width: parent.width - coverImage.width - parent.spacing - Theme.paddingLarge * 2
+ height: parent.height
+ spacing: Theme.paddingSmall
+ anchors.verticalCenter: parent.verticalCenter
+
+ Label {
+ id: artistName
+ width: parent.width
+ truncationMode: TruncationMode.Fade
+ color: Theme.highlightColor
+ font.pixelSize: Theme.fontSizeLarge
+ }
+
+ Item {
+ width: parent.width
+ height: parent.height - artistName.height - parent.spacing
+ clip: true
+
+ Flickable {
+ id: bioFlickable
+ anchors.fill: parent
+ contentHeight: bioText.height
+ clip: true
+
+ Label {
+ id: bioText
+ width: parent.width
+ wrapMode: Text.WordWrap
+ textFormat: Text.RichText // Wichtig für HTML-Links
+ color: Theme.secondaryColor
+ font.pixelSize: Theme.fontSizeSmall
+
+ onLinkActivated: {
+ var parts = link.split(":")
+ if (parts.length === 2) {
+ if (parts[0] === "artist") {
+ pageStack.push(Qt.resolvedUrl("ArtistPage.qml"),
+ { artistId: parseInt(parts[1]) })
+ } else if (parts[0] === "album") {
+ pageStack.push(Qt.resolvedUrl("AlbumPage.qml"),
+ { albumId: parseInt(parts[1]) })
+ }
+ }
+ }
+ }
+ }
+
+ // Scrollbar für die Biografie
+ VerticalScrollDecorator {
+ flickable: bioFlickable
+ }
+ }
+ }
+
}
+ }
- fillMode: Image.PreserveAspectFit
+ // Albums Section
+ SectionHeader {
+ text: qsTr("Albums")
}
- Label
- {
- id: artistName
- anchors {
- top : coverImage.bottom
+
+ // Ersetze den ScrollDecorator mit diesem angepassten horizontalen Scroll-Indikator
+ SilicaListView {
+ id: albumsView
+ width: parent.width
+ height: Theme.itemSizeLarge * 2.5 // Höhe vergrößert
+ orientation: ListView.Horizontal
+ clip: true
+ spacing: Theme.paddingMedium // Abstand zwischen den Items
+
+ model: ListModel {}
+
+ delegate: BackgroundItem {
+ width: Theme.itemSizeLarge * 2 // Breite vergrößert
+ height: albumsView.height
+
+ Column {
+ anchors {
+ fill: parent
+ margins: Theme.paddingSmall
+ }
+ spacing: Theme.paddingMedium // Mehr Abstand zwischen Bild und Text
+
+ Image {
+ width: parent.width
+ height: width // Quadratisches Cover
+ source: model.cover
+ fillMode: Image.PreserveAspectCrop
+ }
+
+ Label {
+ width: parent.width
+ text: model.title
+ truncationMode: TruncationMode.Fade
+ font.pixelSize: Theme.fontSizeSmall // Größere Schrift
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.Wrap // Text kann umbrechen
+ maximumLineCount: 2 // Maximal zwei Zeilen
+ }
}
- truncationMode: TruncationMode.Fade
+
+ onClicked: pageStack.push(Qt.resolvedUrl("AlbumPage.qml"),
+ { albumId: model.albumId })
+ }
+
+ // Horizontaler Scroll-Indikator
+ Rectangle {
+ visible: albumsView.contentWidth > albumsView.width
+ height: 2
color: Theme.highlightColor
+ opacity: 0.4
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ }
+
+ Rectangle {
+ height: parent.height
+ color: Theme.highlightColor
+ width: Math.max(parent.width * (albumsView.width / albumsView.contentWidth), Theme.paddingLarge)
+ x: (parent.width - width) * (albumsView.contentX / (albumsView.contentWidth - albumsView.width))
+ visible: albumsView.contentWidth > albumsView.width
+ }
}
}
+
+ // Top Tracks Section
+ SectionHeader {
+ text: qsTr("Popular Tracks")
+ }
+
TrackList {
id: topTracks
- title : "Popular Tracks"
+ width: parent.width
+ height: artistPage.height - y - (minPlayerPanel.open ? minPlayerPanel.height : 0)
+ type: "tracklist"
+ }
+
+ // Similiar Artists Section
+ SectionHeader {
+ id:similarArtistsSection
+ text: qsTr("Similiar Artists")
+ }
+
+ // Ersetze den ScrollDecorator mit diesem angepassten horizontalen Scroll-Indikator
+ SilicaListView {
+ id: simartistView
+ width: parent.width
+ height: Theme.itemSizeLarge * 2.5 // Höhe vergrößert
+ orientation: ListView.Horizontal
+ clip: true
+ spacing: Theme.paddingMedium // Abstand zwischen den Items
+
+ model: ListModel {}
+
+ delegate: BackgroundItem {
+ width: Theme.itemSizeLarge * 2 // Breite vergrößert
+ height: simartistView.height
+
+ Column {
+ anchors {
+ fill: parent
+ margins: Theme.paddingSmall
+ }
+ spacing: Theme.paddingMedium // Mehr Abstand zwischen Bild und Text
+
+ Image {
+ width: parent.width
+ height: width // Quadratisches Cover
+ source: model.cover
+ fillMode: Image.PreserveAspectCrop
+ }
+
+ Label {
+ width: parent.width
+ text: model.name
+ truncationMode: TruncationMode.Fade
+ font.pixelSize: Theme.fontSizeSmall // Größere Schrift
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.Wrap // Text kann umbrechen
+ maximumLineCount: 2 // Maximal zwei Zeilen
+ }
+ }
+
+ onClicked: pageStack.push(Qt.resolvedUrl("ArtistPage.qml"),
+ { artistId: model.artistId })
+ }
+ }
+ // Horizontaler Scroll-Indikator
+ Rectangle {
+ visible: simartistView.contentWidth > simartistView.width
+ height: 2
+ color: Theme.highlightColor
+ opacity: 0.4
anchors {
- top: infoCoulumn.bottom// Anker oben an den unteren Rand der Column
- topMargin: 650 // Abstand zwischen der Column und dem ListView
- left: parent.left // Anker links am linken Rand des Eltern-Elements (Page)
- right: parent.right // Anker rechts am rechten Rand des Eltern-Elements (Page)
- leftMargin: Theme.horizontalPageMargin
- rightMargin: Theme.horizontalPageMargin
- //bottom: parent.bottom// Anker unten am unteren Rand des Eltern-Elements (Page)
- }
- height: 600
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ }
+
+ Rectangle {
+ height: parent.height
+ color: Theme.highlightColor
+ width: Math.max(parent.width * (simartistView.width / simartistView.contentWidth), Theme.paddingLarge)
+ x: (parent.width - width) * (simartistView.contentX / (simartistView.contentWidth - simartistView.width))
+ visible: simartistView.contentWidth > simartistView.width
+ }
}
+ }
+
+ VerticalScrollDecorator {}
+ onContentYChanged: {
+ if (contentY > Theme.paddingLarge) {
+ isHeaderCollapsed = true
+ } else {
+ isHeaderCollapsed = false
+ }
}
+ }
+
+ Component.onCompleted: {
+ if (artistId > 0) {
+ artistData = cacheManager.getArtist(artistId)
+ if (!artistData) {
+ console.log("Artist nicht im Cache gefunden:", artistId)
+ }
+ header.title = artistData.name
+ artistName.text = artistData.name
+ coverImage.source = artistData.image
+ if (artistData.bio) {
+ console.log("Verarbeite Bio...")
+ var processedBio = processWimpLinks(artistData.bio)
+ bioText.text = processedBio
+ }
+ tidalApi.getAlbumsofArtist(artistData.artistid)
+ tidalApi.getTopTracksofArtist(artistData.artistid)
+ tidalApi.getSimiliarArtist(artistData.artistid)
+ }
+ }
Connections {
target: tidalApi
- onArtistChanged:
- {
+ onArtistChanged: {
header.title = name
+ artistName.text = name
coverImage.source = img
+ bioText.text = ""
}
- onTrackAdded:
- {
+
+ onTrackAdded: {
topTracks.addTrack(title, artist, album, id, duration)
}
+ // Neues Signal für Alben
+ onAlbumofArtist: {
+
+ albumsView.model.append({
+ title: album_info.title,
+ cover: album_info.image,
+ albumId: album_info.albumid
+ })
+ }
+
+ onSimilarArtist: {
+
+ simartistView.model.append({
+ name: artist_info.name,
+ cover: artist_info.image,
+ artistId: artist_info.artistid
+ })
+ }
+
+ onNoSimilarArtists: {
+ // Optional: Section Header ausblenden
+ similarArtistsSection.visible = false
+ simartistView.visible = false
+ }
+
}
}
diff --git a/qml/pages/Search.qml b/qml/pages/Search.qml
index 059a293..c82cbee 100644
--- a/qml/pages/Search.qml
+++ b/qml/pages/Search.qml
@@ -35,7 +35,7 @@ Item {
id: searchField
width: parent.width
placeholderText: qsTr("Type and Search")
- text: ""
+ text: "Corvus Corax"
label: qsTr("Please wait for login ...")
enabled: tidalApi.loginTrue
@@ -150,7 +150,9 @@ Item {
name: track.title,
artist: track.artist,
album: track.album,
- id: track.id,
+ trackid: track.trackid,
+ albumid: track.albumid,
+ artistid: track.artistid,
type: typeTrack,
image: track.image,
duration: track.duration,
@@ -159,9 +161,11 @@ Item {
}
function createAlbumItem(album) {
+ console.log(album.title, album.albumid)
return {
name: album.title,
- id: album.id,
+ albumid: album.albumid,
+ artistid: album.artistid,
type: typeAlbum,
image: album.image,
duration: album.duration
@@ -171,7 +175,8 @@ Item {
function createArtistItem(artist) {
return {
name: artist.name,
- id: artist.id,
+ artistid: artist.artistid,
+ albumid:artist.albumid,
type: typeArtist,
image: artist.image
}
diff --git a/qml/pages/TrackList.qml b/qml/pages/TrackList.qml
index 1722a75..9a0973e 100644
--- a/qml/pages/TrackList.qml
+++ b/qml/pages/TrackList.qml
@@ -8,7 +8,7 @@ Item {
property string title: ""
property string playlistId: ""
property int albumId: -1
- property string type: "current" // "playlist" oder "current" oder "album"
+ property string type: "current" // "playlist" oder "current" oder "album" oder "tracklist"
Timer {
id: updateTimer
@@ -138,13 +138,13 @@ Item {
MenuItem {
text: qsTr("Play Now")
onClicked: {
- playlistManager.playTrack(model.id)
+ playlistManager.playTrack(model.trackid)
}
}
MenuItem {
text: qsTr("Add to Queue")
onClicked: {
- playlistManager.appendTrack(model.id)
+ playlistManager.appendTrack(model.trackid)
}
}
}
@@ -154,7 +154,7 @@ Item {
playlistManager.playPosition(model.index)
} else {
- playlistManager.playTrack(model.id)
+ playlistManager.playTrack(model.trackid)
}
}
}
@@ -192,7 +192,7 @@ Item {
"title": track_info.title,
"artist": track_info.artist,
"album": track_info.album,
- "id": track_info.id,
+ "trackid": track_info.trackid,
"duration": track_info.duration,
"image": track_info.image
})
@@ -205,7 +205,20 @@ Item {
"title": track_info.title,
"artist": track_info.artist,
"album": track_info.album,
- "id": track_info.id,
+ "trackid": track_info.trackid,
+ "duration": track_info.duration,
+ "image": track_info.image
+ })
+ }
+ }
+
+ onTopTracksofArtist: {
+ if (type === "tracklist") {
+ listModel.append({
+ "title": track_info.title,
+ "artist": track_info.artist,
+ "album": track_info.album,
+ "trackid": track_info.trackid,
"duration": track_info.duration,
"image": track_info.image
})
@@ -221,7 +234,7 @@ Item {
"title": title,
"artist": artist,
"album": album,
- "id": id,
+ "trackid": trackid,
"duration": duration,
"image": image,
"index": index
diff --git a/qml/pages/stuff/SearchResultDelegate.qml b/qml/pages/stuff/SearchResultDelegate.qml
index caccfb3..a8b55ad 100644
--- a/qml/pages/stuff/SearchResultDelegate.qml
+++ b/qml/pages/stuff/SearchResultDelegate.qml
@@ -56,12 +56,35 @@ ListItem {
MenuItem {
text: qsTr("Play Album")
visible: itemData.type === 1 // typeTrack
- onClicked: playlistManager.playAlbumFromTrack(itemData.id)
+ onClicked: playlistManager.playAlbumFromTrack(itemData.trackid)
}
MenuItem {
text: qsTr("Queue")
- onClicked: playlistManager.appendTrack(itemData.id)
+ onClicked: playlistManager.appendTrack(itemData.trackid)
+ }
+
+ MenuItem {
+ text: qsTr("Album Info")
+ onClicked:
+ {
+ pageStack.push(Qt.resolvedUrl("../AlbumPage.qml"),
+ {
+ "albumId" :itemData.albumid
+ })
+ }
+ }
+
+ MenuItem {
+ text: qsTr("Artist Info")
+ onClicked:
+ {
+ console.log(itemData.trackid)
+ pageStack.push(Qt.resolvedUrl("../ArtistPage.qml"),
+ {
+ "artistId" :itemData.artistid
+ })
+ }
}
MenuItem {
@@ -69,7 +92,7 @@ ListItem {
onClicked: delegate.remorseAction(qsTr("Deleting"), function() {
listModel.remove(model.index)
})
- }
+ }
}
onClicked: handleItemClick(itemData)
@@ -109,10 +132,10 @@ ListItem {
function handlePlay(item) {
switch(item.type) {
case 1: // Track
- playlistManager.playTrack(item.id)
+ playlistManager.playTrack(item.trackid)
break
case 2: // Album
- playlistManager.playAlbum(item.id)
+ playlistManager.playAlbum(item.albumid)
break
case 4: // Playlist
tidalApi.playPlaylist(item.uid)
@@ -128,16 +151,21 @@ ListItem {
{
"albumId": item.albumid
})
+ console.log(item.albumid)
+
break
case 2: // Album
pageStack.push(Qt.resolvedUrl("../AlbumPage.qml"),
{
- "albumId" :item.id
+ "albumId" :item.albumid
})
+
break
case 3: // Artist
- pageStack.push(Qt.resolvedUrl("../ArtistPage.qml"))
- tidalApi.getArtistInfo(item.id)
+ pageStack.push(Qt.resolvedUrl("../ArtistPage.qml"),
+ {
+ "artistId" :item.artistid
+ })
break
}
}
diff --git a/qml/playlistmanager.py b/qml/playlistmanager.py
index c59cb43..6ec21b3 100644
--- a/qml/playlistmanager.py
+++ b/qml/playlistmanager.py
@@ -1,5 +1,6 @@
import pyotherside
+
class PlaylistManager:
def __init__(self):
self.current_index = -1
@@ -109,4 +110,5 @@ def clearList(self):
self.playlist = []
self._notify_playlist_state()
+
PL = PlaylistManager()
diff --git a/qml/tidal.py b/qml/tidal.py
index b87bb29..bfbb579 100644
--- a/qml/tidal.py
+++ b/qml/tidal.py
@@ -9,10 +9,18 @@
import tidalapi
import pyotherside
+from requests.exceptions import HTTPError
+
+
class Tidal:
def __init__(self):
self.session = None
self.config = None
+ self.top_tracks = 20
+ self.album_search = 20
+ self.track_search = 20
+ self.artist_search = 20
+
pyotherside.send('loadingStarted')
def initialize(self, quality="HIGH"):
@@ -31,6 +39,12 @@ def initialize(self, quality="HIGH"):
self.config = tidalapi.Config(quality=selected_quality, video_quality=tidalapi.VideoQuality.low)
self.session = tidalapi.Session(self.config)
+ def setconfig(self, top_tracks, album_search, track_search, artist_search):
+ self.top_tracks = top_tracks
+ self.album_search = album_search
+ self.track_search = track_search
+ self.artist_search = artist_search
+
def login(self, token_type, access_token, refresh_token, expiry_time):
if access_token == token_type:
pyotherside.send("oauth_login_failed")
@@ -63,10 +77,12 @@ def request_oauth(self):
def handle_track(self, track):
try:
return {
- "id": str(track.id),
+ "trackid": str(track.id),
"title": str(track.name),
"artist": str(track.artist.name),
+ "artistid": str(track.artist.id),
"album": str(track.album.name),
+ "albumid": int(track.album.id),
"duration": int(track.duration),
"image": track.album.image(320) if hasattr(track.album, 'image') else "",
"track_num" : track.track_num,
@@ -80,7 +96,7 @@ def handle_track(self, track):
def handle_artist(self, artist):
try:
return {
- "id": str(artist.id),
+ "artistid": str(artist.id),
"name": str(artist.name),
"image": artist.image(320) if hasattr(artist, 'image') else "",
"type": "artist",
@@ -90,14 +106,29 @@ def handle_artist(self, artist):
print(f"Error handling artist: {e}")
return None
+ except HTTPError as e:
+ if e.response.status_code == 404:
+ return {
+ "artistid": str(artist.id),
+ "name": str(artist.name),
+ "image": artist.image(320) if hasattr(artist, 'image') else "",
+ "type": "artist",
+ "bio" : ""
+ }
+ else:
+ return f"Error fetching biography: {str(e)}"
+
def handle_album(self, album):
try:
return {
- "id": str(album.id),
+ "albumid": int(album.id),
"title": str(album.name),
"artist": str(album.artist.name),
+ "artistid" : str(album.artist.id),
"image": album.image(320) if hasattr(album, 'image') else "",
"duration": int(album.duration) if hasattr(album, 'duration') else 0,
+ "num_tracks": int(album.num_tracks),
+ "year": int(album.year),
"type": "album"
}
except AttributeError as e:
@@ -129,7 +160,7 @@ def handle_playlist(self, playlist):
def genericSearch(self, text):
pyotherside.send('loadingStarted')
- result = self.session.search(text)
+ result = self.session.search(text,limit=self.top_tracks)
search_results = {
"tracks": [],
"artists": [],
@@ -282,7 +313,7 @@ def playAlbumTracks(self, id):
track_info = self.handle_track(track)
if track_info:
pyotherside.send("cacheTrack", track_info)
- pyotherside.send("addTracktoPL", track_info['id'])
+ pyotherside.send("addTracktoPL", track_info['trackid'])
pyotherside.send("fillFinished")
def playAlbumfromTrack(self, id):
@@ -290,7 +321,7 @@ def playAlbumfromTrack(self, id):
track_info = self.handle_track(track)
if track_info:
pyotherside.send("cacheTrack", track_info)
- pyotherside.send("addTracktoPL", track_info['id'])
+ pyotherside.send("addTracktoPL", track_info['trackid'])
pyotherside.send("fillFinished")
def getTopTracks(self, id, max):
@@ -300,7 +331,7 @@ def getTopTracks(self, id, max):
if track_info:
pyotherside.send("cacheTrack", track_info)
pyotherside.send("addTrack",
- track_info['id'],
+ track_info['trackid'],
track_info['title'],
track_info['album'],
track_info['artist'],
@@ -343,7 +374,7 @@ def playPlaylist(self, id):
track_info = self.handle_track(track)
if track_info:
pyotherside.send("cacheTrack", track_info)
- pyotherside.send("addTracktoPL", track_info['id'])
+ pyotherside.send("addTracktoPL", track_info['trackid'])
#if i == 0:
# pyotherside.send("fillStarted")
@@ -363,4 +394,48 @@ def getPlaylistTracks(self, playlist_id):
finally:
pyotherside.send('loadingFinished')
+ def getAlbumsofArtist(self, id):
+ pyotherside.send('loadingStarted')
+ albums = self.session.artist(int(id)).get_albums()
+ for ti in albums:
+ i = self.handle_album(ti)
+ pyotherside.send("cacheAlbum", i)
+ pyotherside.send("AlbumofArtist", i)
+
+ pyotherside.send('loadingFinished')
+
+ def getTopTracksofArtist(self, id):
+ pyotherside.send('loadingStarted')
+ tracks = self.session.artist(int(id)).get_top_tracks(self.top_tracks)
+ for ti in tracks:
+ i = self.handle_track(ti)
+ pyotherside.send("cacheTrack", i)
+ pyotherside.send("TopTrackofArtist", i)
+
+ pyotherside.send('loadingFinished')
+
+ def getSimiliarArtist(self, id):
+ pyotherside.send('loadingStarted')
+ try:
+ artists = self.session.artist(int(id)).get_similar()
+ if artists: # Wenn Artists zurückgegeben wurden
+ for ti in artists:
+ i = self.handle_artist(ti)
+ pyotherside.send("cacheArtist", i)
+ pyotherside.send("SimilarArtist", i)
+ else:
+ pyotherside.send("noSimilarArtists") # Signal wenn keine ähnlichen Künstler gefunden
+ except requests.exceptions.HTTPError as e:
+ if e.response.status_code == 404:
+ print(f"Keine ähnlichen Künstler gefunden für ID: {id}")
+ pyotherside.send("noSimilarArtists")
+ else:
+ print(f"HTTP Fehler beim Abrufen ähnlicher Künstler: {e}")
+ pyotherside.send("apiError", str(e))
+ except Exception as e:
+ print(f"Allgemeiner Fehler beim Abrufen ähnlicher Künstler: {e}")
+ pyotherside.send("apiError", str(e))
+ finally:
+ pyotherside.send('loadingFinished')
+
Tidaler = Tidal()