import 'package:flutter/material.dart'; import 'dart:async'; import '../database/db_helper.dart'; import '../services/player_service.dart'; import '../services/musik_scanner.dart'; import '../services/favoriten_service.dart'; import '../services/download_service.dart'; import '../models/song.dart'; import '../utils/farb_theme.dart'; import '../widgets/mini_player.dart'; import '../widgets/melo_header.dart'; import '../widgets/statistik_card.dart'; import '../widgets/tag_leiste.dart'; import '../widgets/song_tile.dart'; class MeloHome extends StatefulWidget { const MeloHome({super.key}); @override State createState() => _MeloHomeState(); } class _MeloHomeState extends State { final PlayerService _player = PlayerService(); final DbHelper _db = DbHelper(); final FavoritenService _favoriten = FavoritenService(); final MusikScanner _scanner = MusikScanner(); final DownloadService _downloader = DownloadService(); List _songs = []; String _aktiverTag = 'Alle'; bool _ladt = true; Set _favoritenIds = {}; final List> _beispielTags = [ {'name': 'Alle', 'icon': ''}, {'name': '❤️ Für uns', 'icon': '❤️'}, {'name': 'Nightcore', 'icon': '⚡'}, {'name': 'Traurig', 'icon': '😢'}, {'name': 'Party', 'icon': '🎉'}, {'name': 'Mitsingen', 'icon': '🎤'}, {'name': '2000er', 'icon': '📀'}, ]; @override void initState() { super.initState(); _ladeSongs(); } List get _gefilterteSongs { if (_aktiverTag == 'Alle') return _songs; return _songs.where((s) => s.titel.contains(_aktiverTag) || s.kuenstler.contains(_aktiverTag) ).toList(); } Future _ladeSongs() async { setState(() => _ladt = true); var songs = await _db.alleSongs(); if (songs.isEmpty) { final beispiele = [ Song(titel: 'Leichtes Gepäck', kuenstler: 'Silbermond', album: 'Schritte', dauerSekunden: 232, dateiPfad: '', groesseBytes: 5242880, istHeruntergeladen: true), Song(titel: 'Auf uns', kuenstler: 'Andreas Bourani', album: 'Hey', dauerSekunden: 241, dateiPfad: '', groesseBytes: 4819200, istHeruntergeladen: true), Song(titel: '80 Millionen', kuenstler: 'Max Giesinger', album: 'Der Junge', dauerSekunden: 225, dateiPfad: '', groesseBytes: 5107200, istHeruntergeladen: true), Song(titel: 'Tage wie diese', kuenstler: 'Die Toten Hosen', album: 'Ballast', dauerSekunden: 252, dateiPfad: '', groesseBytes: 4300800, istHeruntergeladen: true), Song(titel: 'Atlantis', kuenstler: 'Frida Gold', album: 'Liebe', dauerSekunden: 228, dateiPfad: '', groesseBytes: 3987200, istHeruntergeladen: true), ]; await _db.songsEinfuegen(beispiele); songs = await _db.alleSongs(); } final favoritenSet = await _favoriten.favoritenIds(); if (mounted) { setState(() { _songs = songs; _favoritenIds = favoritenSet; _ladt = false; }); } } Future _zeigeSuche() async { final controller = TextEditingController(); final ergebnis = await showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: MeloTheme.dunkel1, title: const Text('🔍 Song suchen', style: TextStyle(color: Colors.white, fontSize: 18)), content: TextField( controller: controller, autofocus: true, style: const TextStyle(color: Colors.white), decoration: const InputDecoration( hintText: 'Titel oder Künstler...', hintStyle: TextStyle(color: Colors.grey), border: OutlineInputBorder(), ), ), actions: [ TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('Abbrechen')), TextButton( onPressed: () => Navigator.pop(ctx, controller.text), child: const Text('Suchen', style: TextStyle(color: MeloTheme.rot)), ), ], ), ); if (ergebnis == null || ergebnis.isEmpty) return; final gefiltert = _songs.where((s) => s.titel.toLowerCase().contains(ergebnis.toLowerCase()) || s.kuenstler.toLowerCase().contains(ergebnis.toLowerCase()) ).toList(); if (!mounted) return; showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: MeloTheme.dunkel1, title: Text('🔍 ${gefiltert.length} Treffer', style: const TextStyle(color: Colors.white)), content: SizedBox( width: double.maxFinite, height: 300, child: gefiltert.isEmpty ? const Center(child: Text('Keine Treffer', style: TextStyle(color: Colors.grey))) : ListView.builder( itemCount: gefiltert.length, itemBuilder: (_, i) => ListTile( leading: Container( width: 36, height: 36, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), gradient: const LinearGradient(colors: [Color(0xFF1A0000), Color(0xFF660000)]), ), child: const Center(child: Text('♪', style: TextStyle(fontSize: 14, color: Colors.white54))), ), title: Text(gefiltert[i].titel, style: const TextStyle(color: Colors.white)), subtitle: Text(gefiltert[i].kuenstler, style: const TextStyle(color: Colors.grey)), onTap: () { Navigator.pop(ctx); _spieleSong(gefiltert[i]); }, ), ), ), actions: [TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('Schließen'))], ), ); } Future _scanMusik() async { final erlaubt = await _scanner.frageSpeicherZugriff(); if (!erlaubt) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Bitte Speicherzugriff erlauben')), ); } return; } await _scanner.scanneMusikOrdner(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Scannen fertig: ${_scanner.anzahlNeueSongs} neue Songs gefunden')), ); await _ladeSongs(); } } Future _zeigeDownloadDialog() async { final controller = TextEditingController(); final url = await showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: MeloTheme.dunkel1, title: const Text('⬇ YouTube-Link einfügen', style: TextStyle(color: Colors.white)), content: TextField( controller: controller, autofocus: true, style: const TextStyle(color: Colors.white), decoration: const InputDecoration( hintText: 'https://youtube.com/watch?v=...', hintStyle: TextStyle(color: Colors.grey), border: OutlineInputBorder(), ), ), actions: [ TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('Abbrechen')), TextButton( onPressed: () => Navigator.pop(ctx, controller.text), child: const Text('Download', style: TextStyle(color: MeloTheme.rot)), ), ], ), ); if (url == null || url.isEmpty) return; if (!mounted) return; showDialog( context: context, barrierDismissible: false, builder: (ctx) { Timer? timer; return StatefulBuilder( builder: (ctx, setDialogState) { timer ??= Timer.periodic(const Duration(milliseconds: 200), (_) { if (ctx.mounted) setDialogState(() {}); }); _downloader.downloadVonUrl(url).then((song) { timer?.cancel(); if (song != null && ctx.mounted) { setDialogState(() {}); Future.delayed(const Duration(milliseconds: 800), () { if (ctx.mounted) Navigator.pop(ctx); _ladeSongs(); }); } else if (ctx.mounted) { Future.delayed(const Duration(seconds: 3), () { if (ctx.mounted) Navigator.pop(ctx); }); } }); final fehler = _downloader.fehler; final fortschritt = _downloader.fortschritt; final statusText = fehler ?? _downloader.aktuellerTitel ?? 'Song wird heruntergeladen...'; return AlertDialog( backgroundColor: MeloTheme.dunkel1, title: const Text('⬇ Download', style: TextStyle(color: Colors.white)), content: Column( mainAxisSize: MainAxisSize.min, children: [ if (fehler == null) LinearProgressIndicator( value: fortschritt > 0 ? fortschritt : null, color: MeloTheme.rot, ) else const Icon(Icons.error, color: Colors.red, size: 40), const SizedBox(height: 12), Text(statusText, style: TextStyle(fontSize: 13, color: fehler != null ? Colors.red : Colors.white70), textAlign: TextAlign.center, ), if (fortschritt > 0) ...[ const SizedBox(height: 8), Text('${(fortschritt * 100).toStringAsFixed(0)}%', style: const TextStyle(fontSize: 12, color: Colors.grey)), ], ], ), ); }, ); }, ); } void _spieleSong(Song song) { _player.setWarteschlange(_songs, startIndex: _songs.indexWhere((s) => s.id == song.id)); _player.spiele(song); } Future _favoritenUmschalten(Song song) async { if (song.id == null) return; await _favoriten.umschalten(song.id!); final aktuelleIds = await _favoriten.favoritenIds(); if (mounted) setState(() => _favoritenIds = aktuelleIds); } @override Widget build(BuildContext context) { if (_ladt) { return const Scaffold( backgroundColor: MeloTheme.schwarz, body: Center(child: CircularProgressIndicator(color: MeloTheme.rot)), ); } final gesamtMB = _songs.isEmpty ? '0' : (_songs.fold(0, (int s, Song song) => s + song.groesseBytes) / 1048576).toStringAsFixed(0); final gesamtMin = _songs.isEmpty ? 0 : (_songs.fold(0, (int s, Song song) => s + song.dauerSekunden) / 60).round(); return Scaffold( backgroundColor: MeloTheme.schwarz, body: SafeArea( child: Column( children: [ MeloHeader(onDownload: _zeigeDownloadDialog, onSearch: _zeigeSuche), StatistikCard( anzahlSongs: _songs.length, gesamtMB: gesamtMB, gesamtMin: gesamtMin, anzahlFavoriten: _favoritenIds.length, ), TagLeiste( tags: _beispielTags, aktiverTag: _aktiverTag, onTagSelected: (tag) => setState(() => _aktiverTag = tag), ), Expanded(child: _songListe()), const MiniPlayer(), const SizedBox(height: 8), ], ), ), bottomNavigationBar: _bottomNav(), ); } Widget _songListe() { final songs = _gefilterteSongs; return Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(20, 12, 20, 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('📂 Alle Songs', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white)), Row(children: [ Text('${songs.length} Titel${_aktiverTag != 'Alle' ? ' (gefiltert)' : ''}', style: TextStyle(fontSize: 12, color: MeloTheme.rot)), const SizedBox(width: 8), GestureDetector( onTap: _scanMusik, child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( border: Border.all(color: MeloTheme.dunkel2), borderRadius: BorderRadius.circular(8), ), child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.refresh, size: 12, color: MeloTheme.rot), SizedBox(width: 4), Text('Scannen', style: TextStyle(fontSize: 11, color: MeloTheme.rot)), ], ), ), ), ]), ], ), ), Expanded( child: ListView.builder( padding: const EdgeInsets.fromLTRB(20, 4, 20, 4), itemCount: songs.length, itemBuilder: (_, i) => SongTile( song: songs[i], istFavorit: songs[i].id != null && _favoritenIds.contains(songs[i].id), onFavoriteToggle: _favoritenUmschalten, onPlay: _spieleSong, onMetadataChanged: _ladeSongs, ), ), ), ], ); } Widget _bottomNav() { return Container( decoration: const BoxDecoration( border: Border(top: BorderSide(color: MeloTheme.dunkel1)), ), child: BottomNavigationBar( type: BottomNavigationBarType.fixed, backgroundColor: MeloTheme.schwarz, selectedItemColor: MeloTheme.rot, unselectedItemColor: MeloTheme.textSekundaer, items: const [ BottomNavigationBarItem(icon: Icon(Icons.music_note, size: 22), label: 'Musik'), BottomNavigationBarItem(icon: Icon(Icons.download, size: 22), label: 'Downloads'), BottomNavigationBarItem(icon: Icon(Icons.label, size: 22), label: 'Tags'), BottomNavigationBarItem(icon: Icon(Icons.favorite, size: 22), label: 'Favoriten'), BottomNavigationBarItem(icon: Icon(Icons.settings, size: 22), label: 'Einstellungen'), ], ), ); } }