import 'dart:io'; import 'package:flutter/material.dart'; import '../models/song.dart'; import '../utils/farb_theme.dart'; import 'metadaten_dialog.dart'; import 'tag_auswahl_dialog.dart'; class SongTile extends StatelessWidget { final Song song; final bool istFavorit; final ValueChanged onFavoriteToggle; final ValueChanged onPlay; final VoidCallback onMetadataChanged; final ValueChanged? onAddToPlaylist; final ValueChanged? onErneutHerunterladen; final ValueChanged? onSpieleAlsNaechstes; final ValueChanged? onAmEndeHinzufuegen; const SongTile({ super.key, required this.song, required this.istFavorit, required this.onFavoriteToggle, required this.onPlay, required this.onMetadataChanged, this.onAddToPlaylist, this.onErneutHerunterladen, this.onSpieleAlsNaechstes, this.onAmEndeHinzufuegen, }); @override Widget build(BuildContext context) { final hatDatei = song.dateiPfad.isNotEmpty; final hatCover = song.coverPfad != null && File(song.coverPfad!).existsSync(); return ListTile( contentPadding: const EdgeInsets.symmetric(vertical: 2), leading: ClipRRect( borderRadius: BorderRadius.circular(12), child: Container( width: 44, height: 44, decoration: BoxDecoration( gradient: LinearGradient( colors: [ MeloTheme.akzent.withValues(alpha: 0.6), MeloTheme.akzent.withValues(alpha: 0.15), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), child: hatCover ? Image.file(File(song.coverPfad!), fit: BoxFit.cover) : const Center(child: Text('♪', style: TextStyle(fontSize: 18, color: Colors.white54))), ), ), title: Row( children: [ if (song.istKorrupt) const Padding( padding: EdgeInsets.only(right: 6), child: Text('⚠️', style: TextStyle(fontSize: 16)), ), Expanded( child: Text( song.titel, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: song.istKorrupt ? MeloTheme.textSekundaer : Colors.white, decoration: song.istKorrupt ? TextDecoration.lineThrough : null, ), ), ), ], ), subtitle: Text(song.kuenstler, style: const TextStyle(fontSize: 12, color: MeloTheme.textSekundaer)), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ // ── Korrupt: 🔄 Neu laden (Server-Quelle via cloud_id oder Legacy-ytUrl) ── if (song.istKorrupt && (song.cloudId != null || song.ytUrl != null) && onErneutHerunterladen != null) InkWell( borderRadius: BorderRadius.circular(50), onTap: () => onErneutHerunterladen!(song), child: const Padding( padding: EdgeInsets.all(6), child: Text('🔄', style: TextStyle(fontSize: 16)), ), ), InkWell( borderRadius: BorderRadius.circular(50), onTap: () async { final geaendert = await showDialog( context: context, builder: (_) => MetadatenDialog(song: song), ); if (geaendert == true) onMetadataChanged(); }, child: Padding( padding: const EdgeInsets.all(6), child: Icon(Icons.edit, size: 14, color: MeloTheme.textSekundaer), ), ), InkWell( borderRadius: BorderRadius.circular(50), onTap: () => showDialog( context: context, builder: (_) => TagAuswahlDialog( songId: song.id!, songTitel: song.titel, onChanged: onMetadataChanged, ), ), child: Padding( padding: const EdgeInsets.all(6), child: const Icon(Icons.label_outline, size: 14, color: MeloTheme.textSekundaer), ), ), if (onAddToPlaylist != null) InkWell( borderRadius: BorderRadius.circular(50), onTap: () => onAddToPlaylist!(song), child: Padding( padding: const EdgeInsets.all(6), child: const Icon(Icons.playlist_add, size: 14, color: MeloTheme.textSekundaer), ), ), InkWell( borderRadius: BorderRadius.circular(50), onTap: () => onFavoriteToggle(song), child: Padding( padding: const EdgeInsets.all(6), child: Icon( istFavorit ? Icons.favorite : Icons.favorite_border, size: 16, color: MeloTheme.akzent, ), ), ), Text(song.dauerFormatiert, style: const TextStyle(fontSize: 12, color: MeloTheme.textSekundaer)), ], ), onTap: hatDatei ? () => onPlay(song) : null, onLongPress: hatDatei ? () => _zeigeKontextMenu(context) : null, ); } /// Kontextmenü per Long-Press: Schnellaktionen auf den Song. /// Zeigt nur Aktionen, deren Funktionen verfügbar sind: /// ▶️ Abspielen · ➕ Warteschlange · ⭐ Favorit · ⬇️ Download · 🏷 Tags · ✏️ Umbenennen void _zeigeKontextMenu(BuildContext context) { final hatTags = song.id != null; showModalBottomSheet( context: context, backgroundColor: MeloTheme.dunkel1, builder: (ctx) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ // Song-Info-Kopfzeile Padding( padding: const EdgeInsets.fromLTRB(20, 14, 20, 6), child: Row( children: [ ClipRRect( borderRadius: BorderRadius.circular(8), child: Container( width: 36, height: 36, decoration: const BoxDecoration( gradient: LinearGradient(colors: [Color(0xFF1A0000), Color(0xFF660000)]), ), child: const Center(child: Text('♪', style: TextStyle(fontSize: 14, color: Colors.white54))), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( song.titel, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w600), ), Text( song.kuenstler, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(color: MeloTheme.textSekundaer, fontSize: 12), ), ], ), ), ], ), ), const Divider(color: MeloTheme.dunkel2, height: 12), // ▶️ Abspielen ListTile( leading: const Icon(Icons.play_arrow, color: MeloTheme.rot, size: 20), title: const Text('Abspielen', style: TextStyle(color: Colors.white, fontSize: 14)), onTap: () { Navigator.pop(ctx); onPlay(song); }, ), // ➕ Warteschlange (Play Next) if (onSpieleAlsNaechstes != null) ListTile( leading: const Icon(Icons.playlist_play, color: MeloTheme.rot, size: 20), title: const Text('Als Nächstes abspielen', style: TextStyle(color: Colors.white, fontSize: 14)), onTap: () { Navigator.pop(ctx); onSpieleAlsNaechstes!.call(song); }, ), // ➕ Am Ende hinzufügen if (onAmEndeHinzufuegen != null) ListTile( leading: const Icon(Icons.playlist_add, color: MeloTheme.rot, size: 20), title: const Text('Am Ende hinzufügen', style: TextStyle(color: Colors.white, fontSize: 14)), onTap: () { Navigator.pop(ctx); onAmEndeHinzufuegen!(song); }, ), // ⭐ Favorit ListTile( leading: Icon(istFavorit ? Icons.favorite : Icons.favorite_border, color: MeloTheme.rot, size: 20), title: Text( istFavorit ? 'Favorit entfernen' : 'Zu Favoriten', style: const TextStyle(color: Colors.white, fontSize: 14), ), onTap: () { Navigator.pop(ctx); onFavoriteToggle(song); }, ), // ⬇️ Neu laden – wenn Server-Quelle (cloud_id) oder Legacy-ytUrl existiert if ((song.cloudId != null || song.ytUrl != null) && onErneutHerunterladen != null) ListTile( leading: const Icon(Icons.download, color: MeloTheme.rot, size: 20), title: Text( song.istKorrupt ? 'Erneut herunterladen' : 'Neu laden', style: const TextStyle(color: Colors.white, fontSize: 14), ), onTap: () { Navigator.pop(ctx); onErneutHerunterladen!(song); }, ), // 🏷 Tags – nur wenn der Song in der DB ist if (hatTags) ListTile( leading: const Icon(Icons.label_outline, color: MeloTheme.rot, size: 20), title: const Text('Tags bearbeiten', style: TextStyle(color: Colors.white, fontSize: 14)), onTap: () { Navigator.pop(ctx); showDialog( context: context, builder: (_) => TagAuswahlDialog( songId: song.id!, songTitel: song.titel, onChanged: onMetadataChanged, ), ); }, ), // ✏️ Umbenennen (Metadaten) if (hatTags) ListTile( leading: const Icon(Icons.edit, color: MeloTheme.rot, size: 20), title: const Text('Umbenennen', style: TextStyle(color: Colors.white, fontSize: 14)), onTap: () async { Navigator.pop(ctx); final geaendert = await showDialog( context: context, builder: (_) => MetadatenDialog(song: song), ); if (geaendert == true) onMetadataChanged(); }, ), const SizedBox(height: 8), ], ), ), ); } }