v2.51.3 — Schnellaktionen per Longpress auf Songs

## 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)
This commit is contained in:
Dustin
2026-08-05 07:57:37 +02:00
parent fdfc166807
commit 4b9cfefe00
+118 -14
View File
@@ -134,14 +134,15 @@ class SongTile extends StatelessWidget {
], ],
), ),
onTap: hatDatei ? () => onPlay(song) : null, onTap: hatDatei ? () => onPlay(song) : null,
onLongPress: (hatDatei && (onSpieleAlsNaechstes != null || onAmEndeHinzufuegen != null)) onLongPress: hatDatei ? () => _zeigeKontextMenu(context) : null,
? () => _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) { void _zeigeKontextMenu(BuildContext context) {
final hatTags = song.id != null;
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
backgroundColor: MeloTheme.dunkel1, backgroundColor: MeloTheme.dunkel1,
@@ -149,29 +150,132 @@ class SongTile extends StatelessWidget {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
ListTile( // Song-Info-Kopfzeile
leading: const Icon(Icons.playlist_play, color: MeloTheme.rot, size: 20), Padding(
title: const Text( padding: const EdgeInsets.fromLTRB(20, 14, 20, 6),
'Als Nächstes abspielen', child: Row(
style: TextStyle(color: Colors.white, fontSize: 14), 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: () { onTap: () {
Navigator.pop(ctx); 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) if (onAmEndeHinzufuegen != null)
ListTile( ListTile(
leading: const Icon(Icons.playlist_add, color: MeloTheme.rot, size: 20), leading: const Icon(Icons.playlist_add, color: MeloTheme.rot, size: 20),
title: const Text( title: const Text('Am Ende hinzufügen', style: TextStyle(color: Colors.white, fontSize: 14)),
'Am Ende hinzufügen',
style: TextStyle(color: Colors.white, fontSize: 14),
),
onTap: () { onTap: () {
Navigator.pop(ctx); Navigator.pop(ctx);
onAmEndeHinzufuegen!(song); 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<bool>(
context: context,
builder: (_) => MetadatenDialog(song: song),
);
if (geaendert == true) onMetadataChanged();
},
),
const SizedBox(height: 8),
], ],
), ),
), ),