Hide listened.

This commit is contained in:
stonegate 2020-08-24 00:35:16 +08:00
parent 5a32511ac9
commit 208edeb67e
8 changed files with 864 additions and 682 deletions

View File

@ -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(),
);
});
}
}

View File

@ -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,
);
},
),
],
],
);
}),
);
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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>>(

View File

@ -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),

View File

@ -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));

View File

@ -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);
}
}