From eecd9939f03191f135cdbc5fd509f505adbc2f1b Mon Sep 17 00:00:00 2001 From: Nmstr Date: Sun, 17 Aug 2025 17:40:08 +0200 Subject: [PATCH 1/3] added base for artist display --- SubDrome/Artist/ArtistDisplay.qml | 30 +++++++++++++++++++++++++ SubDrome/Artist/ArtistDisplayTopper.qml | 28 +++++++++++++++++++++++ SubDrome/Artist/qmldir | 1 + SubDrome/Bars/Sidebar.qml | 2 +- SubDrome/Player.qml | 5 +++++ 5 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 SubDrome/Artist/ArtistDisplay.qml create mode 100644 SubDrome/Artist/ArtistDisplayTopper.qml create mode 100644 SubDrome/Artist/qmldir diff --git a/SubDrome/Artist/ArtistDisplay.qml b/SubDrome/Artist/ArtistDisplay.qml new file mode 100644 index 0000000..0026d0b --- /dev/null +++ b/SubDrome/Artist/ArtistDisplay.qml @@ -0,0 +1,30 @@ +import QtQuick 2.15 + +Rectangle { + id: artistDisplay + color: "transparent" + + ArtistDisplayTopper { + id: topper + anchors { + top: parent.top + left: parent.left + right: parent.right + } + + implicitHeight: 50 + } + + Rectangle { + anchors { + top: topper.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + } + anchors.margins: 20 + color: "#424242" + radius: 10 + clip: true + } +} diff --git a/SubDrome/Artist/ArtistDisplayTopper.qml b/SubDrome/Artist/ArtistDisplayTopper.qml new file mode 100644 index 0000000..516e6d0 --- /dev/null +++ b/SubDrome/Artist/ArtistDisplayTopper.qml @@ -0,0 +1,28 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +Rectangle { + id: topper + color: "transparent" + + TextField { + id: searchField + anchors { + left: parent.left + top: parent.top + leftMargin: 20 + topMargin: 20 + } + width: 200 + placeholderText: "Search" + placeholderTextColor: "#888" + color: "white" + font.pixelSize: 16 + background: Rectangle { + color: "#424242" + radius: 5 + border.color: "#888" + } + onTextChanged: {} + } +} diff --git a/SubDrome/Artist/qmldir b/SubDrome/Artist/qmldir new file mode 100644 index 0000000..4d08fda --- /dev/null +++ b/SubDrome/Artist/qmldir @@ -0,0 +1 @@ +ArtistDisplay 1.0 ArtistDisplay.qml diff --git a/SubDrome/Bars/Sidebar.qml b/SubDrome/Bars/Sidebar.qml index 2cfbdf8..d42557e 100644 --- a/SubDrome/Bars/Sidebar.qml +++ b/SubDrome/Bars/Sidebar.qml @@ -42,7 +42,7 @@ Rectangle { onClicked: { albumDisplay.loadAlbums("frequent", 1) } } SidebarItem { iconSource: "qrc:/icons/artist.svg"; label: "Artists"; - onClicked: { } } + onClicked: { contentStack.currentIndex = 3 } } SidebarItem { iconSource: "qrc:/icons/song.svg"; label: "Songs"; onClicked: { } } SidebarItem { iconSource: "qrc:/icons/playlist.svg"; label: "Playlists"; diff --git a/SubDrome/Player.qml b/SubDrome/Player.qml index 88cad20..7e48570 100644 --- a/SubDrome/Player.qml +++ b/SubDrome/Player.qml @@ -5,6 +5,7 @@ import Album 1.0 import Bars 1.0 import Queue 1.0 import Playlist 1.0 +import Artist 1.0 Rectangle { Rectangle { @@ -56,6 +57,10 @@ Rectangle { PlaylistPage { id: playlistPage } + + ArtistDisplay { + id: artistDisplay + } } } From f2738853a52db69375cb3afc34a6cc6f3bab332d Mon Sep 17 00:00:00 2001 From: Nmstr Date: Sun, 17 Aug 2025 18:14:16 +0200 Subject: [PATCH 2/3] artists now display --- SubDrome/Artist/ArtistDisplay.qml | 58 +++++++++++++++++++++++++++++++ SubDrome/Bars/Sidebar.qml | 2 +- SubDrome/api_handler.py | 15 ++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/SubDrome/Artist/ArtistDisplay.qml b/SubDrome/Artist/ArtistDisplay.qml index 0026d0b..fcb6ecc 100644 --- a/SubDrome/Artist/ArtistDisplay.qml +++ b/SubDrome/Artist/ArtistDisplay.qml @@ -4,6 +4,23 @@ Rectangle { id: artistDisplay color: "transparent" + function loadArtists(type, page) { + artistListModel.clear(); + contentStack.currentIndex = 3; + + let artists = apiHandler.get_artists(); + for (let i = 0; i < artists.length; i++) { + let artist = artists[i]; + artistListModel.append({ + id: artist[0], + name: artist[1], + albumCount: artist[2] + }); + } + } + + ListModel { id: artistListModel } + ArtistDisplayTopper { id: topper anchors { @@ -26,5 +43,46 @@ Rectangle { color: "#424242" radius: 10 clip: true + + ListView { + id: artistListView + anchors { + fill: parent + margins: 20 + } + model: artistListModel + + delegate: Rectangle { + width: parent.width + height: 40 + color: index % 2 === 0 ? "#333" : "#222" + + Row { + anchors.fill: parent + spacing: 10 + + Image { + source: "qrc:/icons/artist.svg" + width: 40 + height: 40 + fillMode: Image.PreserveAspectFit + } + + Text { + text: model.name + color: "white" + font.pixelSize: 18 + verticalAlignment: Text.AlignVCenter + } + + Text { + text: model.albumCount + " albums" + color: "#888" + font.pixelSize: 14 + verticalAlignment: Text.AlignVCenter + } + } + } + } } } diff --git a/SubDrome/Bars/Sidebar.qml b/SubDrome/Bars/Sidebar.qml index d42557e..a20969b 100644 --- a/SubDrome/Bars/Sidebar.qml +++ b/SubDrome/Bars/Sidebar.qml @@ -42,7 +42,7 @@ Rectangle { onClicked: { albumDisplay.loadAlbums("frequent", 1) } } SidebarItem { iconSource: "qrc:/icons/artist.svg"; label: "Artists"; - onClicked: { contentStack.currentIndex = 3 } } + onClicked: { artistDisplay.loadArtists() } } SidebarItem { iconSource: "qrc:/icons/song.svg"; label: "Songs"; onClicked: { } } SidebarItem { iconSource: "qrc:/icons/playlist.svg"; label: "Playlists"; diff --git a/SubDrome/api_handler.py b/SubDrome/api_handler.py index acf23ef..88f6ecf 100644 --- a/SubDrome/api_handler.py +++ b/SubDrome/api_handler.py @@ -301,3 +301,18 @@ def set_rating(self, target_id: str, rating: int) -> None: "rating": rating } self._send_request("setRating", extra_params) + + @Slot(result="QVariant") + def get_artists(self): + response = self._send_request("getArtists") + if response.get("status") == "ok": + artists = [] + for indices in response.get("artists", {}).get("index", []): + for artist in indices.get("artist", []): + artists.append([ + artist.get("id", ""), + artist.get("name", ""), + artist.get("albumCount", 0) + ]) + return artists + return [[]] From 5e81966d281f7e6f684e68de9fbf6731c1377ed0 Mon Sep 17 00:00:00 2001 From: Nmstr Date: Sun, 17 Aug 2025 18:32:06 +0200 Subject: [PATCH 3/3] artists can now be searched --- SubDrome/Artist/ArtistDisplay.qml | 6 ++- SubDrome/Artist/ArtistDisplayTopper.qml | 4 +- SubDrome/api_handler.py | 57 ++++++++++++++++++++++--- 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/SubDrome/Artist/ArtistDisplay.qml b/SubDrome/Artist/ArtistDisplay.qml index fcb6ecc..53e86c9 100644 --- a/SubDrome/Artist/ArtistDisplay.qml +++ b/SubDrome/Artist/ArtistDisplay.qml @@ -4,11 +4,13 @@ Rectangle { id: artistDisplay color: "transparent" - function loadArtists(type, page) { + function loadArtists(artists) { artistListModel.clear(); contentStack.currentIndex = 3; - let artists = apiHandler.get_artists(); + if (!artists) { + artists = apiHandler.get_artists(); + } for (let i = 0; i < artists.length; i++) { let artist = artists[i]; artistListModel.append({ diff --git a/SubDrome/Artist/ArtistDisplayTopper.qml b/SubDrome/Artist/ArtistDisplayTopper.qml index 516e6d0..b8b05a2 100644 --- a/SubDrome/Artist/ArtistDisplayTopper.qml +++ b/SubDrome/Artist/ArtistDisplayTopper.qml @@ -23,6 +23,8 @@ Rectangle { radius: 5 border.color: "#888" } - onTextChanged: {} + onTextChanged: { + artistDisplay.loadArtists(apiHandler.search_artists(text, 1)); + } } } diff --git a/SubDrome/api_handler.py b/SubDrome/api_handler.py index 88f6ecf..d87c801 100644 --- a/SubDrome/api_handler.py +++ b/SubDrome/api_handler.py @@ -191,22 +191,39 @@ def download_song(self, song_id: str) -> str: pass return "" - @Slot(str, int) - def search_albums(self, query: str, page: int) -> None: + def _general_search(self, query: str, page: int) -> dict: """ - Search for albums + Perform a general search on the Subsonic server. :param query: The search query. :param page: The page number. + :return: A dictionary containing search results or an empty dictionary if the request fails. """ extra_params = { "query": query, + "artistCount": 20, + "artistOffset": page * 20 - 20, "albumCount": 20, - "albumOffset": page * 20 - 20 + "albumOffset": page * 20 - 20, + "songCount": 20, + "songOffset": page * 20 - 20 } - response = self._send_request("search2", extra_params) + response = self._send_request("search3", extra_params) if response.get("status") == "ok": + return response.get("searchResult3", {}) + return {} + + @Slot(str, int) + def search_albums(self, query: str, page: int) -> None: + """ + Search for albums + :param query: The search query. + :param page: The page number. + """ + search_result = self._general_search(query, page) + response_albums = search_result.get("album", []) + if response_albums: albums = [] - for album in response.get("searchResult2", {}).get("album", []): + for album in response_albums: cover_id = album.get("coverArt", "") album_id = album.get("id", "") self.thread_manager.start(lambda cid=cover_id, aid=album_id: self.get_cover_art(cid, aid)) @@ -218,6 +235,28 @@ def search_albums(self, query: str, page: int) -> None: ]) self.albumsUpdated.emit(albums) + @Slot(str, int, result="QVariant") + def search_artists(self, query: str, page: int) -> list: + """ + Search for artists + :param query: The search query. + :param page: The page number. + :return: A list of artists matching the search query. + """ + search_result = self._general_search(query, page) + response_artists = search_result.get("artist", []) + if response_artists: + artists = [] + for artist in response_artists: + artist_id = artist.get("id", "") + artists.append([ + artist_id, + artist.get("name"), + artist.get("albumCount") + ]) + return artists + return [[]] + @Slot() def update_playlist_list(self) -> None: """ @@ -303,7 +342,11 @@ def set_rating(self, target_id: str, rating: int) -> None: self._send_request("setRating", extra_params) @Slot(result="QVariant") - def get_artists(self): + def get_artists(self) -> list: + """ + Fetch the list of artists from the server. + :return: A list of artists with their ID, name, and album count. + """ response = self._send_request("getArtists") if response.get("status") == "ok": artists = []