import 'dart:io'; import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:marquee/marquee.dart'; import 'package:tuple/tuple.dart'; import 'package:audio_service/audio_service.dart'; import 'package:line_icons/line_icons.dart'; import '../type/episodebrief.dart'; import '../state/audio_state.dart'; import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/key_value_storage.dart'; import '../util/pageroute.dart'; import '../util/colorize.dart'; import '../util/context_extension.dart'; import '../util/custompaint.dart'; import '../util/custom_slider.dart'; import '../episodes/episode_detail.dart'; import 'playlist.dart'; import 'audiopanel.dart'; final List _customShadow = [ BoxShadow(blurRadius: 26, offset: Offset(-6, -6), color: Colors.white), BoxShadow( blurRadius: 8, offset: Offset(2, 2), color: Colors.grey[600].withOpacity(0.4)) ]; final List _customShadowNight = [ BoxShadow( blurRadius: 6, offset: Offset(-1, -1), color: Colors.grey[100].withOpacity(0.3)), BoxShadow(blurRadius: 8, offset: Offset(2, 2), color: Colors.black) ]; String _stringForSeconds(double seconds) { if (seconds == null) return null; return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}'; } const List minsToSelect = [10, 15, 20, 25, 30, 45, 60, 70, 80, 90, 99]; const List speedToSelect = [0.5, 0.6, 0.8, 1.0, 1.2, 1.5, 2.0]; class PlayerWidget extends StatefulWidget { @override _PlayerWidgetState createState() => _PlayerWidgetState(); } class _PlayerWidgetState extends State { final GlobalKey miniPlaylistKey = GlobalKey(); Widget _playlist(BuildContext context) { var audio = Provider.of(context, listen: false); return Container( alignment: Alignment.topLeft, height: 300, width: double.infinity, decoration: BoxDecoration( color: Theme.of(context).primaryColor, ), child: Column( children: [ Container( padding: EdgeInsets.symmetric(horizontal: 20.0), height: 60.0, // color: context.primaryColorDark, alignment: Alignment.centerLeft, child: Row( children: [ Container( padding: EdgeInsets.symmetric(horizontal: 20.0), height: 20.0, // color: context.primaryColorDark, alignment: Alignment.centerLeft, child: Text( context.s.homeMenuPlaylist, style: TextStyle( color: Theme.of(context).accentColor, fontWeight: FontWeight.bold, fontSize: 16), ), ), Spacer(), Container( height: 60, alignment: Alignment.center, child: Container( alignment: Alignment.center, height: 30, width: 60, decoration: BoxDecoration( color: Theme.of(context).primaryColor, borderRadius: BorderRadius.all(Radius.circular(15)), border: Border.all( color: Theme.of(context).brightness == Brightness.dark ? Colors.black12 : Colors.white10, width: 1), boxShadow: Theme.of(context).brightness == Brightness.dark ? _customShadowNight : _customShadow), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.all(Radius.circular(15)), onTap: () { audio.playNext(); miniPlaylistKey.currentState.removeItem( 0, (context, animation) => Container()); miniPlaylistKey.currentState.insertItem(0); }, child: SizedBox( height: 30, width: 60, child: Icon( Icons.skip_next, size: 30, ), ), ), ), ), ), Container( margin: EdgeInsets.only(left: 20), width: 30.0, height: 30.0, decoration: BoxDecoration( boxShadow: (Theme.of(context).brightness == Brightness.dark) ? _customShadowNight : _customShadow, color: Theme.of(context).primaryColor, shape: BoxShape.circle, ), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.all(Radius.circular(15.0)), onTap: () { Navigator.push( context, SlideLeftRoute(page: PlaylistPage()), ); }, child: SizedBox( height: 30.0, width: 30.0, child: Transform.rotate( angle: math.pi, child: Icon( LineIcons.database_solid, size: 20.0, ), ), ), ), ), ), ], ), ), Expanded( child: Selector, bool>>( selector: (_, audio) => Tuple2(audio.queue.playlist, audio.queueUpdate), builder: (_, data, __) { return AnimatedList( key: miniPlaylistKey, shrinkWrap: true, scrollDirection: Axis.vertical, initialItemCount: data.item1.length, itemBuilder: (context, index, animation) => ScaleTransition( alignment: Alignment.center, scale: animation, child: index == 0 || index > data.item1.length - 1 ? Center() : Column( children: [ Row( children: [ Expanded( child: Material( color: Colors.transparent, child: InkWell( onTap: () { audio.episodeLoad(data.item1[index]); miniPlaylistKey.currentState .removeItem( index, (context, animation) => Center()); miniPlaylistKey.currentState .insertItem(0); }, child: Container( height: 60, padding: EdgeInsets.symmetric( horizontal: 20), alignment: Alignment.centerLeft, child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Container( padding: EdgeInsets.all(10.0), child: ClipRRect( borderRadius: BorderRadius.all( Radius.circular( 15.0)), child: Container( height: 30.0, width: 30.0, child: Image.file(File( "${data.item1[index].imagePath}"))), ), ), Expanded( child: Container( alignment: Alignment.centerLeft, child: Text( data.item1[index].title, maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ), ], ), ), ), ), ), Container( margin: EdgeInsets.symmetric(horizontal: 20), width: 30.0, height: 30.0, decoration: BoxDecoration( boxShadow: (Theme.of(context).brightness == Brightness.dark) ? _customShadowNight : _customShadow, color: Theme.of(context).primaryColor, shape: BoxShape.circle, ), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.all( Radius.circular(15.0)), onTap: () async { await audio .moveToTop(data.item1[index]); miniPlaylistKey.currentState .removeItem( index, (context, animation) => Center(), duration: Duration(milliseconds: 500), ); miniPlaylistKey.currentState .insertItem(1, duration: Duration( milliseconds: 200)); }, child: SizedBox( height: 30.0, width: 30.0, child: Transform.rotate( angle: math.pi, child: Icon( LineIcons.download_solid, size: 20.0, ), ), ), ), ), ), ], ), Divider(height: 2), ], ), ), ); }, ), ), ], ), ); } Widget _expandedPanel(BuildContext context) { return DefaultTabController( initialIndex: 1, length: 3, child: Stack( children: [ TabBarView( children: [ SleepMode(), ControlPanel(), _playlist(context), // ShareClip(), ], ), Positioned( bottom: 10, left: MediaQuery.of(context).size.width / 2 - 25, child: Container( alignment: Alignment.center, width: 50.0, height: 10.0, //color: Colors.blue, child: TabBar( labelPadding: EdgeInsets.only(top: 0), indicatorPadding: EdgeInsets.all(0), indicatorSize: TabBarIndicatorSize.tab, indicatorWeight: 0, indicator: BoxDecoration( shape: BoxShape.circle, color: Theme.of(context).accentColor, ), tabs: [ Container( // child: Text('p'), height: 8.0, width: 8.0, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.transparent, border: Border.all( color: Theme.of(context).accentColor, width: 2.0)), ), Container( height: 8.0, width: 8.0, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.transparent, border: Border.all( color: Theme.of(context).accentColor, width: 2.0)), ), Container( height: 8.0, width: 8.0, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.transparent, border: Border.all( color: Theme.of(context).accentColor, width: 2.0)), ), // Container( // height: 8.0, // width: 8.0, // decoration: BoxDecoration( // shape: BoxShape.circle, // color: Colors.transparent, // border: Border.all( // color: Theme.of(context).accentColor, // width: 2.0)), // ), ]), ), ), ], ), ); } Widget _miniPanel(double width, BuildContext context) { var audio = Provider.of(context, listen: false); final s = context.s; return Container( decoration: BoxDecoration( color: Theme.of(context).primaryColor, ), height: 60, child: Column(mainAxisAlignment: MainAxisAlignment.start, children: [ Selector>( selector: (_, audio) => Tuple2(audio.episode?.primaryColor, audio.seekSliderValue), builder: (_, data, __) { Color _c = (Theme.of(context).brightness == Brightness.light) ? data.item1.colorizedark() : data.item1.colorizeLight(); return SizedBox( height: 2, child: LinearProgressIndicator( value: data.item2, backgroundColor: Theme.of(context).primaryColor, valueColor: AlwaysStoppedAnimation(_c), )); }, ), Expanded( child: Container( padding: EdgeInsets.only(left: 15, right: 10), alignment: Alignment.center, child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( flex: 4, child: Selector( selector: (_, audio) => audio.episode?.title, builder: (_, title, __) { return Text( title, style: TextStyle(fontWeight: FontWeight.bold), maxLines: 2, overflow: TextOverflow.clip, ); }, ), ), Expanded( flex: 2, child: Selector>( selector: (_, audio) => Tuple3( audio.audioState, (audio.backgroundAudioDuration - audio.backgroundAudioPosition) / 1000, audio.remoteErrorMessage), builder: (_, data, __) { return Container( padding: EdgeInsets.symmetric(horizontal: 10), alignment: Alignment.center, child: data.item3 != null ? Text(data.item3, style: const TextStyle( color: const Color(0xFFFF0000))) : data.item1 == BasicPlaybackState.buffering || data.item1 == BasicPlaybackState.connecting || data.item1 == BasicPlaybackState.skippingToNext || data.item1 == BasicPlaybackState.stopped ? Text( s.buffering, style: TextStyle( color: Theme.of(context).accentColor), ) : Text( s.timeLeft( _stringForSeconds(data.item2) ?? ''), maxLines: 2, ), ); }, ), ), Expanded( flex: 2, child: Selector( selector: (_, audio) => audio.audioState, builder: (_, audioplay, __) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ //Spacer(), audioplay == BasicPlaybackState.playing ? InkWell( onTap: audioplay == BasicPlaybackState.playing ? () { audio.pauseAduio(); } : null, child: ImageRotate( title: audio.episode?.title, path: audio.episode?.imagePath), ) : InkWell( onTap: audioplay == BasicPlaybackState.playing ? null : () { audio.resumeAudio(); }, child: Stack( alignment: Alignment.center, children: [ Container( padding: EdgeInsets.symmetric( vertical: 10.0), child: Container( height: 30.0, width: 30.0, child: CircleAvatar( backgroundImage: FileImage(File( "${audio.episode.imagePath}")), )), ), Container( height: 40.0, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.black), ), Icon( Icons.play_arrow, color: Colors.white, ) ], ), ), IconButton( padding: EdgeInsets.zero, onPressed: () => audio.playNext(), iconSize: 20.0, icon: Icon(Icons.skip_next), color: Theme.of(context).tabBarTheme.labelColor), ], ); }), ), ], ), ), ), ]), ); } @override Widget build(BuildContext context) { double _width = MediaQuery.of(context).size.width; return Selector( selector: (_, audio) => audio.playerRunning, builder: (_, playerrunning, __) { return !playerrunning ? Center() : AudioPanel( miniPanel: _miniPanel(_width, context), expandedPanel: _expandedPanel(context)); }, ); } } class LastPosition extends StatefulWidget { LastPosition({Key key}) : super(key: key); @override _LastPositionState createState() => _LastPositionState(); } class _LastPositionState extends State { static String _stringForSeconds(double seconds) { if (seconds == null) return null; return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}'; } Future getPosition(EpisodeBrief episode) async { var dbHelper = DBHelper(); return await dbHelper.getPosition(episode); } @override Widget build(BuildContext context) { final s = context.s; var audio = Provider.of(context, listen: false); return Selector( selector: (_, audio) => audio.episode, builder: (context, episode, child) { return FutureBuilder( future: getPosition(episode), builder: (context, snapshot) { if (snapshot.hasError) print(snapshot.error); return snapshot.hasData ? snapshot.data.seekValue > 0.90 ? Container( height: 20.0, alignment: Alignment.center, padding: EdgeInsets.symmetric(horizontal: 5), decoration: BoxDecoration( border: Border.all( width: 1, color: Theme.of(context) .textTheme .bodyText1 .color), borderRadius: BorderRadius.all(Radius.circular(10.0))), child: Text(s.listened)) : snapshot.data.seconds < 10 ? Center() : Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.all(Radius.circular(10.0)), onTap: () => audio.seekTo( (snapshot.data.seconds * 1000).toInt()), child: Container( width: 120.0, height: 20.0, alignment: Alignment.center, decoration: BoxDecoration( border: Border.all( width: 1, color: Theme.of(context) .textTheme .bodyText1 .color), borderRadius: BorderRadius.all( Radius.circular(10.0))), child: Text(s.timeLastPlayed( _stringForSeconds( snapshot.data.seconds))), ), ), ) : Center(); }); }, ); } } class ImageRotate extends StatefulWidget { final String title; final String path; ImageRotate({this.title, this.path, Key key}) : super(key: key); @override _ImageRotateState createState() => _ImageRotateState(); } class _ImageRotateState extends State with SingleTickerProviderStateMixin { Animation _animation; AnimationController _controller; double _value; @override void initState() { super.initState(); _value = 0; _controller = AnimationController( vsync: this, duration: Duration(milliseconds: 2000), ); _animation = Tween(begin: 0.0, end: 1.0).animate(_controller) ..addListener(() { if (mounted) setState(() { _value = _animation.value; }); }); _controller.forward(); _controller.addStatusListener((status) { if (status == AnimationStatus.completed) { _controller.reset(); } else if (status == AnimationStatus.dismissed) { _controller.forward(); } }); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Transform.rotate( angle: 2 * math.pi * _value, child: Container( padding: EdgeInsets.symmetric(vertical: 10.0), child: ClipRRect( borderRadius: BorderRadius.all(Radius.circular(15.0)), child: Container( height: 30.0, width: 30.0, color: Colors.white, child: Image.file(File("${widget.path}")), ), ), ), ); } } class Meteor extends CustomPainter { Paint _paint; Meteor() { _paint = Paint() ..color = Colors.white ..strokeWidth = 2.0 ..strokeCap = StrokeCap.round; } @override void paint(Canvas canvas, Size size) { canvas.drawLine(Offset(0, 0), Offset(size.width, size.height), _paint); } @override bool shouldRepaint(Meteor oldDelegate) { return false; } } class MeteorLoader extends StatefulWidget { @override _MeteorLoaderState createState() => _MeteorLoaderState(); } class _MeteorLoaderState extends State with SingleTickerProviderStateMixin { double _fraction = 0.0; double _move = 0.0; Animation animation; AnimationController controller; @override void initState() { super.initState(); controller = AnimationController(vsync: this, duration: Duration(milliseconds: 500)); animation = Tween(begin: 0.0, end: 1.0).animate(controller) ..addListener(() { if (mounted) setState(() { _move = animation.value; if (animation.value <= 0.5) { _fraction = animation.value * 2; } else { _fraction = 2 - (animation.value) * 2; } }); }); controller.forward(); // controller.addStatusListener((status) { // if (status == AnimationStatus.completed) { // controller.reset(); // } else if (status == AnimationStatus.dismissed) { // controller.forward(); // } // }); } @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Positioned( top: 300 * _move + 10, left: 150 * _move + 50, child: SizedBox( width: 50 * _fraction, height: 100 * _fraction, child: CustomPaint(painter: Meteor())), ); } } class SleepMode extends StatefulWidget { SleepMode({Key key}) : super(key: key); @override SleepModeState createState() => SleepModeState(); } class SleepModeState extends State with SingleTickerProviderStateMixin { int _minSelected; AnimationController _controller; Animation _animation; Future _getDefaultTime() async { KeyValueStorage defaultSleepTimerStorage = KeyValueStorage(defaultSleepTimerKey); int defaultTime = await defaultSleepTimerStorage.getInt(defaultValue: 30); setState(() => _minSelected = defaultTime); } @override void initState() { super.initState(); _minSelected = 30; _getDefaultTime(); _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 400)); _animation = Tween(begin: 0.0, end: 1.0).animate(_controller) ..addListener(() { Provider.of(context, listen: false) .setSwitchValue = _animation.value; }); _controller.addStatusListener((status) { if (status == AnimationStatus.completed) { Provider.of(context, listen: false) .sleepTimer(_minSelected); } }); } @override void dispose() { _controller.dispose(); super.dispose(); } List customShadow(double scale) => [ BoxShadow( blurRadius: 26 * (1 - scale), offset: Offset(-6, -6) * (1 - scale), color: Colors.white), BoxShadow( blurRadius: 8 * (1 - scale), offset: Offset(2, 2) * (1 - scale), color: Colors.grey[600].withOpacity(0.4)) ]; List customShadowNight(double scale) => [ BoxShadow( blurRadius: 6 * (1 - scale), offset: Offset(-1, -1) * (1 - scale), color: Colors.grey[100].withOpacity(0.3)), BoxShadow( blurRadius: 8 * (1 - scale), offset: Offset(2, 2) * (1 - scale), color: Colors.black) ]; @override Widget build(BuildContext context) { final s = context.s; final ColorTween _colorTween = ColorTween(begin: context.primaryColor, end: Colors.black); var audio = Provider.of(context, listen: false); return Selector>( selector: (_, audio) => Tuple3(audio.timeLeft, audio.switchValue, audio.sleepTimerMode), builder: (_, data, __) { double fraction = data.item2 < 0.5 ? data.item2 * 2 : 1; double move = data.item2 > 0.5 ? data.item2 * 2 - 1 : 0; return Container( height: 300, color: _colorTween.transform(move), child: Stack( children: [ Column( mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Padding( padding: EdgeInsets.all(5), ), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: minsToSelect .map((e) => InkWell( onTap: () => setState(() => _minSelected = e), child: Stack( alignment: Alignment.center, children: [ Container( margin: EdgeInsets.symmetric( horizontal: 10.0), decoration: BoxDecoration( boxShadow: !(e == _minSelected || fraction > 0) ? (Theme.of(context).brightness == Brightness.dark) ? customShadowNight(fraction) : customShadow(fraction) : null, color: (e == _minSelected) ? Theme.of(context).accentColor : Theme.of(context).primaryColor, shape: BoxShape.circle, ), alignment: Alignment.center, height: 30, width: 30, child: Text(e.toString(), style: TextStyle( fontWeight: FontWeight.bold, color: (e == _minSelected) ? Colors.white : null)), ), Container( height: 30 * move, width: 30 * move, decoration: BoxDecoration( color: _colorTween.transform(fraction), shape: BoxShape.circle), ), ], ), )) .toList(), ), ), ), Stack( children: [ Container( height: 100, alignment: Alignment.center, ), Positioned( left: data.item3 == SleepTimerMode.timer ? -context.width * (move) / 4 : context.width * (move) / 4, child: Container( height: 100, width: context.width, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Container( alignment: Alignment.center, height: 40, width: 120, decoration: BoxDecoration( border: Border.all(color: context.primaryColor), boxShadow: context.brightness == Brightness.light ? customShadow(fraction) : customShadowNight(fraction), color: _colorTween.transform(move), borderRadius: BorderRadius.all(Radius.circular(20)), ), child: Material( color: Colors.transparent, child: InkWell( onTap: () { audio.setSleepTimerMode = SleepTimerMode.endOfEpisode; if (fraction == 0) { _controller.forward(); } else if (fraction == 1) { _controller.reverse(); audio.cancelTimer(); } }, borderRadius: BorderRadius.all(Radius.circular(20)), child: SizedBox( height: 40, width: 120, child: Center( child: Text( s.endOfEpisode, style: TextStyle( // fontWeight: FontWeight.bold, // fontSize: 20, color: (move > 0 ? Colors.white : null)), ))), ), ), ), Container( height: 100 * (1 - fraction), width: 2, color: context.primaryColorDark, ), Container( height: 40, width: 120, alignment: Alignment.center, decoration: BoxDecoration( border: Border.all(color: context.primaryColor), boxShadow: context.brightness == Brightness.light ? customShadow(fraction) : customShadowNight(fraction), color: _colorTween.transform(move), borderRadius: BorderRadius.all(Radius.circular(20)), ), child: Material( color: Colors.transparent, child: InkWell( onTap: () { audio.setSleepTimerMode = SleepTimerMode.timer; if (fraction == 0) { _controller.forward(); } else if (fraction == 1) { _controller.reverse(); audio.cancelTimer(); } }, borderRadius: BorderRadius.all(Radius.circular(20)), child: SizedBox( height: 40, width: 120, child: Center( child: Text( data.item2 == 1 ? _stringForSeconds( data.item1.toDouble()) : _stringForSeconds( (_minSelected * 60) .toDouble()), style: TextStyle( // fontWeight: FontWeight.bold, // fontSize: 20, color: (move > 0 ? Colors.white : null)), ), ), ), ), ), ) ], ), ), ), ], ), ], ), Positioned( bottom: 50 + 20 * data.item2, left: context.width / 2 - 100, width: 200, child: Container( alignment: Alignment.center, child: Text(s.goodNight, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 20, color: Colors.white.withOpacity(fraction))), ), ), Positioned( bottom: 100 * (1 - data.item2) - 30, left: context.width / 2 - 100, width: 200, child: Container( alignment: Alignment.center, child: Text(s.sleepTimer, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)), ), ), data.item2 == 1 ? CustomPaint(painter: StarSky()) : Center(), data.item2 == 1 ? MeteorLoader() : Center(), ], ), ); }, ); } } class ControlPanel extends StatefulWidget { ControlPanel({Key key}) : super(key: key); @override _ControlPanelState createState() => _ControlPanelState(); } class _ControlPanelState extends State with SingleTickerProviderStateMixin { double _speedSelected; double _setSpeed; AnimationController _controller; Animation _animation; List customShadow(double scale) => [ BoxShadow( blurRadius: 26 * (1 - scale), offset: Offset(-6, -6) * (1 - scale), color: Colors.white), BoxShadow( blurRadius: 8 * (1 - scale), offset: Offset(2, 2) * (1 - scale), color: Colors.grey[600].withOpacity(0.4)) ]; List customShadowNight(double scale) => [ BoxShadow( blurRadius: 6 * (1 - scale), offset: Offset(-1, -1) * (1 - scale), color: Colors.grey[100].withOpacity(0.3)), BoxShadow( blurRadius: 8 * (1 - scale), offset: Offset(2, 2) * (1 - scale), color: Colors.black) ]; @override void initState() { _speedSelected = 0; _setSpeed = 0; _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 400)); _animation = Tween(begin: 0.0, end: 1.0).animate(_controller) ..addListener(() { setState(() => _setSpeed = _animation.value); }); super.initState(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { var audio = Provider.of(context, listen: false); return Container( color: Theme.of(context).primaryColor, height: 300, padding: EdgeInsets.symmetric(horizontal: 10.0), child: Stack( children: [ Column( mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Consumer( builder: (_, data, __) { Color _c = (Theme.of(context).brightness == Brightness.light) ? data.episode.primaryColor.colorizedark() : data.episode.primaryColor.colorizeLight(); return Column( mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Container( padding: EdgeInsets.only(top: 10, left: 10, right: 10), child: SliderTheme( data: SliderTheme.of(context).copyWith( activeTrackColor: Theme.of(context).brightness == Brightness.dark ? Colors.black38 : Colors.grey[400], inactiveTrackColor: Theme.of(context).primaryColorDark, trackHeight: 20.0, trackShape: MyRectangularTrackShape(), thumbColor: Theme.of(context).accentColor, thumbShape: MyRoundSliderThumpShape( enabledThumbRadius: 10.0, disabledThumbRadius: 10.0, thumbCenterColor: _c), overlayColor: Theme.of(context).accentColor.withAlpha(32), overlayShape: RoundSliderOverlayShape(overlayRadius: 4.0), ), child: Slider( value: data.seekSliderValue, onChanged: (double val) { audio.sliderSeek(val); }), ), ), Container( height: 20.0, padding: EdgeInsets.symmetric(horizontal: 30.0), child: Row( children: [ Text( _stringForSeconds( data.backgroundAudioPosition / 1000) ?? '', style: TextStyle(fontSize: 10), ), Expanded( child: Container( alignment: Alignment.center, child: data.remoteErrorMessage != null ? Text(data.remoteErrorMessage, style: const TextStyle( color: const Color(0xFFFF0000))) : Text( data.audioState == BasicPlaybackState .buffering || data.audioState == BasicPlaybackState .connecting || data.audioState == BasicPlaybackState.none || data.audioState == BasicPlaybackState .skippingToNext ? context.s.buffering : '', style: TextStyle( color: Theme.of(context) .accentColor), ), ), ), Text( _stringForSeconds( data.backgroundAudioDuration / 1000) ?? '', style: TextStyle(fontSize: 10), ), ], ), ), ], ); }, ), Container( height: 100, child: Selector( selector: (_, audio) => audio.audioState, builder: (_, backplay, __) { return Material( color: Colors.transparent, child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( padding: EdgeInsets.symmetric(horizontal: 25.0), onPressed: backplay == BasicPlaybackState.playing ? () => audio.forwardAudio(-10) : null, iconSize: 32.0, icon: Icon(Icons.replay_10), color: Colors.grey[500]), Container( margin: EdgeInsets.symmetric(horizontal: 30), height: 60, width: 60, decoration: BoxDecoration( color: Theme.of(context).primaryColor, shape: BoxShape.circle, border: Border.all( color: Theme.of(context).brightness == Brightness.dark ? Colors.black12 : Colors.white10, width: 1), boxShadow: Theme.of(context).brightness == Brightness.dark ? _customShadowNight : _customShadow), child: backplay == BasicPlaybackState.playing ? Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.all( Radius.circular(30)), onTap: backplay == BasicPlaybackState.playing ? () { audio.pauseAduio(); } : null, child: SizedBox( height: 60, width: 60, child: Icon( Icons.pause, size: 40, ), ), ), ) : Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.all( Radius.circular(30)), onTap: backplay == BasicPlaybackState.playing ? null : () { audio.resumeAudio(); }, child: SizedBox( height: 60, width: 60, child: Icon( Icons.play_arrow, size: 40, color: Theme.of(context).accentColor, ), ), ), ), ), IconButton( padding: EdgeInsets.symmetric(horizontal: 25.0), onPressed: backplay == BasicPlaybackState.playing ? () => audio.forwardAudio(30) : null, iconSize: 32.0, icon: Icon(Icons.forward_30), color: Colors.grey[500]), ], ), ); }, ), ), Container( height: 70.0, padding: EdgeInsets.only(left: 60, right: 60, bottom: 10, top: 10), alignment: Alignment.center, child: Selector( selector: (_, audio) => audio.episode.title, builder: (_, title, __) { return Container( child: LayoutBuilder( builder: (context, size) { var span = TextSpan( text: title, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 20)); var tp = TextPainter( text: span, maxLines: 1, textDirection: TextDirection.ltr); tp.layout(maxWidth: size.maxWidth); if (tp.didExceedMaxLines) { return Marquee( text: title, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 18), scrollAxis: Axis.horizontal, crossAxisAlignment: CrossAxisAlignment.start, blankSpace: 30.0, velocity: 50.0, pauseAfterRound: Duration.zero, startPadding: 30.0, accelerationDuration: Duration(milliseconds: 100), accelerationCurve: Curves.linear, decelerationDuration: Duration(milliseconds: 100), decelerationCurve: Curves.linear, ); } else { return Text( title, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 20), ); } }, ), ); }, ), ), Spacer(), Selector>( selector: (_, audio) => Tuple3(audio.episode, audio.stopOnComplete, audio.startSleepTimer), builder: (_, data, __) { return Container( padding: EdgeInsets.only(left: 5.0, right: 5.0, bottom: 5.0), child: Row( mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ InkWell( onTap: () => Navigator.push( context, SlideUptRoute( page: EpisodeDetail( episodeItem: data.item1, heroTag: 'playpanel'))), child: Container( height: 30.0, width: 30.0, child: CircleAvatar( backgroundImage: FileImage(File("${data.item1.imagePath}")), ), ), ), Container( padding: EdgeInsets.symmetric(horizontal: 5.0), width: 150, child: Text( data.item1.feedTitle, maxLines: 1, overflow: TextOverflow.fade, ), ), Spacer(), LastPosition(), IconButton( padding: EdgeInsets.zero, onPressed: () { if (_setSpeed == 0) _controller.forward(); else _controller.reverse(); }, icon: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Transform.rotate( angle: math.pi * _setSpeed, child: Text('X')), Selector( selector: (_, audio) => audio.currentSpeed ?? 1.0, builder: (context, value, child) => Text(value.toString()), ), ], ), ), ], ), ); }, ), ]), Positioned( right: 0, bottom: 50.0, child: _setSpeed == 0 ? Center() : SingleChildScrollView( padding: EdgeInsets.all(10.0), scrollDirection: Axis.horizontal, child: Row( children: speedToSelect .map((e) => InkWell( onTap: () { if (_setSpeed == 1) { setState(() => _speedSelected = e); audio.setSpeed(e); } }, child: Container( height: 30, width: 30, margin: EdgeInsets.symmetric(horizontal: 5), decoration: e == _speedSelected && _setSpeed > 0 ? BoxDecoration( color: context.accentColor, shape: BoxShape.circle, boxShadow: context.brightness == Brightness.light ? customShadow(1.0) : customShadowNight(1.0), ) : BoxDecoration( color: context.primaryColor, shape: BoxShape.circle, boxShadow: context.brightness == Brightness.light ? customShadow(1 - _setSpeed) : customShadowNight( 1 - _setSpeed)), alignment: Alignment.center, child: _setSpeed > 0 ? Text(e.toString(), style: TextStyle( fontWeight: FontWeight.bold, color: e == _speedSelected ? Colors.white : null)) : Center(), ), )) .toList(), ), ), ), ], ), ); } }