diff --git a/SubDrome/Artist/ArtistDisplay.qml b/SubDrome/Artist/ArtistDisplay.qml new file mode 100644 index 0000000..53e86c9 --- /dev/null +++ b/SubDrome/Artist/ArtistDisplay.qml @@ -0,0 +1,90 @@ +import QtQuick 2.15 + +Rectangle { + id: artistDisplay + color: "transparent" + + function loadArtists(artists) { + artistListModel.clear(); + contentStack.currentIndex = 3; + + if (!artists) { + 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 { + 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 + + 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/Artist/ArtistDisplayTopper.qml b/SubDrome/Artist/ArtistDisplayTopper.qml new file mode 100644 index 0000000..b8b05a2 --- /dev/null +++ b/SubDrome/Artist/ArtistDisplayTopper.qml @@ -0,0 +1,30 @@ +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: { + artistDisplay.loadArtists(apiHandler.search_artists(text, 1)); + } + } +} 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..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: { } } + onClicked: { artistDisplay.loadArtists() } } 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 + } } } diff --git a/SubDrome/api_handler.py b/SubDrome/api_handler.py index acf23ef..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: """ @@ -301,3 +340,22 @@ 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) -> 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 = [] + 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 [[]]