feat(navidrome): Erweiterte Service-APIs (Artists, Favoriten)

- 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
This commit is contained in:
Hermes (Server)
2026-08-19 18:34:52 +02:00
parent 12cada99cf
commit 6ba1632082
+88
View File
@@ -178,4 +178,92 @@ class NavidromeService {
Uri? coverUrl(String coverId) {
return _uri('getCoverArt.view', {'id': coverId});
}
Future<bool> 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<bool> 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<List<SubsonicSong>> 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<List<SubsonicArtist>> 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 = <SubsonicArtist>[];
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<List<SubsonicSong>> 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 = <SubsonicSong>[];
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<String, dynamic> j) => SubsonicArtist(
id: j['id'] as String,
name: j['name'] as String? ?? 'Unbekannt',
);
}