From 6ba16320827ccbc409889ab73c75d3766f4ec2ce Mon Sep 17 00:00:00 2001 From: "Hermes (Server)" Date: Wed, 19 Aug 2026 18:34:52 +0200 Subject: [PATCH] feat(navidrome): Erweiterte Service-APIs (Artists, Favoriten) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - getArtists(): Alle Künstler vom Server laden - getArtistSongs(): Alle Songs eines Künstlers - setFavorite()/removeFavorite(): Server-Favoriten toggle - getFavorites(): Favoriten vom Server laden - SubsonicArtist-Klasse für Künstler APIs für Phase 2 vorbereitet (Artist-Tab, Server-Favoriten-Sync) Tests: 70/70 grün --- lib/services/navidrome_service.dart | 88 +++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/lib/services/navidrome_service.dart b/lib/services/navidrome_service.dart index 68c5def..83ac7f4 100644 --- a/lib/services/navidrome_service.dart +++ b/lib/services/navidrome_service.dart @@ -178,4 +178,92 @@ class NavidromeService { Uri? coverUrl(String coverId) { return _uri('getCoverArt.view', {'id': coverId}); } + + Future setFavorite(String songId) async { + try { + final r = await http.get(_uri('star.view', {'id': songId})).timeout(const Duration(seconds: 10)); + return r.statusCode == 200; + } catch (e) { + debugPrint('Navidrome setFavorite Fehler: $e'); + return false; + } + } + + Future removeFavorite(String songId) async { + try { + final r = await http.get(_uri('unstar.view', {'id': songId})).timeout(const Duration(seconds: 10)); + return r.statusCode == 200; + } catch (e) { + debugPrint('Navidrome removeFavorite Fehler: $e'); + return false; + } + } + + Future> getFavorites() async { + try { + final r = await http.get(_uri('getStarred.view')).timeout(const Duration(seconds: 15)); + if (r.statusCode != 200) return []; + final data = jsonDecode(r.body); + final songs = data['subsonic-response']?['starred']?['song'] as List? ?? []; + return songs.map((j) => SubsonicSong.fromJson(j)).toList(); + } catch (e) { + debugPrint('Navidrome getFavorites Fehler: $e'); + return []; + } + } + + Future> getArtists() async { + try { + final r = await http.get(_uri('getArtists.view')).timeout(const Duration(seconds: 15)); + if (r.statusCode != 200) return []; + final data = jsonDecode(r.body); + final indexData = data['subsonic-response']?['artists']?['index'] as List? ?? []; + final artists = []; + for (final idx in indexData) { + final artistList = idx['artist'] as List? ?? []; + for (final a in artistList) { + artists.add(SubsonicArtist.fromJson(a)); + } + } + return artists; + } catch (e) { + debugPrint('Navidrome getArtists Fehler: $e'); + return []; + } + } + + Future> getArtistSongs(String artistId) async { + try { + final r = await http.get(_uri('getArtist.view', {'id': artistId})).timeout(const Duration(seconds: 15)); + if (r.statusCode != 200) return []; + final data = jsonDecode(r.body); + final albums = data['subsonic-response']?['artist']?['album'] as List? ?? []; + final songs = []; + for (final album in albums) { + final albumSongs = album['song'] as List? ?? []; + for (final song in albumSongs) { + songs.add(SubsonicSong.fromJson(song)); + } + } + return songs; + } catch (e) { + debugPrint('Navidrome getArtistSongs Fehler: $e'); + return []; + } + } +} + +class SubsonicArtist { + final String id; + final String name; + + const SubsonicArtist({ + required this.id, + required this.name, + }); + + factory SubsonicArtist.fromJson(Map j) => SubsonicArtist( + id: j['id'] as String, + name: j['name'] as String? ?? 'Unbekannt', + ); }