import 'package:anitrack/src/data/anime.dart'; import 'package:anitrack/src/data/manga.dart'; import 'package:anitrack/src/data/type.dart'; import 'package:anitrack/src/service/database.dart'; import 'package:bloc/bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:get_it/get_it.dart'; part 'anime_list_state.dart'; part 'anime_list_event.dart'; part 'anime_list_bloc.freezed.dart'; class AnimeListBloc extends Bloc { AnimeListBloc() : super(AnimeListState()) { on(_onAnimeAdded); on(_onMangaAdded); on(_onAnimeIncremented); on(_onAnimeDecremented); on(_onAnimesLoaded); on(_onAnimesFiltered); on(_onTrackingTypeChanged); on(_onMangasFiltered); on(_onMangaIncremented); on(_onMangaDecremented); on(_onAnimeUpdated); on(_onMangaUpdated); on(_onAnimeRemoved); on(_onMangaRemoved); on(_onButtonVisibilityToggled); } /// Internal anime state final List _animes = List.empty(growable: true); final List _mangas = List.empty(growable: true); List _getFilteredAnime({ MediumTrackingState? trackingState, }) { final filterState = trackingState ?? state.animeFilterState; if (filterState == MediumTrackingState.all) return _animes; return _animes.where((anime) => anime.state == filterState).toList(); } List _getFilteredManga({ MediumTrackingState? trackingState, }) { final filterState = trackingState ?? state.mangaFilterState; if (state.mangaFilterState == MediumTrackingState.all) return _mangas; return _mangas.where((manga) => manga.state == filterState).toList(); } Future _onAnimeAdded( AnimeAddedEvent event, Emitter emit, ) async { // Add the anime to the database await GetIt.I.get().addAnime(event.data); // Add it to the cache _animes.add(event.data); emit( state.copyWith( animes: _getFilteredAnime(), ), ); } Future _onMangaAdded( MangaAddedEvent event, Emitter emit, ) async { // Add the manga to the database await GetIt.I.get().addManga(event.data); // Add it to the cache _mangas.add(event.data); emit( state.copyWith( mangas: _getFilteredManga(), ), ); } Future _onAnimeIncremented( AnimeEpisodeIncrementedEvent event, Emitter emit, ) async { final index = state.animes.indexWhere((item) => item.id == event.id); if (index == -1) return; final anime = state.animes[index]; if (anime.episodesTotal != null && anime.episodesWatched + 1 > anime.episodesTotal!) return; final newList = List.from(state.animes); final newAnime = anime.copyWith( episodesWatched: anime.episodesWatched + 1, ); newList[index] = newAnime; emit( state.copyWith( animes: newList, ), ); await GetIt.I.get().updateAnime(newAnime); } Future _onAnimeDecremented( AnimeEpisodeDecrementedEvent event, Emitter emit, ) async { final index = state.animes.indexWhere((item) => item.id == event.id); if (index == -1) return; final anime = state.animes[index]; if (anime.episodesWatched - 1 < 0) return; final newList = List.from(state.animes); final newAnime = anime.copyWith( episodesWatched: anime.episodesWatched - 1, ); newList[index] = newAnime; emit( state.copyWith( animes: newList, ), ); await GetIt.I.get().updateAnime(newAnime); } Future _onAnimesLoaded( AnimesLoadedEvent event, Emitter emit, ) async { _animes.addAll( await GetIt.I.get().loadAnimes(), ); _mangas.addAll( await GetIt.I.get().loadMangas(), ); emit( state.copyWith( animes: _getFilteredAnime(), mangas: _getFilteredManga(), ), ); } Future _onAnimesFiltered( AnimeFilterChangedEvent event, Emitter emit, ) async { emit( state.copyWith( animeFilterState: event.filterState, animes: _getFilteredAnime(trackingState: event.filterState), ), ); } Future _onMangasFiltered( MangaFilterChangedEvent event, Emitter emit, ) async { emit( state.copyWith( mangaFilterState: event.filterState, mangas: _getFilteredManga(trackingState: event.filterState), ), ); } Future _onTrackingTypeChanged( AnimeTrackingTypeChanged event, Emitter emit, ) async { emit( state.copyWith( trackingType: event.type, buttonVisibility: true, ), ); } Future _onMangaIncremented( MangaChapterIncrementedEvent event, Emitter emit, ) async { final index = state.mangas.indexWhere((item) => item.id == event.id); assert(index != -1, 'The manga must exist'); final manga = state.mangas[index]; if (manga.chaptersTotal != null && manga.chaptersRead + 1 > manga.chaptersTotal!) return; final newList = List.from(state.mangas); final newManga = manga.copyWith( chaptersRead: manga.chaptersRead + 1, ); newList[index] = newManga; // Update the cache final cacheIndex = _mangas.indexWhere((m) => m.id == event.id); assert(cacheIndex != -1, 'The manga must exist'); _mangas[cacheIndex] = newManga; emit( state.copyWith( mangas: newList, ), ); await GetIt.I.get().updateManga(newManga); } Future _onMangaDecremented( MangaChapterDecrementedEvent event, Emitter emit, ) async { final index = state.mangas.indexWhere((item) => item.id == event.id); if (index == -1) return; final manga = state.mangas[index]; if (manga.chaptersRead - 1 < 0) return; final newList = List.from(state.mangas); final newManga = manga.copyWith( chaptersRead: manga.chaptersRead - 1, ); newList[index] = newManga; // Update the cache final cacheIndex = _mangas.indexWhere((m) => m.id == event.id); assert(cacheIndex != -1, 'The manga must exist'); _mangas[cacheIndex] = newManga; emit( state.copyWith( mangas: newList, ), ); await GetIt.I.get().updateManga(newManga); } Future _onAnimeUpdated( AnimeUpdatedEvent event, Emitter emit, ) async { final index = _animes.indexWhere((anime) => anime.id == event.anime.id); assert(index != -1, 'The anime must exist'); _animes[index] = event.anime; emit( state.copyWith( animes: _getFilteredAnime(), ), ); } Future _onMangaUpdated( MangaUpdatedEvent event, Emitter emit, ) async { final index = _mangas.indexWhere((manga) => manga.id == event.manga.id); assert(index != -1, 'The manga must exist'); _mangas[index] = event.manga; emit( state.copyWith( mangas: _getFilteredManga(), ), ); } Future _onAnimeRemoved( AnimeRemovedEvent event, Emitter emit, ) async { emit( state.copyWith( animes: List.from( state.animes.where((anime) => anime.id != event.id), ), ), ); // Update the cache final cacheIndex = _mangas.indexWhere((m) => m.id == event.id); assert(cacheIndex != -1, 'The anime must exist'); _mangas.removeAt(cacheIndex); // Update the database await GetIt.I.get().deleteAnime(event.id); } Future _onMangaRemoved( MangaRemovedEvent event, Emitter emit, ) async { emit( state.copyWith( mangas: List.from( state.mangas.where((manga) => manga.id != event.id), ), ), ); // Update the cache final cacheIndex = _animes.indexWhere((a) => a.id == event.id); assert(cacheIndex != -1, 'The manga must exist'); _animes.removeAt(cacheIndex); // Update the database await GetIt.I.get().deleteManga(event.id); } Future _onButtonVisibilityToggled( AddButtonVisibilitySetEvent event, Emitter emit, ) async { emit( state.copyWith( buttonVisibility: event.state, ), ); } }