Support auto download.

This commit is contained in:
stonegate 2020-06-11 23:13:10 +08:00
parent f4b56938ae
commit e69a2dbc00
18 changed files with 138 additions and 125 deletions

View File

@ -267,7 +267,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
? (_description.contains('<'))
? Html(
padding: EdgeInsets.only(
left: 20.0, right: 20, bottom: 10),
left: 20.0, right: 20, bottom: 20),
defaultTextStyle:
// GoogleFonts.libreBaskerville(
GoogleFonts.martel(
@ -290,7 +290,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
padding: EdgeInsets.only(
left: 20.0,
right: 20.0,
bottom: 10.0),
bottom: 20.0),
alignment: Alignment.topLeft,
child: SelectableLinkify(
onOpen: (link) {
@ -390,18 +390,16 @@ class _MenuBarState extends State<MenuBar> {
return await dbHelper.getPosition(episode);
}
Future<int> saveLiked(String url) async {
saveLiked(String url) async {
var dbHelper = DBHelper();
int result = await dbHelper.setLiked(url);
await dbHelper.setLiked(url);
setState(() {});
return result;
}
Future<int> setUnliked(String url) async {
setUnliked(String url) async {
var dbHelper = DBHelper();
int result = await dbHelper.setUniked(url);
await dbHelper.setUniked(url);
setState(() {});
return result;
}
Future<bool> _isLiked(EpisodeBrief episode) async {

View File

@ -7,7 +7,6 @@ import 'package:provider/provider.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:tuple/tuple.dart';
import 'package:line_icons/line_icons.dart';
import 'package:feature_discovery/feature_discovery.dart';
import '../type/episodebrief.dart';
import '../state/podcast_group.dart';
@ -507,16 +506,14 @@ class ShowEpisode extends StatelessWidget {
}
}
Future<int> _saveLiked(String url) async {
_saveLiked(String url) async {
var dbHelper = DBHelper();
int result = await dbHelper.setLiked(url);
return result;
await dbHelper.setLiked(url);
}
Future<int> _setUnliked(String url) async {
_setUnliked(String url) async {
var dbHelper = DBHelper();
int result = await dbHelper.setUniked(url);
return result;
await dbHelper.setUniked(url);
}
_showPopupMenu(Offset offset, EpisodeBrief episode, BuildContext context,

View File

@ -1,8 +1,14 @@
import 'package:connectivity/connectivity.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../local_storage/key_value_storage.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../state/podcast_group.dart';
import '../state/subscribe_podcast.dart';
import '../state/download_state.dart';
import '../state/refresh_podcast.dart';
import '../type/episodebrief.dart';
import '../util/context_extension.dart';
class Import extends StatelessWidget {
@ -24,6 +30,30 @@ class Import extends StatelessWidget {
);
}
_autoDownloadNew(BuildContext context) async {
final DBHelper dbHelper = DBHelper();
var downloader = Provider.of<DownloadState>(context, listen: false);
var result = await Connectivity().checkConnectivity();
KeyValueStorage autoDownloadStorage =
KeyValueStorage(autoDownloadNetworkKey);
int autoDownloadNetwork = await autoDownloadStorage.getInt();
if (autoDownloadNetwork == 1) {
List<EpisodeBrief> episodes = await dbHelper.getNewEpisodes('all');
// For safety
if (episodes.length < 100)
episodes.forEach((episode) {
downloader.startTask(episode, showNotification: false);
});
} else if (result == ConnectivityResult.wifi) {
List<EpisodeBrief> episodes = await dbHelper.getNewEpisodes('all');
//For safety
if (episodes.length < 100)
episodes.forEach((episode) {
downloader.startTask(episode, showNotification: false);
});
}
}
@override
Widget build(BuildContext context) {
GroupList groupList = Provider.of<GroupList>(context, listen: false);
@ -39,7 +69,7 @@ class Import extends StatelessWidget {
groupList.subscribeNewPodcast(item.id);
return importColumn("Fetch data ${item.title}", context);
case SubscribeState.fetch:
// groupList.updatePodcast(item.id);
// groupList.updatePodcast(item.id);
return importColumn("Subscribe success ${item.title}", context);
case SubscribeState.exist:
return importColumn(
@ -57,7 +87,7 @@ class Import extends StatelessWidget {
RefreshItem item = refreshWorker.currentRefreshItem;
if (refreshWorker.complete) {
groupList.updateGroups();
// audio.addNewEpisode('all');
_autoDownloadNew(context);
}
switch (item.refreshState) {
case RefreshState.fetch:

View File

@ -76,7 +76,7 @@ class _PopupMenuState extends State<PopupMenu> {
try {
String opml = file.readAsStringSync();
var content = xml.parse(opml);
var content = xml.XmlDocument.parse(opml);
var total = content
.findAllElements('outline')
.map((ele) => OmplOutline.parse(ele))
@ -184,11 +184,6 @@ class _PopupMenuState extends State<PopupMenu> {
),
),
),
// PopupMenuItem(
// value: 3,
// child: setting.theme != 2 ? Text('Night Mode') : Text('Light Mode'),
// ),
PopupMenuItem(
value: 4,
child: Container(

View File

@ -273,8 +273,8 @@ class DBHelper {
Future<int> isListened(String url) async {
var dbClient = await database;
int i = 0;
List<Map> list =
await dbClient.rawQuery("SELECT listen_time FROM PlayHistory WHERE enclosure_url = ?", [url]);
List<Map> list = await dbClient.rawQuery(
"SELECT listen_time FROM PlayHistory WHERE enclosure_url = ?", [url]);
if (list.length == 0)
return 0;
else {
@ -648,7 +648,7 @@ class DBHelper {
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit, E.liked,
E.downloaded, P.primaryColor, E.media_id, E.is_new, P.skip_seconds
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE E.is_new = 1 ORDER BY E.milliseconds ASC""",
WHERE E.is_new = 1 AND E.downloaded = 'ND' AND P.auto_download = 1 ORDER BY E.milliseconds ASC""",
);
else
list = await dbClient.rawQuery(
@ -656,7 +656,7 @@ class DBHelper {
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit, E.liked,
E.downloaded, P.primaryColor, E.media_id, E.is_new, P.skip_seconds
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE E.is_new = 1 AND E.feed_id = ? ORDER BY E.milliseconds ASC""",
WHERE E.is_new = 1 AND E.downloaded = 'ND' AND E.feed_id = ? ORDER BY E.milliseconds ASC""",
[id]);
if (list.length > 0)
for (int x = 0; x < list.length; x++) {
@ -961,9 +961,11 @@ class DBHelper {
return episodes;
}
Future<int> removeAllNewMark() async {
removeAllNewMark() async {
var dbClient = await database;
return await dbClient.rawUpdate("UPDATE Episodes SET is_new = 0 ");
await dbClient.transaction((txn) async {
await txn.rawUpdate("UPDATE Episodes SET is_new = 0 ");
});
}
Future<List<EpisodeBrief>> getGroupNewRssItem(List<String> group) async {
@ -1000,20 +1002,23 @@ class DBHelper {
return episodes;
}
Future<int> removeGroupNewMark(List<String> group) async {
removeGroupNewMark(List<String> group) async {
var dbClient = await database;
if (group.length > 0) {
List<String> s = group.map<String>((e) => "'$e'").toList();
return await dbClient.rawUpdate(
"UPDATE Episodes SET is_new = 0 WHERE feed_id in (${s.join(',')})");
await dbClient.transaction((txn) async {
await txn.rawUpdate(
"UPDATE Episodes SET is_new = 0 WHERE feed_id in (${s.join(',')})");
});
}
return 0;
}
Future<int> removeEpisodeNewMark(String url) async {
removeEpisodeNewMark(String url) async {
var dbClient = await database;
return await dbClient.rawUpdate(
"UPDATE Episodes SET is_new = 0 WHERE enclosure_url = ?", [url]);
await dbClient.transaction((txn) async {
await txn.rawUpdate(
"UPDATE Episodes SET is_new = 0 WHERE enclosure_url = ?", [url]);
});
}
Future<List<EpisodeBrief>> getLikedRssItem(int i, int sortBy) async {
@ -1069,20 +1074,22 @@ class DBHelper {
return episodes;
}
Future<int> setLiked(String url) async {
setLiked(String url) async {
var dbClient = await database;
int milliseconds = DateTime.now().millisecondsSinceEpoch;
int count = await dbClient.rawUpdate(
"UPDATE Episodes SET liked = 1, liked_date = ? WHERE enclosure_url= ?",
[milliseconds, url]);
return count;
await dbClient.transaction((txn) async {
await txn.rawUpdate(
"UPDATE Episodes SET liked = 1, liked_date = ? WHERE enclosure_url= ?",
[milliseconds, url]);
});
}
Future<int> setUniked(String url) async {
setUniked(String url) async {
var dbClient = await database;
int count = await dbClient.rawUpdate(
"UPDATE Episodes SET liked = 0 WHERE enclosure_url = ?", [url]);
return count;
await dbClient.transaction((txn) async {
await txn.rawUpdate(
"UPDATE Episodes SET liked = 0 WHERE enclosure_url = ?", [url]);
});
}
Future<bool> isLiked(String url) async {

View File

@ -46,7 +46,6 @@ class _PodcastDetailState extends State<PodcastDetail> {
bool _scroll;
Future _updateRssItem(PodcastLocal podcastLocal) async {
var dbHelper = DBHelper();
final result = await dbHelper.updatePodcastRss(podcastLocal);
if (result == 0) {
Fluttertoast.showToast(
@ -58,8 +57,10 @@ class _PodcastDetailState extends State<PodcastDetail> {
msg: 'Updated $result Episodes',
gravity: ToastGravity.TOP,
);
bool autoDownload = await dbHelper.getAutoDownload(podcastLocal.id);
if (autoDownload) {
var downloader = Provider.of<DownloadState>(context, listen: false);
var result = await Connectivity().checkConnectivity();
KeyValueStorage autoDownloadStorage =
KeyValueStorage(autoDownloadNetworkKey);
@ -67,19 +68,21 @@ class _PodcastDetailState extends State<PodcastDetail> {
if (autoDownloadNetwork == 1) {
List<EpisodeBrief> episodes =
await dbHelper.getNewEpisodes(podcastLocal.id);
episodes.forEach((episode) {
DownloadState().startTask(episode, showNotification: false);
});
// For safety
if (episodes.length < 100)
episodes.forEach((episode) {
downloader.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);
});
//For safety
if (episodes.length < 100)
episodes.forEach((episode) {
downloader.startTask(episode, showNotification: false);
});
}
}
// Provider.of<GroupList>(context, listen: false)
// .updatePodcast(podcastLocal.id);
} else {
Fluttertoast.showToast(
msg: 'Update failed, network error',

View File

@ -361,7 +361,7 @@ class _PodcastCardState extends State<PodcastCard>
builder: (context, snapshot) {
return _buttonOnMenu(
icon: Container(
child: Icon(Icons.done_all,
child: Icon(Icons.file_download,
size: _value * 15,
color: snapshot.data
? Colors.white
@ -369,20 +369,23 @@ class _PodcastCardState extends State<PodcastCard>
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),
border: snapshot.data
? Border.all(
width: 1,
color: snapshot.data
? context.accentColor
: context.textTheme
.subtitle1.color)
: null,
shape: BoxShape.circle,
color: snapshot.data
? context.accentColor
: null),
),
tooltip: 'Auto Download',
onTap: () {
_setAutoDownload(widget.podcastLocal.id,
onTap: () async {
await _setAutoDownload(
widget.podcastLocal.id,
!snapshot.data);
setState(() {});
},

View File

@ -2,7 +2,6 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart';
import 'package:line_icons/line_icons.dart';
import 'package:provider/provider.dart';

View File

@ -300,7 +300,7 @@ class _PlayedHistoryState extends State<PlayedHistory>
builder: (context, snapshot) {
return snapshot.hasData
? ListView.builder(
// shrinkWrap: true,
// shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
@ -498,7 +498,6 @@ class HistoryChart extends StatelessWidget {
]),
dotData: FlDotData(
show: true,
dotSize: 5,
),
),
],

View File

@ -449,7 +449,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
newEpisodes = await dbHelper.getRecentNewRssItem();
else
newEpisodes = await dbHelper.getGroupNewRssItem(group);
if (newEpisodes.length > 0)
if (newEpisodes.length > 0 && newEpisodes.length < 100)
await Future.forEach(newEpisodes, (episode) async {
await addToPlaylist(episode);
});

View File

@ -80,8 +80,7 @@ class PodcastGroup {
class GroupList extends ChangeNotifier {
List<PodcastGroup> _groups;
DBHelper dbHelper = DBHelper();
UnmodifiableListView<PodcastGroup> get groups =>
UnmodifiableListView(_groups);
List<PodcastGroup> get groups => _groups;
KeyValueStorage storage = KeyValueStorage('groups');
GroupList({List<PodcastGroup> groups}) : _groups = groups ?? [];
@ -112,10 +111,18 @@ class GroupList extends ChangeNotifier {
}
}
_initGroup() async {
storage.getGroups().then((loadgroups) async {
_groups.addAll(loadgroups.map((e) => PodcastGroup.fromEntity(e)));
await Future.forEach(_groups, (group) async {
await group.getPodcasts();
});
});
}
@override
void addListener(VoidCallback listener) {
super.addListener(listener);
loadGroups();
loadGroups().then((value) => super.addListener(listener));
}
Future loadGroups() async {
@ -142,7 +149,7 @@ class GroupList extends ChangeNotifier {
Future addGroup(PodcastGroup podcastGroup) async {
_isLoading = true;
_groups.add(podcastGroup);
_saveGroup();
await _saveGroup();
_isLoading = false;
notifyListeners();
}
@ -154,7 +161,7 @@ class GroupList extends ChangeNotifier {
_groups[0].podcastList.insert(0, podcast);
}
});
_saveGroup();
await _saveGroup();
_groups.remove(podcastGroup);
await _groups[0].getPodcasts();
_isLoading = false;
@ -170,13 +177,13 @@ class GroupList extends ChangeNotifier {
_saveGroup();
}
void _saveGroup() {
storage.saveGroup(_groups.map((it) => it.toEntity()).toList());
_saveGroup() async {
await storage.saveGroup(_groups.map((it) => it.toEntity()).toList());
}
Future subscribe(PodcastLocal podcastLocal) async {
_groups[0].podcastList.insert(0, podcastLocal.id);
_saveGroup();
await _saveGroup();
await dbHelper.savePodcastLocal(podcastLocal);
await _groups[0].getPodcasts();
notifyListeners();
@ -194,8 +201,9 @@ class GroupList extends ChangeNotifier {
}
Future subscribeNewPodcast(String id) async {
_groups[0].podcastList.insert(0, id);
_saveGroup();
if (!_groups[0].podcastList.contains(id))
_groups[0].podcastList.insert(0, id);
await _saveGroup();
await _groups[0].getPodcasts();
notifyListeners();
}
@ -224,7 +232,7 @@ class GroupList extends ChangeNotifier {
list.forEach((s) {
s.podcastList.insert(0, id);
});
_saveGroup();
await _saveGroup();
await Future.forEach(_groups, (group) async {
await group.getPodcasts();
});
@ -239,7 +247,7 @@ class GroupList extends ChangeNotifier {
_groups.forEach((group) async {
group.podcastList.remove(id);
});
_saveGroup();
await _saveGroup();
await dbHelper.delPodcastLocal(id);
await Future.forEach(_groups, (group) async {
await group.getPodcasts();
@ -250,7 +258,7 @@ class GroupList extends ChangeNotifier {
saveOrder(PodcastGroup group) async {
group.podcastList = group.ordereddPodcasts.map((e) => e.id).toList();
_saveGroup();
await _saveGroup();
await group.getPodcasts();
notifyListeners();
}

View File

@ -2,13 +2,10 @@ import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:flutter_isolate/flutter_isolate.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 }
@ -71,33 +68,13 @@ 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]);
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);
print('Refresh ' + podcastLocal.title + updateCount.toString());
});
// KeyValueStorage refreshcountstorage = KeyValueStorage('refreshcount');
// await refreshcountstorage.saveInt(i);

View File

@ -32,13 +32,13 @@ void callbackDispatcher() {
List<EpisodeBrief> episodes =
await dbHelper.getNewEpisodes(podcastLocal.id);
episodes.forEach((episode) {
DownloadState().startTask(episode, showNotification: false);
DownloadState().startTask(episode, showNotification: true);
});
} else if (result == ConnectivityResult.wifi) {
List<EpisodeBrief> episodes =
await dbHelper.getNewEpisodes(podcastLocal.id);
episodes.forEach((episode) {
DownloadState().startTask(episode, showNotification: false);
DownloadState().startTask(episode, showNotification: true);
});
}
}

View File

@ -66,7 +66,6 @@ class SubscribeWorker extends ChangeNotifier {
_setCurrentSubscribeItem(SubscribeItem(message[1], message[0],
subscribeState: SubscribeState.values[message[2]],
id: message.length == 4 ? message[3] : ''));
print(message[2]);
} else if (message is String && message == "done") {
subIsolate.kill();
subIsolate = null;

View File

@ -74,16 +74,14 @@ class EpisodeGrid extends StatelessWidget {
}
}
Future<int> _saveLiked(String url) async {
_saveLiked(String url) async {
var dbHelper = DBHelper();
int result = await dbHelper.setLiked(url);
return result;
await dbHelper.setLiked(url);
}
Future<int> _setUnliked(String url) async {
_setUnliked(String url) async {
var dbHelper = DBHelper();
int result = await dbHelper.setUniked(url);
return result;
await dbHelper.setUniked(url);
}
String _stringForSeconds(double seconds) {
@ -116,9 +114,9 @@ class EpisodeGrid extends StatelessWidget {
),
);
Widget _listenIndicater(BuildContext context,
{EpisodeBrief episode, int isListened}) =>
Center();
//Widget _listenIndicater(BuildContext context,
// {EpisodeBrief episode, int isListened}) =>
// Center();
// Selector<AudioPlayerNotifier, Tuple2<EpisodeBrief, bool>>(
// selector: (_, audio) => Tuple2(audio.episode, audio.playerRunning),
// builder: (_, data, __) {
@ -488,9 +486,9 @@ class EpisodeGrid extends StatelessWidget {
episode: episodes[index],
color: _c),
Spacer(),
// _listenIndicater(context,
// episode: episodes[index],
// isListened: snapshot.data),
// _listenIndicater(context,
// episode: episodes[index],
// isListened: snapshot.data),
_downloadIndicater(context,
episode: episodes[index]),
_isNewIndicator(episodes[index]),

View File

@ -39,7 +39,7 @@ class AtomFeed {
});
factory AtomFeed.parse(String xmlString) {
var document = parse(xmlString);
var document = XmlDocument.parse(xmlString);
XmlElement feedElement;
try {
feedElement = document.findElements("feed").first;

View File

@ -59,7 +59,7 @@ class RssFeed {
});
factory RssFeed.parse(String xmlString) {
var document = parse(xmlString);
var document = XmlDocument.parse(xmlString);
XmlElement channelElement;
try {
channelElement = document.findAllElements("channel").first;

View File

@ -33,7 +33,7 @@ dependencies:
tuple: ^1.0.3
cached_network_image: ^2.2.0+1
workmanager: ^0.2.2
fl_chart: ^0.9.4
fl_chart: ^0.10.0
audio_service: ^0.8.0
flutter_file_dialog: ^0.0.5
flutter_linkify: ^3.1.3
@ -57,8 +57,8 @@ dev_dependencies:
sdk: flutter
dependency_overrides:
flutter_isolate: "1.0.0+13"
xml: "4.1.0"
flutter_isolate: "1.0.0+14"
xml: "4.2.0"
flutter:
assets: