import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../services/download_service.dart'; import '../utils/farb_theme.dart'; import '../services/melo_logger.dart'; class DownloadScreen extends StatefulWidget { final DownloadService downloader; final VoidCallback onSongsChanged; const DownloadScreen({ super.key, required this.downloader, required this.onSongsChanged, }); @override State createState() => _DownloadScreenState(); } class _DownloadScreenState extends State { final _urlController = TextEditingController(); bool _ladt = false; String? _fehler; String? _erfolg; String _speicherOrt = 'App-intern'; bool _speichertInDownloads = false; @override void initState() { super.initState(); _ladeSpeicherPfad(); } Future _ladeSpeicherPfad() async { final prefs = await SharedPreferences.getInstance(); final inDownloads = prefs.getBool('download_in_downloads') ?? false; if (inDownloads) { final dir = await getDownloadsDirectory(); if (dir != null) { final pfad = '${dir.path}/Melo'; widget.downloader.setzeSpeicherPfad(pfad); setState(() { _speichertInDownloads = true; _speicherOrt = '⬇ Downloads/Melo'; }); } } } Future _ordnerDialog() async { final auswahl = await showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: MeloTheme.dunkel1, title: const Text('Speicherort', style: TextStyle(color: Colors.white, fontSize: 16)), content: Column( mainAxisSize: MainAxisSize.min, children: [ _optionTile(ctx, 'πŸ“ App-intern (Music/)', 'intern', icon: Icons.phone_android), const Divider(color: MeloTheme.dunkel2), _optionTile(ctx, '⬇ Downloads/Melo', 'downloads', icon: Icons.download), ], ), ), ); if (auswahl == null) return; final prefs = await SharedPreferences.getInstance(); if (auswahl == 'downloads') { final dir = await getDownloadsDirectory(); if (dir != null) { final pfad = '${dir.path}/Melo'; await prefs.setBool('download_in_downloads', true); widget.downloader.setzeSpeicherPfad(pfad); setState(() { _speichertInDownloads = true; _speicherOrt = '⬇ Downloads/Melo'; }); } } else { await prefs.setBool('download_in_downloads', false); widget.downloader.setzeSpeicherPfad(''); setState(() { _speichertInDownloads = false; _speicherOrt = 'πŸ“ App-intern (Music/)'; }); } } Widget _optionTile(BuildContext ctx, String label, String wert, {required IconData icon}) { return ListTile( leading: Icon(icon, color: MeloTheme.rot, size: 20), title: Text(label, style: const TextStyle(color: Colors.white, fontSize: 13)), onTap: () => Navigator.pop(ctx, wert), ); } void _starteDownload() async { final input = _urlController.text.trim(); if (input.isEmpty) { setState(() => _fehler = 'Bitte eine YouTube-URL einfΓΌgen'); return; } setState(() { _ladt = true; _fehler = null; _erfolg = null; }); MeloLogger().aktion('download_start', {'url': input.substring(0, 40)}); final anzahl = await widget.downloader.downloadBatch(input); if (mounted) { setState(() { _ladt = false; if (anzahl > 0) { _erfolg = 'βœ… $anzahl Song${anzahl > 1 ? 's' : ''} gespeichert'; } else { _fehler = widget.downloader.fehler ?? 'Download fehlgeschlagen'; } }); widget.onSongsChanged(); } } void _abbrechen() { widget.downloader.abbrechen(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: MeloTheme.schwarz, appBar: AppBar( backgroundColor: MeloTheme.dunkel1, title: const Row(children: [ Icon(Icons.download, color: MeloTheme.rot, size: 20), SizedBox(width: 8), Text('Downloads', style: TextStyle(color: Colors.white, fontSize: 18)), ]), actions: [ if (_erfolg != null || _fehler != null) IconButton( icon: const Icon(Icons.refresh, color: Colors.grey, size: 20), onPressed: () => setState(() { _fehler = null; _erfolg = null; _urlController.clear(); }), ), ], ), body: Padding( padding: const EdgeInsets.all(20), child: Column( children: [ // ─── Zielordner ─── GestureDetector( onTap: _ladt ? null : _ordnerDialog, child: Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(12), border: Border.all(color: MeloTheme.dunkel2), ), child: Row(children: [ const Icon(Icons.folder, color: MeloTheme.rot, size: 18), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Speicherort', style: TextStyle(color: Colors.grey, fontSize: 11)), Text(_speicherOrt, style: const TextStyle(color: Colors.white, fontSize: 13)), ], ), ), const Icon(Icons.chevron_right, color: Colors.grey, size: 18), ]), ), ), const SizedBox(height: 12), // ─── Eingabefeld ─── TextField( controller: _urlController, enabled: !_ladt, maxLines: 3, style: const TextStyle(color: Colors.white, fontSize: 14), decoration: InputDecoration( hintText: 'YouTube-URL hier einfΓΌgen...\n\nMehrere URLs: eine pro Zeile\nPlaylists werden erkannt 🎯', hintStyle: const TextStyle(color: Colors.grey, fontSize: 13), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), filled: true, fillColor: MeloTheme.dunkel1, contentPadding: const EdgeInsets.all(16), ), ), const SizedBox(height: 12), // ─── Download- & Abbruch-Button ─── Row( children: [ Expanded( flex: _ladt ? 3 : 1, child: SizedBox( height: 48, child: ElevatedButton.icon( onPressed: _ladt ? null : _starteDownload, icon: _ladt ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : const Icon(Icons.download, size: 20), label: Text(_ladt ? 'Lade herunter...' : '⬇ Download'), style: ElevatedButton.styleFrom( backgroundColor: MeloTheme.rot, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ), ), ), if (_ladt) ...[ const SizedBox(width: 8), Expanded( flex: 1, child: SizedBox( height: 48, child: ElevatedButton.icon( onPressed: _abbrechen, icon: const Icon(Icons.cancel, size: 20), label: const Text('Stop'), style: ElevatedButton.styleFrom( backgroundColor: Colors.red.shade800, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ), ), ), ], ], ), const SizedBox(height: 16), // ─── Live-Status via ListenableBuilder ─── if (_ladt) ListenableBuilder( listenable: widget.downloader, builder: (context, _) { final status = widget.downloader.aktuellerTitel; final fortschritt = widget.downloader.fortschritt; return Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(12), ), child: Column(children: [ if (status != null) ...[ Text(status, style: const TextStyle(color: Colors.white70, fontSize: 13), textAlign: TextAlign.center), ], if (fortschritt > 0) ...[ const SizedBox(height: 8), LinearProgressIndicator( value: fortschritt, color: MeloTheme.rot, backgroundColor: MeloTheme.dunkel2), const SizedBox(height: 4), Text('${(fortschritt * 100).toStringAsFixed(0)}%', style: const TextStyle(color: Colors.grey, fontSize: 11)), ], ]), ); }, ), // ─── Erfolg ─── if (_erfolg != null) Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.green.withValues(alpha: 0.3)), ), child: Row(children: [ const Icon(Icons.check_circle, color: Colors.green, size: 24), const SizedBox(width: 12), Expanded(child: Text(_erfolg!, style: const TextStyle(color: Colors.green, fontSize: 13))), ]), ), // ─── Fehler ─── if (_fehler != null) Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.red.withValues(alpha: 0.3)), ), child: Row(children: [ const Icon(Icons.error_outline, color: Colors.red, size: 24), const SizedBox(width: 12), Expanded(child: Text(_fehler!, style: const TextStyle(color: Colors.red, fontSize: 13))), ]), ), const Spacer(), // ─── Tipps ─── Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('πŸ’‘ Tipps', style: TextStyle(color: Colors.grey, fontSize: 12, fontWeight: FontWeight.w600)), const SizedBox(height: 6), _tipp('Einzel-URL: youtube.com/watch?v=...'), _tipp('Playlist: youtube.com/playlist?list=...'), _tipp('Mehrere: eine URL pro Zeile'), _tipp('Cooldown: 5s zwischen Downloads ⏱'), ], ), ), const SizedBox(height: 20), ], ), ), ); } Widget _tipp(String text) { return Padding( padding: const EdgeInsets.only(bottom: 4), child: Row(children: [ const Text('β€’ ', style: TextStyle(color: MeloTheme.rot, fontSize: 12)), Expanded(child: Text(text, style: const TextStyle(color: Colors.grey, fontSize: 11))), ]), ); } }