feat: Integrate the AniList API since Jikan is shutting down
This commit is contained in:
@@ -317,9 +317,9 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
||||
);
|
||||
|
||||
// Update the cache
|
||||
final cacheIndex = _mangas.indexWhere((m) => m.id == event.id);
|
||||
final cacheIndex = _animes.indexWhere((m) => m.id == event.id);
|
||||
assert(cacheIndex != -1, 'The anime must exist');
|
||||
_mangas.removeAt(cacheIndex);
|
||||
_animes.removeAt(cacheIndex);
|
||||
|
||||
// Update the database
|
||||
await GetIt.I.get<DatabaseService>().deleteAnime(event.id);
|
||||
@@ -338,9 +338,9 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
||||
);
|
||||
|
||||
// Update the cache
|
||||
final cacheIndex = _animes.indexWhere((a) => a.id == event.id);
|
||||
final cacheIndex = _mangas.indexWhere((a) => a.id == event.id);
|
||||
assert(cacheIndex != -1, 'The manga must exist');
|
||||
_animes.removeAt(cacheIndex);
|
||||
_mangas.removeAt(cacheIndex);
|
||||
|
||||
// Update the database
|
||||
await GetIt.I.get<DatabaseService>().deleteManga(event.id);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import 'package:anitrack/src/data/anime.dart';
|
||||
import 'package:anitrack/src/data/manga.dart';
|
||||
import 'package:anitrack/src/data/search_result.dart';
|
||||
import 'package:anitrack/src/data/source.dart';
|
||||
import 'package:anitrack/src/data/type.dart';
|
||||
import 'package:anitrack/src/service/anilist/anilist_client.dart';
|
||||
import 'package:anitrack/src/service/anilist/model.dart';
|
||||
import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart' as list;
|
||||
import 'package:anitrack/src/ui/bloc/navigation_bloc.dart';
|
||||
import 'package:anitrack/src/ui/constants.dart';
|
||||
@@ -22,6 +25,9 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
on<ResultTappedEvent>(_onResultTapped);
|
||||
}
|
||||
|
||||
// The client to use for AniList GraphQL queries.
|
||||
final _anilistClient = AniListClient();
|
||||
|
||||
Future<void> _onRequested(
|
||||
AnimeSearchRequestedEvent event,
|
||||
Emitter<AnimeSearchState> emit,
|
||||
@@ -67,23 +73,21 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
|
||||
if (state.trackingType == TrackingMediumType.anime) {
|
||||
// Anime
|
||||
final result = await Jikan().searchAnime(
|
||||
query: state.searchQuery,
|
||||
);
|
||||
final result = await _anilistClient.searchAnime(state.searchQuery);
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
working: false,
|
||||
searchResults: result
|
||||
.map(
|
||||
(Anime anime) => SearchResult(
|
||||
(anime) => SearchResult(
|
||||
anime.title,
|
||||
anime.malId.toString(),
|
||||
anime.id,
|
||||
anime.episodes,
|
||||
anime.imageUrl,
|
||||
anime.synopsis ?? '',
|
||||
anime.airing,
|
||||
anime.broadcast?.split(' ').first,
|
||||
anime.description ?? '',
|
||||
anime.isAiring,
|
||||
anime.broadcastDay,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
@@ -91,21 +95,19 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
);
|
||||
} else {
|
||||
// Manga
|
||||
final result = await Jikan().searchManga(
|
||||
query: state.searchQuery,
|
||||
);
|
||||
final result = await _anilistClient.searchManga(state.searchQuery);
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
working: false,
|
||||
searchResults: result
|
||||
.map(
|
||||
(Manga manga) => SearchResult(
|
||||
(manga) => SearchResult(
|
||||
manga.title,
|
||||
manga.malId.toString(),
|
||||
manga.id,
|
||||
manga.chapters,
|
||||
manga.imageUrl,
|
||||
manga.synopsis ?? '',
|
||||
manga.description ?? '',
|
||||
// TODO(Unknown): Implement for Manga
|
||||
false,
|
||||
null,
|
||||
@@ -133,6 +135,7 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
event.result.thumbnailUrl,
|
||||
event.result.isAiring,
|
||||
event.result.broadcastDay,
|
||||
TrackingDataSource.anilist,
|
||||
),
|
||||
)
|
||||
: list.MangaAddedEvent(
|
||||
@@ -144,6 +147,7 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
0,
|
||||
event.result.total,
|
||||
event.result.thumbnailUrl,
|
||||
TrackingDataSource.anilist,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:anitrack/src/data/source.dart';
|
||||
import 'package:anitrack/src/service/anilist/anilist_client.dart';
|
||||
import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
@@ -43,9 +45,18 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
String? broadcastDay;
|
||||
bool airing;
|
||||
try {
|
||||
final apiData = await Jikan().getAnime(int.parse(anime.id));
|
||||
airing = apiData.airing;
|
||||
broadcastDay = apiData.broadcast?.split(' ').first;
|
||||
switch (anime.source) {
|
||||
case TrackingDataSource.mal:
|
||||
final apiData = await Jikan().getAnime(int.parse(anime.id));
|
||||
airing = apiData.airing;
|
||||
broadcastDay = apiData.broadcast?.split(' ').first;
|
||||
case TrackingDataSource.anilist:
|
||||
final apiData = await GetIt.I.get<AniListClient>().getAnimeById(
|
||||
anime.id,
|
||||
);
|
||||
airing = apiData.isAiring;
|
||||
broadcastDay = apiData.broadcastDay;
|
||||
}
|
||||
} catch (ex) {
|
||||
print('API request for anime ${anime.id} failed: $ex');
|
||||
airing = false;
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:io';
|
||||
import 'package:anitrack/i18n/strings.g.dart';
|
||||
import 'package:anitrack/src/data/anime.dart';
|
||||
import 'package:anitrack/src/data/manga.dart';
|
||||
import 'package:anitrack/src/data/source.dart';
|
||||
import 'package:anitrack/src/data/type.dart';
|
||||
import 'package:anitrack/src/service/database.dart';
|
||||
import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
|
||||
@@ -131,6 +132,7 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
|
||||
// NOTE: When the calendar gets refreshed, this should also get cleared
|
||||
true,
|
||||
null,
|
||||
TrackingDataSource.mal,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -207,6 +209,7 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
|
||||
// 0 means that MAL does not know
|
||||
totalChapters == 0 ? null : totalChapters,
|
||||
data.imageUrl,
|
||||
TrackingDataSource.mal,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -122,8 +122,7 @@ class CalendarPageState extends State<CalendarPage> {
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Map<Weekday, List<AnimeTrackingData>> _constructAiringMap() {
|
||||
final airingAnimeMap = <Weekday, List<AnimeTrackingData>>{};
|
||||
for (final anime in GetIt.I.get<AnimeListBloc>().unfilteredAnime) {
|
||||
if (!anime.airing) continue;
|
||||
@@ -158,166 +157,176 @@ class CalendarPageState extends State<CalendarPage> {
|
||||
|
||||
airingAnimeMap.addOrSet(day, anime);
|
||||
}
|
||||
return airingAnimeMap;
|
||||
}
|
||||
|
||||
return BlocListener<CalendarBloc, CalendarState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.refreshing != current.refreshing,
|
||||
listener: (context, state) {
|
||||
// Force an update
|
||||
if (!state.refreshing) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
child: WillPopScope(
|
||||
onWillPop: () async => !context.read<CalendarBloc>().state.refreshing,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: 8,
|
||||
right: 8,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.calendar.calendar),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.read<CalendarBloc>().add(
|
||||
RefreshPerformedEvent(),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.refresh),
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<AnimeListBloc, AnimeListState>(
|
||||
builder: (context, animeListState) {
|
||||
final airingAnimeMap = _constructAiringMap();
|
||||
return BlocListener<CalendarBloc, CalendarState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.refreshing != current.refreshing,
|
||||
listener: (context, state) {
|
||||
// Force an update
|
||||
if (!state.refreshing) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
child: WillPopScope(
|
||||
onWillPop: () async =>
|
||||
!context.read<CalendarBloc>().state.refreshing,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: 8,
|
||||
right: 8,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.calendar.calendar),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.read<CalendarBloc>().add(
|
||||
RefreshPerformedEvent(),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.refresh),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
drawer: getDrawer(context),
|
||||
body: Padding(
|
||||
padding: const EdgeInsetsGeometry.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
// Render all available weekdays
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.unknown,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.monday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.tuesday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.wednesday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.thursday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.friday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.saturday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.sunday,
|
||||
airingAnimeMap,
|
||||
drawer: getDrawer(context),
|
||||
body: Padding(
|
||||
padding: const EdgeInsetsGeometry.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
// Render all available weekdays
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.unknown,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.monday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.tuesday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.wednesday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.thursday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.friday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.saturday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.sunday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
|
||||
// Provide a nice bottom padding, while keeping the elastic effect attached
|
||||
// to the bottom-most edge.
|
||||
const SliverToBoxAdapter(
|
||||
// Provide a nice bottom padding, while keeping the elastic effect attached
|
||||
// to the bottom-most edge.
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: BlocBuilder<CalendarBloc, CalendarState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.refreshing != current.refreshing,
|
||||
builder: (context, state) {
|
||||
if (!state.refreshing) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return const ModalBarrier(
|
||||
dismissible: false,
|
||||
color: Colors.black54,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: BlocBuilder<CalendarBloc, CalendarState>(
|
||||
builder: (context, state) {
|
||||
if (!state.refreshing) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
height: 16,
|
||||
width: 150,
|
||||
height: 150,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(25),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
Text(
|
||||
t.settings.importIndicator(
|
||||
current: state.refreshingCount,
|
||||
total: state.refreshingTotal,
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: BlocBuilder<CalendarBloc, CalendarState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.refreshing != current.refreshing,
|
||||
builder: (context, state) {
|
||||
if (!state.refreshing) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return const ModalBarrier(
|
||||
dismissible: false,
|
||||
color: Colors.black54,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: BlocBuilder<CalendarBloc, CalendarState>(
|
||||
builder: (context, state) {
|
||||
if (!state.refreshing) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: 150,
|
||||
height: 150,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(25),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
Text(
|
||||
t.settings.importIndicator(
|
||||
current: state.refreshingCount,
|
||||
total: state.refreshingTotal,
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user