Remove listened indicator and increase the color difference

Improve download manage page, support fliters
This commit is contained in:
stonegate 2020-06-11 00:36:53 +08:00
parent 935566b304
commit f4b56938ae
15 changed files with 581 additions and 298 deletions

View File

@ -269,14 +269,15 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
padding: EdgeInsets.only(
left: 20.0, right: 20, bottom: 10),
defaultTextStyle:
GoogleFonts.libreBaskerville(
// GoogleFonts.libreBaskerville(
GoogleFonts.martel(
textStyle: TextStyle(
height: 1.8,
),
),
data: _description,
linkStyle: TextStyle(
color: Theme.of(context).accentColor,
color: context.accentColor,
// decoration: TextDecoration.underline,
textBaseline: TextBaseline.ideographic),
onLinkTap: (url) {
@ -296,7 +297,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
_launchUrl(link.url);
},
text: _description,
style: GoogleFonts.libreBaskerville(
style: GoogleFonts.martel(
textStyle: TextStyle(
height: 1.8,
),
@ -384,8 +385,6 @@ class MenuBar extends StatefulWidget {
}
class _MenuBarState extends State<MenuBar> {
bool _liked = false;
Future<PlayHistory> getPosition(EpisodeBrief episode) async {
var dbHelper = DBHelper();
return await dbHelper.getPosition(episode);
@ -394,17 +393,14 @@ class _MenuBarState extends State<MenuBar> {
Future<int> saveLiked(String url) async {
var dbHelper = DBHelper();
int result = await dbHelper.setLiked(url);
if (result == 1 && mounted) setState(() => _liked = true);
setState(() {});
return result;
}
Future<int> setUnliked(String url) async {
var dbHelper = DBHelper();
int result = await dbHelper.setUniked(url);
if (result == 1 && mounted)
setState(() {
_liked = false;
});
setState(() {});
return result;
}
@ -418,12 +414,6 @@ class _MenuBarState extends State<MenuBar> {
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
}
@override
void initState() {
super.initState();
_liked = false;
}
Widget _buttonOnMenu(Widget widget, VoidCallback onTap) => Material(
color: Colors.transparent,
child: InkWell(
@ -495,7 +485,7 @@ class _MenuBarState extends State<MenuBar> {
future: _isLiked(widget.episodeItem),
initialData: false,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return (!snapshot.data && !_liked)
return (!snapshot.data)
? _buttonOnMenu(
Icon(
Icons.favorite_border,
@ -508,23 +498,13 @@ class _MenuBarState extends State<MenuBar> {
await Future.delayed(Duration(seconds: 2));
_overlayEntry?.remove();
})
: (snapshot.data && !_liked)
? _buttonOnMenu(
Icon(
Icons.favorite,
color: Colors.red,
),
() => setUnliked(
widget.episodeItem.enclosureUrl))
: _buttonOnMenu(
Icon(
Icons.favorite,
color: Colors.red,
),
() {
setUnliked(widget.episodeItem.enclosureUrl);
},
);
: _buttonOnMenu(
Icon(
Icons.favorite,
color: Colors.red,
),
() =>
setUnliked(widget.episodeItem.enclosureUrl));
},
),
DownloadButton(episode: widget.episodeItem),
@ -534,7 +514,7 @@ class _MenuBarState extends State<MenuBar> {
return data.contains(widget.episodeItem)
? _buttonOnMenu(
Icon(Icons.playlist_add_check,
color: Theme.of(context).accentColor), () {
color: context.accentColor), () {
audio.delFromPlaylist(widget.episodeItem);
Fluttertoast.showToast(
msg: 'Removed from playlist',

View File

@ -376,14 +376,10 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
}
}
class PodcastPreview extends StatefulWidget {
class PodcastPreview extends StatelessWidget {
final PodcastLocal podcastLocal;
PodcastPreview({this.podcastLocal, Key key}) : super(key: key);
@override
_PodcastPreviewState createState() => _PodcastPreviewState();
}
class _PodcastPreviewState extends State<PodcastPreview> {
Future<List<EpisodeBrief>> _getRssItemTop(PodcastLocal podcastLocal) async {
var dbHelper = DBHelper();
List<EpisodeBrief> episodes = await dbHelper.getRssItemTop(podcastLocal.id);
@ -393,8 +389,8 @@ class _PodcastPreviewState extends State<PodcastPreview> {
@override
Widget build(BuildContext context) {
Color _c = (Theme.of(context).brightness == Brightness.light)
? widget.podcastLocal.primaryColor.colorizedark()
: widget.podcastLocal.primaryColor.colorizeLight();
? podcastLocal.primaryColor.colorizedark()
: podcastLocal.primaryColor.colorizeLight();
return Column(
children: <Widget>[
Expanded(
@ -402,7 +398,7 @@ class _PodcastPreviewState extends State<PodcastPreview> {
selector: (_, worker) => worker.created,
builder: (context, created, child) {
return FutureBuilder<List<EpisodeBrief>>(
future: _getRssItemTop(widget.podcastLocal),
future: _getRssItemTop(podcastLocal),
builder: (context, snapshot) {
if (snapshot.hasError) {
print(snapshot.error);
@ -411,7 +407,7 @@ class _PodcastPreviewState extends State<PodcastPreview> {
return (snapshot.hasData)
? ShowEpisode(
episodes: snapshot.data,
podcastLocal: widget.podcastLocal,
podcastLocal: podcastLocal,
)
: Container(
padding: EdgeInsets.all(5.0),
@ -429,7 +425,7 @@ class _PodcastPreviewState extends State<PodcastPreview> {
children: <Widget>[
Expanded(
flex: 4,
child: Text(widget.podcastLocal.title,
child: Text(podcastLocal.title,
maxLines: 1,
overflow: TextOverflow.visible,
style: TextStyle(fontWeight: FontWeight.bold, color: _c)),
@ -450,11 +446,11 @@ class _PodcastPreviewState extends State<PodcastPreview> {
context,
SlideLeftHideRoute(
transitionPage: PodcastDetail(
podcastLocal: widget.podcastLocal,
podcastLocal: podcastLocal,
hide: playerRunning,
),
page: PodcastDetail(
podcastLocal: widget.podcastLocal,
podcastLocal: podcastLocal,
)),
);
},
@ -606,7 +602,7 @@ class ShowEpisode extends StatelessWidget {
Padding(
padding: EdgeInsets.symmetric(horizontal: 2),
),
isListened > 0.95
isListened > 0
? Text('Listened',
style: TextStyle(
color: context.textColor.withOpacity(0.5)))
@ -671,7 +667,7 @@ class ShowEpisode extends StatelessWidget {
}
break;
case 3:
if (isListened < 0.95) {
if (isListened < 1) {
await _markListened(episode);
audio.setEpisodeState = true;
Fluttertoast.showToast(

View File

@ -24,6 +24,7 @@ const String favLayoutKey = 'favLayoutKey';
const String downloadLayoutKey = 'downloadLayoutKey';
const String autoDownloadNetworkKey = 'autoDownloadNetwork';
const String episodePopupMenuKey = 'episodePopupMenuKey';
const String autoDeleteKey = 'autoDeleteKey';
class KeyValueStorage {
final String key;

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:sqflite/sqflite.dart';
import 'dart:async';
import 'package:path/path.dart';
@ -272,9 +274,7 @@ class DBHelper {
var dbClient = await database;
int i = 0;
List<Map> list =
await dbClient.rawQuery("""SELECT listen_time FROM PlayHistory
WHERE enclosure_url = ?
""", [url]);
await dbClient.rawQuery("SELECT listen_time FROM PlayHistory WHERE enclosure_url = ?", [url]);
if (list.length == 0)
return 0;
else {
@ -708,35 +708,35 @@ class DBHelper {
return episodes;
}
Future<EpisodeBrief> getRssItemDownload(String url) async {
var dbClient = await database;
EpisodeBrief episode;
List<Map> 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, E.liked,
E.downloaded, P.primaryColor, E.media_id, E.is_new, P.skip_seconds
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
where E.enclosure_url = ? ORDER BY E.milliseconds DESC LIMIT 3""",
[url]);
//Future<EpisodeBrief> getRssItemDownload(String url) async {
// var dbClient = await database;
// EpisodeBrief episode;
// List<Map> 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, E.liked,
// E.downloaded, P.primaryColor, E.media_id, E.is_new, P.skip_seconds
// FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
// where E.enclosure_url = ? ORDER BY E.milliseconds DESC LIMIT 3""",
// [url]);
if (list != null)
episode = EpisodeBrief(
list.first['title'],
list.first['enclosure_url'],
list.first['enclosure_length'],
list.first['milliseconds'],
list.first['feed_title'],
list.first['primaryColor'],
list.first['liked'],
list.first['downloaded'],
list.first['duration'],
list.first['explicit'],
list.first['imagePath'],
list.first['media_id'],
list.first['is_new'],
list.first['skip_seconds']);
return episode;
}
// if (list != null)
// episode = EpisodeBrief(
// list.first['title'],
// list.first['enclosure_url'],
// list.first['enclosure_length'],
// list.first['milliseconds'],
// list.first['feed_title'],
// list.first['primaryColor'],
// list.first['liked'],
// list.first['downloaded'],
// list.first['duration'],
// list.first['explicit'],
// list.first['imagePath'],
// list.first['media_id'],
// list.first['is_new'],
// list.first['skip_seconds']);
// return episode;
//}
Future<List<EpisodeBrief>> getRecentRssItem(int top) async {
var dbClient = await database;
@ -835,8 +835,8 @@ class DBHelper {
// var dbClient = await database;
// List<EpisodeBrief> episodes = [];
// List<Map> list = await dbClient.rawQuery(
// """SELECT E.title, E.enclosure_url, E.enclosure_length,
// E.milliseconds, P.title as feed_title, E.duration, E.explicit, E.liked,
// """SELECT E.title, E.enclosure_url, E.enclosure_length,
// E.milliseconds, P.title as feed_title, E.duration, E.explicit, E.liked,
// E.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds
// FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
// WHERE is_new = 1 AND downloaded != 'ND' AND P.id = ?ORDER BY E.milliseconds DESC """,
@ -861,6 +861,105 @@ class DBHelper {
// }
// return episodes;
//}
Future<List<EpisodeBrief>> getOutdatedEpisode(int days) async {
var dbClient = await database;
List<EpisodeBrief> episodes = [];
if (days > 0) {
int deadline =
DateTime.now().subtract(Duration(days: days)).millisecondsSinceEpoch;
List<Map> list = await dbClient
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.milliseconds, P.title as feed_title, E.duration, E.explicit, E.liked,
E.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE E.download_date < ? AND E.enclosure_url != E.media_id
ORDER BY E.milliseconds DESC""", [deadline]);
for (int x = 0; x < list.length; x++) {
episodes.add(EpisodeBrief(
list[x]['title'],
list[x]['enclosure_url'],
list[x]['enclosure_length'],
list[x]['milliseconds'],
list[x]['feed_title'],
list[x]['primaryColor'],
list[x]['liked'],
list[x]['downloaded'],
list[x]['duration'],
list[x]['explicit'],
list[x]['imagePath'],
list[x]['media_id'],
list[x]['is_new'],
list[x]['skip_seconds']));
}
}
return episodes;
}
Future<List<EpisodeBrief>> getDownloadedEpisode(int mode) async {
var dbClient = await database;
List<EpisodeBrief> episodes = [];
List<Map> list;
if (mode == 0)
list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.download_date,
E.milliseconds, P.title as feed_title, E.duration, E.explicit, E.liked,
E.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE E.enclosure_url != E.media_id
ORDER BY E.download_date DESC""",
);
else if (mode == 1)
list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.download_date,
E.milliseconds, P.title as feed_title, E.duration, E.explicit, E.liked,
E.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE E.enclosure_url != E.media_id
ORDER BY E.download_date ASC""",
);
else if (mode == 2)
list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.download_date,
E.milliseconds, P.title as feed_title, E.duration, E.explicit, E.liked,
E.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE E.enclosure_url != E.media_id
ORDER BY E.enclosure_length DESC""",
);
for (int x = 0; x < list.length; x++) {
int size;
if (list[x]['enclosure_length'] == null ||
list[x]['enclosure_length'] == 0) {
String uri = list[x]['media_id'];
FileStat fileStat = await File(uri.substring(6)).stat();
size = fileStat.size;
await dbClient.rawUpdate(
"UPDATE Episodes SET enclosure_length = ?, WHERE media_id = ?",
[size, uri]);
} else
size = list[x]['enclosure_length'];
episodes.add(
EpisodeBrief(
list[x]['title'],
list[x]['enclosure_url'],
size,
list[x]['milliseconds'],
list[x]['feed_title'],
list[x]['primaryColor'],
list[x]['liked'],
list[x]['downloaded'],
list[x]['duration'],
list[x]['explicit'],
list[x]['imagePath'],
list[x]['media_id'],
list[x]['is_new'],
list[x]['skip_seconds'],
downloadDate: list[x]['download_date']),
);
}
return episodes;
}
Future<int> removeAllNewMark() async {
var dbClient = await database;
@ -995,9 +1094,9 @@ class DBHelper {
Future<bool> isDownloaded(String url) async {
var dbClient = await database;
List<Map> list = await dbClient
.rawQuery("SELECT downloaded FROM Episodes WHERE enclosure_url = ?", [url]);
return list.first['downloaded'] == 'ND' ? false: true;
List<Map> list = await dbClient.rawQuery(
"SELECT downloaded FROM Episodes WHERE enclosure_url = ?", [url]);
return list.first['downloaded'] == 'ND' ? false : true;
}
Future<int> saveDownloaded(String url, String id) async {

View File

@ -5,9 +5,12 @@ import 'package:flutter/services.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart';
import 'package:line_icons/line_icons.dart';
import 'package:provider/provider.dart';
import 'package:google_fonts/google_fonts.dart';
import '../type/episodebrief.dart';
import '../util/context_extension.dart';
import '../state/download_state.dart';
import '../local_storage/sqflite_localpodcast.dart';
class DownloadsManage extends StatefulWidget {
@ -18,25 +21,23 @@ class DownloadsManage extends StatefulWidget {
class _DownloadsManageState extends State<DownloadsManage> {
//Downloaded size
int _size;
int _mode;
//Downloaded files
int _fileNum;
bool _loadEpisodes;
bool _clearing;
bool _onlyListened;
List<EpisodeBrief> _selectedList;
List<EpisodeBrief> _episodes = [];
_getDownloadedRssItem() async {
_episodes = [];
final tasks = await FlutterDownloader.loadTasksWithRawQuery(
query: "SELECT * FROM task WHERE status = 3");
Future<List<EpisodeBrief>> _getDownloadedEpisode(int mode) async {
List<EpisodeBrief> episodes = [];
var dbHelper = DBHelper();
await Future.forEach(tasks, (task) async {
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(task.url);
_episodes.add(episode);
});
setState(() {
_loadEpisodes = true;
});
episodes = await dbHelper.getDownloadedEpisode(mode);
return episodes;
}
Future<int> _isListened(EpisodeBrief episode) async {
DBHelper dbHelper = DBHelper();
return await dbHelper.isListened(episode.enclosureUrl);
}
_getStorageSize() async {
@ -58,12 +59,13 @@ class _DownloadsManageState extends State<DownloadsManage> {
_delSelectedEpisodes() async {
setState(() => _clearing = true);
await Future.forEach(_selectedList, (EpisodeBrief episode) async {
await FlutterDownloader.remove(
taskId: episode.downloaded, shouldDeleteContent: true);
var dbHelper = DBHelper();
await dbHelper.delDownloaded(episode.enclosureUrl);
setState(() =>
_episodes.removeWhere((e) => e.enclosureUrl == episode.enclosureUrl));
var downloader = Provider.of<DownloadState>(context, listen: false);
await downloader.removeTask(episode);
// await FlutterDownloader.remove(
// taskId: episode.downloaded, shouldDeleteContent: true);
// var dbHelper = DBHelper();
// await dbHelper.delDownloaded(episode.enclosureUrl);
setState(() {});
});
await Future.delayed(Duration(seconds: 1));
setState(() {
@ -90,10 +92,10 @@ class _DownloadsManageState extends State<DownloadsManage> {
void initState() {
super.initState();
_clearing = false;
_loadEpisodes = false;
_selectedList = [];
_mode = 0;
_onlyListened = false;
_getStorageSize();
_getDownloadedRssItem();
}
@override
@ -107,9 +109,8 @@ class _DownloadsManageState extends State<DownloadsManage> {
),
child: Scaffold(
appBar: AppBar(
title: Text('Downloads'),
elevation: 0,
backgroundColor: Theme.of(context).primaryColor,
backgroundColor: context.primaryColor,
),
body: SafeArea(
child: Stack(
@ -119,125 +120,248 @@ class _DownloadsManageState extends State<DownloadsManage> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 100.0,
padding: EdgeInsets.only(bottom: 20, left: 60),
alignment: Alignment.centerLeft,
child: RichText(
text: TextSpan(
text: 'Total ',
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 20,
height: 140.0,
color: context.primaryColor,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 20),
child: RichText(
text: TextSpan(
text: 'Total ',
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 20,
),
children: <TextSpan>[
TextSpan(
text: _fileNum.toString(),
style: GoogleFonts.cairo(
textStyle: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 40,
)),
),
TextSpan(
text: _fileNum < 2
? ' episode'
: ' episodes ',
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 20,
)),
TextSpan(
text: (_size ~/ 1000000).toString(),
style: GoogleFonts.cairo(
textStyle: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 50,
)),
),
TextSpan(
text: ' Mb',
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 20,
)),
],
),
),
),
children: <TextSpan>[
TextSpan(
text: _fileNum.toString(),
style: GoogleFonts.cairo(
textStyle: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 40,
)),
),
TextSpan(
text: _fileNum < 2 ? ' episode' : ' episodes ',
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 20,
)),
TextSpan(
text: (_size ~/ 1000000).toString(),
style: GoogleFonts.cairo(
textStyle: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 50,
)),
),
TextSpan(
text: ' Mb',
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 20,
)),
],
),
),
),
_loadEpisodes
? Expanded(
child: ListView.builder(
itemCount: _episodes.length,
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
return Column(
children: <Widget>[
ListTile(
onTap: () {
if (_selectedList
.contains(_episodes[index])) {
setState(() => _selectedList
.removeWhere((episode) =>
episode.enclosureUrl ==
_episodes[index]
.enclosureUrl));
} else {
setState(() => _selectedList
.add(_episodes[index]));
}
},
leading: CircleAvatar(
backgroundImage: FileImage(File(
"${_episodes[index].imagePath}")),
),
title: Text(
_episodes[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: _episodes[index]
.enclosureLength !=
0
? Text(((_episodes[index]
.enclosureLength) ~/
1000000)
.toString() +
' Mb')
: Center(),
trailing: Checkbox(
value: _selectedList
.contains(_episodes[index]),
onChanged: (bool boo) {
if (boo) {
setState(() => _selectedList
.add(_episodes[index]));
} else {
setState(() => _selectedList
.removeWhere((episode) =>
episode.enclosureUrl ==
_episodes[index]
.enclosureUrl));
}
},
),
Spacer(),
SizedBox(
height: 40,
child: Row(
children: [
Material(
color: Colors.transparent,
child: PopupMenuButton<int>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10))),
elevation: 1,
tooltip: 'Sort By',
child: Container(
height: 40,
padding:
EdgeInsets.symmetric(horizontal: 20),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Sory by'),
Padding(
padding: EdgeInsets.symmetric(
horizontal: 5),
),
Icon(
_mode == 0
? LineIcons
.hourglass_start_solid
: _mode == 1
? LineIcons
.hourglass_half_solid
: LineIcons.save,
size: 18,
)
],
)),
itemBuilder: (context) => [
PopupMenuItem(
value: 0,
child: Text('Newest first'),
),
Divider(
height: 2,
PopupMenuItem(
value: 1,
child: Text('Oldest first'),
),
PopupMenuItem(
value: 2,
child: Text('Size'),
),
],
);
}),
)
: CircularProgressIndicator(),
onSelected: (value) {
if (value == 0)
setState(() => _mode = 0);
else if (value == 1)
setState(() => _mode = 1);
else if (value == 2)
setState(() => _mode = 2);
},
),
),
//Spacer(),
Material(
color: Colors.transparent,
child: InkWell(
onTap: () => setState(() {
_onlyListened = !_onlyListened;
}),
child: Row(
children: [
Padding(
padding:
EdgeInsets.symmetric(horizontal: 5),
),
Text('Listened Only'),
Checkbox(
value: _onlyListened,
onChanged: (value) {
setState(() {
_onlyListened = value;
});
}),
],
),
),
),
],
),
),
],
),
),
Expanded(
child: FutureBuilder<List<EpisodeBrief>>(
future: _getDownloadedEpisode(_mode),
initialData: [],
builder: (context, snapshot) {
var _episodes = snapshot.data;
return ListView.builder(
itemCount: _episodes.length,
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
return FutureBuilder(
future: _isListened(_episodes[index]),
initialData: 0,
builder: (context, snapshot) {
return (_onlyListened &&
snapshot.data > 0)
? Center()
: Column(
children: <Widget>[
ListTile(
onTap: () {
if (_selectedList.contains(
_episodes[index])) {
setState(() => _selectedList
.removeWhere((episode) =>
episode
.enclosureUrl ==
_episodes[index]
.enclosureUrl));
} else {
setState(() => _selectedList
.add(_episodes[index]));
}
},
leading: CircleAvatar(
backgroundImage: FileImage(File(
"${_episodes[index].imagePath}")),
),
title: Text(
_episodes[index].title,
maxLines: 1,
overflow:
TextOverflow.ellipsis,
),
subtitle: Row(
children: [
Text(_episodes[index]
.downloadDateToString()),
SizedBox(width: 20),
_episodes[index]
.enclosureLength !=
0
? Text(((_episodes[index]
.enclosureLength) ~/
1000000)
.toString() +
' Mb')
: SizedBox(
width: 1,
),
],
),
trailing: Checkbox(
value: _selectedList.contains(
_episodes[index]),
onChanged: (bool boo) {
if (boo) {
setState(() =>
_selectedList.add(
_episodes[
index]));
} else {
setState(() => _selectedList
.removeWhere((episode) =>
episode
.enclosureUrl ==
_episodes[index]
.enclosureUrl));
}
},
),
),
Divider(
height: 2,
),
],
);
});
});
},
),
)
],
),
AnimatedPositioned(
duration: Duration(milliseconds: 800),
curve: Curves.elasticInOut,
left: MediaQuery.of(context).size.width / 2 - 50,
left: context.width / 2 - 50,
bottom: _selectedList.length == 0 ? -100 : 30,
child: InkWell(
onTap: () => _delSelectedEpisodes(),

View File

@ -300,14 +300,13 @@ class _PlayedHistoryState extends State<PlayedHistory>
builder: (context, snapshot) {
return snapshot.hasData
? ListView.builder(
shrinkWrap: true,
// shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
bool _status = snapshot.data[index].status;
return Container(
color: Theme.of(context).scaffoldBackgroundColor,
padding: const EdgeInsets.symmetric(vertical: 5),
color: context.scaffoldBackgroundColor,
child: Column(
children: <Widget>[
ListTile(

View File

@ -180,7 +180,6 @@ class _LayoutSettingState extends State<LayoutSetting> {
text: 'Favorite tab', key: favLayoutKey),
_setDefaultGridView(context,
text: 'Downlaod tab', key: downloadLayoutKey),
Divider(height: 2),
]),
Divider(height: 2)
]),

View File

@ -21,7 +21,8 @@ List<Libries> google = [
List<Libries> fonts = [
Libries('Libre Baskerville', font,
"https://fonts.google.com/specimen/Libre+Baskerville"),
Libries('Teko', font, "https://fonts.google.com/specimen/Teko")
Libries('Teko', font, "https://fonts.google.com/specimen/Teko"),
Libries('Martel', font, "https://fonts.google.com/specimen/Martel")
];
List<Libries> plugins = [
@ -68,5 +69,6 @@ List<Libries> plugins = [
Libries('Rxdart', apacheLicense, 'https://pub.dev/packages/rxdart'),
Libries('flutter_isolate', mit, 'https://pub.dev/packages/flutter_isolate'),
Libries('auto_animated', mit, 'https://pub.dev/packages/auto_animated'),
Libries('wc_flutter_share', apacheLicense, 'https://pub.dev/packages/wc_flutter_share')
Libries('wc_flutter_share', apacheLicense,
'https://pub.dev/packages/wc_flutter_share')
];

View File

@ -39,6 +39,21 @@ class _PopupMenuSettingState extends State<PopupMenuSetting> {
leading: icon,
title: Text(text),
subtitle: Text(description),
onTap: e == 0
? null
: () {
if (e >= 10) {
int index = menu.indexOf(e);
menu.remove(e);
menu.insert(index, e - 10);
_saveEpisodeMene(menu);
} else if (e < 10) {
int index = menu.indexOf(e);
menu.remove(e);
menu.insert(index, e + 10);
_saveEpisodeMene(menu);
}
},
trailing: Checkbox(
value: e < 10,
onChanged: e == 0
@ -131,11 +146,8 @@ class _PopupMenuSettingState extends State<PopupMenuSetting> {
break;
case 2:
return _popupMenuItem(menu, e,
icon: Icon(
LineIcons.heart,
color: Colors.red,
size: 21
),
icon: Icon(LineIcons.heart,
color: Colors.red, size: 21),
text: 'Like',
description: 'Add episode to favorite');
break;

View File

@ -179,12 +179,6 @@ class _SettingsState extends State<Settings>
leading: Icon(LineIcons.play_circle),
title: Text('Play'),
subtitle: Text('Playlist and player'),
// trailing: Selector<AudioPlayerNotifier, bool>(
// selector: (_, audio) => audio.autoPlay,
// builder: (_, data, __) => Switch(
// value: data,
// onChanged: (boo) => audio.autoPlaySwitch = boo),
// ),
),
Divider(height: 2),
ListTile(

View File

@ -30,11 +30,6 @@ class _StorageSettingState extends State<StorageSetting>
setState(() => _value = _animation.value);
});
_controller.forward();
// _controller.addStatusListener((status) {
// if (status == AnimationStatus.completed) {
// _controller.reset();
// }
// });
}
}
@ -44,6 +39,22 @@ class _StorageSettingState extends State<StorageSetting>
return value != 0;
}
Future<int> _getAutoDeleteDays() async {
KeyValueStorage storage = KeyValueStorage(autoDeleteKey);
int days = await storage.getInt();
if (days == 0) {
storage.saveInt(30);
return 30;
}
return days;
}
_setAutoDeleteDays(int days) async {
KeyValueStorage storage = KeyValueStorage(autoDeleteKey);
await storage.saveInt(days);
setState(() {});
}
_setAudtDownloadNetwork(bool boo) async {
KeyValueStorage storage = KeyValueStorage(autoDownloadNetworkKey);
await storage.saveInt(boo ? 1 : 0);
@ -186,10 +197,41 @@ class _StorageSettingState extends State<StorageSetting>
subtitle: Text('Manage downloaded audio files'),
),
Divider(height: 2),
FutureBuilder<int>(
future: _getAutoDeleteDays(),
initialData: 30,
builder: (context, snapshot) {
return ListTile(
contentPadding:
EdgeInsets.only(left: 80.0, right: 20),
title: Text('Auto delete downloads after'),
subtitle: Text('Default 30 days.'),
trailing: DropdownButton(
hint: snapshot.data == -1
? Text('Never')
: Text(snapshot.data.toString() + 'days'),
underline: Center(),
elevation: 1,
value: snapshot.data,
onChanged: (value) async {
await _setAutoDeleteDays(value);
},
items: <int>[-1, 10, 30]
.map<DropdownMenuItem<int>>((e) {
return DropdownMenuItem<int>(
value: e,
child: e == -1
? Text('Never')
: Text(e.toString() + ' days'));
}).toList()),
);
},
),
Divider(height: 2),
ListTile(
contentPadding: EdgeInsets.only(left: 80.0, right: 25),
// leading: Icon(Icons.colorize),
title: Text('Cache'),
title: Text('Audio cache'),
subtitle: Text('Audio cache max size'),
trailing: Text.rich(TextSpan(
text: '${(_value ~/ 100) * 100}',

View File

@ -4,10 +4,10 @@ import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:intl/intl.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
import 'package:tsacdop/local_storage/key_value_storage.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import '../type/episodebrief.dart';
@ -37,6 +37,10 @@ class DownloadState extends ChangeNotifier {
List<EpisodeTask> _episodeTasks = [];
List<EpisodeTask> get episodeTasks => _episodeTasks;
DownloadState() {
_autoDelete();
}
@override
void addListener(VoidCallback listener) async {
_loadTasks();
@ -55,7 +59,6 @@ class DownloadState extends ChangeNotifier {
_episodeTasks.add(EpisodeTask(episode, task.taskId,
progress: task.progress, status: task.status));
});
print(_episodeTasks.length);
notifyListeners();
}
@ -205,4 +208,19 @@ class DownloadState extends ChangeNotifier {
_episodeTasks.removeWhere(
(element) => element.episode.enclosureUrl == episode.enclosureUrl);
}
_autoDelete() async {
print('Start auto delete outdated episodes');
KeyValueStorage autoDeleteStorage = KeyValueStorage(autoDeleteKey);
int autoDelete = await autoDeleteStorage.getInt();
if (autoDelete == 0)
await autoDeleteStorage.saveInt(30);
else if (autoDelete > 0) {
List<EpisodeBrief> episodes =
await dbHelper.getOutdatedEpisode(autoDelete);
if (episodes.length > 0) {
await Future.forEach(episodes, (episode) => delTask(episode));
}
}
}
}

View File

@ -3,7 +3,7 @@ import 'package:audio_service/audio_service.dart';
class EpisodeBrief {
final String title;
String description;
final String description;
final int pubDate;
final int enclosureLength;
final String enclosureUrl;
@ -17,6 +17,7 @@ class EpisodeBrief {
final String mediaId;
final int isNew;
final int skipSeconds;
final int downloadDate;
EpisodeBrief(
this.title,
this.enclosureUrl,
@ -31,7 +32,10 @@ class EpisodeBrief {
this.imagePath,
this.mediaId,
this.isNew,
this.skipSeconds);
this.skipSeconds,
{this.description = '',
this.downloadDate = 0})
: assert(enclosureUrl != null);
String dateToString() {
DateTime date = DateTime.fromMillisecondsSinceEpoch(pubDate, isUtc: true);
@ -50,6 +54,23 @@ class EpisodeBrief {
}
}
String downloadDateToString() {
DateTime date = DateTime.fromMillisecondsSinceEpoch(downloadDate);
var diffrence = DateTime.now().toUtc().difference(date);
if (diffrence.inHours < 1) {
return '1 hour ago';
} else if (diffrence.inHours < 24) {
return '${diffrence.inHours} hours ago';
} else if (diffrence.inHours == 24) {
return '1 day ago';
} else if (diffrence.inDays < 7) {
return '${diffrence.inDays} days ago';
} else {
return DateFormat.yMMMd().format(
DateTime.fromMillisecondsSinceEpoch(pubDate, isUtc: true).toLocal());
}
}
MediaItem toMediaItem() {
return MediaItem(
id: mediaId,

View File

@ -3,7 +3,7 @@ class PodcastLocal {
final String imageUrl;
final String rssUrl;
final String author;
final String primaryColor;
final String id;
final String imagePath;
@ -13,20 +13,16 @@ class PodcastLocal {
final String description;
int upateCount;
int episodeCount;
PodcastLocal(
this.title,
this.imageUrl,
this.rssUrl,
this.primaryColor,
this.author,
this.id,
this.imagePath,
this.provider,
this.link,
{
this.description ='',
this.upateCount = 0,
this.episodeCount = 0
}
);
PodcastLocal(this.title, this.imageUrl, this.rssUrl, this.primaryColor,
this.author, this.id, this.imagePath, this.provider, this.link,
{this.description = '', this.upateCount = 0, this.episodeCount = 0})
: assert(rssUrl != null);
@override
bool operator ==(Object podcastLocal) =>
podcastLocal is PodcastLocal &&
podcastLocal.rssUrl == rssUrl &&
podcastLocal.id == id;
@override
int get hashCode => id.hashCode + rssUrl.hashCode;
}

View File

@ -118,36 +118,37 @@ class EpisodeGrid extends StatelessWidget {
Widget _listenIndicater(BuildContext context,
{EpisodeBrief episode, int isListened}) =>
Selector<AudioPlayerNotifier, Tuple2<EpisodeBrief, bool>>(
selector: (_, audio) => Tuple2(audio.episode, audio.playerRunning),
builder: (_, data, __) {
return (episode.enclosureUrl == data.item1?.enclosureUrl &&
data.item2)
? Container(
height: 20,
width: 20,
margin: EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
shape: BoxShape.circle,
),
child: WaveLoader(color: context.accentColor))
: layout != Layout.three && isListened > 0
? Container(
height: 20,
width: 20,
margin: EdgeInsets.symmetric(horizontal: 2),
padding: EdgeInsets.all(2),
decoration: BoxDecoration(
color: context.accentColor,
shape: BoxShape.circle,
),
child: CustomPaint(
painter: ListenedAllPainter(
Colors.white,
)),
)
: Center();
});
Center();
// Selector<AudioPlayerNotifier, Tuple2<EpisodeBrief, bool>>(
// selector: (_, audio) => Tuple2(audio.episode, audio.playerRunning),
// builder: (_, data, __) {
// return (episode.enclosureUrl == data.item1?.enclosureUrl &&
// data.item2)
// ? Container(
// height: 20,
// width: 20,
// margin: EdgeInsets.symmetric(horizontal: 2),
// decoration: BoxDecoration(
// shape: BoxShape.circle,
// ),
// child: WaveLoader(color: context.accentColor))
// : layout != Layout.three && isListened > 0
// ? Container(
// height: 20,
// width: 20,
// margin: EdgeInsets.symmetric(horizontal: 2),
// padding: EdgeInsets.all(2),
// decoration: BoxDecoration(
// color: context.accentColor,
// shape: BoxShape.circle,
// ),
// child: CustomPaint(
// painter: ListenedAllPainter(
// Colors.white,
// )),
// )
// : Center();
// });
Widget _downloadIndicater(BuildContext context, {EpisodeBrief episode}) =>
showDownload || layout != Layout.three
@ -294,7 +295,7 @@ class EpisodeGrid extends StatelessWidget {
Padding(
padding: EdgeInsets.symmetric(horizontal: 2),
),
isListened > 0.95
isListened > 0
? Text('Listened',
style: TextStyle(
color: context.textColor.withOpacity(0.5)))
@ -359,7 +360,7 @@ class EpisodeGrid extends StatelessWidget {
}
break;
case 3:
if (isListened < 0.95) {
if (isListened < 1) {
await _markListened(episode);
audio.setEpisodeState = true;
Fluttertoast.showToast(
@ -368,7 +369,6 @@ class EpisodeGrid extends StatelessWidget {
);
}
break;
case 4:
if (!isDownload) downloader.startTask(episode);
break;
@ -428,7 +428,7 @@ class EpisodeGrid extends StatelessWidget {
BorderRadius.all(Radius.circular(5.0)),
color: snapshot.data > 0
? context.brightness == Brightness.light
? context.primaryColor
? Colors.grey[200]
: Color.fromRGBO(40, 40, 40, 1)
: context.scaffoldBackgroundColor,
boxShadow: [
@ -488,9 +488,9 @@ class EpisodeGrid extends StatelessWidget {
episode: episodes[index],
color: _c),
Spacer(),
_listenIndicater(context,
episode: episodes[index],
isListened: snapshot.data),
// _listenIndicater(context,
// episode: episodes[index],
// isListened: snapshot.data),
_downloadIndicater(context,
episode: episodes[index]),
_isNewIndicator(episodes[index]),