import 'package:audio_service/audio_service.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../player/audio_handler.dart'; import '../playlists/create_playlist_dialog.dart'; import '../settings/app_settings.dart'; import '../shared/cover.dart'; import '../shared/favorite_button.dart'; import 'category_service.dart'; import 'database.dart'; import 'playlist_service.dart'; import 'song_detail_sheet.dart'; import 'song_media.dart'; /// Scrollbare Songliste; Tippen spielt die ganze Liste ab dem Song ab. class SongList extends StatelessWidget { const SongList(this.songs, {super.key}); final List songs; /// Öffnet ein Bottom-Sheet mit allen Playlisten, um [song] hinzuzufügen. Future _showAddToPlaylist(BuildContext context, Song song) async { final db = context.read(); final service = context.read(); await showModalBottomSheet( context: context, builder: (sheetContext) => SafeArea( child: StreamBuilder>( stream: db.watchPlaylists(), builder: (context, snapshot) { final playlists = snapshot.data ?? const []; return Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.add), title: const Text('Neue Playlist'), onTap: () async { final newId = await showDialog( context: sheetContext, builder: (_) => const CreatePlaylistDialog(), ); if (newId == null) return; final position = (await db.watchPlaylistSongs(newId).first).length; await service.addSongToPlaylist(newId, song.id, position); if (sheetContext.mounted) { Navigator.pop(sheetContext); ScaffoldMessenger.of(sheetContext).showSnackBar( const SnackBar(content: Text('Zu Playlist hinzugefügt')), ); } }, ), for (final playlist in playlists) ListTile( leading: const Icon(Icons.playlist_play), title: Text(playlist.name), onTap: () async { final position = (await db.watchPlaylistSongs(playlist.id).first).length; await service.addSongToPlaylist(playlist.id, song.id, position); if (sheetContext.mounted) { Navigator.pop(sheetContext); ScaffoldMessenger.of(sheetContext).showSnackBar( SnackBar(content: Text('Zu ${playlist.name} hinzugefügt')), ); } }, ), ], ); }, ), ), ); } /// "Künstler | Kategorie1 · Kategorie2" — wie im UI-Entwurf. static String _subtitle(Song song, List categories) { final artist = song.artist ?? 'Unbekannt'; if (categories.isEmpty) return artist; return '$artist | ${categories.join(' · ')}'; } /// Menü hinter dem Drei-Punkte-Symbol einer Songzeile. /// /// Die Offline-Meldungen greifen hier heute nie: diese Liste zeigt nur /// Titel der Bibliothek, und die liegen als Datei vor. Sie stehen trotzdem /// da, weil die Alternative — den Rückgabewert wegwerfen und "hinzugefügt" /// melden — still lügen würde, sobald ein Server-Titel hier ankommt. Future _showMenu(BuildContext context, Song song) async { final handler = context.read(); final categories = context.read(); final settings = context.read(); final messenger = ScaffoldMessenger.of(context); MediaItem item() => songToMediaItem( song, cover: categories.coverFor(song, groupByCategory: settings.groupCoversByCategory), ); await showModalBottomSheet( context: context, builder: (sheetContext) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.info_outline), title: const Text('Metadaten'), onTap: () { Navigator.pop(sheetContext); SongDetailSheet.show(context, song); }, ), ListTile( leading: const Icon(Icons.playlist_play), title: const Text('Als Nächstes spielen'), onTap: () async { Navigator.pop(sheetContext); final ok = await handler.playNext(item()); messenger.showSnackBar(SnackBar( content: Text(ok ? 'Läuft als Nächstes' : nichtOfflineHinweis))); }, ), ListTile( leading: const Icon(Icons.queue), title: const Text('Zur Warteschlange hinzufügen'), subtitle: const Text( 'Nur für jetzt — nicht in Favoriten oder Wiedergabelisten'), onTap: () async { Navigator.pop(sheetContext); final ok = await handler.addToQueue(item()); messenger.showSnackBar(SnackBar( content: Text(ok ? 'Zur Warteschlange hinzugefügt' : nichtOfflineHinweis))); }, ), ListTile( leading: const Icon(Icons.playlist_add), title: const Text('Zu Wiedergabeliste hinzufügen'), onTap: () { Navigator.pop(sheetContext); _showAddToPlaylist(context, song); }, ), ], ), ), ); } @override Widget build(BuildContext context) { final handler = context.read(); final categories = context.watch(); final settings = context.watch(); return ListView.builder( itemCount: songs.length, itemBuilder: (context, i) { final s = songs[i]; final cover = categories.coverFor(s, groupByCategory: settings.groupCoversByCategory); return ListTile( leading: CoverImage( artUri: cover != null ? Uri.file(cover) : null, size: 48, radius: 6, ), title: Text(s.title, maxLines: 1, overflow: TextOverflow.ellipsis), subtitle: Text( _subtitle(s, categories.of(s.id)), maxLines: 1, overflow: TextOverflow.ellipsis, ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ FavoriteButton(songId: s.id), IconButton( tooltip: 'Mehr', icon: const Icon(Icons.more_vert), onPressed: () => _showMenu(context, s), ), ], ), onTap: () async { final messenger = ScaffoldMessenger.of(context); try { final uebersprungen = await playSongs( handler, songs, i, coverOf: (song) => categories.coverFor(song, groupByCategory: settings.groupCoversByCategory), ); if (uebersprungen > 0) { messenger.showSnackBar( SnackBar(content: Text(offlineHinweis(uebersprungen))), ); } } catch (e) { messenger.showSnackBar( SnackBar(content: Text('Wiedergabe fehlgeschlagen: $e')), ); } }, ); }, ); } }