diff --git a/lib/bloc/podcast/episode_bloc.dart b/lib/bloc/podcast/episode_bloc.dart index 1595178d..37a3d629 100644 --- a/lib/bloc/podcast/episode_bloc.dart +++ b/lib/bloc/podcast/episode_bloc.dart @@ -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>> _loadDownloads(bool silent) async* { diff --git a/lib/bloc/settings/settings_bloc.dart b/lib/bloc/settings/settings_bloc.dart index e0060e65..fcd0e398 100644 --- a/lib/bloc/settings/settings_bloc.dart +++ b/lib/bloc/settings/settings_bloc.dart @@ -16,6 +16,7 @@ class SettingsBloc extends Bloc { final BehaviorSubject _settings = BehaviorSubject.seeded(AppSettings.sensibleDefaults()); final BehaviorSubject _darkMode = BehaviorSubject(); final BehaviorSubject _markDeletedAsPlayed = BehaviorSubject(); + final BehaviorSubject _deleteDownloadedPlayedEpisodes = BehaviorSubject(); final BehaviorSubject _storeDownloadOnSDCard = BehaviorSubject(); final BehaviorSubject _playbackSpeed = BehaviorSubject(); final BehaviorSubject _searchProvider = BehaviorSubject(); @@ -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, @@ -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); @@ -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; @@ -172,6 +182,7 @@ class SettingsBloc extends Bloc { void dispose() { _darkMode.close(); _markDeletedAsPlayed.close(); + _deleteDownloadedPlayedEpisodes.close(); _storeDownloadOnSDCard.close(); _playbackSpeed.close(); _searchProvider.close(); diff --git a/lib/entities/app_settings.dart b/lib/entities/app_settings.dart index 060b5ab2..b226459f 100644 --- a/lib/entities/app_settings.dart +++ b/lib/entities/app_settings.dart @@ -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; @@ -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, @@ -63,6 +67,7 @@ class AppSettings { AppSettings.sensibleDefaults() : theme = 'dark', markDeletedEpisodesAsPlayed = false, + deleteDownloadedPlayedEpisodes = false, storeDownloadsSDCard = false, playbackSpeed = 1.0, searchProvider = 'itunes', @@ -78,6 +83,7 @@ class AppSettings { AppSettings copyWith({ String? theme, bool? markDeletedEpisodesAsPlayed, + bool? deleteDownloadedPlayedEpisodes, bool? storeDownloadsSDCard, double? playbackSpeed, String? searchProvider, @@ -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, diff --git a/lib/l10n/L.dart b/lib/l10n/L.dart index 693dfaa3..de42eb7d 100644 --- a/lib/l10n/L.dart +++ b/lib/l10n/L.dart @@ -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( diff --git a/lib/services/audio/default_audio_player_service.dart b/lib/services/audio/default_audio_player_service.dart index 9b86c70e..e965cb3b 100644 --- a/lib/services/audio/default_audio_player_service.dart +++ b/lib/services/audio/default_audio_player_service.dart @@ -100,6 +100,7 @@ class DefaultAudioPlayerService extends AudioPlayerService { builder: () => _DefaultAudioPlayerHandler( repository: repository, settings: settingsService, + podcastService: podcastService, ), config: const AudioServiceConfig( androidResumeOnClick: true, @@ -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) { @@ -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; @@ -807,6 +816,7 @@ class _DefaultAudioPlayerHandler extends BaseAudioHandler with SeekHandler { _DefaultAudioPlayerHandler({ required this.repository, required this.settings, + required this.podcastService, }) { _initPlayer(); } @@ -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; diff --git a/lib/services/settings/mobile_settings_service.dart b/lib/services/settings/mobile_settings_service.dart index f1afa152..35b765c8 100644 --- a/lib/services/settings/mobile_settings_service.dart +++ b/lib/services/settings/mobile_settings_service.dart @@ -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; diff --git a/lib/services/settings/settings_service.dart b/lib/services/settings/settings_service.dart index c5552e6d..59b27ddd 100644 --- a/lib/services/settings/settings_service.dart +++ b/lib/services/settings/settings_service.dart @@ -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); diff --git a/lib/ui/settings/settings.dart b/lib/ui/settings/settings.dart index 3c7c15ae..e8970cd8 100644 --- a/lib/ui/settings/settings.dart +++ b/lib/ui/settings/settings.dart @@ -79,6 +79,14 @@ class _SettingsState extends State { ), ), ), + 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( diff --git a/test/unit/mocks/mock_settings_service.dart b/test/unit/mocks/mock_settings_service.dart index 3536a54d..66353d0d 100644 --- a/test/unit/mocks/mock_settings_service.dart +++ b/test/unit/mocks/mock_settings_service.dart @@ -21,6 +21,9 @@ class MockSettingsService extends SettingsService { @override bool markDeletedEpisodesAsPlayed = false; + @override + bool deleteDownloadedPlayedEpisodes = false; + @override double playbackSpeed = 1; diff --git a/test/unit/services/settings_test.dart b/test/unit/services/settings_test.dart index e0abfb4d..61117ff6 100644 --- a/test/unit/services/settings_test.dart +++ b/test/unit/services/settings_test.dart @@ -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))); }