From 54268cf8b97d40522c6a307ee55e2a21efabc722 Mon Sep 17 00:00:00 2001 From: stonega Date: Sun, 3 Jan 2021 00:48:26 +0800 Subject: [PATCH] Podcast page transition with fixed player. --- lib/home/home_groups.dart | 56 +++++++++++------ lib/podcasts/podcast_detail.dart | 64 +++++++++---------- lib/util/hide_player_route.dart | 104 +++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 53 deletions(-) create mode 100644 lib/util/hide_player_route.dart diff --git a/lib/home/home_groups.dart b/lib/home/home_groups.dart index 764daf1..44c449b 100644 --- a/lib/home/home_groups.dart +++ b/lib/home/home_groups.dart @@ -10,7 +10,7 @@ import 'package:focused_menu/modals.dart'; import 'package:line_icons/line_icons.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; +import 'package:tuple/tuple.dart' as tuple; import '../episodes/episode_detail.dart'; import '../local_storage/key_value_storage.dart'; @@ -26,9 +26,11 @@ import '../type/episodebrief.dart'; import '../type/play_histroy.dart'; import '../type/podcastlocal.dart'; import '../util/extension_helper.dart'; +import '../util/hide_player_route.dart'; import '../util/pageroute.dart'; import '../widgets/custom_widget.dart'; import '../widgets/general_dialog.dart'; +import 'home.dart'; class ScrollPodcasts extends StatefulWidget { @override @@ -106,8 +108,9 @@ class _ScrollPodcastsState extends State Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; final s = context.s; - return Selector, bool>>( - selector: (_, groupList) => Tuple2(groupList.groups, groupList.created), + return Selector, bool>>( + selector: (_, groupList) => + tuple.Tuple2(groupList.groups, groupList.created), builder: (_, data, __) { var groups = data.item1; var import = data.item2; @@ -441,12 +444,23 @@ class _ScrollPodcastsState extends State child: InkWell( onTap: () { Navigator.push( - context, - SlideLeftRoute( - page: PodcastDetail( - podcastLocal: podcastLocal, - )), - ); + context, + HidePlayerRoute( + PodcastDetail( + podcastLocal: podcastLocal, + ), + PodcastDetail( + podcastLocal: + podcastLocal, + hide: true), + duration: Duration( + milliseconds: 300), + ) + // SlideLeftRoute( + // page: PodcastDetail( + // podcastLocal: podcastLocal, + // )), + ); }, child: PodcastPreview( podcastLocal: podcastLocal, @@ -553,7 +567,7 @@ class ShowEpisode extends StatelessWidget { final DBHelper _dbHelper = DBHelper(); ShowEpisode({Key key, this.episodes, this.podcastLocal}) : super(key: key); - Future>> _initData( + Future>> _initData( EpisodeBrief episode) async { final menuList = await _getEpisodeMenu(); final tapToOpen = await _getTapToOpenPopupMenu(); @@ -561,7 +575,7 @@ class ShowEpisode extends StatelessWidget { final liked = await _isLiked(episode); final downloaded = await _isDownloaded(episode); - return Tuple5(listened, liked, downloaded, tapToOpen, menuList); + return tuple.Tuple5(listened, liked, downloaded, tapToOpen, menuList); } Future _isListened(EpisodeBrief episode) async { @@ -690,17 +704,17 @@ class ShowEpisode extends StatelessWidget { (context, index) { final c = podcastLocal.backgroudColor(context); return Selector>>( - selector: (_, audio) => Tuple2( + tuple.Tuple2>>( + selector: (_, audio) => tuple.Tuple2( audio?.episode, audio.queue.episodes .map((e) => e.enclosureUrl) .toList(), ), builder: (_, data, __) => FutureBuilder< - Tuple5>>( + tuple.Tuple5>>( future: _initData(episodes[index]), - initialData: Tuple5(0, false, false, false, []), + initialData: tuple.Tuple5(0, false, false, false, []), builder: (context, snapshot) { final isListened = snapshot.data.item1; final isLiked = snapshot.data.item2; @@ -919,11 +933,13 @@ class ShowEpisode extends StatelessWidget { ), ), Spacer(), - Selector>( - selector: (_, audio) => Tuple2( - audio.episode, - audio.playerRunning), + Selector< + AudioPlayerNotifier, + tuple.Tuple2>( + selector: (_, audio) => + tuple.Tuple2(audio.episode, + audio.playerRunning), builder: (_, data, __) { return (episodes[index] .enclosureUrl == diff --git a/lib/podcasts/podcast_detail.dart b/lib/podcasts/podcast_detail.dart index 26da043..16dd5cc 100644 --- a/lib/podcasts/podcast_detail.dart +++ b/lib/podcasts/podcast_detail.dart @@ -30,7 +30,7 @@ import '../widgets/general_dialog.dart'; import '../widgets/muiliselect_bar.dart'; import 'podcast_settings.dart'; -const KDefaultAvatar = """http://xuanmei.us/assets/default/avatar_small- +const String kDefaultAvatar = """http://xuanmei.us/assets/default/avatar_small- 170afdc2be97fc6148b283083942d82c101d4c1061f6b28f87c8958b52664af9.jpg"""; class PodcastDetail extends StatefulWidget { @@ -89,7 +89,6 @@ class _PodcastDetailState extends State { bool _selectAll; bool _selectBefore; bool _selectAfter; - bool _loadEpisodes = false; ///Show podcast info. bool _showInfo; @@ -106,8 +105,6 @@ class _PodcastDetailState extends State { _selectAfter = false; _selectBefore = false; _showInfo = false; - Future.delayed(Duration(milliseconds: 200)) - .then((value) => setState(() => _loadEpisodes = true)); } @override @@ -293,8 +290,8 @@ class _PodcastDetailState extends State { if (snapshot.hasData) ...snapshot.data.item2 .map((host) { - final image = host.image == KDefaultAvatar - ? KDefaultAvatar + final image = host.image == kDefaultAvatar + ? kDefaultAvatar : host.image; return Container( padding: EdgeInsets.fromLTRB(5, 10, 5, 0), @@ -615,35 +612,36 @@ class _PodcastDetailState extends State { } }), Spacer(), - Material( - color: Colors.transparent, - clipBehavior: Clip.hardEdge, - borderRadius: BorderRadius.circular(100), - child: TweenAnimationBuilder( - duration: Duration(milliseconds: 500), - curve: Curves.easeInOutQuart, - tween: Tween(begin: 0.0, end: 1.0), - builder: (context, angle, child) => Transform.rotate( - angle: math.pi * 2 * angle, - child: SizedBox( - width: 30, - child: IconButton( - padding: EdgeInsets.zero, - tooltip: s.homeSubMenuSortBy, - icon: Icon( - _reverse - ? LineIcons.hourglass_start_solid - : LineIcons.hourglass_end_solid, - color: _reverse ? context.accentColor : null, + if (!widget.hide) + Material( + color: Colors.transparent, + clipBehavior: Clip.hardEdge, + borderRadius: BorderRadius.circular(100), + child: TweenAnimationBuilder( + duration: Duration(milliseconds: 500), + curve: Curves.easeInOutQuart, + tween: Tween(begin: 0.0, end: 1.0), + builder: (context, angle, child) => Transform.rotate( + angle: math.pi * 2 * angle, + child: SizedBox( + width: 30, + child: IconButton( + padding: EdgeInsets.zero, + tooltip: s.homeSubMenuSortBy, + icon: Icon( + _reverse + ? LineIcons.hourglass_start_solid + : LineIcons.hourglass_end_solid, + color: _reverse ? context.accentColor : null, + ), + iconSize: 18, + onPressed: () { + setState(() => _reverse = !_reverse); + }, ), - iconSize: 18, - onPressed: () { - setState(() => _reverse = !_reverse); - }, ), ), - ), - )), + )), FutureBuilder( future: _getHideListened(), builder: (context, snapshot) { @@ -908,7 +906,7 @@ class _PodcastDetailState extends State { child: _multiSelect ? Center() : _actionBar(context)), - if (_loadEpisodes) + if (!widget.hide) FutureBuilder>( future: _getRssItem(widget.podcastLocal, count: _top, diff --git a/lib/util/hide_player_route.dart b/lib/util/hide_player_route.dart new file mode 100644 index 0000000..e4f1d25 --- /dev/null +++ b/lib/util/hide_player_route.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart' as tuple; + +import '../home/audioplayer.dart'; +import '../state/audio_state.dart'; +import '../util/extension_helper.dart'; + +class HidePlayerRoute extends ModalRoute { + HidePlayerRoute(this.openPage, this.transitionPage, {Duration duration}) + : transitionDuration = duration; + final openPage; + final transitionPage; + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return Selector>( + selector: (_, audio) => + tuple.Tuple2(audio.playerRunning, audio.playerHeight), + builder: (_, data, __) => Align( + alignment: Alignment.topLeft, + child: AnimatedBuilder( + animation: animation, + builder: (context, child) { + if (animation.isCompleted) { + return SizedBox.expand( + child: Material( + color: Colors.transparent, + child: Builder( + builder: (context) { + return openPage; + }, + ), + ), + ); + } + final Animation curvedAnimation = CurvedAnimation( + parent: animation, + curve: Curves.fastOutSlowIn, + reverseCurve: Curves.fastOutSlowIn.flipped, + ); + final playerHeight = kMinPlayerHeight[data.item2.index]; + final playerRunning = data.item1; + return SizedBox.expand( + child: Container( + child: Align( + alignment: Alignment.topLeft, + child: Transform.translate( + offset: + Offset(context.width * (1 - animation.value), 0), + child: SizedBox( + width: context.width, + height: context.height * + (playerRunning + ? (1 - playerHeight / context.height) + : 1), + child: Material( + clipBehavior: Clip.antiAlias, + animationDuration: Duration.zero, + child: FittedBox( + fit: BoxFit.fitWidth, + alignment: Alignment.topLeft, + child: SizedBox( + width: context.width, + height: context.height, + child: Builder( + builder: (context) { + return transitionPage; + }, + ), + ), + ), + ), + ), + ), + ), + ), + ); + }, + ), + )); + } + + @override + bool get maintainState => true; + + @override + Color get barrierColor => null; + + @override + bool get opaque => true; + + @override + bool get barrierDismissible => false; + + @override + String get barrierLabel => null; + + @override + final Duration transitionDuration; +} + +mixin Tuple2 {}