import 'package:audio_service/audio_service.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../shared/cover.dart'; import '../shared/lauf_balken.dart'; import '../shared/theme.dart'; import 'audio_handler.dart'; /// Warteschlangen-Ansicht: aktuelle Queue anzeigen, zu einem Titel springen, /// Titel entfernen oder per Drag & Drop umsortieren. /// /// Der `MeloAudioHandler` mutiert die Warteschlange direkt /// ([MeloAudioHandler.removeQueueItemAt], [MeloAudioHandler.moveQueueItem]) — /// die laufende Wiedergabe bleibt dabei unangetastet. class QueueScreen extends StatelessWidget { const QueueScreen({super.key}); @override Widget build(BuildContext context) { final handler = context.read(); return Scaffold( appBar: AppBar(title: const Text('Warteschlange')), body: StreamBuilder>( stream: handler.queue, builder: (context, queueSnap) { final queue = queueSnap.data ?? const []; if (queue.isEmpty) { return const Center( child: Text('Warteschlange ist leer', style: TextStyle(color: MeloTheme.text2)), ); } return StreamBuilder( stream: handler.mediaItem, builder: (context, currentSnap) { final currentId = currentSnap.data?.id; return ReorderableListView.builder( itemCount: queue.length, onReorderItem: (oldIndex, newIndex) => handler.moveQueueItem(oldIndex, newIndex), itemBuilder: (context, i) => _Zeile( key: ValueKey(queue[i].id), item: queue[i], laeuft: queue[i].id == currentId, onSpringen: () => handler.skipToQueueItem(i), onEntfernen: () => handler.removeQueueItemAt(i), ), ); }, ); }, ), ); } } /// Eine Zeile der Warteschlange. /// /// Eigene Klasse, damit der Wiedergabe-Zustand (läuft / pausiert) hier /// beobachtet wird statt in einem dritten `StreamBuilder`, der die ganze /// Liste umschließt und bei jedem Pausieren neu aufbaut. class _Zeile extends StatelessWidget { const _Zeile({ super.key, required this.item, required this.laeuft, required this.onSpringen, required this.onEntfernen, }); final MediaItem item; /// Dieser Eintrag steht in der Wiedergabe — auch pausiert. final bool laeuft; final VoidCallback onSpringen; final VoidCallback onEntfernen; @override Widget build(BuildContext context) { final handler = context.read(); // Nur die laufende Zeile hört wirklich zu — der Zustand meldet auch den // Pufferstand und feuert mehrmals pro Sekunde. Der StreamBuilder bleibt // aber stehen, damit die Zeile beim Titelwechsel nicht ihre Form ändert // und dabei ihr Coverbild neu laden muss. return StreamBuilder( stream: laeuft ? handler.playbackState : null, initialData: handler.playbackState.valueOrNull, builder: (context, zustandSnap) => _kachel(spielt: laeuft && (zustandSnap.data?.playing ?? false)), ); } Widget _kachel({required bool spielt}) { return ListTile( // Dasselbe Zeichen wie in der Bibliothek: Balken über dem Cover, // damit man dieselbe Stelle wiedererkennt. leading: LaufMarkierung( markiert: laeuft, spielt: spielt, radius: 6, child: CoverImage(artUri: item.artUri, size: 48, radius: 6), ), title: Text( item.title, maxLines: 1, overflow: TextOverflow.ellipsis, style: laeuft ? const TextStyle( color: MeloTheme.red, fontWeight: FontWeight.bold) : null, ), subtitle: Text(item.artist ?? 'Unbekannt', maxLines: 1, overflow: TextOverflow.ellipsis), trailing: IconButton( tooltip: 'Aus Warteschlange entfernen', icon: const Icon(Icons.close), onPressed: onEntfernen, ), onTap: onSpringen, ); } }