From c18368310162f11510ec8d87882938065c4641f5 Mon Sep 17 00:00:00 2001 From: stonegate Date: Fri, 24 Jul 2020 02:42:25 +0800 Subject: [PATCH] Add filter in podcast detail page. --- lib/local_storage/sqflite_localpodcast.dart | 555 +++++++-------- lib/podcasts/podcast_detail.dart | 497 ++++++++++---- lib/type/episodebrief.dart | 26 +- lib/util/episodegrid.dart | 704 ++++++++++---------- lib/util/extension_helper.dart | 6 + 5 files changed, 977 insertions(+), 811 deletions(-) diff --git a/lib/local_storage/sqflite_localpodcast.dart b/lib/local_storage/sqflite_localpodcast.dart index 810f428..cf498f9 100644 --- a/lib/local_storage/sqflite_localpodcast.dart +++ b/lib/local_storage/sqflite_localpodcast.dart @@ -10,6 +10,8 @@ import '../type/episodebrief.dart'; import '../webfeed/webfeed.dart'; import '../type/sub_history.dart'; +enum Filter { downloaded, liked, search, all } + class DBHelper { static Database _db; Future get database async { @@ -491,7 +493,6 @@ class DBHelper { var feed = RssFeed.parse(response.data); String url, description; feed.items.removeWhere((item) => item == null); - int result = feed.items.length; var dbClient = await database; int count = Sqflite.firstIntValue(await dbClient.rawQuery( @@ -556,85 +557,131 @@ class DBHelper { } } - Future> getRssItem(String id, int i, bool reverse) async { + Future> getRssItem(String id, int count, bool reverse, + {Filter filter = Filter.all, String query = ''}) async { var dbClient = await database; List episodes = []; - if (i == -1) { - List 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, 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 + List list = []; + if (count == -1) { + switch (filter) { + case Filter.all: + 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 = ? ORDER BY E.milliseconds ASC""", [id]); - for (var i in list) { - episodes.add(EpisodeBrief( - i['title'], - i['enclosure_url'], - i['enclosure_length'], - i['milliseconds'], - i['feedTitle'], - i['primaryColor'], - i['liked'], - i['downloaded'], - i['duration'], - i['explicit'], - i['imagePath'], - i['media_id'], - i['is_new'], - i['skip_seconds'])); + 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 ASC""", [id]); + 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.media_id != E.enclosure_url ORDER BY E.milliseconds ASC""", + [id]); + 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) { - List 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, 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 P.id = ? ORDER BY E.milliseconds ASC LIMIT ?""", [id, i]); - for (var i in list) { - episodes.add(EpisodeBrief( - i['title'], - i['enclosure_url'], - i['enclosure_length'], - i['milliseconds'], - i['feedTitle'], - i['primaryColor'], - i['liked'], - i['downloaded'], - i['duration'], - i['explicit'], - i['imagePath'], - i['media_id'], - i['is_new'], - i['skip_seconds'])); + switch (filter) { + case Filter.all: + 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 = ? ORDER BY E.milliseconds ASC LIMIT ?""", [id, count]); + 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 ASC 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 ASC 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 ASC LIMIT ?""", + [id, '%$query%', count]); + break; + default: } - return episodes; } else { - List 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, 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 P.id = ? ORDER BY E.milliseconds DESC LIMIT ?""", [id, i]); + switch (filter) { + case Filter.all: + 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 = ? ORDER BY E.milliseconds DESC LIMIT ?""", [id, count]); + 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) { episodes.add(EpisodeBrief( - i['title'], - i['enclosure_url'], - i['enclosure_length'], - i['milliseconds'], - i['feedTitle'], - i['primaryColor'], - i['liked'], - i['downloaded'], - i['duration'], - i['explicit'], - i['imagePath'], - i['media_id'], - i['is_new'], - i['skip_seconds'])); + i['title'], + i['enclosure_url'], + i['enclosure_length'], + i['milliseconds'], + i['feedTitle'], + i['primaryColor'], + i['duration'], + i['explicit'], + i['imagePath'], + )); } - return episodes; - } + return episodes; } Future> getNewEpisodes(String id) async { @@ -644,36 +691,30 @@ class DBHelper { if (id == 'all') 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 + E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit, + P.primaryColor 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""", ); else 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 + E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit, + P.primaryColor 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""", [id]); if (list.isNotEmpty) for (var i in list) { episodes.add(EpisodeBrief( - i['title'], - i['enclosure_url'], - i['enclosure_length'], - i['milliseconds'], - i['feed_title'], - i['primaryColor'], - i['liked'], - i['downloaded'], - i['duration'], - i['explicit'], - i['imagePath'], - i['media_id'], - i['is_new'], - i['skip_seconds'])); + i['title'], + i['enclosure_url'], + i['enclosure_length'], + i['milliseconds'], + i['feed_title'], + i['primaryColor'], + i['duration'], + i['explicit'], + i['imagePath'], + )); } return episodes; } @@ -683,85 +724,45 @@ class DBHelper { List episodes = []; List 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 + E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit, + P.primaryColor 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]); for (var i in list) { episodes.add(EpisodeBrief( - i['title'], - i['enclosure_url'], - i['enclosure_length'], - i['milliseconds'], - i['feed_title'], - i['primaryColor'], - i['liked'], - i['downloaded'], - i['duration'], - i['explicit'], - i['imagePath'], - i['media_id'], - i['is_new'], - i['skip_seconds'])); + i['title'], + i['enclosure_url'], + i['enclosure_length'], + i['milliseconds'], + i['feed_title'], + i['primaryColor'], + i['duration'], + i['explicit'], + i['imagePath'], + )); } return episodes; } - //Future getRssItemDownload(String url) async { - // var dbClient = await database; - // EpisodeBrief episode; - // List 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> getRecentRssItem(int top) async { var dbClient = await database; List episodes = []; List 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 + E.milliseconds, P.title as feed_title, E.duration, E.explicit, + P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id ORDER BY E.milliseconds DESC LIMIT ? """, [top]); for (var i in list) { episodes.add(EpisodeBrief( - i['title'], - i['enclosure_url'], - i['enclosure_length'], - i['milliseconds'], - i['feed_title'], - i['primaryColor'], - i['liked'], - i['downloaded'], - i['duration'], - i['explicit'], - i['imagePath'], - i['media_id'], - i['is_new'], - i['skip_seconds'])); + i['title'], + i['enclosure_url'], + i['enclosure_length'], + i['milliseconds'], + i['feed_title'], + i['primaryColor'], + i['duration'], + i['explicit'], + i['imagePath'], + )); } return episodes; } @@ -774,27 +775,22 @@ class DBHelper { List s = group.map((e) => "'$e'").toList(); List 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 + E.milliseconds, P.title as feed_title, E.duration, E.explicit, + P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id WHERE P.id in (${s.join(',')}) ORDER BY E.milliseconds DESC LIMIT ? """, [top]); for (var i in list) { episodes.add(EpisodeBrief( - i['title'], - i['enclosure_url'], - i['enclosure_length'], - i['milliseconds'], - i['feed_title'], - i['primaryColor'], - i['liked'], - i['downloaded'], - i['duration'], - i['explicit'], - i['imagePath'], - i['media_id'], - i['is_new'], - i['skip_seconds'])); + i['title'], + i['enclosure_url'], + i['enclosure_length'], + i['milliseconds'], + i['feed_title'], + i['primaryColor'], + i['duration'], + i['explicit'], + i['imagePath'], + )); } } return episodes; @@ -805,87 +801,47 @@ class DBHelper { List episodes = []; List 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 + E.milliseconds, P.title as feed_title, E.duration, E.explicit, + P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id WHERE is_new = 1 ORDER BY E.milliseconds DESC """, ); for (var i in list) { episodes.add(EpisodeBrief( - i['title'], - i['enclosure_url'], - i['enclosure_length'], - i['milliseconds'], - i['feed_title'], - i['primaryColor'], - i['liked'], - i['downloaded'], - i['duration'], - i['explicit'], - i['imagePath'], - i['media_id'], - i['is_new'], - i['skip_seconds'])); + i['title'], + i['enclosure_url'], + i['enclosure_length'], + i['milliseconds'], + i['feed_title'], + i['primaryColor'], + i['duration'], + i['explicit'], + i['imagePath'], + )); } return episodes; } - //Future> getNewRssItem(String id) async { - // var dbClient = await database; - // List episodes = []; - // List 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> getOutdatedEpisode(int deadline) async { var dbClient = await database; List episodes = []; List 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 + E.milliseconds, P.title as feed_title, E.duration, E.explicit, + P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id WHERE E.download_date < ? AND E.enclosure_url != E.media_id ORDER BY E.milliseconds DESC""", [deadline]); for (var i in list) { episodes.add(EpisodeBrief( - i['title'], - i['enclosure_url'], - i['enclosure_length'], - i['milliseconds'], - i['feed_title'], - i['primaryColor'], - i['liked'], - i['downloaded'], - i['duration'], - i['explicit'], - i['imagePath'], - i['media_id'], - i['is_new'], - i['skip_seconds'])); + i['title'], + i['enclosure_url'], + i['enclosure_length'], + i['milliseconds'], + i['feed_title'], + i['primaryColor'], + i['duration'], + i['explicit'], + i['imagePath'], + )); } return episodes; } @@ -897,10 +853,9 @@ class DBHelper { //Ordered by date 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 + """SELECT E.title, E.enclosure_url, E.enclosure_length, E.download_date, + E.milliseconds, P.title as feed_title, E.duration, E.explicit, + P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id WHERE E.enclosure_url != E.media_id ORDER BY E.download_date DESC""", ); @@ -908,9 +863,8 @@ class DBHelper { 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 + E.milliseconds, P.title as feed_title, E.duration, E.explicit, + P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id WHERE E.enclosure_url != E.media_id ORDER BY E.download_date ASC""", ); @@ -918,9 +872,8 @@ class DBHelper { 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 + E.milliseconds, P.title as feed_title, E.duration, E.explicit, + P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id WHERE E.enclosure_url != E.media_id ORDER BY E.enclosure_length DESC""", ); @@ -933,14 +886,9 @@ class DBHelper { i['milliseconds'], i['feed_title'], i['primaryColor'], - i['liked'], - i['downloaded'], i['duration'], i['explicit'], i['imagePath'], - i['media_id'], - i['is_new'], - i['skip_seconds'], downloadDate: i['download_date']), ); } @@ -961,28 +909,23 @@ class DBHelper { List s = group.map((e) => "'$e'").toList(); List 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 + E.milliseconds, P.title as feed_title, E.duration, E.explicit, + P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id WHERE P.id in (${s.join(',')}) AND is_new = 1 ORDER BY E.milliseconds DESC""", ); for (var i in list) { episodes.add(EpisodeBrief( - i['title'], - i['enclosure_url'], - i['enclosure_length'], - i['milliseconds'], - i['feed_title'], - i['primaryColor'], - i['liked'], - i['downloaded'], - i['duration'], - i['explicit'], - i['imagePath'], - i['media_id'], - i['is_new'], - i['skip_seconds'])); + i['title'], + i['enclosure_url'], + i['enclosure_length'], + i['milliseconds'], + i['feed_title'], + i['primaryColor'], + i['duration'], + i['explicit'], + i['imagePath'], + )); } } return episodes; @@ -1014,48 +957,40 @@ class DBHelper { if (sortBy == 0) { List 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 + P.title as feed_title, E.duration, E.explicit, P.primaryColor + FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id WHERE E.liked = 1 ORDER BY E.milliseconds DESC LIMIT ?""", [i]); for (var i in list) { episodes.add(EpisodeBrief( - i['title'], - i['enclosure_url'], - i['enclosure_length'], - i['milliseconds'], - i['feed_title'], - i['primaryColor'], - i['liked'], - i['downloaded'], - i['duration'], - i['explicit'], - i['imagePath'], - i['media_id'], - i['is_new'], - i['skip_seconds'])); + i['title'], + i['enclosure_url'], + i['enclosure_length'], + i['milliseconds'], + i['feed_title'], + i['primaryColor'], + i['duration'], + i['explicit'], + i['imagePath'], + )); } } else { List 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 + P.title as feed_title, E.duration, E.explicit, P.primaryColor + FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id WHERE E.liked = 1 ORDER BY E.liked_date DESC LIMIT ?""", [i]); for (var i in list) { episodes.add(EpisodeBrief( - i['title'], - i['enclosure_url'], - i['enclosure_length'], - i['milliseconds'], - i['feed_title'], - i['primaryColor'], - i['liked'], - i['downloaded'], - i['duration'], - i['explicit'], - i['imagePath'], - i['media_id'], - i['is_new'], - i['skip_seconds'])); + i['title'], + i['enclosure_url'], + i['enclosure_length'], + i['milliseconds'], + i['feed_title'], + i['primaryColor'], + i['duration'], + i['explicit'], + i['imagePath'], + )); } } return episodes; @@ -1150,8 +1085,8 @@ class DBHelper { EpisodeBrief episode; List 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 + P.title as feed_title, E.duration, E.explicit, P.skip_seconds, + P.primaryColor, E.media_id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id WHERE E.enclosure_url = ?""", [url]); if (list.isEmpty) { return null; @@ -1163,14 +1098,11 @@ class DBHelper { 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']); + mediaId: list.first['media_id'], + skipSeconds: list.first['skip_seconds']); return episode; } } @@ -1180,8 +1112,8 @@ class DBHelper { EpisodeBrief episode; List 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 + P.title as feed_title, E.duration, E.explicit, P.skip_seconds, + P.primaryColor, E.media_id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id WHERE E.media_id = ?""", [id]); if (list.isEmpty) return null; @@ -1193,14 +1125,11 @@ class DBHelper { 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']); + mediaId: list.first['media_id'], + skipSeconds: list.first['skip_seconds']); return episode; } } diff --git a/lib/podcasts/podcast_detail.dart b/lib/podcasts/podcast_detail.dart index 637310d..d525c4d 100644 --- a/lib/podcasts/podcast_detail.dart +++ b/lib/podcasts/podcast_detail.dart @@ -52,7 +52,46 @@ class _PodcastDetailState extends State { /// Default 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 { var dbHelper = DBHelper(); final result = await dbHelper.updatePodcastRss(podcastLocal); @@ -99,15 +138,16 @@ class _PodcastDetailState extends State { if (mounted) setState(() {}); } - Future> _getRssItem( - PodcastLocal podcastLocal, int i, bool reverse) async { + Future> _getRssItem(PodcastLocal podcastLocal, + {int count, bool reverse, Filter filter, String query}) async { var dbHelper = DBHelper(); + List episodes = []; _episodeCount = await dbHelper.getPodcastCounts(podcastLocal.id); KeyValueStorage storage = KeyValueStorage(podcastLayoutKey); int index = await storage.getInt(defaultValue: 1); if (_layout == null) _layout = Layout.values[index]; - List episodes = - await dbHelper.getRssItem(podcastLocal.id, i, reverse); + episodes = await dbHelper.getRssItem(podcastLocal.id, count, reverse, + filter: filter, query: query); if (podcastLocal.provider.contains('fireside')) { FiresideData data = FiresideData(podcastLocal.id, podcastLocal.link); await data.getData(); @@ -117,17 +157,6 @@ class _PodcastDetailState extends State { 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 { DBHelper dbHelper = DBHelper(); List episodes = @@ -234,6 +263,24 @@ class _PodcastDetailState extends State { ); } + _customPopupMenu( + {Widget child, + String tooltip, + List> itemBuilder, + Function(int) onSelected}) => + Material( + color: Colors.transparent, + child: PopupMenuButton( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + elevation: 1, + tooltip: tooltip, + child: child, + itemBuilder: (context) => itemBuilder, + onSelected: (value) => onSelected(value), + ), + ); + _confirmMarkListened(BuildContext context) => generalDialog( context, title: Text(context.s.markConfirm), @@ -261,34 +308,6 @@ class _PodcastDetailState extends State { ], ); - 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 Widget build(BuildContext context) { Color _color = widget.podcastLocal.primaryColor.colorizedark(); @@ -321,8 +340,11 @@ class _PodcastDetailState extends State { children: [ Expanded( child: FutureBuilder>( - future: - _getRssItem(widget.podcastLocal, _top, _reverse), + future: _getRssItem(widget.podcastLocal, + count: _top, + reverse: _reverse, + filter: _filter, + query: _query), builder: (context, snapshot) { if (snapshot.hasError) print(snapshot.error); return (snapshot.hasData) @@ -339,16 +361,14 @@ class _PodcastDetailState extends State { Duration(seconds: 3)); if (mounted) setState(() { - _top = _top + 33; + _top = _top + 36; _loadMore = false; }); } if (_controller.offset > 0 && mounted && !_scroll) - setState(() { - _scroll = true; - }); + setState(() => _scroll = true); }), physics: const AlwaysScrollableScrollPhysics(), @@ -356,38 +376,47 @@ class _PodcastDetailState extends State { SliverAppBar( brightness: Brightness.dark, actions: [ - PopupMenuButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(10))), - elevation: 2, + _customPopupMenu( tooltip: s.menu, - itemBuilder: (context) => [ - widget.podcastLocal.link != null - ? PopupMenuItem( - value: 0, - child: Container( - padding: EdgeInsets.only( - left: 10), - child: Row( - children: [ - Icon(Icons.link, - color: Theme.of( - context) - .tabBarTheme - .labelColor), - Padding( - padding: EdgeInsets - .symmetric( - horizontal: - 5.0), - ), - Text(s.menuVisitSite), - ], + onSelected: (int value) { + switch (value) { + 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, + child: Container( + padding: + EdgeInsets.only(left: 10), + child: Row( + children: [ + Icon(Icons.link, + color: context + .textColor), + Padding( + padding: EdgeInsets + .symmetric( + horizontal: + 5.0), ), - ), - ) - : Center(), + Text(s.menuVisitSite), + ], + ), + ), + ), PopupMenuItem( value: 1, child: Container( @@ -397,9 +426,7 @@ class _PodcastDetailState extends State { children: [ Icon( Icons.rss_feed, - color: Theme.of(context) - .tabBarTheme - .labelColor, + color: context.textColor, ), Padding( padding: @@ -427,9 +454,7 @@ class _PodcastDetailState extends State { painter: ListenedAllPainter( context - .textTheme - .bodyText1 - .color, + .textColor, stroke: 2)), ), Padding( @@ -445,35 +470,18 @@ class _PodcastDetailState extends State { ), ), ], - 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, iconTheme: IconThemeData( color: Colors.white, ), - expandedHeight: 150 + - MediaQuery.of(context).padding.top, + expandedHeight: 150 + context.paddingTop, backgroundColor: _color, floating: true, pinned: true, - flexibleSpace: LayoutBuilder(builder: - (BuildContext context, - BoxConstraints constraints) { + flexibleSpace: LayoutBuilder( + builder: (context, constraints) { _topHeight = constraints.biggest.height; return FlexibleSpaceBar( background: Stack( @@ -481,9 +489,7 @@ class _PodcastDetailState extends State { Container( margin: EdgeInsets.only( top: 120 + - MediaQuery.of(context) - .padding - .top), + context.paddingTop), padding: EdgeInsets.only( left: 80, right: 120), color: Colors.white10, @@ -506,18 +512,17 @@ class _PodcastDetailState extends State { style: TextStyle( color: Colors.white)), - widget.podcastLocal.provider - .isNotEmpty - ? Text( - s.hostedOn(widget - .podcastLocal - .provider), - maxLines: 1, - style: TextStyle( - color: Colors - .white), - ) - : Center(), + if (widget.podcastLocal + .provider.isNotEmpty) + Text( + s.hostedOn(widget + .podcastLocal + .provider), + maxLines: 1, + style: TextStyle( + color: + Colors.white), + ), ], ), ), @@ -539,10 +544,7 @@ class _PodcastDetailState extends State { ], ), title: _topHeight < - 70 + - MediaQuery.of(context) - .padding - .top + 70 + context.paddingTop ? Text(widget.podcastLocal.title, maxLines: 1, overflow: @@ -563,12 +565,7 @@ class _PodcastDetailState extends State { children: [ Material( color: Colors.transparent, - child: PopupMenuButton( - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular( - 10)), - elevation: 1, + child: _customPopupMenu( tooltip: s.homeSubMenuSortBy, child: Container( height: 30, @@ -581,12 +578,7 @@ class _PodcastDetailState extends State { children: [ Text(s .homeSubMenuSortBy), - Padding( - padding: EdgeInsets - .symmetric( - horizontal: - 5), - ), + SizedBox(width: 10), Icon( _reverse ? LineIcons @@ -597,7 +589,7 @@ class _PodcastDetailState extends State { ) ], )), - itemBuilder: (context) => [ + itemBuilder: [ PopupMenuItem( value: 0, child: @@ -619,6 +611,124 @@ class _PodcastDetailState extends State { }, ), ), + Material( + color: Colors.transparent, + child: _customPopupMenu( + tooltip: 'Filter', + child: Container( + height: 30, + padding: + EdgeInsets.symmetric( + horizontal: 15), + child: Row( + mainAxisSize: + MainAxisSize.min, + children: [ + 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(), Material( color: Colors.transparent, @@ -692,6 +802,7 @@ class _PodcastDetailState extends State { reverse: _reverse, episodeCount: _episodeCount, initNum: _scroll ? 0 : 12, + hideListened: _hideListened, ), SliverList( delegate: SliverChildBuilderDelegate( @@ -834,3 +945,111 @@ class _AboutPodcastState extends State { ); } } + +class SearchEpisdoe extends StatefulWidget { + SearchEpisdoe({this.onSearch, Key key}) : super(key: key); + final ValueChanged onSearch; + @override + _SearchEpisodeState createState() => _SearchEpisodeState(); +} + +class _SearchEpisodeState extends State { + 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( + 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: [ + 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: [ + 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(), + ), + ], + ), + ), + ); + } +} diff --git a/lib/type/episodebrief.dart b/lib/type/episodebrief.dart index 6293e3b..721274c 100644 --- a/lib/type/episodebrief.dart +++ b/lib/type/episodebrief.dart @@ -1,6 +1,5 @@ import 'dart:ui'; -import 'package:intl/intl.dart'; import 'package:audio_service/audio_service.dart'; class EpisodeBrief { @@ -27,35 +26,18 @@ class EpisodeBrief { this.pubDate, this.feedTitle, this.primaryColor, - this.liked, - this.downloaded, this.duration, this.explicit, this.imagePath, - this.mediaId, + {this.mediaId, + this.liked, + this.downloaded, this.isNew, this.skipSeconds, - {this.description = '', + this.description = '', this.downloadDate = 0}) : 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() { return MediaItem( id: mediaId, diff --git a/lib/util/episodegrid.dart b/lib/util/episodegrid.dart index 1540af4..6b04fed 100644 --- a/lib/util/episodegrid.dart +++ b/lib/util/episodegrid.dart @@ -34,31 +34,24 @@ class EpisodeGrid extends StatelessWidget { final int episodeCount; final Layout layout; final bool reverse; + final int initNum; - EpisodeGrid({ - Key key, - @required this.episodes, - this.initNum = 12, - this.showDownload = false, - this.showFavorite = false, - this.showNumber = false, - this.episodeCount = 0, - this.layout = Layout.three, - this.reverse, - }) : super(key: key); - String _dateToString(BuildContext context, {int pubDate}) { - 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()); - } - } + + /// Hide listened episode. + final bool hideListened; + + EpisodeGrid( + {Key key, + @required this.episodes, + this.initNum = 12, + this.showDownload = false, + this.showFavorite = false, + this.showNumber = false, + this.episodeCount = 0, + this.layout = Layout.three, + this.reverse, + this.hideListened = false}) + : super(key: key); Future _isListened(EpisodeBrief episode) async { DBHelper dbHelper = DBHelper(); @@ -123,6 +116,7 @@ class EpisodeGrid extends StatelessWidget { return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}'; } + /// Episode title widget. Widget _title(EpisodeBrief episode) => Container( alignment: layout == Layout.one ? Alignment.centerLeft : Alignment.topLeft, @@ -135,6 +129,7 @@ class EpisodeGrid extends StatelessWidget { ), ); + /// Circel avatar widget. Widget _circleImage(BuildContext context, {EpisodeBrief episode, Color color, bool boo}) => Container( @@ -170,6 +165,7 @@ class EpisodeGrid extends StatelessWidget { : Center() : Center(); + /// New indicator widget. Widget _isNewIndicator(EpisodeBrief episode) => episode.isNew == 1 ? Container( padding: EdgeInsets.symmetric(horizontal: 2), @@ -178,6 +174,7 @@ class EpisodeGrid extends StatelessWidget { ) : Center(); + /// Count indicator widget. Widget _numberIndicater(BuildContext context, {int index, Color color}) => showNumber ? Container( @@ -196,9 +193,10 @@ class EpisodeGrid extends StatelessWidget { ) : Center(); + /// Pubdate widget Widget _pubDate(BuildContext context, {EpisodeBrief episode, Color color}) => Text( - _dateToString(context, pubDate: episode.pubDate), + episode.pubDate.toDate(context), style: TextStyle( fontSize: context.width / 35, color: color, @@ -259,332 +257,364 @@ class EpisodeGrid extends StatelessWidget { bool isDownloaded = snapshot.data.item3; bool tapToOpen = snapshot.data.item4; List menuList = snapshot.data.item5; - return Container( - decoration: BoxDecoration( - borderRadius: - BorderRadius.all(Radius.circular(5.0)), - color: isListened > 0 - ? context.brightness == Brightness.light - ? Colors.grey[200] - : Color.fromRGBO(40, 40, 40, 1) - : context.scaffoldBackgroundColor, - boxShadow: [ - BoxShadow( - color: context.brightness == Brightness.light - ? context.primaryColor - : Color.fromRGBO(40, 40, 40, 1), - blurRadius: 0.5, - spreadRadius: 0.5, - ), - ]), - alignment: Alignment.center, - child: Container( - decoration: BoxDecoration( - borderRadius: - BorderRadius.all(Radius.circular(5.0)), - border: Border.all( - color: context.brightness == Brightness.light - ? context.primaryColor - : context.scaffoldBackgroundColor, - width: 1.0, - ), - ), - child: FocusedMenuHolder( - blurSize: 0.0, - menuItemExtent: 45, - menuBoxDecoration: BoxDecoration( - color: Colors.transparent, - borderRadius: - BorderRadius.all(Radius.circular(15.0))), - duration: Duration(milliseconds: 100), - tapMode: - tapToOpen ? TapMode.onTap : TapMode.onLongPress, - animateMenuItems: false, - blurBackgroundColor: - context.brightness == Brightness.light - ? Colors.white38 - : Colors.black38, - bottomOffsetHeight: 10, - menuOffset: 6, - menuItems: [ - FocusedMenuItem( - backgroundColor: - context.brightness == Brightness.light - ? context.primaryColor - : context.scaffoldBackgroundColor, - title: Text(data.item1 != episodes[index] - ? s.play - : s.playing), - trailingIcon: Icon( - LineIcons.play_circle_solid, - color: Theme.of(context).accentColor, + return (hideListened && isListened > 0) + ? Center() + : Container( + decoration: BoxDecoration( + borderRadius: + BorderRadius.all(Radius.circular(5.0)), + color: isListened > 0 + ? context.brightness == Brightness.light + ? Colors.grey[200] + : Color.fromRGBO(40, 40, 40, 1) + : context.scaffoldBackgroundColor, + boxShadow: [ + BoxShadow( + color: + context.brightness == Brightness.light + ? context.primaryColor + : Color.fromRGBO(40, 40, 40, 1), + blurRadius: 0.5, + spreadRadius: 0.5, + ), + ]), + alignment: Alignment.center, + child: Container( + decoration: BoxDecoration( + borderRadius: + BorderRadius.all(Radius.circular(5.0)), + border: Border.all( + color: + context.brightness == Brightness.light + ? context.primaryColor + : context.scaffoldBackgroundColor, + width: 1.0, ), - onPressed: () { - if (data.item1 != episodes[index]) - audio.episodeLoad(episodes[index]); - }), - menuList.contains(1) - ? FocusedMenuItem( - backgroundColor: - context.brightness == Brightness.light - ? context.primaryColor - : context.scaffoldBackgroundColor, - title: data.item2.contains( - episodes[index].enclosureUrl) - ? Text(s.remove) - : Text(s.later), - trailingIcon: Icon( - LineIcons.clock_solid, - color: Colors.cyan, - ), - onPressed: () { - if (!data.item2.contains( - episodes[index].enclosureUrl)) { - audio.addToPlaylist(episodes[index]); - Fluttertoast.showToast( - msg: s.toastAddPlaylist, - gravity: ToastGravity.BOTTOM, - ); - } else { - audio - .delFromPlaylist(episodes[index]); - Fluttertoast.showToast( - msg: s.toastRemovePlaylist, - gravity: ToastGravity.BOTTOM, - ); - } - }) - : null, - menuList.contains(2) - ? FocusedMenuItem( - backgroundColor: - context.brightness == Brightness.light - ? context.primaryColor - : context.scaffoldBackgroundColor, - title: isLiked - ? Text(s.unlike) - : Text(s.like), - trailingIcon: Icon(LineIcons.heart, - color: Colors.red, size: 21), - onPressed: () async { - if (isLiked) { - await _setUnliked( - episodes[index].enclosureUrl); - audio.setEpisodeState = true; - Fluttertoast.showToast( - msg: s.unliked, - gravity: ToastGravity.BOTTOM, - ); - } else { - await _saveLiked( - episodes[index].enclosureUrl); - audio.setEpisodeState = true; - Fluttertoast.showToast( - msg: s.liked, - gravity: ToastGravity.BOTTOM, - ); - } - }) - : null, - menuList.contains(3) - ? FocusedMenuItem( - backgroundColor: - context.brightness == Brightness.light - ? context.primaryColor - : context.scaffoldBackgroundColor, - title: isListened > 0 - ? Text(s.listened, - style: TextStyle( - color: context.textColor - .withOpacity(0.5))) - : Text( - s.markListened, - maxLines: 1, - overflow: TextOverflow.ellipsis, + ), + child: FocusedMenuHolder( + blurSize: 0.0, + menuItemExtent: 45, + menuBoxDecoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.all( + Radius.circular(15.0))), + duration: Duration(milliseconds: 100), + tapMode: tapToOpen + ? TapMode.onTap + : TapMode.onLongPress, + animateMenuItems: false, + blurBackgroundColor: + context.brightness == Brightness.light + ? Colors.white38 + : Colors.black38, + bottomOffsetHeight: 10, + menuOffset: 6, + menuItems: [ + FocusedMenuItem( + backgroundColor: context.brightness == + Brightness.light + ? context.primaryColor + : context.scaffoldBackgroundColor, + title: Text( + data.item1 != episodes[index] + ? s.play + : s.playing), + trailingIcon: Icon( + LineIcons.play_circle_solid, + color: Theme.of(context).accentColor, + ), + onPressed: () { + if (data.item1 != episodes[index]) + audio.episodeLoad(episodes[index]); + }), + menuList.contains(1) + ? FocusedMenuItem( + backgroundColor: context + .brightness == + Brightness.light + ? context.primaryColor + : context + .scaffoldBackgroundColor, + title: data.item2.contains( + episodes[index] + .enclosureUrl) + ? Text(s.remove) + : Text(s.later), + trailingIcon: Icon( + LineIcons.clock_solid, + color: Colors.cyan, ), - trailingIcon: SizedBox( - width: 23, - height: 23, - child: CustomPaint( - painter: ListenedAllPainter( - Colors.blue, - stroke: 1.5)), - ), - onPressed: () async { - if (isListened < 1) { - await _markListened(episodes[index]); - audio.setEpisodeState = true; - Fluttertoast.showToast( - msg: s.markListened, - gravity: ToastGravity.BOTTOM, - ); - } - }) - : null, - menuList.contains(4) - ? FocusedMenuItem( - backgroundColor: - context.brightness == Brightness.light - ? context.primaryColor - : context.scaffoldBackgroundColor, - title: isDownloaded - ? Text(s.downloaded, - style: TextStyle( - color: context.textColor - .withOpacity(0.5))) - : Text(s.download), - trailingIcon: Icon( - LineIcons.download_solid, - color: Colors.green), - onPressed: () { - if (!isDownloaded) - downloader.startTask(episodes[index]); - }) - : null - ], - action: action, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Expanded( - flex: layout == Layout.one ? 1 : 2, - child: Row( + onPressed: () { + if (!data.item2.contains( + episodes[index] + .enclosureUrl)) { + audio.addToPlaylist( + episodes[index]); + Fluttertoast.showToast( + msg: s.toastAddPlaylist, + gravity: ToastGravity.BOTTOM, + ); + } else { + audio.delFromPlaylist( + episodes[index]); + Fluttertoast.showToast( + msg: s.toastRemovePlaylist, + gravity: ToastGravity.BOTTOM, + ); + } + }) + : null, + menuList.contains(2) + ? FocusedMenuItem( + backgroundColor: context + .brightness == + Brightness.light + ? context.primaryColor + : context + .scaffoldBackgroundColor, + title: isLiked + ? Text(s.unlike) + : Text(s.like), + trailingIcon: Icon(LineIcons.heart, + color: Colors.red, size: 21), + onPressed: () async { + if (isLiked) { + await _setUnliked( + episodes[index] + .enclosureUrl); + audio.setEpisodeState = true; + Fluttertoast.showToast( + msg: s.unliked, + gravity: ToastGravity.BOTTOM, + ); + } else { + await _saveLiked(episodes[index] + .enclosureUrl); + audio.setEpisodeState = true; + Fluttertoast.showToast( + msg: s.liked, + gravity: ToastGravity.BOTTOM, + ); + } + }) + : null, + menuList.contains(3) + ? FocusedMenuItem( + backgroundColor: context + .brightness == + Brightness.light + ? context.primaryColor + : context + .scaffoldBackgroundColor, + title: isListened > 0 + ? Text(s.listened, + style: TextStyle( + color: context.textColor + .withOpacity(0.5))) + : Text( + s.markListened, + maxLines: 1, + overflow: + TextOverflow.ellipsis, + ), + trailingIcon: SizedBox( + width: 23, + height: 23, + child: CustomPaint( + painter: ListenedAllPainter( + Colors.blue, + stroke: 1.5)), + ), + onPressed: () async { + if (isListened < 1) { + await _markListened( + episodes[index]); + audio.setEpisodeState = true; + Fluttertoast.showToast( + msg: s.markListened, + gravity: ToastGravity.BOTTOM, + ); + } + }) + : null, + menuList.contains(4) + ? FocusedMenuItem( + backgroundColor: context + .brightness == + Brightness.light + ? context.primaryColor + : context + .scaffoldBackgroundColor, + title: isDownloaded + ? Text(s.downloaded, + style: TextStyle( + color: context.textColor + .withOpacity(0.5))) + : Text(s.download), + trailingIcon: Icon( + LineIcons.download_solid, + color: Colors.green), + onPressed: () { + if (!isDownloaded) + downloader + .startTask(episodes[index]); + }) + : null + ], + action: action, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ - layout != Layout.one - ? _circleImage(context, - episode: episodes[index], - color: _c, - boo: boo) - : _pubDate(context, - episode: episodes[index], - color: _c), - Spacer(), - // _listenIndicater(context, - // episode: episodes[index], - // isListened: snapshot.data), - _isNewIndicator(episodes[index]), - _downloadIndicater(context, - episode: episodes[index], - isDownloaded: isDownloaded), - _numberIndicater(context, - index: index, color: _c) - ], - ), - ), - Expanded( - flex: layout == Layout.one ? 3 : 5, - child: layout != Layout.one - ? _title(episodes[index]) - : Row( - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - _circleImage(context, + Expanded( + flex: layout == Layout.one ? 1 : 2, + child: Row( + mainAxisAlignment: + MainAxisAlignment.start, + children: [ + layout != Layout.one + ? _circleImage(context, + episode: episodes[index], + color: _c, + boo: boo) + : _pubDate(context, + episode: episodes[index], + color: _c), + Spacer(), + // _listenIndicater(context, + // episode: episodes[index], + // isListened: snapshot.data), + _isNewIndicator(episodes[index]), + _downloadIndicater(context, episode: episodes[index], - color: _c, - boo: boo), - SizedBox( - width: 5, - ), - Expanded( - child: - _title(episodes[index])) + isDownloaded: isDownloaded), + _numberIndicater(context, + index: index, color: _c) ], ), - ), - Expanded( - flex: 1, - child: Row( - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - if (layout != Layout.one) - Align( - alignment: Alignment.bottomLeft, - child: _pubDate(context, - episode: episodes[index], - color: _c), - ), - Spacer(), - layout != Layout.three && - episodes[index].duration != 0 - ? Container( - alignment: Alignment.center, - child: Text( - _stringForSeconds( - episodes[index] - .duration - .toDouble()), - style: TextStyle( - fontSize: _width / 35), - ), - ) - : Center(), - episodes[index].duration == 0 || - episodes[index] - .enclosureLength == - null || - episodes[index] - .enclosureLength == - 0 || - layout == Layout.three - ? Center() - : Text( - '|', - style: TextStyle( - fontSize: _width / 35, - // color: _c, - // fontStyle: FontStyle.italic, - ), - ), - layout != Layout.three && - episodes[index] - .enclosureLength != - null && - episodes[index] - .enclosureLength != - 0 - ? Container( - alignment: Alignment.center, - child: Text( - ((episodes[index] - .enclosureLength) ~/ - 1000000) - .toString() + - 'MB', - style: TextStyle( - fontSize: _width / 35), - ), - ) - : Center(), - Padding( - padding: EdgeInsets.all(1), ), - showFavorite || layout != Layout.three - ? isLiked - ? IconTheme( - data: IconThemeData( - size: _width / 35), - child: Icon( - Icons.favorite, - color: Colors.red, + Expanded( + flex: layout == Layout.one ? 3 : 5, + child: layout != Layout.one + ? _title(episodes[index]) + : Row( + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + _circleImage(context, + episode: + episodes[index], + color: _c, + boo: boo), + SizedBox( + width: 5, ), - ) - : Center() - : Center() + Expanded( + child: _title( + episodes[index])) + ], + ), + ), + Expanded( + flex: 1, + child: Row( + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + if (layout != Layout.one) + Align( + alignment: + Alignment.bottomLeft, + child: _pubDate(context, + episode: episodes[index], + color: _c), + ), + Spacer(), + layout != Layout.three && + episodes[index] + .duration != + 0 + ? Container( + alignment: + Alignment.center, + child: Text( + _stringForSeconds( + episodes[index] + .duration + .toDouble()), + style: TextStyle( + fontSize: + _width / 35), + ), + ) + : Center(), + episodes[index].duration == 0 || + episodes[index] + .enclosureLength == + null || + episodes[index] + .enclosureLength == + 0 || + layout == Layout.three + ? Center() + : Text( + '|', + style: TextStyle( + fontSize: _width / 35, + // color: _c, + // fontStyle: FontStyle.italic, + ), + ), + layout != Layout.three && + episodes[index] + .enclosureLength != + null && + episodes[index] + .enclosureLength != + 0 + ? Container( + alignment: + Alignment.center, + child: Text( + ((episodes[index] + .enclosureLength) ~/ + 1000000) + .toString() + + 'MB', + style: TextStyle( + fontSize: + _width / 35), + ), + ) + : Center(), + Padding( + padding: EdgeInsets.all(1), + ), + showFavorite || + layout != Layout.three + ? isLiked + ? IconTheme( + data: IconThemeData( + size: + _width / 35), + child: Icon( + Icons.favorite, + color: Colors.red, + ), + ) + : Center() + : Center() + ], + ), + ), ], ), ), - ], + ), ), - ), - ), - ), - ); + ); }), ), ), diff --git a/lib/util/extension_helper.dart b/lib/util/extension_helper.dart index 67bfe23..e62cc3d 100644 --- a/lib/util/extension_helper.dart +++ b/lib/util/extension_helper.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:intl/intl.dart'; import 'package:url_launcher/url_launcher.dart'; import '../generated/l10n.dart'; @@ -14,6 +15,7 @@ extension ContextExtension on BuildContext { Brightness get brightness => Theme.of(this).brightness; double get width => MediaQuery.of(this).size.width; double get height => MediaQuery.of(this).size.height; + double get paddingTop => MediaQuery.of(this).padding.top; TextTheme get textTheme => Theme.of(this).textTheme; S get s => S.of(this); } @@ -58,6 +60,10 @@ extension StringExtension on String { await launch(this); } else { print('Could not launch $this'); + Fluttertoast.showToast( + msg: '$this Invalid Link', + gravity: ToastGravity.TOP, + ); } }