From 4b9cfefe00335744fbf2c6fd671eb087fe15533d Mon Sep 17 00:00:00 2001 From: Dustin Date: Wed, 5 Aug 2026 07:57:37 +0200 Subject: [PATCH] =?UTF-8?q?v2.51.3=20=E2=80=94=20Schnellaktionen=20per=20L?= =?UTF-8?q?ongpress=20auf=20Songs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Schnellaktionen (song_tile.dart) - Longpress öffnet Bottom-Sheet mit: ▶️ Abspielen, ➕ Als Nächstes, ➕ Am Ende hinzufügen, ⭐ Favorit (hinzufügen/entfernen), ⬇️ Herunterladen/Erneut herunterladen, 🏷 Tags bearbeiten, ✏️ Umbenennen - Nur verfügbare Aktionen werden gezeigt (Download nur mit ytUrl, Tags/Umbenennen nur mit DB-id) - Song-Info-Kopfzeile im Sheet (Titel + Künstler) --- lib/widgets/song_tile.dart | 132 +++++++++++++++++++++++++++++++++---- 1 file changed, 118 insertions(+), 14 deletions(-) diff --git a/lib/widgets/song_tile.dart b/lib/widgets/song_tile.dart index 80499c7..70e8e57 100644 --- a/lib/widgets/song_tile.dart +++ b/lib/widgets/song_tile.dart @@ -134,14 +134,15 @@ class SongTile extends StatelessWidget { ], ), onTap: hatDatei ? () => onPlay(song) : null, - onLongPress: (hatDatei && (onSpieleAlsNaechstes != null || onAmEndeHinzufuegen != null)) - ? () => _zeigeKontextMenu(context) - : null, + onLongPress: hatDatei ? () => _zeigeKontextMenu(context) : null, ); } - /// Kontextmenü per Long-Press: "Als Nächstes abspielen" + "Am Ende hinzufügen" + /// 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, @@ -149,29 +150,132 @@ class SongTile extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - 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), + // 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); - onSpieleAlsNaechstes?.call(song); + 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), - ), + 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); + }, + ), + // ⬇️ Download – nur wenn eine YouTube-Quell-URL existiert + if (song.ytUrl != null && onErneutHerunterladen != null) + ListTile( + leading: const Icon(Icons.download, color: MeloTheme.rot, size: 20), + title: Text( + song.istKorrupt ? 'Erneut herunterladen' : 'Herunterladen', + 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), ], ), ),