import 'package:audio_service/audio_service.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../shared/cover.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` bietet keine Live-Queue-Mutation (kein /// `removeQueueItem`/`moveQueueItem`), daher wird die Queue clientseitig neu /// berechnet und per `loadPlaylist` neu geladen. 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) => _reorder(handler, oldIndex, newIndex), itemBuilder: (context, i) { final item = queue[i]; final playing = item.id == currentId; return ListTile( key: ValueKey(item.id), leading: CoverImage(artUri: item.artUri, size: 48, radius: 6), title: Text( item.title, maxLines: 1, overflow: TextOverflow.ellipsis, style: playing ? 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: () => _remove(handler, i), ), onTap: () => handler.skipToQueueItem(i), ); }, ); }, ); }, ), ); } Future _remove(MeloAudioHandler handler, int index) async { final queue = handler.queue.valueOrNull ?? const []; if (index < 0 || index >= queue.length) return; final playingId = handler.mediaItem.valueOrNull?.id; final position = await handler.positionStream.first; final newQueue = removeFromQueue(queue, index); if (newQueue.isEmpty) { await handler.stop(); return; } final fallbackIndex = index.clamp(0, newQueue.length - 1); final newIndex = newPlayingIndex(newQueue, playingId, fallbackIndex); await handler.loadPlaylist(newQueue, startIndex: newIndex); if (newQueue[newIndex].id == playingId) { await handler.seek(position); } } Future _reorder( MeloAudioHandler handler, int oldIndex, int newIndex) async { final queue = handler.queue.valueOrNull ?? const []; if (queue.isEmpty) return; final playingId = handler.mediaItem.valueOrNull?.id; final position = await handler.positionStream.first; final newQueue = reorderQueue(queue, oldIndex, newIndex); final fallbackIndex = newIndex.clamp(0, newQueue.length - 1); final newPlayIndex = newPlayingIndex(newQueue, playingId, fallbackIndex); await handler.loadPlaylist(newQueue, startIndex: newPlayIndex); if (newQueue[newPlayIndex].id == playingId) { await handler.seek(position); } } } // === Reine Queue-Logik (ohne AudioHandler testbar) === /// Entfernt den Titel an [removeIndex] aus [queue]. List removeFromQueue(List queue, int removeIndex) { return List.from(queue)..removeAt(removeIndex); } /// Verschiebt den Titel von [oldIndex] nach [newIndex]. `newIndex` ist bereits /// um das entfernte Element bereinigt (siehe `ReorderableListView.onReorderItem`). List reorderQueue(List queue, int oldIndex, int newIndex) { final result = List.from(queue); final moved = result.removeAt(oldIndex); result.insert(newIndex, moved); return result; } /// Ermittelt den Index des zuvor abgespielten Titels ([playingId]) in /// [newQueue]. Ist er nicht mehr vorhanden, wird auf [fallbackIndex] /// zurückgefallen (z.B. der Titel, der jetzt an seiner alten Position steht). int newPlayingIndex( List newQueue, String? playingId, int fallbackIndex) { if (newQueue.isEmpty) return 0; if (playingId != null) { final idx = newQueue.indexWhere((item) => item.id == playingId); if (idx != -1) return idx; } return fallbackIndex.clamp(0, newQueue.length - 1); }