Podcast page transition with fixed player.

This commit is contained in:
stonega 2021-01-03 00:48:26 +08:00
parent daaeb7c8c1
commit 54268cf8b9
3 changed files with 171 additions and 53 deletions

View File

@ -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<ScrollPodcasts>
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
final s = context.s;
return Selector<GroupList, Tuple2<List<PodcastGroup>, bool>>(
selector: (_, groupList) => Tuple2(groupList.groups, groupList.created),
return Selector<GroupList, tuple.Tuple2<List<PodcastGroup>, 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<ScrollPodcasts>
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<Tuple5<int, bool, bool, bool, List<int>>> _initData(
Future<tuple.Tuple5<int, bool, bool, bool, List<int>>> _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<int> _isListened(EpisodeBrief episode) async {
@ -690,17 +704,17 @@ class ShowEpisode extends StatelessWidget {
(context, index) {
final c = podcastLocal.backgroudColor(context);
return Selector<AudioPlayerNotifier,
Tuple2<EpisodeBrief, List<String>>>(
selector: (_, audio) => Tuple2(
tuple.Tuple2<EpisodeBrief, List<String>>>(
selector: (_, audio) => tuple.Tuple2(
audio?.episode,
audio.queue.episodes
.map((e) => e.enclosureUrl)
.toList(),
),
builder: (_, data, __) => FutureBuilder<
Tuple5<int, bool, bool, bool, List<int>>>(
tuple.Tuple5<int, bool, bool, bool, List<int>>>(
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<AudioPlayerNotifier,
Tuple2<EpisodeBrief, bool>>(
selector: (_, audio) => Tuple2(
audio.episode,
audio.playerRunning),
Selector<
AudioPlayerNotifier,
tuple.Tuple2<EpisodeBrief,
bool>>(
selector: (_, audio) =>
tuple.Tuple2(audio.episode,
audio.playerRunning),
builder: (_, data, __) {
return (episodes[index]
.enclosureUrl ==

View File

@ -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<PodcastDetail> {
bool _selectAll;
bool _selectBefore;
bool _selectAfter;
bool _loadEpisodes = false;
///Show podcast info.
bool _showInfo;
@ -106,8 +105,6 @@ class _PodcastDetailState extends State<PodcastDetail> {
_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<PodcastDetail> {
if (snapshot.hasData)
...snapshot.data.item2
.map<Widget>((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<PodcastDetail> {
}
}),
Spacer(),
Material(
color: Colors.transparent,
clipBehavior: Clip.hardEdge,
borderRadius: BorderRadius.circular(100),
child: TweenAnimationBuilder(
duration: Duration(milliseconds: 500),
curve: Curves.easeInOutQuart,
tween: Tween<double>(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<double>(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<bool>(
future: _getHideListened(),
builder: (context, snapshot) {
@ -908,7 +906,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
child: _multiSelect
? Center()
: _actionBar(context)),
if (_loadEpisodes)
if (!widget.hide)
FutureBuilder<List<EpisodeBrief>>(
future: _getRssItem(widget.podcastLocal,
count: _top,

View File

@ -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<void> {
HidePlayerRoute(this.openPage, this.transitionPage, {Duration duration})
: transitionDuration = duration;
final openPage;
final transitionPage;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return Selector<AudioPlayerNotifier, tuple.Tuple2<bool, PlayerHeight>>(
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<double> 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 {}