🎤 Feature B: Artist-Browse UI (Phase 2, Part 3/3)
- Toggle-Buttons in Navidrome-Tab: "📀 Alben" ↔ "🎤 Künstler" - Artist-Liste zeigt Namen pro Künstler, Tap lädt alle Songs des Künstlers - _playArtist() Methode lädt Künstler-Songs via getArtistSongs() API - Albums/Artists sind Lazy-Loaded (nur beim Toggle laden) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XJzQjUtnvYUtHdnTs3iCru
This commit is contained in:
co-authored by
Claude Haiku 4.5
parent
2548e38af9
commit
3068bbdabb
+120
-18
@@ -253,7 +253,9 @@ class _NavidromeTab extends StatefulWidget {
|
|||||||
class _NavidromeTabState extends State<_NavidromeTab> {
|
class _NavidromeTabState extends State<_NavidromeTab> {
|
||||||
late final NavidromeService _nav = NavidromeService();
|
late final NavidromeService _nav = NavidromeService();
|
||||||
List<SubsonicAlbum> _albums = [];
|
List<SubsonicAlbum> _albums = [];
|
||||||
|
List<SubsonicArtist> _artists = [];
|
||||||
bool _loading = false;
|
bool _loading = false;
|
||||||
|
bool _showArtists = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -286,6 +288,23 @@ class _NavidromeTabState extends State<_NavidromeTab> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _loadArtists() async {
|
||||||
|
if (!_nav.istVerbunden) return;
|
||||||
|
setState(() => _loading = true);
|
||||||
|
try {
|
||||||
|
final artists = await _nav.getArtists();
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_artists = artists;
|
||||||
|
_loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) setState(() => _loading = false);
|
||||||
|
debugPrint('Fehler beim Laden der Künstler: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (!_nav.istVerbunden) {
|
if (!_nav.istVerbunden) {
|
||||||
@@ -331,25 +350,79 @@ class _NavidromeTabState extends State<_NavidromeTab> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ListView.builder(
|
return Column(
|
||||||
padding: const EdgeInsets.all(12),
|
children: [
|
||||||
itemCount: _albums.length,
|
Padding(
|
||||||
itemBuilder: (context, i) {
|
padding: const EdgeInsets.all(12),
|
||||||
final album = _albums[i];
|
child: Row(
|
||||||
return Card(
|
children: [
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
Expanded(
|
||||||
child: ListTile(
|
child: FilledButton(
|
||||||
leading: Icon(
|
onPressed: () => setState(() {
|
||||||
Icons.album,
|
_showArtists = false;
|
||||||
size: 48,
|
if (_albums.isEmpty) _loadAlbums();
|
||||||
color: Colors.redAccent.withValues(alpha: 0.5),
|
}),
|
||||||
),
|
style: FilledButton.styleFrom(
|
||||||
title: Text(album.name),
|
backgroundColor: !_showArtists ? Colors.redAccent : Colors.grey.shade700,
|
||||||
subtitle: Text('${album.songCount} Songs'),
|
),
|
||||||
onTap: () => _playAlbum(album),
|
child: const Text('📀 Alben'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton(
|
||||||
|
onPressed: () => setState(() {
|
||||||
|
_showArtists = true;
|
||||||
|
if (_artists.isEmpty) _loadArtists();
|
||||||
|
}),
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
backgroundColor: _showArtists ? Colors.redAccent : Colors.grey.shade700,
|
||||||
|
),
|
||||||
|
child: const Text('🎤 Künstler'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
Expanded(
|
||||||
|
child: _showArtists
|
||||||
|
? ListView.builder(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
itemCount: _artists.length,
|
||||||
|
itemBuilder: (context, i) {
|
||||||
|
final artist = _artists[i];
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: ListTile(
|
||||||
|
leading: const Icon(Icons.person, size: 48),
|
||||||
|
title: Text(artist.name),
|
||||||
|
onTap: () => _playArtist(artist),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
itemCount: _albums.length,
|
||||||
|
itemBuilder: (context, i) {
|
||||||
|
final album = _albums[i];
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
Icons.album,
|
||||||
|
size: 48,
|
||||||
|
color: Colors.redAccent.withValues(alpha: 0.5),
|
||||||
|
),
|
||||||
|
title: Text(album.name),
|
||||||
|
subtitle: Text('${album.songCount} Songs'),
|
||||||
|
onTap: () => _playAlbum(album),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,4 +460,33 @@ class _NavidromeTabState extends State<_NavidromeTab> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _playArtist(SubsonicArtist artist) async {
|
||||||
|
final handler = context.read<MeloAudioHandler>();
|
||||||
|
try {
|
||||||
|
final songs = await _nav.getArtistSongs(artist.id);
|
||||||
|
if (songs.isEmpty) {
|
||||||
|
if (!mounted) return;
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Künstler hat keine Songs')),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final items = songs
|
||||||
|
.map((s) => MediaItem(
|
||||||
|
id: _nav.streamUrl(s.id).toString(),
|
||||||
|
title: s.titel,
|
||||||
|
artist: s.kuenstler,
|
||||||
|
album: s.album,
|
||||||
|
duration: Duration(seconds: s.dauerSekunden),
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
await handler.loadPlaylist(items);
|
||||||
|
} catch (e) {
|
||||||
|
if (!mounted) return;
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Fehler: $e')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user