feat(ui): Implement a simple details screen
This commit is contained in:
parent
8794c3cd60
commit
1892ec5e7a
@ -1,9 +1,11 @@
|
||||
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/details_bloc.dart';
|
||||
import 'package:anitrack/src/ui/bloc/navigation_bloc.dart';
|
||||
import 'package:anitrack/src/ui/constants.dart';
|
||||
import 'package:anitrack/src/ui/pages/anime_list.dart';
|
||||
import 'package:anitrack/src/ui/pages/anime_search.dart';
|
||||
import 'package:anitrack/src/ui/pages/details.dart';
|
||||
import 'package:anitrack/src/service/database.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -23,6 +25,7 @@ void main() async {
|
||||
GetIt.I.registerSingleton<DatabaseService>(database);
|
||||
GetIt.I.registerSingleton<AnimeListBloc>(AnimeListBloc());
|
||||
GetIt.I.registerSingleton<AnimeSearchBloc>(AnimeSearchBloc());
|
||||
GetIt.I.registerSingleton<DetailsBloc>(DetailsBloc());
|
||||
GetIt.I.registerSingleton<NavigationBloc>(NavigationBloc(navKey));
|
||||
|
||||
// Load animes
|
||||
@ -39,6 +42,9 @@ void main() async {
|
||||
BlocProvider<AnimeSearchBloc>(
|
||||
create: (_) => GetIt.I.get<AnimeSearchBloc>(),
|
||||
),
|
||||
BlocProvider<DetailsBloc>(
|
||||
create: (_) => GetIt.I.get<DetailsBloc>(),
|
||||
),
|
||||
BlocProvider<NavigationBloc>(
|
||||
create: (_) => GetIt.I.get<NavigationBloc>(),
|
||||
),
|
||||
@ -77,6 +83,7 @@ class MyApp extends StatelessWidget {
|
||||
case '/':
|
||||
case animeListRoute: return AnimeListPage.route;
|
||||
case animeSearchRoute: return AnimeSearchPage.route;
|
||||
case detailsRoute: return DetailsPage.route;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -24,6 +24,18 @@ extension AnimeTrackStateExtension on AnimeTrackingState {
|
||||
case AnimeTrackingState.all: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
String toNameString() {
|
||||
assert(this != AnimeTrackingState.all, 'AnimeTrackingState.all must not be stringified');
|
||||
|
||||
switch (this) {
|
||||
case AnimeTrackingState.watching: return 'Watching';
|
||||
case AnimeTrackingState.completed: return 'Completed';
|
||||
case AnimeTrackingState.planToWatch: return 'Plan to watch';
|
||||
case AnimeTrackingState.dropped: return 'Dropped';
|
||||
case AnimeTrackingState.all: return 'All';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AnimeTrackingStateConverter implements JsonConverter<AnimeTrackingState, int> {
|
||||
|
@ -7,7 +7,7 @@ part 'manga.g.dart';
|
||||
enum MangaTrackingState {
|
||||
reading, // 0
|
||||
completed, // 1
|
||||
planToWatch, // 2
|
||||
planToRead, // 2
|
||||
dropped, // 3
|
||||
/// This is a pseudo state, i.e. it should never be set
|
||||
all, // -1
|
||||
@ -19,11 +19,23 @@ extension MangaTrackStateExtension on MangaTrackingState {
|
||||
switch (this) {
|
||||
case MangaTrackingState.reading: return 0;
|
||||
case MangaTrackingState.completed: return 1;
|
||||
case MangaTrackingState.planToWatch: return 2;
|
||||
case MangaTrackingState.planToRead: return 2;
|
||||
case MangaTrackingState.dropped: return 3;
|
||||
case MangaTrackingState.all: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
String toNameString() {
|
||||
assert(this != MangaTrackingState.all, 'MangaTrackingState.all must not be stringified');
|
||||
|
||||
switch (this) {
|
||||
case MangaTrackingState.reading: return 'Reading';
|
||||
case MangaTrackingState.completed: return 'Completed';
|
||||
case MangaTrackingState.planToRead: return 'Plan to read';
|
||||
case MangaTrackingState.dropped: return 'Dropped';
|
||||
case MangaTrackingState.all: return 'All';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MangaTrackingStateConverter implements JsonConverter<MangaTrackingState, int> {
|
||||
@ -34,11 +46,11 @@ class MangaTrackingStateConverter implements JsonConverter<MangaTrackingState, i
|
||||
switch (json) {
|
||||
case 0: return MangaTrackingState.reading;
|
||||
case 1: return MangaTrackingState.completed;
|
||||
case 2: return MangaTrackingState.planToWatch;
|
||||
case 2: return MangaTrackingState.planToRead;
|
||||
case 3: return MangaTrackingState.dropped;
|
||||
}
|
||||
|
||||
return MangaTrackingState.planToWatch;
|
||||
return MangaTrackingState.planToRead;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -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;
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
84
lib/src/ui/bloc/details_bloc.dart
Normal file
84
lib/src/ui/bloc/details_bloc.dart
Normal 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),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
151
lib/src/ui/bloc/details_bloc.freezed.dart
Normal file
151
lib/src/ui/bloc/details_bloc.freezed.dart
Normal 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;
|
||||
}
|
23
lib/src/ui/bloc/details_event.dart
Normal file
23
lib/src/ui/bloc/details_event.dart
Normal 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;
|
||||
}
|
9
lib/src/ui/bloc/details_state.dart
Normal file
9
lib/src/ui/bloc/details_state.dart
Normal 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;
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
const animeListRoute = '/anime/list';
|
||||
const animeSearchRoute = '/anime/search';
|
||||
const detailsRoute = '/anime/details';
|
||||
|
@ -3,6 +3,7 @@ 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/anime_search_bloc.dart';
|
||||
import 'package:anitrack/src/ui/bloc/details_bloc.dart';
|
||||
import 'package:anitrack/src/ui/constants.dart';
|
||||
import 'package:anitrack/src/ui/widgets/list_item.dart';
|
||||
import 'package:bottom_bar/bottom_bar.dart';
|
||||
@ -83,8 +84,8 @@ class AnimeListPage extends StatelessWidget {
|
||||
child: Text('Completed'),
|
||||
),
|
||||
const PopupMenuItem<MangaTrackingState>(
|
||||
value: MangaTrackingState.planToWatch,
|
||||
child: Text('Plan to watch'),
|
||||
value: MangaTrackingState.planToRead,
|
||||
child: Text('Plan to read'),
|
||||
),
|
||||
const PopupMenuItem<MangaTrackingState>(
|
||||
value: MangaTrackingState.dropped,
|
||||
@ -123,7 +124,13 @@ class AnimeListPage extends StatelessWidget {
|
||||
if (anime.state != state.animeFilterState) return Container();
|
||||
}
|
||||
|
||||
return ListItem(
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
context.read<DetailsBloc>().add(
|
||||
AnimeDetailsRequestedEvent(anime),
|
||||
);
|
||||
},
|
||||
child: ListItem(
|
||||
title: anime.title,
|
||||
thumbnailUrl: anime.thumbnailUrl,
|
||||
extra: [
|
||||
@ -142,6 +149,7 @@ class AnimeListPage extends StatelessWidget {
|
||||
AnimeEpisodeIncrementedEvent(state.animes[index].id),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -153,7 +161,13 @@ class AnimeListPage extends StatelessWidget {
|
||||
if (manga.state != state.mangaFilterState) return Container();
|
||||
}
|
||||
|
||||
return ListItem(
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
context.read<DetailsBloc>().add(
|
||||
MangaDetailsRequestedEvent(manga),
|
||||
);
|
||||
},
|
||||
child: ListItem(
|
||||
title: manga.title,
|
||||
thumbnailUrl: manga.thumbnailUrl,
|
||||
extra: [
|
||||
@ -172,6 +186,7 @@ class AnimeListPage extends StatelessWidget {
|
||||
MangaChapterIncrementedEvent(state.mangas[index].id),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
174
lib/src/ui/pages/details.dart
Normal file
174
lib/src/ui/pages/details.dart
Normal file
@ -0,0 +1,174 @@
|
||||
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/details_bloc.dart';
|
||||
import 'package:anitrack/src/ui/constants.dart';
|
||||
import 'package:anitrack/src/ui/widgets/image.dart';
|
||||
import 'package:anitrack/src/ui/widgets/list_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
Widget _makeListTile(BuildContext context, dynamic value, String text, bool selected) {
|
||||
return ListTile(
|
||||
title: Text(text),
|
||||
trailing: selected ?
|
||||
Icon(Icons.check) :
|
||||
null,
|
||||
onTap: () {
|
||||
Navigator.of(context).pop(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class DetailsPage extends StatelessWidget {
|
||||
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
||||
builder: (_) => DetailsPage(),
|
||||
settings: const RouteSettings(
|
||||
name: detailsRoute,
|
||||
),
|
||||
);
|
||||
|
||||
String _getTrackingStateText(DetailsState state) {
|
||||
if (state.trackingType == TrackingMediumType.anime) {
|
||||
return (state.data as AnimeTrackingData).state.toNameString();
|
||||
} else {
|
||||
return (state.data as MangaTrackingData).state.toNameString();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Details'),
|
||||
),
|
||||
body: BlocBuilder<DetailsBloc, DetailsState>(
|
||||
builder: (context, state) {
|
||||
return state.data == null ?
|
||||
Container() :
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: ListView(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AnimeCoverImage(
|
||||
url: state.trackingType == TrackingMediumType.anime ?
|
||||
(state.data as AnimeTrackingData).thumbnailUrl :
|
||||
(state.data as MangaTrackingData).thumbnailUrl,
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
(state.data as AnimeTrackingData).title :
|
||||
(state.data as MangaTrackingData).title,
|
||||
textAlign: TextAlign.left,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
||||
TextButton(
|
||||
child: Text(
|
||||
_getTrackingStateText(state),
|
||||
),
|
||||
onPressed: () async {
|
||||
final result = await showModalBottomSheet<dynamic>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
_makeListTile(
|
||||
context,
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
AnimeTrackingState.watching :
|
||||
MangaTrackingState.reading,
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
'Watching' :
|
||||
'Reading',
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
(state.data as AnimeTrackingData).state == AnimeTrackingState.watching :
|
||||
(state.data as MangaTrackingData).state == MangaTrackingState.reading,
|
||||
),
|
||||
_makeListTile(
|
||||
context,
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
AnimeTrackingState.completed :
|
||||
MangaTrackingState.completed,
|
||||
'Completed',
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
(state.data as AnimeTrackingData).state == AnimeTrackingState.completed :
|
||||
(state.data as MangaTrackingData).state == MangaTrackingState.completed,
|
||||
),
|
||||
_makeListTile(
|
||||
context,
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
AnimeTrackingState.planToWatch :
|
||||
MangaTrackingState.planToRead,
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
'Plan to watch' :
|
||||
'Plan to read',
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
(state.data as AnimeTrackingData).state == AnimeTrackingState.planToWatch :
|
||||
(state.data as MangaTrackingData).state == MangaTrackingState.planToRead,
|
||||
),
|
||||
_makeListTile(
|
||||
context,
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
AnimeTrackingState.dropped :
|
||||
MangaTrackingState.dropped,
|
||||
'Dropped',
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
(state.data as AnimeTrackingData).state == AnimeTrackingState.dropped :
|
||||
(state.data as MangaTrackingData).state == MangaTrackingState.dropped,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (result == null) return;
|
||||
|
||||
if (state.trackingType == TrackingMediumType.anime) {
|
||||
context.read<DetailsBloc>().add(
|
||||
DetailsUpdatedEvent(
|
||||
(state.data as AnimeTrackingData).copyWith(
|
||||
state: result as AnimeTrackingState,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
context.read<DetailsBloc>().add(
|
||||
DetailsUpdatedEvent(
|
||||
(state.data as MangaTrackingData).copyWith(
|
||||
state: result as MangaTrackingState,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user