Podcast page transition with fixed player.
This commit is contained in:
parent
daaeb7c8c1
commit
54268cf8b9
|
@ -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 ==
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {}
|
Loading…
Reference in New Issue