diff --git a/lib/player/now_playing_screen.dart b/lib/player/now_playing_screen.dart index 2ebf653..c63b798 100644 --- a/lib/player/now_playing_screen.dart +++ b/lib/player/now_playing_screen.dart @@ -2,6 +2,7 @@ import 'package:audio_service/audio_service.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import '../services/navidrome_service.dart'; import '../shared/cover.dart'; import '../shared/favorite_button.dart'; import '../shared/theme.dart'; @@ -12,6 +13,13 @@ import 'queue_screen.dart'; class NowPlayingScreen extends StatelessWidget { const NowPlayingScreen({super.key}); + void _showLyrics(BuildContext context, String songId) { + showModalBottomSheet( + context: context, + builder: (ctx) => _LyricsSheet(songId: songId), + ); + } + @override Widget build(BuildContext context) { final handler = context.read(); @@ -22,9 +30,19 @@ class NowPlayingScreen extends StatelessWidget { StreamBuilder( stream: handler.mediaItem, builder: (context, snapshot) { - final songId = snapshot.data?.extras?['songId'] as String?; - if (songId == null) return const SizedBox.shrink(); - return FavoriteButton(songId: songId); + final item = snapshot.data; + if (item == null) return const SizedBox.shrink(); + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + tooltip: 'Lyrics', + icon: const Icon(Icons.lyrics), + onPressed: () => _showLyrics(context, item.id), + ), + FavoriteButton(songId: item.extras?['songId'] as String? ?? ''), + ], + ); }, ), _SleepTimerButton(handler: handler), @@ -285,3 +303,83 @@ class _SleepTimerButton extends StatelessWidget { ); } } + +class _LyricsSheet extends StatefulWidget { + const _LyricsSheet({required this.songId}); + final String songId; + + @override + State<_LyricsSheet> createState() => _LyricsSheetState(); +} + +class _LyricsSheetState extends State<_LyricsSheet> { + late final NavidromeService _nav = NavidromeService(); + Lyrics? _lyrics; + bool _loading = true; + + @override + void initState() { + super.initState(); + _nav.ladeGespeicherteZugangsdaten().then((_) { + if (_nav.istVerbunden) { + _nav.getLyrics(widget.songId).then((lyrics) { + if (mounted) { + setState(() { + _lyrics = lyrics; + _loading = false; + }); + } + }); + } else { + setState(() => _loading = false); + } + }); + } + + @override + Widget build(BuildContext context) { + return Container( + color: MeloTheme.black, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('📝 Lyrics', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), + IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.pop(context), + ), + ], + ), + ), + Expanded( + child: _loading + ? const Center( + child: CircularProgressIndicator(color: MeloTheme.red), + ) + : _lyrics?.isEmpty ?? true + ? const Center( + child: Text('Keine Lyrics verfügbar', + style: TextStyle(color: Colors.white54)), + ) + : SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Text( + _lyrics?.text ?? '', + style: const TextStyle( + fontSize: 14, + height: 1.6, + color: Colors.white, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/services/navidrome_service.dart b/lib/services/navidrome_service.dart index 83ac7f4..dc64fa2 100644 --- a/lib/services/navidrome_service.dart +++ b/lib/services/navidrome_service.dart @@ -251,6 +251,19 @@ class NavidromeService { return []; } } + + Future getLyrics(String songId) async { + try { + final r = await http.get(_uri('getLyrics.view', {'id': songId})).timeout(const Duration(seconds: 10)); + if (r.statusCode != 200) return Lyrics.empty(); + final data = jsonDecode(r.body); + final lyrics = data['subsonic-response']?['lyrics']?['value'] as String?; + return Lyrics.fromText(lyrics); + } catch (e) { + debugPrint('Navidrome getLyrics Fehler: $e'); + return Lyrics.empty(); + } + } } class SubsonicArtist { @@ -267,3 +280,17 @@ class SubsonicArtist { name: j['name'] as String? ?? 'Unbekannt', ); } + +class Lyrics { + final String? text; + final bool isEmpty; + + const Lyrics({ + this.text, + this.isEmpty = true, + }); + + factory Lyrics.empty() => const Lyrics(isEmpty: true); + factory Lyrics.fromText(String? text) => + Lyrics(text: text, isEmpty: text?.isEmpty ?? true); +}