1
0
mirror of https://github.com/stonega/tsacdop synced 2025-02-18 04:20:37 +01:00

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( padding: EdgeInsets.only(
left: 20.0, right: 20, bottom: 10), left: 20.0, right: 20, bottom: 10),
defaultTextStyle: defaultTextStyle:
GoogleFonts.libreBaskerville( // GoogleFonts.libreBaskerville(
GoogleFonts.martel(
textStyle: TextStyle( textStyle: TextStyle(
height: 1.8, height: 1.8,
), ),
), ),
data: _description, data: _description,
linkStyle: TextStyle( linkStyle: TextStyle(
color: Theme.of(context).accentColor, color: context.accentColor,
// decoration: TextDecoration.underline, // decoration: TextDecoration.underline,
textBaseline: TextBaseline.ideographic), textBaseline: TextBaseline.ideographic),
onLinkTap: (url) { onLinkTap: (url) {
@ -296,7 +297,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
_launchUrl(link.url); _launchUrl(link.url);
}, },
text: _description, text: _description,
style: GoogleFonts.libreBaskerville( style: GoogleFonts.martel(
textStyle: TextStyle( textStyle: TextStyle(
height: 1.8, height: 1.8,
), ),
@ -384,8 +385,6 @@ class MenuBar extends StatefulWidget {
} }
class _MenuBarState extends State<MenuBar> { class _MenuBarState extends State<MenuBar> {
bool _liked = false;
Future<PlayHistory> getPosition(EpisodeBrief episode) async { Future<PlayHistory> getPosition(EpisodeBrief episode) async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
return await dbHelper.getPosition(episode); return await dbHelper.getPosition(episode);
@ -394,17 +393,14 @@ class _MenuBarState extends State<MenuBar> {
Future<int> saveLiked(String url) async { Future<int> saveLiked(String url) async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
int result = await dbHelper.setLiked(url); int result = await dbHelper.setLiked(url);
if (result == 1 && mounted) setState(() => _liked = true); setState(() {});
return result; return result;
} }
Future<int> setUnliked(String url) async { Future<int> setUnliked(String url) async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
int result = await dbHelper.setUniked(url); int result = await dbHelper.setUniked(url);
if (result == 1 && mounted) setState(() {});
setState(() {
_liked = false;
});
return result; return result;
} }
@ -418,12 +414,6 @@ class _MenuBarState extends State<MenuBar> {
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}'; return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
} }
@override
void initState() {
super.initState();
_liked = false;
}
Widget _buttonOnMenu(Widget widget, VoidCallback onTap) => Material( Widget _buttonOnMenu(Widget widget, VoidCallback onTap) => Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
@ -495,7 +485,7 @@ class _MenuBarState extends State<MenuBar> {
future: _isLiked(widget.episodeItem), future: _isLiked(widget.episodeItem),
initialData: false, initialData: false,
builder: (BuildContext context, AsyncSnapshot snapshot) { builder: (BuildContext context, AsyncSnapshot snapshot) {
return (!snapshot.data && !_liked) return (!snapshot.data)
? _buttonOnMenu( ? _buttonOnMenu(
Icon( Icon(
Icons.favorite_border, Icons.favorite_border,
@ -508,23 +498,13 @@ class _MenuBarState extends State<MenuBar> {
await Future.delayed(Duration(seconds: 2)); await Future.delayed(Duration(seconds: 2));
_overlayEntry?.remove(); _overlayEntry?.remove();
}) })
: (snapshot.data && !_liked) : _buttonOnMenu(
? _buttonOnMenu( Icon(
Icon( Icons.favorite,
Icons.favorite, color: Colors.red,
color: Colors.red, ),
), () =>
() => setUnliked( setUnliked(widget.episodeItem.enclosureUrl));
widget.episodeItem.enclosureUrl))
: _buttonOnMenu(
Icon(
Icons.favorite,
color: Colors.red,
),
() {
setUnliked(widget.episodeItem.enclosureUrl);
},
);
}, },
), ),
DownloadButton(episode: widget.episodeItem), DownloadButton(episode: widget.episodeItem),
@ -534,7 +514,7 @@ class _MenuBarState extends State<MenuBar> {
return data.contains(widget.episodeItem) return data.contains(widget.episodeItem)
? _buttonOnMenu( ? _buttonOnMenu(
Icon(Icons.playlist_add_check, Icon(Icons.playlist_add_check,
color: Theme.of(context).accentColor), () { color: context.accentColor), () {
audio.delFromPlaylist(widget.episodeItem); audio.delFromPlaylist(widget.episodeItem);
Fluttertoast.showToast( Fluttertoast.showToast(
msg: 'Removed from playlist', 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; final PodcastLocal podcastLocal;
PodcastPreview({this.podcastLocal, Key key}) : super(key: key); PodcastPreview({this.podcastLocal, Key key}) : super(key: key);
@override
_PodcastPreviewState createState() => _PodcastPreviewState();
}
class _PodcastPreviewState extends State<PodcastPreview> {
Future<List<EpisodeBrief>> _getRssItemTop(PodcastLocal podcastLocal) async { Future<List<EpisodeBrief>> _getRssItemTop(PodcastLocal podcastLocal) async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
List<EpisodeBrief> episodes = await dbHelper.getRssItemTop(podcastLocal.id); List<EpisodeBrief> episodes = await dbHelper.getRssItemTop(podcastLocal.id);
@ -393,8 +389,8 @@ class _PodcastPreviewState extends State<PodcastPreview> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color _c = (Theme.of(context).brightness == Brightness.light) Color _c = (Theme.of(context).brightness == Brightness.light)
? widget.podcastLocal.primaryColor.colorizedark() ? podcastLocal.primaryColor.colorizedark()
: widget.podcastLocal.primaryColor.colorizeLight(); : podcastLocal.primaryColor.colorizeLight();
return Column( return Column(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
@ -402,7 +398,7 @@ class _PodcastPreviewState extends State<PodcastPreview> {
selector: (_, worker) => worker.created, selector: (_, worker) => worker.created,
builder: (context, created, child) { builder: (context, created, child) {
return FutureBuilder<List<EpisodeBrief>>( return FutureBuilder<List<EpisodeBrief>>(
future: _getRssItemTop(widget.podcastLocal), future: _getRssItemTop(podcastLocal),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasError) { if (snapshot.hasError) {
print(snapshot.error); print(snapshot.error);
@ -411,7 +407,7 @@ class _PodcastPreviewState extends State<PodcastPreview> {
return (snapshot.hasData) return (snapshot.hasData)
? ShowEpisode( ? ShowEpisode(
episodes: snapshot.data, episodes: snapshot.data,
podcastLocal: widget.podcastLocal, podcastLocal: podcastLocal,
) )
: Container( : Container(
padding: EdgeInsets.all(5.0), padding: EdgeInsets.all(5.0),
@ -429,7 +425,7 @@ class _PodcastPreviewState extends State<PodcastPreview> {
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
flex: 4, flex: 4,
child: Text(widget.podcastLocal.title, child: Text(podcastLocal.title,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.visible, overflow: TextOverflow.visible,
style: TextStyle(fontWeight: FontWeight.bold, color: _c)), style: TextStyle(fontWeight: FontWeight.bold, color: _c)),
@ -450,11 +446,11 @@ class _PodcastPreviewState extends State<PodcastPreview> {
context, context,
SlideLeftHideRoute( SlideLeftHideRoute(
transitionPage: PodcastDetail( transitionPage: PodcastDetail(
podcastLocal: widget.podcastLocal, podcastLocal: podcastLocal,
hide: playerRunning, hide: playerRunning,
), ),
page: PodcastDetail( page: PodcastDetail(
podcastLocal: widget.podcastLocal, podcastLocal: podcastLocal,
)), )),
); );
}, },
@ -606,7 +602,7 @@ class ShowEpisode extends StatelessWidget {
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 2), padding: EdgeInsets.symmetric(horizontal: 2),
), ),
isListened > 0.95 isListened > 0
? Text('Listened', ? Text('Listened',
style: TextStyle( style: TextStyle(
color: context.textColor.withOpacity(0.5))) color: context.textColor.withOpacity(0.5)))
@ -671,7 +667,7 @@ class ShowEpisode extends StatelessWidget {
} }
break; break;
case 3: case 3:
if (isListened < 0.95) { if (isListened < 1) {
await _markListened(episode); await _markListened(episode);
audio.setEpisodeState = true; audio.setEpisodeState = true;
Fluttertoast.showToast( Fluttertoast.showToast(

View File

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

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:sqflite/sqflite.dart'; import 'package:sqflite/sqflite.dart';
import 'dart:async'; import 'dart:async';
import 'package:path/path.dart'; import 'package:path/path.dart';
@ -272,9 +274,7 @@ class DBHelper {
var dbClient = await database; var dbClient = await database;
int i = 0; int i = 0;
List<Map> list = List<Map> list =
await dbClient.rawQuery("""SELECT listen_time FROM PlayHistory await dbClient.rawQuery("SELECT listen_time FROM PlayHistory WHERE enclosure_url = ?", [url]);
WHERE enclosure_url = ?
""", [url]);
if (list.length == 0) if (list.length == 0)
return 0; return 0;
else { else {
@ -708,35 +708,35 @@ class DBHelper {
return episodes; return episodes;
} }
Future<EpisodeBrief> getRssItemDownload(String url) async { //Future<EpisodeBrief> getRssItemDownload(String url) async {
var dbClient = await database; // var dbClient = await database;
EpisodeBrief episode; // EpisodeBrief episode;
List<Map> list = await dbClient.rawQuery( // List<Map> list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, // """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.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 // 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 // FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
where E.enclosure_url = ? ORDER BY E.milliseconds DESC LIMIT 3""", // where E.enclosure_url = ? ORDER BY E.milliseconds DESC LIMIT 3""",
[url]); // [url]);
if (list != null) // if (list != null)
episode = EpisodeBrief( // episode = EpisodeBrief(
list.first['title'], // list.first['title'],
list.first['enclosure_url'], // list.first['enclosure_url'],
list.first['enclosure_length'], // list.first['enclosure_length'],
list.first['milliseconds'], // list.first['milliseconds'],
list.first['feed_title'], // list.first['feed_title'],
list.first['primaryColor'], // list.first['primaryColor'],
list.first['liked'], // list.first['liked'],
list.first['downloaded'], // list.first['downloaded'],
list.first['duration'], // list.first['duration'],
list.first['explicit'], // list.first['explicit'],
list.first['imagePath'], // list.first['imagePath'],
list.first['media_id'], // list.first['media_id'],
list.first['is_new'], // list.first['is_new'],
list.first['skip_seconds']); // list.first['skip_seconds']);
return episode; // return episode;
} //}
Future<List<EpisodeBrief>> getRecentRssItem(int top) async { Future<List<EpisodeBrief>> getRecentRssItem(int top) async {
var dbClient = await database; var dbClient = await database;
@ -835,8 +835,8 @@ class DBHelper {
// var dbClient = await database; // var dbClient = await database;
// List<EpisodeBrief> episodes = []; // List<EpisodeBrief> episodes = [];
// List<Map> list = await dbClient.rawQuery( // List<Map> list = await dbClient.rawQuery(
// """SELECT E.title, E.enclosure_url, E.enclosure_length, // """SELECT E.title, E.enclosure_url, E.enclosure_length,
// E.milliseconds, P.title as feed_title, E.duration, E.explicit, E.liked, // 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 // 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 // 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 """, // WHERE is_new = 1 AND downloaded != 'ND' AND P.id = ?ORDER BY E.milliseconds DESC """,
@ -861,6 +861,105 @@ class DBHelper {
// } // }
// return episodes; // 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 { Future<int> removeAllNewMark() async {
var dbClient = await database; var dbClient = await database;
@ -995,9 +1094,9 @@ class DBHelper {
Future<bool> isDownloaded(String url) async { Future<bool> isDownloaded(String url) async {
var dbClient = await database; var dbClient = await database;
List<Map> list = await dbClient List<Map> list = await dbClient.rawQuery(
.rawQuery("SELECT downloaded FROM Episodes WHERE enclosure_url = ?", [url]); "SELECT downloaded FROM Episodes WHERE enclosure_url = ?", [url]);
return list.first['downloaded'] == 'ND' ? false: true; return list.first['downloaded'] == 'ND' ? false : true;
} }
Future<int> saveDownloaded(String url, String id) async { 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:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
import 'package:provider/provider.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import '../type/episodebrief.dart'; import '../type/episodebrief.dart';
import '../util/context_extension.dart';
import '../state/download_state.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
class DownloadsManage extends StatefulWidget { class DownloadsManage extends StatefulWidget {
@ -18,25 +21,23 @@ class DownloadsManage extends StatefulWidget {
class _DownloadsManageState extends State<DownloadsManage> { class _DownloadsManageState extends State<DownloadsManage> {
//Downloaded size //Downloaded size
int _size; int _size;
int _mode;
//Downloaded files //Downloaded files
int _fileNum; int _fileNum;
bool _loadEpisodes;
bool _clearing; bool _clearing;
bool _onlyListened;
List<EpisodeBrief> _selectedList; List<EpisodeBrief> _selectedList;
List<EpisodeBrief> _episodes = [];
_getDownloadedRssItem() async { Future<List<EpisodeBrief>> _getDownloadedEpisode(int mode) async {
_episodes = []; List<EpisodeBrief> episodes = [];
final tasks = await FlutterDownloader.loadTasksWithRawQuery(
query: "SELECT * FROM task WHERE status = 3");
var dbHelper = DBHelper(); var dbHelper = DBHelper();
await Future.forEach(tasks, (task) async { episodes = await dbHelper.getDownloadedEpisode(mode);
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(task.url); return episodes;
_episodes.add(episode); }
});
setState(() { Future<int> _isListened(EpisodeBrief episode) async {
_loadEpisodes = true; DBHelper dbHelper = DBHelper();
}); return await dbHelper.isListened(episode.enclosureUrl);
} }
_getStorageSize() async { _getStorageSize() async {
@ -58,12 +59,13 @@ class _DownloadsManageState extends State<DownloadsManage> {
_delSelectedEpisodes() async { _delSelectedEpisodes() async {
setState(() => _clearing = true); setState(() => _clearing = true);
await Future.forEach(_selectedList, (EpisodeBrief episode) async { await Future.forEach(_selectedList, (EpisodeBrief episode) async {
await FlutterDownloader.remove( var downloader = Provider.of<DownloadState>(context, listen: false);
taskId: episode.downloaded, shouldDeleteContent: true); await downloader.removeTask(episode);
var dbHelper = DBHelper(); // await FlutterDownloader.remove(
await dbHelper.delDownloaded(episode.enclosureUrl); // taskId: episode.downloaded, shouldDeleteContent: true);
setState(() => // var dbHelper = DBHelper();
_episodes.removeWhere((e) => e.enclosureUrl == episode.enclosureUrl)); // await dbHelper.delDownloaded(episode.enclosureUrl);
setState(() {});
}); });
await Future.delayed(Duration(seconds: 1)); await Future.delayed(Duration(seconds: 1));
setState(() { setState(() {
@ -90,10 +92,10 @@ class _DownloadsManageState extends State<DownloadsManage> {
void initState() { void initState() {
super.initState(); super.initState();
_clearing = false; _clearing = false;
_loadEpisodes = false;
_selectedList = []; _selectedList = [];
_mode = 0;
_onlyListened = false;
_getStorageSize(); _getStorageSize();
_getDownloadedRssItem();
} }
@override @override
@ -107,9 +109,8 @@ class _DownloadsManageState extends State<DownloadsManage> {
), ),
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('Downloads'),
elevation: 0, elevation: 0,
backgroundColor: Theme.of(context).primaryColor, backgroundColor: context.primaryColor,
), ),
body: SafeArea( body: SafeArea(
child: Stack( child: Stack(
@ -119,125 +120,248 @@ class _DownloadsManageState extends State<DownloadsManage> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Padding(
padding: EdgeInsets.all(10.0),
),
Container( Container(
height: 100.0, height: 140.0,
padding: EdgeInsets.only(bottom: 20, left: 60), color: context.primaryColor,
alignment: Alignment.centerLeft, child: Column(
child: RichText( mainAxisAlignment: MainAxisAlignment.start,
text: TextSpan( children: [
text: 'Total ', Padding(
style: TextStyle( padding: const EdgeInsets.only(left: 20),
color: Theme.of(context).accentColor, child: RichText(
fontSize: 20, 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>[ Spacer(),
TextSpan( SizedBox(
text: _fileNum.toString(), height: 40,
style: GoogleFonts.cairo( child: Row(
textStyle: TextStyle( children: [
color: Theme.of(context).accentColor, Material(
fontSize: 40, color: Colors.transparent,
)), child: PopupMenuButton<int>(
), shape: RoundedRectangleBorder(
TextSpan( borderRadius: BorderRadius.all(
text: _fileNum < 2 ? ' episode' : ' episodes ', Radius.circular(10))),
style: TextStyle( elevation: 1,
color: Theme.of(context).accentColor, tooltip: 'Sort By',
fontSize: 20, child: Container(
)), height: 40,
TextSpan( padding:
text: (_size ~/ 1000000).toString(), EdgeInsets.symmetric(horizontal: 20),
style: GoogleFonts.cairo( child: Row(
textStyle: TextStyle( mainAxisSize: MainAxisSize.min,
color: Theme.of(context).accentColor, children: <Widget>[
fontSize: 50, Text('Sory by'),
)), Padding(
), padding: EdgeInsets.symmetric(
TextSpan( horizontal: 5),
text: ' Mb', ),
style: TextStyle( Icon(
color: Theme.of(context).accentColor, _mode == 0
fontSize: 20, ? LineIcons
)), .hourglass_start_solid
], : _mode == 1
), ? LineIcons
), .hourglass_half_solid
), : LineIcons.save,
_loadEpisodes size: 18,
? Expanded( )
child: ListView.builder( ],
itemCount: _episodes.length, )),
shrinkWrap: true, itemBuilder: (context) => [
scrollDirection: Axis.vertical, PopupMenuItem(
itemBuilder: (context, index) { value: 0,
return Column( child: Text('Newest first'),
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));
}
},
),
), ),
Divider( PopupMenuItem(
height: 2, value: 1,
child: Text('Oldest first'),
),
PopupMenuItem(
value: 2,
child: Text('Size'),
), ),
], ],
); onSelected: (value) {
}), if (value == 0)
) setState(() => _mode = 0);
: CircularProgressIndicator(), 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( AnimatedPositioned(
duration: Duration(milliseconds: 800), duration: Duration(milliseconds: 800),
curve: Curves.elasticInOut, curve: Curves.elasticInOut,
left: MediaQuery.of(context).size.width / 2 - 50, left: context.width / 2 - 50,
bottom: _selectedList.length == 0 ? -100 : 30, bottom: _selectedList.length == 0 ? -100 : 30,
child: InkWell( child: InkWell(
onTap: () => _delSelectedEpisodes(), onTap: () => _delSelectedEpisodes(),

View File

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

View File

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

View File

@ -21,7 +21,8 @@ List<Libries> google = [
List<Libries> fonts = [ List<Libries> fonts = [
Libries('Libre Baskerville', font, Libries('Libre Baskerville', font,
"https://fonts.google.com/specimen/Libre+Baskerville"), "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 = [ List<Libries> plugins = [
@ -68,5 +69,6 @@ List<Libries> plugins = [
Libries('Rxdart', apacheLicense, 'https://pub.dev/packages/rxdart'), Libries('Rxdart', apacheLicense, 'https://pub.dev/packages/rxdart'),
Libries('flutter_isolate', mit, 'https://pub.dev/packages/flutter_isolate'), Libries('flutter_isolate', mit, 'https://pub.dev/packages/flutter_isolate'),
Libries('auto_animated', mit, 'https://pub.dev/packages/auto_animated'), 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, leading: icon,
title: Text(text), title: Text(text),
subtitle: Text(description), 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( trailing: Checkbox(
value: e < 10, value: e < 10,
onChanged: e == 0 onChanged: e == 0
@ -131,11 +146,8 @@ class _PopupMenuSettingState extends State<PopupMenuSetting> {
break; break;
case 2: case 2:
return _popupMenuItem(menu, e, return _popupMenuItem(menu, e,
icon: Icon( icon: Icon(LineIcons.heart,
LineIcons.heart, color: Colors.red, size: 21),
color: Colors.red,
size: 21
),
text: 'Like', text: 'Like',
description: 'Add episode to favorite'); description: 'Add episode to favorite');
break; break;

View File

@ -179,12 +179,6 @@ class _SettingsState extends State<Settings>
leading: Icon(LineIcons.play_circle), leading: Icon(LineIcons.play_circle),
title: Text('Play'), title: Text('Play'),
subtitle: Text('Playlist and player'), 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), Divider(height: 2),
ListTile( ListTile(

View File

@ -30,11 +30,6 @@ class _StorageSettingState extends State<StorageSetting>
setState(() => _value = _animation.value); setState(() => _value = _animation.value);
}); });
_controller.forward(); _controller.forward();
// _controller.addStatusListener((status) {
// if (status == AnimationStatus.completed) {
// _controller.reset();
// }
// });
} }
} }
@ -44,6 +39,22 @@ class _StorageSettingState extends State<StorageSetting>
return value != 0; 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 { _setAudtDownloadNetwork(bool boo) async {
KeyValueStorage storage = KeyValueStorage(autoDownloadNetworkKey); KeyValueStorage storage = KeyValueStorage(autoDownloadNetworkKey);
await storage.saveInt(boo ? 1 : 0); await storage.saveInt(boo ? 1 : 0);
@ -186,10 +197,41 @@ class _StorageSettingState extends State<StorageSetting>
subtitle: Text('Manage downloaded audio files'), subtitle: Text('Manage downloaded audio files'),
), ),
Divider(height: 2), 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( ListTile(
contentPadding: EdgeInsets.only(left: 80.0, right: 25), contentPadding: EdgeInsets.only(left: 80.0, right: 25),
// leading: Icon(Icons.colorize), // leading: Icon(Icons.colorize),
title: Text('Cache'), title: Text('Audio cache'),
subtitle: Text('Audio cache max size'), subtitle: Text('Audio cache max size'),
trailing: Text.rich(TextSpan( trailing: Text.rich(TextSpan(
text: '${(_value ~/ 100) * 100}', text: '${(_value ~/ 100) * 100}',

View File

@ -4,10 +4,10 @@ import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:intl/intl.dart';
import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path; 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 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import '../type/episodebrief.dart'; import '../type/episodebrief.dart';
@ -37,6 +37,10 @@ class DownloadState extends ChangeNotifier {
List<EpisodeTask> _episodeTasks = []; List<EpisodeTask> _episodeTasks = [];
List<EpisodeTask> get episodeTasks => _episodeTasks; List<EpisodeTask> get episodeTasks => _episodeTasks;
DownloadState() {
_autoDelete();
}
@override @override
void addListener(VoidCallback listener) async { void addListener(VoidCallback listener) async {
_loadTasks(); _loadTasks();
@ -55,7 +59,6 @@ class DownloadState extends ChangeNotifier {
_episodeTasks.add(EpisodeTask(episode, task.taskId, _episodeTasks.add(EpisodeTask(episode, task.taskId,
progress: task.progress, status: task.status)); progress: task.progress, status: task.status));
}); });
print(_episodeTasks.length);
notifyListeners(); notifyListeners();
} }
@ -205,4 +208,19 @@ class DownloadState extends ChangeNotifier {
_episodeTasks.removeWhere( _episodeTasks.removeWhere(
(element) => element.episode.enclosureUrl == episode.enclosureUrl); (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 { class EpisodeBrief {
final String title; final String title;
String description; final String description;
final int pubDate; final int pubDate;
final int enclosureLength; final int enclosureLength;
final String enclosureUrl; final String enclosureUrl;
@ -17,6 +17,7 @@ class EpisodeBrief {
final String mediaId; final String mediaId;
final int isNew; final int isNew;
final int skipSeconds; final int skipSeconds;
final int downloadDate;
EpisodeBrief( EpisodeBrief(
this.title, this.title,
this.enclosureUrl, this.enclosureUrl,
@ -31,7 +32,10 @@ class EpisodeBrief {
this.imagePath, this.imagePath,
this.mediaId, this.mediaId,
this.isNew, this.isNew,
this.skipSeconds); this.skipSeconds,
{this.description = '',
this.downloadDate = 0})
: assert(enclosureUrl != null);
String dateToString() { String dateToString() {
DateTime date = DateTime.fromMillisecondsSinceEpoch(pubDate, isUtc: true); 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() { MediaItem toMediaItem() {
return MediaItem( return MediaItem(
id: mediaId, id: mediaId,

View File

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

View File

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