Skip to content

Commit

Permalink
Feature/automatically remove downloaded played episodes (#119)
Browse files Browse the repository at this point in the history
* Add settings

* Implement delete case

* Add test for settings related flag

* Remove useless log

* Move settings before SD Storage and change label

* Delegate deleting download episode to podcastService instead repository

* Varied deletion of episode from storage instead of (erroneously) from database

* Merge IOS related files from master

* Extend filter on episodeListener to played episodes.
With this trick, downloaded section is refreshed and the listener do not listen on all events

* Add control to check if episode has been downloaded before delete it

* Fix check for downloaded file
  • Loading branch information
mrkrash authored Nov 20, 2024
1 parent 6920c45 commit b15a21e
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 2 deletions.
2 changes: 1 addition & 1 deletion lib/bloc/podcast/episode_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class EpisodeBloc extends Bloc {

void _listenEpisodeEvents() {
// Listen for episode updates. If the episode is downloaded, we need to update.
podcastService.episodeListener!.where((event) => event.episode.downloaded).listen((event) => fetchDownloads(true));
podcastService.episodeListener!.where((event) => event.episode.downloaded || event.episode.played).listen((event) => fetchDownloads(true));
}

Stream<BlocState<List<Episode>>> _loadDownloads(bool silent) async* {
Expand Down
11 changes: 11 additions & 0 deletions lib/bloc/settings/settings_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class SettingsBloc extends Bloc {
final BehaviorSubject<AppSettings> _settings = BehaviorSubject<AppSettings>.seeded(AppSettings.sensibleDefaults());
final BehaviorSubject<bool> _darkMode = BehaviorSubject<bool>();
final BehaviorSubject<bool> _markDeletedAsPlayed = BehaviorSubject<bool>();
final BehaviorSubject<bool> _deleteDownloadedPlayedEpisodes = BehaviorSubject<bool>();
final BehaviorSubject<bool> _storeDownloadOnSDCard = BehaviorSubject<bool>();
final BehaviorSubject<double> _playbackSpeed = BehaviorSubject<double>();
final BehaviorSubject<String> _searchProvider = BehaviorSubject<String>();
Expand Down Expand Up @@ -44,6 +45,7 @@ class SettingsBloc extends Bloc {
_currentSettings = AppSettings(
theme: _settingsService.themeDarkMode ? 'dark' : 'light',
markDeletedEpisodesAsPlayed: _settingsService.markDeletedEpisodesAsPlayed,
deleteDownloadedPlayedEpisodes: _settingsService.deleteDownloadedPlayedEpisodes,
storeDownloadsSDCard: _settingsService.storeDownloadsSDCard,
playbackSpeed: _settingsService.playbackSpeed,
searchProvider: _settingsService.searchProvider,
Expand Down Expand Up @@ -71,6 +73,12 @@ class SettingsBloc extends Bloc {
_settingsService.markDeletedEpisodesAsPlayed = mark;
});

_deleteDownloadedPlayedEpisodes.listen((bool delete) {
_currentSettings = _currentSettings.copyWith(deleteDownloadedPlayedEpisodes: delete);
_settings.add(_currentSettings);
_settingsService.deleteDownloadedPlayedEpisodes = delete;
});

_storeDownloadOnSDCard.listen((bool sdcard) {
_currentSettings = _currentSettings.copyWith(storeDownloadsSDCard: sdcard);
_settings.add(_currentSettings);
Expand Down Expand Up @@ -148,6 +156,8 @@ class SettingsBloc extends Bloc {

void Function(bool) get markDeletedAsPlayed => _markDeletedAsPlayed.add;

void Function(bool) get deleteDownloadedPlayedEpisodes => _deleteDownloadedPlayedEpisodes.add;

void Function(double) get setPlaybackSpeed => _playbackSpeed.add;

void Function(bool) get setAutoOpenNowPlaying => _autoOpenNowPlaying.add;
Expand All @@ -172,6 +182,7 @@ class SettingsBloc extends Bloc {
void dispose() {
_darkMode.close();
_markDeletedAsPlayed.close();
_deleteDownloadedPlayedEpisodes.close();
_storeDownloadOnSDCard.close();
_playbackSpeed.close();
_searchProvider.close();
Expand Down
7 changes: 7 additions & 0 deletions lib/entities/app_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class AppSettings {
/// True if episodes are marked as played when deleted.
final bool markDeletedEpisodesAsPlayed;

/// True if downloaded played episodes must be deleted automatically.
final bool deleteDownloadedPlayedEpisodes;

/// True if downloads should be saved to the SD card.
final bool storeDownloadsSDCard;

Expand Down Expand Up @@ -47,6 +50,7 @@ class AppSettings {
AppSettings({
required this.theme,
required this.markDeletedEpisodesAsPlayed,
required this.deleteDownloadedPlayedEpisodes,
required this.storeDownloadsSDCard,
required this.playbackSpeed,
required this.searchProvider,
Expand All @@ -63,6 +67,7 @@ class AppSettings {
AppSettings.sensibleDefaults()
: theme = 'dark',
markDeletedEpisodesAsPlayed = false,
deleteDownloadedPlayedEpisodes = false,
storeDownloadsSDCard = false,
playbackSpeed = 1.0,
searchProvider = 'itunes',
Expand All @@ -78,6 +83,7 @@ class AppSettings {
AppSettings copyWith({
String? theme,
bool? markDeletedEpisodesAsPlayed,
bool? deleteDownloadedPlayedEpisodes,
bool? storeDownloadsSDCard,
double? playbackSpeed,
String? searchProvider,
Expand All @@ -93,6 +99,7 @@ class AppSettings {
AppSettings(
theme: theme ?? this.theme,
markDeletedEpisodesAsPlayed: markDeletedEpisodesAsPlayed ?? this.markDeletedEpisodesAsPlayed,
deleteDownloadedPlayedEpisodes: deleteDownloadedPlayedEpisodes ?? this.deleteDownloadedPlayedEpisodes,
storeDownloadsSDCard: storeDownloadsSDCard ?? this.storeDownloadsSDCard,
playbackSpeed: playbackSpeed ?? this.playbackSpeed,
searchProvider: searchProvider ?? this.searchProvider,
Expand Down
10 changes: 10 additions & 0 deletions lib/l10n/L.dart
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,16 @@ class L {
);
}

String get settings_delete_played_label {
return message('settings_delete_played_label') ??
Intl.message(
'Delete downloaded episodes once played',
name: 'settings_delete_played_label',
desc: 'Delete downloaded episodes once played setting',
locale: localeName,
);
}

String get settings_download_sd_card_label {
return message('settings_download_sd_card_label') ??
Intl.message(
Expand Down
16 changes: 15 additions & 1 deletion lib/services/audio/default_audio_player_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class DefaultAudioPlayerService extends AudioPlayerService {
builder: () => _DefaultAudioPlayerHandler(
repository: repository,
settings: settingsService,
podcastService: podcastService,
),
config: const AudioServiceConfig(
androidResumeOnClick: true,
Expand Down Expand Up @@ -524,6 +525,13 @@ class DefaultAudioPlayerService extends AudioPlayerService {

log.fine('We have completed episode ${_currentEpisode?.title}');

if (
settingsService.deleteDownloadedPlayedEpisodes &&
_currentEpisode?.downloadState == DownloadState.downloaded
) {
await podcastService.deleteDownload(_currentEpisode!);
}

_stopPositionTicker();

if (_queue.isEmpty) {
Expand Down Expand Up @@ -781,6 +789,7 @@ class _DefaultAudioPlayerHandler extends BaseAudioHandler with SeekHandler {
final log = Logger('DefaultAudioPlayerHandler');
final Repository repository;
final SettingsService settings;
final PodcastService podcastService;

static const rewindMillis = 10001;
static const fastForwardMillis = 30000;
Expand All @@ -807,6 +816,7 @@ class _DefaultAudioPlayerHandler extends BaseAudioHandler with SeekHandler {
_DefaultAudioPlayerHandler({
required this.repository,
required this.settings,
required this.podcastService,
}) {
_initPlayer();
}
Expand Down Expand Up @@ -1075,7 +1085,11 @@ class _DefaultAudioPlayerHandler extends BaseAudioHandler with SeekHandler {
storedEpisode.position = 0;
storedEpisode.played = true;

await repository.saveEpisode(storedEpisode);
if (settings.deleteDownloadedPlayedEpisodes) {
podcastService.deleteDownload(storedEpisode);
} else {
await repository.saveEpisode(storedEpisode);
}
} else if (currentPosition != storedEpisode.position) {
storedEpisode.position = currentPosition;

Expand Down
9 changes: 9 additions & 0 deletions lib/services/settings/mobile_settings_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ class MobileSettingsService extends SettingsService {
settingsNotifier.sink.add('markplayedasdeleted');
}

@override
bool get deleteDownloadedPlayedEpisodes => _sharedPreferences.getBool('deleteDownloadedPlayedEpisodes') ?? false;

@override
set deleteDownloadedPlayedEpisodes(bool value) {
_sharedPreferences.setBool('deleteDownloadedPlayedEpisodes', value);
settingsNotifier.sink.add('deleteDownloadedPlayedEpisodes');
}

@override
bool get storeDownloadsSDCard => _sharedPreferences.getBool('savesdcard') ?? false;

Expand Down
4 changes: 4 additions & 0 deletions lib/services/settings/settings_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ abstract class SettingsService {

set markDeletedEpisodesAsPlayed(bool value);

bool get deleteDownloadedPlayedEpisodes;

set deleteDownloadedPlayedEpisodes(bool value);

bool get storeDownloadsSDCard;

set storeDownloadsSDCard(bool value);
Expand Down
8 changes: 8 additions & 0 deletions lib/ui/settings/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ class _SettingsState extends State<Settings> {
),
),
),
ListTile(
shape: const RoundedRectangleBorder(side: BorderSide.none),
title: Text(L.of(context)!.settings_delete_played_label),
trailing: Switch.adaptive(
value: snapshot.data!.deleteDownloadedPlayedEpisodes,
onChanged: (value) => setState(() => settingsBloc.deleteDownloadedPlayedEpisodes(value)),
)
),
sdcard
? MergeSemantics(
child: ListTile(
Expand Down
3 changes: 3 additions & 0 deletions test/unit/mocks/mock_settings_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class MockSettingsService extends SettingsService {
@override
bool markDeletedEpisodesAsPlayed = false;

@override
bool deleteDownloadedPlayedEpisodes = false;

@override
double playbackSpeed = 1;

Expand Down
7 changes: 7 additions & 0 deletions test/unit/services/settings_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,11 @@ void main() {
mobileSettingsService?.layoutMode = 1;
expect(mobileSettingsService?.layoutMode, 1);
}, timeout: const Timeout(Duration(milliseconds: timeout)));

test('Test delete played downloaded episodes', () async {
expect(mobileSettingsService?.deleteDownloadedPlayedEpisodes, false);
expectLater(settingsListener, emits('deleteDownloadedPlayedEpisodes'));
mobileSettingsService?.deleteDownloadedPlayedEpisodes = true;
expect(mobileSettingsService?.deleteDownloadedPlayedEpisodes, true);
}, timeout: const Timeout(Duration(milliseconds: timeout)));
}

0 comments on commit b15a21e

Please sign in to comment.