Auto delete downloads after played.

This commit is contained in:
stonegate 2020-10-11 20:28:10 +08:00
parent 7af2cb5a09
commit e8882a62b8
7 changed files with 288 additions and 194 deletions

View File

@ -57,6 +57,7 @@ const String hidePodcastDiscoveryKey = 'hidePodcastDiscoveryKey';
const String searchEngineKey = 'searchEngineKey'; const String searchEngineKey = 'searchEngineKey';
const String markListenedAfterSkipKey = 'markListenedAfterSkipKey'; const String markListenedAfterSkipKey = 'markListenedAfterSkipKey';
const String downloadPositionKey = 'downloadPositionKey'; const String downloadPositionKey = 'downloadPositionKey';
const String deleteAfterPlayedKey = 'removeAfterPlayedKey';
class KeyValueStorage { class KeyValueStorage {
final String key; final String key;

View File

@ -1091,7 +1091,8 @@ class DBHelper {
return episodes; return episodes;
} }
Future<List<EpisodeBrief>> getOutdatedEpisode(int deadline) async { Future<List<EpisodeBrief>> getOutdatedEpisode(int deadline,
{bool deletePlayed}) async {
var dbClient = await database; var dbClient = await database;
var episodes = <EpisodeBrief>[]; var episodes = <EpisodeBrief>[];
List<Map> list = await dbClient.rawQuery( List<Map> list = await dbClient.rawQuery(
@ -1100,6 +1101,7 @@ class DBHelper {
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE E.download_date < ? AND E.enclosure_url != E.media_id WHERE E.download_date < ? AND E.enclosure_url != E.media_id
ORDER BY E.milliseconds DESC""", [deadline]); ORDER BY E.milliseconds DESC""", [deadline]);
if (list.isNotEmpty) {
for (var i in list) { for (var i in list) {
episodes.add(EpisodeBrief( episodes.add(EpisodeBrief(
i['title'], i['title'],
@ -1113,6 +1115,32 @@ class DBHelper {
i['imagePath'], i['imagePath'],
i['is_new'])); i['is_new']));
} }
}
if (deletePlayed) {
List<Map> results = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P
ON E.feed_id = P.id LEFT JOIN PlayHistory H ON E.enclosure_url =
H.enclosure_url WHERE E.enclosure_url != E.media_id
GROUP BY E.enclosure_url HAVING SUM(H.listen_time) > 0 ORDER BY
E.milliseconds DESC""");
if (results.isNotEmpty) {
for (var i in results) {
episodes.add(EpisodeBrief(
i['title'],
i['enclosure_url'],
i['enclosure_length'],
i['milliseconds'],
i['feed_title'],
i['primaryColor'],
i['duration'],
i['explicit'],
i['imagePath'],
i['is_new']));
}
}
}
return episodes; return episodes;
} }

View File

@ -67,6 +67,11 @@ class _StorageSettingState extends State<StorageSetting>
return index; return index;
} }
Future<bool> _getDelteAfterPlayed() async {
final storage = KeyValueStorage(deleteAfterPlayedKey);
return await storage.getBool(defaultValue: false);
}
Future<void> _setAutoDeleteDays(int days) async { Future<void> _setAutoDeleteDays(int days) async {
var storage = KeyValueStorage(autoDeleteKey); var storage = KeyValueStorage(autoDeleteKey);
await storage.saveInt(days); await storage.saveInt(days);
@ -83,6 +88,11 @@ class _StorageSettingState extends State<StorageSetting>
await storage.saveInt(index); await storage.saveInt(index);
} }
Future<void> _setDeleteAfterPlayed(bool boo) async {
final storage = KeyValueStorage(deleteAfterPlayedKey);
await storage.saveBool(boo);
}
double _value; double _value;
@override @override
@ -117,6 +127,7 @@ class _StorageSettingState extends State<StorageSetting>
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
), ),
body: SafeArea( body: SafeArea(
child: SingleChildScrollView(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -190,8 +201,10 @@ class _StorageSettingState extends State<StorageSetting>
.copyWith(color: context.accentColor)), .copyWith(color: context.accentColor)),
), ),
ListTile( ListTile(
onTap: () => Navigator.push(context, onTap: () => Navigator.push(
MaterialPageRoute(builder: (context) => DownloadsManage())), context,
MaterialPageRoute(
builder: (context) => DownloadsManage())),
contentPadding: EdgeInsets.symmetric(horizontal: 70.0), contentPadding: EdgeInsets.symmetric(horizontal: 70.0),
title: Text(s.download), title: Text(s.download),
subtitle: Text(s.settingsManageDownloadDes), subtitle: Text(s.settingsManageDownloadDes),
@ -203,8 +216,10 @@ class _StorageSettingState extends State<StorageSetting>
return ListTile( return ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 20, 10), contentPadding: EdgeInsets.fromLTRB(70, 10, 20, 10),
title: Text(s.settingsDownloadPosition), title: Text(s.settingsDownloadPosition),
subtitle: Text(_dirs == null ? '' : _dirs[snapshot.data], subtitle: Text(
maxLines: 2, overflow: TextOverflow.ellipsis), _dirs == null ? '' : _dirs[snapshot.data],
maxLines: 2,
overflow: TextOverflow.ellipsis),
onTap: () => generalSheet( onTap: () => generalSheet(
context, context,
title: s.settingsDownloadPosition, title: s.settingsDownloadPosition,
@ -258,6 +273,30 @@ class _StorageSettingState extends State<StorageSetting>
); );
}, },
), ),
FutureBuilder<bool>(
future: _getDelteAfterPlayed(),
initialData: false,
builder: (context, snapshot) {
return ListTile(
onTap: () async {
_setDeleteAfterPlayed(snapshot.data);
setState(() {});
},
contentPadding: EdgeInsets.only(left: 70.0, right: 25),
title: Text('Delete download after played'),
subtitle: Text('Delete after played'),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: snapshot.data,
onChanged: (value) async {
await _setDeleteAfterPlayed(value);
setState(() {});
},
),
),
);
}),
ListTile( ListTile(
contentPadding: EdgeInsets.only(left: 70.0, right: 25), contentPadding: EdgeInsets.only(left: 70.0, right: 25),
// leading: Icon(Icons.colorize), // leading: Icon(Icons.colorize),
@ -269,16 +308,19 @@ class _StorageSettingState extends State<StorageSetting>
textStyle: context.textTheme.headline6 textStyle: context.textTheme.headline6
.copyWith(color: context.accentColor)), .copyWith(color: context.accentColor)),
children: [ children: [
TextSpan(text: ' Mb', style: context.textTheme.subtitle2), TextSpan(
text: ' Mb', style: context.textTheme.subtitle2),
])), ])),
), ),
Padding( Padding(
padding: EdgeInsets.only(left: 50.0, right: 20.0, bottom: 10.0), padding:
EdgeInsets.only(left: 50.0, right: 20.0, bottom: 10.0),
child: SliderTheme( child: SliderTheme(
data: Theme.of(context).sliderTheme.copyWith( data: Theme.of(context).sliderTheme.copyWith(
showValueIndicator: ShowValueIndicator.always, showValueIndicator: ShowValueIndicator.always,
trackHeight: 2, trackHeight: 2,
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 6)), thumbShape:
RoundSliderThumbShape(enabledThumbRadius: 6)),
child: Slider( child: Slider(
label: '${_value ~/ 100 * 100} Mb', label: '${_value ~/ 100 * 100} Mb',
activeColor: context.accentColor, activeColor: context.accentColor,
@ -300,6 +342,7 @@ class _StorageSettingState extends State<StorageSetting>
), ),
), ),
), ),
),
); );
} }
} }

View File

@ -335,7 +335,7 @@ class DownloadState extends ChangeNotifier {
taskId: task.taskId, shouldDeleteContent: false); taskId: task.taskId, shouldDeleteContent: false);
} }
Future delTask(EpisodeBrief episode) async { Future<void> delTask(EpisodeBrief episode) async {
var task = episodeToTask(episode); var task = episodeToTask(episode);
await FlutterDownloader.remove( await FlutterDownloader.remove(
taskId: task.taskId, shouldDeleteContent: true); taskId: task.taskId, shouldDeleteContent: true);
@ -350,22 +350,25 @@ class DownloadState extends ChangeNotifier {
_removeTask(episode); _removeTask(episode);
} }
_removeTask(EpisodeBrief episode) { void _removeTask(EpisodeBrief episode) {
_episodeTasks.removeWhere((element) => element.episode == episode); _episodeTasks.removeWhere((element) => element.episode == episode);
notifyListeners(); notifyListeners();
} }
_autoDelete() async { Future<void> _autoDelete() async {
developer.log('Start auto delete outdated episodes'); developer.log('Start auto delete outdated episodes');
var autoDeleteStorage = KeyValueStorage(autoDeleteKey); final autoDeleteStorage = KeyValueStorage(autoDeleteKey);
var autoDelete = await autoDeleteStorage.getInt(); final deletePlayedStorage = KeyValueStorage(deleteAfterPlayedKey);
final autoDelete = await autoDeleteStorage.getInt();
final deletePlayed = await deletePlayedStorage.getBool(defaultValue: false);
if (autoDelete == 0) { if (autoDelete == 0) {
await autoDeleteStorage.saveInt(30); await autoDeleteStorage.saveInt(30);
} else if (autoDelete > 0) { } else if (autoDelete > 0) {
var deadline = DateTime.now() var deadline = DateTime.now()
.subtract(Duration(days: autoDelete)) .subtract(Duration(days: autoDelete))
.millisecondsSinceEpoch; .millisecondsSinceEpoch;
var episodes = await dbHelper.getOutdatedEpisode(deadline); var episodes = await dbHelper.getOutdatedEpisode(deadline,
deletePlayed: deletePlayed);
if (episodes.isNotEmpty) { if (episodes.isNotEmpty) {
for (var episode in episodes) { for (var episode in episodes) {
await delTask(episode); await delTask(episode);

View File

@ -362,7 +362,7 @@ class GroupList extends ChangeNotifier {
} }
/// Load groups from storage at start. /// Load groups from storage at start.
Future loadGroups() async { Future<void> loadGroups() async {
_isLoading = true; _isLoading = true;
notifyListeners(); notifyListeners();
_groupStorage.getGroups().then((loadgroups) async { _groupStorage.getGroups().then((loadgroups) async {
@ -376,7 +376,7 @@ class GroupList extends ChangeNotifier {
} }
/// Update podcasts of each group /// Update podcasts of each group
Future updateGroups() async { Future<void> updateGroups() async {
for (var group in _groups) { for (var group in _groups) {
await group.getPodcasts(); await group.getPodcasts();
} }
@ -384,7 +384,7 @@ class GroupList extends ChangeNotifier {
} }
/// Add new group. /// Add new group.
Future addGroup(PodcastGroup podcastGroup) async { Future<void> addGroup(PodcastGroup podcastGroup) async {
_isLoading = true; _isLoading = true;
_groups.add(podcastGroup); _groups.add(podcastGroup);
await _saveGroup(); await _saveGroup();
@ -393,7 +393,7 @@ class GroupList extends ChangeNotifier {
} }
/// Remove group. /// Remove group.
Future delGroup(PodcastGroup podcastGroup) async { Future<void> delGroup(PodcastGroup podcastGroup) async {
_isLoading = true; _isLoading = true;
for (var podcast in podcastGroup.podcastList) { for (var podcast in podcastGroup.podcastList) {
if (!_groups.first.podcastList.contains(podcast)) { if (!_groups.first.podcastList.contains(podcast)) {

View File

@ -496,6 +496,11 @@ class SettingState extends ChangeNotifier {
var speedList = await KeyValueStorage(speedListKey).getStringList(); var speedList = await KeyValueStorage(speedListKey).getStringList();
var hidePodcastDiscovery = await KeyValueStorage(hidePodcastDiscoveryKey) var hidePodcastDiscovery = await KeyValueStorage(hidePodcastDiscoveryKey)
.getBool(defaultValue: false); .getBool(defaultValue: false);
final markListenedAfterSKip =
await KeyValueStorage(markListenedAfterSkipKey)
.getBool(defaultValue: false);
final deleteAfterPlayed = await KeyValueStorage(deleteAfterPlayedKey)
.getBool(defaultValue: false);
return SettingsBackup( return SettingsBackup(
theme: theme, theme: theme,
@ -527,7 +532,9 @@ class SettingState extends ChangeNotifier {
notificationLayout: notificationLayout, notificationLayout: notificationLayout,
showNotesFont: showNotesFont, showNotesFont: showNotesFont,
speedList: speedList, speedList: speedList,
hidePodcastDiscovery: hidePodcastDiscovery); hidePodcastDiscovery: hidePodcastDiscovery,
markListenedAfterSkip: markListenedAfterSKip,
deleteAfterPlayed: deleteAfterPlayed);
} }
Future<void> restore(SettingsBackup backup) async { Future<void> restore(SettingsBackup backup) async {
@ -563,6 +570,10 @@ class SettingState extends ChangeNotifier {
.saveInt(backup.notificationLayout); .saveInt(backup.notificationLayout);
await showNotesFontStorage.saveInt(backup.showNotesFont); await showNotesFontStorage.saveInt(backup.showNotesFont);
await KeyValueStorage(speedListKey).saveStringList(backup.speedList); await KeyValueStorage(speedListKey).saveStringList(backup.speedList);
await KeyValueStorage(markListenedAfterSkipKey)
.saveBool(backup.markListenedAfterSkip);
await KeyValueStorage(deleteAfterPlayedKey)
.saveBool(backup.deleteAfterPlayed);
if (backup.locale == '') { if (backup.locale == '') {
await localeStorage.saveStringList([]); await localeStorage.saveStringList([]);

View File

@ -29,6 +29,8 @@ class SettingsBackup {
final int showNotesFont; final int showNotesFont;
final List<String> speedList; final List<String> speedList;
final bool hidePodcastDiscovery; final bool hidePodcastDiscovery;
final bool markListenedAfterSkip;
final bool deleteAfterPlayed;
SettingsBackup( SettingsBackup(
{this.theme, {this.theme,
@ -60,7 +62,9 @@ class SettingsBackup {
this.notificationLayout, this.notificationLayout,
this.showNotesFont, this.showNotesFont,
this.speedList, this.speedList,
this.hidePodcastDiscovery}); this.hidePodcastDiscovery,
this.markListenedAfterSkip,
this.deleteAfterPlayed});
Map<String, Object> toJson() { Map<String, Object> toJson() {
return { return {
@ -92,7 +96,9 @@ class SettingsBackup {
'notificationLayout': notificationLayout, 'notificationLayout': notificationLayout,
'showNotesFont': showNotesFont, 'showNotesFont': showNotesFont,
'speedList': speedList, 'speedList': speedList,
'hidePodcastDiscovery': hidePodcastDiscovery 'hidePodcastDiscovery': hidePodcastDiscovery,
'markListenedAfterSkip': markListenedAfterSkip,
'deleteAfterPlayed': deleteAfterPlayed
}; };
} }
@ -128,6 +134,8 @@ class SettingsBackup {
notificationLayout: json['notificationLayout'] as int, notificationLayout: json['notificationLayout'] as int,
showNotesFont: json['showNotesFont'] as int, showNotesFont: json['showNotesFont'] as int,
speedList: speedList, speedList: speedList,
hidePodcastDiscovery: json['hidePodcastDiscovery'] as bool); hidePodcastDiscovery: json['hidePodcastDiscovery'] as bool,
markListenedAfterSkip: json['markListenedAfterSkip'] as bool,
deleteAfterPlayed: json['deleteAfterPlayed'] as bool);
} }
} }