From 4009f84c38a663730a158e23b7671570a25b217c Mon Sep 17 00:00:00 2001 From: stonegate Date: Tue, 28 Jul 2020 12:35:57 +0800 Subject: [PATCH] Update player widget design. --- lib/home/audioplayer.dart | 1503 +++++++++++-------- lib/home/home.dart | 158 +- lib/local_storage/sqflite_localpodcast.dart | 4 +- lib/podcasts/podcast_detail.dart | 33 +- lib/util/audiopanel.dart | 103 +- 5 files changed, 1011 insertions(+), 790 deletions(-) diff --git a/lib/home/audioplayer.dart b/lib/home/audioplayer.dart index 1dd8a7d..e2eb1cd 100644 --- a/lib/home/audioplayer.dart +++ b/lib/home/audioplayer.dart @@ -38,12 +38,6 @@ final List _customShadowNight = [ 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]; @@ -371,16 +365,6 @@ class _PlayerWidgetState extends State { 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)), - // ), ]), ), ), @@ -463,7 +447,7 @@ class _PlayerWidgetState extends State { ) : Text( s.timeLeft( - _stringForSeconds(data.item2) ?? ''), + (data.item2).toInt().toTime ?? ''), maxLines: 2, ), ); @@ -479,7 +463,6 @@ class _PlayerWidgetState extends State { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - //Spacer(), data.item1 ? Stack( alignment: Alignment.center, @@ -487,7 +470,7 @@ class _PlayerWidgetState extends State { Container( padding: EdgeInsets.symmetric( vertical: 10.0), - child: Container( + child: SizedBox( height: 30.0, width: 30.0, child: CircleAvatar( @@ -521,7 +504,7 @@ class _PlayerWidgetState extends State { Container( padding: EdgeInsets.symmetric( vertical: 10.0), - child: Container( + child: SizedBox( height: 30.0, width: 30.0, child: CircleAvatar( @@ -572,7 +555,7 @@ class _PlayerWidgetState extends State { ? Center() : AudioPanel( miniPanel: _miniPanel(_width, context), - expandedPanel: _expandedPanel(context)); + expandedPanel: ControlPanel()); }, ); } @@ -767,13 +750,6 @@ class _MeteorLoaderState extends State } }); controller.forward(); - // controller.addStatusListener((status) { - // if (status == AnimationStatus.completed) { - // controller.reset(); - // } else if (status == AnimationStatus.dismissed) { - // controller.forward(); - // } - // }); } @override @@ -795,6 +771,271 @@ class _MeteorLoaderState extends State } } +class PlaylistWidget extends StatefulWidget { + const PlaylistWidget({Key key}) : super(key: key); + + @override + _PlaylistWidgetState createState() => _PlaylistWidgetState(); +} + +class _PlaylistWidgetState extends State { + final GlobalKey miniPlaylistKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + var audio = Provider.of(context, listen: false); + return Container( + alignment: Alignment.topLeft, + height: 300, + width: double.infinity, + decoration: BoxDecoration( + color: context.accentColor.withAlpha(70), + borderRadius: BorderRadius.circular(10), + ), + child: Column( + children: [ + 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), + ], + ), + ), + ); + }, + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 20.0), + height: 60.0, + 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, + ), + ), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} + class SleepMode extends StatefulWidget { SleepMode({Key key}) : super(key: key); @@ -866,236 +1107,239 @@ class SleepModeState extends State Widget build(BuildContext context) { final s = context.s; final _colorTween = - ColorTween(begin: context.primaryColor, end: Colors.black); + ColorTween(begin: context.accentColor.withAlpha(60), end: Colors.black); var audio = Provider.of(context, listen: false); return Selector>( selector: (_, audio) => - Tuple3(audio.timeLeft, audio.switchValue, audio.sleepTimerMode), + Tuple3(audio?.timeLeft, audio?.switchValue, audio.sleepTimerMode), builder: (_, data, __) { - var fraction = data.item2 < 0.5 ? data.item2 * 2 : 1; - var 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, + var fraction = math.min(data.item2 * 2, 1.0); + var move = math.max(data.item2 * 2 - 1, 0.0); + return LayoutBuilder(builder: (context, constraints) { + return Container( + height: 300, + decoration: BoxDecoration( + color: _colorTween.transform(move), + borderRadius: BorderRadius.circular(10)), + 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) + ? (context.brightness == + Brightness.dark) + ? customShadowNight( + fraction) + : customShadow(fraction) + : null, + color: (e == _minSelected) + ? context.accentColor + : 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)), ), - 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(), + 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( + Stack( + children: [ + 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(); - } - }, + 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: SizedBox( + ), + 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( - 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)), + child: Text( + data.item2 == 1 + ? data.item1.toTime + : (_minSelected * 60).toTime, + 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: 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)), + 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(), - ], - ), - ); + data.item2 == 1 ? CustomPaint(painter: StarSky()) : Center(), + data.item2 == 1 ? MeteorLoader() : Center(), + ], + ), + ); + }); }, ); } @@ -1103,7 +1347,6 @@ class SleepModeState extends State class ControlPanel extends StatefulWidget { ControlPanel({Key key}) : super(key: key); - @override _ControlPanelState createState() => _ControlPanelState(); } @@ -1157,367 +1400,425 @@ class _ControlPanelState extends State @override Widget build(BuildContext context) { var audio = Provider.of(context, listen: false); - return Container( - color: context.primaryColor, - height: 300, - padding: EdgeInsets.symmetric(horizontal: 10.0), - child: Stack( - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Consumer( - builder: (_, data, __) { - var _c = (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.grey[900] - : Colors.grey[400], - inactiveTrackColor: context.primaryColorDark, - trackHeight: 20.0, - trackShape: MyRectangularTrackShape(), - thumbColor: 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: (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: Color(0xFFFF0000))) - : Text( - data.audioState == - AudioProcessingState - .buffering || - data.audioState == - AudioProcessingState - .connecting || - data.audioState == - AudioProcessingState - .none || - data.audioState == - AudioProcessingState - .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.playing, - builder: (_, playing, __) { - return Material( - color: Colors.transparent, - child: Row( + + return LayoutBuilder( + builder: (context, constraints) { + print(constraints.maxHeight); + var maxHeight = constraints.maxHeight; + return Container( + color: context.primaryColor, + height: 300, + //padding: EdgeInsets.symmetric(horizontal: 10.0), + child: Stack( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Consumer( + builder: (_, data, __) { + var _c = (context.brightness == Brightness.light) + ? data.episode.primaryColor.colorizedark() + : data.episode.primaryColor.colorizeLight(); + return Column( + mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - IconButton( - padding: EdgeInsets.symmetric(horizontal: 0), - onPressed: - playing ? () => audio.rewind() : null, - iconSize: 32.0, - icon: Icon(Icons.fast_rewind), - color: Colors.grey[500]), - Selector( - selector: (_, audio) => audio.rewindSeconds, - builder: (_, seconds, __) => Padding( - padding: const EdgeInsets.only(top: 5.0), - child: Text('$seconds s', - style: GoogleFonts.teko( - textBaseline: - TextBaseline.ideographic, - textStyle: TextStyle( - color: Colors.grey[500], - fontSize: 20), - )), - )), + children: [ Container( - margin: EdgeInsets.symmetric(horizontal: 30), - height: 60, - width: 60, - decoration: BoxDecoration( - color: context.primaryColor, - shape: BoxShape.circle, - border: Border.all( - color: - context.brightness == Brightness.dark - ? Colors.black12 - : Colors.white10, - width: 1), - boxShadow: - context.brightness == Brightness.dark - ? _customShadowNight - : _customShadow), - child: playing - ? Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.all( - Radius.circular(30)), - onTap: playing - ? () { - audio.pauseAduio(); - } - : null, - child: SizedBox( - height: 60, - width: 60, - child: Icon( - Icons.pause, - size: 40, + padding: + EdgeInsets.only(top: 10, left: 10, right: 10), + child: SliderTheme( + data: SliderTheme.of(context).copyWith( + activeTrackColor: maxHeight <= 300 + ? Theme.of(context).brightness == + Brightness.dark + ? Colors.grey[900] + : Colors.grey[400] + : context.primaryColor, + inactiveTrackColor: maxHeight > 300 + ? context.primaryColor + : context.primaryColorDark, + trackHeight: 20.0, + trackShape: MyRectangularTrackShape(), + thumbColor: 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: (val) { + audio.sliderSeek(val); + }), + ), + ), + Container( + height: 20.0, + padding: EdgeInsets.symmetric(horizontal: 30.0), + child: maxHeight > 300 + ? Center() + : Row( + children: [ + Text( + (data.backgroundAudioPosition ~/ 1000) + .toTime ?? + '', + style: TextStyle(fontSize: 10), + ), + Expanded( + child: Container( + alignment: Alignment.center, + child: data.remoteErrorMessage != + null + ? Text(data.remoteErrorMessage, + style: const TextStyle( + color: + Color(0xFFFF0000))) + : Text( + data.audioState == + AudioProcessingState + .buffering || + data.audioState == + AudioProcessingState + .connecting || + data.audioState == + AudioProcessingState + .none || + data.audioState == + AudioProcessingState + .skippingToNext + ? context.s.buffering + : '', + style: TextStyle( + color: Theme.of(context) + .accentColor), + ), ), ), - ), - ) - : Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.all( - Radius.circular(30)), - onTap: playing - ? null - : () { - audio.resumeAudio(); - }, - child: SizedBox( - height: 60, - width: 60, - child: Icon( - Icons.play_arrow, - size: 40, - color: context.accentColor, - ), + Text( + (data.backgroundAudioDuration ~/ 1000) + .toTime ?? + '', + style: TextStyle(fontSize: 10), ), - ), + ], ), ), - Selector( - selector: (_, audio) => - audio.fastForwardSeconds, - builder: (_, seconds, __) => Padding( - padding: const EdgeInsets.only(top: 5.0), - child: Text('$seconds s', - style: GoogleFonts.teko( - textStyle: TextStyle( - color: Colors.grey[500], - fontSize: 20), - )), - )), - IconButton( - padding: EdgeInsets.symmetric(horizontal: 10.0), - onPressed: - playing ? () => audio.fastForward() : null, - iconSize: 32.0, - icon: Icon(Icons.fast_forward), - 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( + height: 100, + child: Selector( + selector: (_, audio) => audio.playing, + builder: (_, playing, __) { + return Material( + color: Colors.transparent, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconButton( + padding: + EdgeInsets.symmetric(horizontal: 0), + onPressed: + playing ? () => audio.rewind() : null, + iconSize: 32.0, + icon: Icon(Icons.fast_rewind), + color: Colors.grey[500]), + Selector( + selector: (_, audio) => audio.rewindSeconds, + builder: (_, seconds, __) => Padding( + padding: + const EdgeInsets.only(top: 5.0), + child: Text('$seconds s', + style: GoogleFonts.teko( + textBaseline: + TextBaseline.ideographic, + textStyle: TextStyle( + color: Colors.grey[500], + fontSize: 20), + )), + )), + Container( + margin: EdgeInsets.symmetric(horizontal: 30), + height: 60, + width: 60, + decoration: BoxDecoration( + color: context.primaryColor, + shape: BoxShape.circle, + border: Border.all( + color: context.brightness == + Brightness.dark + ? Colors.black12 + : Colors.white10, + width: 1), + boxShadow: + context.brightness == Brightness.dark + ? _customShadowNight + : _customShadow), + child: playing + ? Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.all( + Radius.circular(30)), + onTap: 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: playing + ? null + : () { + audio.resumeAudio(); + }, + child: SizedBox( + height: 60, + width: 60, + child: Icon( + Icons.play_arrow, + size: 40, + color: context.accentColor, + ), + ), + ), + ), + ), + Selector( + selector: (_, audio) => + audio.fastForwardSeconds, + builder: (_, seconds, __) => Padding( + padding: + const EdgeInsets.only(top: 5.0), + child: Text('$seconds s', + style: GoogleFonts.teko( + textStyle: TextStyle( + color: Colors.grey[500], + fontSize: 20), + )), + )), + IconButton( + padding: + EdgeInsets.symmetric(horizontal: 10.0), + onPressed: playing + ? () => audio.fastForward() + : null, + iconSize: 32.0, + icon: Icon(Icons.fast_forward), + 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), + ); + } + }, + ), + ); + }, + ), + ), + if (constraints.maxHeight > 300) + SizedBox( + height: constraints.maxHeight - 300, + child: SingleChildScrollView( + physics: NeverScrollableScrollPhysics(), + child: SizedBox( + height: 300, + child: PageView( + scrollDirection: Axis.horizontal, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20.0), + child: PlaylistWidget(), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20.0), + child: SleepMode(), + ) + ], ), - ), - ), - 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, + )), + ), + Expanded( + child: 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: [ - Transform.rotate( - angle: math.pi * _setSpeed, - child: Text('X')), - Selector( - selector: (_, audio) => - audio.currentSpeed ?? 1.0, - builder: (context, value, child) => - Text(value.toString()), + 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, + ), + ), + IconButton( + icon: Icon(Icons.playlist_play), + onPressed: () {}, + ), + IconButton( + icon: Icon(Icons.timer), onPressed: null), + 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 + ), + ]), + 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, @@ -1534,23 +1835,25 @@ class _ControlPanelState extends State ? 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(), - ), - ), + alignment: Alignment.center, + child: _setSpeed > 0 + ? Text(e.toString(), + style: TextStyle( + fontWeight: FontWeight.bold, + color: e == _speedSelected + ? Colors.white + : null)) + : Center(), + ), + )) + .toList(), + ), + ), + ), + ], ), - ], - ), + ); + }, ); } } diff --git a/lib/home/home.dart b/lib/home/home.dart index 2b9f1a4..a03dbd4 100644 --- a/lib/home/home.dart +++ b/lib/home/home.dart @@ -943,85 +943,31 @@ class _RecentUpdateState extends State<_RecentUpdate> child: IconButton( tooltip: s .addNewEpisodeTooltip, - icon: - // Icon(Icons.playlist_add), - SizedBox( - height: - 16, - width: 21, - child: CustomPaint( - painter: AddToPlaylistPainter( - context - .textColor, - context - .textColor, - ))), + icon: SizedBox( + height: 16, + width: 21, + child: + CustomPaint( + painter: + AddToPlaylistPainter( + context + .textColor, + context + .textColor, + ))), onPressed: () {}), ); }), Material( - color: Colors.transparent, - child: IconButton( - tooltip: s.changeLayout, - padding: EdgeInsets.zero, - onPressed: () { - if (_layout == - Layout.three) { - setState(() { - _layout = Layout.one; - }); - } else if (_layout == - Layout.two) { - setState(() { - _layout = Layout.three; - }); - } else { - setState(() { - _layout = Layout.two; - }); - } - }, - icon: _layout == Layout.three - ? SizedBox( - height: 10, - width: 30, - child: CustomPaint( - painter: LayoutPainter( - 0, - context - .textTheme - .bodyText1 - .color), - ), - ) - : _layout == Layout.two - ? SizedBox( - height: 10, - width: 30, - child: - CustomPaint( - painter: LayoutPainter( - 1, - context - .textTheme - .bodyText1 - .color), - ), - ) - : SizedBox( - height: 10, - width: 30, - child: - CustomPaint( - painter: LayoutPainter( - 4, - context - .textTheme - .bodyText1 - .color), - ), - ), - )), + color: Colors.transparent, + child: LayoutButton( + layout: _layout, + onPressed: (layout) => + setState(() { + _layout = layout; + }), + ), + ), ], ), )), @@ -1204,62 +1150,12 @@ class _MyFavoriteState extends State<_MyFavorite> Spacer(), Material( color: Colors.transparent, - child: IconButton( - padding: EdgeInsets.zero, - onPressed: () { - if (_layout == Layout.three) { - setState(() { - _layout = Layout.one; - }); - } else if (_layout == - Layout.two) { - setState(() { - _layout = Layout.three; - }); - } else { - setState(() { - _layout = Layout.two; - }); - } - }, - icon: _layout == Layout.three - ? SizedBox( - height: 10, - width: 30, - child: CustomPaint( - painter: LayoutPainter( - 0, - context - .textTheme - .bodyText1 - .color), - ), - ) - : _layout == Layout.two - ? SizedBox( - height: 10, - width: 30, - child: CustomPaint( - painter: LayoutPainter( - 1, - context - .textTheme - .bodyText1 - .color), - ), - ) - : SizedBox( - height: 10, - width: 30, - child: CustomPaint( - painter: LayoutPainter( - 4, - context - .textTheme - .bodyText1 - .color), - ), - )), + child: LayoutButton( + layout: _layout, + onPressed: (layout) => setState(() { + _layout = layout; + }), + ), ), ], )), diff --git a/lib/local_storage/sqflite_localpodcast.dart b/lib/local_storage/sqflite_localpodcast.dart index 96cc32f..2b65da7 100644 --- a/lib/local_storage/sqflite_localpodcast.dart +++ b/lib/local_storage/sqflite_localpodcast.dart @@ -270,7 +270,7 @@ class DBHelper { var playHistory = []; for (var record in list) { playHistory.add(PlayHistory(record['title'], record['enclosure_url'], - record['seconds'], record['seek_value'], + (record['seconds']).toInt(), record['seek_value'], playdate: DateTime.fromMillisecondsSinceEpoch(record['add_date']))); } return playHistory; @@ -337,7 +337,7 @@ class DBHelper { [episodeBrief.enclosureUrl]); return list.isNotEmpty ? PlayHistory(list.first['title'], list.first['enclosure_url'], - list.first['seconds'], list.first['seek_value'], + (list.first['seconds']).toInt(), list.first['seek_value'], playdate: DateTime.fromMillisecondsSinceEpoch(list.first['add_date'])) : PlayHistory(episodeBrief.title, episodeBrief.enclosureUrl, 0, 0); diff --git a/lib/podcasts/podcast_detail.dart b/lib/podcasts/podcast_detail.dart index 77a59ba..35439cb 100644 --- a/lib/podcasts/podcast_detail.dart +++ b/lib/podcasts/podcast_detail.dart @@ -606,34 +606,11 @@ class _PodcastDetailState extends State { color: Colors.transparent, clipBehavior: Clip.hardEdge, borderRadius: BorderRadius.circular(100), - child: IconButton( - padding: EdgeInsets.zero, - onPressed: () { - if (_layout == Layout.three) { - setState(() { - _layout = Layout.one; - }); - } else if (_layout == Layout.two) { - setState(() { - _layout = Layout.three; - }); - } else { - setState(() { - _layout = Layout.two; - }); - } - }, - icon: _layout == Layout.three - ? IconPainter( - LayoutPainter(0, context.textColor), - ) - : _layout == Layout.two - ? IconPainter( - LayoutPainter(1, context.textColor), - ) - : IconPainter( - LayoutPainter(4, context.textColor), - ), + child: LayoutButton( + layout: _layout, + onPressed: (layout) => setState(() { + _layout = layout; + }), ), ), SizedBox(width: 10) diff --git a/lib/util/audiopanel.dart b/lib/util/audiopanel.dart index f3a0546..2fae672 100644 --- a/lib/util/audiopanel.dart +++ b/lib/util/audiopanel.dart @@ -1,15 +1,23 @@ +import 'dart:math' as math; + import 'package:flutter/material.dart'; +import 'extension_helper.dart'; + enum SlideDirection { up, down } class AudioPanel extends StatefulWidget { final Widget miniPanel; final Widget expandedPanel; + final Widget optionPanel; + final Widget cover; final double minHeight; final double maxHeight; AudioPanel( {@required this.miniPanel, @required this.expandedPanel, + this.optionPanel, + this.cover, this.minHeight = 60, this.maxHeight = 300, Key key}) @@ -26,11 +34,11 @@ class _AudioPanelState extends State with TickerProviderStateMixin { AnimationController _slowController; Animation _animation; SlideDirection _slideDirection; + double _expandHeight; @override void initState() { initSize = widget.minHeight; - _slideDirection = SlideDirection.up; _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 50)) ..addListener(() { @@ -44,7 +52,9 @@ class _AudioPanelState extends State with TickerProviderStateMixin { _animation = Tween(begin: 0, end: initSize).animate(_slowController); _controller.forward(); + _slideDirection = SlideDirection.up; super.initState(); + _expandHeight = 600; } @override @@ -54,6 +64,16 @@ class _AudioPanelState extends State with TickerProviderStateMixin { super.dispose(); } + double _getHeight() { + if (_animation.value >= _expandHeight) { + return _expandHeight; + } else if (_animation.value <= widget.minHeight) { + return widget.minHeight; + } else { + return _animation.value; + } + } + @override Widget build(BuildContext context) { return Stack(children: [ @@ -65,7 +85,8 @@ class _AudioPanelState extends State with TickerProviderStateMixin { child: Container( color: Theme.of(context) .scaffoldBackgroundColor - .withOpacity(0.8), + .withOpacity(0.9 * + math.min(_animation.value / widget.maxHeight, 1)), ), ), ) @@ -78,11 +99,7 @@ class _AudioPanelState extends State with TickerProviderStateMixin { onVerticalDragUpdate: _update, onVerticalDragEnd: (event) => _end(), child: Container( - height: (_animation.value >= widget.maxHeight) - ? widget.maxHeight - : (_animation.value <= widget.minHeight) - ? widget.minHeight - : _animation.value, + height: _getHeight(), child: _animation.value < widget.minHeight + 30 ? Container( color: Theme.of(context).primaryColor, @@ -97,7 +114,7 @@ class _AudioPanelState extends State with TickerProviderStateMixin { ) : Container( decoration: BoxDecoration( - color: Theme.of(context).primaryColor, + color: context.primaryColor, boxShadow: [ BoxShadow( offset: Offset(0, -0.5), @@ -117,7 +134,8 @@ class _AudioPanelState extends State with TickerProviderStateMixin { (widget.maxHeight - widget.minHeight - 50) : 1, child: Container( - height: widget.maxHeight, + height: math.max(widget.maxHeight, + math.min(_animation.value, _expandHeight)), child: widget.expandedPanel, ), ), @@ -160,12 +178,21 @@ class _AudioPanelState extends State with TickerProviderStateMixin { _end() { if (_slideDirection == SlideDirection.up) { if (_move > 20) { - setState(() { - _animation = - Tween(begin: _animation.value, end: widget.maxHeight) - .animate(_slowController); - initSize = widget.maxHeight; - }); + if (_animation.value > widget.maxHeight + 20) { + setState(() { + _animation = + Tween(begin: _animation.value, end: _expandHeight) + .animate(_slowController); + initSize = _expandHeight; + }); + } else { + setState(() { + _animation = + Tween(begin: _animation.value, end: widget.maxHeight) + .animate(_slowController); + initSize = widget.maxHeight; + }); + } _slowController.forward(); } else { setState(() { @@ -178,26 +205,44 @@ class _AudioPanelState extends State with TickerProviderStateMixin { } } else if (_slideDirection == SlideDirection.down) { if (_move > -50) { - setState(() { - _animation = - Tween(begin: _animation.value, end: widget.maxHeight) - .animate(_slowController); - initSize = widget.maxHeight; - }); + if (_animation.value > widget.maxHeight) { + setState(() { + _animation = + Tween(begin: _animation.value, end: _expandHeight) + .animate(_slowController); + initSize = _expandHeight; + }); + } else { + setState(() { + _animation = + Tween(begin: _animation.value, end: widget.maxHeight) + .animate(_slowController); + initSize = widget.maxHeight; + }); + } _slowController.forward(); } else { - setState(() { - _animation = - Tween(begin: _animation.value, end: widget.minHeight) - .animate(_controller); - initSize = widget.minHeight; - }); + if (_animation.value > widget.maxHeight) { + setState(() { + _animation = + Tween(begin: _animation.value, end: widget.maxHeight) + .animate(_slowController); + initSize = widget.maxHeight; + }); + } else { + setState(() { + _animation = + Tween(begin: _animation.value, end: widget.minHeight) + .animate(_controller); + initSize = widget.minHeight; + }); + } _controller.forward(); } } - if (_animation.value >= widget.maxHeight) { + if (_animation.value >= _expandHeight) { setState(() { - initSize = widget.maxHeight; + initSize = _expandHeight; }); } else if (_animation.value < widget.minHeight) { setState(() {