From e8882a62b89af2510d677b5bbb303bb773432451 Mon Sep 17 00:00:00 2001 From: stonegate Date: Sun, 11 Oct 2020 20:28:10 +0800 Subject: [PATCH] Auto delete downloads after played. --- lib/local_storage/key_value_storage.dart | 1 + lib/local_storage/sqflite_localpodcast.dart | 54 ++- lib/settings/storage.dart | 377 +++++++++++--------- lib/state/download_state.dart | 15 +- lib/state/podcast_group.dart | 8 +- lib/state/setting_state.dart | 13 +- lib/type/settings_backup.dart | 14 +- 7 files changed, 288 insertions(+), 194 deletions(-) diff --git a/lib/local_storage/key_value_storage.dart b/lib/local_storage/key_value_storage.dart index c28948e..73d9221 100644 --- a/lib/local_storage/key_value_storage.dart +++ b/lib/local_storage/key_value_storage.dart @@ -57,6 +57,7 @@ const String hidePodcastDiscoveryKey = 'hidePodcastDiscoveryKey'; const String searchEngineKey = 'searchEngineKey'; const String markListenedAfterSkipKey = 'markListenedAfterSkipKey'; const String downloadPositionKey = 'downloadPositionKey'; +const String deleteAfterPlayedKey = 'removeAfterPlayedKey'; class KeyValueStorage { final String key; diff --git a/lib/local_storage/sqflite_localpodcast.dart b/lib/local_storage/sqflite_localpodcast.dart index f60adc2..2dc24e4 100644 --- a/lib/local_storage/sqflite_localpodcast.dart +++ b/lib/local_storage/sqflite_localpodcast.dart @@ -1091,7 +1091,8 @@ class DBHelper { return episodes; } - Future> getOutdatedEpisode(int deadline) async { + Future> getOutdatedEpisode(int deadline, + {bool deletePlayed}) async { var dbClient = await database; var episodes = []; List list = await dbClient.rawQuery( @@ -1100,18 +1101,45 @@ class DBHelper { 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 ORDER BY E.milliseconds DESC""", [deadline]); - for (var i in list) { - 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'])); + if (list.isNotEmpty) { + for (var i in list) { + 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'])); + } + } + if (deletePlayed) { + List 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; } diff --git a/lib/settings/storage.dart b/lib/settings/storage.dart index 5ae1bd3..2d33331 100644 --- a/lib/settings/storage.dart +++ b/lib/settings/storage.dart @@ -67,6 +67,11 @@ class _StorageSettingState extends State return index; } + Future _getDelteAfterPlayed() async { + final storage = KeyValueStorage(deleteAfterPlayedKey); + return await storage.getBool(defaultValue: false); + } + Future _setAutoDeleteDays(int days) async { var storage = KeyValueStorage(autoDeleteKey); await storage.saveInt(days); @@ -83,6 +88,11 @@ class _StorageSettingState extends State await storage.saveInt(index); } + Future _setDeleteAfterPlayed(bool boo) async { + final storage = KeyValueStorage(deleteAfterPlayedKey); + await storage.saveBool(boo); + } + double _value; @override @@ -117,186 +127,219 @@ class _StorageSettingState extends State backgroundColor: Theme.of(context).primaryColor, ), body: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.all(10.0), - ), - Container( - height: 30.0, - padding: EdgeInsets.symmetric(horizontal: 70), - alignment: Alignment.centerLeft, - child: Text(s.network, - style: context.textTheme.bodyText1 - .copyWith(color: context.accentColor)), - ), - Selector( - selector: (_, settings) => settings.downloadUsingData, - builder: (_, data, __) { - return ListTile( - onTap: () => settings.downloadUsingData = !data, - contentPadding: EdgeInsets.only( - left: 70.0, right: 25, bottom: 10, top: 10), - title: Text(s.settingsNetworkCellular), - subtitle: Text(s.settingsNetworkCellularDes), - trailing: Transform.scale( - scale: 0.9, - child: Switch( - value: data, - onChanged: (value) => - settings.downloadUsingData = value, - ), - ), - ); - }, - ), - FutureBuilder( - future: _getAutoDownloadNetwork(), - initialData: false, - builder: (context, snapshot) { + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.all(10.0), + ), + Container( + height: 30.0, + padding: EdgeInsets.symmetric(horizontal: 70), + alignment: Alignment.centerLeft, + child: Text(s.network, + style: context.textTheme.bodyText1 + .copyWith(color: context.accentColor)), + ), + Selector( + selector: (_, settings) => settings.downloadUsingData, + builder: (_, data, __) { return ListTile( - onTap: () async { - _setAudtDownloadNetwork(!snapshot.data); - setState(() {}); - }, + onTap: () => settings.downloadUsingData = !data, contentPadding: EdgeInsets.only( left: 70.0, right: 25, bottom: 10, top: 10), - title: Text(s.settingsNetworkCellularAuto), - subtitle: Text(s.settingsNetworkCellularAutoDes), + title: Text(s.settingsNetworkCellular), + subtitle: Text(s.settingsNetworkCellularDes), trailing: Transform.scale( scale: 0.9, child: Switch( - value: snapshot.data, - onChanged: (value) async { - await _setAudtDownloadNetwork(value); - setState(() {}); - }, + value: data, + onChanged: (value) => + settings.downloadUsingData = value, ), ), ); - }), - Divider(height: 1), - Padding( - padding: EdgeInsets.all(10.0), - ), - Container( - height: 30.0, - padding: EdgeInsets.symmetric(horizontal: 70), - alignment: Alignment.centerLeft, - child: Text(s.settingStorage, - style: context.textTheme.bodyText1 - .copyWith(color: context.accentColor)), - ), - ListTile( - onTap: () => Navigator.push(context, - MaterialPageRoute(builder: (context) => DownloadsManage())), - contentPadding: EdgeInsets.symmetric(horizontal: 70.0), - title: Text(s.download), - subtitle: Text(s.settingsManageDownloadDes), - ), - FutureBuilder( - future: _getDownloadPasition(), - initialData: 0, + }, + ), + FutureBuilder( + future: _getAutoDownloadNetwork(), + initialData: false, + builder: (context, snapshot) { + return ListTile( + onTap: () async { + _setAudtDownloadNetwork(!snapshot.data); + setState(() {}); + }, + contentPadding: EdgeInsets.only( + left: 70.0, right: 25, bottom: 10, top: 10), + title: Text(s.settingsNetworkCellularAuto), + subtitle: Text(s.settingsNetworkCellularAutoDes), + trailing: Transform.scale( + scale: 0.9, + child: Switch( + value: snapshot.data, + onChanged: (value) async { + await _setAudtDownloadNetwork(value); + setState(() {}); + }, + ), + ), + ); + }), + Divider(height: 1), + Padding( + padding: EdgeInsets.all(10.0), + ), + Container( + height: 30.0, + padding: EdgeInsets.symmetric(horizontal: 70), + alignment: Alignment.centerLeft, + child: Text(s.settingStorage, + style: context.textTheme.bodyText1 + .copyWith(color: context.accentColor)), + ), + ListTile( + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DownloadsManage())), + contentPadding: EdgeInsets.symmetric(horizontal: 70.0), + title: Text(s.download), + subtitle: Text(s.settingsManageDownloadDes), + ), + FutureBuilder( + future: _getDownloadPasition(), + initialData: 0, + builder: (context, snapshot) { + return ListTile( + contentPadding: EdgeInsets.fromLTRB(70, 10, 20, 10), + title: Text(s.settingsDownloadPosition), + subtitle: Text( + _dirs == null ? '' : _dirs[snapshot.data], + maxLines: 2, + overflow: TextOverflow.ellipsis), + onTap: () => generalSheet( + context, + title: s.settingsDownloadPosition, + child: Column(children: [ + SizedBox( + height: 10, + ), + for (var dir in _dirs) + ListTile( + title: Text(dir), + onTap: () => + _setDownloadPosition(_dirs.indexOf(dir)), + trailing: Radio( + value: _dirs.indexOf(dir), + groupValue: snapshot.data, + onChanged: _setDownloadPosition), + ), + SizedBox( + height: 30, + ) + ]), + ), + ); + }), + FutureBuilder( + future: _getAutoDeleteDays(), + initialData: 30, builder: (context, snapshot) { return ListTile( - contentPadding: EdgeInsets.fromLTRB(70, 10, 20, 10), - title: Text(s.settingsDownloadPosition), - subtitle: Text(_dirs == null ? '' : _dirs[snapshot.data], - maxLines: 2, overflow: TextOverflow.ellipsis), - onTap: () => generalSheet( - context, - title: s.settingsDownloadPosition, - child: Column(children: [ - SizedBox( - height: 10, - ), - for (var dir in _dirs) - ListTile( - title: Text(dir), - onTap: () => - _setDownloadPosition(_dirs.indexOf(dir)), - trailing: Radio( - value: _dirs.indexOf(dir), - groupValue: snapshot.data, - onChanged: _setDownloadPosition), - ), - SizedBox( - height: 30, - ) - ]), - ), + contentPadding: EdgeInsets.only(left: 70.0, right: 20), + title: Text(s.settingsAutoDelete), + subtitle: Text(s.settingsAutoDeleteDes), + trailing: MyDropdownButton( + hint: snapshot.data == -1 + ? Text(s.daysCount(0)) + : Text(s.daysCount(snapshot.data)), + underline: Center(), + elevation: 1, + value: snapshot.data, + onChanged: (value) async { + await _setAutoDeleteDays(value); + }, + items: [-1, 5, 10, 15, 30] + .map>((e) { + return DropdownMenuItem( + value: e, + child: e == -1 + ? Text(s.daysCount(0)) + : Text(s.daysCount(e))); + }).toList()), ); - }), - FutureBuilder( - future: _getAutoDeleteDays(), - initialData: 30, - builder: (context, snapshot) { - return ListTile( - contentPadding: EdgeInsets.only(left: 70.0, right: 20), - title: Text(s.settingsAutoDelete), - subtitle: Text(s.settingsAutoDeleteDes), - trailing: MyDropdownButton( - hint: snapshot.data == -1 - ? Text(s.daysCount(0)) - : Text(s.daysCount(snapshot.data)), - underline: Center(), - elevation: 1, - value: snapshot.data, - onChanged: (value) async { - await _setAutoDeleteDays(value); - }, - items: [-1, 5, 10, 15, 30] - .map>((e) { - return DropdownMenuItem( - value: e, - child: e == -1 - ? Text(s.daysCount(0)) - : Text(s.daysCount(e))); - }).toList()), - ); - }, - ), - ListTile( - contentPadding: EdgeInsets.only(left: 70.0, right: 25), - // leading: Icon(Icons.colorize), - title: Text(s.settingsAudioCache), - subtitle: Text(s.settingsAudioCacheDes), - trailing: Text.rich(TextSpan( - text: '${(_value ~/ 100) * 100}', - style: GoogleFonts.teko( - textStyle: context.textTheme.headline6 - .copyWith(color: context.accentColor)), - children: [ - TextSpan(text: ' Mb', style: context.textTheme.subtitle2), - ])), - ), - Padding( - padding: EdgeInsets.only(left: 50.0, right: 20.0, bottom: 10.0), - child: SliderTheme( - data: Theme.of(context).sliderTheme.copyWith( - showValueIndicator: ShowValueIndicator.always, - trackHeight: 2, - thumbShape: RoundSliderThumbShape(enabledThumbRadius: 6)), - child: Slider( - label: '${_value ~/ 100 * 100} Mb', - activeColor: context.accentColor, - inactiveColor: context.primaryColorDark, - value: _value, - min: 100, - max: 1000, - divisions: 9, - onChanged: (val) { - setState(() { - _value = val; - }); - cacheStorage.saveInt((val * 1024 * 1024).toInt()); - }), + }, ), - ), - Divider(height: 1), - ], + FutureBuilder( + 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( + contentPadding: EdgeInsets.only(left: 70.0, right: 25), + // leading: Icon(Icons.colorize), + title: Text(s.settingsAudioCache), + subtitle: Text(s.settingsAudioCacheDes), + trailing: Text.rich(TextSpan( + text: '${(_value ~/ 100) * 100}', + style: GoogleFonts.teko( + textStyle: context.textTheme.headline6 + .copyWith(color: context.accentColor)), + children: [ + TextSpan( + text: ' Mb', style: context.textTheme.subtitle2), + ])), + ), + Padding( + padding: + EdgeInsets.only(left: 50.0, right: 20.0, bottom: 10.0), + child: SliderTheme( + data: Theme.of(context).sliderTheme.copyWith( + showValueIndicator: ShowValueIndicator.always, + trackHeight: 2, + thumbShape: + RoundSliderThumbShape(enabledThumbRadius: 6)), + child: Slider( + label: '${_value ~/ 100 * 100} Mb', + activeColor: context.accentColor, + inactiveColor: context.primaryColorDark, + value: _value, + min: 100, + max: 1000, + divisions: 9, + onChanged: (val) { + setState(() { + _value = val; + }); + cacheStorage.saveInt((val * 1024 * 1024).toInt()); + }), + ), + ), + Divider(height: 1), + ], + ), ), ), ), diff --git a/lib/state/download_state.dart b/lib/state/download_state.dart index 37e27c0..363d487 100644 --- a/lib/state/download_state.dart +++ b/lib/state/download_state.dart @@ -335,7 +335,7 @@ class DownloadState extends ChangeNotifier { taskId: task.taskId, shouldDeleteContent: false); } - Future delTask(EpisodeBrief episode) async { + Future delTask(EpisodeBrief episode) async { var task = episodeToTask(episode); await FlutterDownloader.remove( taskId: task.taskId, shouldDeleteContent: true); @@ -350,22 +350,25 @@ class DownloadState extends ChangeNotifier { _removeTask(episode); } - _removeTask(EpisodeBrief episode) { + void _removeTask(EpisodeBrief episode) { _episodeTasks.removeWhere((element) => element.episode == episode); notifyListeners(); } - _autoDelete() async { + Future _autoDelete() async { developer.log('Start auto delete outdated episodes'); - var autoDeleteStorage = KeyValueStorage(autoDeleteKey); - var autoDelete = await autoDeleteStorage.getInt(); + final autoDeleteStorage = KeyValueStorage(autoDeleteKey); + final deletePlayedStorage = KeyValueStorage(deleteAfterPlayedKey); + final autoDelete = await autoDeleteStorage.getInt(); + final deletePlayed = await deletePlayedStorage.getBool(defaultValue: false); if (autoDelete == 0) { await autoDeleteStorage.saveInt(30); } else if (autoDelete > 0) { var deadline = DateTime.now() .subtract(Duration(days: autoDelete)) .millisecondsSinceEpoch; - var episodes = await dbHelper.getOutdatedEpisode(deadline); + var episodes = await dbHelper.getOutdatedEpisode(deadline, + deletePlayed: deletePlayed); if (episodes.isNotEmpty) { for (var episode in episodes) { await delTask(episode); diff --git a/lib/state/podcast_group.dart b/lib/state/podcast_group.dart index 13ba8f4..3a86331 100644 --- a/lib/state/podcast_group.dart +++ b/lib/state/podcast_group.dart @@ -362,7 +362,7 @@ class GroupList extends ChangeNotifier { } /// Load groups from storage at start. - Future loadGroups() async { + Future loadGroups() async { _isLoading = true; notifyListeners(); _groupStorage.getGroups().then((loadgroups) async { @@ -376,7 +376,7 @@ class GroupList extends ChangeNotifier { } /// Update podcasts of each group - Future updateGroups() async { + Future updateGroups() async { for (var group in _groups) { await group.getPodcasts(); } @@ -384,7 +384,7 @@ class GroupList extends ChangeNotifier { } /// Add new group. - Future addGroup(PodcastGroup podcastGroup) async { + Future addGroup(PodcastGroup podcastGroup) async { _isLoading = true; _groups.add(podcastGroup); await _saveGroup(); @@ -393,7 +393,7 @@ class GroupList extends ChangeNotifier { } /// Remove group. - Future delGroup(PodcastGroup podcastGroup) async { + Future delGroup(PodcastGroup podcastGroup) async { _isLoading = true; for (var podcast in podcastGroup.podcastList) { if (!_groups.first.podcastList.contains(podcast)) { diff --git a/lib/state/setting_state.dart b/lib/state/setting_state.dart index 63150f3..ea2e7c7 100644 --- a/lib/state/setting_state.dart +++ b/lib/state/setting_state.dart @@ -496,6 +496,11 @@ class SettingState extends ChangeNotifier { var speedList = await KeyValueStorage(speedListKey).getStringList(); var hidePodcastDiscovery = await KeyValueStorage(hidePodcastDiscoveryKey) .getBool(defaultValue: false); + final markListenedAfterSKip = + await KeyValueStorage(markListenedAfterSkipKey) + .getBool(defaultValue: false); + final deleteAfterPlayed = await KeyValueStorage(deleteAfterPlayedKey) + .getBool(defaultValue: false); return SettingsBackup( theme: theme, @@ -527,7 +532,9 @@ class SettingState extends ChangeNotifier { notificationLayout: notificationLayout, showNotesFont: showNotesFont, speedList: speedList, - hidePodcastDiscovery: hidePodcastDiscovery); + hidePodcastDiscovery: hidePodcastDiscovery, + markListenedAfterSkip: markListenedAfterSKip, + deleteAfterPlayed: deleteAfterPlayed); } Future restore(SettingsBackup backup) async { @@ -563,6 +570,10 @@ class SettingState extends ChangeNotifier { .saveInt(backup.notificationLayout); await showNotesFontStorage.saveInt(backup.showNotesFont); await KeyValueStorage(speedListKey).saveStringList(backup.speedList); + await KeyValueStorage(markListenedAfterSkipKey) + .saveBool(backup.markListenedAfterSkip); + await KeyValueStorage(deleteAfterPlayedKey) + .saveBool(backup.deleteAfterPlayed); if (backup.locale == '') { await localeStorage.saveStringList([]); diff --git a/lib/type/settings_backup.dart b/lib/type/settings_backup.dart index fadc9f3..aa240a1 100644 --- a/lib/type/settings_backup.dart +++ b/lib/type/settings_backup.dart @@ -29,6 +29,8 @@ class SettingsBackup { final int showNotesFont; final List speedList; final bool hidePodcastDiscovery; + final bool markListenedAfterSkip; + final bool deleteAfterPlayed; SettingsBackup( {this.theme, @@ -60,7 +62,9 @@ class SettingsBackup { this.notificationLayout, this.showNotesFont, this.speedList, - this.hidePodcastDiscovery}); + this.hidePodcastDiscovery, + this.markListenedAfterSkip, + this.deleteAfterPlayed}); Map toJson() { return { @@ -92,7 +96,9 @@ class SettingsBackup { 'notificationLayout': notificationLayout, 'showNotesFont': showNotesFont, 'speedList': speedList, - 'hidePodcastDiscovery': hidePodcastDiscovery + 'hidePodcastDiscovery': hidePodcastDiscovery, + 'markListenedAfterSkip': markListenedAfterSkip, + 'deleteAfterPlayed': deleteAfterPlayed }; } @@ -128,6 +134,8 @@ class SettingsBackup { notificationLayout: json['notificationLayout'] as int, showNotesFont: json['showNotesFont'] as int, speedList: speedList, - hidePodcastDiscovery: json['hidePodcastDiscovery'] as bool); + hidePodcastDiscovery: json['hidePodcastDiscovery'] as bool, + markListenedAfterSkip: json['markListenedAfterSkip'] as bool, + deleteAfterPlayed: json['deleteAfterPlayed'] as bool); } }