import 'package:audio_service/audio_service.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../shared/cover.dart'; import '../shared/favorite_button.dart'; import '../shared/theme.dart'; import 'audio_handler.dart'; /// Vollbild-Wiedergabe: Cover, Titel, Fortschritt, Transport-Controls. class NowPlayingScreen extends StatelessWidget { const NowPlayingScreen({super.key}); @override Widget build(BuildContext context) { final handler = context.read(); return Scaffold( appBar: AppBar( backgroundColor: Colors.transparent, actions: [ 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); }, ), _SleepTimerButton(handler: handler), ], ), body: SafeArea( child: StreamBuilder( stream: handler.mediaItem, builder: (context, snapshot) { final item = snapshot.data; if (item == null) { return const Center(child: Text('Nichts in Wiedergabe')); } return Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Column( children: [ Expanded(child: Center(child: _Cover(item: item))), const SizedBox(height: 24), Text( item.title, style: Theme.of(context).textTheme.headlineSmall, maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, ), const SizedBox(height: 8), Text( item.artist ?? 'Unbekannt', style: const TextStyle(color: Colors.white54), ), const SizedBox(height: 24), _ProgressBar(handler: handler), const _Controls(), const SizedBox(height: 16), ], ), ); }, ), ), ); } } class _Cover extends StatelessWidget { const _Cover({required this.item}); final MediaItem item; @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: 1, child: CoverImage(artUri: item.artUri, radius: 16), ); } } class _ProgressBar extends StatelessWidget { const _ProgressBar({required this.handler}); final MeloAudioHandler handler; @override Widget build(BuildContext context) { return StreamBuilder( stream: handler.durationStream, builder: (context, dSnap) { final total = dSnap.data ?? Duration.zero; return StreamBuilder( stream: handler.positionStream, builder: (context, pSnap) { var position = pSnap.data ?? Duration.zero; final hasDuration = total > Duration.zero; if (hasDuration && position > total) position = total; final maxMs = hasDuration ? total.inMilliseconds.toDouble() : 1.0; final valueMs = position.inMilliseconds.toDouble().clamp(0.0, maxMs).toDouble(); return Column( children: [ Slider( value: valueMs, max: maxMs, semanticFormatterCallback: (v) => _fmt(Duration(milliseconds: v.round())), onChanged: hasDuration ? (v) => handler.seek(Duration(milliseconds: v.round())) : null, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(_fmt(position), style: const TextStyle(color: Colors.white54)), Text(_fmt(total), style: const TextStyle(color: Colors.white54)), ], ), ), ], ); }, ); }, ); } static String _fmt(Duration d) { final m = d.inMinutes.remainder(60).toString().padLeft(2, '0'); final s = d.inSeconds.remainder(60).toString().padLeft(2, '0'); return '$m:$s'; } } class _Controls extends StatelessWidget { const _Controls(); @override Widget build(BuildContext context) { final handler = context.read(); return StreamBuilder( stream: handler.playbackState, builder: (context, snapshot) { final state = snapshot.data; final playing = state?.playing ?? false; final shuffle = state?.shuffleMode == AudioServiceShuffleMode.all; final repeat = state?.repeatMode ?? AudioServiceRepeatMode.none; return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( tooltip: 'Zufallswiedergabe', icon: Icon(Icons.shuffle, color: shuffle ? MeloTheme.red : Colors.white54), onPressed: () => handler.setShuffleMode(shuffle ? AudioServiceShuffleMode.none : AudioServiceShuffleMode.all), ), IconButton( tooltip: 'Vorheriger Titel', iconSize: 40, icon: const Icon(Icons.skip_previous), onPressed: handler.skipToPrevious, ), IconButton( tooltip: playing ? 'Pause' : 'Abspielen', iconSize: 64, icon: Icon(playing ? Icons.pause_circle : Icons.play_circle, color: MeloTheme.red), onPressed: playing ? handler.pause : handler.play, ), IconButton( tooltip: 'Nächster Titel', iconSize: 40, icon: const Icon(Icons.skip_next), onPressed: handler.skipToNext, ), IconButton( tooltip: 'Wiederholen', icon: Icon( repeat == AudioServiceRepeatMode.one ? Icons.repeat_one : Icons.repeat, color: repeat == AudioServiceRepeatMode.none ? Colors.white54 : MeloTheme.red, ), onPressed: () => handler.setRepeatMode(switch (repeat) { AudioServiceRepeatMode.none => AudioServiceRepeatMode.all, AudioServiceRepeatMode.all => AudioServiceRepeatMode.one, _ => AudioServiceRepeatMode.none, }), ), ], ); }, ); } } class _SleepTimerButton extends StatelessWidget { const _SleepTimerButton({required this.handler}); final MeloAudioHandler handler; void _showSleepOptions(BuildContext context) { showModalBottomSheet( context: context, builder: (ctx) => Container( color: Theme.of(ctx).scaffoldBackgroundColor, child: Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.all(16), child: Text( 'Sleep-Timer', style: Theme.of(ctx).textTheme.titleLarge, ), ), ...const [5, 10, 15, 30, 60].map( (minutes) => ListTile( title: Text('$minutes Minuten'), onTap: () { handler.sleepTimer.start(Duration(minutes: minutes)); Navigator.pop(ctx); }, ), ), ValueListenableBuilder( valueListenable: handler.sleepTimer.remaining, builder: (_, remaining, _) => remaining == null ? const SizedBox.shrink() : ListTile( title: const Text('Timer beenden'), leading: const Icon(Icons.close), onTap: () { handler.sleepTimer.cancel(); Navigator.pop(ctx); }, ), ), const SizedBox(height: 16), ], ), ), ); } @override Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: handler.sleepTimer.remaining, builder: (context, remaining, _) { if (remaining == null) { return IconButton( tooltip: 'Sleep-Timer', icon: const Icon(Icons.bedtime_outlined), onPressed: () => _showSleepOptions(context), ); } final minutes = remaining.inMinutes; final seconds = (remaining.inSeconds % 60).toString().padLeft(2, '0'); return IconButton( tooltip: 'Sleep-Timer: noch $minutes:$seconds', icon: Icon(Icons.bedtime, color: MeloTheme.red), onPressed: () => _showSleepOptions(context), ); }, ); } }