import 'dart:async'; import 'package:flutter/material.dart'; import '../services/player_service.dart'; import '../models/song.dart'; import '../utils/farb_theme.dart'; /// Bottom-Sheet mit der aktuellen Warteschlange: /// aktueller Song markiert, Tipp = abspielen, Drag-and-Drop = umsortieren. class WarteschlangeSheet extends StatefulWidget { const WarteschlangeSheet({super.key}); @override State createState() => _WarteschlangeSheetState(); } class _WarteschlangeSheetState extends State { final PlayerService _player = PlayerService(); Timer? _tick; @override void initState() { super.initState(); // Sekunden-Tick für die Sleep-Timer-Restzeitanzeige _tick = Timer.periodic(const Duration(seconds: 1), (_) { if (mounted && _player.sleepTimerAktiv) setState(() {}); }); } @override void dispose() { _tick?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return ListenableBuilder( listenable: _player, builder: (context, _) { final queue = _player.warteschlange; final aktueller = _player.aktuellerIndex; return SafeArea( child: Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: ConstrainedBox( constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.75, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Griff Container( width: 36, height: 4, margin: const EdgeInsets.only(top: 10, bottom: 4), decoration: BoxDecoration( color: MeloTheme.dunkel2, borderRadius: BorderRadius.circular(2), ), ), // Kopfzeile Padding( padding: const EdgeInsets.fromLTRB(20, 8, 8, 4), child: Row( children: [ const Icon(Icons.queue_music, size: 18, color: MeloTheme.rot), const SizedBox(width: 8), Text( 'Warteschlange', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white, ), ), const SizedBox(width: 8), Text( '${queue.length} Titel', style: const TextStyle( fontSize: 12, color: MeloTheme.textSekundaer, ), ), const Spacer(), IconButton( icon: const Icon(Icons.close, size: 18, color: MeloTheme.textSekundaer), onPressed: () => Navigator.pop(context), tooltip: 'Schließen', ), ], ), ), const Divider(height: 1, color: MeloTheme.dunkel2), _sleepTimerZeile(), const Divider(height: 1, color: MeloTheme.dunkel2), // Liste if (queue.isEmpty) const Expanded( child: Center( child: Text( 'Keine Songs in der Warteschlange', style: TextStyle(fontSize: 13, color: MeloTheme.textSekundaer), ), ), ) else Flexible( child: ReorderableListView.builder( shrinkWrap: true, padding: const EdgeInsets.fromLTRB(8, 4, 8, 16), itemCount: queue.length, // onReorderItem liefert den newIndex bereits angepasst onReorderItem: (alt, neu) => _player.verschiebeInWarteschlange(alt, neu), itemBuilder: (_, i) => _eintrag(queue[i], i, aktueller), ), ), ], ), ), ), ); }, ); } /// Sleep-Timer-Zeile: Optionen Aus/15/30/45/60 Min + Restzeit wenn aktiv Widget _sleepTimerZeile() { final aktiv = _player.sleepTimerAktiv; final rest = _player.sleepRestzeit; return Padding( padding: const EdgeInsets.fromLTRB(20, 8, 20, 8), child: Row( children: [ Icon( Icons.bedtime, size: 16, color: aktiv ? MeloTheme.rot : MeloTheme.textSekundaer, ), const SizedBox(width: 8), const Text( 'Schlaf-Timer', style: TextStyle(fontSize: 13, color: Colors.white), ), if (aktiv && rest != null) ...[ const SizedBox(width: 8), Text( _formatRestzeit(rest), style: const TextStyle(fontSize: 12, color: MeloTheme.rot), ), ], const Spacer(), _zeitChip('Aus', null, aktiv: !aktiv), _zeitChip('15', const Duration(minutes: 15)), _zeitChip('30', const Duration(minutes: 30)), _zeitChip('45', const Duration(minutes: 45)), _zeitChip('60', const Duration(minutes: 60)), ], ), ); } Widget _zeitChip(String label, Duration? dauer, {bool aktiv = false}) { return GestureDetector( onTap: () { if (dauer == null) { _player.cancelSleepTimer(); } else { _player.setSleepTimer(dauer); } }, child: Container( margin: const EdgeInsets.only(left: 6), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: aktiv ? MeloTheme.rot : MeloTheme.dunkel2, borderRadius: BorderRadius.circular(12), ), child: Text( label, style: TextStyle( fontSize: 11, color: aktiv ? Colors.white : MeloTheme.textSekundaer, ), ), ), ); } String _formatRestzeit(Duration rest) { final min = rest.inMinutes; final sek = rest.inSeconds % 60; return '⏱ ${min.toString().padLeft(2, '0')}:${sek.toString().padLeft(2, '0')}'; } Widget _eintrag(Song song, int index, int aktueller) { final istAktuell = index == aktueller; return ListTile( key: ValueKey('$index-${song.id ?? song.titel}'), dense: true, leading: istAktuell ? const Icon(Icons.play_arrow, color: MeloTheme.rot, size: 18) : SizedBox( width: 18, child: Text( '${index + 1}', textAlign: TextAlign.center, style: const TextStyle(fontSize: 11, color: MeloTheme.textSekundaer), ), ), title: Text( song.titel, style: TextStyle( fontSize: 13, fontWeight: istAktuell ? FontWeight.w700 : FontWeight.w400, color: istAktuell ? MeloTheme.rot : Colors.white, ), ), subtitle: Text( song.kuenstler, style: const TextStyle(fontSize: 11, color: MeloTheme.textSekundaer), ), trailing: Text( song.dauerFormatiert, style: const TextStyle(fontSize: 11, color: MeloTheme.textSekundaer), ), onTap: () => _player.spiele(song), ); } }