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:
+118
-14
@@ -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<bool>(
|
||||
context: context,
|
||||
builder: (_) => MetadatenDialog(song: song),
|
||||
);
|
||||
if (geaendert == true) onMetadataChanged();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user