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 markListenedAfterSkipKey = 'markListenedAfterSkipKey';
const String downloadPositionKey = 'downloadPositionKey';
const String deleteAfterPlayedKey = 'removeAfterPlayedKey';
class KeyValueStorage {
final String key;

View File

@ -1091,7 +1091,8 @@ class DBHelper {
return episodes;
}
Future<List<EpisodeBrief>> getOutdatedEpisode(int deadline) async {
Future<List<EpisodeBrief>> getOutdatedEpisode(int deadline,
{bool deletePlayed}) async {
var dbClient = await database;
var episodes = <EpisodeBrief>[];
List<Map> 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<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;
}

View File

@ -67,6 +67,11 @@ class _StorageSettingState extends State<StorageSetting>
return index;
}
Future<bool> _getDelteAfterPlayed() async {
final storage = KeyValueStorage(deleteAfterPlayedKey);
return await storage.getBool(defaultValue: false);
}
Future<void> _setAutoDeleteDays(int days) async {
var storage = KeyValueStorage(autoDeleteKey);
await storage.saveInt(days);
@ -83,6 +88,11 @@ class _StorageSettingState extends State<StorageSetting>
await storage.saveInt(index);
}
Future<void> _setDeleteAfterPlayed(bool boo) async {
final storage = KeyValueStorage(deleteAfterPlayedKey);
await storage.saveBool(boo);
}
double _value;
@override
@ -117,186 +127,219 @@ class _StorageSettingState extends State<StorageSetting>
backgroundColor: Theme.of(context).primaryColor,
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
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<SettingState, bool>(
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<bool>(
future: _getAutoDownloadNetwork(),
initialData: false,
builder: (context, snapshot) {
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
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<SettingState, bool>(
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<int>(
future: _getDownloadPasition(),
initialData: 0,
},
),
FutureBuilder<bool>(
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<int>(
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<int>(
value: _dirs.indexOf(dir),
groupValue: snapshot.data,
onChanged: _setDownloadPosition),
),
SizedBox(
height: 30,
)
]),
),
);
}),
FutureBuilder<int>(
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<int>(
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: <int>[-1, 5, 10, 15, 30]
.map<DropdownMenuItem<int>>((e) {
return DropdownMenuItem<int>(
value: e,
child: e == -1
? Text(s.daysCount(0))
: Text(s.daysCount(e)));
}).toList()),
);
}),
FutureBuilder<int>(
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: <int>[-1, 5, 10, 15, 30]
.map<DropdownMenuItem<int>>((e) {
return DropdownMenuItem<int>(
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<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(
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),
],
),
),
),
),

View File

@ -335,7 +335,7 @@ class DownloadState extends ChangeNotifier {
taskId: task.taskId, shouldDeleteContent: false);
}
Future delTask(EpisodeBrief episode) async {
Future<void> 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<void> _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);

View File

@ -362,7 +362,7 @@ class GroupList extends ChangeNotifier {
}
/// Load groups from storage at start.
Future loadGroups() async {
Future<void> 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<void> 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<void> 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<void> delGroup(PodcastGroup podcastGroup) async {
_isLoading = true;
for (var podcast in podcastGroup.podcastList) {
if (!_groups.first.podcastList.contains(podcast)) {

View File

@ -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<void> 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([]);

View File

@ -29,6 +29,8 @@ class SettingsBackup {
final int showNotesFont;
final List<String> 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<String, Object> 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);
}
}