import 'package:audio_service/audio_service.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../player/audio_handler.dart'; import '../services/navidrome_service.dart'; /// Online-Tab: durchsucht den verbundenen Navidrome-Server. Abgespielte /// Titel werden vom [MeloAudioHandler] automatisch lokal zwischengespeichert /// und stehen danach offline zur Verfügung. class DownloadsScreen extends StatelessWidget { const DownloadsScreen({super.key}); @override Widget build(BuildContext context) { return const SafeArea( bottom: false, child: Column( children: [ Padding( padding: EdgeInsets.fromLTRB(20, 16, 20, 4), child: Align( alignment: Alignment.centerLeft, child: Text('Online', style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700)), ), ), Expanded(child: _ServerBrowser()), ], ), ); } } class _ServerBrowser extends StatefulWidget { const _ServerBrowser(); @override State<_ServerBrowser> createState() => _ServerBrowserState(); } class _ServerBrowserState extends State<_ServerBrowser> { late final NavidromeService _nav = NavidromeService(); List _albums = []; List _artists = []; bool _loading = false; bool _showArtists = false; String? _error; @override void initState() { super.initState(); _nav.ladeGespeicherteZugangsdaten().then((_) { if (_nav.istVerbunden && mounted) { _loadAlbums(); } }); } Future _loadAlbums() async { if (!_nav.istVerbunden) { return; } setState(() { _loading = true; _error = null; }); try { final alben = await _nav.getAlben(anzahl: 50); if (mounted) { setState(() { _albums = alben; _loading = false; }); } } catch (e) { if (mounted) { setState(() { _loading = false; _error = e is NavidromeException ? e.message : 'Verbindungsfehler'; }); } debugPrint('Fehler beim Laden der Alben: $e'); } } Future _loadArtists() async { if (!_nav.istVerbunden) return; setState(() { _loading = true; _error = null; }); try { final artists = await _nav.getArtists(); if (mounted) { setState(() { _artists = artists; _loading = false; }); } } catch (e) { if (mounted) { setState(() { _loading = false; _error = e is NavidromeException ? e.message : 'Verbindungsfehler'; }); } debugPrint('Fehler beim Laden der Künstler: $e'); } } @override Widget build(BuildContext context) { if (!_nav.istVerbunden) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.cloud_off, size: 64, color: Colors.white24), const SizedBox(height: 12), const Text('Musikserver nicht verbunden', style: TextStyle(color: Colors.white54)), const SizedBox(height: 12), const Text('Gehe zu Einstellungen → Navidrome zum Verbinden', style: TextStyle(color: Colors.white30, fontSize: 12)), ], ), ); } if (_loading) { return const Center( child: CircularProgressIndicator(color: Colors.redAccent), ); } if (_albums.isEmpty) { final hatFehler = _error != null; return Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 32), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(hatFehler ? Icons.error_outline : Icons.album, size: 64, color: hatFehler ? Colors.redAccent : Colors.white24), const SizedBox(height: 12), Text( hatFehler ? 'Serverfehler: $_error' : 'Keine Alben geladen', textAlign: TextAlign.center, style: const TextStyle(color: Colors.white54), ), const SizedBox(height: 16), FilledButton.icon( icon: const Icon(Icons.refresh), label: const Text('Erneut versuchen'), onPressed: _loadAlbums, ), ], ), ), ); } return Column( children: [ Padding( padding: const EdgeInsets.all(12), child: Row( children: [ Expanded( child: FilledButton( onPressed: () => setState(() { _showArtists = false; if (_albums.isEmpty) _loadAlbums(); }), style: FilledButton.styleFrom( backgroundColor: !_showArtists ? Colors.redAccent : Colors.grey.shade700, ), child: const Text('📀 Alben'), ), ), const SizedBox(width: 8), Expanded( child: FilledButton( onPressed: () => setState(() { _showArtists = true; if (_artists.isEmpty) _loadArtists(); }), style: FilledButton.styleFrom( backgroundColor: _showArtists ? Colors.redAccent : Colors.grey.shade700, ), child: const Text('🎤 Künstler'), ), ), ], ), ), Expanded( child: _showArtists ? ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 12), itemCount: _artists.length, itemBuilder: (context, i) { final artist = _artists[i]; return Card( margin: const EdgeInsets.only(bottom: 8), child: ListTile( leading: const Icon(Icons.person, size: 48), title: Text(artist.name), onTap: () => _playArtist(artist), ), ); }, ) : ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 12), itemCount: _albums.length, itemBuilder: (context, i) { final album = _albums[i]; return Card( margin: const EdgeInsets.only(bottom: 8), child: ListTile( leading: Icon( Icons.album, size: 48, color: Colors.redAccent.withValues(alpha: 0.5), ), title: Text(album.name), subtitle: Text('${album.songCount} Songs'), onTap: () => _playAlbum(album), ), ); }, ), ), ], ); } Future _playAlbum(SubsonicAlbum album) async { final handler = context.read(); try { final songs = await _nav.getSongs(album.id); if (songs.isEmpty) { if (!mounted) { return; } final messenger = ScaffoldMessenger.of(context); messenger.showSnackBar( const SnackBar(content: Text('Album hat keine Songs')), ); return; } final items = songs .map((s) => MediaItem( id: _nav.streamUrl(s.id).toString(), title: s.titel, artist: s.kuenstler, album: s.album, duration: Duration(seconds: s.dauerSekunden), )) .toList(); await handler.loadPlaylist(items); } catch (e) { if (!mounted) { return; } final messenger = ScaffoldMessenger.of(context); messenger.showSnackBar( SnackBar(content: Text('Fehler: $e')), ); } } Future _playArtist(SubsonicArtist artist) async { final handler = context.read(); try { final songs = await _nav.getArtistSongs(artist.id); if (songs.isEmpty) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Künstler hat keine Songs')), ); return; } final items = songs .map((s) => MediaItem( id: _nav.streamUrl(s.id).toString(), title: s.titel, artist: s.kuenstler, album: s.album, duration: Duration(seconds: s.dauerSekunden), )) .toList(); await handler.loadPlaylist(items); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Fehler: $e')), ); } } }