Add filter in podcast detail page.

This commit is contained in:
stonegate 2020-07-24 02:42:25 +08:00
parent d9bb4cf987
commit c183683101
5 changed files with 977 additions and 811 deletions

View File

@ -10,6 +10,8 @@ import '../type/episodebrief.dart';
import '../webfeed/webfeed.dart'; import '../webfeed/webfeed.dart';
import '../type/sub_history.dart'; import '../type/sub_history.dart';
enum Filter { downloaded, liked, search, all }
class DBHelper { class DBHelper {
static Database _db; static Database _db;
Future<Database> get database async { Future<Database> get database async {
@ -491,7 +493,6 @@ class DBHelper {
var feed = RssFeed.parse(response.data); var feed = RssFeed.parse(response.data);
String url, description; String url, description;
feed.items.removeWhere((item) => item == null); feed.items.removeWhere((item) => item == null);
int result = feed.items.length;
var dbClient = await database; var dbClient = await database;
int count = Sqflite.firstIntValue(await dbClient.rawQuery( int count = Sqflite.firstIntValue(await dbClient.rawQuery(
@ -556,66 +557,117 @@ class DBHelper {
} }
} }
Future<List<EpisodeBrief>> getRssItem(String id, int i, bool reverse) async { Future<List<EpisodeBrief>> getRssItem(String id, int count, bool reverse,
{Filter filter = Filter.all, String query = ''}) async {
var dbClient = await database; var dbClient = await database;
List<EpisodeBrief> episodes = []; List<EpisodeBrief> episodes = [];
if (i == -1) { List<Map> list = [];
List<Map> list = await dbClient if (count == -1) {
switch (filter) {
case Filter.all:
list = await dbClient
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length, .rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit, E.liked, E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit,
E.downloaded, P.primaryColor , E.media_id, E.is_new, P.skip_seconds P.primaryColor 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 P.id = ? ORDER BY E.milliseconds ASC""", [id]); WHERE P.id = ? ORDER BY E.milliseconds ASC""", [id]);
for (var i in list) { break;
episodes.add(EpisodeBrief( case Filter.liked:
i['title'], list = await dbClient
i['enclosure_url'], .rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
i['enclosure_length'], E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit,
i['milliseconds'], P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
i['feedTitle'], WHERE P.id = ? AND E.liked = 1 ORDER BY E.milliseconds ASC""", [id]);
i['primaryColor'], break;
i['liked'], case Filter.downloaded:
i['downloaded'], list = await dbClient.rawQuery(
i['duration'], """SELECT E.title, E.enclosure_url, E.enclosure_length,
i['explicit'], E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit,
i['imagePath'], P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
i['media_id'], WHERE P.id = ? AND E.media_id != E.enclosure_url ORDER BY E.milliseconds ASC""",
i['is_new'], [id]);
i['skip_seconds'])); break;
case Filter.search:
list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit,
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE P.id = ? AND E.title LIKE ? ORDER BY E.milliseconds ASC""",
[id, '%$query%']);
break;
default:
} }
return episodes;
} else if (reverse) { } else if (reverse) {
List<Map> list = await dbClient switch (filter) {
case Filter.all:
list = await dbClient
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length, .rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit, E.liked, E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit,
E.downloaded, P.primaryColor , E.media_id, E.is_new, P.skip_seconds P.primaryColor 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 P.id = ? ORDER BY E.milliseconds ASC LIMIT ?""", [id, count]);
WHERE P.id = ? ORDER BY E.milliseconds ASC LIMIT ?""", [id, i]); break;
for (var i in list) { case Filter.liked:
episodes.add(EpisodeBrief( list = await dbClient.rawQuery(
i['title'], """SELECT E.title, E.enclosure_url, E.enclosure_length,
i['enclosure_url'], E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit,
i['enclosure_length'], P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
i['milliseconds'], WHERE P.id = ? AND E.liked = 1 ORDER BY E.milliseconds ASC LIMIT ?""",
i['feedTitle'], [id, count]);
i['primaryColor'], break;
i['liked'], case Filter.downloaded:
i['downloaded'], list = await dbClient.rawQuery(
i['duration'], """SELECT E.title, E.enclosure_url, E.enclosure_length,
i['explicit'], E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit,
i['imagePath'], P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
i['media_id'], WHERE P.id = ? AND E.enclosure_url != E.media_id ORDER BY E.milliseconds ASC LIMIT ?""",
i['is_new'], [id, count]);
i['skip_seconds'])); break;
case Filter.search:
list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit,
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE P.id = ? AND E.title LIKE ? ORDER BY E.milliseconds ASC LIMIT ?""",
[id, '%$query%', count]);
break;
default:
} }
return episodes;
} else { } else {
List<Map> list = await dbClient switch (filter) {
case Filter.all:
list = await dbClient
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length, .rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit, E.liked, E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit,
E.downloaded, P.primaryColor , E.media_id, E.is_new, P.skip_seconds P.primaryColor 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 P.id = ? ORDER BY E.milliseconds DESC LIMIT ?""", [id, count]);
WHERE P.id = ? ORDER BY E.milliseconds DESC LIMIT ?""", [id, i]); break;
case Filter.liked:
list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit,
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE P.id = ? AND E.liked = 1 ORDER BY E.milliseconds DESC LIMIT ?""",
[id, count]);
break;
case Filter.downloaded:
list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit,
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE P.id = ? AND E.enclosure_url != E.media_id ORDER BY E.milliseconds DESC LIMIT ?""",
[id, count]);
break;
case Filter.search:
list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit,
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE P.id = ? AND E.title LIKE ? ORDER BY E.milliseconds DESC LIMIT ?""",
[id, '%$query%', count]);
break;
default:
}
}
if (list.isNotEmpty)
for (var i in list) { for (var i in list) {
episodes.add(EpisodeBrief( episodes.add(EpisodeBrief(
i['title'], i['title'],
@ -624,18 +676,13 @@ class DBHelper {
i['milliseconds'], i['milliseconds'],
i['feedTitle'], i['feedTitle'],
i['primaryColor'], i['primaryColor'],
i['liked'],
i['downloaded'],
i['duration'], i['duration'],
i['explicit'], i['explicit'],
i['imagePath'], i['imagePath'],
i['media_id'], ));
i['is_new'],
i['skip_seconds']));
} }
return episodes; return episodes;
} }
}
Future<List<EpisodeBrief>> getNewEpisodes(String id) async { Future<List<EpisodeBrief>> getNewEpisodes(String id) async {
var dbClient = await database; var dbClient = await database;
@ -644,17 +691,15 @@ class DBHelper {
if (id == 'all') if (id == 'all')
list = await dbClient.rawQuery( 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.downloaded, P.primaryColor, E.media_id, E.is_new, P.skip_seconds P.primaryColor 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.is_new = 1 AND E.downloaded = 'ND' AND P.auto_download = 1 ORDER BY E.milliseconds ASC""", WHERE E.is_new = 1 AND E.downloaded = 'ND' AND P.auto_download = 1 ORDER BY E.milliseconds ASC""",
); );
else else
list = await dbClient.rawQuery( 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.downloaded, P.primaryColor, E.media_id, E.is_new, P.skip_seconds P.primaryColor 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.is_new = 1 AND E.downloaded = 'ND' AND E.feed_id = ? ORDER BY E.milliseconds ASC""", WHERE E.is_new = 1 AND E.downloaded = 'ND' AND E.feed_id = ? ORDER BY E.milliseconds ASC""",
[id]); [id]);
if (list.isNotEmpty) if (list.isNotEmpty)
@ -666,14 +711,10 @@ class DBHelper {
i['milliseconds'], i['milliseconds'],
i['feed_title'], i['feed_title'],
i['primaryColor'], i['primaryColor'],
i['liked'],
i['downloaded'],
i['duration'], i['duration'],
i['explicit'], i['explicit'],
i['imagePath'], i['imagePath'],
i['media_id'], ));
i['is_new'],
i['skip_seconds']));
} }
return episodes; return episodes;
} }
@ -683,9 +724,8 @@ class DBHelper {
List<EpisodeBrief> episodes = []; List<EpisodeBrief> episodes = [];
List<Map> list = await dbClient List<Map> list = await dbClient
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length, .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.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
E.downloaded, P.primaryColor, E.media_id, E.is_new, P.skip_seconds P.primaryColor 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.feed_id = ? ORDER BY E.milliseconds DESC LIMIT 2""", [id]); where E.feed_id = ? ORDER BY E.milliseconds DESC LIMIT 2""", [id]);
for (var i in list) { for (var i in list) {
episodes.add(EpisodeBrief( episodes.add(EpisodeBrief(
@ -695,56 +735,21 @@ class DBHelper {
i['milliseconds'], i['milliseconds'],
i['feed_title'], i['feed_title'],
i['primaryColor'], i['primaryColor'],
i['liked'],
i['downloaded'],
i['duration'], i['duration'],
i['explicit'], i['explicit'],
i['imagePath'], i['imagePath'],
i['media_id'], ));
i['is_new'],
i['skip_seconds']));
} }
return episodes; 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]);
// 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 { Future<List<EpisodeBrief>> getRecentRssItem(int top) async {
var dbClient = await database; var dbClient = await database;
List<EpisodeBrief> episodes = []; List<EpisodeBrief> episodes = [];
List<Map> list = await dbClient List<Map> list = await dbClient
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length, .rawQuery("""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.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds P.imagePath, P.primaryColor 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
ORDER BY E.milliseconds DESC LIMIT ? """, [top]); ORDER BY E.milliseconds DESC LIMIT ? """, [top]);
for (var i in list) { for (var i in list) {
episodes.add(EpisodeBrief( episodes.add(EpisodeBrief(
@ -754,14 +759,10 @@ class DBHelper {
i['milliseconds'], i['milliseconds'],
i['feed_title'], i['feed_title'],
i['primaryColor'], i['primaryColor'],
i['liked'],
i['downloaded'],
i['duration'], i['duration'],
i['explicit'], i['explicit'],
i['imagePath'], i['imagePath'],
i['media_id'], ));
i['is_new'],
i['skip_seconds']));
} }
return episodes; return episodes;
} }
@ -774,9 +775,8 @@ class DBHelper {
List<String> s = group.map<String>((e) => "'$e'").toList(); List<String> s = group.map<String>((e) => "'$e'").toList();
List<Map> list = await dbClient List<Map> list = await dbClient
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length, .rawQuery("""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.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds P.imagePath, P.primaryColor 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 P.id in (${s.join(',')}) WHERE P.id in (${s.join(',')})
ORDER BY E.milliseconds DESC LIMIT ? """, [top]); ORDER BY E.milliseconds DESC LIMIT ? """, [top]);
for (var i in list) { for (var i in list) {
@ -787,14 +787,10 @@ class DBHelper {
i['milliseconds'], i['milliseconds'],
i['feed_title'], i['feed_title'],
i['primaryColor'], i['primaryColor'],
i['liked'],
i['downloaded'],
i['duration'], i['duration'],
i['explicit'], i['explicit'],
i['imagePath'], i['imagePath'],
i['media_id'], ));
i['is_new'],
i['skip_seconds']));
} }
} }
return episodes; return episodes;
@ -805,9 +801,8 @@ class DBHelper {
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.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds P.imagePath, P.primaryColor 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 ORDER BY E.milliseconds DESC """, WHERE is_new = 1 ORDER BY E.milliseconds DESC """,
); );
for (var i in list) { for (var i in list) {
@ -818,56 +813,21 @@ class DBHelper {
i['milliseconds'], i['milliseconds'],
i['feed_title'], i['feed_title'],
i['primaryColor'], i['primaryColor'],
i['liked'],
i['downloaded'],
i['duration'], i['duration'],
i['explicit'], i['explicit'],
i['imagePath'], i['imagePath'],
i['media_id'], ));
i['is_new'],
i['skip_seconds']));
} }
return episodes; return episodes;
} }
//Future<List<EpisodeBrief>> getNewRssItem(String id) async {
// 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,
// 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 """,
// [id],
// );
// 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>> getOutdatedEpisode(int deadline) async { Future<List<EpisodeBrief>> getOutdatedEpisode(int deadline) async {
var dbClient = await database; var dbClient = await database;
List<EpisodeBrief> episodes = []; List<EpisodeBrief> episodes = [];
List<Map> list = await dbClient List<Map> list = await dbClient
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length, .rawQuery("""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.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds P.imagePath, P.primaryColor 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.download_date < ? AND E.enclosure_url != E.media_id WHERE E.download_date < ? AND E.enclosure_url != E.media_id
ORDER BY E.milliseconds DESC""", [deadline]); ORDER BY E.milliseconds DESC""", [deadline]);
for (var i in list) { for (var i in list) {
@ -878,14 +838,10 @@ class DBHelper {
i['milliseconds'], i['milliseconds'],
i['feed_title'], i['feed_title'],
i['primaryColor'], i['primaryColor'],
i['liked'],
i['downloaded'],
i['duration'], i['duration'],
i['explicit'], i['explicit'],
i['imagePath'], i['imagePath'],
i['media_id'], ));
i['is_new'],
i['skip_seconds']));
} }
return episodes; return episodes;
} }
@ -898,9 +854,8 @@ class DBHelper {
if (mode == 0) if (mode == 0)
list = await dbClient.rawQuery( list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.download_date, """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.milliseconds, P.title as feed_title, E.duration, E.explicit,
E.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds P.imagePath, P.primaryColor 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 != E.media_id WHERE E.enclosure_url != E.media_id
ORDER BY E.download_date DESC""", ORDER BY E.download_date DESC""",
); );
@ -908,9 +863,8 @@ class DBHelper {
else if (mode == 1) else if (mode == 1)
list = await dbClient.rawQuery( list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.download_date, """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.milliseconds, P.title as feed_title, E.duration, E.explicit,
E.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds P.imagePath, P.primaryColor 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 != E.media_id WHERE E.enclosure_url != E.media_id
ORDER BY E.download_date ASC""", ORDER BY E.download_date ASC""",
); );
@ -918,9 +872,8 @@ class DBHelper {
else if (mode == 2) else if (mode == 2)
list = await dbClient.rawQuery( list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.download_date, """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.milliseconds, P.title as feed_title, E.duration, E.explicit,
E.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds P.imagePath, P.primaryColor 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 != E.media_id WHERE E.enclosure_url != E.media_id
ORDER BY E.enclosure_length DESC""", ORDER BY E.enclosure_length DESC""",
); );
@ -933,14 +886,9 @@ class DBHelper {
i['milliseconds'], i['milliseconds'],
i['feed_title'], i['feed_title'],
i['primaryColor'], i['primaryColor'],
i['liked'],
i['downloaded'],
i['duration'], i['duration'],
i['explicit'], i['explicit'],
i['imagePath'], i['imagePath'],
i['media_id'],
i['is_new'],
i['skip_seconds'],
downloadDate: i['download_date']), downloadDate: i['download_date']),
); );
} }
@ -961,9 +909,8 @@ class DBHelper {
List<String> s = group.map<String>((e) => "'$e'").toList(); List<String> s = group.map<String>((e) => "'$e'").toList();
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.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds P.imagePath, P.primaryColor 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 P.id in (${s.join(',')}) AND is_new = 1 WHERE P.id in (${s.join(',')}) AND is_new = 1
ORDER BY E.milliseconds DESC""", ORDER BY E.milliseconds DESC""",
); );
@ -975,14 +922,10 @@ class DBHelper {
i['milliseconds'], i['milliseconds'],
i['feed_title'], i['feed_title'],
i['primaryColor'], i['primaryColor'],
i['liked'],
i['downloaded'],
i['duration'], i['duration'],
i['explicit'], i['explicit'],
i['imagePath'], i['imagePath'],
i['media_id'], ));
i['is_new'],
i['skip_seconds']));
} }
} }
return episodes; return episodes;
@ -1014,8 +957,8 @@ class DBHelper {
if (sortBy == 0) { if (sortBy == 0) {
List<Map> list = await dbClient.rawQuery( List<Map> list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath, """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.title as feed_title, E.duration, E.explicit, P.primaryColor
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.liked = 1 ORDER BY E.milliseconds DESC LIMIT ?""", [i]); WHERE E.liked = 1 ORDER BY E.milliseconds DESC LIMIT ?""", [i]);
for (var i in list) { for (var i in list) {
episodes.add(EpisodeBrief( episodes.add(EpisodeBrief(
@ -1025,20 +968,16 @@ class DBHelper {
i['milliseconds'], i['milliseconds'],
i['feed_title'], i['feed_title'],
i['primaryColor'], i['primaryColor'],
i['liked'],
i['downloaded'],
i['duration'], i['duration'],
i['explicit'], i['explicit'],
i['imagePath'], i['imagePath'],
i['media_id'], ));
i['is_new'],
i['skip_seconds']));
} }
} else { } else {
List<Map> list = await dbClient.rawQuery( List<Map> list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath, """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.title as feed_title, E.duration, E.explicit, P.primaryColor
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.liked = 1 ORDER BY E.liked_date DESC LIMIT ?""", [i]); WHERE E.liked = 1 ORDER BY E.liked_date DESC LIMIT ?""", [i]);
for (var i in list) { for (var i in list) {
episodes.add(EpisodeBrief( episodes.add(EpisodeBrief(
@ -1048,14 +987,10 @@ class DBHelper {
i['milliseconds'], i['milliseconds'],
i['feed_title'], i['feed_title'],
i['primaryColor'], i['primaryColor'],
i['liked'],
i['downloaded'],
i['duration'], i['duration'],
i['explicit'], i['explicit'],
i['imagePath'], i['imagePath'],
i['media_id'], ));
i['is_new'],
i['skip_seconds']));
} }
} }
return episodes; return episodes;
@ -1150,8 +1085,8 @@ class DBHelper {
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, E.milliseconds, P.imagePath, """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.title as feed_title, E.duration, E.explicit, P.skip_seconds,
P.primaryColor, E.media_id, E.is_new, P.skip_seconds FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id P.primaryColor, E.media_id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE E.enclosure_url = ?""", [url]); WHERE E.enclosure_url = ?""", [url]);
if (list.isEmpty) { if (list.isEmpty) {
return null; return null;
@ -1163,14 +1098,11 @@ class DBHelper {
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['downloaded'],
list.first['duration'], list.first['duration'],
list.first['explicit'], list.first['explicit'],
list.first['imagePath'], list.first['imagePath'],
list.first['media_id'], mediaId: list.first['media_id'],
list.first['is_new'], skipSeconds: list.first['skip_seconds']);
list.first['skip_seconds']);
return episode; return episode;
} }
} }
@ -1180,8 +1112,8 @@ class DBHelper {
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, E.milliseconds, P.imagePath, """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.title as feed_title, E.duration, E.explicit, P.skip_seconds,
P.primaryColor, E.media_id, E.is_new, P.skip_seconds FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id P.primaryColor, E.media_id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE E.media_id = ?""", [id]); WHERE E.media_id = ?""", [id]);
if (list.isEmpty) if (list.isEmpty)
return null; return null;
@ -1193,14 +1125,11 @@ class DBHelper {
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['downloaded'],
list.first['duration'], list.first['duration'],
list.first['explicit'], list.first['explicit'],
list.first['imagePath'], list.first['imagePath'],
list.first['media_id'], mediaId: list.first['media_id'],
list.first['is_new'], skipSeconds: list.first['skip_seconds']);
list.first['skip_seconds']);
return episode; return episode;
} }
} }

View File

@ -52,7 +52,46 @@ class _PodcastDetailState extends State<PodcastDetail> {
/// Default layout. /// Default layout.
Layout _layout; Layout _layout;
bool _scroll; /// If true, stop grid load animation.
bool _scroll = false;
double _topHeight = 0;
ScrollController _controller;
/// Episodes num load first time.
int _top = 96;
/// Load more episodes when scroll to bottom.
bool _loadMore = false;
/// Change sort by.
bool _reverse = false;
/// Filter type.
Filter _filter = Filter.all;
/// Query string
String _query = '';
///Hide listened.
bool _hideListened = false;
@override
void initState() {
super.initState();
_loadMore = false;
_reverse = false;
_controller = ScrollController();
_scroll = false;
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Future _updateRssItem(BuildContext context, PodcastLocal podcastLocal) async { Future _updateRssItem(BuildContext context, PodcastLocal podcastLocal) async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
final result = await dbHelper.updatePodcastRss(podcastLocal); final result = await dbHelper.updatePodcastRss(podcastLocal);
@ -99,15 +138,16 @@ class _PodcastDetailState extends State<PodcastDetail> {
if (mounted) setState(() {}); if (mounted) setState(() {});
} }
Future<List<EpisodeBrief>> _getRssItem( Future<List<EpisodeBrief>> _getRssItem(PodcastLocal podcastLocal,
PodcastLocal podcastLocal, int i, bool reverse) async { {int count, bool reverse, Filter filter, String query}) async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
List<EpisodeBrief> episodes = [];
_episodeCount = await dbHelper.getPodcastCounts(podcastLocal.id); _episodeCount = await dbHelper.getPodcastCounts(podcastLocal.id);
KeyValueStorage storage = KeyValueStorage(podcastLayoutKey); KeyValueStorage storage = KeyValueStorage(podcastLayoutKey);
int index = await storage.getInt(defaultValue: 1); int index = await storage.getInt(defaultValue: 1);
if (_layout == null) _layout = Layout.values[index]; if (_layout == null) _layout = Layout.values[index];
List<EpisodeBrief> episodes = episodes = await dbHelper.getRssItem(podcastLocal.id, count, reverse,
await dbHelper.getRssItem(podcastLocal.id, i, reverse); filter: filter, query: query);
if (podcastLocal.provider.contains('fireside')) { if (podcastLocal.provider.contains('fireside')) {
FiresideData data = FiresideData(podcastLocal.id, podcastLocal.link); FiresideData data = FiresideData(podcastLocal.id, podcastLocal.link);
await data.getData(); await data.getData();
@ -117,17 +157,6 @@ class _PodcastDetailState extends State<PodcastDetail> {
return episodes; return episodes;
} }
_launchUrl(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
Fluttertoast.showToast(
msg: '$url Invalid Link',
gravity: ToastGravity.TOP,
);
}
}
_markListened(String podcastId) async { _markListened(String podcastId) async {
DBHelper dbHelper = DBHelper(); DBHelper dbHelper = DBHelper();
List<EpisodeBrief> episodes = List<EpisodeBrief> episodes =
@ -234,6 +263,24 @@ class _PodcastDetailState extends State<PodcastDetail> {
); );
} }
_customPopupMenu(
{Widget child,
String tooltip,
List<PopupMenuEntry<int>> itemBuilder,
Function(int) onSelected}) =>
Material(
color: Colors.transparent,
child: PopupMenuButton<int>(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
elevation: 1,
tooltip: tooltip,
child: child,
itemBuilder: (context) => itemBuilder,
onSelected: (value) => onSelected(value),
),
);
_confirmMarkListened(BuildContext context) => generalDialog( _confirmMarkListened(BuildContext context) => generalDialog(
context, context,
title: Text(context.s.markConfirm), title: Text(context.s.markConfirm),
@ -261,34 +308,6 @@ class _PodcastDetailState extends State<PodcastDetail> {
], ],
); );
double _topHeight = 0;
ScrollController _controller;
/// Episodes num load first time.
int _top;
/// Load more episodes when scroll to bottom.
bool _loadMore;
/// Change sort by.
bool _reverse;
@override
void initState() {
super.initState();
_loadMore = false;
_top = 99;
_reverse = false;
_controller = ScrollController();
_scroll = false;
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color _color = widget.podcastLocal.primaryColor.colorizedark(); Color _color = widget.podcastLocal.primaryColor.colorizedark();
@ -321,8 +340,11 @@ class _PodcastDetailState extends State<PodcastDetail> {
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: FutureBuilder<List<EpisodeBrief>>( child: FutureBuilder<List<EpisodeBrief>>(
future: future: _getRssItem(widget.podcastLocal,
_getRssItem(widget.podcastLocal, _top, _reverse), count: _top,
reverse: _reverse,
filter: _filter,
query: _query),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error); if (snapshot.hasError) print(snapshot.error);
return (snapshot.hasData) return (snapshot.hasData)
@ -339,16 +361,14 @@ class _PodcastDetailState extends State<PodcastDetail> {
Duration(seconds: 3)); Duration(seconds: 3));
if (mounted) if (mounted)
setState(() { setState(() {
_top = _top + 33; _top = _top + 36;
_loadMore = false; _loadMore = false;
}); });
} }
if (_controller.offset > 0 && if (_controller.offset > 0 &&
mounted && mounted &&
!_scroll) !_scroll)
setState(() { setState(() => _scroll = true);
_scroll = true;
});
}), }),
physics: physics:
const AlwaysScrollableScrollPhysics(), const AlwaysScrollableScrollPhysics(),
@ -356,26 +376,36 @@ class _PodcastDetailState extends State<PodcastDetail> {
SliverAppBar( SliverAppBar(
brightness: Brightness.dark, brightness: Brightness.dark,
actions: <Widget>[ actions: <Widget>[
PopupMenuButton<int>( _customPopupMenu(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10))),
elevation: 2,
tooltip: s.menu, tooltip: s.menu,
itemBuilder: (context) => [ onSelected: (int value) {
widget.podcastLocal.link != null switch (value) {
? PopupMenuItem( case 0:
widget.podcastLocal.link
.launchUrl;
break;
case 1:
widget.podcastLocal.rssUrl
.launchUrl;
break;
case 2:
_confirmMarkListened(context);
break;
}
},
itemBuilder: [
if (widget.podcastLocal.link !=
null)
PopupMenuItem(
value: 0, value: 0,
child: Container( child: Container(
padding: EdgeInsets.only( padding:
left: 10), EdgeInsets.only(left: 10),
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Icon(Icons.link, Icon(Icons.link,
color: Theme.of( color: context
context) .textColor),
.tabBarTheme
.labelColor),
Padding( Padding(
padding: EdgeInsets padding: EdgeInsets
.symmetric( .symmetric(
@ -386,8 +416,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
], ],
), ),
), ),
) ),
: Center(),
PopupMenuItem( PopupMenuItem(
value: 1, value: 1,
child: Container( child: Container(
@ -397,9 +426,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
children: <Widget>[ children: <Widget>[
Icon( Icon(
Icons.rss_feed, Icons.rss_feed,
color: Theme.of(context) color: context.textColor,
.tabBarTheme
.labelColor,
), ),
Padding( Padding(
padding: padding:
@ -427,9 +454,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
painter: painter:
ListenedAllPainter( ListenedAllPainter(
context context
.textTheme .textColor,
.bodyText1
.color,
stroke: 2)), stroke: 2)),
), ),
Padding( Padding(
@ -445,35 +470,18 @@ class _PodcastDetailState extends State<PodcastDetail> {
), ),
), ),
], ],
onSelected: (int value) { ),
switch (value) {
case 0:
_launchUrl(
widget.podcastLocal.link);
break;
case 1:
_launchUrl(
widget.podcastLocal.rssUrl);
break;
case 2:
_confirmMarkListened(context);
break;
}
},
)
], ],
elevation: 0, elevation: 0,
iconTheme: IconThemeData( iconTheme: IconThemeData(
color: Colors.white, color: Colors.white,
), ),
expandedHeight: 150 + expandedHeight: 150 + context.paddingTop,
MediaQuery.of(context).padding.top,
backgroundColor: _color, backgroundColor: _color,
floating: true, floating: true,
pinned: true, pinned: true,
flexibleSpace: LayoutBuilder(builder: flexibleSpace: LayoutBuilder(
(BuildContext context, builder: (context, constraints) {
BoxConstraints constraints) {
_topHeight = constraints.biggest.height; _topHeight = constraints.biggest.height;
return FlexibleSpaceBar( return FlexibleSpaceBar(
background: Stack( background: Stack(
@ -481,9 +489,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
Container( Container(
margin: EdgeInsets.only( margin: EdgeInsets.only(
top: 120 + top: 120 +
MediaQuery.of(context) context.paddingTop),
.padding
.top),
padding: EdgeInsets.only( padding: EdgeInsets.only(
left: 80, right: 120), left: 80, right: 120),
color: Colors.white10, color: Colors.white10,
@ -506,18 +512,17 @@ class _PodcastDetailState extends State<PodcastDetail> {
style: TextStyle( style: TextStyle(
color: color:
Colors.white)), Colors.white)),
widget.podcastLocal.provider if (widget.podcastLocal
.isNotEmpty .provider.isNotEmpty)
? Text( Text(
s.hostedOn(widget s.hostedOn(widget
.podcastLocal .podcastLocal
.provider), .provider),
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
color: Colors color:
.white), Colors.white),
) ),
: Center(),
], ],
), ),
), ),
@ -539,10 +544,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
], ],
), ),
title: _topHeight < title: _topHeight <
70 + 70 + context.paddingTop
MediaQuery.of(context)
.padding
.top
? Text(widget.podcastLocal.title, ? Text(widget.podcastLocal.title,
maxLines: 1, maxLines: 1,
overflow: overflow:
@ -563,12 +565,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
children: <Widget>[ children: <Widget>[
Material( Material(
color: Colors.transparent, color: Colors.transparent,
child: PopupMenuButton<int>( child: _customPopupMenu(
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(
10)),
elevation: 1,
tooltip: s.homeSubMenuSortBy, tooltip: s.homeSubMenuSortBy,
child: Container( child: Container(
height: 30, height: 30,
@ -581,12 +578,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
children: <Widget>[ children: <Widget>[
Text(s Text(s
.homeSubMenuSortBy), .homeSubMenuSortBy),
Padding( SizedBox(width: 10),
padding: EdgeInsets
.symmetric(
horizontal:
5),
),
Icon( Icon(
_reverse _reverse
? LineIcons ? LineIcons
@ -597,7 +589,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
) )
], ],
)), )),
itemBuilder: (context) => [ itemBuilder: [
PopupMenuItem( PopupMenuItem(
value: 0, value: 0,
child: child:
@ -619,6 +611,124 @@ class _PodcastDetailState extends State<PodcastDetail> {
}, },
), ),
), ),
Material(
color: Colors.transparent,
child: _customPopupMenu(
tooltip: 'Filter',
child: Container(
height: 30,
padding:
EdgeInsets.symmetric(
horizontal: 15),
child: Row(
mainAxisSize:
MainAxisSize.min,
children: <Widget>[
Text('Filter'),
SizedBox(width: 10),
Icon(
LineIcons
.filter_solid,
size: 18,
)
],
),
),
itemBuilder: [
PopupMenuItem(
value: 0,
child: Text('All'),
),
PopupMenuItem(
value: 1,
child: Text(
'Not listened'),
),
PopupMenuItem(
value: 2,
child: Text('Liked'),
),
PopupMenuItem(
value: 3,
child:
Text('Downloaded'),
),
PopupMenuItem(
value: 4,
child: Text('Search'),
),
],
onSelected: (value) {
switch (value) {
case 0:
if (_filter !=
Filter.all)
setState(() {
_hideListened =
false;
_filter =
Filter.all;
});
break;
case 1:
setState(() =>
_hideListened =
true);
break;
case 2:
if (_filter !=
Filter.liked)
setState(() =>
_filter = Filter
.liked);
break;
case 3:
if (_filter !=
Filter.downloaded)
setState(() =>
_filter = Filter
.downloaded);
break;
case 4:
showGeneralDialog(
context: context,
barrierDismissible:
true,
barrierLabel:
MaterialLocalizations
.of(
context)
.modalBarrierDismissLabel,
barrierColor:
Colors
.black54,
transitionDuration:
const Duration(
milliseconds:
200),
pageBuilder: (BuildContext
context,
Animation
animaiton,
Animation
secondaryAnimation) =>
SearchEpisdoe(
onSearch:
(query) {
setState(
() {
_query =
query;
_filter =
Filter.search;
});
},
));
break;
default:
}
}),
),
Spacer(), Spacer(),
Material( Material(
color: Colors.transparent, color: Colors.transparent,
@ -692,6 +802,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
reverse: _reverse, reverse: _reverse,
episodeCount: _episodeCount, episodeCount: _episodeCount,
initNum: _scroll ? 0 : 12, initNum: _scroll ? 0 : 12,
hideListened: _hideListened,
), ),
SliverList( SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
@ -834,3 +945,111 @@ class _AboutPodcastState extends State<AboutPodcast> {
); );
} }
} }
class SearchEpisdoe extends StatefulWidget {
SearchEpisdoe({this.onSearch, Key key}) : super(key: key);
final ValueChanged<String> onSearch;
@override
_SearchEpisodeState createState() => _SearchEpisodeState();
}
class _SearchEpisodeState extends State<SearchEpisdoe> {
TextEditingController _controller;
String _query;
int _error;
@override
void initState() {
super.initState();
_error = 0;
_controller = TextEditingController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.light,
systemNavigationBarColor:
Theme.of(context).brightness == Brightness.light
? Color.fromRGBO(113, 113, 113, 1)
: Color.fromRGBO(5, 5, 5, 1),
),
child: AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
elevation: 1,
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
titlePadding:
const EdgeInsets.only(top: 20, left: 20, right: 20, bottom: 20),
actionsPadding: EdgeInsets.all(0),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(
s.cancel,
style: TextStyle(color: Colors.grey[600]),
),
),
FlatButton(
onPressed: (_query != null && _query != '')
? () {
{
widget.onSearch(_query);
Navigator.of(context).pop();
}
}
: null,
child: Text(s.confirm,
style: TextStyle(color: Theme.of(context).accentColor)),
)
],
title: SizedBox(width: context.width - 160, child: Text(s.newGroup)),
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 10),
hintText: s.newGroup,
hintStyle: TextStyle(fontSize: 18),
filled: true,
focusedBorder: UnderlineInputBorder(
borderSide:
BorderSide(color: context.accentColor, width: 2.0),
),
enabledBorder: UnderlineInputBorder(
borderSide:
BorderSide(color: context.accentColor, width: 2.0),
),
),
cursorRadius: Radius.circular(2),
autofocus: true,
maxLines: 1,
controller: _controller,
onChanged: (value) {
_query = value;
},
),
Container(
alignment: Alignment.centerLeft,
child: (_error == 1)
? Text(
s.groupExisted,
style: TextStyle(color: Colors.red[400]),
)
: Center(),
),
],
),
),
);
}
}

View File

@ -1,6 +1,5 @@
import 'dart:ui'; import 'dart:ui';
import 'package:intl/intl.dart';
import 'package:audio_service/audio_service.dart'; import 'package:audio_service/audio_service.dart';
class EpisodeBrief { class EpisodeBrief {
@ -27,35 +26,18 @@ class EpisodeBrief {
this.pubDate, this.pubDate,
this.feedTitle, this.feedTitle,
this.primaryColor, this.primaryColor,
this.liked,
this.downloaded,
this.duration, this.duration,
this.explicit, this.explicit,
this.imagePath, this.imagePath,
this.mediaId, {this.mediaId,
this.liked,
this.downloaded,
this.isNew, this.isNew,
this.skipSeconds, this.skipSeconds,
{this.description = '', this.description = '',
this.downloadDate = 0}) this.downloadDate = 0})
: assert(enclosureUrl != null); : assert(enclosureUrl != null);
String ateToString() {
DateTime date = DateTime.fromMillisecondsSinceEpoch(pubDate, isUtc: true);
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

@ -34,9 +34,14 @@ class EpisodeGrid extends StatelessWidget {
final int episodeCount; final int episodeCount;
final Layout layout; final Layout layout;
final bool reverse; final bool reverse;
final int initNum; final int initNum;
EpisodeGrid({
Key key, /// Hide listened episode.
final bool hideListened;
EpisodeGrid(
{Key key,
@required this.episodes, @required this.episodes,
this.initNum = 12, this.initNum = 12,
this.showDownload = false, this.showDownload = false,
@ -45,20 +50,8 @@ class EpisodeGrid extends StatelessWidget {
this.episodeCount = 0, this.episodeCount = 0,
this.layout = Layout.three, this.layout = Layout.three,
this.reverse, this.reverse,
}) : super(key: key); this.hideListened = false})
String _dateToString(BuildContext context, {int pubDate}) { : super(key: key);
final s = context.s;
DateTime date = DateTime.fromMillisecondsSinceEpoch(pubDate, isUtc: true);
var difference = DateTime.now().toUtc().difference(date);
if (difference.inHours < 24) {
return s.hoursAgo(difference.inHours);
} else if (difference.inDays < 7) {
return s.daysAgo(difference.inDays);
} else {
return DateFormat.yMMMd().format(
DateTime.fromMillisecondsSinceEpoch(pubDate, isUtc: true).toLocal());
}
}
Future<int> _isListened(EpisodeBrief episode) async { Future<int> _isListened(EpisodeBrief episode) async {
DBHelper dbHelper = DBHelper(); DBHelper dbHelper = DBHelper();
@ -123,6 +116,7 @@ class EpisodeGrid extends StatelessWidget {
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}'; return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
} }
/// Episode title widget.
Widget _title(EpisodeBrief episode) => Container( Widget _title(EpisodeBrief episode) => Container(
alignment: alignment:
layout == Layout.one ? Alignment.centerLeft : Alignment.topLeft, layout == Layout.one ? Alignment.centerLeft : Alignment.topLeft,
@ -135,6 +129,7 @@ class EpisodeGrid extends StatelessWidget {
), ),
); );
/// Circel avatar widget.
Widget _circleImage(BuildContext context, Widget _circleImage(BuildContext context,
{EpisodeBrief episode, Color color, bool boo}) => {EpisodeBrief episode, Color color, bool boo}) =>
Container( Container(
@ -170,6 +165,7 @@ class EpisodeGrid extends StatelessWidget {
: Center() : Center()
: Center(); : Center();
/// New indicator widget.
Widget _isNewIndicator(EpisodeBrief episode) => episode.isNew == 1 Widget _isNewIndicator(EpisodeBrief episode) => episode.isNew == 1
? Container( ? Container(
padding: EdgeInsets.symmetric(horizontal: 2), padding: EdgeInsets.symmetric(horizontal: 2),
@ -178,6 +174,7 @@ class EpisodeGrid extends StatelessWidget {
) )
: Center(); : Center();
/// Count indicator widget.
Widget _numberIndicater(BuildContext context, {int index, Color color}) => Widget _numberIndicater(BuildContext context, {int index, Color color}) =>
showNumber showNumber
? Container( ? Container(
@ -196,9 +193,10 @@ class EpisodeGrid extends StatelessWidget {
) )
: Center(); : Center();
/// Pubdate widget
Widget _pubDate(BuildContext context, {EpisodeBrief episode, Color color}) => Widget _pubDate(BuildContext context, {EpisodeBrief episode, Color color}) =>
Text( Text(
_dateToString(context, pubDate: episode.pubDate), episode.pubDate.toDate(context),
style: TextStyle( style: TextStyle(
fontSize: context.width / 35, fontSize: context.width / 35,
color: color, color: color,
@ -259,7 +257,9 @@ class EpisodeGrid extends StatelessWidget {
bool isDownloaded = snapshot.data.item3; bool isDownloaded = snapshot.data.item3;
bool tapToOpen = snapshot.data.item4; bool tapToOpen = snapshot.data.item4;
List<int> menuList = snapshot.data.item5; List<int> menuList = snapshot.data.item5;
return Container( return (hideListened && isListened > 0)
? Center()
: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(5.0)), BorderRadius.all(Radius.circular(5.0)),
@ -270,7 +270,8 @@ class EpisodeGrid extends StatelessWidget {
: context.scaffoldBackgroundColor, : context.scaffoldBackgroundColor,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: context.brightness == Brightness.light color:
context.brightness == Brightness.light
? context.primaryColor ? context.primaryColor
: Color.fromRGBO(40, 40, 40, 1), : Color.fromRGBO(40, 40, 40, 1),
blurRadius: 0.5, blurRadius: 0.5,
@ -283,7 +284,8 @@ class EpisodeGrid extends StatelessWidget {
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(5.0)), BorderRadius.all(Radius.circular(5.0)),
border: Border.all( border: Border.all(
color: context.brightness == Brightness.light color:
context.brightness == Brightness.light
? context.primaryColor ? context.primaryColor
: context.scaffoldBackgroundColor, : context.scaffoldBackgroundColor,
width: 1.0, width: 1.0,
@ -294,11 +296,12 @@ class EpisodeGrid extends StatelessWidget {
menuItemExtent: 45, menuItemExtent: 45,
menuBoxDecoration: BoxDecoration( menuBoxDecoration: BoxDecoration(
color: Colors.transparent, color: Colors.transparent,
borderRadius: borderRadius: BorderRadius.all(
BorderRadius.all(Radius.circular(15.0))), Radius.circular(15.0))),
duration: Duration(milliseconds: 100), duration: Duration(milliseconds: 100),
tapMode: tapMode: tapToOpen
tapToOpen ? TapMode.onTap : TapMode.onLongPress, ? TapMode.onTap
: TapMode.onLongPress,
animateMenuItems: false, animateMenuItems: false,
blurBackgroundColor: blurBackgroundColor:
context.brightness == Brightness.light context.brightness == Brightness.light
@ -308,11 +311,12 @@ class EpisodeGrid extends StatelessWidget {
menuOffset: 6, menuOffset: 6,
menuItems: <FocusedMenuItem>[ menuItems: <FocusedMenuItem>[
FocusedMenuItem( FocusedMenuItem(
backgroundColor: backgroundColor: context.brightness ==
context.brightness == Brightness.light Brightness.light
? context.primaryColor ? context.primaryColor
: context.scaffoldBackgroundColor, : context.scaffoldBackgroundColor,
title: Text(data.item1 != episodes[index] title: Text(
data.item1 != episodes[index]
? s.play ? s.play
: s.playing), : s.playing),
trailingIcon: Icon( trailingIcon: Icon(
@ -325,12 +329,15 @@ class EpisodeGrid extends StatelessWidget {
}), }),
menuList.contains(1) menuList.contains(1)
? FocusedMenuItem( ? FocusedMenuItem(
backgroundColor: backgroundColor: context
context.brightness == Brightness.light .brightness ==
Brightness.light
? context.primaryColor ? context.primaryColor
: context.scaffoldBackgroundColor, : context
.scaffoldBackgroundColor,
title: data.item2.contains( title: data.item2.contains(
episodes[index].enclosureUrl) episodes[index]
.enclosureUrl)
? Text(s.remove) ? Text(s.remove)
: Text(s.later), : Text(s.later),
trailingIcon: Icon( trailingIcon: Icon(
@ -339,15 +346,17 @@ class EpisodeGrid extends StatelessWidget {
), ),
onPressed: () { onPressed: () {
if (!data.item2.contains( if (!data.item2.contains(
episodes[index].enclosureUrl)) { episodes[index]
audio.addToPlaylist(episodes[index]); .enclosureUrl)) {
audio.addToPlaylist(
episodes[index]);
Fluttertoast.showToast( Fluttertoast.showToast(
msg: s.toastAddPlaylist, msg: s.toastAddPlaylist,
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
); );
} else { } else {
audio audio.delFromPlaylist(
.delFromPlaylist(episodes[index]); episodes[index]);
Fluttertoast.showToast( Fluttertoast.showToast(
msg: s.toastRemovePlaylist, msg: s.toastRemovePlaylist,
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
@ -357,10 +366,12 @@ class EpisodeGrid extends StatelessWidget {
: null, : null,
menuList.contains(2) menuList.contains(2)
? FocusedMenuItem( ? FocusedMenuItem(
backgroundColor: backgroundColor: context
context.brightness == Brightness.light .brightness ==
Brightness.light
? context.primaryColor ? context.primaryColor
: context.scaffoldBackgroundColor, : context
.scaffoldBackgroundColor,
title: isLiked title: isLiked
? Text(s.unlike) ? Text(s.unlike)
: Text(s.like), : Text(s.like),
@ -369,15 +380,16 @@ class EpisodeGrid extends StatelessWidget {
onPressed: () async { onPressed: () async {
if (isLiked) { if (isLiked) {
await _setUnliked( await _setUnliked(
episodes[index].enclosureUrl); episodes[index]
.enclosureUrl);
audio.setEpisodeState = true; audio.setEpisodeState = true;
Fluttertoast.showToast( Fluttertoast.showToast(
msg: s.unliked, msg: s.unliked,
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
); );
} else { } else {
await _saveLiked( await _saveLiked(episodes[index]
episodes[index].enclosureUrl); .enclosureUrl);
audio.setEpisodeState = true; audio.setEpisodeState = true;
Fluttertoast.showToast( Fluttertoast.showToast(
msg: s.liked, msg: s.liked,
@ -388,10 +400,12 @@ class EpisodeGrid extends StatelessWidget {
: null, : null,
menuList.contains(3) menuList.contains(3)
? FocusedMenuItem( ? FocusedMenuItem(
backgroundColor: backgroundColor: context
context.brightness == Brightness.light .brightness ==
Brightness.light
? context.primaryColor ? context.primaryColor
: context.scaffoldBackgroundColor, : context
.scaffoldBackgroundColor,
title: isListened > 0 title: isListened > 0
? Text(s.listened, ? Text(s.listened,
style: TextStyle( style: TextStyle(
@ -400,7 +414,8 @@ class EpisodeGrid extends StatelessWidget {
: Text( : Text(
s.markListened, s.markListened,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow:
TextOverflow.ellipsis,
), ),
trailingIcon: SizedBox( trailingIcon: SizedBox(
width: 23, width: 23,
@ -412,7 +427,8 @@ class EpisodeGrid extends StatelessWidget {
), ),
onPressed: () async { onPressed: () async {
if (isListened < 1) { if (isListened < 1) {
await _markListened(episodes[index]); await _markListened(
episodes[index]);
audio.setEpisodeState = true; audio.setEpisodeState = true;
Fluttertoast.showToast( Fluttertoast.showToast(
msg: s.markListened, msg: s.markListened,
@ -423,10 +439,12 @@ class EpisodeGrid extends StatelessWidget {
: null, : null,
menuList.contains(4) menuList.contains(4)
? FocusedMenuItem( ? FocusedMenuItem(
backgroundColor: backgroundColor: context
context.brightness == Brightness.light .brightness ==
Brightness.light
? context.primaryColor ? context.primaryColor
: context.scaffoldBackgroundColor, : context
.scaffoldBackgroundColor,
title: isDownloaded title: isDownloaded
? Text(s.downloaded, ? Text(s.downloaded,
style: TextStyle( style: TextStyle(
@ -438,7 +456,8 @@ class EpisodeGrid extends StatelessWidget {
color: Colors.green), color: Colors.green),
onPressed: () { onPressed: () {
if (!isDownloaded) if (!isDownloaded)
downloader.startTask(episodes[index]); downloader
.startTask(episodes[index]);
}) })
: null : null
], ],
@ -446,7 +465,8 @@ class EpisodeGrid extends StatelessWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment:
MainAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
flex: layout == Layout.one ? 1 : 2, flex: layout == Layout.one ? 1 : 2,
@ -484,15 +504,16 @@ class EpisodeGrid extends StatelessWidget {
CrossAxisAlignment.center, CrossAxisAlignment.center,
children: [ children: [
_circleImage(context, _circleImage(context,
episode: episodes[index], episode:
episodes[index],
color: _c, color: _c,
boo: boo), boo: boo),
SizedBox( SizedBox(
width: 5, width: 5,
), ),
Expanded( Expanded(
child: child: _title(
_title(episodes[index])) episodes[index]))
], ],
), ),
), ),
@ -504,23 +525,28 @@ class EpisodeGrid extends StatelessWidget {
children: <Widget>[ children: <Widget>[
if (layout != Layout.one) if (layout != Layout.one)
Align( Align(
alignment: Alignment.bottomLeft, alignment:
Alignment.bottomLeft,
child: _pubDate(context, child: _pubDate(context,
episode: episodes[index], episode: episodes[index],
color: _c), color: _c),
), ),
Spacer(), Spacer(),
layout != Layout.three && layout != Layout.three &&
episodes[index].duration != 0 episodes[index]
.duration !=
0
? Container( ? Container(
alignment: Alignment.center, alignment:
Alignment.center,
child: Text( child: Text(
_stringForSeconds( _stringForSeconds(
episodes[index] episodes[index]
.duration .duration
.toDouble()), .toDouble()),
style: TextStyle( style: TextStyle(
fontSize: _width / 35), fontSize:
_width / 35),
), ),
) )
: Center(), : Center(),
@ -549,7 +575,8 @@ class EpisodeGrid extends StatelessWidget {
.enclosureLength != .enclosureLength !=
0 0
? Container( ? Container(
alignment: Alignment.center, alignment:
Alignment.center,
child: Text( child: Text(
((episodes[index] ((episodes[index]
.enclosureLength) ~/ .enclosureLength) ~/
@ -557,18 +584,21 @@ class EpisodeGrid extends StatelessWidget {
.toString() + .toString() +
'MB', 'MB',
style: TextStyle( style: TextStyle(
fontSize: _width / 35), fontSize:
_width / 35),
), ),
) )
: Center(), : Center(),
Padding( Padding(
padding: EdgeInsets.all(1), padding: EdgeInsets.all(1),
), ),
showFavorite || layout != Layout.three showFavorite ||
layout != Layout.three
? isLiked ? isLiked
? IconTheme( ? IconTheme(
data: IconThemeData( data: IconThemeData(
size: _width / 35), size:
_width / 35),
child: Icon( child: Icon(
Icons.favorite, Icons.favorite,
color: Colors.red, color: Colors.red,

View File

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../generated/l10n.dart'; import '../generated/l10n.dart';
@ -14,6 +15,7 @@ extension ContextExtension on BuildContext {
Brightness get brightness => Theme.of(this).brightness; Brightness get brightness => Theme.of(this).brightness;
double get width => MediaQuery.of(this).size.width; double get width => MediaQuery.of(this).size.width;
double get height => MediaQuery.of(this).size.height; double get height => MediaQuery.of(this).size.height;
double get paddingTop => MediaQuery.of(this).padding.top;
TextTheme get textTheme => Theme.of(this).textTheme; TextTheme get textTheme => Theme.of(this).textTheme;
S get s => S.of(this); S get s => S.of(this);
} }
@ -58,6 +60,10 @@ extension StringExtension on String {
await launch(this); await launch(this);
} else { } else {
print('Could not launch $this'); print('Could not launch $this');
Fluttertoast.showToast(
msg: '$this Invalid Link',
gravity: ToastGravity.TOP,
);
} }
} }