From 1a497a78ed458cb72ce5821d900b782be44457d8 Mon Sep 17 00:00:00 2001 From: stonegate Date: Mon, 8 Jun 2020 02:42:27 +0800 Subject: [PATCH] :construction: Auto download --- lib/local_storage/sqflite_localpodcast.dart | 84 ++++--- lib/podcasts/podcastgroup.dart | 258 +++++++++++--------- lib/state/download_state.dart | 2 + lib/state/settingstate.dart | 44 ++-- 4 files changed, 229 insertions(+), 159 deletions(-) diff --git a/lib/local_storage/sqflite_localpodcast.dart b/lib/local_storage/sqflite_localpodcast.dart index 14ab241..535c213 100644 --- a/lib/local_storage/sqflite_localpodcast.dart +++ b/lib/local_storage/sqflite_localpodcast.dart @@ -22,7 +22,7 @@ class DBHelper { var documentsDirectory = await getDatabasesPath(); String path = join(documentsDirectory, "podcasts.db"); Database theDb = await openDatabase(path, - version: 2, onCreate: _onCreate, onUpgrade: _onUpgrade); + version: 3, onCreate: _onCreate, onUpgrade: _onUpgrade); return theDb; } @@ -32,7 +32,7 @@ class DBHelper { imageUrl TEXT,rssUrl TEXT UNIQUE, primaryColor TEXT, author TEXT, description TEXT, add_date INTEGER, imagePath TEXT, provider TEXT, link TEXT, background_image TEXT DEFAULT '',hosts TEXT DEFAULT '',update_count INTEGER DEFAULT 0, - episode_count INTEGER DEFAULT 0, skip_seconds INTEGER DEFAULT 0)"""); + episode_count INTEGER DEFAULT 0, skip_seconds INTEGER DEFAULT 0, auto_download INTEGER DEFAULT 0)"""); await db .execute("""CREATE TABLE Episodes(id INTEGER PRIMARY KEY,title TEXT, enclosure_url TEXT UNIQUE, enclosure_length INTEGER, pubDate TEXT, @@ -51,7 +51,12 @@ class DBHelper { void _onUpgrade(Database db, int oldVersion, int newVersion) async { if (oldVersion == 1) { await db.execute( - "ALTER TABLE PodcastLocal ADD skip_seconds INTEGER DEFAULT 0"); + "ALTER TABLE PodcastLocal ADD skip_seconds INTEGER DEFAULT 0 "); + await db.execute( + "ALTER TABLE PodcastLocal ADD auto_download INTEGER DEFAULT 0"); + } else if (oldVersion == 2) { + await db.execute( + "ALTER TABLE PodcastLocal ADD auto_download INTEGER DEFAULT 0"); } } @@ -131,6 +136,20 @@ class DBHelper { "UPDATE PodcastLocal SET skip_seconds = ? WHERE id = ?", [seconds, id]); } + Future getAutoDownload(String id) async { + var dbClient = await database; + List list = await dbClient + .rawQuery('SELECT auto_download FROM PodcastLocal WHERE id = ?', [id]); + return list.first['auto_download'] == 1; + } + + Future saveAutoDownload(String id, bool boo) async { + var dbClient = await database; + return await dbClient.rawUpdate( + "UPDATE PodcastLocal SET auto_download = ? WHERE id = ?", + [boo ? 1 : 0, id]); + } + Future checkPodcast(String url) async { var dbClient = await database; List list = await dbClient @@ -813,6 +832,37 @@ class DBHelper { return episodes; } + Future> gettNewRssItem(String id) async { + var dbClient = await database; + List episodes = []; + List list = await dbClient.rawQuery( + """SELECT E.title, E.enclosure_url, E.enclosure_length, + E.milliseconds, P.title as feed_title, E.duration, E.explicit, E.liked, + E.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds + FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id + WHERE is_new = 1 AND downloaded != 'ND' AND P.id = ?ORDER BY E.milliseconds DESC """, + [id], + ); + for (int x = 0; x < list.length; x++) { + episodes.add(EpisodeBrief( + list[x]['title'], + list[x]['enclosure_url'], + list[x]['enclosure_length'], + list[x]['milliseconds'], + list[x]['feed_title'], + list[x]['primaryColor'], + list[x]['liked'], + list[x]['downloaded'], + list[x]['duration'], + list[x]['explicit'], + list[x]['imagePath'], + list[x]['media_id'], + list[x]['is_new'], + list[x]['skip_seconds'])); + } + return episodes; + } + Future removeAllNewMark() async { var dbClient = await database; return await dbClient.rawUpdate("UPDATE Episodes SET is_new = 0 "); @@ -971,34 +1021,6 @@ class DBHelper { return count; } - Future> getDownloadedRssItem() async { - var dbClient = await database; - List episodes = List(); - List list = await dbClient.rawQuery( - """SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath, - P.title as feed_title, E.duration, E.explicit, E.liked, E.downloaded, - P.primaryColor, E.media_id, E.is_new, P.skip_seconds FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id - WHERE E.downloaded != 'ND' ORDER BY E.download_date DESC"""); - 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 getDescription(String url) async { var dbClient = await database; List list = await dbClient.rawQuery( diff --git a/lib/podcasts/podcastgroup.dart b/lib/podcasts/podcastgroup.dart index ace9579..1c7962e 100644 --- a/lib/podcasts/podcastgroup.dart +++ b/lib/podcasts/podcastgroup.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:provider/provider.dart'; +import 'package:line_icons/line_icons.dart'; import '../state/podcast_group.dart'; import '../type/podcastlocal.dart'; @@ -16,7 +17,6 @@ import '../util/colorize.dart'; import '../util/duraiton_picker.dart'; import '../util/context_extension.dart'; import '../util/general_dialog.dart'; -import 'podcastmanage.dart'; class PodcastGroupList extends StatefulWidget { final PodcastGroup group; @@ -97,6 +97,16 @@ class _PodcastCardState extends State await dbHelper.saveSkipSeconds(id, seconds); } + _setAutoDownload(String id, bool boo) async { + DBHelper dbHelper = DBHelper(); + await dbHelper.saveAutoDownload(id, boo); + } + + Future _getAutoDownload(String id) async { + DBHelper dbHelper = DBHelper(); + return await dbHelper.getAutoDownload(id); + } + String _stringForSeconds(double seconds) { if (seconds == null) return null; return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}'; @@ -120,14 +130,25 @@ class _PodcastCardState extends State }); } - Widget _buttonOnMenu(Widget widget, VoidCallback onTap) => Material( + Widget _buttonOnMenu({Widget icon, VoidCallback onTap, String tooltip}) => + Material( color: Colors.transparent, child: InkWell( onTap: onTap, child: Container( height: 50.0, - padding: EdgeInsets.symmetric(horizontal: 15.0), - child: widget), + padding: EdgeInsets.symmetric(horizontal: 5.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: icon, + ), + Text(tooltip, style: context.textTheme.subtitle2), + ], + )), ), ); @@ -209,20 +230,6 @@ class _PodcastCardState extends State child: Text(group.name)); }).toList(), ), - FutureBuilder( - future: getSkipSecond(widget.podcastLocal.id), - initialData: 0, - builder: (context, snapshot) { - return snapshot.data == 0 - ? Center() - : Container( - alignment: Alignment.centerLeft, - child: Text('Skip ' + - _stringForSeconds( - snapshot.data.toDouble())), - ); - }, - ), ], )), Spacer(), @@ -230,9 +237,6 @@ class _PodcastCardState extends State angle: math.pi * _value, child: Icon(Icons.keyboard_arrow_down), ), - // Icon(_loadMenu - // ? Icons.keyboard_arrow_up - // : Icons.keyboard_arrow_down), Padding( padding: EdgeInsets.symmetric(horizontal: 5.0), ), @@ -326,97 +330,127 @@ class _PodcastCardState extends State mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buttonOnMenu( - Icon(Icons.fullscreen, size: 20 * _value), - () => Navigator.push( - context, - ScaleRoute( - page: PodcastDetail( - podcastLocal: widget.podcastLocal, - )), - )), - _buttonOnMenu(Icon(Icons.add, size: 20 * _value), - () { - setState(() { - _addGroup = true; - }); - }), + icon: Icon(Icons.add, + size: _value == 0 ? 1 : 20 * _value), + onTap: () { + setState(() { + _addGroup = true; + }); + }, + tooltip: 'Group'), + FutureBuilder( + future: + _getAutoDownload(widget.podcastLocal.id), + 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', + onTap: () { + _setAutoDownload(widget.podcastLocal.id, + !snapshot.data); + setState(() {}); + }, + ); + }, + ), + FutureBuilder( + future: getSkipSecond(widget.podcastLocal.id), + initialData: 0, + builder: (context, snapshot) { + return _buttonOnMenu( + icon: Icon( + Icons.fast_forward, + size: _value == 0 ? 1 : 20 * (_value), + ), + tooltip: 'Skip' + + (snapshot.data == 0 + ? '' + : _stringForSeconds( + snapshot.data.toDouble())), + onTap: () { + generalDialog( + context, + title: Text('Skip seconds at start', + maxLines: 2), + content: DurationPicker( + duration: Duration( + seconds: _skipSeconds ?? 0), + onChange: (value) => + _seconds = value.inSeconds, + ), + actions: [ + FlatButton( + onPressed: () { + Navigator.of(context).pop(); + _seconds = 0; + }, + child: Text( + 'CANCEL', + style: TextStyle( + color: Colors.grey[600]), + ), + ), + FlatButton( + onPressed: () { + Navigator.of(context).pop(); + saveSkipSeconds( + widget.podcastLocal.id, + _seconds); + }, + child: Text( + 'CONFIRM', + style: TextStyle( + color: + context.accentColor), + ), + ) + ], + ); + }); + }), _buttonOnMenu( - Icon( - Icons.fast_forward, - size: 20 * (_value), - ), () { - generalDialog( - context, - title: Text('Skip seconds at start', - maxLines: 2), - content: DurationPicker( - duration: - Duration(seconds: _skipSeconds ?? 0), - onChange: (value) => - _seconds = value.inSeconds, - ), - actions: [ - FlatButton( - onPressed: () { - Navigator.of(context).pop(); - _seconds = 0; - }, - child: Text( - 'CANCEL', - style: - TextStyle(color: Colors.grey[600]), - ), - ), - FlatButton( - onPressed: () { - Navigator.of(context).pop(); - saveSkipSeconds( - widget.podcastLocal.id, _seconds); - }, - child: Text( - 'CONFIRM', - style: TextStyle( - color: context.accentColor), - ), - ) - ], - ); - }), - _buttonOnMenu( - Icon( + icon: Icon( Icons.delete, color: Colors.red, - size: 20 * (_value), - ), () { - generalDialog( - context, - title: Text('Remove confirm'), - content: Text( - 'Are you sure you want to unsubscribe?'), - actions: [ - FlatButton( - onPressed: () => - Navigator.of(context).pop(), - child: Text( - 'CANCEL', - style: - TextStyle(color: Colors.grey[600]), - ), - ), - FlatButton( - onPressed: () { - groupList.removePodcast( - widget.podcastLocal.id); - Navigator.of(context).pop(); - }, - child: Text( - 'CONFIRM', - style: TextStyle(color: Colors.red), - ), - ) - ], - ); - }), + size: _value == 0 ? 1 : 20 * _value, + ), + tooltip: 'Remove', + onTap: () { + generalDialog( + context, + title: Text('Remove confirm'), + content: Text( + 'Are you sure you want to unsubscribe?'), + actions: [ + FlatButton( + onPressed: () => + Navigator.of(context).pop(), + child: Text( + 'CANCEL', + style: TextStyle( + color: Colors.grey[600]), + ), + ), + FlatButton( + onPressed: () { + groupList.removePodcast( + widget.podcastLocal.id); + Navigator.of(context).pop(); + }, + child: Text( + 'CONFIRM', + style: TextStyle(color: Colors.red), + ), + ) + ], + ); + }), ], ), ), @@ -469,8 +503,7 @@ class _RenameGroupState extends State { borderRadius: BorderRadius.all(Radius.circular(10))), elevation: 1, contentPadding: EdgeInsets.symmetric(horizontal: 20), - titlePadding: EdgeInsets.only( - top: 20, left: 20, right: 20, bottom: 20), + titlePadding: EdgeInsets.only(top: 20, left: 20, right: 20, bottom: 20), actionsPadding: EdgeInsets.all(0), actions: [ FlatButton( @@ -497,7 +530,8 @@ class _RenameGroupState extends State { style: TextStyle(color: Theme.of(context).accentColor)), ) ], - title: SizedBox(width: context.width - 160, child: Text('Edit group name')), + title: SizedBox( + width: context.width - 160, child: Text('Edit group name')), content: Column( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/state/download_state.dart b/lib/state/download_state.dart index 117334c..eec88c2 100644 --- a/lib/state/download_state.dart +++ b/lib/state/download_state.dart @@ -152,6 +152,8 @@ class DownloadState extends ChangeNotifier { openFileFromNotification: false, ); _episodeTasks.add(EpisodeTask(episode, taskId)); + var dbHelper = DBHelper(); + await dbHelper.saveDownloaded(taskId, episode.enclosureUrl); notifyListeners(); } diff --git a/lib/state/settingstate.dart b/lib/state/settingstate.dart index 6a5ccb6..f00cf94 100644 --- a/lib/state/settingstate.dart +++ b/lib/state/settingstate.dart @@ -2,29 +2,41 @@ import 'dart:io'; 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'; import '../type/podcastlocal.dart'; +import '../type/episodebrief.dart'; +import 'download_state.dart'; void callbackDispatcher() { - if(Platform.isAndroid) - Workmanager.executeTask((task, inputData) async { - var dbHelper = DBHelper(); - List podcastList = await dbHelper.getPodcastLocalAll(); - //lastWork is a indicator for if the app was opened since last backgroundwork - //if the app wes opend,then the old marked new episode would be marked not new. - KeyValueStorage lastWorkStorage = KeyValueStorage(lastWorkKey); - int lastWork = await lastWorkStorage.getInt(); - await Future.forEach(podcastList, (podcastLocal) async { - await dbHelper.updatePodcastRss(podcastLocal, removeMark: lastWork); - print('Refresh ' + podcastLocal.title); + if (Platform.isAndroid) + Workmanager.executeTask((task, inputData) async { + var dbHelper = DBHelper(); + List podcastList = await dbHelper.getPodcastLocalAll(); + //lastWork is a indicator for if the app was opened since last backgroundwork + //if the app wes opend,then the old marked new episode would be marked not new. + KeyValueStorage lastWorkStorage = KeyValueStorage(lastWorkKey); + int lastWork = await lastWorkStorage.getInt(); + await Future.forEach(podcastList, (podcastLocal) async { + int updateCount = + await dbHelper.updatePodcastRss(podcastLocal, removeMark: lastWork); + bool autoDownload = await dbHelper.getAutoDownload(podcastLocal.id); + if (autoDownload && updateCount > 0) { + List episodes = + await dbHelper.getNewEpisodes(podcastLocal.id); + episodes.forEach((episode) { + DownloadState().startTask(episode); + }); + } + print('Refresh ' + podcastLocal.title); + }); + await lastWorkStorage.saveInt(1); + KeyValueStorage refreshstorage = KeyValueStorage(refreshdateKey); + await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch); + return Future.value(true); }); - await lastWorkStorage.saveInt(1); - KeyValueStorage refreshstorage = KeyValueStorage(refreshdateKey); - await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch); - return Future.value(true); - }); } ThemeData lightTheme = ThemeData(