rewrite: Added a factory pattern for metadata services

This commit is contained in:
Kevin Rodrigues Borges
2026-05-25 17:14:36 +01:00
parent 9a17bd801c
commit d5deb875be
16 changed files with 739 additions and 436 deletions

View File

@@ -8,12 +8,12 @@ import 'package:unyo/application/cubits/effect_mixin.dart';
import 'package:unyo/application/effects/app_effects.dart';
import 'package:unyo/application/states/anime_advanced_search_state.dart';
import 'package:unyo/core/di/locator.dart';
import 'package:unyo/core/enums/service.dart';
import 'package:unyo/core/notification/anime_genres_notifier.dart';
import 'package:unyo/core/notification/anime_notifier.dart';
import 'package:unyo/core/notification/media_list_notifier.dart';
import 'package:unyo/core/notification/user_notifier.dart';
import 'package:unyo/data/repositories/anime_repository_anilist.dart';
import 'package:unyo/domain/repositories/anime_repository.dart';
import 'package:unyo/domain/entities/media/anime.dart';
import 'package:unyo/domain/entities/list/media_list.dart';
import 'package:unyo/domain/entities/user/user.dart';
@@ -31,14 +31,14 @@ class AnimeAdvancedSearchCubit extends Cubit<AnimeAdvancedSearchState>
late final StreamSubscription<String> _selectedAnimeAdvancedSearchGenresFiltersSubscription;
// Repositories
final AnimeRepositoryAnilist _animeRepositoryAnilist;
final AnimeRepository _animeRepository;
AnimeAdvancedSearchCubit(
this._loggedUserNotifier,
this._selectedMediaListNotifier,
this._selectedAnimeNotifier,
this._selectedAnimeAdvancedSearchGenresFilters,
this._animeRepositoryAnilist,
this._animeRepository,
) : super(AnimeAdvancedSearchState(loggedUser: UserModel.empty())) {
_init();
}
@@ -173,29 +173,21 @@ class AnimeAdvancedSearchCubit extends Cubit<AnimeAdvancedSearchState>
Future<void> _getLoggedUserServiceFilters(User loggedUser) async {
try {
switch (loggedUser.settings.service) {
case Service.anilist:
final filters = await _animeRepositoryAnilist.getUserAnimeAdvancedSearchFilters();
emit(
state.copyWith(
genresFilters: (filters['genres']?.$1 ?? false, filters['genres']?.$2 ?? []),
seasonFilters: (filters['seasons']?.$1 ?? false, filters['seasons']?.$2 ?? []),
formatFilters: (filters['formats']?.$1 ?? false, filters['formats']?.$2 ?? []),
airingStatusFilters: (
filters['airingStatuses']?.$1 ?? false,
filters['airingStatuses']?.$2 ?? [],
),
yearFilters: (filters['years']?.$1 ?? false, filters['years']?.$2 ?? []),
searchSortOptions: (filters['sortOptions']?.$1 ?? false, filters['sortOptions']?.$2 ?? []),
searchSortOrder: (filters['sortOrders']?.$1 ?? false, filters['sortOrders']?.$2 ?? []),
),
);
break;
case Service.mal:
case Service.shikimori:
case Service.simkl:
case Service.kitsu:
}
final filters = await _animeRepository.getUserAnimeAdvancedSearchFilters();
emit(
state.copyWith(
genresFilters: (filters['genres']?.$1 ?? false, filters['genres']?.$2 ?? []),
seasonFilters: (filters['seasons']?.$1 ?? false, filters['seasons']?.$2 ?? []),
formatFilters: (filters['formats']?.$1 ?? false, filters['formats']?.$2 ?? []),
airingStatusFilters: (
filters['airingStatuses']?.$1 ?? false,
filters['airingStatuses']?.$2 ?? [],
),
yearFilters: (filters['years']?.$1 ?? false, filters['years']?.$2 ?? []),
searchSortOptions: (filters['sortOptions']?.$1 ?? false, filters['sortOptions']?.$2 ?? []),
searchSortOrder: (filters['sortOrders']?.$1 ?? false, filters['sortOrders']?.$2 ?? []),
),
);
} catch (e, stackTrace) {
_logger.e("Error getting logged user service filters $e", stackTrace: stackTrace);
handleError("Error getting logged user service filters", stackTrace: stackTrace);
@@ -203,24 +195,17 @@ class AnimeAdvancedSearchCubit extends Cubit<AnimeAdvancedSearchState>
}
Future<void> _performAnimeAdvancedSearch() async {
switch (state.loggedUser.settings.service) {
case Service.anilist:
List<Anime> searchResults = await _animeRepositoryAnilist.performAnimeAdvancedSearch(
state.searchQuery,
state.selectedGenres,
state.selectedSeason,
state.selectedFormat,
int.tryParse(state.selectedYear ?? ''),
state.selectedAiringStatus,
"${state.selectedSearchSortOption.toUpperCase().replaceAll(" ", "_")}_${state.selectedSearchOrder.toUpperCase()}",
1,
state.loggedUser
);
emit(state.copyWith(searchResults: searchResults));
case Service.mal:
case Service.shikimori:
case Service.simkl:
case Service.kitsu:
}
List<Anime> searchResults = await _animeRepository.performAnimeAdvancedSearch(
state.searchQuery,
state.selectedGenres,
state.selectedSeason,
state.selectedFormat,
int.tryParse(state.selectedYear ?? ''),
state.selectedAiringStatus,
"${state.selectedSearchSortOption.toUpperCase().replaceAll(" ", "_")}_${state.selectedSearchOrder.toUpperCase()}",
1,
state.loggedUser
);
emit(state.copyWith(searchResults: searchResults));
}
}

View File

@@ -9,14 +9,14 @@ import 'package:unyo/application/cubits/effect_mixin.dart';
import 'package:unyo/application/effects/app_effects.dart';
import 'package:unyo/application/states/anime_state.dart';
import 'package:unyo/core/di/locator.dart';
import 'package:unyo/core/enums/service.dart';
import 'package:unyo/core/notification/anime_genres_notifier.dart';
import 'package:unyo/core/notification/anime_notifier.dart';
import 'package:unyo/core/notification/media_list_notifier.dart';
import 'package:unyo/core/notification/reload/reload_type.dart';
import 'package:unyo/core/notification/user_notifier.dart';
import 'package:unyo/core/services/api/http/http_exception.dart';
import 'package:unyo/data/repositories/anime_repository_anilist.dart';
import 'package:unyo/domain/repositories/anime_repository.dart';
import 'package:unyo/domain/entities/media/anime.dart';
import 'package:unyo/domain/entities/list/media_list.dart';
import 'package:unyo/domain/entities/user/user.dart';
@@ -24,7 +24,7 @@ import 'package:unyo/domain/entities/user/user.dart';
import '../../core/notification/reload/reload_notifier.dart';
class AnimeCubit extends Cubit<AnimeState> with EffectMixin<AnimeState> {
final AnimeRepositoryAnilist _animeRepositoryAnilist;
final AnimeRepository _animeRepository;
final UserNotifier _loggedUserNotifier;
final AnimeNotifier _selectedAnimeNotifier;
final AnimeGenresNotifier _selectedAnimeAdvancedSearchGenresFilters;
@@ -35,7 +35,7 @@ class AnimeCubit extends Cubit<AnimeState> with EffectMixin<AnimeState> {
final Logger _logger = sl<Logger>();
AnimeCubit(
this._animeRepositoryAnilist,
this._animeRepository,
this._loggedUserNotifier,
this._selectedAnimeNotifier,
this._selectedAnimeAdvancedSearchGenresFilters,
@@ -123,17 +123,10 @@ class AnimeCubit extends Cubit<AnimeState> with EffectMixin<AnimeState> {
Future<void> _fetchRecentlyReleased(int page, User loggedUser, {bool ignoreCache = false}) async {
try {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist recently released anime");
(bool, List<Anime>) recentlyReleased = await _animeRepositoryAnilist
.getRecentlyReleasedAnimes(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(recentlyReleased: recentlyReleased));
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
}
_logger.i("Fetching recently released anime");
(bool, List<Anime>) recentlyReleased = await _animeRepository
.getRecentlyReleasedAnimes(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(recentlyReleased: recentlyReleased));
} on HttpServerException catch (e, stackTrace) {
handleError(
"Failed to fetch recently released anime:",
@@ -150,27 +143,20 @@ class AnimeCubit extends Cubit<AnimeState> with EffectMixin<AnimeState> {
Future<void> _fetchTrending(int page, User loggedUser, {bool ignoreCache = false}) async {
try {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist trending anime");
(bool, List<Anime>) trending = await _animeRepositoryAnilist
.getTrendingAnimes(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(trending: trending));
if (page == 1) {
List<Anime> banners = trending.$2.where((anime) => anime.bannerImage != "").toList();
emit(
state.copyWith(
banners: banners.sublist(
0,
banners.length > 20 ? 20 : banners.length,
),
),
);
}
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
_logger.i("Fetching trending anime");
(bool, List<Anime>) trending = await _animeRepository
.getTrendingAnimes(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(trending: trending));
if (page == 1) {
List<Anime> banners = trending.$2.where((anime) => anime.bannerImage != "").toList();
emit(
state.copyWith(
banners: banners.sublist(
0,
banners.length > 20 ? 20 : banners.length,
),
),
);
}
} on HttpServerException catch (e, stackTrace) {
handleError(
@@ -185,17 +171,10 @@ class AnimeCubit extends Cubit<AnimeState> with EffectMixin<AnimeState> {
Future<void> _fetchPopular(int page, User loggedUser, {bool ignoreCache = false}) async {
try {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist popular anime");
(bool, List<Anime>) popular = await _animeRepositoryAnilist
.getPopularAnimes(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(popular: popular));
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
}
_logger.i("Fetching popular anime");
(bool, List<Anime>) popular = await _animeRepository
.getPopularAnimes(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(popular: popular));
} on HttpServerException catch (e, stackTrace) {
handleError(
"Failed to fetch popular anime:",
@@ -209,17 +188,10 @@ class AnimeCubit extends Cubit<AnimeState> with EffectMixin<AnimeState> {
Future<void> _fetchRecentlyCompleted(int page, User loggedUser, {bool ignoreCache = false}) async {
try {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist recently completed anime");
(bool, List<Anime>) recentlyCompleted = await _animeRepositoryAnilist
.getRecentlyCompletedAnimes(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(recentlyCompleted: recentlyCompleted));
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
}
_logger.i("Fetching recently completed anime");
(bool, List<Anime>) recentlyCompleted = await _animeRepository
.getRecentlyCompletedAnimes(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(recentlyCompleted: recentlyCompleted));
} on HttpServerException catch (e, stackTrace) {
handleError(
"Failed to fetch recently completed anime:",
@@ -236,17 +208,10 @@ class AnimeCubit extends Cubit<AnimeState> with EffectMixin<AnimeState> {
Future<void> _fetchUpcoming(int page, User loggedUser, {bool ignoreCache = false}) async {
try {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist upcoming anime");
(bool, List<Anime>) upcoming = await _animeRepositoryAnilist
.getUpcomingAnimes(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(upcoming: upcoming));
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
}
_logger.i("Fetching upcoming anime");
(bool, List<Anime>) upcoming = await _animeRepository
.getUpcomingAnimes(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(upcoming: upcoming));
} on HttpServerException catch (e, stackTrace) {
handleError(
"Failed to fetch upcoming anime:",

View File

@@ -30,7 +30,7 @@ import 'package:unyo/core/notification/video_info_notifier.dart';
import 'package:unyo/core/services/api/http/http_exception.dart';
import 'package:unyo/data/models/anilist/anilist_user_model.dart';
import 'package:unyo/data/models/local/local_user_model.dart';
import 'package:unyo/data/repositories/anime_repository_anilist.dart';
import 'package:unyo/domain/repositories/anime_repository.dart';
import 'package:unyo/data/repositories/episode_repository_anizip.dart';
import 'package:unyo/data/repositories/extension_repository_aniyomi.dart';
import 'package:unyo/data/repositories/repositories.dart';
@@ -49,7 +49,7 @@ import 'package:unyo/presentation/drawers/anime_server_selection_drawer.dart';
class AnimeDetailsCubit extends Cubit<AnimeDetailsState> with EffectMixin<AnimeDetailsState> {
// Repositories
final AnimeRepositoryAnilist _animeRepositoryAnilist;
final AnimeRepository _animeRepository;
final EpisodeRepositoryAnizip _episodeRepositoryAnizip;
final ExtensionRepositoryAniyomi _extensionRepositoryAniyomi;
final UserRepositoryAnilist _userRepositoryAnilist;
@@ -74,7 +74,7 @@ class AnimeDetailsCubit extends Cubit<AnimeDetailsState> with EffectMixin<AnimeD
final Logger _logger = sl<Logger>();
AnimeDetailsCubit(
this._animeRepositoryAnilist,
this._animeRepository,
this._episodeRepositoryAnizip,
this._loggedUserNotifier,
this._selectedAnimeNotifier,
@@ -278,26 +278,15 @@ class AnimeDetailsCubit extends Cubit<AnimeDetailsState> with EffectMixin<AnimeD
MediaListEntry desiredMediaListEntry = state.newMediaListEntry;
MediaListEntry beforeUpdateMediaListEntry = state.mediaListEntry;
try {
switch (state.loggedUser.settings.service) {
case Service.anilist:
_logger.i("Updating Media List Entry to $desiredMediaListEntry on Anilist");
await _animeRepositoryAnilist.updateMediaListEntry(
desiredMediaListEntry,
state.selectedAnime,
state.loggedUser,
);
_reloadNotifier.emitReload(ReloadType.animeMediaListEntryUpdated);
if (desiredMediaListEntry.status != beforeUpdateMediaListEntry.status) {
_reloadNotifier.emitReload(ReloadType.homeMediaListEntryUpdated);
}
case Service.mal:
_logger.i("Updating Media List Entry to $desiredMediaListEntry on MyAnimeList");
case Service.shikimori:
_logger.i("Updating Media List Entry to $desiredMediaListEntry on Shikimori");
case Service.kitsu:
_logger.i("Updating Media List Entry to $desiredMediaListEntry on Kitsu");
case Service.simkl:
_logger.i("Updating Media List Entry to $desiredMediaListEntry on Simkl");
_logger.i("Updating Media List Entry to $desiredMediaListEntry");
await _animeRepository.updateMediaListEntry(
desiredMediaListEntry,
state.selectedAnime,
state.loggedUser,
);
_reloadNotifier.emitReload(ReloadType.animeMediaListEntryUpdated);
if (desiredMediaListEntry.status != beforeUpdateMediaListEntry.status) {
_reloadNotifier.emitReload(ReloadType.homeMediaListEntryUpdated);
}
} on HttpServerException catch (e, stackTrace) {
handleError("Error updating Anime Entry:", responseBody: e.message, stackTrace: stackTrace);
@@ -430,25 +419,14 @@ class AnimeDetailsCubit extends Cubit<AnimeDetailsState> with EffectMixin<AnimeD
Future<void> _getUserMediaListEntry(User loggedUser, Anime selectedAnime, {bool ignoreCache = false}) async {
try {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching User Media List Entry from AniList for ${state.selectedAnime.title.userPreferred}");
MediaListEntry mediaListEntry = await _animeRepositoryAnilist.getMediaListEntry(
selectedAnime,
loggedUser,
ignoreCache: ignoreCache,
);
emit(state.copyWith(mediaListEntry: mediaListEntry, newMediaListEntry: mediaListEntry));
_mediaListEntryNotifier.updateSelectedMediaListEntry(mediaListEntry);
case Service.mal:
_logger.i("Fetching User Media List Entry from MyAnimeList for ${state.selectedAnime.title.userPreferred}");
case Service.shikimori:
_logger.i("Fetching User Media List Entry from Shikimori for ${state.selectedAnime.title.userPreferred}");
case Service.kitsu:
_logger.i("Fetching User Media List Entry from Kitsu for ${state.selectedAnime.title.userPreferred}");
case Service.simkl:
_logger.i("Fetching User Media List Entry from Simkl for ${state.selectedAnime.title.userPreferred}");
}
_logger.i("Fetching User Media List Entry for ${state.selectedAnime.title.userPreferred}");
MediaListEntry mediaListEntry = await _animeRepository.getMediaListEntry(
selectedAnime,
loggedUser,
ignoreCache: ignoreCache,
);
emit(state.copyWith(mediaListEntry: mediaListEntry, newMediaListEntry: mediaListEntry));
_mediaListEntryNotifier.updateSelectedMediaListEntry(mediaListEntry);
} on HttpServerException catch (e, stackTrace) {
handleError("Error fetching User Media List entry:", responseBody: e.message, stackTrace: stackTrace);
} catch (e, stackTrace) {
@@ -458,41 +436,30 @@ class AnimeDetailsCubit extends Cubit<AnimeDetailsState> with EffectMixin<AnimeD
Future<void> _getAnimeDetails(User loggedUser, Anime selectedAnime) async {
try {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anime Details from AniList for ${state.selectedAnime.title.userPreferred}");
(bool, AnimeDetails) animeDetails = await _animeRepositoryAnilist.getAnimeDetails(
selectedAnime,
loggedUser,
);
emit(
state.copyWith(
characters: (animeDetails.$2.characters.isNotEmpty, animeDetails.$2.characters),
mediaListEntry: animeDetails.$2.mediaListEntry,
newMediaListEntry: animeDetails.$2.mediaListEntry,
recommendations: (
animeDetails.$2.recommendedAnimes.isNotEmpty,
animeDetails.$2.recommendedAnimes,
),
),
);
if (animeDetails.$2.recommendedAnimes.isEmpty) {
(bool, List<Anime>) trendingAnimes = await _animeRepositoryAnilist.getTrendingAnimes(
1,
loggedUser,
);
emit(state.copyWith(recommendations: (trendingAnimes.$1, trendingAnimes.$2.shuffled(Random()))));
}
_getAlternativeImage(loggedUser, selectedAnime);
case Service.mal:
_logger.i("Fetching Anime Details from MyAnimeList for ${state.selectedAnime.title.userPreferred}");
case Service.shikimori:
_logger.i("Fetching Anime Details from Shikimori for ${state.selectedAnime.title.userPreferred}");
case Service.kitsu:
_logger.i("Fetching Anime Details from Kitsu for ${state.selectedAnime.title.userPreferred}");
case Service.simkl:
_logger.i("Fetching Anime Details from Simkl for ${state.selectedAnime.title.userPreferred}");
_logger.i("Fetching Anime Details for ${state.selectedAnime.title.userPreferred}");
(bool, AnimeDetails) animeDetails = await _animeRepository.getAnimeDetails(
selectedAnime,
loggedUser,
);
emit(
state.copyWith(
characters: (animeDetails.$2.characters.isNotEmpty, animeDetails.$2.characters),
mediaListEntry: animeDetails.$2.mediaListEntry,
newMediaListEntry: animeDetails.$2.mediaListEntry,
recommendations: (
animeDetails.$2.recommendedAnimes.isNotEmpty,
animeDetails.$2.recommendedAnimes,
),
),
);
if (animeDetails.$2.recommendedAnimes.isEmpty) {
(bool, List<Anime>) trendingAnimes = await _animeRepository.getTrendingAnimes(
1,
loggedUser,
);
emit(state.copyWith(recommendations: (trendingAnimes.$1, trendingAnimes.$2.shuffled(Random()))));
}
_getAlternativeImage(loggedUser, selectedAnime);
} on HttpServerException catch (e, stackTrace) {
handleError("Error fetching Anime details:", responseBody: e.message, stackTrace: stackTrace);
} catch (e, stackTrace) {
@@ -750,20 +717,9 @@ class AnimeDetailsCubit extends Cubit<AnimeDetailsState> with EffectMixin<AnimeD
Future<void> _getAnimeBanners(User loggedUser) async {
try {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anime Banners from AniList for ${state.selectedAnime.title.userPreferred}");
List<String> banners = await _animeRepositoryAnilist.getMediaCoverImages(loggedUser);
emit(state.copyWith(banners: banners));
case Service.mal:
_logger.i("Fetching Anime Banners from MyAnimeList for ${state.selectedAnime.title.userPreferred}");
case Service.shikimori:
_logger.i("Fetching Anime Banners from Shikimori for ${state.selectedAnime.title.userPreferred}");
case Service.kitsu:
_logger.i("Fetching Anime Banners from Kitsu for ${state.selectedAnime.title.userPreferred}");
case Service.simkl:
_logger.i("Fetching Anime Banners from Simkl for ${state.selectedAnime.title.userPreferred}");
}
_logger.i("Fetching Anime Banners for ${state.selectedAnime.title.userPreferred}");
List<String> banners = await _animeRepository.getMediaCoverImages(loggedUser);
emit(state.copyWith(banners: banners));
} on HttpServerException catch (e, stackTrace) {
handleError("Error fetching Anime banners:", responseBody: e.message, stackTrace: stackTrace);
} catch (e, stackTrace) {

View File

@@ -10,24 +10,24 @@ import 'package:unyo/application/cubits/effect_mixin.dart';
import 'package:unyo/application/effects/app_effects.dart';
import 'package:unyo/application/states/calendar_state.dart';
import 'package:unyo/core/di/locator.dart';
import 'package:unyo/core/enums/service.dart';
import 'package:unyo/core/notification/anime_notifier.dart';
import 'package:unyo/core/notification/media_list_notifier.dart';
import 'package:unyo/core/notification/user_notifier.dart';
import 'package:unyo/data/repositories/anime_repository_anilist.dart';
import 'package:unyo/domain/repositories/anime_repository.dart';
import 'package:unyo/domain/entities/media/anime.dart';
import 'package:unyo/domain/entities/list/media_list.dart';
import 'package:unyo/domain/entities/user/user.dart';
class CalendarCubit extends Cubit<CalendarState> with EffectMixin<CalendarState> {
final Logger _logger = sl<Logger>();
final AnimeRepositoryAnilist _animeRepositoryAnilist;
final AnimeRepository _animeRepository;
final UserNotifier _loggedUserNotifier;
final AnimeNotifier _selectedAnimeNotifier;
final MediaListNotifier _selectedMediaListNotifier;
late StreamSubscription<User> _loggedUserSubscription;
CalendarCubit(this._animeRepositoryAnilist, this._loggedUserNotifier, this._selectedAnimeNotifier, this._selectedMediaListNotifier)
CalendarCubit(this._animeRepository, this._loggedUserNotifier, this._selectedAnimeNotifier, this._selectedMediaListNotifier)
: super(CalendarState(animeCalendarReleases: {}, loggedUser: UserModel.empty())) {
_init();
}
@@ -70,20 +70,13 @@ class CalendarCubit extends Cubit<CalendarState> with EffectMixin<CalendarState>
Future<void> _getCalendarEvents(User loggedUser) async {
try {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist calendar releases");
Map<String, List<Anime>> animeCalendarReleases = await _animeRepositoryAnilist.getCalendarReleases(1, loggedUser);
emit(
state.copyWith(
animeCalendarReleases: animeCalendarReleases,
),
);
case Service.mal:
case Service.shikimori:
case Service.kitsu:
case Service.simkl:
}
_logger.i("Fetching calendar releases");
Map<String, List<Anime>> animeCalendarReleases = await _animeRepository.getCalendarReleases(1, loggedUser);
emit(
state.copyWith(
animeCalendarReleases: animeCalendarReleases,
),
);
} catch (e, stackTrace) {
handleError("Error fetching user info: $e", stackTrace: stackTrace);
}

View File

@@ -12,7 +12,7 @@ import 'package:unyo/application/cubits/effect_mixin.dart';
import 'package:unyo/application/states/home_state.dart';
import 'package:unyo/core/di/locator.dart';
import 'package:unyo/core/enums/selected_menu_option.dart';
import 'package:unyo/core/enums/service.dart';
import 'package:unyo/core/notification/anime_notifier.dart';
import 'package:unyo/core/notification/manga_notifier.dart';
import 'package:unyo/core/notification/media_list_notifier.dart';
@@ -23,7 +23,7 @@ import 'package:unyo/core/notification/user_notifier.dart';
import 'package:unyo/application/effects/app_effects.dart';
import 'package:unyo/data/models/anilist/anilist_user_model.dart';
import 'package:unyo/data/models/local/local_user_model.dart';
import 'package:unyo/data/repositories/anime_repository_anilist.dart';
import 'package:unyo/domain/repositories/anime_repository.dart';
import 'package:unyo/data/repositories/repositories.dart';
import 'package:unyo/domain/entities/media/anime.dart';
import 'package:unyo/domain/entities/media/manga.dart';
@@ -33,7 +33,7 @@ import 'package:unyo/domain/entities/user/user.dart';
class HomeCubit extends Cubit<HomeState> with EffectMixin<HomeState> {
// Repositories
final UserRepositoryAnilist _userRepositoryAnilist;
final AnimeRepositoryAnilist _animeRepositoryAnilist;
final AnimeRepository _animeRepository;
// Notifiers / Subscriptions
final UserNotifier _loggedUserNotifier;
final MenuBarNotifier _menuBarNotifier;
@@ -51,7 +51,7 @@ class HomeCubit extends Cubit<HomeState> with EffectMixin<HomeState> {
this._selectedMangaNotifier,
this._selectedMediaListNotifier,
this._userRepositoryAnilist,
this._animeRepositoryAnilist,
this._animeRepository,
this._menuBarNotifier,
this._reloadNotifier,
) : super(
@@ -162,20 +162,9 @@ class HomeCubit extends Cubit<HomeState> with EffectMixin<HomeState> {
Future<void> _getMediaCoverImages(User loggedUser, {bool ignoreCache = false}) async {
try {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Media Cover Images from AniList");
List<String> mediaCoverImages = await _animeRepositoryAnilist.getMediaCoverImages(loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(mediaCoverImages: mediaCoverImages));
case Service.mal:
_logger.i("Fetching Media Cover Images from MyAnimeList");
case Service.shikimori:
_logger.i("Fetching Media Cover Images from Shikimori");
case Service.kitsu:
_logger.i("Fetching Media Cover Images from Kitsu");
case Service.simkl:
_logger.i("Fetching Media Cover Images from Simkl");
}
_logger.i("Fetching Media Cover Images");
List<String> mediaCoverImages = await _animeRepository.getMediaCoverImages(loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(mediaCoverImages: mediaCoverImages));
} catch (e, stackTrace) {
handleError("Error fetching media cover images: $e", stackTrace: stackTrace);
}

View File

@@ -8,12 +8,12 @@ import 'package:unyo/application/cubits/effect_mixin.dart';
import 'package:unyo/application/effects/app_effects.dart';
import 'package:unyo/application/states/manga_advanced_search_state.dart';
import 'package:unyo/core/di/locator.dart';
import 'package:unyo/core/enums/service.dart';
import 'package:unyo/core/notification/manga_genres_notifier.dart';
import 'package:unyo/core/notification/manga_notifier.dart';
import 'package:unyo/core/notification/media_list_notifier.dart';
import 'package:unyo/core/notification/user_notifier.dart';
import 'package:unyo/data/repositories/manga_repository_anilist.dart';
import 'package:unyo/domain/repositories/manga_repository.dart';
import 'package:unyo/domain/entities/media/manga.dart';
import 'package:unyo/domain/entities/list/media_list.dart';
import 'package:unyo/domain/entities/user/user.dart';
@@ -31,14 +31,14 @@ class MangaAdvancedSearchCubit extends Cubit<MangaAdvancedSearchState>
late final StreamSubscription<String> _selectedMangaAdvancedSearchGenresFiltersSubscription;
// Repositories
final MangaRepositoryAnilist _mangaRepositoryAnilist;
final MangaRepository _mangaRepository;
MangaAdvancedSearchCubit(
this._loggedUserNotifier,
this._selectedMediaListNotifier,
this._selectedMangaNotifier,
this._selectedMangaAdvancedSearchGenresFilters,
this._mangaRepositoryAnilist,
this._mangaRepository,
) : super(MangaAdvancedSearchState(loggedUser: UserModel.empty())) {
_init();
}
@@ -162,28 +162,20 @@ class MangaAdvancedSearchCubit extends Cubit<MangaAdvancedSearchState>
Future<void> _getLoggedUserServiceFilters(User loggedUser) async {
try {
switch (loggedUser.settings.service) {
case Service.anilist:
final filters = await _mangaRepositoryAnilist.getUserMangaAdvancedSearchFilters();
emit(
state.copyWith(
genresFilters: (filters['genres']?.$1 ?? false, filters['genres']?.$2 ?? []),
formatFilters: (filters['formats']?.$1 ?? false, filters['formats']?.$2 ?? []),
countryFilters: (filters['countries']?.$1 ?? false, filters['countries']?.$2 ?? []),
airingStatusFilters: (
filters['airingStatuses']?.$1 ?? false,
filters['airingStatuses']?.$2 ?? [],
),
searchSortOptions: (filters['sortOptions']?.$1 ?? false, filters['sortOptions']?.$2 ?? []),
searchSortOrder: (filters['sortOrders']?.$1 ?? false, filters['sortOrders']?.$2 ?? []),
),
);
break;
case Service.mal:
case Service.shikimori:
case Service.simkl:
case Service.kitsu:
}
final filters = await _mangaRepository.getUserMangaAdvancedSearchFilters();
emit(
state.copyWith(
genresFilters: (filters['genres']?.$1 ?? false, filters['genres']?.$2 ?? []),
formatFilters: (filters['formats']?.$1 ?? false, filters['formats']?.$2 ?? []),
countryFilters: (filters['countries']?.$1 ?? false, filters['countries']?.$2 ?? []),
airingStatusFilters: (
filters['airingStatuses']?.$1 ?? false,
filters['airingStatuses']?.$2 ?? [],
),
searchSortOptions: (filters['sortOptions']?.$1 ?? false, filters['sortOptions']?.$2 ?? []),
searchSortOrder: (filters['sortOrders']?.$1 ?? false, filters['sortOrders']?.$2 ?? []),
),
);
} catch (e, stackTrace) {
_logger.e("Error getting logged user service filters $e", stackTrace: stackTrace);
handleError("Error getting logged user service filters", stackTrace: stackTrace);
@@ -191,23 +183,16 @@ class MangaAdvancedSearchCubit extends Cubit<MangaAdvancedSearchState>
}
Future<void> _performMangaAdvancedSearch() async {
switch (state.loggedUser.settings.service) {
case Service.anilist:
List<Manga> searchResults = await _mangaRepositoryAnilist.performMangaAdvancedSearch(
state.searchQuery,
state.selectedGenres,
state.selectedFormat,
state.selectedCountry,
state.selectedAiringStatus,
"${state.selectedSearchSortOption.toUpperCase().replaceAll(" ", "_")}_${state.selectedSearchOrder.toUpperCase()}",
1,
state.loggedUser
);
emit(state.copyWith(searchResults: searchResults));
case Service.mal:
case Service.shikimori:
case Service.simkl:
case Service.kitsu:
}
List<Manga> searchResults = await _mangaRepository.performMangaAdvancedSearch(
state.searchQuery,
state.selectedGenres,
state.selectedFormat,
state.selectedCountry,
state.selectedAiringStatus,
"${state.selectedSearchSortOption.toUpperCase().replaceAll(" ", "_")}_${state.selectedSearchOrder.toUpperCase()}",
1,
state.loggedUser
);
emit(state.copyWith(searchResults: searchResults));
}
}

View File

@@ -6,21 +6,21 @@ import 'package:unyo/application/effects/app_effects.dart';
import 'package:unyo/application/states/manga_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:unyo/core/di/locator.dart';
import 'package:unyo/core/enums/service.dart';
import 'package:unyo/core/notification/manga_genres_notifier.dart';
import 'package:unyo/core/notification/manga_notifier.dart';
import 'package:unyo/core/notification/media_list_notifier.dart';
import 'package:unyo/core/notification/reload/reload_notifier.dart';
import 'package:unyo/core/notification/reload/reload_type.dart';
import 'package:unyo/core/notification/user_notifier.dart';
import 'package:unyo/data/repositories/manga_repository_anilist.dart';
import 'package:unyo/domain/repositories/manga_repository.dart';
import 'package:unyo/domain/entities/media/manga.dart';
import 'package:unyo/domain/entities/list/media_list.dart';
import 'package:unyo/domain/entities/user/user.dart';
import 'effect_mixin.dart';
class MangaCubit extends Cubit<MangaState> with EffectMixin<MangaState> {
final MangaRepositoryAnilist _mangaRepositoryAnilist;
final MangaRepository _mangaRepository;
final UserNotifier _loggedUserNotifier;
final MangaNotifier _selectedMangaNotifier;
final MangaGenresNotifier _selectedMangaAdvancedSearchGenresFilters;
@@ -31,7 +31,7 @@ class MangaCubit extends Cubit<MangaState> with EffectMixin<MangaState> {
final Logger _logger = sl<Logger>();
MangaCubit(
this._mangaRepositoryAnilist,
this._mangaRepository,
this._loggedUserNotifier,
this._selectedMangaNotifier,
this._selectedMangaAdvancedSearchGenresFilters,
@@ -108,26 +108,19 @@ class MangaCubit extends Cubit<MangaState> with EffectMixin<MangaState> {
Future<void> _fetchTrending(int page, User loggedUser, {bool ignoreCache = false}) async {
try {
switch (state.loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist trending manga");
(bool, List<Manga>) trending = await _mangaRepositoryAnilist
.getTrendingMangas(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(trending: trending));
if (page == 1) {
emit(
state.copyWith(
banners:
trending.$2
.where((manga) => manga.bannerImage != "")
.toList(),
),
);
}
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
_logger.i("Fetching trending manga");
(bool, List<Manga>) trending = await _mangaRepository
.getTrendingMangas(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(trending: trending));
if (page == 1) {
emit(
state.copyWith(
banners:
trending.$2
.where((manga) => manga.bannerImage != "")
.toList(),
),
);
}
} catch (e, stackTrace) {
handleError("Failed to fetch trending manga $e", stackTrace: stackTrace);
@@ -136,17 +129,10 @@ class MangaCubit extends Cubit<MangaState> with EffectMixin<MangaState> {
Future<void> _fetchPopular(int page, User loggedUser, {bool ignoreCache = false}) async {
try {
switch (state.loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist popular manga");
(bool, List<Manga>) popular = await _mangaRepositoryAnilist
.getPopularMangas(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(popular: popular));
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
}
_logger.i("Fetching popular manga");
(bool, List<Manga>) popular = await _mangaRepository
.getPopularMangas(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(popular: popular));
} catch (e, stackTrace) {
handleError("Failed to fetch popular manga $e", stackTrace: stackTrace);
}
@@ -154,17 +140,10 @@ class MangaCubit extends Cubit<MangaState> with EffectMixin<MangaState> {
Future<void> _fetchRecentlyCompleted(int page, User loggedUser, {bool ignoreCache = false}) async {
try {
switch (state.loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist recently completed manga");
(bool, List<Manga>) recentlyCompleted = await _mangaRepositoryAnilist
.getRecentlyCompletedMangas(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(recentlyCompleted: recentlyCompleted));
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
}
_logger.i("Fetching recently completed manga");
(bool, List<Manga>) recentlyCompleted = await _mangaRepository
.getRecentlyCompletedMangas(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(recentlyCompleted: recentlyCompleted));
} catch (e, stackTrace) {
handleError(
"Failed to fetch recently completed manga $e",
@@ -175,17 +154,10 @@ class MangaCubit extends Cubit<MangaState> with EffectMixin<MangaState> {
Future<void> _fetchUpcoming(int page, User loggedUser, {bool ignoreCache = false}) async {
try {
switch (state.loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist upcoming manga");
(bool, List<Manga>) upcoming = await _mangaRepositoryAnilist
.getUpcomingMangas(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(upcoming: upcoming));
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
}
_logger.i("Fetching upcoming manga");
(bool, List<Manga>) upcoming = await _mangaRepository
.getUpcomingMangas(page, loggedUser, ignoreCache: ignoreCache);
emit(state.copyWith(upcoming: upcoming));
} catch (e, stackTrace) {
handleError("Failed to fetch upcoming manga $e", stackTrace: stackTrace);
}

View File

@@ -11,7 +11,7 @@ import 'package:unyo/application/effects/app_effects.dart';
import 'package:bloc/bloc.dart';
import 'package:unyo/application/states/manga_details_state.dart';
import 'package:unyo/core/di/locator.dart';
import 'package:unyo/core/enums/service.dart';
import 'package:unyo/core/notification/manga_genres_notifier.dart';
import 'package:unyo/core/notification/manga_notifier.dart';
import 'package:unyo/core/notification/media_list_notifier.dart';
@@ -20,7 +20,7 @@ import 'package:unyo/core/services/api/http/http_exception.dart';
import 'package:unyo/data/models/anilist/anilist_user_model.dart';
import 'package:unyo/data/models/local/local_user_model.dart';
import 'package:unyo/data/repositories/extension_repository_aniyomi.dart';
import 'package:unyo/data/repositories/manga_repository_anilist.dart';
import 'package:unyo/domain/repositories/manga_repository.dart';
import 'package:unyo/data/repositories/repositories.dart';
import 'package:unyo/domain/entities/extension/extension.dart';
import 'package:unyo/domain/entities/media/manga.dart';
@@ -32,7 +32,7 @@ import 'package:unyo/domain/entities/user/user.dart';
class MangaDetailsCubit extends Cubit<MangaDetailsState> with EffectMixin<MangaDetailsState> {
// Repositories
final MangaRepositoryAnilist _mangaRepositoryAnilist;
final MangaRepository _mangaRepository;
final ExtensionRepositoryAniyomi _extensionRepositoryAniyomi;
final UserRepositoryAnilist _userRepositoryAnilist;
@@ -49,7 +49,7 @@ class MangaDetailsCubit extends Cubit<MangaDetailsState> with EffectMixin<MangaD
final Logger _logger = sl<Logger>();
MangaDetailsCubit(
this._mangaRepositoryAnilist,
this._mangaRepository,
this._loggedUserNotifier,
this._selectedMangaNotifier,
this._selectedMangaAdvancedSearchGenresFilters,
@@ -168,35 +168,24 @@ class MangaDetailsCubit extends Cubit<MangaDetailsState> with EffectMixin<MangaD
Future<void> _getMangaDetails(User loggedUser, Manga selectedManga) async {
try {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Manga Details from AniList for ${state.selectedManga.title.userPreferred}");
(bool, MangaDetails) mangaDetails = await _mangaRepositoryAnilist.getMangaDetails(
selectedManga,
loggedUser,
);
emit(
state.copyWith(
mediaListEntry: mangaDetails.$2.mediaListEntry,
characters: (mangaDetails.$2.characters.isNotEmpty, mangaDetails.$2.characters),
recommendations: (
mangaDetails.$2.recommendedMangas.isNotEmpty,
mangaDetails.$2.recommendedMangas,
),
),
);
if (mangaDetails.$2.recommendedMangas.isEmpty) {
(bool, List<Manga>) trendingMangas = await _mangaRepositoryAnilist.getTrendingMangas(1, loggedUser);
emit(state.copyWith(recommendations: (trendingMangas.$1, trendingMangas.$2.shuffled(Random()))));
}
case Service.mal:
_logger.i("Fetching Manga Details from MyMangaList for ${state.selectedManga.title.userPreferred}");
case Service.shikimori:
_logger.i("Fetching Manga Details from Shikimori for ${state.selectedManga.title.userPreferred}");
case Service.kitsu:
_logger.i("Fetching Manga Details from Kitsu for ${state.selectedManga.title.userPreferred}");
case Service.simkl:
_logger.i("Fetching Manga Details from Simkl for ${state.selectedManga.title.userPreferred}");
_logger.i("Fetching Manga Details for ${state.selectedManga.title.userPreferred}");
(bool, MangaDetails) mangaDetails = await _mangaRepository.getMangaDetails(
selectedManga,
loggedUser,
);
emit(
state.copyWith(
mediaListEntry: mangaDetails.$2.mediaListEntry,
characters: (mangaDetails.$2.characters.isNotEmpty, mangaDetails.$2.characters),
recommendations: (
mangaDetails.$2.recommendedMangas.isNotEmpty,
mangaDetails.$2.recommendedMangas,
),
),
);
if (mangaDetails.$2.recommendedMangas.isEmpty) {
(bool, List<Manga>) trendingMangas = await _mangaRepository.getTrendingMangas(1, loggedUser);
emit(state.copyWith(recommendations: (trendingMangas.$1, trendingMangas.$2.shuffled(Random()))));
}
} on HttpServerException catch (e, stackTrace) {
handleError("Error fetching Manga details:", responseBody: e.message, stackTrace: stackTrace);
@@ -266,20 +255,9 @@ class MangaDetailsCubit extends Cubit<MangaDetailsState> with EffectMixin<MangaD
Future<void> _getMangaBanners(User loggedUser) async {
try {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Manga Banners from AniList for ${state.selectedManga.title.userPreferred}");
List<String> banners = await _mangaRepositoryAnilist.getMediaCoverImages(loggedUser);
emit(state.copyWith(banners: banners));
case Service.mal:
_logger.i("Fetching Manga Banners from MyMangaList for ${state.selectedManga.title.userPreferred}");
case Service.shikimori:
_logger.i("Fetching Manga Banners from Shikimori for ${state.selectedManga.title.userPreferred}");
case Service.kitsu:
_logger.i("Fetching Manga Banners from Kitsu for ${state.selectedManga.title.userPreferred}");
case Service.simkl:
_logger.i("Fetching Manga Banners from Simkl for ${state.selectedManga.title.userPreferred}");
}
_logger.i("Fetching Manga Banners for ${state.selectedManga.title.userPreferred}");
List<String> banners = await _mangaRepository.getMediaCoverImages(loggedUser);
emit(state.copyWith(banners: banners));
} on HttpServerException catch (e, stackTrace) {
handleError("Error fetching Manga banners:", responseBody: e.message, stackTrace: stackTrace);
} catch (e, stackTrace) {

View File

@@ -12,7 +12,7 @@ import 'dart:async';
import 'package:unyo/application/cubits/effect_mixin.dart';
import 'package:unyo/application/effects/app_effects.dart';
import 'package:unyo/application/states/video_state.dart';
import 'package:unyo/core/enums/service.dart';
import 'package:unyo/core/notification/anime_notifier.dart';
import 'package:unyo/core/notification/episode_info_notifier.dart';
import 'package:unyo/core/notification/episodes_notifier.dart';
@@ -27,7 +27,7 @@ import 'package:unyo/core/services/api/http/api_response.dart';
import 'package:unyo/core/services/api/http/http_exception.dart';
import 'package:unyo/core/services/api/http/http_service.dart';
import 'package:unyo/core/services/video/video_service.dart';
import 'package:unyo/data/repositories/anime_repository_anilist.dart';
import 'package:unyo/domain/repositories/anime_repository.dart';
import 'package:unyo/data/repositories/extension_repository_aniyomi.dart';
import 'package:unyo/domain/entities/media/anime.dart';
import 'package:unyo/domain/entities/media/episode_info.dart';
@@ -44,7 +44,7 @@ class VideoCubit extends Cubit<VideoState> with EffectMixin<VideoState> {
// Repositories
final ExtensionRepositoryAniyomi _extensionRepositoryAniyomi;
final AnimeRepositoryAnilist _animeRepositoryAnilist;
final AnimeRepository _animeRepository;
// Notifiers / Subscriptions
final UserNotifier _loggedUserNotifier;
@@ -82,7 +82,7 @@ class VideoCubit extends Cubit<VideoState> with EffectMixin<VideoState> {
this._selectedExtensionNotifier,
this._selectedEpisodesNotifier,
this._extensionRepositoryAniyomi,
this._animeRepositoryAnilist,
this._animeRepository,
this._reloadNotifier,
) : super(
VideoState(
@@ -494,25 +494,14 @@ class VideoCubit extends Cubit<VideoState> with EffectMixin<VideoState> {
progress: state.videoInfo.playlistIndex + 1,
status: "Current",
);
switch (state.loggedUser.settings.service) {
case Service.anilist:
_logger.i("Updating Media List Entry to $desiredMediaListEntry on Anilist");
MediaListEntry savedMediaListEntry = await _animeRepositoryAnilist.updateMediaListEntry(
desiredMediaListEntry,
state.selectedAnime,
state.loggedUser,
);
emit(state.copyWith(mediaListEntry: savedMediaListEntry));
_reloadNotifier.emitReload(ReloadType.videoMediaListEntryUpdated);
case Service.mal:
_logger.i("Updating Media List Entry to $desiredMediaListEntry on MyAnimeList");
case Service.shikimori:
_logger.i("Updating Media List Entry to $desiredMediaListEntry on Shikimori");
case Service.kitsu:
_logger.i("Updating Media List Entry to $desiredMediaListEntry on Kitsu");
case Service.simkl:
_logger.i("Updating Media List Entry to $desiredMediaListEntry on Simkl");
}
_logger.i("Updating Media List Entry to $desiredMediaListEntry");
MediaListEntry savedMediaListEntry = await _animeRepository.updateMediaListEntry(
desiredMediaListEntry,
state.selectedAnime,
state.loggedUser,
);
emit(state.copyWith(mediaListEntry: savedMediaListEntry));
_reloadNotifier.emitReload(ReloadType.videoMediaListEntryUpdated);
} on HttpServerException catch (e, stackTrace) {
handleError("Error updating Anime Entry:", responseBody: e.message, stackTrace: stackTrace);
} catch (e, stackTrace) {

View File

@@ -39,6 +39,8 @@ import 'package:unyo/core/services/api/http/http_service.dart';
import 'package:unyo/core/services/effects/app_effect_handler.dart';
import 'package:unyo/core/services/settings/settings_service.dart';
import 'package:unyo/core/services/torrent/torrent_service.dart';
import 'package:unyo/core/factories/anime_repository_factory.dart';
import 'package:unyo/core/factories/manga_repository_factory.dart';
import 'package:unyo/core/theme/color_image_service.dart';
import 'package:unyo/core/theme/theme_service.dart';
import 'package:unyo/data/repositories/anime_repository_anilist.dart';
@@ -46,6 +48,8 @@ import 'package:unyo/data/repositories/episode_repository_anizip.dart';
import 'package:unyo/data/repositories/extension_repository_aniyomi.dart';
import 'package:unyo/data/repositories/manga_repository_anilist.dart';
import 'package:unyo/data/repositories/repositories.dart';
import 'package:unyo/domain/repositories/anime_repository.dart';
import 'package:unyo/domain/repositories/manga_repository.dart';
import 'package:unyo/application/cubits/home_cubit.dart';
final sl = GetIt.instance;
@@ -98,6 +102,8 @@ void setupLocator() async{
));
sl.registerLazySingleton<AnimeRepositoryAnilist>(() => AnimeRepositoryAnilist());
sl.registerLazySingleton<MangaRepositoryAnilist>(() => MangaRepositoryAnilist());
sl.registerLazySingleton<AnimeRepository>(() => AnimeRepositoryFactory());
sl.registerLazySingleton<MangaRepository>(() => MangaRepositoryFactory());
sl.registerLazySingleton<EpisodeRepositoryAnizip>(() => EpisodeRepositoryAnizip());
// Cubits / Blocs
sl.registerFactory<LoginCubit>(
@@ -117,7 +123,7 @@ void setupLocator() async{
sl<MangaNotifier>(),
sl<MediaListNotifier>(),
sl<UserRepositoryAnilist>(),
sl<AnimeRepositoryAnilist>(),
sl<AnimeRepository>(),
sl<MenuBarNotifier>(),
sl<ReloadNotifier>(),
),
@@ -127,7 +133,7 @@ void setupLocator() async{
);
sl.registerFactory<AnimeCubit>(
() => AnimeCubit(
sl<AnimeRepositoryAnilist>(),
sl<AnimeRepository>(),
sl<UserNotifier>(instanceName: config.loggedUserNotifier),
sl<AnimeNotifier>(),
sl<AnimeGenresNotifier>(),
@@ -137,7 +143,7 @@ void setupLocator() async{
);
sl.registerFactory<MangaCubit>(
() => MangaCubit(
sl<MangaRepositoryAnilist>(),
sl<MangaRepository>(),
sl<UserNotifier>(instanceName: config.loggedUserNotifier),
sl<MangaNotifier>(),
sl<MangaGenresNotifier>(),
@@ -162,7 +168,7 @@ void setupLocator() async{
);
sl.registerFactory<CalendarCubit>(
() => CalendarCubit(
sl<AnimeRepositoryAnilist>(),
sl<AnimeRepository>(),
sl<UserNotifier>(instanceName: config.loggedUserNotifier),
sl<AnimeNotifier>(),
sl<MediaListNotifier>(),
@@ -170,7 +176,7 @@ void setupLocator() async{
);
sl.registerFactory<AnimeDetailsCubit>(
() => AnimeDetailsCubit(
sl<AnimeRepositoryAnilist>(),
sl<AnimeRepository>(),
sl<EpisodeRepositoryAnizip>(),
sl<UserNotifier>(instanceName: config.loggedUserNotifier),
sl<AnimeNotifier>(),
@@ -188,7 +194,7 @@ void setupLocator() async{
);
sl.registerFactory<MangaDetailsCubit>(
() => MangaDetailsCubit(
sl<MangaRepositoryAnilist>(),
sl<MangaRepository>(),
sl<UserNotifier>(instanceName: config.loggedUserNotifier),
sl<MangaNotifier>(),
sl<MangaGenresNotifier>(),
@@ -213,7 +219,7 @@ void setupLocator() async{
sl<MediaListNotifier>(),
sl<AnimeNotifier>(),
sl<AnimeGenresNotifier>(),
sl<AnimeRepositoryAnilist>(),
sl<AnimeRepository>(),
),
);
sl.registerFactory<MangaAdvancedSearchCubit>(
@@ -222,7 +228,7 @@ void setupLocator() async{
sl<MediaListNotifier>(),
sl<MangaNotifier>(),
sl<MangaGenresNotifier>(),
sl<MangaRepositoryAnilist>(),
sl<MangaRepository>(),
),
);
sl.registerFactory<VideoCubit>(
@@ -235,7 +241,7 @@ void setupLocator() async{
sl<ExtensionNotifier>(),
sl<EpisodesNotifier>(),
sl<ExtensionRepositoryAniyomi>(),
sl<AnimeRepositoryAnilist>(),
sl<AnimeRepository>(),
sl<ReloadNotifier>(),
)
);

View File

@@ -0,0 +1,288 @@
// External dependencies
import 'package:logger/logger.dart';
// Internal dependencies
import 'package:unyo/core/di/locator.dart';
import 'package:unyo/core/enums/service.dart';
import 'package:unyo/data/repositories/anime_repository_anilist.dart';
import 'package:unyo/domain/entities/media/anime.dart';
import 'package:unyo/domain/entities/media/anime_details.dart';
import 'package:unyo/domain/entities/list/media_list_entry.dart';
import 'package:unyo/domain/entities/user/user.dart';
import 'package:unyo/domain/repositories/anime_repository.dart';
/// Factory that resolves the correct [AnimeRepository] implementation
/// based on the user's selected metadata [Service].
///
/// Currently supports:
/// - [Service.anilist] → delegates to [AnimeRepositoryAnilist]
/// - All other services → returns safe empty defaults
class AnimeRepositoryFactory implements AnimeRepository {
final AnimeRepositoryAnilist _animeRepositoryAnilist = sl<AnimeRepositoryAnilist>();
final Logger _logger = sl<Logger>();
@override
Future<(bool, List<Anime>)> getRecentlyReleasedAnimes(
int page,
User loggedUser, {
bool ignoreCache = false,
}) async {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist recently released anime");
return _animeRepositoryAnilist.getRecentlyReleasedAnimes(
page,
loggedUser,
ignoreCache: ignoreCache,
);
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
return (false, <Anime>[]);
}
}
@override
Future<(bool, List<Anime>)> getTrendingAnimes(
int page,
User loggedUser, {
bool ignoreCache = false,
}) async {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist trending anime");
return _animeRepositoryAnilist.getTrendingAnimes(
page,
loggedUser,
ignoreCache: ignoreCache,
);
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
return (false, <Anime>[]);
}
}
@override
Future<(bool, List<Anime>)> getPopularAnimes(
int page,
User loggedUser, {
bool ignoreCache = false,
}) async {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist popular anime");
return _animeRepositoryAnilist.getPopularAnimes(
page,
loggedUser,
ignoreCache: ignoreCache,
);
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
return (false, <Anime>[]);
}
}
@override
Future<(bool, List<Anime>)> getRecentlyCompletedAnimes(
int page,
User loggedUser, {
bool ignoreCache = false,
}) async {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist recently completed anime");
return _animeRepositoryAnilist.getRecentlyCompletedAnimes(
page,
loggedUser,
ignoreCache: ignoreCache,
);
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
return (false, <Anime>[]);
}
}
@override
Future<(bool, List<Anime>)> getUpcomingAnimes(
int page,
User loggedUser, {
bool ignoreCache = false,
}) async {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist upcoming anime");
return _animeRepositoryAnilist.getUpcomingAnimes(
page,
loggedUser,
ignoreCache: ignoreCache,
);
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
return (false, <Anime>[]);
}
}
@override
Future<Map<String, List<Anime>>> getCalendarReleases(int page, User loggedUser) async {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist calendar releases");
return _animeRepositoryAnilist.getCalendarReleases(page, loggedUser);
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
return {};
}
}
@override
Future<(bool, AnimeDetails)> getAnimeDetails(Anime selectedAnime, User loggedUser) async {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anime Details from AniList for ${selectedAnime.title.userPreferred}");
return _animeRepositoryAnilist.getAnimeDetails(selectedAnime, loggedUser);
case Service.mal:
_logger.i("Fetching Anime Details from MyAnimeList for ${selectedAnime.title.userPreferred}");
return (false, AnimeDetailsModel.empty());
case Service.shikimori:
_logger.i("Fetching Anime Details from Shikimori for ${selectedAnime.title.userPreferred}");
return (false, AnimeDetailsModel.empty());
case Service.kitsu:
_logger.i("Fetching Anime Details from Kitsu for ${selectedAnime.title.userPreferred}");
return (false, AnimeDetailsModel.empty());
case Service.simkl:
_logger.i("Fetching Anime Details from Simkl for ${selectedAnime.title.userPreferred}");
return (false, AnimeDetailsModel.empty());
}
}
@override
Future<Map<String, (bool, List<String>)>> getUserAnimeAdvancedSearchFilters() async {
return _animeRepositoryAnilist.getUserAnimeAdvancedSearchFilters();
}
@override
Future<List<Anime>> performAnimeAdvancedSearch(
String query,
List<String> selectedGenres,
String? selectedSeason,
String? selectedFormat,
int? selectedYear,
String? selectedAiringStatus,
String sort,
int page,
User loggedUser,
) async {
switch (loggedUser.settings.service) {
case Service.anilist:
return _animeRepositoryAnilist.performAnimeAdvancedSearch(
query,
selectedGenres,
selectedSeason,
selectedFormat,
selectedYear,
selectedAiringStatus,
sort,
page,
loggedUser,
);
case Service.mal:
case Service.shikimori:
case Service.simkl:
case Service.kitsu:
return [];
}
}
@override
Future<List<String>> getMediaCoverImages(User loggedUser, {bool ignoreCache = false}) async {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Media Cover Images from AniList");
return _animeRepositoryAnilist.getMediaCoverImages(
loggedUser,
ignoreCache: ignoreCache,
);
case Service.mal:
_logger.i("Fetching Media Cover Images from MyAnimeList");
return [];
case Service.shikimori:
_logger.i("Fetching Media Cover Images from Shikimori");
return [];
case Service.kitsu:
_logger.i("Fetching Media Cover Images from Kitsu");
return [];
case Service.simkl:
_logger.i("Fetching Media Cover Images from Simkl");
return [];
}
}
@override
Future<MediaListEntry> updateMediaListEntry(
MediaListEntry newMediaListEntry,
Anime selectedAnime,
User loggedUser,
) async {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Updating Media List Entry to $newMediaListEntry on Anilist");
return _animeRepositoryAnilist.updateMediaListEntry(
newMediaListEntry,
selectedAnime,
loggedUser,
);
case Service.mal:
_logger.i("Updating Media List Entry to $newMediaListEntry on MyAnimeList");
return MediaListEntryModel.empty();
case Service.shikimori:
_logger.i("Updating Media List Entry to $newMediaListEntry on Shikimori");
return MediaListEntryModel.empty();
case Service.kitsu:
_logger.i("Updating Media List Entry to $newMediaListEntry on Kitsu");
return MediaListEntryModel.empty();
case Service.simkl:
_logger.i("Updating Media List Entry to $newMediaListEntry on Simkl");
return MediaListEntryModel.empty();
}
}
@override
Future<MediaListEntry> getMediaListEntry(
Anime selectedAnime,
User loggedUser, {
bool ignoreCache = false,
}) async {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching User Media List Entry from AniList for ${selectedAnime.title.userPreferred}");
return _animeRepositoryAnilist.getMediaListEntry(
selectedAnime,
loggedUser,
ignoreCache: ignoreCache,
);
case Service.mal:
_logger.i("Fetching User Media List Entry from MyAnimeList for ${selectedAnime.title.userPreferred}");
return MediaListEntryModel.empty();
case Service.shikimori:
_logger.i("Fetching User Media List Entry from Shikimori for ${selectedAnime.title.userPreferred}");
return MediaListEntryModel.empty();
case Service.kitsu:
_logger.i("Fetching User Media List Entry from Kitsu for ${selectedAnime.title.userPreferred}");
return MediaListEntryModel.empty();
case Service.simkl:
_logger.i("Fetching User Media List Entry from Simkl for ${selectedAnime.title.userPreferred}");
return MediaListEntryModel.empty();
}
}
}

View File

@@ -0,0 +1,191 @@
// External dependencies
import 'package:logger/logger.dart';
// Internal dependencies
import 'package:unyo/core/di/locator.dart';
import 'package:unyo/core/enums/service.dart';
import 'package:unyo/data/repositories/manga_repository_anilist.dart';
import 'package:unyo/domain/entities/media/manga.dart';
import 'package:unyo/domain/entities/media/manga_details.dart';
import 'package:unyo/domain/entities/user/user.dart';
import 'package:unyo/domain/repositories/manga_repository.dart';
/// Factory that resolves the correct [MangaRepository] implementation
/// based on the user's selected metadata [Service].
///
/// Currently supports:
/// - [Service.anilist] → delegates to [MangaRepositoryAnilist]
/// - All other services → returns safe empty defaults
class MangaRepositoryFactory implements MangaRepository {
final MangaRepositoryAnilist _mangaRepositoryAnilist = sl<MangaRepositoryAnilist>();
final Logger _logger = sl<Logger>();
@override
Future<(bool, List<Manga>)> getTrendingMangas(
int page,
User loggedUser, {
bool ignoreCache = false,
}) async {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist trending manga");
return _mangaRepositoryAnilist.getTrendingMangas(
page,
loggedUser,
ignoreCache: ignoreCache,
);
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
return (false, <Manga>[]);
}
}
@override
Future<(bool, List<Manga>)> getPopularMangas(
int page,
User loggedUser, {
bool ignoreCache = false,
}) async {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist popular manga");
return _mangaRepositoryAnilist.getPopularMangas(
page,
loggedUser,
ignoreCache: ignoreCache,
);
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
return (false, <Manga>[]);
}
}
@override
Future<(bool, List<Manga>)> getRecentlyCompletedMangas(
int page,
User loggedUser, {
bool ignoreCache = false,
}) async {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist recently completed manga");
return _mangaRepositoryAnilist.getRecentlyCompletedMangas(
page,
loggedUser,
ignoreCache: ignoreCache,
);
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
return (false, <Manga>[]);
}
}
@override
Future<(bool, List<Manga>)> getUpcomingMangas(
int page,
User loggedUser, {
bool ignoreCache = false,
}) async {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Anilist upcoming manga");
return _mangaRepositoryAnilist.getUpcomingMangas(
page,
loggedUser,
ignoreCache: ignoreCache,
);
case Service.mal:
case Service.kitsu:
case Service.shikimori:
case Service.simkl:
return (false, <Manga>[]);
}
}
@override
Future<(bool, MangaDetails)> getMangaDetails(Manga selectedManga, User loggedUser) async {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Manga Details from AniList for ${selectedManga.title.userPreferred}");
return _mangaRepositoryAnilist.getMangaDetails(selectedManga, loggedUser);
case Service.mal:
_logger.i("Fetching Manga Details from MyMangaList for ${selectedManga.title.userPreferred}");
return (false, MangaDetailsModel.empty());
case Service.shikimori:
_logger.i("Fetching Manga Details from Shikimori for ${selectedManga.title.userPreferred}");
return (false, MangaDetailsModel.empty());
case Service.kitsu:
_logger.i("Fetching Manga Details from Kitsu for ${selectedManga.title.userPreferred}");
return (false, MangaDetailsModel.empty());
case Service.simkl:
_logger.i("Fetching Manga Details from Simkl for ${selectedManga.title.userPreferred}");
return (false, MangaDetailsModel.empty());
}
}
@override
Future<Map<String, (bool, List<String>)>> getUserMangaAdvancedSearchFilters() async {
return _mangaRepositoryAnilist.getUserMangaAdvancedSearchFilters();
}
@override
Future<List<Manga>> performMangaAdvancedSearch(
String query,
List<String> selectedGenres,
String? selectedFormat,
String? selectedCountry,
String? selectedAiringStatus,
String sort,
int page,
User loggedUser,
) async {
switch (loggedUser.settings.service) {
case Service.anilist:
return _mangaRepositoryAnilist.performMangaAdvancedSearch(
query,
selectedGenres,
selectedFormat,
selectedCountry,
selectedAiringStatus,
sort,
page,
loggedUser,
);
case Service.mal:
case Service.shikimori:
case Service.simkl:
case Service.kitsu:
return [];
}
}
@override
Future<List<String>> getMediaCoverImages(User loggedUser, {bool ignoreCache = false}) async {
switch (loggedUser.settings.service) {
case Service.anilist:
_logger.i("Fetching Manga Banners from AniList for ...");
return _mangaRepositoryAnilist.getMediaCoverImages(
loggedUser,
ignoreCache: ignoreCache,
);
case Service.mal:
_logger.i("Fetching Manga Banners from MyMangaList for ...");
return [];
case Service.shikimori:
_logger.i("Fetching Manga Banners from Shikimori for ...");
return [];
case Service.kitsu:
_logger.i("Fetching Manga Banners from Kitsu for ...");
return [];
case Service.simkl:
_logger.i("Fetching Manga Banners from Simkl for ...");
return [];
}
}
}

View File

@@ -267,8 +267,8 @@ class MangaRepositoryAnilist with RepositoryMixin implements MangaRepository {
}
@override
Future<List<String>> getMediaCoverImages(User loggedUser) async {
(bool, List<Manga>) popularMangas = await getPopularMangas(1, loggedUser);
Future<List<String>> getMediaCoverImages(User loggedUser, {bool ignoreCache = false}) async {
(bool, List<Manga>) popularMangas = await getPopularMangas(1, loggedUser, ignoreCache: ignoreCache);
return popularMangas.$2.map((manga) => manga.coverImage).where((coverImage) => coverImage != "").shuffled(Random()).toList();
}

View File

@@ -4,11 +4,11 @@ import 'package:unyo/domain/entities/list/media_list_entry.dart';
import 'package:unyo/domain/entities/user/user.dart';
abstract class AnimeRepository {
Future<(bool, List<Anime>)> getRecentlyReleasedAnimes(int page, User loggedUser);
Future<(bool, List<Anime>)> getTrendingAnimes(int page, User loggedUser);
Future<(bool, List<Anime>)> getPopularAnimes(int page, User loggedUser);
Future<(bool, List<Anime>)> getRecentlyCompletedAnimes(int page, User loggedUser);
Future<(bool, List<Anime>)> getUpcomingAnimes(int page, User loggedUser);
Future<(bool, List<Anime>)> getRecentlyReleasedAnimes(int page, User loggedUser, {bool ignoreCache = false});
Future<(bool, List<Anime>)> getTrendingAnimes(int page, User loggedUser, {bool ignoreCache = false});
Future<(bool, List<Anime>)> getPopularAnimes(int page, User loggedUser, {bool ignoreCache = false});
Future<(bool, List<Anime>)> getRecentlyCompletedAnimes(int page, User loggedUser, {bool ignoreCache = false});
Future<(bool, List<Anime>)> getUpcomingAnimes(int page, User loggedUser, {bool ignoreCache = false});
Future<Map<String, List<Anime>>> getCalendarReleases(int page, User loggedUser);
Future<(bool, AnimeDetails)> getAnimeDetails(Anime selectedAnime, User loggedUser);
Future<Map<String, (bool, List<String>)>> getUserAnimeAdvancedSearchFilters();
@@ -23,7 +23,7 @@ abstract class AnimeRepository {
int page,
User loggedUser
);
Future<List<String>> getMediaCoverImages(User loggedUser);
Future<List<String>> getMediaCoverImages(User loggedUser, {bool ignoreCache = false});
Future<MediaListEntry> updateMediaListEntry(MediaListEntry newMediaListEntry, Anime selectedAnime, User loggedUser);
Future<MediaListEntry> getMediaListEntry(Anime selectedAnime, User loggedUser);
Future<MediaListEntry> getMediaListEntry(Anime selectedAnime, User loggedUser, {bool ignoreCache = false});
}

View File

@@ -3,10 +3,10 @@ import 'package:unyo/domain/entities/media/manga_details.dart';
import 'package:unyo/domain/entities/user/user.dart';
abstract class MangaRepository {
Future<(bool, List<Manga>)> getTrendingMangas(int page, User loggedUser);
Future<(bool, List<Manga>)> getPopularMangas(int page, User loggedUser);
Future<(bool, List<Manga>)> getRecentlyCompletedMangas(int page, User loggedUser);
Future<(bool, List<Manga>)> getUpcomingMangas(int page, User loggedUser);
Future<(bool, List<Manga>)> getTrendingMangas(int page, User loggedUser, {bool ignoreCache = false});
Future<(bool, List<Manga>)> getPopularMangas(int page, User loggedUser, {bool ignoreCache = false});
Future<(bool, List<Manga>)> getRecentlyCompletedMangas(int page, User loggedUser, {bool ignoreCache = false});
Future<(bool, List<Manga>)> getUpcomingMangas(int page, User loggedUser, {bool ignoreCache = false});
Future<(bool, MangaDetails)> getMangaDetails(Manga selectedManga, User loggedUser);
Future<Map<String, (bool, List<String>)>> getUserMangaAdvancedSearchFilters();
Future<List<Manga>> performMangaAdvancedSearch(
@@ -19,5 +19,5 @@ abstract class MangaRepository {
int page,
User loggedUser
);
Future<List<String>> getMediaCoverImages(User loggedUser);
Future<List<String>> getMediaCoverImages(User loggedUser, {bool ignoreCache = false});
}

View File

@@ -22,6 +22,10 @@ import 'package:unyo/data/repositories/anime_repository_anilist.dart';
import 'package:unyo/data/repositories/episode_repository_anizip.dart';
import 'package:unyo/data/repositories/manga_repository_anilist.dart';
import 'package:unyo/data/repositories/repositories.dart';
import 'package:unyo/core/factories/anime_repository_factory.dart';
import 'package:unyo/core/factories/manga_repository_factory.dart';
import 'package:unyo/domain/repositories/anime_repository.dart';
import 'package:unyo/domain/repositories/manga_repository.dart';
// DartRX Notifiers
class MockUserNotifier extends Mock implements UserNotifier {}
@@ -96,6 +100,8 @@ Future<void> setupTestLocator() async{
);
sl.registerSingleton<AnimeRepositoryAnilist>(MockAnimeRepositoryAnilist());
sl.registerLazySingleton<MangaRepositoryAnilist>(() => MockMangaRepositoryAnilist());
sl.registerLazySingleton<AnimeRepository>(() => AnimeRepositoryFactory());
sl.registerLazySingleton<MangaRepository>(() => MangaRepositoryFactory());
sl.registerLazySingleton<EpisodeRepositoryAnizip>(() => MockEpisodeRepositoryAnizip());
// Cubits / Blocs - Register factories that create real cubits with mocked dependencies
@@ -105,7 +111,7 @@ Future<void> setupTestLocator() async{
sl<MediaListNotifier>(),
sl<AnimeNotifier>(),
sl<AnimeGenresNotifier>(),
sl<AnimeRepositoryAnilist>(),
sl<AnimeRepository>(),
),
);
}