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:
@@ -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',
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user