feat: Integrate the AniList API since Jikan is shutting down

This commit is contained in:
2026-07-25 16:34:14 +02:00
parent 0dba2a9909
commit 6c2af9fc57
23 changed files with 40865 additions and 218 deletions

View File

@@ -1,6 +1,6 @@
# anitrack # anitrack
A simple Anime and Manga tracker that uses the [Jikan API](https://jikan.moe/) as A simple Anime and Manga tracker that uses the [AniList API](https://docs.anilist.co/guide/introduction) as
its data source. its data source.
## Screenshots ## Screenshots

View File

@@ -1,4 +1,5 @@
import 'package:anitrack/i18n/strings.g.dart'; import 'package:anitrack/i18n/strings.g.dart';
import 'package:anitrack/src/service/anilist/anilist_client.dart';
import 'package:anitrack/src/service/database.dart'; import 'package:anitrack/src/service/database.dart';
import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart'; import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart'; import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart';
@@ -35,6 +36,7 @@ void main() async {
GetIt.I.registerSingleton<NavigationBloc>(NavigationBloc(navKey)); GetIt.I.registerSingleton<NavigationBloc>(NavigationBloc(navKey));
GetIt.I.registerSingleton<SettingsBloc>(SettingsBloc()); GetIt.I.registerSingleton<SettingsBloc>(SettingsBloc());
GetIt.I.registerSingleton<CalendarBloc>(CalendarBloc()); GetIt.I.registerSingleton<CalendarBloc>(CalendarBloc());
GetIt.I.registerSingleton<AniListClient>(AniListClient());
// Load animes // Load animes
GetIt.I.get<AnimeListBloc>().add( GetIt.I.get<AnimeListBloc>().add(

39416
lib/oss_licenses.dart Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
import 'package:anitrack/src/data/source.dart';
import 'package:anitrack/src/data/type.dart'; import 'package:anitrack/src/data/type.dart';
import 'package:anitrack/src/service/database.dart'; import 'package:anitrack/src/service/database.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
@@ -42,6 +43,9 @@ abstract class AnimeTrackingData with _$AnimeTrackingData, TrackingMedium {
/// The day of the week the anime is airing /// The day of the week the anime is airing
String? broadcastDay, String? broadcastDay,
/// The source where we got the data from.
@TrackingDataSourceConverter() TrackingDataSource source,
) = _AnimeTrackingData; ) = _AnimeTrackingData;
AnimeTrackingData._(); AnimeTrackingData._();

View File

@@ -23,7 +23,8 @@ mixin _$AnimeTrackingData {
int? get episodesTotal;/// URL to the thumbnail/cover art for the anime. int? get episodesTotal;/// URL to the thumbnail/cover art for the anime.
String get thumbnailUrl;/// Flag whether the anime is airing String get thumbnailUrl;/// Flag whether the anime is airing
@BoolConverter() bool get airing;/// The day of the week the anime is airing @BoolConverter() bool get airing;/// The day of the week the anime is airing
String? get broadcastDay; String? get broadcastDay;/// The source where we got the data from.
@TrackingDataSourceConverter() TrackingDataSource get source;
/// Create a copy of AnimeTrackingData /// Create a copy of AnimeTrackingData
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -36,16 +37,16 @@ $AnimeTrackingDataCopyWith<AnimeTrackingData> get copyWith => _$AnimeTrackingDat
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AnimeTrackingData&&(identical(other.id, id) || other.id == id)&&(identical(other.state, state) || other.state == state)&&(identical(other.title, title) || other.title == title)&&(identical(other.episodesWatched, episodesWatched) || other.episodesWatched == episodesWatched)&&(identical(other.episodesTotal, episodesTotal) || other.episodesTotal == episodesTotal)&&(identical(other.thumbnailUrl, thumbnailUrl) || other.thumbnailUrl == thumbnailUrl)&&(identical(other.airing, airing) || other.airing == airing)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay)); return identical(this, other) || (other.runtimeType == runtimeType&&other is AnimeTrackingData&&(identical(other.id, id) || other.id == id)&&(identical(other.state, state) || other.state == state)&&(identical(other.title, title) || other.title == title)&&(identical(other.episodesWatched, episodesWatched) || other.episodesWatched == episodesWatched)&&(identical(other.episodesTotal, episodesTotal) || other.episodesTotal == episodesTotal)&&(identical(other.thumbnailUrl, thumbnailUrl) || other.thumbnailUrl == thumbnailUrl)&&(identical(other.airing, airing) || other.airing == airing)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay)&&(identical(other.source, source) || other.source == source));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,id,state,title,episodesWatched,episodesTotal,thumbnailUrl,airing,broadcastDay); int get hashCode => Object.hash(runtimeType,id,state,title,episodesWatched,episodesTotal,thumbnailUrl,airing,broadcastDay,source);
@override @override
String toString() { String toString() {
return 'AnimeTrackingData(id: $id, state: $state, title: $title, episodesWatched: $episodesWatched, episodesTotal: $episodesTotal, thumbnailUrl: $thumbnailUrl, airing: $airing, broadcastDay: $broadcastDay)'; return 'AnimeTrackingData(id: $id, state: $state, title: $title, episodesWatched: $episodesWatched, episodesTotal: $episodesTotal, thumbnailUrl: $thumbnailUrl, airing: $airing, broadcastDay: $broadcastDay, source: $source)';
} }
@@ -56,7 +57,7 @@ abstract mixin class $AnimeTrackingDataCopyWith<$Res> {
factory $AnimeTrackingDataCopyWith(AnimeTrackingData value, $Res Function(AnimeTrackingData) _then) = _$AnimeTrackingDataCopyWithImpl; factory $AnimeTrackingDataCopyWith(AnimeTrackingData value, $Res Function(AnimeTrackingData) _then) = _$AnimeTrackingDataCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
String id,@MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl,@BoolConverter() bool airing, String? broadcastDay String id,@MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl,@BoolConverter() bool airing, String? broadcastDay,@TrackingDataSourceConverter() TrackingDataSource source
}); });
@@ -73,7 +74,7 @@ class _$AnimeTrackingDataCopyWithImpl<$Res>
/// Create a copy of AnimeTrackingData /// Create a copy of AnimeTrackingData
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? state = null,Object? title = null,Object? episodesWatched = null,Object? episodesTotal = freezed,Object? thumbnailUrl = null,Object? airing = null,Object? broadcastDay = freezed,}) { @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? state = null,Object? title = null,Object? episodesWatched = null,Object? episodesTotal = freezed,Object? thumbnailUrl = null,Object? airing = null,Object? broadcastDay = freezed,Object? source = null,}) {
return _then(_self.copyWith( return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,state: null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable as String,state: null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable
@@ -83,7 +84,8 @@ as int,episodesTotal: freezed == episodesTotal ? _self.episodesTotal : episodesT
as int?,thumbnailUrl: null == thumbnailUrl ? _self.thumbnailUrl : thumbnailUrl // ignore: cast_nullable_to_non_nullable as int?,thumbnailUrl: null == thumbnailUrl ? _self.thumbnailUrl : thumbnailUrl // ignore: cast_nullable_to_non_nullable
as String,airing: null == airing ? _self.airing : airing // ignore: cast_nullable_to_non_nullable as String,airing: null == airing ? _self.airing : airing // ignore: cast_nullable_to_non_nullable
as bool,broadcastDay: freezed == broadcastDay ? _self.broadcastDay : broadcastDay // ignore: cast_nullable_to_non_nullable as bool,broadcastDay: freezed == broadcastDay ? _self.broadcastDay : broadcastDay // ignore: cast_nullable_to_non_nullable
as String?, as String?,source: null == source ? _self.source : source // ignore: cast_nullable_to_non_nullable
as TrackingDataSource,
)); ));
} }
@@ -168,10 +170,10 @@ return $default(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl, @BoolConverter() bool airing, String? broadcastDay)? $default,{required TResult orElse(),}) {final _that = this; @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl, @BoolConverter() bool airing, String? broadcastDay, @TrackingDataSourceConverter() TrackingDataSource source)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) { switch (_that) {
case _AnimeTrackingData() when $default != null: case _AnimeTrackingData() when $default != null:
return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.episodesTotal,_that.thumbnailUrl,_that.airing,_that.broadcastDay);case _: return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.episodesTotal,_that.thumbnailUrl,_that.airing,_that.broadcastDay,_that.source);case _:
return orElse(); return orElse();
} }
@@ -189,10 +191,10 @@ return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.epi
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl, @BoolConverter() bool airing, String? broadcastDay) $default,) {final _that = this; @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl, @BoolConverter() bool airing, String? broadcastDay, @TrackingDataSourceConverter() TrackingDataSource source) $default,) {final _that = this;
switch (_that) { switch (_that) {
case _AnimeTrackingData(): case _AnimeTrackingData():
return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.episodesTotal,_that.thumbnailUrl,_that.airing,_that.broadcastDay);case _: return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.episodesTotal,_that.thumbnailUrl,_that.airing,_that.broadcastDay,_that.source);case _:
throw StateError('Unexpected subclass'); throw StateError('Unexpected subclass');
} }
@@ -209,10 +211,10 @@ return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.epi
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl, @BoolConverter() bool airing, String? broadcastDay)? $default,) {final _that = this; @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl, @BoolConverter() bool airing, String? broadcastDay, @TrackingDataSourceConverter() TrackingDataSource source)? $default,) {final _that = this;
switch (_that) { switch (_that) {
case _AnimeTrackingData() when $default != null: case _AnimeTrackingData() when $default != null:
return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.episodesTotal,_that.thumbnailUrl,_that.airing,_that.broadcastDay);case _: return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.episodesTotal,_that.thumbnailUrl,_that.airing,_that.broadcastDay,_that.source);case _:
return null; return null;
} }
@@ -224,7 +226,7 @@ return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.epi
@JsonSerializable() @JsonSerializable()
class _AnimeTrackingData extends AnimeTrackingData { class _AnimeTrackingData extends AnimeTrackingData {
_AnimeTrackingData(this.id, @MediumTrackingStateConverter() this.state, this.title, this.episodesWatched, this.episodesTotal, this.thumbnailUrl, @BoolConverter() this.airing, this.broadcastDay): super._(); _AnimeTrackingData(this.id, @MediumTrackingStateConverter() this.state, this.title, this.episodesWatched, this.episodesTotal, this.thumbnailUrl, @BoolConverter() this.airing, this.broadcastDay, @TrackingDataSourceConverter() this.source): super._();
factory _AnimeTrackingData.fromJson(Map<String, dynamic> json) => _$AnimeTrackingDataFromJson(json); factory _AnimeTrackingData.fromJson(Map<String, dynamic> json) => _$AnimeTrackingDataFromJson(json);
/// The ID of the anime /// The ID of the anime
@@ -243,6 +245,8 @@ class _AnimeTrackingData extends AnimeTrackingData {
@override@BoolConverter() final bool airing; @override@BoolConverter() final bool airing;
/// The day of the week the anime is airing /// The day of the week the anime is airing
@override final String? broadcastDay; @override final String? broadcastDay;
/// The source where we got the data from.
@override@TrackingDataSourceConverter() final TrackingDataSource source;
/// Create a copy of AnimeTrackingData /// Create a copy of AnimeTrackingData
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -257,16 +261,16 @@ Map<String, dynamic> toJson() {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AnimeTrackingData&&(identical(other.id, id) || other.id == id)&&(identical(other.state, state) || other.state == state)&&(identical(other.title, title) || other.title == title)&&(identical(other.episodesWatched, episodesWatched) || other.episodesWatched == episodesWatched)&&(identical(other.episodesTotal, episodesTotal) || other.episodesTotal == episodesTotal)&&(identical(other.thumbnailUrl, thumbnailUrl) || other.thumbnailUrl == thumbnailUrl)&&(identical(other.airing, airing) || other.airing == airing)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay)); return identical(this, other) || (other.runtimeType == runtimeType&&other is _AnimeTrackingData&&(identical(other.id, id) || other.id == id)&&(identical(other.state, state) || other.state == state)&&(identical(other.title, title) || other.title == title)&&(identical(other.episodesWatched, episodesWatched) || other.episodesWatched == episodesWatched)&&(identical(other.episodesTotal, episodesTotal) || other.episodesTotal == episodesTotal)&&(identical(other.thumbnailUrl, thumbnailUrl) || other.thumbnailUrl == thumbnailUrl)&&(identical(other.airing, airing) || other.airing == airing)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay)&&(identical(other.source, source) || other.source == source));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,id,state,title,episodesWatched,episodesTotal,thumbnailUrl,airing,broadcastDay); int get hashCode => Object.hash(runtimeType,id,state,title,episodesWatched,episodesTotal,thumbnailUrl,airing,broadcastDay,source);
@override @override
String toString() { String toString() {
return 'AnimeTrackingData(id: $id, state: $state, title: $title, episodesWatched: $episodesWatched, episodesTotal: $episodesTotal, thumbnailUrl: $thumbnailUrl, airing: $airing, broadcastDay: $broadcastDay)'; return 'AnimeTrackingData(id: $id, state: $state, title: $title, episodesWatched: $episodesWatched, episodesTotal: $episodesTotal, thumbnailUrl: $thumbnailUrl, airing: $airing, broadcastDay: $broadcastDay, source: $source)';
} }
@@ -277,7 +281,7 @@ abstract mixin class _$AnimeTrackingDataCopyWith<$Res> implements $AnimeTracking
factory _$AnimeTrackingDataCopyWith(_AnimeTrackingData value, $Res Function(_AnimeTrackingData) _then) = __$AnimeTrackingDataCopyWithImpl; factory _$AnimeTrackingDataCopyWith(_AnimeTrackingData value, $Res Function(_AnimeTrackingData) _then) = __$AnimeTrackingDataCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
String id,@MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl,@BoolConverter() bool airing, String? broadcastDay String id,@MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl,@BoolConverter() bool airing, String? broadcastDay,@TrackingDataSourceConverter() TrackingDataSource source
}); });
@@ -294,7 +298,7 @@ class __$AnimeTrackingDataCopyWithImpl<$Res>
/// Create a copy of AnimeTrackingData /// Create a copy of AnimeTrackingData
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? state = null,Object? title = null,Object? episodesWatched = null,Object? episodesTotal = freezed,Object? thumbnailUrl = null,Object? airing = null,Object? broadcastDay = freezed,}) { @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? state = null,Object? title = null,Object? episodesWatched = null,Object? episodesTotal = freezed,Object? thumbnailUrl = null,Object? airing = null,Object? broadcastDay = freezed,Object? source = null,}) {
return _then(_AnimeTrackingData( return _then(_AnimeTrackingData(
null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable as String,null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable
@@ -304,7 +308,8 @@ as int,freezed == episodesTotal ? _self.episodesTotal : episodesTotal // ignore:
as int?,null == thumbnailUrl ? _self.thumbnailUrl : thumbnailUrl // ignore: cast_nullable_to_non_nullable as int?,null == thumbnailUrl ? _self.thumbnailUrl : thumbnailUrl // ignore: cast_nullable_to_non_nullable
as String,null == airing ? _self.airing : airing // ignore: cast_nullable_to_non_nullable as String,null == airing ? _self.airing : airing // ignore: cast_nullable_to_non_nullable
as bool,freezed == broadcastDay ? _self.broadcastDay : broadcastDay // ignore: cast_nullable_to_non_nullable as bool,freezed == broadcastDay ? _self.broadcastDay : broadcastDay // ignore: cast_nullable_to_non_nullable
as String?, as String?,null == source ? _self.source : source // ignore: cast_nullable_to_non_nullable
as TrackingDataSource,
)); ));
} }

View File

@@ -18,6 +18,7 @@ _AnimeTrackingData _$AnimeTrackingDataFromJson(Map<String, dynamic> json) =>
json['thumbnailUrl'] as String, json['thumbnailUrl'] as String,
const BoolConverter().fromJson((json['airing'] as num).toInt()), const BoolConverter().fromJson((json['airing'] as num).toInt()),
json['broadcastDay'] as String?, json['broadcastDay'] as String?,
const TrackingDataSourceConverter().fromJson(json['source'] as String),
); );
Map<String, dynamic> _$AnimeTrackingDataToJson(_AnimeTrackingData instance) => Map<String, dynamic> _$AnimeTrackingDataToJson(_AnimeTrackingData instance) =>
@@ -30,4 +31,5 @@ Map<String, dynamic> _$AnimeTrackingDataToJson(_AnimeTrackingData instance) =>
'thumbnailUrl': instance.thumbnailUrl, 'thumbnailUrl': instance.thumbnailUrl,
'airing': const BoolConverter().toJson(instance.airing), 'airing': const BoolConverter().toJson(instance.airing),
'broadcastDay': instance.broadcastDay, 'broadcastDay': instance.broadcastDay,
'source': const TrackingDataSourceConverter().toJson(instance.source),
}; };

View File

@@ -1,3 +1,4 @@
import 'package:anitrack/src/data/source.dart';
import 'package:anitrack/src/data/type.dart'; import 'package:anitrack/src/data/type.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
@@ -28,6 +29,9 @@ abstract class MangaTrackingData with _$MangaTrackingData, TrackingMedium {
/// URL to the thumbnail/cover art for the manga. /// URL to the thumbnail/cover art for the manga.
String thumbnailUrl, String thumbnailUrl,
/// The source where we got the data from.
@TrackingDataSourceConverter() TrackingDataSource source,
) = _MangaTrackingData; ) = _MangaTrackingData;
MangaTrackingData._(); MangaTrackingData._();

View File

@@ -22,7 +22,8 @@ mixin _$MangaTrackingData {
int get chaptersRead;/// Chapters read. int get chaptersRead;/// Chapters read.
int get volumesOwned;/// Episodes watched. int get volumesOwned;/// Episodes watched.
int? get chaptersTotal;/// URL to the thumbnail/cover art for the manga. int? get chaptersTotal;/// URL to the thumbnail/cover art for the manga.
String get thumbnailUrl; String get thumbnailUrl;/// The source where we got the data from.
@TrackingDataSourceConverter() TrackingDataSource get source;
/// Create a copy of MangaTrackingData /// Create a copy of MangaTrackingData
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -35,16 +36,16 @@ $MangaTrackingDataCopyWith<MangaTrackingData> get copyWith => _$MangaTrackingDat
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is MangaTrackingData&&(identical(other.id, id) || other.id == id)&&(identical(other.state, state) || other.state == state)&&(identical(other.title, title) || other.title == title)&&(identical(other.chaptersRead, chaptersRead) || other.chaptersRead == chaptersRead)&&(identical(other.volumesOwned, volumesOwned) || other.volumesOwned == volumesOwned)&&(identical(other.chaptersTotal, chaptersTotal) || other.chaptersTotal == chaptersTotal)&&(identical(other.thumbnailUrl, thumbnailUrl) || other.thumbnailUrl == thumbnailUrl)); return identical(this, other) || (other.runtimeType == runtimeType&&other is MangaTrackingData&&(identical(other.id, id) || other.id == id)&&(identical(other.state, state) || other.state == state)&&(identical(other.title, title) || other.title == title)&&(identical(other.chaptersRead, chaptersRead) || other.chaptersRead == chaptersRead)&&(identical(other.volumesOwned, volumesOwned) || other.volumesOwned == volumesOwned)&&(identical(other.chaptersTotal, chaptersTotal) || other.chaptersTotal == chaptersTotal)&&(identical(other.thumbnailUrl, thumbnailUrl) || other.thumbnailUrl == thumbnailUrl)&&(identical(other.source, source) || other.source == source));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,id,state,title,chaptersRead,volumesOwned,chaptersTotal,thumbnailUrl); int get hashCode => Object.hash(runtimeType,id,state,title,chaptersRead,volumesOwned,chaptersTotal,thumbnailUrl,source);
@override @override
String toString() { String toString() {
return 'MangaTrackingData(id: $id, state: $state, title: $title, chaptersRead: $chaptersRead, volumesOwned: $volumesOwned, chaptersTotal: $chaptersTotal, thumbnailUrl: $thumbnailUrl)'; return 'MangaTrackingData(id: $id, state: $state, title: $title, chaptersRead: $chaptersRead, volumesOwned: $volumesOwned, chaptersTotal: $chaptersTotal, thumbnailUrl: $thumbnailUrl, source: $source)';
} }
@@ -55,7 +56,7 @@ abstract mixin class $MangaTrackingDataCopyWith<$Res> {
factory $MangaTrackingDataCopyWith(MangaTrackingData value, $Res Function(MangaTrackingData) _then) = _$MangaTrackingDataCopyWithImpl; factory $MangaTrackingDataCopyWith(MangaTrackingData value, $Res Function(MangaTrackingData) _then) = _$MangaTrackingDataCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
String id,@MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl String id,@MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl,@TrackingDataSourceConverter() TrackingDataSource source
}); });
@@ -72,7 +73,7 @@ class _$MangaTrackingDataCopyWithImpl<$Res>
/// Create a copy of MangaTrackingData /// Create a copy of MangaTrackingData
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? state = null,Object? title = null,Object? chaptersRead = null,Object? volumesOwned = null,Object? chaptersTotal = freezed,Object? thumbnailUrl = null,}) { @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? state = null,Object? title = null,Object? chaptersRead = null,Object? volumesOwned = null,Object? chaptersTotal = freezed,Object? thumbnailUrl = null,Object? source = null,}) {
return _then(_self.copyWith( return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,state: null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable as String,state: null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable
@@ -81,7 +82,8 @@ as String,chaptersRead: null == chaptersRead ? _self.chaptersRead : chaptersRead
as int,volumesOwned: null == volumesOwned ? _self.volumesOwned : volumesOwned // ignore: cast_nullable_to_non_nullable as int,volumesOwned: null == volumesOwned ? _self.volumesOwned : volumesOwned // ignore: cast_nullable_to_non_nullable
as int,chaptersTotal: freezed == chaptersTotal ? _self.chaptersTotal : chaptersTotal // ignore: cast_nullable_to_non_nullable as int,chaptersTotal: freezed == chaptersTotal ? _self.chaptersTotal : chaptersTotal // ignore: cast_nullable_to_non_nullable
as int?,thumbnailUrl: null == thumbnailUrl ? _self.thumbnailUrl : thumbnailUrl // ignore: cast_nullable_to_non_nullable as int?,thumbnailUrl: null == thumbnailUrl ? _self.thumbnailUrl : thumbnailUrl // ignore: cast_nullable_to_non_nullable
as String, as String,source: null == source ? _self.source : source // ignore: cast_nullable_to_non_nullable
as TrackingDataSource,
)); ));
} }
@@ -166,10 +168,10 @@ return $default(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl)? $default,{required TResult orElse(),}) {final _that = this; @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl, @TrackingDataSourceConverter() TrackingDataSource source)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) { switch (_that) {
case _MangaTrackingData() when $default != null: case _MangaTrackingData() when $default != null:
return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volumesOwned,_that.chaptersTotal,_that.thumbnailUrl);case _: return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volumesOwned,_that.chaptersTotal,_that.thumbnailUrl,_that.source);case _:
return orElse(); return orElse();
} }
@@ -187,10 +189,10 @@ return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volume
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl) $default,) {final _that = this; @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl, @TrackingDataSourceConverter() TrackingDataSource source) $default,) {final _that = this;
switch (_that) { switch (_that) {
case _MangaTrackingData(): case _MangaTrackingData():
return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volumesOwned,_that.chaptersTotal,_that.thumbnailUrl);case _: return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volumesOwned,_that.chaptersTotal,_that.thumbnailUrl,_that.source);case _:
throw StateError('Unexpected subclass'); throw StateError('Unexpected subclass');
} }
@@ -207,10 +209,10 @@ return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volume
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl)? $default,) {final _that = this; @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl, @TrackingDataSourceConverter() TrackingDataSource source)? $default,) {final _that = this;
switch (_that) { switch (_that) {
case _MangaTrackingData() when $default != null: case _MangaTrackingData() when $default != null:
return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volumesOwned,_that.chaptersTotal,_that.thumbnailUrl);case _: return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volumesOwned,_that.chaptersTotal,_that.thumbnailUrl,_that.source);case _:
return null; return null;
} }
@@ -222,7 +224,7 @@ return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volume
@JsonSerializable() @JsonSerializable()
class _MangaTrackingData extends MangaTrackingData { class _MangaTrackingData extends MangaTrackingData {
_MangaTrackingData(this.id, @MediumTrackingStateConverter() this.state, this.title, this.chaptersRead, this.volumesOwned, this.chaptersTotal, this.thumbnailUrl): super._(); _MangaTrackingData(this.id, @MediumTrackingStateConverter() this.state, this.title, this.chaptersRead, this.volumesOwned, this.chaptersTotal, this.thumbnailUrl, @TrackingDataSourceConverter() this.source): super._();
factory _MangaTrackingData.fromJson(Map<String, dynamic> json) => _$MangaTrackingDataFromJson(json); factory _MangaTrackingData.fromJson(Map<String, dynamic> json) => _$MangaTrackingDataFromJson(json);
/// The ID of the manga /// The ID of the manga
@@ -239,6 +241,8 @@ class _MangaTrackingData extends MangaTrackingData {
@override final int? chaptersTotal; @override final int? chaptersTotal;
/// URL to the thumbnail/cover art for the manga. /// URL to the thumbnail/cover art for the manga.
@override final String thumbnailUrl; @override final String thumbnailUrl;
/// The source where we got the data from.
@override@TrackingDataSourceConverter() final TrackingDataSource source;
/// Create a copy of MangaTrackingData /// Create a copy of MangaTrackingData
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -253,16 +257,16 @@ Map<String, dynamic> toJson() {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _MangaTrackingData&&(identical(other.id, id) || other.id == id)&&(identical(other.state, state) || other.state == state)&&(identical(other.title, title) || other.title == title)&&(identical(other.chaptersRead, chaptersRead) || other.chaptersRead == chaptersRead)&&(identical(other.volumesOwned, volumesOwned) || other.volumesOwned == volumesOwned)&&(identical(other.chaptersTotal, chaptersTotal) || other.chaptersTotal == chaptersTotal)&&(identical(other.thumbnailUrl, thumbnailUrl) || other.thumbnailUrl == thumbnailUrl)); return identical(this, other) || (other.runtimeType == runtimeType&&other is _MangaTrackingData&&(identical(other.id, id) || other.id == id)&&(identical(other.state, state) || other.state == state)&&(identical(other.title, title) || other.title == title)&&(identical(other.chaptersRead, chaptersRead) || other.chaptersRead == chaptersRead)&&(identical(other.volumesOwned, volumesOwned) || other.volumesOwned == volumesOwned)&&(identical(other.chaptersTotal, chaptersTotal) || other.chaptersTotal == chaptersTotal)&&(identical(other.thumbnailUrl, thumbnailUrl) || other.thumbnailUrl == thumbnailUrl)&&(identical(other.source, source) || other.source == source));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,id,state,title,chaptersRead,volumesOwned,chaptersTotal,thumbnailUrl); int get hashCode => Object.hash(runtimeType,id,state,title,chaptersRead,volumesOwned,chaptersTotal,thumbnailUrl,source);
@override @override
String toString() { String toString() {
return 'MangaTrackingData(id: $id, state: $state, title: $title, chaptersRead: $chaptersRead, volumesOwned: $volumesOwned, chaptersTotal: $chaptersTotal, thumbnailUrl: $thumbnailUrl)'; return 'MangaTrackingData(id: $id, state: $state, title: $title, chaptersRead: $chaptersRead, volumesOwned: $volumesOwned, chaptersTotal: $chaptersTotal, thumbnailUrl: $thumbnailUrl, source: $source)';
} }
@@ -273,7 +277,7 @@ abstract mixin class _$MangaTrackingDataCopyWith<$Res> implements $MangaTracking
factory _$MangaTrackingDataCopyWith(_MangaTrackingData value, $Res Function(_MangaTrackingData) _then) = __$MangaTrackingDataCopyWithImpl; factory _$MangaTrackingDataCopyWith(_MangaTrackingData value, $Res Function(_MangaTrackingData) _then) = __$MangaTrackingDataCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
String id,@MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl String id,@MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl,@TrackingDataSourceConverter() TrackingDataSource source
}); });
@@ -290,7 +294,7 @@ class __$MangaTrackingDataCopyWithImpl<$Res>
/// Create a copy of MangaTrackingData /// Create a copy of MangaTrackingData
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? state = null,Object? title = null,Object? chaptersRead = null,Object? volumesOwned = null,Object? chaptersTotal = freezed,Object? thumbnailUrl = null,}) { @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? state = null,Object? title = null,Object? chaptersRead = null,Object? volumesOwned = null,Object? chaptersTotal = freezed,Object? thumbnailUrl = null,Object? source = null,}) {
return _then(_MangaTrackingData( return _then(_MangaTrackingData(
null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable as String,null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable
@@ -299,7 +303,8 @@ as String,null == chaptersRead ? _self.chaptersRead : chaptersRead // ignore: ca
as int,null == volumesOwned ? _self.volumesOwned : volumesOwned // ignore: cast_nullable_to_non_nullable as int,null == volumesOwned ? _self.volumesOwned : volumesOwned // ignore: cast_nullable_to_non_nullable
as int,freezed == chaptersTotal ? _self.chaptersTotal : chaptersTotal // ignore: cast_nullable_to_non_nullable as int,freezed == chaptersTotal ? _self.chaptersTotal : chaptersTotal // ignore: cast_nullable_to_non_nullable
as int?,null == thumbnailUrl ? _self.thumbnailUrl : thumbnailUrl // ignore: cast_nullable_to_non_nullable as int?,null == thumbnailUrl ? _self.thumbnailUrl : thumbnailUrl // ignore: cast_nullable_to_non_nullable
as String, as String,null == source ? _self.source : source // ignore: cast_nullable_to_non_nullable
as TrackingDataSource,
)); ));
} }

View File

@@ -17,6 +17,7 @@ _MangaTrackingData _$MangaTrackingDataFromJson(Map<String, dynamic> json) =>
(json['volumesOwned'] as num).toInt(), (json['volumesOwned'] as num).toInt(),
(json['chaptersTotal'] as num?)?.toInt(), (json['chaptersTotal'] as num?)?.toInt(),
json['thumbnailUrl'] as String, json['thumbnailUrl'] as String,
const TrackingDataSourceConverter().fromJson(json['source'] as String),
); );
Map<String, dynamic> _$MangaTrackingDataToJson(_MangaTrackingData instance) => Map<String, dynamic> _$MangaTrackingDataToJson(_MangaTrackingData instance) =>
@@ -28,4 +29,5 @@ Map<String, dynamic> _$MangaTrackingDataToJson(_MangaTrackingData instance) =>
'volumesOwned': instance.volumesOwned, 'volumesOwned': instance.volumesOwned,
'chaptersTotal': instance.chaptersTotal, 'chaptersTotal': instance.chaptersTotal,
'thumbnailUrl': instance.thumbnailUrl, 'thumbnailUrl': instance.thumbnailUrl,
'source': const TrackingDataSourceConverter().toJson(instance.source),
}; };

22
lib/src/data/source.dart Normal file
View File

@@ -0,0 +1,22 @@
import 'package:freezed_annotation/freezed_annotation.dart';
enum TrackingDataSource {
mal('mal'),
anilist('anilist');
const TrackingDataSource(this.value);
final String value;
}
class TrackingDataSourceConverter
implements JsonConverter<TrackingDataSource, String> {
const TrackingDataSourceConverter();
@override
TrackingDataSource fromJson(String json) =>
TrackingDataSource.values.where((val) => val.value == json).first;
@override
String toJson(TrackingDataSource source) => source.value;
}

View File

@@ -0,0 +1,132 @@
import 'package:anitrack/src/service/anilist/model.dart';
import 'package:graphql/client.dart';
class AniListClient {
/// The base GraphQL client for AniList
final _client = GraphQLClient(
link: HttpLink("https://graphql.anilist.co"),
cache: GraphQLCache(),
);
Future<List<AnimeSearchResult>> searchAnime(String q) async {
const query = r'''
query ($search: String!) {
Page {
media(search: $search, type: ANIME) {
id
status
coverImage {
medium
}
episodes
description
nextAiringEpisode {
airingAt
}
title {
romaji
english
native
}
}
}
}
''';
final result = await _client.query(
QueryOptions(
document: gql(query),
variables: {
'search': q,
},
),
);
if (result.hasException) {
// TODO: Handle this more elegantly
print(result.exception.toString());
return [];
}
return (result.data!["Page"]["media"] as List<Object?>)
.cast<Map<String, dynamic>>()
.map(AnimeSearchResult.fromJson)
.toList();
}
Future<List<MangaSearchResult>> searchManga(String q) async {
const query = r'''
query ($search: String!) {
Page {
media(search: $search, type: MANGA) {
id
status
coverImage {
medium
}
chapters
description
title {
romaji
english
native
}
}
}
}
''';
final result = await _client.query(
QueryOptions(
document: gql(query),
variables: {
'search': q,
},
),
);
if (result.hasException) {
// TODO: Handle this more elegantly
print(result.exception.toString());
return [];
}
return (result.data!["Page"]["media"] as List<Object?>)
.cast<Map<String, dynamic>>()
.map(MangaSearchResult.fromJson)
.toList();
}
Future<Anime> getAnimeById(String id) async {
const query = r'''
query ($id: Int) {
Media (id: $id) {
id
status
nextAiringEpisode {
airingAt
}
title {
romaji
english
native
}
}
}
''';
final result = await _client.query(
QueryOptions(
document: gql(query),
variables: {
'id': id,
},
),
);
if (result.hasException) {
print(result.exception.toString());
}
return Anime.fromJson(result.data!['Media'] as Map<String, dynamic>);
}
/*
Future<dynamic> getMangaById(String id) {
}*/
}

View File

@@ -0,0 +1,13 @@
const _weekdays = [
'Mondays',
'Tuesdays',
'Wednesdays',
'Thursdays',
'Fridays',
'Saturdays',
'Sundays',
];
String getAiringDay(int timestamp) {
return _weekdays[DateTime.fromMillisecondsSinceEpoch(timestamp).weekday];
}

View File

@@ -0,0 +1,82 @@
import 'package:anitrack/src/service/anilist/helpers.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'model.freezed.dart';
@freezed
abstract class AnimeSearchResult with _$AnimeSearchResult {
const factory AnimeSearchResult({
required String title,
required String id,
required int? episodes,
required String imageUrl,
required String? description,
required bool isAiring,
required String? broadcastDay,
}) = _AnimeSearchResult;
factory AnimeSearchResult.fromJson(Map<String, Object?> json) {
final airingDayOfTheWeek = json['nextAiringEpisode'] == null
? null
: getAiringDay(
(json['nextAiringEpisode']! as Map<String, dynamic>)['airingAt']!
as int,
);
return AnimeSearchResult(
title: (json['title']! as Map<String, dynamic>)['romaji']! as String,
id: (json['id']! as int).toString(),
episodes: json['episodes'] as int?,
imageUrl:
(json['coverImage']! as Map<String, dynamic>)['medium']! as String,
description: json['description'] as String?,
isAiring: json['status'] == 'RELEASING',
broadcastDay: airingDayOfTheWeek,
);
}
}
@freezed
abstract class MangaSearchResult with _$MangaSearchResult {
const factory MangaSearchResult({
required String title,
required String id,
required int? chapters,
required String imageUrl,
required String? description,
}) = _MangaSearchResult;
factory MangaSearchResult.fromJson(Map<String, Object?> json) {
return MangaSearchResult(
title: (json['title']! as Map<String, dynamic>)['romaji']! as String,
id: (json['id']! as int).toString(),
chapters: json['chapters'] as int?,
imageUrl:
(json['coverImage']! as Map<String, dynamic>)['medium']! as String,
description: json['description'] as String?,
);
}
}
@freezed
abstract class Anime with _$Anime {
const factory Anime({
required String title,
required bool isAiring,
required String? broadcastDay,
}) = _Anime;
factory Anime.fromJson(Map<String, Object?> json) {
final airingDayOfTheWeek = json['nextAiringEpisode'] == null
? null
: getAiringDay(
(json['nextAiringEpisode']! as Map<String, dynamic>)['airingAt']!
as int,
);
return Anime(
title: (json['title']! as Map<String, dynamic>)['romaji']! as String,
isAiring: json['status'] == 'RELEASING',
broadcastDay: airingDayOfTheWeek,
);
}
}

View File

@@ -0,0 +1,821 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$AnimeSearchResult {
String get title; String get id; int? get episodes; String get imageUrl; String? get description; bool get isAiring; String? get broadcastDay;
/// Create a copy of AnimeSearchResult
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$AnimeSearchResultCopyWith<AnimeSearchResult> get copyWith => _$AnimeSearchResultCopyWithImpl<AnimeSearchResult>(this as AnimeSearchResult, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AnimeSearchResult&&(identical(other.title, title) || other.title == title)&&(identical(other.id, id) || other.id == id)&&(identical(other.episodes, episodes) || other.episodes == episodes)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.description, description) || other.description == description)&&(identical(other.isAiring, isAiring) || other.isAiring == isAiring)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay));
}
@override
int get hashCode => Object.hash(runtimeType,title,id,episodes,imageUrl,description,isAiring,broadcastDay);
@override
String toString() {
return 'AnimeSearchResult(title: $title, id: $id, episodes: $episodes, imageUrl: $imageUrl, description: $description, isAiring: $isAiring, broadcastDay: $broadcastDay)';
}
}
/// @nodoc
abstract mixin class $AnimeSearchResultCopyWith<$Res> {
factory $AnimeSearchResultCopyWith(AnimeSearchResult value, $Res Function(AnimeSearchResult) _then) = _$AnimeSearchResultCopyWithImpl;
@useResult
$Res call({
String title, String id, int? episodes, String imageUrl, String? description, bool isAiring, String? broadcastDay
});
}
/// @nodoc
class _$AnimeSearchResultCopyWithImpl<$Res>
implements $AnimeSearchResultCopyWith<$Res> {
_$AnimeSearchResultCopyWithImpl(this._self, this._then);
final AnimeSearchResult _self;
final $Res Function(AnimeSearchResult) _then;
/// Create a copy of AnimeSearchResult
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? title = null,Object? id = null,Object? episodes = freezed,Object? imageUrl = null,Object? description = freezed,Object? isAiring = null,Object? broadcastDay = freezed,}) {
return _then(_self.copyWith(
title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,episodes: freezed == episodes ? _self.episodes : episodes // ignore: cast_nullable_to_non_nullable
as int?,imageUrl: null == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,isAiring: null == isAiring ? _self.isAiring : isAiring // ignore: cast_nullable_to_non_nullable
as bool,broadcastDay: freezed == broadcastDay ? _self.broadcastDay : broadcastDay // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [AnimeSearchResult].
extension AnimeSearchResultPatterns on AnimeSearchResult {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _AnimeSearchResult value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _AnimeSearchResult() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _AnimeSearchResult value) $default,){
final _that = this;
switch (_that) {
case _AnimeSearchResult():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _AnimeSearchResult value)? $default,){
final _that = this;
switch (_that) {
case _AnimeSearchResult() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String title, String id, int? episodes, String imageUrl, String? description, bool isAiring, String? broadcastDay)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _AnimeSearchResult() when $default != null:
return $default(_that.title,_that.id,_that.episodes,_that.imageUrl,_that.description,_that.isAiring,_that.broadcastDay);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String title, String id, int? episodes, String imageUrl, String? description, bool isAiring, String? broadcastDay) $default,) {final _that = this;
switch (_that) {
case _AnimeSearchResult():
return $default(_that.title,_that.id,_that.episodes,_that.imageUrl,_that.description,_that.isAiring,_that.broadcastDay);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String title, String id, int? episodes, String imageUrl, String? description, bool isAiring, String? broadcastDay)? $default,) {final _that = this;
switch (_that) {
case _AnimeSearchResult() when $default != null:
return $default(_that.title,_that.id,_that.episodes,_that.imageUrl,_that.description,_that.isAiring,_that.broadcastDay);case _:
return null;
}
}
}
/// @nodoc
class _AnimeSearchResult implements AnimeSearchResult {
const _AnimeSearchResult({required this.title, required this.id, required this.episodes, required this.imageUrl, required this.description, required this.isAiring, required this.broadcastDay});
@override final String title;
@override final String id;
@override final int? episodes;
@override final String imageUrl;
@override final String? description;
@override final bool isAiring;
@override final String? broadcastDay;
/// Create a copy of AnimeSearchResult
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$AnimeSearchResultCopyWith<_AnimeSearchResult> get copyWith => __$AnimeSearchResultCopyWithImpl<_AnimeSearchResult>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AnimeSearchResult&&(identical(other.title, title) || other.title == title)&&(identical(other.id, id) || other.id == id)&&(identical(other.episodes, episodes) || other.episodes == episodes)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.description, description) || other.description == description)&&(identical(other.isAiring, isAiring) || other.isAiring == isAiring)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay));
}
@override
int get hashCode => Object.hash(runtimeType,title,id,episodes,imageUrl,description,isAiring,broadcastDay);
@override
String toString() {
return 'AnimeSearchResult(title: $title, id: $id, episodes: $episodes, imageUrl: $imageUrl, description: $description, isAiring: $isAiring, broadcastDay: $broadcastDay)';
}
}
/// @nodoc
abstract mixin class _$AnimeSearchResultCopyWith<$Res> implements $AnimeSearchResultCopyWith<$Res> {
factory _$AnimeSearchResultCopyWith(_AnimeSearchResult value, $Res Function(_AnimeSearchResult) _then) = __$AnimeSearchResultCopyWithImpl;
@override @useResult
$Res call({
String title, String id, int? episodes, String imageUrl, String? description, bool isAiring, String? broadcastDay
});
}
/// @nodoc
class __$AnimeSearchResultCopyWithImpl<$Res>
implements _$AnimeSearchResultCopyWith<$Res> {
__$AnimeSearchResultCopyWithImpl(this._self, this._then);
final _AnimeSearchResult _self;
final $Res Function(_AnimeSearchResult) _then;
/// Create a copy of AnimeSearchResult
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? title = null,Object? id = null,Object? episodes = freezed,Object? imageUrl = null,Object? description = freezed,Object? isAiring = null,Object? broadcastDay = freezed,}) {
return _then(_AnimeSearchResult(
title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,episodes: freezed == episodes ? _self.episodes : episodes // ignore: cast_nullable_to_non_nullable
as int?,imageUrl: null == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,isAiring: null == isAiring ? _self.isAiring : isAiring // ignore: cast_nullable_to_non_nullable
as bool,broadcastDay: freezed == broadcastDay ? _self.broadcastDay : broadcastDay // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
mixin _$MangaSearchResult {
String get title; String get id; int? get chapters; String get imageUrl; String? get description;
/// Create a copy of MangaSearchResult
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$MangaSearchResultCopyWith<MangaSearchResult> get copyWith => _$MangaSearchResultCopyWithImpl<MangaSearchResult>(this as MangaSearchResult, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is MangaSearchResult&&(identical(other.title, title) || other.title == title)&&(identical(other.id, id) || other.id == id)&&(identical(other.chapters, chapters) || other.chapters == chapters)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.description, description) || other.description == description));
}
@override
int get hashCode => Object.hash(runtimeType,title,id,chapters,imageUrl,description);
@override
String toString() {
return 'MangaSearchResult(title: $title, id: $id, chapters: $chapters, imageUrl: $imageUrl, description: $description)';
}
}
/// @nodoc
abstract mixin class $MangaSearchResultCopyWith<$Res> {
factory $MangaSearchResultCopyWith(MangaSearchResult value, $Res Function(MangaSearchResult) _then) = _$MangaSearchResultCopyWithImpl;
@useResult
$Res call({
String title, String id, int? chapters, String imageUrl, String? description
});
}
/// @nodoc
class _$MangaSearchResultCopyWithImpl<$Res>
implements $MangaSearchResultCopyWith<$Res> {
_$MangaSearchResultCopyWithImpl(this._self, this._then);
final MangaSearchResult _self;
final $Res Function(MangaSearchResult) _then;
/// Create a copy of MangaSearchResult
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? title = null,Object? id = null,Object? chapters = freezed,Object? imageUrl = null,Object? description = freezed,}) {
return _then(_self.copyWith(
title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,chapters: freezed == chapters ? _self.chapters : chapters // ignore: cast_nullable_to_non_nullable
as int?,imageUrl: null == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [MangaSearchResult].
extension MangaSearchResultPatterns on MangaSearchResult {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _MangaSearchResult value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _MangaSearchResult() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _MangaSearchResult value) $default,){
final _that = this;
switch (_that) {
case _MangaSearchResult():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _MangaSearchResult value)? $default,){
final _that = this;
switch (_that) {
case _MangaSearchResult() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String title, String id, int? chapters, String imageUrl, String? description)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _MangaSearchResult() when $default != null:
return $default(_that.title,_that.id,_that.chapters,_that.imageUrl,_that.description);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String title, String id, int? chapters, String imageUrl, String? description) $default,) {final _that = this;
switch (_that) {
case _MangaSearchResult():
return $default(_that.title,_that.id,_that.chapters,_that.imageUrl,_that.description);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String title, String id, int? chapters, String imageUrl, String? description)? $default,) {final _that = this;
switch (_that) {
case _MangaSearchResult() when $default != null:
return $default(_that.title,_that.id,_that.chapters,_that.imageUrl,_that.description);case _:
return null;
}
}
}
/// @nodoc
class _MangaSearchResult implements MangaSearchResult {
const _MangaSearchResult({required this.title, required this.id, required this.chapters, required this.imageUrl, required this.description});
@override final String title;
@override final String id;
@override final int? chapters;
@override final String imageUrl;
@override final String? description;
/// Create a copy of MangaSearchResult
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$MangaSearchResultCopyWith<_MangaSearchResult> get copyWith => __$MangaSearchResultCopyWithImpl<_MangaSearchResult>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _MangaSearchResult&&(identical(other.title, title) || other.title == title)&&(identical(other.id, id) || other.id == id)&&(identical(other.chapters, chapters) || other.chapters == chapters)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.description, description) || other.description == description));
}
@override
int get hashCode => Object.hash(runtimeType,title,id,chapters,imageUrl,description);
@override
String toString() {
return 'MangaSearchResult(title: $title, id: $id, chapters: $chapters, imageUrl: $imageUrl, description: $description)';
}
}
/// @nodoc
abstract mixin class _$MangaSearchResultCopyWith<$Res> implements $MangaSearchResultCopyWith<$Res> {
factory _$MangaSearchResultCopyWith(_MangaSearchResult value, $Res Function(_MangaSearchResult) _then) = __$MangaSearchResultCopyWithImpl;
@override @useResult
$Res call({
String title, String id, int? chapters, String imageUrl, String? description
});
}
/// @nodoc
class __$MangaSearchResultCopyWithImpl<$Res>
implements _$MangaSearchResultCopyWith<$Res> {
__$MangaSearchResultCopyWithImpl(this._self, this._then);
final _MangaSearchResult _self;
final $Res Function(_MangaSearchResult) _then;
/// Create a copy of MangaSearchResult
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? title = null,Object? id = null,Object? chapters = freezed,Object? imageUrl = null,Object? description = freezed,}) {
return _then(_MangaSearchResult(
title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,chapters: freezed == chapters ? _self.chapters : chapters // ignore: cast_nullable_to_non_nullable
as int?,imageUrl: null == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
mixin _$Anime {
String get title; bool get isAiring; String? get broadcastDay;
/// Create a copy of Anime
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$AnimeCopyWith<Anime> get copyWith => _$AnimeCopyWithImpl<Anime>(this as Anime, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Anime&&(identical(other.title, title) || other.title == title)&&(identical(other.isAiring, isAiring) || other.isAiring == isAiring)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay));
}
@override
int get hashCode => Object.hash(runtimeType,title,isAiring,broadcastDay);
@override
String toString() {
return 'Anime(title: $title, isAiring: $isAiring, broadcastDay: $broadcastDay)';
}
}
/// @nodoc
abstract mixin class $AnimeCopyWith<$Res> {
factory $AnimeCopyWith(Anime value, $Res Function(Anime) _then) = _$AnimeCopyWithImpl;
@useResult
$Res call({
String title, bool isAiring, String? broadcastDay
});
}
/// @nodoc
class _$AnimeCopyWithImpl<$Res>
implements $AnimeCopyWith<$Res> {
_$AnimeCopyWithImpl(this._self, this._then);
final Anime _self;
final $Res Function(Anime) _then;
/// Create a copy of Anime
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? title = null,Object? isAiring = null,Object? broadcastDay = freezed,}) {
return _then(_self.copyWith(
title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,isAiring: null == isAiring ? _self.isAiring : isAiring // ignore: cast_nullable_to_non_nullable
as bool,broadcastDay: freezed == broadcastDay ? _self.broadcastDay : broadcastDay // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [Anime].
extension AnimePatterns on Anime {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _Anime value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _Anime() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _Anime value) $default,){
final _that = this;
switch (_that) {
case _Anime():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _Anime value)? $default,){
final _that = this;
switch (_that) {
case _Anime() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String title, bool isAiring, String? broadcastDay)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Anime() when $default != null:
return $default(_that.title,_that.isAiring,_that.broadcastDay);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String title, bool isAiring, String? broadcastDay) $default,) {final _that = this;
switch (_that) {
case _Anime():
return $default(_that.title,_that.isAiring,_that.broadcastDay);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String title, bool isAiring, String? broadcastDay)? $default,) {final _that = this;
switch (_that) {
case _Anime() when $default != null:
return $default(_that.title,_that.isAiring,_that.broadcastDay);case _:
return null;
}
}
}
/// @nodoc
class _Anime implements Anime {
const _Anime({required this.title, required this.isAiring, required this.broadcastDay});
@override final String title;
@override final bool isAiring;
@override final String? broadcastDay;
/// Create a copy of Anime
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$AnimeCopyWith<_Anime> get copyWith => __$AnimeCopyWithImpl<_Anime>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Anime&&(identical(other.title, title) || other.title == title)&&(identical(other.isAiring, isAiring) || other.isAiring == isAiring)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay));
}
@override
int get hashCode => Object.hash(runtimeType,title,isAiring,broadcastDay);
@override
String toString() {
return 'Anime(title: $title, isAiring: $isAiring, broadcastDay: $broadcastDay)';
}
}
/// @nodoc
abstract mixin class _$AnimeCopyWith<$Res> implements $AnimeCopyWith<$Res> {
factory _$AnimeCopyWith(_Anime value, $Res Function(_Anime) _then) = __$AnimeCopyWithImpl;
@override @useResult
$Res call({
String title, bool isAiring, String? broadcastDay
});
}
/// @nodoc
class __$AnimeCopyWithImpl<$Res>
implements _$AnimeCopyWith<$Res> {
__$AnimeCopyWithImpl(this._self, this._then);
final _Anime _self;
final $Res Function(_Anime) _then;
/// Create a copy of Anime
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? title = null,Object? isAiring = null,Object? broadcastDay = freezed,}) {
return _then(_Anime(
title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,isAiring: null == isAiring ? _self.isAiring : isAiring // ignore: cast_nullable_to_non_nullable
as bool,broadcastDay: freezed == broadcastDay ? _self.broadcastDay : broadcastDay // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on

View File

@@ -6,6 +6,7 @@ import 'package:anitrack/src/data/manga.dart';
import 'package:anitrack/src/service/migrations/0000_airing.dart'; import 'package:anitrack/src/service/migrations/0000_airing.dart';
import 'package:anitrack/src/service/migrations/0000_score.dart'; import 'package:anitrack/src/service/migrations/0000_score.dart';
import 'package:anitrack/src/service/migrations/0001_anime_watcher.dart'; import 'package:anitrack/src/service/migrations/0001_anime_watcher.dart';
import 'package:anitrack/src/service/migrations/0002_anilist.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart';
const animeTable = 'Anime'; const animeTable = 'Anime';
@@ -38,7 +39,8 @@ Future<void> _createDatabase(Database db, int version) async {
otherTitles TEXT NOT NULL, otherTitles TEXT NOT NULL,
score INTEGER, score INTEGER,
airing INTEGER NOT NULL, airing INTEGER NOT NULL,
broadcastDay TEXT broadcastDay TEXT,
source TEXT NOT NULL
)''', )''',
); );
await db.execute( await db.execute(
@@ -52,7 +54,8 @@ Future<void> _createDatabase(Database db, int version) async {
thumbnailUrl TEXT NOT NULL, thumbnailUrl TEXT NOT NULL,
title TEXT NOT NULL, title TEXT NOT NULL,
otherTitles TEXT NOT NULL, otherTitles TEXT NOT NULL,
score INTEGER score INTEGER,
source TEXT NOT NULL
)''', )''',
); );
await db.execute( await db.execute(
@@ -89,7 +92,7 @@ class DatabaseService {
_db = await openDatabase( _db = await openDatabase(
'anitrack.db', 'anitrack.db',
version: 4, version: 5,
onConfigure: (db) async { onConfigure: (db) async {
// In order to do schema changes during database upgrades, we disable foreign // In order to do schema changes during database upgrades, we disable foreign
// keys in the onConfigure phase, but re-enable them here. // keys in the onConfigure phase, but re-enable them here.
@@ -111,6 +114,9 @@ class DatabaseService {
if (oldVersion < 4) { if (oldVersion < 4) {
await migrateFromV3ToV4(db); await migrateFromV3ToV4(db);
} }
if (oldVersion < 5) {
await migrateFromV4ToV5(db);
}
}, },
); );
print(_db.path); print(_db.path);

View File

@@ -0,0 +1,15 @@
import 'package:anitrack/src/service/database.dart';
import 'package:sqflite/sqflite.dart';
Future<void> migrateFromV4ToV5(Database db) async {
await db.execute(
'''
ALTER TABLE $animeTable ADD COLUMN source TEXT NOT NULL DEFAULT 'mal';
''',
);
await db.execute(
'''
ALTER TABLE $mangaTable ADD COLUMN source TEXT NOT NULL DEFAULT 'mal';
''',
);
}

View File

@@ -317,9 +317,9 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
); );
// Update the cache // 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'); assert(cacheIndex != -1, 'The anime must exist');
_mangas.removeAt(cacheIndex); _animes.removeAt(cacheIndex);
// Update the database // Update the database
await GetIt.I.get<DatabaseService>().deleteAnime(event.id); await GetIt.I.get<DatabaseService>().deleteAnime(event.id);
@@ -338,9 +338,9 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
); );
// Update the cache // 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'); assert(cacheIndex != -1, 'The manga must exist');
_animes.removeAt(cacheIndex); _mangas.removeAt(cacheIndex);
// Update the database // Update the database
await GetIt.I.get<DatabaseService>().deleteManga(event.id); await GetIt.I.get<DatabaseService>().deleteManga(event.id);

View File

@@ -1,7 +1,10 @@
import 'package:anitrack/src/data/anime.dart'; import 'package:anitrack/src/data/anime.dart';
import 'package:anitrack/src/data/manga.dart'; import 'package:anitrack/src/data/manga.dart';
import 'package:anitrack/src/data/search_result.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/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/anime_list_bloc.dart' as list;
import 'package:anitrack/src/ui/bloc/navigation_bloc.dart'; import 'package:anitrack/src/ui/bloc/navigation_bloc.dart';
import 'package:anitrack/src/ui/constants.dart'; import 'package:anitrack/src/ui/constants.dart';
@@ -22,6 +25,9 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
on<ResultTappedEvent>(_onResultTapped); on<ResultTappedEvent>(_onResultTapped);
} }
// The client to use for AniList GraphQL queries.
final _anilistClient = AniListClient();
Future<void> _onRequested( Future<void> _onRequested(
AnimeSearchRequestedEvent event, AnimeSearchRequestedEvent event,
Emitter<AnimeSearchState> emit, Emitter<AnimeSearchState> emit,
@@ -67,23 +73,21 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
if (state.trackingType == TrackingMediumType.anime) { if (state.trackingType == TrackingMediumType.anime) {
// Anime // Anime
final result = await Jikan().searchAnime( final result = await _anilistClient.searchAnime(state.searchQuery);
query: state.searchQuery,
);
emit( emit(
state.copyWith( state.copyWith(
working: false, working: false,
searchResults: result searchResults: result
.map( .map(
(Anime anime) => SearchResult( (anime) => SearchResult(
anime.title, anime.title,
anime.malId.toString(), anime.id,
anime.episodes, anime.episodes,
anime.imageUrl, anime.imageUrl,
anime.synopsis ?? '', anime.description ?? '',
anime.airing, anime.isAiring,
anime.broadcast?.split(' ').first, anime.broadcastDay,
), ),
) )
.toList(), .toList(),
@@ -91,21 +95,19 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
); );
} else { } else {
// Manga // Manga
final result = await Jikan().searchManga( final result = await _anilistClient.searchManga(state.searchQuery);
query: state.searchQuery,
);
emit( emit(
state.copyWith( state.copyWith(
working: false, working: false,
searchResults: result searchResults: result
.map( .map(
(Manga manga) => SearchResult( (manga) => SearchResult(
manga.title, manga.title,
manga.malId.toString(), manga.id,
manga.chapters, manga.chapters,
manga.imageUrl, manga.imageUrl,
manga.synopsis ?? '', manga.description ?? '',
// TODO(Unknown): Implement for Manga // TODO(Unknown): Implement for Manga
false, false,
null, null,
@@ -133,6 +135,7 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
event.result.thumbnailUrl, event.result.thumbnailUrl,
event.result.isAiring, event.result.isAiring,
event.result.broadcastDay, event.result.broadcastDay,
TrackingDataSource.anilist,
), ),
) )
: list.MangaAddedEvent( : list.MangaAddedEvent(
@@ -144,6 +147,7 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
0, 0,
event.result.total, event.result.total,
event.result.thumbnailUrl, event.result.thumbnailUrl,
TrackingDataSource.anilist,
), ),
), ),
); );

View File

@@ -1,5 +1,7 @@
import 'dart:async'; 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:anitrack/src/ui/bloc/anime_list_bloc.dart';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
@@ -43,9 +45,18 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
String? broadcastDay; String? broadcastDay;
bool airing; bool airing;
try { try {
final apiData = await Jikan().getAnime(int.parse(anime.id)); switch (anime.source) {
airing = apiData.airing; case TrackingDataSource.mal:
broadcastDay = apiData.broadcast?.split(' ').first; 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) { } catch (ex) {
print('API request for anime ${anime.id} failed: $ex'); print('API request for anime ${anime.id} failed: $ex');
airing = false; airing = false;

View File

@@ -4,6 +4,7 @@ import 'dart:io';
import 'package:anitrack/i18n/strings.g.dart'; import 'package:anitrack/i18n/strings.g.dart';
import 'package:anitrack/src/data/anime.dart'; import 'package:anitrack/src/data/anime.dart';
import 'package:anitrack/src/data/manga.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/data/type.dart';
import 'package:anitrack/src/service/database.dart'; import 'package:anitrack/src/service/database.dart';
import 'package:anitrack/src/ui/bloc/anime_list_bloc.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 // NOTE: When the calendar gets refreshed, this should also get cleared
true, true,
null, null,
TrackingDataSource.mal,
), ),
); );
} }
@@ -207,6 +209,7 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
// 0 means that MAL does not know // 0 means that MAL does not know
totalChapters == 0 ? null : totalChapters, totalChapters == 0 ? null : totalChapters,
data.imageUrl, data.imageUrl,
TrackingDataSource.mal,
), ),
); );
} }

View File

@@ -122,8 +122,7 @@ class CalendarPageState extends State<CalendarPage> {
]; ];
} }
@override Map<Weekday, List<AnimeTrackingData>> _constructAiringMap() {
Widget build(BuildContext context) {
final airingAnimeMap = <Weekday, List<AnimeTrackingData>>{}; final airingAnimeMap = <Weekday, List<AnimeTrackingData>>{};
for (final anime in GetIt.I.get<AnimeListBloc>().unfilteredAnime) { for (final anime in GetIt.I.get<AnimeListBloc>().unfilteredAnime) {
if (!anime.airing) continue; if (!anime.airing) continue;
@@ -158,166 +157,176 @@ class CalendarPageState extends State<CalendarPage> {
airingAnimeMap.addOrSet(day, anime); airingAnimeMap.addOrSet(day, anime);
} }
return airingAnimeMap;
}
return BlocListener<CalendarBloc, CalendarState>( @override
listenWhen: (previous, current) => Widget build(BuildContext context) {
previous.refreshing != current.refreshing, return BlocBuilder<AnimeListBloc, AnimeListState>(
listener: (context, state) { builder: (context, animeListState) {
// Force an update final airingAnimeMap = _constructAiringMap();
if (!state.refreshing) { return BlocListener<CalendarBloc, CalendarState>(
setState(() {}); listenWhen: (previous, current) =>
} previous.refreshing != current.refreshing,
}, listener: (context, state) {
child: WillPopScope( // Force an update
onWillPop: () async => !context.read<CalendarBloc>().state.refreshing, if (!state.refreshing) {
child: Stack( setState(() {});
children: [ }
Positioned( },
left: 8, child: WillPopScope(
right: 8, onWillPop: () async =>
top: 0, !context.read<CalendarBloc>().state.refreshing,
bottom: 0, child: Stack(
child: Scaffold( children: [
appBar: AppBar( Positioned(
title: Text(t.calendar.calendar), left: 8,
actions: [ right: 8,
IconButton( top: 0,
onPressed: () { bottom: 0,
context.read<CalendarBloc>().add( child: Scaffold(
RefreshPerformedEvent(), appBar: AppBar(
); title: Text(t.calendar.calendar),
}, actions: [
icon: const Icon(Icons.refresh), IconButton(
onPressed: () {
context.read<CalendarBloc>().add(
RefreshPerformedEvent(),
);
},
icon: const Icon(Icons.refresh),
),
],
), ),
], drawer: getDrawer(context),
), body: Padding(
drawer: getDrawer(context), padding: const EdgeInsetsGeometry.symmetric(
body: Padding( horizontal: 12,
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,
), ),
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 // Provide a nice bottom padding, while keeping the elastic effect attached
// to the bottom-most edge. // to the bottom-most edge.
const SliverToBoxAdapter( 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( 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,
),
],
),
),
),
);
},
),
),
],
),
),
); );
} }
} }

View File

@@ -429,6 +429,70 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.3"
gql:
dependency: transitive
description:
name: gql
sha256: "67c32325eb55c15f526f0f5e7d8b38a463dbff2ec3c2e046be4a1a95f0dc93d1"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
gql_dedupe_link:
dependency: transitive
description:
name: gql_dedupe_link
sha256: "10bee0564d67c24e0c8bd08bd56e0682b64a135e58afabbeed30d85d5e9fea96"
url: "https://pub.dev"
source: hosted
version: "2.0.4-alpha+1715521079596"
gql_error_link:
dependency: transitive
description:
name: gql_error_link
sha256: dd0f3fbfbcec848ea050507470cdb5d3dc47d29544ae11044a1c883cbe159ccc
url: "https://pub.dev"
source: hosted
version: "1.0.1"
gql_exec:
dependency: transitive
description:
name: gql_exec
sha256: "394944626fae900f1d34343ecf2d62e44eb984826189c8979d305f0ae5846e38"
url: "https://pub.dev"
source: hosted
version: "1.1.1-alpha+1699813812660"
gql_http_link:
dependency: transitive
description:
name: gql_http_link
sha256: "07635e85a4f313836904961904417fd27844fe8f68f77b410a4e6b81d8e9202e"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
gql_link:
dependency: transitive
description:
name: gql_link
sha256: "0730276ce3a6a0ced073194ff923a8d99b3c78e442cbf096eb54fd0c3fa9f974"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
gql_transform_link:
dependency: transitive
description:
name: gql_transform_link
sha256: b3bb06a6991bc5c9d877e2757455f80e2c14dc684b8327bedae4f4ee67afae8b
url: "https://pub.dev"
source: hosted
version: "1.0.1"
graphql:
dependency: "direct main"
description:
name: graphql
sha256: a7cb0b5e8719546bf8d4edf5f57c3690ddf0fcce379c0d9d2287fdab73481090
url: "https://pub.dev"
source: hosted
version: "5.2.4"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:
@@ -437,6 +501,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.3.2"
hive_ce:
dependency: transitive
description:
name: hive_ce
sha256: "8e9980e68643afb1e765d3af32b47996552a64e190d03faf622cea07c1294418"
url: "https://pub.dev"
source: hosted
version: "2.19.3"
hooks: hooks:
dependency: transitive dependency: transitive
description: description:
@@ -501,6 +573,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.5" version: "1.0.5"
isolate_channel:
dependency: transitive
description:
name: isolate_channel
sha256: a9d3d620695bc984244dafae00b95e4319d6974b2d77f4b9e1eb4f2efe099094
url: "https://pub.dev"
source: hosted
version: "0.6.1"
jikan_api: jikan_api:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -629,6 +709,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2" version: "2.0.2"
normalize:
dependency: transitive
description:
name: normalize
sha256: "703f0af9e6f43a5a71536e977b945238bc89f1a941347e7ba467865a20cc1a9f"
url: "https://pub.dev"
source: hosted
version: "0.10.0"
objective_c: objective_c:
dependency: transitive dependency: transitive
description: description:

View File

@@ -21,6 +21,7 @@ dependencies:
fluttertoast: ^9.0.0 fluttertoast: ^9.0.0
freezed_annotation: ^3.1.0 freezed_annotation: ^3.1.0
get_it: ^9.2.1 get_it: ^9.2.1
graphql: ^5.2.4
jikan_api: ^2.2.1 jikan_api: ^2.2.1
json_annotation: ^4.11.0 json_annotation: ^4.11.0
path: ^1.9.0 path: ^1.9.0