Popup menu setting

Auto download on work
Add rewind when using headset
Fixed audio auto play when notification come
This commit is contained in:
stonegate 2020-06-10 15:42:40 +08:00
parent 1a497a78ed
commit 935566b304
24 changed files with 1082 additions and 432 deletions

View File

@ -49,7 +49,7 @@ android {
applicationId "com.stonegate.tsacdop"
minSdkVersion 19
targetSdkVersion 29
versionCode 16
versionCode 17
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View File

@ -55,10 +55,12 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
setState(() {
_showMenu = true;
});
} else
} else if (_controller.offset <
_controller.position.maxScrollExtent * 0.8) {
setState(() {
_showMenu = false;
});
}
}
_launchUrl(String url) async {
@ -113,7 +115,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
PopupMenuButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
elevation: 2,
elevation: 1,
tooltip: 'Menu',
itemBuilder: (context) => [
PopupMenuItem(
@ -152,6 +154,8 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
gravity: ToastGravity.BOTTOM,
);
break;
default:
break;
}
},
),
@ -323,8 +327,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
'Still no shownote received\n for this episode.',
textAlign: TextAlign.center,
style: TextStyle(
color: context.textTheme
.bodyText1.color
color: context.textColor
.withOpacity(0.5))),
],
),
@ -381,7 +384,7 @@ class MenuBar extends StatefulWidget {
}
class _MenuBarState extends State<MenuBar> {
bool _liked;
bool _liked = false;
Future<PlayHistory> getPosition(EpisodeBrief episode) async {
var dbHelper = DBHelper();
@ -401,7 +404,6 @@ class _MenuBarState extends State<MenuBar> {
if (result == 1 && mounted)
setState(() {
_liked = false;
// _like = 0;
});
return result;
}
@ -526,12 +528,10 @@ class _MenuBarState extends State<MenuBar> {
},
),
DownloadButton(episode: widget.episodeItem),
Selector<AudioPlayerNotifier, List<String>>(
selector: (_, audio) => audio.queue.playlist
.map((e) => e.enclosureUrl)
.toList(),
Selector<AudioPlayerNotifier, List<EpisodeBrief>>(
selector: (_, audio) => audio.queue.playlist,
builder: (_, data, __) {
return data.contains(widget.episodeItem.enclosureUrl)
return data.contains(widget.episodeItem)
? _buttonOnMenu(
Icon(Icons.playlist_add_check,
color: Theme.of(context).accentColor), () {

View File

@ -2,7 +2,6 @@ import 'dart:ui';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:permission_handler/permission_handler.dart';
@ -13,6 +12,7 @@ import '../state/download_state.dart';
import '../state/audiostate.dart';
import '../state/settingstate.dart';
import '../type/episodebrief.dart';
import '../util/general_dialog.dart';
class DownloadButton extends StatefulWidget {
final EpisodeBrief episode;
@ -107,54 +107,31 @@ class _DownloadButtonState extends State<DownloadButton> {
Future<bool> _useDataConfirem() async {
bool ifUseData = false;
await 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) =>
AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.light,
systemNavigationBarColor:
Theme.of(context).brightness == Brightness.light
? Color.fromRGBO(113, 113, 113, 1)
: Color.fromRGBO(15, 15, 15, 1),
await generalDialog(
context,
title: Text('Cellular data warn'),
content: Text('Are you sure you want to use cellular data to download?'),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'CANCEL',
style: TextStyle(color: Colors.grey[600]),
),
),
child: AlertDialog(
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0))),
titlePadding:
EdgeInsets.only(top: 20, left: 20, right: 100, bottom: 20),
title: Text('Cellular data warn'),
content:
Text('Are you sure you want to use cellular data to download?'),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'CANCEL',
style: TextStyle(color: Colors.grey[600]),
),
),
FlatButton(
onPressed: () {
ifUseData = true;
Navigator.of(context).pop();
},
child: Text(
'CONFIRM',
style: TextStyle(color: Colors.red),
),
)
],
),
),
FlatButton(
onPressed: () {
ifUseData = true;
Navigator.of(context).pop();
},
child: Text(
'CONFIRM',
style: TextStyle(color: Colors.red),
),
)
],
);
return ifUseData;
}

View File

@ -8,9 +8,13 @@ import 'intl/messages_all.dart';
// Made by Localizely
// **************************************************************************
// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars
class S {
S();
static S current;
static const AppLocalizationDelegate delegate =
AppLocalizationDelegate();
@ -19,7 +23,9 @@ class S {
final localeName = Intl.canonicalizedLocale(name);
return initializeMessages(localeName).then((_) {
Intl.defaultLocale = localeName;
return S();
S.current = S();
return S.current;
});
}

View File

@ -6,7 +6,7 @@ import 'package:line_icons/line_icons.dart';
import '../util/context_extension.dart';
const String version = '0.3.3';
const String version = '0.3.4';
class AboutApp extends StatelessWidget {
_launchUrl(String url) async {

View File

@ -238,7 +238,7 @@ class _SearchListState extends State<SearchList> {
Future<List> _getList(String searchText, int nextOffset) async {
String apiKey = environment['apiKey'];
String url = "https://listen-api.listennotes.com/api/v2/search?q=" +
searchText +
Uri.encodeComponent(searchText) +
"&sort_by_date=0&type=podcast&offset=$nextOffset";
Response response = await Dio().get(url,
options: Options(headers: {

View File

@ -1050,168 +1050,183 @@ class _MyFavoriteState extends State<_MyFavorite>
@override
Widget build(BuildContext context) {
super.build(context);
return FutureBuilder<List<EpisodeBrief>>(
future: _getLikedRssItem(_top, _sortBy),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return (snapshot.hasData)
? snapshot.data.length == 0
? Padding(
padding: EdgeInsets.only(top: 150),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(LineIcons.heartbeat_solid,
size: 80, color: Colors.grey[500]),
Padding(padding: EdgeInsets.symmetric(vertical: 10)),
Text(
'No episode collected yet',
style: TextStyle(color: Colors.grey[500]),
return Selector<AudioPlayerNotifier, bool>(
selector: (_, audio) => audio.episodeState,
builder: (context, episodeState, child) {
return FutureBuilder<List<EpisodeBrief>>(
future: _getLikedRssItem(_top, _sortBy),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return (snapshot.hasData)
? snapshot.data.length == 0
? Padding(
padding: EdgeInsets.only(top: 150),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(LineIcons.heartbeat_solid,
size: 80, color: Colors.grey[500]),
Padding(
padding: EdgeInsets.symmetric(vertical: 10)),
Text(
'No episode collected yet',
style: TextStyle(color: Colors.grey[500]),
)
],
),
)
],
),
)
: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels ==
scrollInfo.metrics.maxScrollExtent &&
snapshot.data.length == _top) _loadMoreEpisode();
return true;
},
child: CustomScrollView(
key: PageStorageKey<String>('favorite'),
slivers: <Widget>[
SliverToBoxAdapter(
child: Container(
height: 40,
color: context.primaryColor,
child: Row(
children: <Widget>[
Material(
color: Colors.transparent,
child: PopupMenuButton<int>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10))),
elevation: 1,
tooltip: 'Sort By',
child: Container(
height: 50,
padding: EdgeInsets.symmetric(
horizontal: 20),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Sory by'),
Padding(
: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels ==
scrollInfo.metrics.maxScrollExtent &&
snapshot.data.length == _top)
_loadMoreEpisode();
return true;
},
child: CustomScrollView(
key: PageStorageKey<String>('favorite'),
slivers: <Widget>[
SliverToBoxAdapter(
child: Container(
height: 40,
color: context.primaryColor,
child: Row(
children: <Widget>[
Material(
color: Colors.transparent,
child: PopupMenuButton<int>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10))),
elevation: 1,
tooltip: 'Sort By',
child: Container(
height: 50,
padding: EdgeInsets.symmetric(
horizontal: 5),
horizontal: 20),
child: Row(
mainAxisSize:
MainAxisSize.min,
children: <Widget>[
Text('Sory by'),
Padding(
padding:
EdgeInsets.symmetric(
horizontal: 5),
),
Icon(
_sortBy == 0
? LineIcons
.cloud_download_alt_solid
: LineIcons
.heartbeat_solid,
size: 18,
)
],
)),
itemBuilder: (context) => [
PopupMenuItem(
value: 0,
child: Text('Update Date'),
),
Icon(
_sortBy == 0
? LineIcons
.cloud_download_alt_solid
: LineIcons.heartbeat_solid,
size: 18,
PopupMenuItem(
value: 1,
child: Text('Like Date'),
)
],
)),
itemBuilder: (context) => [
PopupMenuItem(
value: 0,
child: Text('Update Date'),
onSelected: (value) {
if (value == 0)
setState(() => _sortBy = 0);
else if (value == 1)
setState(() => _sortBy = 1);
},
),
),
Spacer(),
Material(
color: Colors.transparent,
child: IconButton(
padding: EdgeInsets.zero,
onPressed: () {
if (_layout == Layout.three)
setState(() {
_layout = Layout.one;
});
else if (_layout == Layout.two)
setState(() {
_layout = Layout.three;
});
else
setState(() {
_layout = Layout.two;
});
},
icon: _layout == Layout.three
? SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
0,
context
.textTheme
.bodyText1
.color),
),
)
: _layout == Layout.two
? SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
1,
context
.textTheme
.bodyText1
.color),
),
)
: SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
4,
context
.textTheme
.bodyText1
.color),
),
)),
),
PopupMenuItem(
value: 1,
child: Text('Like Date'),
)
],
onSelected: (value) {
if (value == 0)
setState(() => _sortBy = 0);
else if (value == 1)
setState(() => _sortBy = 1);
},
),
),
Spacer(),
Material(
color: Colors.transparent,
child: IconButton(
padding: EdgeInsets.zero,
onPressed: () {
if (_layout == Layout.three)
setState(() {
_layout = Layout.one;
});
else if (_layout == Layout.two)
setState(() {
_layout = Layout.three;
});
else
setState(() {
_layout = Layout.two;
});
},
icon: _layout == Layout.three
? SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
0,
context.textTheme
.bodyText1.color),
),
)
: _layout == Layout.two
? SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
1,
context.textTheme
.bodyText1.color),
),
)
: SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
4,
context.textTheme
.bodyText1.color),
),
)),
),
],
)),
),
EpisodeGrid(
episodes: snapshot.data,
layout: _layout,
initNum: 0,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return _loadMore
? Container(
height: 2,
child: LinearProgressIndicator())
: Center();
},
childCount: 1,
)),
),
EpisodeGrid(
episodes: snapshot.data,
layout: _layout,
initNum: 0,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return _loadMore
? Container(
height: 2,
child: LinearProgressIndicator())
: Center();
},
childCount: 1,
),
),
],
),
),
],
),
)
: Center();
},
);
)
: Center();
},
);
});
}
@override

View File

@ -12,6 +12,7 @@ import 'package:feature_discovery/feature_discovery.dart';
import '../type/episodebrief.dart';
import '../state/podcast_group.dart';
import '../state/subscribe_podcast.dart';
import '../state/download_state.dart';
import '../type/podcastlocal.dart';
import '../state/audiostate.dart';
import '../util/custompaint.dart';
@ -19,6 +20,7 @@ import '../util/pageroute.dart';
import '../util/colorize.dart';
import '../util/context_extension.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../local_storage/key_value_storage.dart';
import '../episodes/episodedetail.dart';
import '../podcasts/podcastdetail.dart';
import '../podcasts/podcastmanage.dart';
@ -478,11 +480,61 @@ class ShowEpisode extends StatelessWidget {
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
}
Future<int> _isListened(EpisodeBrief episode) async {
DBHelper dbHelper = DBHelper();
return await dbHelper.isListened(episode.enclosureUrl);
}
Future<bool> _isLiked(EpisodeBrief episode) async {
DBHelper dbHelper = DBHelper();
return await dbHelper.isLiked(episode.enclosureUrl);
}
Future<List<int>> _getEpisodeMenu() async {
KeyValueStorage popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
List<int> list = await popupMenuStorage.getMenu();
return list;
}
Future<bool> _isDownloaded(EpisodeBrief episode) async {
DBHelper dbHelper = DBHelper();
return await dbHelper.isDownloaded(episode.enclosureUrl);
}
_markListened(EpisodeBrief episode) async {
DBHelper dbHelper = DBHelper();
bool marked = await dbHelper.checkMarked(episode);
if (!marked) {
final PlayHistory history =
PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
await dbHelper.saveHistory(history);
}
}
Future<int> _saveLiked(String url) async {
var dbHelper = DBHelper();
int result = await dbHelper.setLiked(url);
return result;
}
Future<int> _setUnliked(String url) async {
var dbHelper = DBHelper();
int result = await dbHelper.setUniked(url);
return result;
}
_showPopupMenu(Offset offset, EpisodeBrief episode, BuildContext context,
bool isPlaying, bool isInPlaylist) async {
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
double left = offset.dx;
double top = offset.dy;
bool isLiked, isDownload;
int isListened;
var downloader = Provider.of<DownloadState>(context, listen: false);
List<int> 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<int>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
@ -506,39 +558,134 @@ class ShowEpisode extends StatelessWidget {
],
),
),
PopupMenuItem(
value: 1,
child: Row(
children: <Widget>[
Icon(
LineIcons.clock_solid,
color: Colors.red,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 2),
),
!isInPlaylist ? Text('Later') : Text('Remove')
],
)),
menuList.contains(1)
? PopupMenuItem(
value: 1,
child: Row(
children: <Widget>[
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: <Widget>[
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: <Widget>[
SizedBox(
width: 23,
height: 23,
child: CustomPaint(
painter:
ListenedAllPainter(Colors.blue, stroke: 1.5)),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 2),
),
isListened > 0.95
? Text('Listened',
style: TextStyle(
color: context.textColor.withOpacity(0.5)))
: Text('Mark\nListened')
],
))
: null,
menuList.contains(4)
? PopupMenuItem(
value: 4,
child: Row(
children: <Widget>[
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) {
if (value == 0) {
if (!isPlaying) audio.episodeLoad(episode);
} else if (value == 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,
);
}
).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 < 0.95) {
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;
}
});
}

View File

@ -22,6 +22,8 @@ const String podcastLayoutKey = 'podcastLayoutKey';
const String recentLayoutKey = 'recentLayoutKey';
const String favLayoutKey = 'favLayoutKey';
const String downloadLayoutKey = 'downloadLayoutKey';
const String autoDownloadNetworkKey = 'autoDownloadNetwork';
const String episodePopupMenuKey = 'episodePopupMenuKey';
class KeyValueStorage {
final String key;
@ -88,4 +90,18 @@ class KeyValueStorage {
}
return prefs.getString(key);
}
saveMenu(List<int> list) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setStringList(key, list.map((e) => e.toString()).toList());
}
Future<List<int>> getMenu() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.getStringList(key) == null) {
await prefs.setStringList(key, ['0', '1', '12', '13', '14']);
}
List<String> list = prefs.getStringList(key);
return list.map((e) => int.parse(e)).toList();
}
}

View File

@ -382,7 +382,6 @@ class DBHelper {
String month = mmDd.stringMatch(pubDate);
date = DateFormat('yyyy-MM-dd HH:mm', 'en_US')
.parse(month + ' ' + time);
print(month);
print(date.toString());
} else {
date = DateTime.now();
@ -832,36 +831,36 @@ class DBHelper {
return episodes;
}
Future<List<EpisodeBrief>> gettNewRssItem(String id) async {
var dbClient = await database;
List<EpisodeBrief> episodes = [];
List<Map> list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.milliseconds, P.title as feed_title, E.duration, E.explicit, E.liked,
E.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE is_new = 1 AND downloaded != 'ND' AND P.id = ?ORDER BY E.milliseconds DESC """,
[id],
);
for (int x = 0; x < list.length; x++) {
episodes.add(EpisodeBrief(
list[x]['title'],
list[x]['enclosure_url'],
list[x]['enclosure_length'],
list[x]['milliseconds'],
list[x]['feed_title'],
list[x]['primaryColor'],
list[x]['liked'],
list[x]['downloaded'],
list[x]['duration'],
list[x]['explicit'],
list[x]['imagePath'],
list[x]['media_id'],
list[x]['is_new'],
list[x]['skip_seconds']));
}
return episodes;
}
//Future<List<EpisodeBrief>> getNewRssItem(String id) async {
// var dbClient = await database;
// List<EpisodeBrief> episodes = [];
// List<Map> list = await dbClient.rawQuery(
// """SELECT E.title, E.enclosure_url, E.enclosure_length,
// E.milliseconds, P.title as feed_title, E.duration, E.explicit, E.liked,
// E.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds
// FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
// WHERE is_new = 1 AND downloaded != 'ND' AND P.id = ?ORDER BY E.milliseconds DESC """,
// [id],
// );
// for (int x = 0; x < list.length; x++) {
// episodes.add(EpisodeBrief(
// list[x]['title'],
// list[x]['enclosure_url'],
// list[x]['enclosure_length'],
// list[x]['milliseconds'],
// list[x]['feed_title'],
// list[x]['primaryColor'],
// list[x]['liked'],
// list[x]['downloaded'],
// list[x]['duration'],
// list[x]['explicit'],
// list[x]['imagePath'],
// list[x]['media_id'],
// list[x]['is_new'],
// list[x]['skip_seconds']));
// }
// return episodes;
//}
Future<int> removeAllNewMark() async {
var dbClient = await database;
@ -994,6 +993,13 @@ class DBHelper {
return list.first['liked'] == 0 ? false : true;
}
Future<bool> isDownloaded(String url) async {
var dbClient = await database;
List<Map> list = await dbClient
.rawQuery("SELECT downloaded FROM Episodes WHERE enclosure_url = ?", [url]);
return list.first['downloaded'] == 'ND' ? false: true;
}
Future<int> saveDownloaded(String url, String id) async {
var dbClient = await database;
int milliseconds = DateTime.now().millisecondsSinceEpoch;

View File

@ -1,10 +1,12 @@
import 'dart:io';
import 'dart:async';
import 'package:connectivity/connectivity.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:html/parser.dart';
import 'package:tsacdop/state/download_state.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
@ -56,6 +58,26 @@ class _PodcastDetailState extends State<PodcastDetail> {
msg: 'Updated $result Episodes',
gravity: ToastGravity.TOP,
);
bool autoDownload = await dbHelper.getAutoDownload(podcastLocal.id);
if (autoDownload) {
var result = await Connectivity().checkConnectivity();
KeyValueStorage autoDownloadStorage =
KeyValueStorage(autoDownloadNetworkKey);
int autoDownloadNetwork = await autoDownloadStorage.getInt();
if (autoDownloadNetwork == 1) {
List<EpisodeBrief> episodes =
await dbHelper.getNewEpisodes(podcastLocal.id);
episodes.forEach((episode) {
DownloadState().startTask(episode, showNotification: false);
});
} else if (result == ConnectivityResult.wifi) {
List<EpisodeBrief> episodes =
await dbHelper.getNewEpisodes(podcastLocal.id);
episodes.forEach((episode) {
DownloadState().startTask(episode, showNotification: false);
});
}
}
// Provider.of<GroupList>(context, listen: false)
// .updatePodcast(podcastLocal.id);
} else {

View File

@ -5,14 +5,12 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
import 'package:line_icons/line_icons.dart';
import '../state/podcast_group.dart';
import '../type/podcastlocal.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../podcasts/podcastdetail.dart';
import '../util/pageroute.dart';
import '../util/colorize.dart';
import '../util/duraiton_picker.dart';
import '../util/context_extension.dart';
@ -98,8 +96,11 @@ class _PodcastCardState extends State<PodcastCard>
}
_setAutoDownload(String id, bool boo) async {
DBHelper dbHelper = DBHelper();
await dbHelper.saveAutoDownload(id, boo);
bool permission = await _checkPermmison();
if (permission) {
DBHelper dbHelper = DBHelper();
await dbHelper.saveAutoDownload(id, boo);
}
}
Future<bool> _getAutoDownload(String id) async {
@ -107,6 +108,21 @@ class _PodcastCardState extends State<PodcastCard>
return await dbHelper.getAutoDownload(id);
}
Future<bool> _checkPermmison() async {
PermissionStatus permission = await Permission.storage.status;
if (permission != PermissionStatus.granted) {
Map<Permission, PermissionStatus> permissions =
await [Permission.storage].request();
if (permissions[Permission.storage] == PermissionStatus.granted) {
return true;
} else {
return false;
}
} else {
return true;
}
}
String _stringForSeconds(double seconds) {
if (seconds == null) return null;
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
@ -344,13 +360,27 @@ class _PodcastCardState extends State<PodcastCard>
initialData: false,
builder: (context, snapshot) {
return _buttonOnMenu(
icon: Icon(
LineIcons.cloud_download_alt_solid,
size: _value == 0 ? 1 : 20 * _value,
color: snapshot.data
? context.accentColor
: null),
tooltip: 'AutoDownload',
icon: Container(
child: Icon(Icons.done_all,
size: _value * 15,
color: snapshot.data
? Colors.white
: null),
height: _value == 0 ? 1 : 18 * _value,
width: _value == 0 ? 1 : 18 * _value,
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: snapshot.data
? context.accentColor
: context.textTheme.subtitle1
.color),
shape: BoxShape.circle,
color: snapshot.data
? context.accentColor
: null),
),
tooltip: 'Auto Download',
onTap: () {
_setAutoDownload(widget.podcastLocal.id,
!snapshot.data);

View File

@ -58,7 +58,6 @@ class _DownloadsManageState extends State<DownloadsManage> {
_delSelectedEpisodes() async {
setState(() => _clearing = true);
await Future.forEach(_selectedList, (EpisodeBrief episode) async {
print(episode.downloaded);
await FlutterDownloader.remove(
taskId: episode.downloaded, shouldDeleteContent: true);
var dbHelper = DBHelper();
@ -212,7 +211,6 @@ class _DownloadsManageState extends State<DownloadsManage> {
value: _selectedList
.contains(_episodes[index]),
onChanged: (bool boo) {
print(boo);
if (boo) {
setState(() => _selectedList
.add(_episodes[index]));

View File

@ -117,7 +117,6 @@ class _PlayedHistoryState extends State<PlayedHistory>
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
//statusBarColor: Theme.of(context).primaryColor,
),
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,

View File

@ -5,6 +5,7 @@ import '../util/context_extension.dart';
import '../util/episodegrid.dart';
import '../util/custompaint.dart';
import '../local_storage/key_value_storage.dart';
import 'popup_menu.dart';
class LayoutSetting extends StatefulWidget {
const LayoutSetting({Key key}) : super(key: key);
@ -23,7 +24,7 @@ class _LayoutSettingState extends State<LayoutSetting> {
Widget _gridOptions(BuildContext context,
{String key, Layout layout, Layout option, double scale}) =>
Padding(
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0, left: 20.0),
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
child: InkWell(
onTap: () async {
KeyValueStorage storage = KeyValueStorage(key);
@ -34,7 +35,9 @@ class _LayoutSettingState extends State<LayoutSetting> {
child: Container(
height: 30,
width: 50,
color: layout == option ? context.accentColor : Colors.transparent,
color: layout == option
? context.accentColor
: context.primaryColorDark,
alignment: Alignment.center,
child: SizedBox(
height: 10,
@ -80,6 +83,32 @@ class _LayoutSettingState extends State<LayoutSetting> {
});
}
Widget _setDefaultGridView(BuildContext context, {String text, String key}) {
return Padding(
padding: EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
child: context.width > 360
? Row(
children: [
Text(
text,
),
Spacer(),
_setDefaultGrid(context, key: key),
],
)
: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
text,
),
_setDefaultGrid(context, key: key),
],
),
);
}
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
@ -106,7 +135,7 @@ class _LayoutSettingState extends State<LayoutSetting> {
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 80),
alignment: Alignment.centerLeft,
child: Text('Default grid view',
child: Text('Episode popup menu',
style: Theme.of(context)
.textTheme
.bodyText1
@ -118,44 +147,42 @@ class _LayoutSettingState extends State<LayoutSetting> {
scrollDirection: Axis.vertical,
children: <Widget>[
ListTile(
contentPadding:
EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
// leading: Icon(Icons.colorize),
title: Text(
'Podcast page',
),
subtitle:
_setDefaultGrid(context, key: podcastLayoutKey),
),
ListTile(
contentPadding:
EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
// leading: Icon(Icons.colorize),
title: Text(
'Recent tab in homepage',
),
subtitle:
_setDefaultGrid(context, key: recentLayoutKey),
),
ListTile(
contentPadding:
EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
// leading: Icon(Icons.colorize),
title: Text(
'Favorite tab in homepage',
),
subtitle: _setDefaultGrid(context, key: favLayoutKey),
),
ListTile(
contentPadding:
EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
// leading: Icon(Icons.colorize),
title: Text(
'Download tab in homepage',
),
subtitle: _setDefaultGrid(context, key: downloadLayoutKey),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PopupMenuSetting())),
contentPadding: EdgeInsets.only(left: 80.0, right: 20),
title: Text('Episode popup menu'),
subtitle: Text('Change the menu when long tap episode'),
),
Divider(height: 2),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 80),
alignment: Alignment.centerLeft,
child: Text('Default grid view',
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(
color: Theme.of(context).accentColor)),
),
ListView(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[
_setDefaultGridView(context,
text: 'Podcast page', key: podcastLayoutKey),
_setDefaultGridView(context,
text: 'Recent tab', key: recentLayoutKey),
_setDefaultGridView(context,
text: 'Favorite tab', key: favLayoutKey),
_setDefaultGridView(context,
text: 'Downlaod tab', key: downloadLayoutKey),
Divider(height: 2),
]),
Divider(height: 2)
]),
],
),

View File

@ -0,0 +1,176 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:line_icons/line_icons.dart';
import 'package:flare_flutter/flare_actor.dart';
import '../util/context_extension.dart';
import '../util/custompaint.dart';
import '../local_storage/key_value_storage.dart';
class PopupMenuSetting extends StatefulWidget {
const PopupMenuSetting({Key key}) : super(key: key);
@override
_PopupMenuSettingState createState() => _PopupMenuSettingState();
}
class _PopupMenuSettingState extends State<PopupMenuSetting> {
Future<List<int>> _getEpisodeMenu() async {
KeyValueStorage popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
List<int> list = await popupMenuStorage.getMenu();
return list;
}
_saveEpisodeMene(List<int> list) async {
KeyValueStorage popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
await popupMenuStorage.saveMenu(list);
setState(() {});
}
Widget _popupMenuItem(List<int> menu, int e,
{Widget icon,
String text,
String description = '',
bool enable = false}) {
return Padding(
key: ObjectKey(text),
padding: EdgeInsets.only(left: 60.0, right: 20),
child: ListTile(
leading: icon,
title: Text(text),
subtitle: Text(description),
trailing: Checkbox(
value: e < 10,
onChanged: e == 0
? null
: (bool boo) {
if (boo && e >= 10) {
int index = menu.indexOf(e);
menu.remove(e);
menu.insert(index, e - 10);
_saveEpisodeMene(menu);
} else if (e < 10) {
int index = menu.indexOf(e);
menu.remove(e);
menu.insert(index, e + 10);
_saveEpisodeMene(menu);
}
})),
);
}
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: context.primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
child: Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: context.primaryColor,
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
color: context.primaryColor,
height: 200,
// color: Colors.red,
child: FlareActor(
'assets/longtap.flr',
alignment: Alignment.center,
animation: 'longtap',
fit: BoxFit.cover,
)),
Divider(height: 2),
Padding(
padding: EdgeInsets.symmetric(vertical: 10),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 80),
alignment: Alignment.centerLeft,
child: Text('Episode popup menu',
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(color: Theme.of(context).accentColor)),
),
FutureBuilder<List<int>>(
future: _getEpisodeMenu(),
initialData: [0, 1, 12, 13, 14],
builder: (context, snapshot) {
List<int> menu = snapshot.data;
return ListView(
shrinkWrap: true,
children: menu.map<Widget>((int e) {
int i = e % 10;
switch (i) {
case 0:
return _popupMenuItem(menu, e,
icon: Icon(
LineIcons.play_circle_solid,
color: context.accentColor,
),
text: 'Play',
description: 'Play the episode');
break;
case 1:
return _popupMenuItem(menu, e,
icon: Icon(
LineIcons.clock_solid,
color: Colors.cyan,
),
text: 'Later',
description: 'Add episode to playlist');
break;
case 2:
return _popupMenuItem(menu, e,
icon: Icon(
LineIcons.heart,
color: Colors.red,
size: 21
),
text: 'Like',
description: 'Add episode to favorite');
break;
case 3:
return _popupMenuItem(menu, e,
icon: SizedBox(
width: 23,
height: 23,
child: CustomPaint(
painter: ListenedAllPainter(Colors.blue,
stroke: 1.5)),
),
text: 'Mark Listened',
description: 'Mark episode as listened');
break;
case 4:
return _popupMenuItem(menu, e,
icon: Icon(
LineIcons.download_solid,
color: Colors.green,
),
text: 'Download',
description: 'Download episode');
break;
default:
return Text('Text');
break;
}
}).toList(),
);
}),
],
),
)),
);
}
}

View File

@ -20,7 +20,7 @@ class _StorageSettingState extends State<StorageSetting>
Animation<double> _animation;
_getCacheMax() async {
int cache = await cacheStorage.getInt();
int value = cache == 0 ? 500 : cache ~/ (1024 * 1024);
int value = cache == 0 ? 200 : cache ~/ (1024 * 1024);
if (value > 100) {
_controller = AnimationController(
vsync: this, duration: Duration(milliseconds: value * 2));
@ -38,6 +38,17 @@ class _StorageSettingState extends State<StorageSetting>
}
}
Future<bool> _getAutoDownloadNetwork() async {
KeyValueStorage storage = KeyValueStorage(autoDownloadNetworkKey);
int value = await storage.getInt();
return value != 0;
}
_setAudtDownloadNetwork(bool boo) async {
KeyValueStorage storage = KeyValueStorage(autoDownloadNetworkKey);
await storage.saveInt(boo ? 1 : 0);
}
double _value;
@override
@ -96,29 +107,50 @@ class _StorageSettingState extends State<StorageSetting>
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[
ListTile(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DownloadsManage())),
contentPadding: EdgeInsets.only(
left: 80.0, right: 25, bottom: 10, top: 10),
title: Text('Ask before using cellular data'),
subtitle: Text(
'Ask to confirm when using cellular data to download episodes.'),
trailing: Selector<SettingState, bool>(
selector: (_, settings) =>
settings.downloadUsingData,
builder: (_, data, __) {
return Switch(
Selector<SettingState, bool>(
selector: (_, settings) => settings.downloadUsingData,
builder: (_, data, __) {
return ListTile(
onTap: () => settings.downloadUsingData = !data,
contentPadding: EdgeInsets.only(
left: 80.0, right: 25, bottom: 10, top: 10),
title: Text('Ask before using cellular data'),
subtitle: Text(
'Ask to confirm when using cellular data to download episodes.'),
trailing: Switch(
value: data,
onChanged: (value) =>
settings.downloadUsingData = value,
);
},
),
),
);
},
),
Divider(height: 2),
FutureBuilder<bool>(
future: _getAutoDownloadNetwork(),
initialData: false,
builder: (context, snapshot) {
return ListTile(
onTap: () async {
_setAudtDownloadNetwork(!snapshot.data);
setState(() {});
},
contentPadding: EdgeInsets.only(
left: 80.0, right: 25, bottom: 10, top: 10),
title:
Text('Auto download using cellular data'),
subtitle: Text(
'You can set podcast auto download in group manage page.'),
trailing: Switch(
value: snapshot.data,
onChanged: (value) async {
await _setAudtDownloadNetwork(value);
setState(() {});
},
),
);
}),
Divider(height: 2),
],
),
]),
@ -151,7 +183,7 @@ class _StorageSettingState extends State<StorageSetting>
builder: (context) => DownloadsManage())),
contentPadding: EdgeInsets.symmetric(horizontal: 80.0),
title: Text('Downloads'),
subtitle: Text('Manage doanloaded audio files'),
subtitle: Text('Manage downloaded audio files'),
),
Divider(height: 2),
ListTile(
@ -175,8 +207,10 @@ class _StorageSettingState extends State<StorageSetting>
left: 60.0, right: 20.0, bottom: 10.0),
child: SliderTheme(
data: Theme.of(context).sliderTheme.copyWith(
showValueIndicator: ShowValueIndicator.always,
),
showValueIndicator: ShowValueIndicator.always,
trackHeight: 2,
thumbShape:
RoundSliderThumbShape(enabledThumbRadius: 6)),
child: Slider(
label: '${_value ~/ 100 * 100} Mb',
activeColor: context.accentColor,

View File

@ -155,6 +155,8 @@ class AudioPlayerNotifier extends ChangeNotifier {
int _currentPosition;
double _currentSpeed = 1;
BehaviorSubject<List<MediaItem>> queueSubject;
//Update episode card when setting changed
bool _episodeState = false;
BasicPlaybackState get audioState => _audioState;
@ -176,6 +178,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
int get timeLeft => _timeLeft;
double get switchValue => _switchValue;
double get currentSpeed => _currentSpeed;
bool get episodeState => _episodeState;
set setSwitchValue(double value) {
_switchValue = value;
@ -193,6 +196,11 @@ class AudioPlayerNotifier extends ChangeNotifier {
notifyListeners();
}
set setEpisodeState(bool boo) {
_episodeState = !_episodeState;
notifyListeners();
}
Future _getAutoPlay() async {
int i = await autoPlayStorage.getInt();
_autoPlay = i == 0 ? true : false;
@ -315,7 +323,6 @@ class AudioPlayerNotifier extends ChangeNotifier {
queueSubject.addStream(
AudioService.queueStream.distinct().where((event) => event != null));
queueSubject.stream.listen((event) {
print(event.length);
if (event.length == _queue.playlist.length - 1 &&
_audioState == BasicPlaybackState.skippingToNext) {
if (event.length == 0 || _stopOnComplete) {
@ -506,7 +513,6 @@ class AudioPlayerNotifier extends ChangeNotifier {
}
sliderSeek(double val) async {
print(val.toString());
if (_audioState != BasicPlaybackState.connecting &&
_audioState != BasicPlaybackState.none) {
_noSlide = false;
@ -633,6 +639,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
AudioPlayer _audioPlayer = AudioPlayer();
Completer _completer = Completer();
BasicPlaybackState _skipState;
bool _lostFocus;
bool _playing;
bool _stopAtEnd;
int cacheMax;
@ -662,7 +669,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
@override
Future<void> onStart() async {
_stopAtEnd = false;
_lostFocus = false;
var playerStateSubscription = _audioPlayer.playbackStateStream
.where((state) => state == AudioPlaybackState.completed)
.listen((state) {
@ -792,9 +799,12 @@ class AudioPlayerTask extends BackgroundAudioTask {
@override
void onPause() {
if (_skipState == null) {
if (_playing == null) {}
_playing = false;
_audioPlayer.pause();
if (_playing == null) {
} else if (_audioPlayer.playbackEvent.state ==
AudioPlaybackState.playing) {
_playing = false;
_audioPlayer.pause();
}
}
}
@ -857,32 +867,45 @@ class AudioPlayerTask extends BackgroundAudioTask {
milliseconds: AudioServiceBackground.state.position + 30 * 1000));
}
@override
void onRewind() {
_audioPlayer.seek(Duration(
milliseconds: AudioServiceBackground.state.position - 10 * 1000));
}
@override
void onAudioFocusLost() {
if (_skipState == null) {
if (_playing == null ||
_audioPlayer.playbackState == AudioPlaybackState.none ||
_audioPlayer.playbackState == AudioPlaybackState.connecting) {}
_playing = false;
_audioPlayer.pause();
if (_playing == null) {
} else if (_audioPlayer.playbackEvent.state ==
AudioPlaybackState.playing) {
_playing = false;
_lostFocus = true;
_audioPlayer.pause();
}
}
}
@override
void onAudioBecomingNoisy() {
if (_skipState == null) {
if (_playing == null) {}
_playing = false;
_audioPlayer.pause();
if (_playing == null) {
} else if (_audioPlayer.playbackEvent.state ==
AudioPlaybackState.playing) {
_playing = false;
_audioPlayer.pause();
}
}
}
@override
void onAudioFocusGained() {
if (_skipState == null) {
if (_playing == null) {}
_playing = true;
_audioPlayer.play();
if (_lostFocus) {
_lostFocus = false;
_playing = true;
_audioPlayer.play();
}
}
}

View File

@ -93,7 +93,8 @@ class DownloadState extends ChangeNotifier {
final completeTask = await FlutterDownloader.loadTasksWithRawQuery(
query: "SELECT * FROM task WHERE task_id = '${episodeTask.taskId}'");
String filePath = 'file://' +
path.join(completeTask.first.savedDir, completeTask.first.filename);
path.join(completeTask.first.savedDir,
Uri.encodeComponent(completeTask.first.filename));
dbHelper.saveMediaId(
episodeTask.episode.enclosureUrl, filePath, episodeTask.taskId);
EpisodeBrief episode =
@ -124,7 +125,7 @@ class DownloadState extends ChangeNotifier {
super.dispose();
}
Future startTask(EpisodeBrief episode) async {
Future startTask(EpisodeBrief episode, {bool showNotification = true}) async {
final dir = await getExternalStorageDirectory();
String localPath = path.join(dir.path, episode.feedTitle);
final saveDir = Directory(localPath);
@ -137,10 +138,7 @@ class DownloadState extends ChangeNotifier {
now.month.toString() +
now.day.toString() +
now.second.toString();
String title = episode.title.trim().substring(0, 1) == '#'
? episode.title.trim().substring(1)
: episode.title.trim();
String fileName = title +
String fileName = episode.title +
datePlus +
'.' +
episode.enclosureUrl.split('/').last.split('.').last;
@ -148,12 +146,12 @@ class DownloadState extends ChangeNotifier {
fileName: fileName,
url: episode.enclosureUrl,
savedDir: localPath,
showNotification: true,
showNotification: showNotification,
openFileFromNotification: false,
);
_episodeTasks.add(EpisodeTask(episode, taskId));
var dbHelper = DBHelper();
await dbHelper.saveDownloaded(taskId, episode.enclosureUrl);
await dbHelper.saveDownloaded(episode.enclosureUrl, taskId);
notifyListeners();
}

View File

@ -2,10 +2,13 @@ import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:flutter_isolate/flutter_isolate.dart';
import 'package:tsacdop/local_storage/key_value_storage.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:connectivity/connectivity.dart';
import '../local_storage/key_value_storage.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../type/podcastlocal.dart';
import '../type/episodebrief.dart';
import 'download_state.dart';
enum RefreshState { none, fetch, error }
@ -68,13 +71,32 @@ class RefreshWorker extends ChangeNotifier {
Future<void> refreshIsolateEntryPoint(SendPort sendPort) async {
KeyValueStorage refreshstorage = KeyValueStorage(refreshdateKey);
KeyValueStorage autoDownloadStorage = KeyValueStorage(autoDownloadNetworkKey);
await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch);
var dbHelper = DBHelper();
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
//int i = 0;
await Future.forEach<PodcastLocal>(podcastList, (podcastLocal) async {
sendPort.send([podcastLocal.title, 1]);
await dbHelper.updatePodcastRss(podcastLocal);
int updateCount = await dbHelper.updatePodcastRss(podcastLocal);
bool autoDownload = await dbHelper.getAutoDownload(podcastLocal.id);
if (autoDownload && updateCount > 0) {
var result = await Connectivity().checkConnectivity();
int autoDownloadNetwork = await autoDownloadStorage.getInt();
if (autoDownloadNetwork == 1) {
List<EpisodeBrief> episodes =
await dbHelper.getNewEpisodes(podcastLocal.id);
episodes.forEach((episode) {
DownloadState().startTask(episode, showNotification: false);
});
} else if (result == ConnectivityResult.wifi) {
List<EpisodeBrief> episodes =
await dbHelper.getNewEpisodes(podcastLocal.id);
episodes.forEach((episode) {
DownloadState().startTask(episode, showNotification: false);
});
}
}
print('Refresh ' + podcastLocal.title);
});
// KeyValueStorage refreshcountstorage = KeyValueStorage('refreshcount');

View File

@ -1,8 +1,8 @@
import 'dart:io';
import 'package:connectivity/connectivity.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:workmanager/workmanager.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../local_storage/key_value_storage.dart';
@ -24,11 +24,23 @@ void callbackDispatcher() {
await dbHelper.updatePodcastRss(podcastLocal, removeMark: lastWork);
bool autoDownload = await dbHelper.getAutoDownload(podcastLocal.id);
if (autoDownload && updateCount > 0) {
List<EpisodeBrief> episodes =
await dbHelper.getNewEpisodes(podcastLocal.id);
episodes.forEach((episode) {
DownloadState().startTask(episode);
});
var result = await Connectivity().checkConnectivity();
KeyValueStorage autoDownloadStorage =
KeyValueStorage(autoDownloadNetworkKey);
int autoDownloadNetwork = await autoDownloadStorage.getInt();
if (autoDownloadNetwork == 1) {
List<EpisodeBrief> episodes =
await dbHelper.getNewEpisodes(podcastLocal.id);
episodes.forEach((episode) {
DownloadState().startTask(episode, showNotification: false);
});
} else if (result == ConnectivityResult.wifi) {
List<EpisodeBrief> episodes =
await dbHelper.getNewEpisodes(podcastLocal.id);
episodes.forEach((episode) {
DownloadState().startTask(episode, showNotification: false);
});
}
}
print('Refresh ' + podcastLocal.title);
});

View File

@ -5,6 +5,7 @@ extension ContextExtension on BuildContext{
Color get accentColor => Theme.of(this).accentColor;
Color get scaffoldBackgroundColor => Theme.of(this).scaffoldBackgroundColor;
Color get primaryColorDark => Theme.of(this).primaryColorDark;
Color get textColor => Theme.of(this).textTheme.bodyText1.color;
Brightness get brightness => Theme.of(this).brightness;
double get width => MediaQuery.of(this).size.width;
double get height => MediaQuery.of(this).size.width;

View File

@ -11,9 +11,11 @@ import 'package:auto_animated/auto_animated.dart';
import 'open_container.dart';
import '../state/audiostate.dart';
import '../state/download_state.dart';
import '../type/episodebrief.dart';
import '../episodes/episodedetail.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../local_storage/key_value_storage.dart';
import 'colorize.dart';
import 'context_extension.dart';
import 'custompaint.dart';
@ -51,6 +53,39 @@ class EpisodeGrid extends StatelessWidget {
return await dbHelper.isLiked(episode.enclosureUrl);
}
Future<List<int>> _getEpisodeMenu() async {
KeyValueStorage popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
List<int> list = await popupMenuStorage.getMenu();
return list;
}
Future<bool> _isDownloaded(EpisodeBrief episode) async {
DBHelper dbHelper = DBHelper();
return await dbHelper.isDownloaded(episode.enclosureUrl);
}
_markListened(EpisodeBrief episode) async {
DBHelper dbHelper = DBHelper();
bool marked = await dbHelper.checkMarked(episode);
if (!marked) {
final PlayHistory history =
PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
await dbHelper.saveHistory(history);
}
}
Future<int> _saveLiked(String url) async {
var dbHelper = DBHelper();
int result = await dbHelper.setLiked(url);
return result;
}
Future<int> _setUnliked(String url) async {
var dbHelper = DBHelper();
int result = await dbHelper.setUniked(url);
return result;
}
String _stringForSeconds(double seconds) {
if (seconds == null) return null;
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
@ -171,15 +206,23 @@ class EpisodeGrid extends StatelessWidget {
color: color,
fontStyle: FontStyle.italic),
);
@override
Widget build(BuildContext context) {
double _width = MediaQuery.of(context).size.width;
double _width = context.width;
Offset _offset;
_showPopupMenu(Offset offset, EpisodeBrief episode, BuildContext context,
bool isPlaying, bool isInPlaylist) async {
{bool isPlaying, bool isInPlaylist}) async {
bool isLiked, isDownload;
int isListened;
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
var downloader = Provider.of<DownloadState>(context, listen: false);
double left = offset.dx;
double top = offset.dy;
List<int> 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<int>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
@ -203,39 +246,134 @@ class EpisodeGrid extends StatelessWidget {
],
),
),
PopupMenuItem(
value: 1,
child: Row(
children: <Widget>[
Icon(
LineIcons.clock_solid,
color: Colors.red,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 2),
),
!isInPlaylist ? Text('Later') : Text('Remove')
],
)),
menuList.contains(1)
? PopupMenuItem(
value: 1,
child: Row(
children: <Widget>[
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: <Widget>[
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: <Widget>[
SizedBox(
width: 23,
height: 23,
child: CustomPaint(
painter:
ListenedAllPainter(Colors.blue, stroke: 1.5)),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 2),
),
isListened > 0.95
? Text('Listened',
style: TextStyle(
color: context.textColor.withOpacity(0.5)))
: Text('Mark\nListened')
],
))
: null,
menuList.contains(4)
? PopupMenuItem(
value: 4,
child: Row(
children: <Widget>[
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) {
if (value == 0) {
if (!isPlaying) audio.episodeLoad(episode);
} else if (value == 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,
);
}
).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 < 0.95) {
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;
}
});
}
@ -273,9 +411,11 @@ class EpisodeGrid extends StatelessWidget {
opacity: Tween<double>(begin: index < initNum ? 0 : 1, end: 1)
.animate(animation),
child: Selector<AudioPlayerNotifier,
Tuple2<EpisodeBrief, List<String>>>(
selector: (_, audio) => Tuple2(audio?.episode,
audio.queue.playlist.map((e) => e.enclosureUrl).toList()),
Tuple3<EpisodeBrief, List<String>, bool>>(
selector: (_, audio) => Tuple3(
audio?.episode,
audio.queue.playlist.map((e) => e.enclosureUrl).toList(),
audio.episodeState),
builder: (_, data, __) => OpenContainerWrapper(
episode: episodes[index],
closedBuilder: (context, action, boo) => FutureBuilder<int>(
@ -310,12 +450,13 @@ class EpisodeGrid extends StatelessWidget {
details.globalPosition.dx,
details.globalPosition.dy),
onLongPress: () => _showPopupMenu(
_offset,
episodes[index],
context,
data.item1 == episodes[index],
data.item2
.contains(episodes[index].enclosureUrl)),
_offset,
episodes[index],
context,
isPlaying: data.item1 == episodes[index],
isInPlaylist: data.item2
.contains(episodes[index].enclosureUrl),
),
onTap: action,
child: Container(
padding: const EdgeInsets.all(8.0),

View File

@ -1,7 +1,7 @@
name: tsacdop
description: An easy-use podacasts player.
version: 0.3.3
version: 0.3.4
environment:
sdk: ">=2.6.0 <3.0.0"