diff --git a/CHANGELOG.md b/CHANGELOG.md index 302236e..039c1fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ Release date 2020/6/30 ### Bug fixed * Crash on stop player. -* Some donwload file didn't auto deleted. +* Some download file didn't auto deleted. ## v0.3.5 diff --git a/README.md b/README.md index 4a74259..8f952ad 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ ![CircleCI](https://img.shields.io/circleci/build/github/stonega/tsacdop?token=efe1331861e017144f2abb363acd95197e436dad) -![GitHub release (latest by date)](https://img.shields.io/github/v/release/stonega/tsacdop) + -[![GooglePlay](https://img.shields.io/badge/Google-PlayStore-%2323CCC6)](https://play.google.com/store/apps/details?id=com.stonegate.tsacdop) +![GitHub release (latest by date)](https://img.shields.io/github/v/release/stonega/tsacdop) [![GooglePlay](https://img.shields.io/badge/Google-PlayStore-%2323CCC6)](https://play.google.com/store/apps/details?id=com.stonegate.tsacdop) ## About @@ -56,8 +56,6 @@ final environment = {"apiKey":"APIKEY", "shareKey":"SHAREKEY"}; You can get own api key on [ListenNotes](https://www.listennotes.com/api/), basic plan is free to all, and replace "APIKEY" with it. If no api key added, the search function in the app won't work. But you can still add podcasts by serach rss link or import ompl file. -Share_key is used for generate clip. - ## Known Issue * Playlist unstable @@ -74,3 +72,4 @@ A few resources to get you started if this is your first Flutter project: For help getting started with Flutter, view our [online documentation](https://flutter.dev/docs), which offers tutorials, samples, guidance on mobile development, and a full API reference. + diff --git a/lib/home/home_groups.dart b/lib/home/home_groups.dart index 9fd8fcb..0228097 100644 --- a/lib/home/home_groups.dart +++ b/lib/home/home_groups.dart @@ -3,8 +3,11 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:focused_menu/focused_menu.dart'; +import 'package:focused_menu/modals.dart'; import 'package:provider/provider.dart'; import 'package:fluttertoast/fluttertoast.dart'; +import 'package:tsacdop/util/episodegrid.dart'; import 'package:tuple/tuple.dart'; import 'package:line_icons/line_icons.dart'; @@ -469,12 +472,21 @@ class PodcastPreview extends StatelessWidget { class ShowEpisode extends StatelessWidget { final List episodes; final PodcastLocal podcastLocal; + List _menuList = []; ShowEpisode({Key key, this.episodes, this.podcastLocal}) : super(key: key); String _stringForSeconds(double seconds) { if (seconds == null) return null; return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}'; } + Future> _initData(EpisodeBrief episode) async { + int listened = await _isListened(episode); + bool liked = await _isLiked(episode); + bool downloaded = await _isDownloaded(episode); + _menuList = await _getEpisodeMenu(); + return Tuple3(listened, liked, downloaded); + } + Future _isListened(EpisodeBrief episode) async { DBHelper dbHelper = DBHelper(); return await dbHelper.isListened(episode.enclosureUrl); @@ -506,186 +518,188 @@ class ShowEpisode extends StatelessWidget { } } - _saveLiked(String url) async { + _saveLiked(String url) async { var dbHelper = DBHelper(); - await dbHelper.setLiked(url); + await dbHelper.setLiked(url); } _setUnliked(String url) async { var dbHelper = DBHelper(); - await dbHelper.setUniked(url); - } - - _showPopupMenu(Offset offset, EpisodeBrief episode, BuildContext context, - bool isPlaying, bool isInPlaylist) async { - var audio = Provider.of(context, listen: false); - double left = offset.dx; - double top = offset.dy; - bool isLiked, isDownload; - int isListened; - var downloader = Provider.of(context, listen: false); - List menuList = await _getEpisodeMenu(); - if (menuList.contains(3)) isListened = await _isListened(episode); - if (menuList.contains(2)) isLiked = await _isLiked(episode); - if (menuList.contains(4)) isDownload = await _isDownloaded(episode); - await showMenu( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(10))), - context: context, - position: RelativeRect.fromLTRB(left, top, context.width - left, 0), - items: >[ - PopupMenuItem( - value: 0, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Icon( - LineIcons.play_circle_solid, - color: Theme.of(context).accentColor, - ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 2), - ), - !isPlaying ? Text('Play') : Text('Playing'), - ], - ), - ), - menuList.contains(1) - ? PopupMenuItem( - value: 1, - child: Row( - children: [ - Icon( - LineIcons.clock_solid, - color: Colors.red, - ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 2), - ), - !isInPlaylist ? Text('Later') : Text('Remove') - ], - )) - : null, - menuList.contains(2) - ? PopupMenuItem( - value: 2, - child: Row( - children: [ - Icon(LineIcons.heart, color: Colors.red, size: 21), - Padding( - padding: EdgeInsets.symmetric(horizontal: 2), - ), - isLiked - ? Text( - 'Unlike', - ) - : Text('Like') - ], - )) - : null, - menuList.contains(3) - ? PopupMenuItem( - value: 3, - child: Row( - children: [ - SizedBox( - width: 23, - height: 23, - child: CustomPaint( - painter: - ListenedAllPainter(Colors.blue, stroke: 1.5)), - ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 2), - ), - isListened > 0 - ? Text('Listened', - style: TextStyle( - color: context.textColor.withOpacity(0.5))) - : Text('Mark\nListened') - ], - )) - : null, - menuList.contains(4) - ? PopupMenuItem( - value: 4, - child: Row( - children: [ - Icon(LineIcons.download_solid, color: Colors.green), - Padding( - padding: EdgeInsets.symmetric(horizontal: 2), - ), - isDownload - ? Text('Downloaded', - style: TextStyle( - color: context.textColor.withOpacity(0.5))) - : Text('Download') - ], - )) - : null, - ], - elevation: 5.0, - ).then((value) async { - switch (value) { - case 0: - if (!isPlaying) audio.episodeLoad(episode); - break; - case 1: - if (!isInPlaylist) { - audio.addToPlaylist(episode); - Fluttertoast.showToast( - msg: 'Added to playlist', - gravity: ToastGravity.BOTTOM, - ); - } else { - audio.delFromPlaylist(episode); - Fluttertoast.showToast( - msg: 'Removed from playlist', - gravity: ToastGravity.BOTTOM, - ); - } - break; - case 2: - if (isLiked) { - await _setUnliked(episode.enclosureUrl); - audio.setEpisodeState = true; - Fluttertoast.showToast( - msg: 'Unliked', - gravity: ToastGravity.BOTTOM, - ); - } else { - await _saveLiked(episode.enclosureUrl); - audio.setEpisodeState = true; - Fluttertoast.showToast( - msg: 'Liked', - gravity: ToastGravity.BOTTOM, - ); - } - break; - case 3: - if (isListened < 1) { - await _markListened(episode); - audio.setEpisodeState = true; - Fluttertoast.showToast( - msg: 'Mark listened', - gravity: ToastGravity.BOTTOM, - ); - } - break; - - case 4: - if (!isDownload) downloader.startTask(episode); - break; - default: - break; - } - }); + await dbHelper.setUniked(url); } +// _showPopupMenu(Offset offset, EpisodeBrief episode, BuildContext context, +// bool isPlaying, bool isInPlaylist) async { +// var audio = Provider.of(context, listen: false); +// double left = offset.dx; +// double top = offset.dy; +// bool isLiked, isDownload; +// int isListened; +// var downloader = Provider.of(context, listen: false); +// List menuList = await _getEpisodeMenu(); +// if (menuList.contains(3)) isListened = await _isListened(episode); +// if (menuList.contains(2)) isLiked = await _isLiked(episode); +// if (menuList.contains(4)) isDownload = await _isDownloaded(episode); +// await showMenu( +// shape: RoundedRectangleBorder( +// borderRadius: BorderRadius.all(Radius.circular(10))), +// context: context, +// position: RelativeRect.fromLTRB(left, top, context.width - left, 0), +// items: >[ +// PopupMenuItem( +// value: 0, +// child: Row( +// mainAxisAlignment: MainAxisAlignment.start, +// mainAxisSize: MainAxisSize.max, +// children: [ +// Icon( +// LineIcons.play_circle_solid, +// color: Theme.of(context).accentColor, +// ), +// Padding( +// padding: EdgeInsets.symmetric(horizontal: 2), +// ), +// !isPlaying ? Text('Play') : Text('Playing'), +// ], +// ), +// ), +// menuList.contains(1) +// ? PopupMenuItem( +// value: 1, +// child: Row( +// children: [ +// Icon( +// LineIcons.clock_solid, +// color: Colors.red, +// ), +// Padding( +// padding: EdgeInsets.symmetric(horizontal: 2), +// ), +// !isInPlaylist ? Text('Later') : Text('Remove') +// ], +// )) +// : null, +// menuList.contains(2) +// ? PopupMenuItem( +// value: 2, +// child: Row( +// children: [ +// Icon(LineIcons.heart, color: Colors.red, size: 21), +// Padding( +// padding: EdgeInsets.symmetric(horizontal: 2), +// ), +// isLiked +// ? Text( +// 'Unlike', +// ) +// : Text('Like') +// ], +// )) +// : null, +// menuList.contains(3) +// ? PopupMenuItem( +// value: 3, +// child: Row( +// children: [ +// SizedBox( +// width: 23, +// height: 23, +// child: CustomPaint( +// painter: +// ListenedAllPainter(Colors.blue, stroke: 1.5)), +// ), +// Padding( +// padding: EdgeInsets.symmetric(horizontal: 2), +// ), +// isListened > 0 +// ? Text('Listened', +// style: TextStyle( +// color: context.textColor.withOpacity(0.5))) +// : Text('Mark\nListened') +// ], +// )) +// : null, +// menuList.contains(4) +// ? PopupMenuItem( +// value: 4, +// child: Row( +// children: [ +// Icon(LineIcons.download_solid, color: Colors.green), +// Padding( +// padding: EdgeInsets.symmetric(horizontal: 2), +// ), +// isDownload +// ? Text('Downloaded', +// style: TextStyle( +// color: context.textColor.withOpacity(0.5))) +// : Text('Download') +// ], +// )) +// : null, +// ], +// elevation: 5.0, +// ).then((value) async { +// switch (value) { +// case 0: +// if (!isPlaying) audio.episodeLoad(episode); +// break; +// case 1: +// if (!isInPlaylist) { +// audio.addToPlaylist(episode); +// Fluttertoast.showToast( +// msg: 'Added to playlist', +// gravity: ToastGravity.BOTTOM, +// ); +// } else { +// audio.delFromPlaylist(episode); +// Fluttertoast.showToast( +// msg: 'Removed from playlist', +// gravity: ToastGravity.BOTTOM, +// ); +// } +// break; +// case 2: +// if (isLiked) { +// await _setUnliked(episode.enclosureUrl); +// audio.setEpisodeState = true; +// Fluttertoast.showToast( +// msg: 'Unliked', +// gravity: ToastGravity.BOTTOM, +// ); +// } else { +// await _saveLiked(episode.enclosureUrl); +// audio.setEpisodeState = true; +// Fluttertoast.showToast( +// msg: 'Liked', +// gravity: ToastGravity.BOTTOM, +// ); +// } +// break; +// case 3: +// if (isListened < 1) { +// await _markListened(episode); +// audio.setEpisodeState = true; +// Fluttertoast.showToast( +// msg: 'Mark listened', +// gravity: ToastGravity.BOTTOM, +// ); +// } +// break; +// +// case 4: +// if (!isDownload) downloader.startTask(episode); +// break; +// default: +// break; +// } +// }); +// } +// @override Widget build(BuildContext context) { double _width = context.width; + var downloader = Provider.of(context, listen: false); + var audio = Provider.of(context, listen: false); Offset offset; return CustomScrollView( physics: NeverScrollableScrollPhysics(), @@ -706,186 +720,365 @@ class ShowEpisode extends StatelessWidget { ? podcastLocal.primaryColor.colorizedark() : podcastLocal.primaryColor.colorizeLight(); return Selector>>( - selector: (_, audio) => Tuple2( - audio?.episode, - audio.queue.playlist.map((e) => e.enclosureUrl).toList(), - ), - builder: (_, data, __) => Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(5.0)), - color: Theme.of(context).scaffoldBackgroundColor, - ), - alignment: Alignment.center, - child: Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.all(Radius.circular(5.0)), - onTapDown: (details) => offset = Offset( - details.globalPosition.dx, - details.globalPosition.dy), - onLongPress: () => _showPopupMenu( - offset, - episodes[index], - context, - data.item1 == episodes[index], - data.item2.contains(episodes[index].enclosureUrl)), - onTap: () { - Navigator.push( - context, - ScaleRoute( - page: EpisodeDetail( - episodeItem: episodes[index], - heroTag: 'scroll', - //unique hero tag - )), - ); - }, - child: Container( - padding: EdgeInsets.all(10.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - flex: 2, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, + Tuple2>>( + selector: (_, audio) => Tuple2( + audio?.episode, + audio.queue.playlist + .map((e) => e.enclosureUrl) + .toList(), + ), + builder: (_, data, __) => FutureBuilder< + Tuple3>( + future: _initData(episodes[index]), + initialData: Tuple3(0, false, false), + builder: + (BuildContext context, AsyncSnapshot snapshot) { + int isListened = snapshot.data.item1; + bool isLiked = snapshot.data.item2; + bool isDownloaded = snapshot.data.item3; + return Container( + decoration: BoxDecoration( + borderRadius: + BorderRadius.all(Radius.circular(5.0)), + color: Theme.of(context).scaffoldBackgroundColor, + ), + alignment: Alignment.center, + child: + // InkWell( + // borderRadius: + // BorderRadius.all(Radius.circular(5.0)), + // onTapDown: (details) => offset = Offset( + // details.globalPosition.dx, + // details.globalPosition.dy), + // onLongPress: () => _showPopupMenu( + // offset, + // episodes[index], + // context, + // data.item1 == episodes[index], + // data.item2.contains( + // episodes[index].enclosureUrl)), + // onTap: () { + // + // }, + FocusedMenuHolder( + blurSize: 0.0, + menuItemExtent: 45, + menuBoxDecoration: BoxDecoration( + color: Colors.transparent, + borderRadius: + BorderRadius.all(Radius.circular(15.0))), + duration: Duration(milliseconds: 100), + tapMode: 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] + ? "Play" + : "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("Remove") + : Text("Later"), + trailingIcon: Icon( + LineIcons.clock_solid, + color: Colors.cyan, + ), + onPressed: () { + if (!data.item2.contains( + episodes[index].enclosureUrl)) { + audio + .addToPlaylist(episodes[index]); + Fluttertoast.showToast( + msg: 'Added to playlist', + gravity: ToastGravity.BOTTOM, + ); + } else { + audio.delFromPlaylist( + episodes[index]); + Fluttertoast.showToast( + msg: 'Removed from playlist', + gravity: ToastGravity.BOTTOM, + ); + } + }) + : null, + _menuList.contains(2) + ? FocusedMenuItem( + backgroundColor: context.brightness == + Brightness.light + ? context.primaryColor + : context.scaffoldBackgroundColor, + title: isLiked + ? Text("Unlike") + : Text("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: 'Unliked', + gravity: ToastGravity.BOTTOM, + ); + } else { + await _saveLiked( + episodes[index].enclosureUrl); + audio.setEpisodeState = true; + Fluttertoast.showToast( + msg: 'Liked', + gravity: ToastGravity.BOTTOM, + ); + } + }) + : null, + _menuList.contains(3) + ? FocusedMenuItem( + backgroundColor: context.brightness == + Brightness.light + ? context.primaryColor + : context.scaffoldBackgroundColor, + title: isListened > 0 + ? Text('Listened', + style: TextStyle( + color: context.textColor + .withOpacity(0.5))) + : Text( + 'Mark Listened', + 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: 'Mark listened', + gravity: ToastGravity.BOTTOM, + ); + } + }) + : null, + _menuList.contains(4) + ? FocusedMenuItem( + backgroundColor: context.brightness == + Brightness.light + ? context.primaryColor + : context.scaffoldBackgroundColor, + title: isDownloaded + ? Text('Downloaded', + style: TextStyle( + color: context.textColor + .withOpacity(0.5))) + : Text('Download'), + trailingIcon: Icon( + LineIcons.download_solid, + color: Colors.green), + onPressed: () { + if (!isDownloaded) + downloader + .startTask(episodes[index]); + }) + : null + ], + action: () => Navigator.push( + context, + ScaleRoute( + page: EpisodeDetail( + episodeItem: episodes[index], + heroTag: 'scroll', + //unique hero tag + )), + ), + child: Container( + padding: EdgeInsets.all(10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - Hero( - tag: episodes[index].enclosureUrl + - 'scroll', + Expanded( + flex: 2, + child: Row( + mainAxisAlignment: + MainAxisAlignment.start, + children: [ + Hero( + tag: episodes[index].enclosureUrl + + 'scroll', + child: Container( + height: _width / 18, + width: _width / 18, + child: CircleAvatar( + backgroundImage: FileImage(File( + "${podcastLocal.imagePath}")), + ), + ), + ), + Spacer(), + Selector>( + selector: (_, audio) => Tuple2( + audio.episode, + audio.playerRunning), + builder: (_, data, __) { + return (episodes[index] + .enclosureUrl == + data.item1 + ?.enclosureUrl && + data.item2) + ? Container( + height: 20, + width: 20, + margin: EdgeInsets + .symmetric( + horizontal: 2), + decoration: + BoxDecoration( + shape: + BoxShape.circle, + ), + child: WaveLoader( + color: context + .accentColor)) + : Center(); + }), + episodes[index].isNew == 1 + ? Text( + 'New', + style: TextStyle( + color: Colors.red, + fontStyle: + FontStyle.italic), + ) + : Center(), + ], + ), + ), + Expanded( + flex: 5, child: Container( - height: _width / 18, - width: _width / 18, - child: CircleAvatar( - backgroundImage: FileImage(File( - "${podcastLocal.imagePath}")), + padding: EdgeInsets.only(top: 2.0), + alignment: Alignment.topLeft, + child: Text( + episodes[index].title, + style: TextStyle( + //fontSize: _width / 32, + ), + maxLines: 4, + overflow: TextOverflow.fade, ), ), ), - Spacer(), - Selector>( - selector: (_, audio) => Tuple2( - audio.episode, audio.playerRunning), - builder: (_, data, __) { - return (episodes[index] - .enclosureUrl == - data.item1 - ?.enclosureUrl && - data.item2) - ? Container( - height: 20, - width: 20, - margin: EdgeInsets.symmetric( - horizontal: 2), - decoration: BoxDecoration( - shape: BoxShape.circle, + Expanded( + flex: 1, + child: Row( + children: [ + Container( + alignment: Alignment.bottomLeft, + child: Text( + episodes[index].dateToString(), + //podcast[index].pubDate.substring(4, 16), + style: TextStyle( + fontSize: _width / 35, + color: _c, + fontStyle: FontStyle.italic, + ), + ), + ), + Spacer(), + episodes[index].duration != 0 + ? Container( + alignment: Alignment.center, + child: Text( + _stringForSeconds( + episodes[index] + .duration + .toDouble()) + .toString(), + style: TextStyle( + fontSize: _width / 35, + // color: _c, + // fontStyle: FontStyle.italic, + ), + ), + ) + : Center(), + episodes[index].duration == 0 || + episodes[index] + .enclosureLength == + null || + episodes[index] + .enclosureLength == + 0 + ? Center() + : Text( + '|', + style: TextStyle( + fontSize: _width / 35, + // color: _c, + // fontStyle: FontStyle.italic, + ), ), - child: WaveLoader( - color: - context.accentColor)) - : Center(); - }), - episodes[index].isNew == 1 - ? Text( - 'New', - style: TextStyle( - color: Colors.red, - fontStyle: FontStyle.italic), - ) - : Center(), + 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(), + ], + )), ], ), ), - Expanded( - flex: 5, - child: Container( - padding: EdgeInsets.only(top: 2.0), - alignment: Alignment.topLeft, - child: Text( - episodes[index].title, - style: TextStyle( - //fontSize: _width / 32, - ), - maxLines: 4, - overflow: TextOverflow.fade, - ), - ), - ), - Expanded( - flex: 1, - child: Row( - children: [ - Container( - alignment: Alignment.bottomLeft, - child: Text( - episodes[index].dateToString(), - //podcast[index].pubDate.substring(4, 16), - style: TextStyle( - fontSize: _width / 35, - color: _c, - fontStyle: FontStyle.italic, - ), - ), - ), - Spacer(), - episodes[index].duration != 0 - ? Container( - alignment: Alignment.center, - child: Text( - _stringForSeconds( - episodes[index] - .duration - .toDouble()) - .toString(), - style: TextStyle( - fontSize: _width / 35, - // color: _c, - // fontStyle: FontStyle.italic, - ), - ), - ) - : Center(), - episodes[index].duration == 0 || - episodes[index].enclosureLength == - null || - episodes[index].enclosureLength == - 0 - ? Center() - : Text( - '|', - style: TextStyle( - fontSize: _width / 35, - // color: _c, - // fontStyle: FontStyle.italic, - ), - ), - 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(), - ], - )), - ], - ), - ), - ), - ), - ), - ); + ), + ); + })); }, childCount: (episodes.length > 2) ? 2 : episodes.length, ), diff --git a/lib/util/episodegrid.dart b/lib/util/episodegrid.dart index b51e698..560cffd 100644 --- a/lib/util/episodegrid.dart +++ b/lib/util/episodegrid.dart @@ -2,12 +2,15 @@ import 'dart:io'; import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:focused_menu/focused_menu.dart'; +import 'package:focused_menu/modals.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:provider/provider.dart'; import 'package:tuple/tuple.dart'; import 'package:line_icons/line_icons.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:auto_animated/auto_animated.dart'; +import 'package:tuple/tuple.dart'; import 'open_container.dart'; import '../state/audiostate.dart'; @@ -22,6 +25,7 @@ import 'custompaint.dart'; enum Layout { three, two, one } +// ignore: must_be_immutable class EpisodeGrid extends StatelessWidget { final List episodes; final bool showFavorite; @@ -43,11 +47,22 @@ class EpisodeGrid extends StatelessWidget { this.reverse, }) : super(key: key); + List _menuList = []; + Future _isListened(EpisodeBrief episode) async { DBHelper dbHelper = DBHelper(); + _menuList = await _getEpisodeMenu(); return await dbHelper.isListened(episode.enclosureUrl); } + Future> _initData(EpisodeBrief episode) async { + int listened = await _isListened(episode); + bool liked = await _isLiked(episode); + bool downloaded = await _isDownloaded(episode); + _menuList = await _getEpisodeMenu(); + return Tuple3(listened, liked, downloaded); + } + Future _isLiked(EpisodeBrief episode) async { DBHelper dbHelper = DBHelper(); return await dbHelper.isLiked(episode.enclosureUrl); @@ -148,27 +163,26 @@ class EpisodeGrid extends StatelessWidget { // : Center(); // }); - Widget _downloadIndicater(BuildContext context, {EpisodeBrief episode}) => + Widget _downloadIndicater(BuildContext context, + {EpisodeBrief episode, bool isDownloaded}) => showDownload || layout != Layout.three - ? Container( - child: (episode.enclosureUrl != episode.mediaId) - ? Container( - height: 20, - width: 20, - margin: EdgeInsets.symmetric(horizontal: 5), - padding: EdgeInsets.symmetric(horizontal: 2), - decoration: BoxDecoration( - color: context.accentColor, - shape: BoxShape.circle, - ), - child: Icon( - Icons.done_all, - size: 15, - color: Colors.white, - ), - ) - : Center(), - ) + ? isDownloaded + ? Container( + height: 20, + width: 20, + margin: EdgeInsets.symmetric(horizontal: 5), + padding: EdgeInsets.symmetric(horizontal: 2), + decoration: BoxDecoration( + color: context.accentColor, + shape: BoxShape.circle, + ), + child: Icon( + Icons.done_all, + size: 15, + color: Colors.white, + ), + ) + : Center() : Center(); Widget _isNewIndicator(EpisodeBrief episode) => episode.isNew == 1 @@ -209,172 +223,174 @@ class EpisodeGrid extends StatelessWidget { @override Widget build(BuildContext context) { double _width = context.width; - Offset _offset; - _showPopupMenu(Offset offset, EpisodeBrief episode, BuildContext context, - {bool isPlaying, bool isInPlaylist}) async { - bool isLiked, isDownload; - int isListened; - var audio = Provider.of(context, listen: false); - var downloader = Provider.of(context, listen: false); - double left = offset.dx; - double top = offset.dy; - List menuList = await _getEpisodeMenu(); - if (menuList.contains(3)) isListened = await _isListened(episode); - if (menuList.contains(2)) isLiked = await _isLiked(episode); - if (menuList.contains(4)) isDownload = await _isDownloaded(episode); - await showMenu( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(10))), - context: context, - position: RelativeRect.fromLTRB(left, top, _width - left, 0), - items: >[ - PopupMenuItem( - value: 0, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Icon( - LineIcons.play_circle_solid, - color: Theme.of(context).accentColor, - ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 2), - ), - !isPlaying ? Text('Play') : Text('Playing'), - ], - ), - ), - menuList.contains(1) - ? PopupMenuItem( - value: 1, - child: Row( - children: [ - Icon( - LineIcons.clock_solid, - color: Colors.cyan, - ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 2), - ), - !isInPlaylist ? Text('Later') : Text('Remove') - ], - )) - : null, - menuList.contains(2) - ? PopupMenuItem( - value: 2, - child: Row( - children: [ - Icon(LineIcons.heart, color: Colors.red, size: 21), - Padding( - padding: EdgeInsets.symmetric(horizontal: 2), - ), - isLiked - ? Text( - 'Unlike', - ) - : Text('Like') - ], - )) - : null, - menuList.contains(3) - ? PopupMenuItem( - value: 3, - child: Row( - children: [ - SizedBox( - width: 23, - height: 23, - child: CustomPaint( - painter: - ListenedAllPainter(Colors.blue, stroke: 1.5)), - ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 2), - ), - isListened > 0 - ? Text('Listened', - style: TextStyle( - color: context.textColor.withOpacity(0.5))) - : Text('Mark\nListened') - ], - )) - : null, - menuList.contains(4) - ? PopupMenuItem( - value: 4, - child: Row( - children: [ - Icon(LineIcons.download_solid, color: Colors.green), - Padding( - padding: EdgeInsets.symmetric(horizontal: 2), - ), - isDownload - ? Text('Downloaded', - style: TextStyle( - color: context.textColor.withOpacity(0.5))) - : Text('Download') - ], - )) - : null, - ], - elevation: 5.0, - ).then((value) async { - switch (value) { - case 0: - if (!isPlaying) audio.episodeLoad(episode); - break; - case 1: - if (!isInPlaylist) { - audio.addToPlaylist(episode); - Fluttertoast.showToast( - msg: 'Added to playlist', - gravity: ToastGravity.BOTTOM, - ); - } else { - audio.delFromPlaylist(episode); - Fluttertoast.showToast( - msg: 'Removed from playlist', - gravity: ToastGravity.BOTTOM, - ); - } - break; - case 2: - if (isLiked) { - await _setUnliked(episode.enclosureUrl); - audio.setEpisodeState = true; - Fluttertoast.showToast( - msg: 'Unliked', - gravity: ToastGravity.BOTTOM, - ); - } else { - await _saveLiked(episode.enclosureUrl); - audio.setEpisodeState = true; - Fluttertoast.showToast( - msg: 'Liked', - gravity: ToastGravity.BOTTOM, - ); - } - break; - case 3: - if (isListened < 1) { - await _markListened(episode); - audio.setEpisodeState = true; - Fluttertoast.showToast( - msg: 'Mark listened', - gravity: ToastGravity.BOTTOM, - ); - } - break; - case 4: - if (!isDownload) downloader.startTask(episode); - break; - default: - break; - } - }); - } + var audio = Provider.of(context, listen: false); + var downloader = Provider.of(context, listen: false); + //Offset _offset; + //_showPopupMenu(Offset offset, EpisodeBrief episode, BuildContext context, + // {bool isPlaying, bool isInPlaylist}) async { + // bool isLiked, isDownload; + // int isListened; + // var audio = Provider.of(context, listen: false); + // var downloader = Provider.of(context, listen: false); + // double left = offset.dx; + // double top = offset.dy; + // List menuList = await _getEpisodeMenu(); + // if (menuList.contains(3)) isListened = await _isListened(episode); + // if (menuList.contains(2)) isLiked = await _isLiked(episode); + // if (menuList.contains(4)) isDownload = await _isDownloaded(episode); + // await showMenu( + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.all(Radius.circular(10))), + // context: context, + // position: RelativeRect.fromLTRB(left, top, _width - left, 0), + // items: >[ + // PopupMenuItem( + // value: 0, + // child: Row( + // mainAxisAlignment: MainAxisAlignment.start, + // mainAxisSize: MainAxisSize.max, + // children: [ + // Icon( + // LineIcons.play_circle_solid, + // color: Theme.of(context).accentColor, + // ), + // Padding( + // padding: EdgeInsets.symmetric(horizontal: 2), + // ), + // !isPlaying ? Text('Play') : Text('Playing'), + // ], + // ), + // ), + // menuList.contains(1) + // ? PopupMenuItem( + // value: 1, + // child: Row( + // children: [ + // Icon( + // LineIcons.clock_solid, + // color: Colors.cyan, + // ), + // Padding( + // padding: EdgeInsets.symmetric(horizontal: 2), + // ), + // !isInPlaylist ? Text('Later') : Text('Remove') + // ], + // )) + // : null, + // menuList.contains(2) + // ? PopupMenuItem( + // value: 2, + // child: Row( + // children: [ + // Icon(LineIcons.heart, color: Colors.red, size: 21), + // Padding( + // padding: EdgeInsets.symmetric(horizontal: 2), + // ), + // isLiked + // ? Text( + // 'Unlike', + // ) + // : Text('Like') + // ], + // )) + // : null, + // menuList.contains(3) + // ? PopupMenuItem( + // value: 3, + // child: Row( + // children: [ + // SizedBox( + // width: 23, + // height: 23, + // child: CustomPaint( + // painter: + // ListenedAllPainter(Colors.blue, stroke: 1.5)), + // ), + // Padding( + // padding: EdgeInsets.symmetric(horizontal: 2), + // ), + // isListened > 0 + // ? Text('Listened', + // style: TextStyle( + // color: context.textColor.withOpacity(0.5))) + // : Text('Mark\nListened') + // ], + // )) + // : null, + // menuList.contains(4) + // ? PopupMenuItem( + // value: 4, + // child: Row( + // children: [ + // Icon(LineIcons.download_solid, color: Colors.green), + // Padding( + // padding: EdgeInsets.symmetric(horizontal: 2), + // ), + // isDownload + // ? Text('Downloaded', + // style: TextStyle( + // color: context.textColor.withOpacity(0.5))) + // : Text('Download') + // ], + // )) + // : null, + // ], + // elevation: 5.0, + // ).then((value) async { + // switch (value) { + // case 0: + // if (!isPlaying) audio.episodeLoad(episode); + // break; + // case 1: + // if (!isInPlaylist) { + // audio.addToPlaylist(episode); + // Fluttertoast.showToast( + // msg: 'Added to playlist', + // gravity: ToastGravity.BOTTOM, + // ); + // } else { + // audio.delFromPlaylist(episode); + // Fluttertoast.showToast( + // msg: 'Removed from playlist', + // gravity: ToastGravity.BOTTOM, + // ); + // } + // break; + // case 2: + // if (isLiked) { + // await _setUnliked(episode.enclosureUrl); + // audio.setEpisodeState = true; + // Fluttertoast.showToast( + // msg: 'Unliked', + // gravity: ToastGravity.BOTTOM, + // ); + // } else { + // await _saveLiked(episode.enclosureUrl); + // audio.setEpisodeState = true; + // Fluttertoast.showToast( + // msg: 'Liked', + // gravity: ToastGravity.BOTTOM, + // ); + // } + // break; + // case 3: + // if (isListened < 1) { + // await _markListened(episode); + // audio.setEpisodeState = true; + // Fluttertoast.showToast( + // msg: 'Mark listened', + // gravity: ToastGravity.BOTTOM, + // ); + // } + // break; + // case 4: + // if (!isDownload) downloader.startTask(episode); + // break; + // default: + // break; + // } + // }); + //} final options = LiveOptions( delay: Duration.zero, @@ -402,9 +418,8 @@ class EpisodeGrid extends StatelessWidget { Color _c = (Theme.of(context).brightness == Brightness.light) ? episodes[index].primaryColor.colorizedark() : episodes[index].primaryColor.colorizeLight(); - scrollController.addListener(() { - print(scrollController.offset); - }); + scrollController.addListener(() {}); + return FadeTransition( opacity: Tween(begin: index < initNum ? 0 : 1, end: 1) .animate(animation), @@ -416,15 +431,19 @@ class EpisodeGrid extends StatelessWidget { audio.episodeState), builder: (_, data, __) => OpenContainerWrapper( episode: episodes[index], - closedBuilder: (context, action, boo) => FutureBuilder( - future: _isListened(episodes[index]), - initialData: 0, + closedBuilder: (context, action, boo) => FutureBuilder< + Tuple3>( + future: _initData(episodes[index]), + initialData: Tuple3(0, false, false), builder: (BuildContext context, AsyncSnapshot snapshot) { + int isListened = snapshot.data.item1; + bool isLiked = snapshot.data.item2; + bool isDownloaded = snapshot.data.item3; return Container( decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(5.0)), - color: snapshot.data > 0 + color: isListened > 0 ? context.brightness == Brightness.light ? Colors.grey[200] : Color.fromRGBO(40, 40, 40, 1) @@ -439,35 +458,187 @@ class EpisodeGrid extends StatelessWidget { ), ]), alignment: Alignment.center, - child: Material( - color: Colors.transparent, - child: InkWell( + // InkWell( + // borderRadius: + // BorderRadius.all(Radius.circular(5.0)), + // onTapDown: (details) => _offset = Offset( + // details.globalPosition.dx, + // details.globalPosition.dy), + // onLongPress: () => _showPopupMenu( + // _offset, + // episodes[index], + // context, + // isPlaying: data.item1 == episodes[index], + // isInPlaylist: data.item2 + // .contains(episodes[index].enclosureUrl), + // ), + // onTap: action, + child: Container( + decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(5.0)), - onTapDown: (details) => _offset = Offset( - details.globalPosition.dx, - details.globalPosition.dy), - onLongPress: () => _showPopupMenu( - _offset, - episodes[index], - context, - isPlaying: data.item1 == episodes[index], - isInPlaylist: data.item2 - .contains(episodes[index].enclosureUrl), + border: Border.all( + color: context.brightness == Brightness.light + ? context.primaryColor + : context.scaffoldBackgroundColor, + width: 1.0, ), - onTap: action, - child: Container( - padding: const EdgeInsets.all(8.0), - decoration: BoxDecoration( + ), + child: FocusedMenuHolder( + blurSize: 0.0, + menuItemExtent: 45, + menuBoxDecoration: BoxDecoration( + color: Colors.transparent, borderRadius: - BorderRadius.all(Radius.circular(5.0)), - border: Border.all( - color: context.brightness == Brightness.light - ? context.primaryColor - : context.scaffoldBackgroundColor, - width: 1.0, - ), - ), + BorderRadius.all(Radius.circular(15.0))), + duration: Duration(milliseconds: 100), + tapMode: TapMode.onTap, + 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] + ? "Play" + : "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("Remove") + : Text("Later"), + trailingIcon: Icon( + LineIcons.clock_solid, + color: Colors.cyan, + ), + onPressed: () { + if (!data.item2.contains( + episodes[index].enclosureUrl)) { + audio.addToPlaylist(episodes[index]); + Fluttertoast.showToast( + msg: 'Added to playlist', + gravity: ToastGravity.BOTTOM, + ); + } else { + audio + .delFromPlaylist(episodes[index]); + Fluttertoast.showToast( + msg: 'Removed from playlist', + gravity: ToastGravity.BOTTOM, + ); + } + }) + : null, + _menuList.contains(2) + ? FocusedMenuItem( + backgroundColor: + context.brightness == Brightness.light + ? context.primaryColor + : context.scaffoldBackgroundColor, + title: isLiked + ? Text("Unlike") + : Text("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: 'Unliked', + gravity: ToastGravity.BOTTOM, + ); + } else { + await _saveLiked( + episodes[index].enclosureUrl); + audio.setEpisodeState = true; + Fluttertoast.showToast( + msg: 'Liked', + gravity: ToastGravity.BOTTOM, + ); + } + }) + : null, + _menuList.contains(3) + ? FocusedMenuItem( + backgroundColor: + context.brightness == Brightness.light + ? context.primaryColor + : context.scaffoldBackgroundColor, + title: isListened > 0 + ? Text('Listened', + style: TextStyle( + color: context.textColor + .withOpacity(0.5))) + : Text( + 'Mark Listened', + 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: 'Mark listened', + gravity: ToastGravity.BOTTOM, + ); + } + }) + : null, + _menuList.contains(4) + ? FocusedMenuItem( + backgroundColor: + context.brightness == Brightness.light + ? context.primaryColor + : context.scaffoldBackgroundColor, + title: isDownloaded + ? Text('Downloaded', + style: TextStyle( + color: context.textColor + .withOpacity(0.5))) + : Text('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: [ @@ -491,7 +662,8 @@ class EpisodeGrid extends StatelessWidget { // isListened: snapshot.data), _isNewIndicator(episodes[index]), _downloadIndicater(context, - episode: episodes[index]), + episode: episodes[index], + isDownloaded: isDownloaded), _numberIndicater(context, index: index, color: _c) ], @@ -588,27 +760,17 @@ class EpisodeGrid extends StatelessWidget { padding: EdgeInsets.all(1), ), showFavorite || layout != Layout.three - ? FutureBuilder( - future: - _isLiked(episodes[index]), - initialData: false, - builder: (context, snapshot) => - Container( - alignment: Alignment.center, - child: (snapshot.data) - ? IconTheme( - data: IconThemeData( - size: - _width / 35), - child: Icon( - Icons.favorite, - color: Colors.red, - ), - ) - : Center(), - ), - ) - : Center(), + ? isLiked + ? IconTheme( + data: IconThemeData( + size: _width / 35), + child: Icon( + Icons.favorite, + color: Colors.red, + ), + ) + : Center() + : Center() ], ), ), diff --git a/pubspec.yaml b/pubspec.yaml index 0b8a653..5019e9e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,9 @@ dependencies: line_icons: git: url: https://github.com/galonsos/line_icons.git + focused_menu: + git: + url: https://github.com/stonega/focused_menu.git dev_dependencies: flutter_test: