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,
|
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),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user