feat(ui): Implement a simple details screen

This commit is contained in:
2023-02-04 17:17:00 +01:00
parent 8794c3cd60
commit 1892ec5e7a
12 changed files with 574 additions and 40 deletions

View File

@@ -23,6 +23,8 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
on<MangaFilterChangedEvent>(_onMangasFiltered);
on<MangaChapterIncrementedEvent>(_onMangaIncremented);
on<MangaChapterDecrementedEvent>(_onMangaDecremented);
on<AnimeUpdatedEvent>(_onAnimeUpdated);
on<MangaUpdatedEvent>(_onMangaUpdated);
}
Future<void> _onAnimeAdded(AnimeAddedEvent event, Emitter<AnimeListState> emit) async {
@@ -173,4 +175,36 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
await GetIt.I.get<DatabaseService>().updateManga(newManga);
}
Future<void> _onAnimeUpdated(AnimeUpdatedEvent event, Emitter<AnimeListState> emit) async {
emit(
state.copyWith(
animes: List.from(
state.animes.map((anime) {
if (anime.id == event.anime.id) {
return event.anime;
}
return anime;
}),
),
),
);
}
Future<void> _onMangaUpdated(MangaUpdatedEvent event, Emitter<AnimeListState> emit) async {
emit(
state.copyWith(
mangas: List.from(
state.mangas.map((manga) {
if (manga.id == event.manga.id) {
return event.manga;
}
return manga;
}),
),
),
);
}
}

View File

@@ -42,6 +42,12 @@ class AnimeTrackingTypeChanged extends AnimeListEvent {
final TrackingMediumType type;
}
class AnimeUpdatedEvent extends AnimeListEvent {
AnimeUpdatedEvent(this.anime);
final AnimeTrackingData anime;
}
class MangaAddedEvent extends AnimeListEvent {
MangaAddedEvent(this.data);
@@ -70,3 +76,9 @@ class MangaChapterDecrementedEvent extends AnimeListEvent {
/// The ID of the anime
final String id;
}
class MangaUpdatedEvent extends AnimeListEvent {
MangaUpdatedEvent(this.manga);
final MangaTrackingData manga;
}

View File

@@ -0,0 +1,84 @@
import 'package:anitrack/src/data/anime.dart';
import 'package:anitrack/src/data/manga.dart';
import 'package:anitrack/src/data/type.dart';
import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
import 'package:anitrack/src/ui/bloc/navigation_bloc.dart';
import 'package:anitrack/src/ui/constants.dart';
import 'package:anitrack/src/service/database.dart';
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:get_it/get_it.dart';
part 'details_state.dart';
part 'details_event.dart';
part 'details_bloc.freezed.dart';
class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
DetailsBloc() : super(DetailsState()) {
on<AnimeDetailsRequestedEvent>(_onAnimeRequested);
on<MangaDetailsRequestedEvent>(_onMangaRequested);
on<DetailsUpdatedEvent>(_onDetailsUpdated);
}
Future<void> _onAnimeRequested(AnimeDetailsRequestedEvent event, Emitter<DetailsState> emit) async {
emit(
state.copyWith(
trackingType: TrackingMediumType.anime,
data: event.anime,
),
);
GetIt.I.get<NavigationBloc>().add(
PushedNamedEvent(
NavigationDestination(
detailsRoute,
),
),
);
}
Future<void> _onMangaRequested(MangaDetailsRequestedEvent event, Emitter<DetailsState> emit) async {
emit(
state.copyWith(
trackingType: TrackingMediumType.manga,
data: event.manga,
),
);
GetIt.I.get<NavigationBloc>().add(
PushedNamedEvent(
NavigationDestination(
detailsRoute,
),
),
);
}
Future<void> _onDetailsUpdated(DetailsUpdatedEvent event, Emitter<DetailsState> emit) async {
if (state.trackingType == TrackingMediumType.anime) {
emit(
state.copyWith(
data: event.data,
),
);
await GetIt.I.get<DatabaseService>().updateAnime(event.data as AnimeTrackingData);
GetIt.I.get<AnimeListBloc>().add(
AnimeUpdatedEvent(event.data as AnimeTrackingData),
);
} else {
emit(
state.copyWith(
data: event.data,
),
);
await GetIt.I.get<DatabaseService>().updateManga(event.data as MangaTrackingData);
GetIt.I.get<AnimeListBloc>().add(
MangaUpdatedEvent(event.data as MangaTrackingData),
);
}
}
}

View File

@@ -0,0 +1,151 @@
// 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 'details_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 _$DetailsState {
dynamic get data => throw _privateConstructorUsedError;
TrackingMediumType get trackingType => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$DetailsStateCopyWith<DetailsState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $DetailsStateCopyWith<$Res> {
factory $DetailsStateCopyWith(
DetailsState value, $Res Function(DetailsState) then) =
_$DetailsStateCopyWithImpl<$Res>;
$Res call({dynamic data, TrackingMediumType trackingType});
}
/// @nodoc
class _$DetailsStateCopyWithImpl<$Res> implements $DetailsStateCopyWith<$Res> {
_$DetailsStateCopyWithImpl(this._value, this._then);
final DetailsState _value;
// ignore: unused_field
final $Res Function(DetailsState) _then;
@override
$Res call({
Object? data = freezed,
Object? trackingType = freezed,
}) {
return _then(_value.copyWith(
data: data == freezed
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as dynamic,
trackingType: trackingType == freezed
? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable
as TrackingMediumType,
));
}
}
/// @nodoc
abstract class _$$_DetailsStateCopyWith<$Res>
implements $DetailsStateCopyWith<$Res> {
factory _$$_DetailsStateCopyWith(
_$_DetailsState value, $Res Function(_$_DetailsState) then) =
__$$_DetailsStateCopyWithImpl<$Res>;
@override
$Res call({dynamic data, TrackingMediumType trackingType});
}
/// @nodoc
class __$$_DetailsStateCopyWithImpl<$Res>
extends _$DetailsStateCopyWithImpl<$Res>
implements _$$_DetailsStateCopyWith<$Res> {
__$$_DetailsStateCopyWithImpl(
_$_DetailsState _value, $Res Function(_$_DetailsState) _then)
: super(_value, (v) => _then(v as _$_DetailsState));
@override
_$_DetailsState get _value => super._value as _$_DetailsState;
@override
$Res call({
Object? data = freezed,
Object? trackingType = freezed,
}) {
return _then(_$_DetailsState(
data: data == freezed
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as dynamic,
trackingType: trackingType == freezed
? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable
as TrackingMediumType,
));
}
}
/// @nodoc
class _$_DetailsState implements _DetailsState {
_$_DetailsState({this.data, this.trackingType = TrackingMediumType.anime});
@override
final dynamic data;
@override
@JsonKey()
final TrackingMediumType trackingType;
@override
String toString() {
return 'DetailsState(data: $data, trackingType: $trackingType)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_DetailsState &&
const DeepCollectionEquality().equals(other.data, data) &&
const DeepCollectionEquality()
.equals(other.trackingType, trackingType));
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(data),
const DeepCollectionEquality().hash(trackingType));
@JsonKey(ignore: true)
@override
_$$_DetailsStateCopyWith<_$_DetailsState> get copyWith =>
__$$_DetailsStateCopyWithImpl<_$_DetailsState>(this, _$identity);
}
abstract class _DetailsState implements DetailsState {
factory _DetailsState(
{final dynamic data,
final TrackingMediumType trackingType}) = _$_DetailsState;
@override
dynamic get data;
@override
TrackingMediumType get trackingType;
@override
@JsonKey(ignore: true)
_$$_DetailsStateCopyWith<_$_DetailsState> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -0,0 +1,23 @@
part of 'details_bloc.dart';
abstract class DetailsEvent {}
class AnimeDetailsRequestedEvent extends DetailsEvent {
AnimeDetailsRequestedEvent(this.anime);
/// The anime to show details about
final AnimeTrackingData anime;
}
class MangaDetailsRequestedEvent extends DetailsEvent {
MangaDetailsRequestedEvent(this.manga);
/// The manga to show details about
final MangaTrackingData manga;
}
class DetailsUpdatedEvent extends DetailsEvent {
DetailsUpdatedEvent(this.data);
final dynamic data;
}

View File

@@ -0,0 +1,9 @@
part of 'details_bloc.dart';
@freezed
class DetailsState with _$DetailsState {
factory DetailsState({
dynamic data,
@Default(TrackingMediumType.anime) TrackingMediumType trackingType,
}) = _DetailsState;
}