🚧 Auto download

This commit is contained in:
stonegate 2020-06-08 02:42:27 +08:00
parent 9c13450a9c
commit 1a497a78ed
4 changed files with 229 additions and 159 deletions

View File

@ -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<bool> getAutoDownload(String id) async {
var dbClient = await database;
List<Map> list = await dbClient
.rawQuery('SELECT auto_download FROM PodcastLocal WHERE id = ?', [id]);
return list.first['auto_download'] == 1;
}
Future<int> 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<bool> checkPodcast(String url) async {
var dbClient = await database;
List<Map> list = await dbClient
@ -813,6 +832,37 @@ 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<int> 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<List<EpisodeBrief>> getDownloadedRssItem() async {
var dbClient = await database;
List<EpisodeBrief> episodes = List();
List<Map> 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<String> getDescription(String url) async {
var dbClient = await database;
List<Map> list = await dbClient.rawQuery(

View File

@ -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<PodcastCard>
await dbHelper.saveSkipSeconds(id, seconds);
}
_setAutoDownload(String id, bool boo) async {
DBHelper dbHelper = DBHelper();
await dbHelper.saveAutoDownload(id, boo);
}
Future<bool> _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<PodcastCard>
});
}
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<PodcastCard>
child: Text(group.name));
}).toList(),
),
FutureBuilder<int>(
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<PodcastCard>
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<PodcastCard>
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
_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<bool>(
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<int>(
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: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
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<RenameGroup> {
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: <Widget>[
FlatButton(
@ -497,7 +530,8 @@ class _RenameGroupState extends State<RenameGroup> {
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: <Widget>[

View File

@ -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();
}

View File

@ -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<PodcastLocal> 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<PodcastLocal>(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<PodcastLocal> 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<PodcastLocal>(podcastList, (podcastLocal) async {
int updateCount =
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);
});
}
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(