feat: Implement a basic anime calendar

This commit is contained in:
2023-07-16 15:13:35 +02:00
parent fbe72d1232
commit 9fed2116b1
27 changed files with 852 additions and 65 deletions

View File

@@ -270,6 +270,8 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
animes: _getFilteredAnime(),
),
);
await GetIt.I.get<DatabaseService>().updateAnime(event.anime);
}
Future<void> _onMangaUpdated(

View File

@@ -43,9 +43,12 @@ class AnimeTrackingTypeChanged extends AnimeListEvent {
}
class AnimeUpdatedEvent extends AnimeListEvent {
AnimeUpdatedEvent(this.anime);
AnimeUpdatedEvent(this.anime, {this.commit = false});
final AnimeTrackingData anime;
/// Commit the new anime data to the database.
final bool commit;
}
class AnimeRemovedEvent extends AnimeListEvent {

View File

@@ -82,6 +82,8 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
anime.episodes,
anime.imageUrl,
anime.synopsis ?? '',
anime.airing,
anime.broadcast?.split(' ').first,
),
)
.toList(),
@@ -104,6 +106,9 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
manga.chapters,
manga.imageUrl,
manga.synopsis ?? '',
// TODO(Unknown): Implement for Manga
false,
null,
),
)
.toList(),
@@ -126,6 +131,8 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
0,
event.result.total,
event.result.thumbnailUrl,
event.result.isAiring,
event.result.broadcastDay,
),
)
: list.MangaAddedEvent(

View File

@@ -0,0 +1,59 @@
import 'dart:async';
import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:get_it/get_it.dart';
import 'package:jikan_api/jikan_api.dart';
part 'calendar_state.dart';
part 'calendar_bloc.freezed.dart';
part 'calendar_event.dart';
class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
CalendarBloc() : super(CalendarState(false, 0, 0)) {
on<RefreshPerformedEvent>(_onRefreshPerformed);
}
Future<void> _onRefreshPerformed(
RefreshPerformedEvent event,
Emitter<CalendarState> emit,
) async {
emit(
state.copyWith(
refreshing: true,
refreshingCount: 0,
refreshingTotal: 0,
),
);
final al = GetIt.I.get<AnimeListBloc>();
final animes = al.state.animes.where((anime) => anime.airing);
emit(
state.copyWith(
refreshing: true,
refreshingCount: 0,
refreshingTotal: animes.length,
),
);
for (final anime in animes) {
emit(state.copyWith(refreshingCount: state.refreshingCount + 1));
final apiData = await Jikan().getAnime(int.parse(anime.id));
if (!apiData.airing) {
al.add(
AnimeUpdatedEvent(
anime.copyWith(airing: false, broadcastDay: null),
commit: true,
),
);
}
// Prevent hammering Jikan
await Future<void>.delayed(const Duration(milliseconds: 500));
}
emit(state.copyWith(refreshing: false));
}
}

View File

@@ -0,0 +1,169 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// 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
part of 'calendar_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
/// @nodoc
mixin _$CalendarState {
bool get refreshing => throw _privateConstructorUsedError;
int get refreshingCount => throw _privateConstructorUsedError;
int get refreshingTotal => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$CalendarStateCopyWith<CalendarState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $CalendarStateCopyWith<$Res> {
factory $CalendarStateCopyWith(
CalendarState value, $Res Function(CalendarState) then) =
_$CalendarStateCopyWithImpl<$Res>;
$Res call({bool refreshing, int refreshingCount, int refreshingTotal});
}
/// @nodoc
class _$CalendarStateCopyWithImpl<$Res>
implements $CalendarStateCopyWith<$Res> {
_$CalendarStateCopyWithImpl(this._value, this._then);
final CalendarState _value;
// ignore: unused_field
final $Res Function(CalendarState) _then;
@override
$Res call({
Object? refreshing = freezed,
Object? refreshingCount = freezed,
Object? refreshingTotal = freezed,
}) {
return _then(_value.copyWith(
refreshing: refreshing == freezed
? _value.refreshing
: refreshing // ignore: cast_nullable_to_non_nullable
as bool,
refreshingCount: refreshingCount == freezed
? _value.refreshingCount
: refreshingCount // ignore: cast_nullable_to_non_nullable
as int,
refreshingTotal: refreshingTotal == freezed
? _value.refreshingTotal
: refreshingTotal // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
abstract class _$$_CalendarStateCopyWith<$Res>
implements $CalendarStateCopyWith<$Res> {
factory _$$_CalendarStateCopyWith(
_$_CalendarState value, $Res Function(_$_CalendarState) then) =
__$$_CalendarStateCopyWithImpl<$Res>;
@override
$Res call({bool refreshing, int refreshingCount, int refreshingTotal});
}
/// @nodoc
class __$$_CalendarStateCopyWithImpl<$Res>
extends _$CalendarStateCopyWithImpl<$Res>
implements _$$_CalendarStateCopyWith<$Res> {
__$$_CalendarStateCopyWithImpl(
_$_CalendarState _value, $Res Function(_$_CalendarState) _then)
: super(_value, (v) => _then(v as _$_CalendarState));
@override
_$_CalendarState get _value => super._value as _$_CalendarState;
@override
$Res call({
Object? refreshing = freezed,
Object? refreshingCount = freezed,
Object? refreshingTotal = freezed,
}) {
return _then(_$_CalendarState(
refreshing == freezed
? _value.refreshing
: refreshing // ignore: cast_nullable_to_non_nullable
as bool,
refreshingCount == freezed
? _value.refreshingCount
: refreshingCount // ignore: cast_nullable_to_non_nullable
as int,
refreshingTotal == freezed
? _value.refreshingTotal
: refreshingTotal // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
class _$_CalendarState implements _CalendarState {
_$_CalendarState(this.refreshing, this.refreshingCount, this.refreshingTotal);
@override
final bool refreshing;
@override
final int refreshingCount;
@override
final int refreshingTotal;
@override
String toString() {
return 'CalendarState(refreshing: $refreshing, refreshingCount: $refreshingCount, refreshingTotal: $refreshingTotal)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_CalendarState &&
const DeepCollectionEquality()
.equals(other.refreshing, refreshing) &&
const DeepCollectionEquality()
.equals(other.refreshingCount, refreshingCount) &&
const DeepCollectionEquality()
.equals(other.refreshingTotal, refreshingTotal));
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(refreshing),
const DeepCollectionEquality().hash(refreshingCount),
const DeepCollectionEquality().hash(refreshingTotal));
@JsonKey(ignore: true)
@override
_$$_CalendarStateCopyWith<_$_CalendarState> get copyWith =>
__$$_CalendarStateCopyWithImpl<_$_CalendarState>(this, _$identity);
}
abstract class _CalendarState implements CalendarState {
factory _CalendarState(final bool refreshing, final int refreshingCount,
final int refreshingTotal) = _$_CalendarState;
@override
bool get refreshing;
@override
int get refreshingCount;
@override
int get refreshingTotal;
@override
@JsonKey(ignore: true)
_$$_CalendarStateCopyWith<_$_CalendarState> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -0,0 +1,6 @@
part of 'calendar_bloc.dart';
abstract class CalendarEvent {}
/// Triggered by the UI when the user wants to refresh the airing anime list.
class RefreshPerformedEvent extends CalendarEvent {}

View File

@@ -0,0 +1,10 @@
part of 'calendar_bloc.dart';
@freezed
class CalendarState with _$CalendarState {
factory CalendarState(
bool refreshing,
int refreshingCount,
int refreshingTotal,
) = _CalendarState;
}

View File

@@ -28,6 +28,7 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
emit(
state.copyWith(
trackingType: TrackingMediumType.anime,
heroImagePrefix: event.heroImagePrefix,
data: event.anime,
),
);

View File

@@ -17,6 +17,7 @@ final _privateConstructorUsedError = UnsupportedError(
/// @nodoc
mixin _$DetailsState {
TrackingMedium? get data => throw _privateConstructorUsedError;
String? get heroImagePrefix => throw _privateConstructorUsedError;
TrackingMediumType get trackingType => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@@ -29,7 +30,10 @@ abstract class $DetailsStateCopyWith<$Res> {
factory $DetailsStateCopyWith(
DetailsState value, $Res Function(DetailsState) then) =
_$DetailsStateCopyWithImpl<$Res>;
$Res call({TrackingMedium? data, TrackingMediumType trackingType});
$Res call(
{TrackingMedium? data,
String? heroImagePrefix,
TrackingMediumType trackingType});
}
/// @nodoc
@@ -43,6 +47,7 @@ class _$DetailsStateCopyWithImpl<$Res> implements $DetailsStateCopyWith<$Res> {
@override
$Res call({
Object? data = freezed,
Object? heroImagePrefix = freezed,
Object? trackingType = freezed,
}) {
return _then(_value.copyWith(
@@ -50,6 +55,10 @@ class _$DetailsStateCopyWithImpl<$Res> implements $DetailsStateCopyWith<$Res> {
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as TrackingMedium?,
heroImagePrefix: heroImagePrefix == freezed
? _value.heroImagePrefix
: heroImagePrefix // ignore: cast_nullable_to_non_nullable
as String?,
trackingType: trackingType == freezed
? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable
@@ -65,7 +74,10 @@ abstract class _$$_DetailsStateCopyWith<$Res>
_$_DetailsState value, $Res Function(_$_DetailsState) then) =
__$$_DetailsStateCopyWithImpl<$Res>;
@override
$Res call({TrackingMedium? data, TrackingMediumType trackingType});
$Res call(
{TrackingMedium? data,
String? heroImagePrefix,
TrackingMediumType trackingType});
}
/// @nodoc
@@ -82,6 +94,7 @@ class __$$_DetailsStateCopyWithImpl<$Res>
@override
$Res call({
Object? data = freezed,
Object? heroImagePrefix = freezed,
Object? trackingType = freezed,
}) {
return _then(_$_DetailsState(
@@ -89,6 +102,10 @@ class __$$_DetailsStateCopyWithImpl<$Res>
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as TrackingMedium?,
heroImagePrefix: heroImagePrefix == freezed
? _value.heroImagePrefix
: heroImagePrefix // ignore: cast_nullable_to_non_nullable
as String?,
trackingType: trackingType == freezed
? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable
@@ -100,17 +117,22 @@ class __$$_DetailsStateCopyWithImpl<$Res>
/// @nodoc
class _$_DetailsState implements _DetailsState {
_$_DetailsState({this.data, this.trackingType = TrackingMediumType.anime});
_$_DetailsState(
{this.data,
this.heroImagePrefix,
this.trackingType = TrackingMediumType.anime});
@override
final TrackingMedium? data;
@override
final String? heroImagePrefix;
@override
@JsonKey()
final TrackingMediumType trackingType;
@override
String toString() {
return 'DetailsState(data: $data, trackingType: $trackingType)';
return 'DetailsState(data: $data, heroImagePrefix: $heroImagePrefix, trackingType: $trackingType)';
}
@override
@@ -119,6 +141,8 @@ class _$_DetailsState implements _DetailsState {
(other.runtimeType == runtimeType &&
other is _$_DetailsState &&
const DeepCollectionEquality().equals(other.data, data) &&
const DeepCollectionEquality()
.equals(other.heroImagePrefix, heroImagePrefix) &&
const DeepCollectionEquality()
.equals(other.trackingType, trackingType));
}
@@ -127,6 +151,7 @@ class _$_DetailsState implements _DetailsState {
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(data),
const DeepCollectionEquality().hash(heroImagePrefix),
const DeepCollectionEquality().hash(trackingType));
@JsonKey(ignore: true)
@@ -138,11 +163,14 @@ class _$_DetailsState implements _DetailsState {
abstract class _DetailsState implements DetailsState {
factory _DetailsState(
{final TrackingMedium? data,
final String? heroImagePrefix,
final TrackingMediumType trackingType}) = _$_DetailsState;
@override
TrackingMedium? get data;
@override
String? get heroImagePrefix;
@override
TrackingMediumType get trackingType;
@override
@JsonKey(ignore: true)

View File

@@ -3,10 +3,15 @@ part of 'details_bloc.dart';
abstract class DetailsEvent {}
class AnimeDetailsRequestedEvent extends DetailsEvent {
AnimeDetailsRequestedEvent(this.anime);
AnimeDetailsRequestedEvent(
this.anime, {
this.heroImagePrefix,
});
/// The anime to show details about
final AnimeTrackingData anime;
final String? heroImagePrefix;
}
class MangaDetailsRequestedEvent extends DetailsEvent {

View File

@@ -4,6 +4,7 @@ part of 'details_bloc.dart';
class DetailsState with _$DetailsState {
factory DetailsState({
TrackingMedium? data,
String? heroImagePrefix,
@Default(TrackingMediumType.anime) TrackingMediumType trackingType,
}) = _DetailsState;
}

View File

@@ -119,6 +119,9 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
// 0 means that MAL does not know
totalEpisodes == 0 ? null : totalEpisodes,
data.imageUrl,
// NOTE: When the calendar gets refreshed, this should also get cleared
true,
null,
),
);
}