import 'package:flutter/material.dart'; import 'dart:async'; import '../viewmodels/melo_home_viewmodel.dart'; import '../models/song.dart'; import '../models/playlist.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'; import 'download_screen.dart'; import 'cloud_screen.dart'; import 'settings_screen.dart'; import 'login_screen.dart'; import '../services/cloud_service.dart'; import '../services/auth_service.dart'; import '../config/app_config.dart'; class MeloHome extends StatefulWidget { const MeloHome({super.key}); @override State createState() => _MeloHomeState(); } class _MeloHomeState extends State { final MeloHomeViewModel _vm = MeloHomeViewModel(); final CloudService _cloud = CloudService(); int _aktiverTab = 0; @override void initState() { super.initState(); _vm.ladeSongs(); } @override void dispose() { _vm.dispose(); super.dispose(); } Future _zeigeSuche() async { final controller = TextEditingController(); String modus = 'Alle'; final ergebnis = await showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, setDialogState) => AlertDialog( backgroundColor: MeloTheme.dunkel1, title: const Text('🔍 Song suchen', style: TextStyle(color: Colors.white, fontSize: 18)), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: controller, autofocus: true, style: const TextStyle(color: Colors.white), decoration: const InputDecoration( hintText: 'Titel, Künstler oder Tag...', hintStyle: TextStyle(color: Colors.grey), border: OutlineInputBorder(), ), ), const SizedBox(height: 12), Row( children: [ _suchChip('Alle', modus == 'Alle', () => setDialogState(() => modus = 'Alle')), const SizedBox(width: 6), _suchChip('Titel', modus == 'Titel', () => setDialogState(() => modus = 'Titel')), const SizedBox(width: 6), _suchChip('Künstler', modus == 'Künstler', () => setDialogState(() => modus = 'Künstler')), const SizedBox(width: 6), _suchChip('Tag', modus == 'Tag', () => setDialogState(() => modus = 'Tag')), ], ), ], ), actions: [ TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('Abbrechen')), TextButton( onPressed: () => Navigator.pop(ctx, '${controller.text}|$modus'), child: const Text('Suchen', style: TextStyle(color: MeloTheme.rot)), ), ], ), ), ); if (ergebnis == null || ergebnis.isEmpty) return; final teile = ergebnis.split('|'); final suchtext = teile[0].toLowerCase(); final suchModus = teile.length > 1 ? teile[1] : 'Alle'; if (suchtext.isEmpty) return; // Tag-Namen die zum Suchtext passen final matchingTags = _vm.tags .where((t) => t['name'] != 'Alle' && t['name']!.toLowerCase().contains(suchtext)) .map((t) => t['name']!) .toSet(); final gefiltert = _vm.songs.where((s) { if (suchModus == 'Titel') return s.titel.toLowerCase().contains(suchtext); if (suchModus == 'Künstler') return s.kuenstler.toLowerCase().contains(suchtext); if (suchModus == 'Tag') { return s.tagIds != null && matchingTags.any((name) { final tag = _vm.tagsMap[name]; return tag != null && s.tagIds!.contains(tag.id); }); } // Alle if (s.titel.toLowerCase().contains(suchtext)) return true; if (s.kuenstler.toLowerCase().contains(suchtext)) return true; return s.tagIds != null && matchingTags.any((name) { final tag = _vm.tagsMap[name]; return tag != null && s.tagIds!.contains(tag.id); }); }).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); _vm.spieleSong(gefiltert[i]); }, ), ), ), actions: [TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('Schließen'))], ), ); } Future _scanMusik() async { final erlaubt = await _vm.scanner.frageSpeicherZugriff(); if (!erlaubt) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Bitte Speicherzugriff erlauben')), ); } return; } await _vm.scanner.scanneMusikOrdner(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Scannen fertig: ${_vm.scanner.anzahlNeueSongs} neue Songs gefunden')), ); await _vm.ladeSongs(); } } void _zeigeServerBrowser() { if (_vm.navidrome.istVerbunden) { // Abmelden _vm.navidrome.setCredentials('', '', ''); _vm.navidromeAlben.clear(); setState(() {}); return; } // Verbinden: einfacher Login-Dialog final urlCtrl = TextEditingController(text: AppConfig.navidromeUrl); final userCtrl = TextEditingController(); final passCtrl = TextEditingController(); showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: MeloTheme.dunkel1, title: const Text('🌐 Navidrome verbinden', style: TextStyle(color: Colors.white, fontSize: 15)), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField(controller: urlCtrl, style: const TextStyle(color: Colors.white), decoration: const InputDecoration(labelText: 'Server', border: OutlineInputBorder(), labelStyle: TextStyle(color: Colors.grey))), const SizedBox(height: 8), TextField(controller: userCtrl, style: const TextStyle(color: Colors.white), decoration: const InputDecoration(labelText: 'Benutzer', border: OutlineInputBorder(), labelStyle: TextStyle(color: Colors.grey))), const SizedBox(height: 8), TextField(controller: passCtrl, obscureText: true, style: const TextStyle(color: Colors.white), decoration: const InputDecoration(labelText: 'Passwort', border: OutlineInputBorder(), labelStyle: TextStyle(color: Colors.grey))), ], ), actions: [ TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('Abbrechen')), TextButton( onPressed: () async { _vm.verbindeNavidrome(urlCtrl.text, userCtrl.text, passCtrl.text); await _vm.ladeNavidromeAlben(); await _vm.navidrome.speichereZugangsdaten(urlCtrl.text, userCtrl.text, passCtrl.text); if (ctx.mounted) Navigator.pop(ctx); setState(() {}); }, child: const Text('Verbinden', style: TextStyle(color: MeloTheme.rot)), ), ], ), ); } void _zeigeAddToPlaylist(Song song) async { final playlists = await _vm.playlists.allePlaylists(); if (!mounted || playlists.isEmpty) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Erst eine Playlist erstellen')), ); } return; } final auswahl = await showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: MeloTheme.dunkel1, title: const Text('Zu Playlist hinzufügen', style: TextStyle(color: Colors.white, fontSize: 16)), content: SizedBox( width: double.maxFinite, child: ListView.builder( shrinkWrap: true, itemCount: playlists.length, itemBuilder: (_, i) => ListTile( leading: const Icon(Icons.queue_music, color: MeloTheme.rot, size: 18), title: Text(playlists[i].name, style: const TextStyle(color: Colors.white, fontSize: 14)), subtitle: Text('${playlists[i].songCount} Songs', style: const TextStyle(fontSize: 11, color: MeloTheme.textSekundaer)), onTap: () => Navigator.pop(ctx, playlists[i]), ), ), ), actions: [TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('Abbrechen'))], ), ); if (auswahl != null && song.id != null) { await _vm.playlists.songHinzufuegen(auswahl.id!, song.id!); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('→ ${auswahl.name}')), ); } } } Future _zeigeDownloadDialog() async { // Statt Dialog → zum Download-Tab wechseln setState(() => _aktiverTab = 1); } @override Widget build(BuildContext context) { return ListenableBuilder( listenable: _vm, builder: (_, _) { if (_vm.ladt) { return const Scaffold( backgroundColor: MeloTheme.schwarz, body: Center(child: CircularProgressIndicator(color: MeloTheme.rot)), ); } final gesamtMB = _vm.songs.isEmpty ? '0' : (_vm.songs.fold(0, (int s, Song song) => s + song.groesseBytes) / 1048576).toStringAsFixed(0); final gesamtMin = _vm.songs.isEmpty ? 0 : (_vm.songs.fold(0, (int s, Song song) => s + song.dauerSekunden) / 60).round(); return Scaffold( backgroundColor: MeloTheme.schwarz, body: SafeArea( child: _aktiverTab == 1 ? DownloadScreen( downloader: _vm.downloader, onSongsChanged: _vm.ladeSongs, ) : _aktiverTab == 4 ? CloudScreen( cloud: _cloud, onZurueck: () => setState(() => _aktiverTab = 0), ) : Column( children: [ MeloHeader(onDownload: _zeigeDownloadDialog, onSearch: _zeigeSuche, onServer: _zeigeServerBrowser), // ─── EIN/AUS: RecentWidget (Zuletzt gehört) ─── // Entferne die Kommentarzeichen um RecentWidget zu aktivieren: // RecentWidget(songs: _vm.letzteSongs, onPlay: _vm.spieleSong), StatistikCard( anzahlSongs: _vm.songs.length, gesamtMB: gesamtMB, gesamtMin: gesamtMin, anzahlFavoriten: _vm.favoritenIds.length, ), // ─── EIN/AUS: Hidden Message "Seit 2008" ─── // Entferne die Kommentarzeichen um die Botschaft zu aktivieren: if (_vm.zeigeBotschaft) _botschaftBanner(), TagLeiste( tags: _vm.tags, aktiveTags: _vm.aktiveTags, onTagToggled: _vm.toggleTag, ), // ─── EIN/AUS: TagStatsWidget (Tag-Counts) ─── // Entferne die Kommentarzeichen um TagStatsWidget zu aktivieren: // TagStatsWidget(tagCounts: _vm.tagCounts), Expanded(child: _songListe()), const MiniPlayer(), const SizedBox(height: 8), ], ), ), bottomNavigationBar: _bottomNav(), ); }, ); } Widget _songListe() { final songs = _vm.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${_vm.aktiveTags.isNotEmpty ? ' (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 && _vm.favoritenIds.contains(songs[i].id), onFavoriteToggle: _vm.favoritenUmschalten, onPlay: _vm.spieleSong, onMetadataChanged: _vm.ladeSongs, onAddToPlaylist: _zeigeAddToPlaylist, ), ), ), ], ); } Future _oeffneEinstellungen() async { final result = await Navigator.push( context, MaterialPageRoute(builder: (_) => const SettingsScreen()), ); if (!mounted || result == null) return; switch (result) { case 'server': _zeigeServerBrowser(); break; case 'logout': await AuthService().logout(); if (mounted) { Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => const LoginScreen()), ); } break; } } 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, currentIndex: _aktiverTab, onTap: (i) { if (i == 5) { // Einstellungen als Screen öffnen _oeffneEinstellungen(); return; } setState(() => _aktiverTab = i); // Zurück zu Musik → Filter zurücksetzen wenn Favoriten aktiv if (i == 0 && _vm.aktiveTags.contains('★ Favoriten')) { _vm.aktiveTags.clear(); } else if (i == 3) { _vm.aktiveTags = {'★ Favoriten'}; } }, items: const [ BottomNavigationBarItem(icon: Icon(Icons.music_note, size: 22), label: 'Musik'), BottomNavigationBarItem(icon: Icon(Icons.add_circle, size: 22), label: 'Lied +'), BottomNavigationBarItem(icon: Icon(Icons.label, size: 22), label: 'Tags'), BottomNavigationBarItem(icon: Icon(Icons.favorite, size: 22), label: 'Favoriten'), BottomNavigationBarItem(icon: Icon(Icons.cloud, size: 22), label: 'Cloud'), BottomNavigationBarItem(icon: Icon(Icons.settings, size: 22), label: 'Einstellungen'), ], ), ); } /// Hilfs-Widget für Such-Modus-Chips Widget _suchChip(String label, bool aktiv, VoidCallback onTap) { return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, 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)), ), ); } /// Banner "💌 Seit 2008" – erscheint nach 10 Playbacks Widget _botschaftBanner() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( gradient: const LinearGradient(colors: [Color(0xFF2A0000), Color(0xFF1A0000)]), borderRadius: BorderRadius.circular(12), border: Border.all(color: MeloTheme.rot.withValues(alpha: 0.4)), ), child: Row( children: [ const Text('💌', style: TextStyle(fontSize: 20)), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: const [ Text('Seit 2008', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700, color: Colors.white)), Text('Danke, dass du immer da bist ♥', style: TextStyle(fontSize: 11, color: MeloTheme.textSekundaer)), ], ), ), GestureDetector( onTap: _vm.botschaftAusblenden, child: const Icon(Icons.close, size: 16, color: MeloTheme.textSekundaer), ), ], ), ), ); } }