// Copyright 2020 Ben Hills and the project contributors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:pinepods_mobile/bloc/bloc.dart'; import 'package:pinepods_mobile/entities/episode.dart'; import 'package:pinepods_mobile/services/audio/audio_player_service.dart'; import 'package:pinepods_mobile/services/podcast/podcast_service.dart'; import 'package:pinepods_mobile/state/bloc_state.dart'; import 'package:logging/logging.dart'; import 'package:rxdart/rxdart.dart'; /// The BLoC provides access to [Episode] details outside the direct scope /// of a [Podcast]. class EpisodeBloc extends Bloc { final log = Logger('EpisodeBloc'); final PodcastService podcastService; final AudioPlayerService audioPlayerService; /// Add to sink to fetch list of current downloaded episodes. final BehaviorSubject _downloadsInput = BehaviorSubject(); /// Add to sink to fetch list of current episodes. final BehaviorSubject _episodesInput = BehaviorSubject(); /// Add to sink to delete the passed [Episode] from storage. final PublishSubject _deleteDownload = PublishSubject(); /// Add to sink to toggle played status of the [Episode]. final PublishSubject _togglePlayed = PublishSubject(); /// Stream of currently downloaded episodes Stream>>? _downloadsOutput; /// Stream of current episodes Stream>>? _episodesOutput; /// Cache of our currently downloaded episodes. List? _episodes; EpisodeBloc({ required this.podcastService, required this.audioPlayerService, }) { _init(); } void _init() { _downloadsOutput = _downloadsInput.switchMap>>((bool silent) => _loadDownloads(silent)); _episodesOutput = _episodesInput.switchMap>>((bool silent) => _loadEpisodes(silent)); _handleDeleteDownloads(); _handleMarkAsPlayed(); _listenEpisodeEvents(); } void _handleDeleteDownloads() async { _deleteDownload.stream.listen((episode) async { var nowPlaying = audioPlayerService.nowPlaying?.guid == episode?.guid; /// If we are attempting to delete the episode we are currently playing, we need to stop the audio. if (nowPlaying) { await audioPlayerService.stop(); } await podcastService.deleteDownload(episode!); fetchDownloads(true); }); } void _handleMarkAsPlayed() async { _togglePlayed.stream.listen((episode) async { await podcastService.toggleEpisodePlayed(episode!); fetchDownloads(true); }); } void _listenEpisodeEvents() { // Listen for episode updates. If the episode is downloaded, we need to update. podcastService.episodeListener!.where((event) => event.episode.downloaded || event.episode.played).listen((event) => fetchDownloads(true)); } Stream>> _loadDownloads(bool silent) async* { if (!silent) { yield BlocLoadingState(); } _episodes = await podcastService.loadDownloads(); yield BlocPopulatedState>(results: _episodes); } Stream>> _loadEpisodes(bool silent) async* { if (!silent) { yield BlocLoadingState(); } _episodes = await podcastService.loadEpisodes(); yield BlocPopulatedState>(results: _episodes); } @override void dispose() { _downloadsInput.close(); _deleteDownload.close(); _togglePlayed.close(); } void Function(bool) get fetchDownloads => _downloadsInput.add; void Function(bool) get fetchEpisodes => _episodesInput.add; Stream>>? get downloads => _downloadsOutput; Stream>>? get episodes => _episodesOutput; void Function(Episode?) get deleteDownload => _deleteDownload.add; void Function(Episode?) get togglePlayed => _togglePlayed.add; }