import 'package:audio_service/audio_service.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:simple_gesture_detector/simple_gesture_detector.dart'; import '../services/finamp_settings_helper.dart'; import '../services/media_state_stream.dart'; import 'album_image.dart'; import '../models/jellyfin_models.dart'; import '../services/process_artist.dart'; import '../services/music_player_background_task.dart'; import '../screens/player_screen.dart'; import 'PlayerScreen/progress_slider.dart'; class NowPlayingBar extends StatelessWidget { const NowPlayingBar({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; final barColor = Color.alphaBlend( colorScheme.primary.withOpacity(0.08), colorScheme.surface, ); final audioHandler = GetIt.instance(); return SimpleGestureDetector( onVerticalSwipe: (direction) { if (direction == SwipeDirection.up) { Navigator.of(context).pushNamed(PlayerScreen.routeName); } }, child: StreamBuilder( stream: mediaStateStream, builder: (context, snapshot) { if (snapshot.hasData) { final playing = snapshot.data!.playbackState.playing; // If we have a media item and the player hasn't finished, show // the now playing bar. if (snapshot.data!.mediaItem != null) { final item = BaseItemDto.fromJson( snapshot.data!.mediaItem!.extras!["itemJson"], ); return SafeArea( minimum: const EdgeInsets.fromLTRB(12, 0, 12, 8), child: DecoratedBox( decoration: BoxDecoration( color: barColor, borderRadius: BorderRadius.circular(28), border: Border.all( color: colorScheme.outlineVariant.withOpacity(0.45), ), boxShadow: [ BoxShadow( color: colorScheme.shadow.withOpacity(0.16), blurRadius: 24, offset: const Offset(0, 10), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(28), child: Material( color: Colors.transparent, child: SizedBox( width: MediaQuery.of(context).size.width, height: 78, child: Stack( children: [ Align( alignment: Alignment.topCenter, child: Opacity( opacity: 0.9, child: const ProgressSlider( allowSeeking: false, showBuffer: false, showDuration: false, showPlaceholder: false, ), ), ), Dismissible( key: const Key("NowPlayingBar"), direction: FinampSettingsHelper .finampSettings .disableGesture ? DismissDirection.none : DismissDirection.horizontal, confirmDismiss: (direction) async { if (direction == DismissDirection.endToStart) { audioHandler.skipToNext(); } else { audioHandler.skipToPrevious(); } return false; }, background: Padding( padding: const EdgeInsets.symmetric( horizontal: 20, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Icon( Icons.skip_previous, color: colorScheme.primary, ), Icon( Icons.skip_next, color: colorScheme.primary, ), ], ), ), child: ListTile( contentPadding: const EdgeInsets.fromLTRB( 12, 8, 8, 8, ), minLeadingWidth: 0, onTap: () => Navigator.of( context, ).pushNamed(PlayerScreen.routeName), leading: SizedBox( height: 54, width: 54, child: ClipRRect( borderRadius: BorderRadius.circular(16), child: AlbumImage(item: item), ), ), title: Text( snapshot.data!.mediaItem!.title, softWrap: false, maxLines: 1, overflow: TextOverflow.fade, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w700, ), ), subtitle: Text( processArtist( snapshot.data!.mediaItem!.artist, context, ), maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodySmall?.copyWith( color: colorScheme.onSurfaceVariant, ), ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ if (snapshot .data! .playbackState .processingState != AudioProcessingState.idle) IconButton( key: const ValueKey("StopButton"), icon: const Icon(Icons.stop_rounded), onPressed: () => audioHandler.stop(), ), IconButton.filled( icon: Icon( playing ? Icons.pause_rounded : Icons.play_arrow_rounded, ), onPressed: playing ? () => audioHandler.pause() : () => audioHandler.play(), ), ], ), ), ), ], ), ), ), ), ), ); } else { return const SizedBox.shrink(); } } else { return const SizedBox.shrink(); } }, ), ); } }