Hide listened.
This commit is contained in:
parent
5a32511ac9
commit
208edeb67e
|
@ -60,45 +60,46 @@ Widget _downloadButton(EpisodeTask task, BuildContext context) {
|
|||
]);
|
||||
break;
|
||||
default:
|
||||
return Center();
|
||||
return SizedBox(
|
||||
width: 10,
|
||||
height: 10,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DownloadListState extends State<DownloadList> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverPadding(
|
||||
padding: EdgeInsets.zero,
|
||||
sliver: Consumer<DownloadState>(builder: (_, downloader, __) {
|
||||
final tasks = downloader.episodeTasks
|
||||
.where((task) => task.status.value != 3)
|
||||
.toList();
|
||||
return tasks.length > 0
|
||||
? SliverPadding(
|
||||
padding: EdgeInsets.symmetric(vertical: 5.0),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return ListTile(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
ScaleRoute(
|
||||
page: EpisodeDetail(
|
||||
episodeItem: tasks[index].episode,
|
||||
)),
|
||||
),
|
||||
title: Row(
|
||||
return Consumer<DownloadState>(builder: (_, downloader, __) {
|
||||
final tasks = downloader.episodeTasks
|
||||
.where((task) => task.status.value != 3)
|
||||
.toList();
|
||||
return tasks.length > 0
|
||||
? SliverPadding(
|
||||
padding: EdgeInsets.symmetric(vertical: 5.0),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return ListTile(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
ScaleRoute(
|
||||
page: EpisodeDetail(
|
||||
episodeItem: tasks[index].episode,
|
||||
)),
|
||||
),
|
||||
title: SizedBox(
|
||||
height: 40,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: Container(
|
||||
child: Text(
|
||||
tasks[index].episode.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
child: Text(
|
||||
tasks[index].episode.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
|
@ -108,6 +109,7 @@ class _DownloadListState extends State<DownloadList> {
|
|||
DownloadTaskStatus.failed
|
||||
? Container(
|
||||
width: 40.0,
|
||||
height: 20.0,
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 2),
|
||||
alignment: Alignment.center,
|
||||
|
@ -121,29 +123,32 @@ class _DownloadListState extends State<DownloadList> {
|
|||
maxLines: 1,
|
||||
style: TextStyle(color: Colors.white),
|
||||
))
|
||||
: Container(),
|
||||
: Container(
|
||||
height: 40,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: SizedBox(
|
||||
height: 2,
|
||||
child: LinearProgressIndicator(
|
||||
value: tasks[index].progress / 100,
|
||||
),
|
||||
),
|
||||
subtitle: SizedBox(
|
||||
height: 2,
|
||||
child: LinearProgressIndicator(
|
||||
value: tasks[index].progress / 100,
|
||||
),
|
||||
leading: CircleAvatar(
|
||||
backgroundImage: tasks[index].episode.avatarImage),
|
||||
trailing: _downloadButton(tasks[index], context),
|
||||
);
|
||||
},
|
||||
childCount: tasks.length,
|
||||
),
|
||||
),
|
||||
leading: CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundImage: tasks[index].episode.avatarImage),
|
||||
trailing: _downloadButton(tasks[index], context),
|
||||
);
|
||||
},
|
||||
childCount: tasks.length,
|
||||
),
|
||||
)
|
||||
: SliverToBoxAdapter(
|
||||
child: Center(),
|
||||
);
|
||||
}),
|
||||
);
|
||||
),
|
||||
)
|
||||
: SliverToBoxAdapter(
|
||||
child: Center(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,213 +98,66 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
var settings = Provider.of<SettingState>(context, listen: false);
|
||||
final s = context.s;
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
systemNavigationBarIconBrightness:
|
||||
Theme.of(context).accentColorBrightness,
|
||||
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
|
||||
systemNavigationBarColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
child: Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (_playerKey.currentState != null &&
|
||||
_playerKey.currentState.initSize > 100) {
|
||||
_playerKey.currentState.backToMini();
|
||||
return false;
|
||||
} else if (Platform.isAndroid) {
|
||||
_androidAppRetain.invokeMethod('sendToBackground');
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
child: SafeArea(
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: NestedScrollView(
|
||||
innerScrollPositionKeyBuilder: () {
|
||||
return Key('tab${_controller.index}');
|
||||
},
|
||||
pinnedHeaderSliverHeightBuilder: () => 50,
|
||||
headerSliverBuilder: (context, innerBoxScrolled) {
|
||||
return <Widget>[
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
height: 50.0,
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
DescribedFeatureOverlay(
|
||||
featureId: addFeature,
|
||||
tapTarget:
|
||||
Icon(Icons.add_circle_outline),
|
||||
title:
|
||||
Text(s.featureDiscoverySearch),
|
||||
backgroundColor: Colors.cyan[600],
|
||||
overflowMode: feature1OverflowMode,
|
||||
onDismiss: () {
|
||||
return Future.value(true);
|
||||
},
|
||||
description: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(s
|
||||
.featureDiscoverySearchDes),
|
||||
FlatButton(
|
||||
color: Colors.cyan[500],
|
||||
padding:
|
||||
const EdgeInsets.all(0),
|
||||
child: Text(s.understood,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(
|
||||
color: Colors
|
||||
.white)),
|
||||
onPressed: () async =>
|
||||
FeatureDiscovery
|
||||
.completeCurrentStep(
|
||||
context),
|
||||
),
|
||||
FlatButton(
|
||||
color: Colors.cyan[500],
|
||||
padding:
|
||||
const EdgeInsets.all(0),
|
||||
child: Text(s.dismiss,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(
|
||||
color: Colors
|
||||
.white)),
|
||||
onPressed: () =>
|
||||
FeatureDiscovery
|
||||
.dismissAll(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: IconButton(
|
||||
tooltip: s.add,
|
||||
icon: const Icon(
|
||||
Icons.add_circle_outline),
|
||||
onPressed: () async {
|
||||
await showSearch<int>(
|
||||
context: context,
|
||||
delegate: MyHomePageDelegate(
|
||||
searchFieldLabel:
|
||||
s.searchPodcast),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => {
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.light
|
||||
? settings.setTheme =
|
||||
ThemeMode.dark
|
||||
: settings.setTheme =
|
||||
ThemeMode.light
|
||||
},
|
||||
child: Image(
|
||||
image: Theme.of(context)
|
||||
.brightness ==
|
||||
Brightness.light
|
||||
? AssetImage(
|
||||
'assets/text.png')
|
||||
: AssetImage(
|
||||
'assets/text_light.png'),
|
||||
height: 30,
|
||||
),
|
||||
),
|
||||
DescribedFeatureOverlay(
|
||||
featureId: menuFeature,
|
||||
tapTarget: Icon(Icons.more_vert),
|
||||
backgroundColor: Colors.cyan[500],
|
||||
onDismiss: () =>
|
||||
Future.value(true),
|
||||
title:
|
||||
Text(s.featureDiscoveryOMPL),
|
||||
description: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Text(s
|
||||
.featureDiscoveryOMPLDes),
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
padding:
|
||||
const EdgeInsets.all(0),
|
||||
child: Text(s.understood,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(
|
||||
color: Colors
|
||||
.white)),
|
||||
onPressed: () async =>
|
||||
FeatureDiscovery
|
||||
.completeCurrentStep(
|
||||
context),
|
||||
),
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
padding:
|
||||
const EdgeInsets.all(0),
|
||||
child: Text(s.dismiss,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(
|
||||
color: Colors
|
||||
.white)),
|
||||
onPressed: () =>
|
||||
FeatureDiscovery
|
||||
.dismissAll(
|
||||
context),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: PopupMenu()),
|
||||
],
|
||||
),
|
||||
),
|
||||
Import(),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return DescribedFeatureOverlay(
|
||||
featureId: groupsFeature,
|
||||
tapTarget: Center(
|
||||
child: Text(
|
||||
s.featureDiscoveryPodcast,
|
||||
textAlign: TextAlign.center,
|
||||
)),
|
||||
backgroundColor: Colors.cyan[500],
|
||||
enablePulsingAnimation: false,
|
||||
onDismiss: () => Future.value(true),
|
||||
title:
|
||||
Text(s.featureDiscoveryPodcastTitle),
|
||||
description: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(s.featureDiscoveryPodcastDes),
|
||||
Row(
|
||||
children: [
|
||||
value: SystemUiOverlayStyle(
|
||||
systemNavigationBarIconBrightness:
|
||||
Theme.of(context).accentColorBrightness,
|
||||
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
|
||||
systemNavigationBarColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
child: Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (_playerKey.currentState != null &&
|
||||
_playerKey.currentState.initSize > 100) {
|
||||
_playerKey.currentState.backToMini();
|
||||
return false;
|
||||
} else if (Platform.isAndroid) {
|
||||
_androidAppRetain.invokeMethod('sendToBackground');
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
child: SafeArea(
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: NestedScrollView(
|
||||
innerScrollPositionKeyBuilder: () {
|
||||
return Key('tab${_controller.index}');
|
||||
},
|
||||
pinnedHeaderSliverHeightBuilder: () => 50,
|
||||
headerSliverBuilder: (context, innerBoxScrolled) {
|
||||
return <Widget>[
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
height: 50.0,
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
DescribedFeatureOverlay(
|
||||
featureId: addFeature,
|
||||
tapTarget:
|
||||
Icon(Icons.add_circle_outline),
|
||||
title: Text(s.featureDiscoverySearch),
|
||||
backgroundColor: Colors.cyan[600],
|
||||
overflowMode: feature1OverflowMode,
|
||||
onDismiss: () {
|
||||
return Future.value(true);
|
||||
},
|
||||
description: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(s.featureDiscoverySearchDes),
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
color: Colors.cyan[500],
|
||||
padding:
|
||||
const EdgeInsets.all(0),
|
||||
child: Text(s.understood,
|
||||
|
@ -319,11 +172,8 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
.completeCurrentStep(
|
||||
context),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 5)),
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
color: Colors.cyan[500],
|
||||
padding:
|
||||
const EdgeInsets.all(0),
|
||||
child: Text(s.dismiss,
|
||||
|
@ -339,66 +189,50 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
child: SizedBox(
|
||||
height: height,
|
||||
width: width,
|
||||
child: ScrollPodcasts(),
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: 1,
|
||||
),
|
||||
),
|
||||
SliverPersistentHeader(
|
||||
delegate: _SliverAppBarDelegate(
|
||||
TabBar(
|
||||
indicator: _getIndicator(context),
|
||||
isScrollable: true,
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
controller: _controller,
|
||||
tabs: <Widget>[
|
||||
Tab(
|
||||
child: Text(s.homeTabMenuRecent),
|
||||
),
|
||||
Tab(
|
||||
child: Text(s.homeTabMenuFavotite),
|
||||
),
|
||||
Tab(
|
||||
child: Text(s.download),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
pinned: true,
|
||||
),
|
||||
];
|
||||
},
|
||||
body: ScrollConfiguration(
|
||||
behavior: NoGrowBehavior(),
|
||||
child: TabBarView(
|
||||
controller: _controller,
|
||||
children: <Widget>[
|
||||
NestedScrollViewInnerScrollPositionKeyWidget(
|
||||
Key('tab0'),
|
||||
DescribedFeatureOverlay(
|
||||
featureId: podcastFeature,
|
||||
tapTarget: Text(
|
||||
s.featureDiscoveryEpisode,
|
||||
textAlign: TextAlign.center),
|
||||
backgroundColor: Colors.cyan[500],
|
||||
enablePulsingAnimation: false,
|
||||
onDismiss: () => Future.value(true),
|
||||
title: Text(
|
||||
s.featureDiscoveryEpisodeTitle),
|
||||
description: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(s.featureDiscoveryEpisodeDes),
|
||||
Row(
|
||||
children: [
|
||||
child: IconButton(
|
||||
tooltip: s.add,
|
||||
icon: const Icon(
|
||||
Icons.add_circle_outline),
|
||||
onPressed: () async {
|
||||
await showSearch<int>(
|
||||
context: context,
|
||||
delegate: MyHomePageDelegate(
|
||||
searchFieldLabel:
|
||||
s.searchPodcast),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => {
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.light
|
||||
? settings.setTheme =
|
||||
ThemeMode.dark
|
||||
: settings.setTheme =
|
||||
ThemeMode.light
|
||||
},
|
||||
child: Image(
|
||||
image: Theme.of(context)
|
||||
.brightness ==
|
||||
Brightness.light
|
||||
? AssetImage('assets/text.png')
|
||||
: AssetImage(
|
||||
'assets/text_light.png'),
|
||||
height: 30,
|
||||
),
|
||||
),
|
||||
DescribedFeatureOverlay(
|
||||
featureId: menuFeature,
|
||||
tapTarget: Icon(Icons.more_vert),
|
||||
backgroundColor: Colors.cyan[500],
|
||||
onDismiss: () => Future.value(true),
|
||||
title: Text(s.featureDiscoveryOMPL),
|
||||
description: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Text(s.featureDiscoveryOMPLDes),
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
padding:
|
||||
|
@ -415,10 +249,6 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
.completeCurrentStep(
|
||||
context),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsets.symmetric(
|
||||
horizontal: 5)),
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
padding:
|
||||
|
@ -436,33 +266,182 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
),
|
||||
],
|
||||
),
|
||||
child: PopupMenu()),
|
||||
],
|
||||
),
|
||||
),
|
||||
Import(),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return DescribedFeatureOverlay(
|
||||
featureId: groupsFeature,
|
||||
tapTarget: Center(
|
||||
child: Text(
|
||||
s.featureDiscoveryPodcast,
|
||||
textAlign: TextAlign.center,
|
||||
)),
|
||||
backgroundColor: Colors.cyan[500],
|
||||
enablePulsingAnimation: false,
|
||||
onDismiss: () => Future.value(true),
|
||||
title: Text(s.featureDiscoveryPodcastTitle),
|
||||
description: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(s.featureDiscoveryPodcastDes),
|
||||
Row(
|
||||
children: [
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Text(s.understood,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(
|
||||
color: Colors.white)),
|
||||
onPressed: () async =>
|
||||
FeatureDiscovery
|
||||
.completeCurrentStep(
|
||||
context),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 5)),
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Text(s.dismiss,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(
|
||||
color: Colors.white)),
|
||||
onPressed: () =>
|
||||
FeatureDiscovery.dismissAll(
|
||||
context),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: _RecentUpdate())),
|
||||
NestedScrollViewInnerScrollPositionKeyWidget(
|
||||
Key('tab1'), _MyFavorite()),
|
||||
NestedScrollViewInnerScrollPositionKeyWidget(
|
||||
Key('tab2'), _MyDownload()),
|
||||
],
|
||||
],
|
||||
),
|
||||
child: SizedBox(
|
||||
height: height,
|
||||
width: width,
|
||||
child: ScrollPodcasts(),
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverPersistentHeader(
|
||||
delegate: _SliverAppBarDelegate(
|
||||
TabBar(
|
||||
indicator: _getIndicator(context),
|
||||
isScrollable: true,
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
controller: _controller,
|
||||
tabs: <Widget>[
|
||||
Tab(
|
||||
child: Text(s.homeTabMenuRecent),
|
||||
),
|
||||
Tab(
|
||||
child: Text(s.homeTabMenuFavotite),
|
||||
),
|
||||
Tab(
|
||||
child: Text(s.download),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
pinned: true,
|
||||
),
|
||||
];
|
||||
},
|
||||
body: TabBarView(
|
||||
controller: _controller,
|
||||
children: <Widget>[
|
||||
NestedScrollViewInnerScrollPositionKeyWidget(
|
||||
Key('tab0'),
|
||||
DescribedFeatureOverlay(
|
||||
featureId: podcastFeature,
|
||||
tapTarget: Text(s.featureDiscoveryEpisode,
|
||||
textAlign: TextAlign.center),
|
||||
backgroundColor: Colors.cyan[500],
|
||||
enablePulsingAnimation: false,
|
||||
onDismiss: () => Future.value(true),
|
||||
title: Text(s.featureDiscoveryEpisodeTitle),
|
||||
description: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(s.featureDiscoveryEpisodeDes),
|
||||
Row(
|
||||
children: [
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Text(s.understood,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(
|
||||
color: Colors.white)),
|
||||
onPressed: () async =>
|
||||
FeatureDiscovery
|
||||
.completeCurrentStep(
|
||||
context),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 5)),
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Text(s.dismiss,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(
|
||||
color: Colors.white)),
|
||||
onPressed: () =>
|
||||
FeatureDiscovery.dismissAll(
|
||||
context),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
child: _RecentUpdate())),
|
||||
NestedScrollViewInnerScrollPositionKeyWidget(
|
||||
Key('tab1'), _MyFavorite()),
|
||||
NestedScrollViewInnerScrollPositionKeyWidget(
|
||||
Key('tab2'), _MyDownload()),
|
||||
],
|
||||
),
|
||||
),
|
||||
Selector<AudioPlayerNotifier, bool>(
|
||||
selector: (_, audio) => audio.playerRunning,
|
||||
builder: (_, data, __) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: data ? 60.0 : 0),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
Container(child: PlayerWidget(playerKey: _playerKey)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Selector<AudioPlayerNotifier, bool>(
|
||||
selector: (_, audio) => audio.playerRunning,
|
||||
builder: (_, data, __) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: data ? 60.0 : 0),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
Container(child: PlayerWidget(playerKey: _playerKey)),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -728,17 +707,23 @@ class _RecentUpdate extends StatefulWidget {
|
|||
|
||||
class _RecentUpdateState extends State<_RecentUpdate>
|
||||
with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin {
|
||||
Future<List<EpisodeBrief>> _getRssItem(int top, List<String> group) async {
|
||||
Future<List<EpisodeBrief>> _getRssItem(int top, List<String> group,
|
||||
{bool hideListened}) async {
|
||||
var storage = KeyValueStorage(recentLayoutKey);
|
||||
var hideListenedStorage = KeyValueStorage(hideListenedKey);
|
||||
var index = await storage.getInt(defaultValue: 1);
|
||||
if (_layout == null) _layout = Layout.values[index];
|
||||
|
||||
if (_hideListened == null) {
|
||||
_hideListened = await hideListenedStorage.getBool(defaultValue: false);
|
||||
}
|
||||
var dbHelper = DBHelper();
|
||||
List<EpisodeBrief> episodes;
|
||||
if (group.first == 'All') {
|
||||
episodes = await dbHelper.getRecentRssItem(top);
|
||||
episodes =
|
||||
await dbHelper.getRecentRssItem(top, hideListened: _hideListened);
|
||||
} else {
|
||||
episodes = await dbHelper.getGroupRssItem(top, group);
|
||||
episodes = await dbHelper.getGroupRssItem(top, group,
|
||||
hideListened: _hideListened);
|
||||
}
|
||||
return episodes;
|
||||
}
|
||||
|
@ -755,7 +740,7 @@ class _RecentUpdateState extends State<_RecentUpdate>
|
|||
}
|
||||
|
||||
/// Load more episodes.
|
||||
_loadMoreEpisode() async {
|
||||
Future<void> _loadMoreEpisode() async {
|
||||
if (mounted) setState(() => _loadMore = true);
|
||||
await Future.delayed(Duration(seconds: 3));
|
||||
if (mounted) {
|
||||
|
@ -776,6 +761,7 @@ class _RecentUpdateState extends State<_RecentUpdate>
|
|||
String _groupName;
|
||||
List<String> _group;
|
||||
Layout _layout;
|
||||
bool _hideListened;
|
||||
bool _scroll;
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -797,7 +783,7 @@ class _RecentUpdateState extends State<_RecentUpdate>
|
|||
selector: (_, worker) => worker.created,
|
||||
builder: (context, created, child) {
|
||||
return FutureBuilder<List<EpisodeBrief>>(
|
||||
future: _getRssItem(_top, _group),
|
||||
future: _getRssItem(_top, _group, hideListened: _hideListened),
|
||||
builder: (context, snapshot) {
|
||||
return (snapshot.hasData)
|
||||
? snapshot.data.length == 0
|
||||
|
@ -1006,6 +992,25 @@ class _RecentUpdateState extends State<_RecentUpdate>
|
|||
() {}),
|
||||
);
|
||||
}),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
icon: SizedBox(
|
||||
width: 30,
|
||||
height: 15,
|
||||
child: HideListened(
|
||||
hideListened:
|
||||
_hideListened ??
|
||||
false,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() =>
|
||||
_hideListened =
|
||||
!_hideListened);
|
||||
},
|
||||
),
|
||||
),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: LayoutButton(
|
||||
|
@ -1057,16 +1062,22 @@ class _MyFavorite extends StatefulWidget {
|
|||
|
||||
class _MyFavoriteState extends State<_MyFavorite>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
Future<List<EpisodeBrief>> _getLikedRssItem(int top, int sortBy) async {
|
||||
Future<List<EpisodeBrief>> _getLikedRssItem(int top, int sortBy,
|
||||
{bool hideListened}) async {
|
||||
var storage = KeyValueStorage(favLayoutKey);
|
||||
var index = await storage.getInt(defaultValue: 1);
|
||||
var hideListenedStorage = KeyValueStorage(hideListenedKey);
|
||||
if (_layout == null) _layout = Layout.values[index];
|
||||
if (_hideListened == null) {
|
||||
_hideListened = await hideListenedStorage.getBool(defaultValue: false);
|
||||
}
|
||||
var dbHelper = DBHelper();
|
||||
var episodes = await dbHelper.getLikedRssItem(top, sortBy);
|
||||
var episodes = await dbHelper.getLikedRssItem(top, sortBy,
|
||||
hideListened: _hideListened);
|
||||
return episodes;
|
||||
}
|
||||
|
||||
_loadMoreEpisode() async {
|
||||
Future<void> _loadMoreEpisode() async {
|
||||
if (mounted) setState(() => _loadMore = true);
|
||||
await Future.delayed(Duration(seconds: 3));
|
||||
if (mounted) {
|
||||
|
@ -1081,6 +1092,7 @@ class _MyFavoriteState extends State<_MyFavorite>
|
|||
bool _loadMore;
|
||||
Layout _layout;
|
||||
int _sortBy;
|
||||
bool _hideListened;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -1096,7 +1108,8 @@ class _MyFavoriteState extends State<_MyFavorite>
|
|||
selector: (_, audio) => audio.episodeState,
|
||||
builder: (context, episodeState, child) {
|
||||
return FutureBuilder<List<EpisodeBrief>>(
|
||||
future: _getLikedRssItem(_top, _sortBy),
|
||||
future:
|
||||
_getLikedRssItem(_top, _sortBy, hideListened: _hideListened),
|
||||
builder: (context, snapshot) {
|
||||
return (snapshot.hasData)
|
||||
? snapshot.data.length == 0
|
||||
|
@ -1199,6 +1212,23 @@ class _MyFavoriteState extends State<_MyFavorite>
|
|||
),
|
||||
),
|
||||
Spacer(),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
icon: SizedBox(
|
||||
width: 30,
|
||||
height: 15,
|
||||
child: HideListened(
|
||||
hideListened:
|
||||
_hideListened ?? false,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() => _hideListened =
|
||||
!_hideListened);
|
||||
},
|
||||
),
|
||||
),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: LayoutButton(
|
||||
|
@ -1249,89 +1279,100 @@ class _MyDownload extends StatefulWidget {
|
|||
class _MyDownloadState extends State<_MyDownload>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
Layout _layout;
|
||||
_getLayout() async {
|
||||
var keyValueStorage = KeyValueStorage(downloadLayoutKey);
|
||||
var layout = await keyValueStorage.getInt(defaultValue: 1);
|
||||
if (_layout == null) {
|
||||
setState(() {
|
||||
_layout = Layout.values[layout];
|
||||
});
|
||||
int _sortBy = 0;
|
||||
bool _hideListened;
|
||||
Future<List<EpisodeBrief>> _getDownloadedEpisodes(int sortBy,
|
||||
{bool hideListened}) async {
|
||||
var storage = KeyValueStorage(downloadLayoutKey);
|
||||
var index = await storage.getInt(defaultValue: 1);
|
||||
var hideListenedStorage = KeyValueStorage(hideListenedKey);
|
||||
if (_layout == null) _layout = Layout.values[index];
|
||||
if (_hideListened == null) {
|
||||
_hideListened = await hideListenedStorage.getBool(defaultValue: false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_getLayout();
|
||||
var dbHelper = DBHelper();
|
||||
var episodes = await dbHelper.getDownloadedEpisode(sortBy,
|
||||
hideListened: _hideListened);
|
||||
return episodes;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
final s = context.s;
|
||||
return CustomScrollView(
|
||||
key: PageStorageKey<String>('download_list'),
|
||||
slivers: <Widget>[
|
||||
DownloadList(),
|
||||
SliverToBoxAdapter(
|
||||
child: _layout == null
|
||||
? Center()
|
||||
: Container(
|
||||
height: 40,
|
||||
color: context.primaryColor,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(s.downloaded)),
|
||||
Spacer(),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: LayoutButton(
|
||||
layout: _layout,
|
||||
onPressed: (layout) => setState(() {
|
||||
_layout = layout;
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
Consumer<DownloadState>(
|
||||
builder: (_, downloader, __) {
|
||||
var episodes = downloader.episodeTasks
|
||||
.where((task) => task.status.value == 3)
|
||||
.toList()
|
||||
.map((e) => e.episode)
|
||||
.toList()
|
||||
.reversed
|
||||
.toList();
|
||||
return episodes.length == 0
|
||||
? SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 110),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Icon(LineIcons.download_solid,
|
||||
size: 80, color: Colors.grey[500]),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 10)),
|
||||
Text(
|
||||
s.noEpisodeDownload,
|
||||
style: TextStyle(color: Colors.grey[500]),
|
||||
)
|
||||
return Consumer<DownloadState>(
|
||||
builder: (_, data, __) => FutureBuilder<List<EpisodeBrief>>(
|
||||
future: _getDownloadedEpisodes(_sortBy, hideListened: _hideListened),
|
||||
builder: (context, snapshot) {
|
||||
var episodes = snapshot.data ?? [];
|
||||
return CustomScrollView(
|
||||
key: PageStorageKey<String>('download_list'),
|
||||
slivers: <Widget>[
|
||||
DownloadList(),
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
height: 40,
|
||||
color: context.primaryColor,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(s.downloaded)),
|
||||
Spacer(),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
icon: SizedBox(
|
||||
width: 30,
|
||||
height: 15,
|
||||
child: HideListened(
|
||||
hideListened: _hideListened ?? false,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() => _hideListened = !_hideListened);
|
||||
},
|
||||
),
|
||||
),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: LayoutButton(
|
||||
layout: _layout ?? Layout.one,
|
||||
onPressed: (layout) => setState(() {
|
||||
_layout = layout;
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
episodes.length == 0
|
||||
? SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 110),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Icon(LineIcons.download_solid,
|
||||
size: 80, color: Colors.grey[500]),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 10)),
|
||||
Text(
|
||||
s.noEpisodeDownload,
|
||||
style: TextStyle(color: Colors.grey[500]),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: EpisodeGrid(
|
||||
episodes: episodes,
|
||||
layout: _layout,
|
||||
initNum: 0,
|
||||
),
|
||||
),
|
||||
)
|
||||
: EpisodeGrid(
|
||||
episodes: episodes,
|
||||
layout: _layout,
|
||||
initNum: 0,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ const String skipSilenceKey = 'skipSilenceKey';
|
|||
const String localeKey = 'localeKey';
|
||||
const String boostVolumeKey = 'boostVolumeKey';
|
||||
const String volumeGainKey = 'volumeGainKey';
|
||||
const String hideListenedKey = 'hideListenedKey';
|
||||
|
||||
class KeyValueStorage {
|
||||
final String key;
|
||||
|
|
|
@ -799,32 +799,55 @@ class DBHelper {
|
|||
return episodes;
|
||||
}
|
||||
|
||||
Future<List<EpisodeBrief>> getRecentRssItem(int top) async {
|
||||
Future<List<EpisodeBrief>> getRecentRssItem(int top,
|
||||
{bool hideListened = false}) async {
|
||||
var dbClient = await database;
|
||||
var episodes = <EpisodeBrief>[];
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
var list = <Map>[];
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
ORDER BY E.milliseconds DESC LIMIT ? """, [top]);
|
||||
for (var i in list) {
|
||||
episodes.add(EpisodeBrief(
|
||||
i['title'],
|
||||
i['enclosure_url'],
|
||||
i['enclosure_length'],
|
||||
i['milliseconds'],
|
||||
i['feed_title'],
|
||||
i['primaryColor'],
|
||||
i['duration'],
|
||||
i['explicit'],
|
||||
i['imagePath'],
|
||||
i['is_new']));
|
||||
if (list.isNotEmpty) {
|
||||
if (!hideListened) {
|
||||
for (var i in list) {
|
||||
episodes.add(EpisodeBrief(
|
||||
i['title'],
|
||||
i['enclosure_url'],
|
||||
i['enclosure_length'],
|
||||
i['milliseconds'],
|
||||
i['feedTitle'],
|
||||
i['primaryColor'],
|
||||
i['duration'],
|
||||
i['explicit'],
|
||||
i['imagePath'],
|
||||
i['is_new']));
|
||||
}
|
||||
} else {
|
||||
for (var i in list) {
|
||||
var listened = await isListened(i['enclosure_url']);
|
||||
if (listened == 0) {
|
||||
episodes.add(EpisodeBrief(
|
||||
i['title'],
|
||||
i['enclosure_url'],
|
||||
i['enclosure_length'],
|
||||
i['milliseconds'],
|
||||
i['feedTitle'],
|
||||
i['primaryColor'],
|
||||
i['duration'],
|
||||
i['explicit'],
|
||||
i['imagePath'],
|
||||
i['is_new']));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return episodes;
|
||||
}
|
||||
|
||||
Future<List<EpisodeBrief>> getGroupRssItem(
|
||||
int top, List<String> group) async {
|
||||
Future<List<EpisodeBrief>> getGroupRssItem(int top, List<String> group,
|
||||
{bool hideListened = false}) async {
|
||||
var dbClient = await database;
|
||||
var episodes = <EpisodeBrief>[];
|
||||
if (group.length > 0) {
|
||||
|
@ -835,18 +858,39 @@ class DBHelper {
|
|||
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE P.id in (${s.join(',')})
|
||||
ORDER BY E.milliseconds DESC LIMIT ? """, [top]);
|
||||
for (var i in list) {
|
||||
episodes.add(EpisodeBrief(
|
||||
i['title'],
|
||||
i['enclosure_url'],
|
||||
i['enclosure_length'],
|
||||
i['milliseconds'],
|
||||
i['feed_title'],
|
||||
i['primaryColor'],
|
||||
i['duration'],
|
||||
i['explicit'],
|
||||
i['imagePath'],
|
||||
i['is_new']));
|
||||
if (list.isNotEmpty) {
|
||||
if (!hideListened) {
|
||||
for (var i in list) {
|
||||
episodes.add(EpisodeBrief(
|
||||
i['title'],
|
||||
i['enclosure_url'],
|
||||
i['enclosure_length'],
|
||||
i['milliseconds'],
|
||||
i['feedTitle'],
|
||||
i['primaryColor'],
|
||||
i['duration'],
|
||||
i['explicit'],
|
||||
i['imagePath'],
|
||||
i['is_new']));
|
||||
}
|
||||
} else {
|
||||
for (var i in list) {
|
||||
var listened = await isListened(i['enclosure_url']);
|
||||
if (listened == 0) {
|
||||
episodes.add(EpisodeBrief(
|
||||
i['title'],
|
||||
i['enclosure_url'],
|
||||
i['enclosure_length'],
|
||||
i['milliseconds'],
|
||||
i['feedTitle'],
|
||||
i['primaryColor'],
|
||||
i['duration'],
|
||||
i['explicit'],
|
||||
i['imagePath'],
|
||||
i['is_new']));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return episodes;
|
||||
|
@ -903,7 +947,8 @@ class DBHelper {
|
|||
return episodes;
|
||||
}
|
||||
|
||||
Future<List<EpisodeBrief>> getDownloadedEpisode(int mode) async {
|
||||
Future<List<EpisodeBrief>> getDownloadedEpisode(int mode,
|
||||
{bool hideListened = false}) async {
|
||||
var dbClient = await database;
|
||||
var episodes = <EpisodeBrief>[];
|
||||
List<Map> list;
|
||||
|
@ -933,26 +978,46 @@ class DBHelper {
|
|||
ORDER BY E.enclosure_length DESC""",
|
||||
);
|
||||
}
|
||||
for (var i in list) {
|
||||
episodes.add(
|
||||
EpisodeBrief(
|
||||
i['title'],
|
||||
i['enclosure_url'],
|
||||
i['enclosure_length'],
|
||||
i['milliseconds'],
|
||||
i['feed_title'],
|
||||
i['primaryColor'],
|
||||
i['duration'],
|
||||
i['explicit'],
|
||||
i['imagePath'],
|
||||
i['is_new'],
|
||||
downloadDate: i['download_date']),
|
||||
);
|
||||
if (list.isNotEmpty) {
|
||||
if (!hideListened) {
|
||||
for (var i in list) {
|
||||
episodes.add(EpisodeBrief(
|
||||
i['title'],
|
||||
i['enclosure_url'],
|
||||
i['enclosure_length'],
|
||||
i['milliseconds'],
|
||||
i['feedTitle'],
|
||||
i['primaryColor'],
|
||||
i['duration'],
|
||||
i['explicit'],
|
||||
i['imagePath'],
|
||||
i['is_new'],
|
||||
downloadDate: i['download_date']));
|
||||
}
|
||||
} else {
|
||||
for (var i in list) {
|
||||
var listened = await isListened(i['enclosure_url']);
|
||||
if (listened == 0) {
|
||||
episodes.add(EpisodeBrief(
|
||||
i['title'],
|
||||
i['enclosure_url'],
|
||||
i['enclosure_length'],
|
||||
i['milliseconds'],
|
||||
i['feedTitle'],
|
||||
i['primaryColor'],
|
||||
i['duration'],
|
||||
i['explicit'],
|
||||
i['imagePath'],
|
||||
i['is_new'],
|
||||
downloadDate: i['download_date']));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return episodes;
|
||||
}
|
||||
|
||||
removeAllNewMark() async {
|
||||
Future<void> removeAllNewMark() async {
|
||||
var dbClient = await database;
|
||||
await dbClient.transaction((txn) async {
|
||||
await txn.rawUpdate("UPDATE Episodes SET is_new = 0 ");
|
||||
|
@ -1009,48 +1074,59 @@ class DBHelper {
|
|||
developer.log('remove new episode $url');
|
||||
}
|
||||
|
||||
Future<List<EpisodeBrief>> getLikedRssItem(int i, int sortBy) async {
|
||||
Future<List<EpisodeBrief>> getLikedRssItem(int i, int sortBy,
|
||||
{bool hideListened = false}) async {
|
||||
var dbClient = await database;
|
||||
var episodes = <EpisodeBrief>[];
|
||||
var list = <Map>[];
|
||||
if (sortBy == 0) {
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
||||
P.title as feed_title, E.duration, E.explicit, P.primaryColor, E.is_new
|
||||
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE E.liked = 1 ORDER BY E.milliseconds DESC LIMIT ?""", [i]);
|
||||
for (var i in list) {
|
||||
episodes.add(EpisodeBrief(
|
||||
i['title'],
|
||||
i['enclosure_url'],
|
||||
i['enclosure_length'],
|
||||
i['milliseconds'],
|
||||
i['feed_title'],
|
||||
i['primaryColor'],
|
||||
i['duration'],
|
||||
i['explicit'],
|
||||
i['imagePath'],
|
||||
i['is_new']));
|
||||
}
|
||||
} else {
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
||||
P.title as feed_title, E.duration, E.explicit, P.primaryColor, E.is_new
|
||||
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE E.liked = 1 ORDER BY E.liked_date DESC LIMIT ?""", [i]);
|
||||
for (var i in list) {
|
||||
episodes.add(EpisodeBrief(
|
||||
i['title'],
|
||||
i['enclosure_url'],
|
||||
i['enclosure_length'],
|
||||
i['milliseconds'],
|
||||
i['feed_title'],
|
||||
i['primaryColor'],
|
||||
i['duration'],
|
||||
i['explicit'],
|
||||
i['imagePath'],
|
||||
i['is_new']));
|
||||
}
|
||||
if (list.isNotEmpty) {
|
||||
if (!hideListened) {
|
||||
for (var i in list) {
|
||||
episodes.add(EpisodeBrief(
|
||||
i['title'],
|
||||
i['enclosure_url'],
|
||||
i['enclosure_length'],
|
||||
i['milliseconds'],
|
||||
i['feedTitle'],
|
||||
i['primaryColor'],
|
||||
i['duration'],
|
||||
i['explicit'],
|
||||
i['imagePath'],
|
||||
i['is_new']));
|
||||
}
|
||||
} else {
|
||||
for (var i in list) {
|
||||
var listened = await isListened(i['enclosure_url']);
|
||||
if (listened == 0) {
|
||||
episodes.add(EpisodeBrief(
|
||||
i['title'],
|
||||
i['enclosure_url'],
|
||||
i['enclosure_length'],
|
||||
i['milliseconds'],
|
||||
i['feedTitle'],
|
||||
i['primaryColor'],
|
||||
i['duration'],
|
||||
i['explicit'],
|
||||
i['imagePath'],
|
||||
i['is_new']));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return episodes;
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
String _query = '';
|
||||
|
||||
///Hide listened.
|
||||
bool _hideListened = false;
|
||||
bool _hideListened;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -139,8 +139,12 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
var episodes = <EpisodeBrief>[];
|
||||
_episodeCount = await dbHelper.getPodcastCounts(podcastLocal.id);
|
||||
var storage = KeyValueStorage(podcastLayoutKey);
|
||||
var hideListenedStorage = KeyValueStorage(hideListenedKey);
|
||||
var index = await storage.getInt(defaultValue: 1);
|
||||
if (_layout == null) _layout = Layout.values[index];
|
||||
if (_hideListened == null) {
|
||||
_hideListened = await hideListenedStorage.getBool(defaultValue: false);
|
||||
}
|
||||
episodes = await dbHelper.getRssItem(podcastLocal.id, count,
|
||||
reverse: reverse,
|
||||
filter: filter,
|
||||
|
@ -165,7 +169,13 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
return index;
|
||||
}
|
||||
|
||||
Widget podcastInfo(BuildContext context) {
|
||||
Future<bool> _getHideListened() async {
|
||||
var hideListenedStorage = KeyValueStorage(hideListenedKey);
|
||||
var hideListened = await hideListenedStorage.getBool(defaultValue: false);
|
||||
return hideListened;
|
||||
}
|
||||
|
||||
Widget _podcastInfo(BuildContext context) {
|
||||
return Container(
|
||||
height: 170,
|
||||
padding: EdgeInsets.only(top: 40, left: 80, right: 130),
|
||||
|
@ -182,7 +192,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget hostsList(BuildContext context, PodcastLocal podcastLocal) {
|
||||
Widget _hostsList(BuildContext context, PodcastLocal podcastLocal) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
@ -297,7 +307,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
);
|
||||
}
|
||||
|
||||
_customPopupMenu(
|
||||
Widget _customPopupMenu(
|
||||
{Widget child,
|
||||
String tooltip,
|
||||
List<PopupMenuEntry<int>> itemBuilder,
|
||||
|
@ -318,6 +328,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
onSelected: (value) => onSelected(value),
|
||||
),
|
||||
);
|
||||
|
||||
Widget _rightTopMenu(BuildContext context) {
|
||||
final s = context.s;
|
||||
return _customPopupMenu(
|
||||
|
@ -587,22 +598,29 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
}
|
||||
}),
|
||||
Spacer(),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
child: IconButton(
|
||||
icon: SizedBox(
|
||||
width: 30,
|
||||
height: 30,
|
||||
child: HideListened(
|
||||
hideListened: _hideListened,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() => _hideListened = !_hideListened);
|
||||
},
|
||||
)),
|
||||
FutureBuilder<bool>(
|
||||
future: _getHideListened(),
|
||||
builder: (context, snapshot) {
|
||||
if (_hideListened == null) {
|
||||
_hideListened = snapshot.data;
|
||||
}
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
child: IconButton(
|
||||
icon: SizedBox(
|
||||
width: 30,
|
||||
height: 30,
|
||||
child: HideListened(
|
||||
hideListened: _hideListened ?? false,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() => _hideListened = !_hideListened);
|
||||
},
|
||||
));
|
||||
}),
|
||||
FutureBuilder<int>(
|
||||
future: _getLayout(),
|
||||
initialData: 1,
|
||||
|
@ -656,7 +674,6 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
color: context.accentColor,
|
||||
onRefresh: () async {
|
||||
await _updateRssItem(context, widget.podcastLocal);
|
||||
// audio.addNewEpisode(widget.podcastLocal.id);
|
||||
},
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
|
@ -666,146 +683,151 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
controller: _controller
|
||||
..addListener(() async {
|
||||
if (_controller.offset ==
|
||||
_controller.position.maxScrollExtent &&
|
||||
_dataCount == _top) {
|
||||
if (mounted) {
|
||||
setState(() => _loadMore = true);
|
||||
child: ScrollConfiguration(
|
||||
behavior: NoGrowBehavior(),
|
||||
child: CustomScrollView(
|
||||
controller: _controller
|
||||
..addListener(() async {
|
||||
if (_controller.offset ==
|
||||
_controller.position.maxScrollExtent &&
|
||||
_dataCount == _top) {
|
||||
if (mounted) {
|
||||
setState(() => _loadMore = true);
|
||||
}
|
||||
await Future.delayed(Duration(seconds: 3));
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_top = _top + 36;
|
||||
_loadMore = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
await Future.delayed(Duration(seconds: 3));
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_top = _top + 36;
|
||||
_loadMore = false;
|
||||
});
|
||||
if (_controller.offset > 0 &&
|
||||
mounted &&
|
||||
!_scroll) {
|
||||
setState(() => _scroll = true);
|
||||
}
|
||||
}
|
||||
if (_controller.offset > 0 &&
|
||||
mounted &&
|
||||
!_scroll) {
|
||||
setState(() => _scroll = true);
|
||||
}
|
||||
}),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: <Widget>[
|
||||
SliverAppBar(
|
||||
brightness: Brightness.dark,
|
||||
actions: <Widget>[_rightTopMenu(context)],
|
||||
elevation: 0,
|
||||
iconTheme: IconThemeData(
|
||||
color: Colors.white,
|
||||
),
|
||||
expandedHeight: 150 + context.paddingTop,
|
||||
backgroundColor: _color,
|
||||
floating: true,
|
||||
pinned: true,
|
||||
flexibleSpace: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
_topHeight = constraints.biggest.height;
|
||||
return FlexibleSpaceBar(
|
||||
background: Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
top: 120 + context.paddingTop),
|
||||
padding: EdgeInsets.only(
|
||||
left: 80, right: 120),
|
||||
color: Colors.white10,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
widget.podcastLocal.author ??
|
||||
'',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Colors.white)),
|
||||
if (widget.podcastLocal.provider
|
||||
.isNotEmpty)
|
||||
Text(
|
||||
s.hostedOn(widget
|
||||
.podcastLocal.provider),
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.centerRight,
|
||||
padding: EdgeInsets.only(right: 10),
|
||||
child: SizedBox(
|
||||
height: 120,
|
||||
child: Image.file(File(
|
||||
"${widget.podcastLocal.imagePath}")),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: podcastInfo(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
title: _topHeight < 70 + context.paddingTop
|
||||
? Text(widget.podcastLocal.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.clip,
|
||||
style: TextStyle(color: Colors.white))
|
||||
: Center(),
|
||||
);
|
||||
}),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: hostsList(context, widget.podcastLocal),
|
||||
),
|
||||
SliverToBoxAdapter(child: _actionBar(context)),
|
||||
FutureBuilder<List<EpisodeBrief>>(
|
||||
future: _getRssItem(widget.podcastLocal,
|
||||
count: _top,
|
||||
reverse: _reverse,
|
||||
filter: _filter,
|
||||
query: _query),
|
||||
builder: (context, snapshot) {
|
||||
return (snapshot.hasData)
|
||||
? EpisodeGrid(
|
||||
episodes: snapshot.data,
|
||||
showFavorite: true,
|
||||
showNumber: _filter == Filter.all &&
|
||||
!_hideListened
|
||||
? true
|
||||
: false,
|
||||
layout: _layout,
|
||||
reverse: _reverse,
|
||||
episodeCount: _episodeCount,
|
||||
initNum: _scroll ? 0 : 12,
|
||||
)
|
||||
: SliverToBoxAdapter(
|
||||
child: Center(),
|
||||
);
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: <Widget>[
|
||||
SliverAppBar(
|
||||
brightness: Brightness.dark,
|
||||
actions: <Widget>[_rightTopMenu(context)],
|
||||
elevation: 0,
|
||||
iconTheme: IconThemeData(
|
||||
color: Colors.white,
|
||||
),
|
||||
expandedHeight: 150 + context.paddingTop,
|
||||
backgroundColor: _color,
|
||||
floating: true,
|
||||
pinned: true,
|
||||
flexibleSpace: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
_topHeight = constraints.biggest.height;
|
||||
return FlexibleSpaceBar(
|
||||
background: Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
top: 120 + context.paddingTop),
|
||||
padding: EdgeInsets.only(
|
||||
left: 80, right: 120),
|
||||
color: Colors.white10,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
widget.podcastLocal.author ??
|
||||
'',
|
||||
maxLines: 1,
|
||||
overflow:
|
||||
TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Colors.white)),
|
||||
if (widget.podcastLocal.provider
|
||||
.isNotEmpty)
|
||||
Text(
|
||||
s.hostedOn(widget
|
||||
.podcastLocal.provider),
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.centerRight,
|
||||
padding: EdgeInsets.only(right: 10),
|
||||
child: SizedBox(
|
||||
height: 120,
|
||||
child: Image.file(File(
|
||||
"${widget.podcastLocal.imagePath}")),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: _podcastInfo(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
title: _topHeight < 70 + context.paddingTop
|
||||
? Text(widget.podcastLocal.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.clip,
|
||||
style:
|
||||
TextStyle(color: Colors.white))
|
||||
: Center(),
|
||||
);
|
||||
}),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return _loadMore
|
||||
? Container(
|
||||
height: 2,
|
||||
child: LinearProgressIndicator())
|
||||
: Center();
|
||||
},
|
||||
childCount: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
SliverToBoxAdapter(
|
||||
child: _hostsList(context, widget.podcastLocal),
|
||||
),
|
||||
SliverToBoxAdapter(child: _actionBar(context)),
|
||||
FutureBuilder<List<EpisodeBrief>>(
|
||||
future: _getRssItem(widget.podcastLocal,
|
||||
count: _top,
|
||||
reverse: _reverse,
|
||||
filter: _filter,
|
||||
query: _query),
|
||||
builder: (context, snapshot) {
|
||||
return (snapshot.hasData)
|
||||
? EpisodeGrid(
|
||||
episodes: snapshot.data,
|
||||
showFavorite: true,
|
||||
showNumber: _filter == Filter.all &&
|
||||
!_hideListened
|
||||
? true
|
||||
: false,
|
||||
layout: _layout,
|
||||
reverse: _reverse,
|
||||
episodeCount: _episodeCount,
|
||||
initNum: _scroll ? 0 : 12,
|
||||
)
|
||||
: SliverToBoxAdapter(
|
||||
child: Center(),
|
||||
);
|
||||
}),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return _loadMore
|
||||
? Container(
|
||||
height: 2,
|
||||
child: LinearProgressIndicator())
|
||||
: Center();
|
||||
},
|
||||
childCount: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Selector<AudioPlayerNotifier, Tuple2<bool, PlayerHeight>>(
|
||||
|
|
|
@ -24,6 +24,18 @@ class _LayoutSettingState extends State<LayoutSetting> {
|
|||
return Layout.values[layout];
|
||||
}
|
||||
|
||||
Future<bool> _hideListened() async {
|
||||
var hideListenedStorage = KeyValueStorage(hideListenedKey);
|
||||
var hideListened = await hideListenedStorage.getBool(defaultValue: false);
|
||||
return hideListened;
|
||||
}
|
||||
|
||||
Future<void> _saveHideListened(bool boo) async {
|
||||
var hideListenedStorage = KeyValueStorage(hideListenedKey);
|
||||
await hideListenedStorage.saveBool(boo);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
String _getHeightString(PlayerHeight mode) {
|
||||
final s = context.s;
|
||||
switch (mode) {
|
||||
|
@ -245,6 +257,22 @@ class _LayoutSettingState extends State<LayoutSetting> {
|
|||
shrinkWrap: true,
|
||||
scrollDirection: Axis.vertical,
|
||||
children: <Widget>[
|
||||
FutureBuilder<bool>(
|
||||
future: _hideListened(),
|
||||
initialData: false,
|
||||
builder: (context, snapshot) => ListTile(
|
||||
contentPadding: EdgeInsets.only(left: 70, right: 10),
|
||||
onTap: () => _saveHideListened(!snapshot.data),
|
||||
title: Text('Hide listened'),
|
||||
subtitle: Text('Hide listened episodes by default'),
|
||||
trailing: Transform.scale(
|
||||
scale: 0.9,
|
||||
child: Switch(
|
||||
value: snapshot.data,
|
||||
onChanged: _saveHideListened),
|
||||
),
|
||||
),
|
||||
),
|
||||
_setDefaultGridView(context,
|
||||
text: s.settingsDefaultGridPodcast,
|
||||
key: podcastLayoutKey),
|
||||
|
|
|
@ -454,33 +454,37 @@ class SettingState extends ChangeNotifier {
|
|||
var localeList = await localeStorage.getStringList();
|
||||
var backupLocale =
|
||||
localeList.isEmpty ? '' : '${'${localeList.first}-'}${localeList[1]}';
|
||||
var hideListened =
|
||||
await KeyValueStorage(hideListenedKey).getBool(defaultValue: false);
|
||||
|
||||
return SettingsBackup(
|
||||
theme: theme,
|
||||
accentColor: accentColor,
|
||||
realDark: realDark,
|
||||
autoPlay: autoPlay,
|
||||
autoUpdate: autoUpdate,
|
||||
updateInterval: updateInterval,
|
||||
downloadUsingData: downloadUsingData,
|
||||
cacheMax: cacheMax,
|
||||
podcastLayout: podcastLayout,
|
||||
recentLayout: recentLayout,
|
||||
favLayout: favLayout,
|
||||
downloadLayout: downloadLayout,
|
||||
autoDownloadNetwork: autoDownloadNetwork,
|
||||
episodePopupMenu: episodePopupMenu.map((e) => e.toString()).toList(),
|
||||
autoDelete: autoDelete,
|
||||
autoSleepTimer: autoSleepTimer,
|
||||
autoSleepTimerStart: autoSleepTimerStart,
|
||||
autoSleepTimerEnd: autoSleepTimerEnd,
|
||||
autoSleepTimerMode: autoSleepTimerMode,
|
||||
defaultSleepTime: defaultSleepTime,
|
||||
tapToOpenPopupMenu: tapToOpenPopupMenu,
|
||||
fastForwardSeconds: fastForwardSeconds,
|
||||
rewindSeconds: rewindSeconds,
|
||||
playerHeight: playerHeight,
|
||||
locale: backupLocale);
|
||||
theme: theme,
|
||||
accentColor: accentColor,
|
||||
realDark: realDark,
|
||||
autoPlay: autoPlay,
|
||||
autoUpdate: autoUpdate,
|
||||
updateInterval: updateInterval,
|
||||
downloadUsingData: downloadUsingData,
|
||||
cacheMax: cacheMax,
|
||||
podcastLayout: podcastLayout,
|
||||
recentLayout: recentLayout,
|
||||
favLayout: favLayout,
|
||||
downloadLayout: downloadLayout,
|
||||
autoDownloadNetwork: autoDownloadNetwork,
|
||||
episodePopupMenu: episodePopupMenu.map((e) => e.toString()).toList(),
|
||||
autoDelete: autoDelete,
|
||||
autoSleepTimer: autoSleepTimer,
|
||||
autoSleepTimerStart: autoSleepTimerStart,
|
||||
autoSleepTimerEnd: autoSleepTimerEnd,
|
||||
autoSleepTimerMode: autoSleepTimerMode,
|
||||
defaultSleepTime: defaultSleepTime,
|
||||
tapToOpenPopupMenu: tapToOpenPopupMenu,
|
||||
fastForwardSeconds: fastForwardSeconds,
|
||||
rewindSeconds: rewindSeconds,
|
||||
playerHeight: playerHeight,
|
||||
locale: backupLocale,
|
||||
hideListened: hideListened,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> restore(SettingsBackup backup) async {
|
||||
|
@ -511,6 +515,7 @@ class SettingState extends ChangeNotifier {
|
|||
await KeyValueStorage(playerHeightKey).saveInt(backup.playerHeight);
|
||||
await KeyValueStorage(tapToOpenPopupMenuKey)
|
||||
.saveBool(backup.tapToOpenPopupMenu);
|
||||
await KeyValueStorage(hideListenedKey).saveBool(backup.hideListened);
|
||||
if (backup.locale == '') {
|
||||
await localeStorage.saveStringList([]);
|
||||
await S.load(Locale(Intl.systemLocale));
|
||||
|
|
|
@ -24,6 +24,7 @@ class SettingsBackup {
|
|||
final int rewindSeconds;
|
||||
final int playerHeight;
|
||||
final String locale;
|
||||
final bool hideListened;
|
||||
SettingsBackup(
|
||||
{this.theme,
|
||||
this.accentColor,
|
||||
|
@ -49,7 +50,8 @@ class SettingsBackup {
|
|||
this.fastForwardSeconds,
|
||||
this.rewindSeconds,
|
||||
this.playerHeight,
|
||||
this.locale});
|
||||
this.locale,
|
||||
this.hideListened});
|
||||
|
||||
Map<String, Object> toJson() {
|
||||
return {
|
||||
|
@ -76,7 +78,8 @@ class SettingsBackup {
|
|||
'fastForwardSeconds': fastForwardSeconds,
|
||||
'rewindSeconds': rewindSeconds,
|
||||
'playerHeight': playerHeight,
|
||||
'locale': locale
|
||||
'locale': locale,
|
||||
'hideListened': hideListened
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -106,6 +109,7 @@ class SettingsBackup {
|
|||
fastForwardSeconds: json['fastForwardSeconds'] as int,
|
||||
rewindSeconds: json['rewindSeconds'] as int,
|
||||
playerHeight: json['playerHeight'] as int,
|
||||
locale: json['locale'] as String);
|
||||
locale: json['locale'] as String,
|
||||
hideListened: json['hideListened'] as bool);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue