import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../playlists/playlists_screen.dart'; import '../settings/settings_screen.dart'; import '../shared/cover.dart'; import '../shared/sort_store.dart'; import '../shared/sub_tabs.dart'; import '../shared/sortable_song_list.dart'; import '../shared/theme.dart'; import 'album_list.dart'; import 'artist_list.dart'; import 'database.dart'; import 'library_service.dart'; import 'music_recognition_sheet.dart'; import 'permissions.dart'; import 'song_list.dart'; /// Startbildschirm: Kopfzeile mit Einstellungen/Suche/Musikerkennung, /// Schnellzugriffe, Unterreiter (Songs/Künstler/Alben) und darunter der /// jeweilige Inhalt. Bewusst ohne AppBar — die Kopfzeile ist Teil des Inhalts. class MyMusicScreen extends StatefulWidget { const MyMusicScreen({super.key, this.onSearchTap, this.onFavoritesTap}); /// Wechselt zum Suche-Tab; die Suche ist ein eigener Tab, kein eigener Screen. final VoidCallback? onSearchTap; /// Wechselt zum Favoriten-Tab. final VoidCallback? onFavoritesTap; @override State createState() => _MyMusicScreenState(); } class _MyMusicScreenState extends State { /// 0 = Songs, 1 = Künstler, 2 = Alben. int _subTab = 0; @override Widget build(BuildContext context) { final db = context.read(); final lib = context.watch(); return SafeArea( bottom: false, child: Column( children: [ _Header(onSearchTap: widget.onSearchTap), if (lib.permissionDenied) MaterialBanner( backgroundColor: Colors.red.shade900, content: const Text('Zugriff auf Musik verweigert. ' 'Bitte in den Einstellungen erlauben.'), actions: [ TextButton( onPressed: openMusicPermissionSettings, child: const Text('Einstellungen'), ), ], ), if (lib.scanError != null) MaterialBanner( backgroundColor: Colors.red.shade900, content: Text(lib.scanError!), actions: [ TextButton( onPressed: lib.dismissScanError, child: const Text('Verwerfen'), ), ], ), if (lib.scanning) Padding( padding: const EdgeInsets.all(12), child: Column( children: [ const LinearProgressIndicator(), const SizedBox(height: 6), Text( 'Scanne … ${lib.scanDone}' '${lib.scanTotal > 0 ? ' / ${lib.scanTotal}' : ''}', style: const TextStyle(color: Colors.white54, fontSize: 12), ), ], ), ), _QuickAccessRow(onFavoritesTap: widget.onFavoritesTap), SubTabs( labels: const ['Songs', 'Künstler', 'Alben'], index: _subTab, onChanged: (i) => setState(() => _subTab = i), ), Expanded( child: switch (_subTab) { 1 => const ArtistListScreen(), 2 => const AlbumListScreen(), _ => StreamBuilder>( stream: db.watchSongs(), builder: (context, snapshot) { if (snapshot.hasError) { return Center( child: Text('Fehler: ${snapshot.error}', style: const TextStyle(color: Colors.white54)), ); } final songs = snapshot.data ?? const []; return SortableSongList( songs: songs, storeKey: SortStore.meineMusik, empty: lib.scanning ? const SizedBox.shrink() : const _Empty(), ); }, ), }, ), ], ), ); } } /// Einstellungen · Suchfeld · Musikerkennung. class _Header extends StatelessWidget { const _Header({this.onSearchTap}); final VoidCallback? onSearchTap; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.fromLTRB(8, 8, 8, 4), child: Row( children: [ IconButton( tooltip: 'Einstellungen', icon: const Icon(Icons.tune), onPressed: () => Navigator.of(context).push( MaterialPageRoute(builder: (_) => const SettingsScreen()), ), ), Expanded( child: InkWell( onTap: onSearchTap, borderRadius: BorderRadius.circular(24), child: Container( height: 44, decoration: BoxDecoration( color: MeloTheme.surfaceHigh, borderRadius: BorderRadius.circular(24), ), child: const Row( children: [ SizedBox(width: 14), Icon(Icons.search, color: Colors.white54, size: 20), SizedBox(width: 10), Text( 'Titel, Künstler und Alben suchen', style: TextStyle(color: Colors.white54, fontSize: 14), ), ], ), ), ), ), IconButton( tooltip: 'Musik erkennen', icon: const Icon(Icons.help_outline), onPressed: () => MusicRecognitionSheet.show(context), ), ], ), ); } } /// Waagerecht scrollende Schnellzugriffe (Favoriten, Wiedergabelisten, …). class _QuickAccessRow extends StatelessWidget { const _QuickAccessRow({this.onFavoritesTap}); final VoidCallback? onFavoritesTap; @override Widget build(BuildContext context) { final db = context.read(); return SizedBox( height: 104, child: ListView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 12), children: [ StreamBuilder>( stream: db.watchFavorites(), builder: (context, snapshot) { final favorites = snapshot.data ?? const []; return _QuickCard( icon: Icons.favorite, label: 'Favoriten', subtitle: '${favorites.length} Songs', coverPath: favorites.isEmpty ? null : favorites.first.coverPath, onTap: onFavoritesTap, ); }, ), StreamBuilder>( stream: db.watchPlaylists(), builder: (context, snapshot) { final playlists = snapshot.data ?? const []; return _QuickCard( icon: Icons.queue_music, label: 'Wiedergabelisten', subtitle: '${playlists.length} Listen', onTap: () => Navigator.of(context).push( MaterialPageRoute(builder: (_) => const PlaylistsScreen()), ), ); }, ), StreamBuilder>( stream: db.watchRecent(limit: 50), builder: (context, snapshot) { final recent = snapshot.data ?? const []; return _QuickCard( icon: Icons.schedule, label: 'Zuletzt', subtitle: 'Neu hinzugefügt', coverPath: recent.isEmpty ? null : recent.first.coverPath, onTap: () => Navigator.of(context).push( MaterialPageRoute(builder: (_) => const RecentlyAddedScreen()), ), ); }, ), ], ), ); } } class _QuickCard extends StatelessWidget { const _QuickCard({ required this.icon, required this.label, this.subtitle, this.coverPath, this.onTap, }); final IconData icon; final String label; final String? subtitle; final String? coverPath; final VoidCallback? onTap; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), child: SizedBox( width: 150, child: Material( color: MeloTheme.surfaceHigh, borderRadius: BorderRadius.circular(12), clipBehavior: Clip.antiAlias, child: InkWell( onTap: onTap, child: Stack( fit: StackFit.expand, children: [ if (coverPath != null) Opacity( opacity: 0.35, child: CoverImage( artUri: Uri.file(coverPath!), size: 150, radius: 0, ), ), Padding( padding: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Icon(icon, size: 22, color: Colors.white), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text(label, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 14)), if (subtitle != null) Text(subtitle!, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Colors.white54, fontSize: 11)), ], ), ], ), ), ], ), ), ), ), ); } } /// Leerer Zustand mit direktem Weg zum Ordner-Scan. class _Empty extends StatelessWidget { const _Empty(); @override Widget build(BuildContext context) { final lib = context.read(); return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.library_music, size: 64, color: Colors.white24), const SizedBox(height: 12), const Text('Noch keine Musik', style: TextStyle(color: Colors.white54)), const SizedBox(height: 16), FilledButton.icon( icon: const Icon(Icons.create_new_folder_outlined), label: Text(addMusicLabel), onPressed: lib.pickFolderAndScan, ), ], ), ); } } /// Vollbild-Liste der zuletzt hinzugefügten Songs (Schnellzugriff "Zuletzt"). class RecentlyAddedScreen extends StatelessWidget { const RecentlyAddedScreen({super.key}); @override Widget build(BuildContext context) { final db = context.read(); return Scaffold( appBar: AppBar(title: const Text('Zuletzt hinzugefügt')), body: StreamBuilder>( stream: db.watchRecent(limit: 100), builder: (context, snapshot) { final songs = snapshot.data ?? const []; if (songs.isEmpty) { return const Center( child: Text('Noch nichts hinzugefügt', style: TextStyle(color: Colors.white54)), ); } return SongList(songs); }, ), ); } }