feat(ui): Implement filtering

This commit is contained in:
PapaTutuWawa 2023-02-04 11:34:09 +01:00
parent 632be66702
commit d273a6deb2
6 changed files with 96 additions and 10 deletions

View File

@ -9,15 +9,19 @@ enum AnimeTrackingState {
completed, // 1 completed, // 1
planToWatch, // 2 planToWatch, // 2
dropped, // 3 dropped, // 3
/// This is a pseudo state, i.e. it should never be set
all, // -1
} }
extension AnimeTrackStateExtension on AnimeTrackingState { extension AnimeTrackStateExtension on AnimeTrackingState {
int toInteger() { int toInteger() {
assert(this != AnimeTrackingState.all, 'AnimeTrackingState.all must not be serialized');
switch (this) { switch (this) {
case AnimeTrackingState.watching: return 0; case AnimeTrackingState.watching: return 0;
case AnimeTrackingState.completed: return 1; case AnimeTrackingState.completed: return 1;
case AnimeTrackingState.planToWatch: return 2; case AnimeTrackingState.planToWatch: return 2;
case AnimeTrackingState.dropped: return 3; case AnimeTrackingState.dropped: return 3;
case AnimeTrackingState.all: return -1;
} }
} }
} }

View File

@ -15,6 +15,7 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
on<AnimeEpisodeIncrementedEvent>(_onIncremented); on<AnimeEpisodeIncrementedEvent>(_onIncremented);
on<AnimeEpisodeDecrementedEvent>(_onDecremented); on<AnimeEpisodeDecrementedEvent>(_onDecremented);
on<AnimesLoadedEvent>(_onAnimesLoaded); on<AnimesLoadedEvent>(_onAnimesLoaded);
on<AnimeFilterChangedEvent>(_onAnimesFiltered);
} }
Future<void> _onAnimeAdded(AnimeAddedEvent event, Emitter<AnimeListState> emit) async { Future<void> _onAnimeAdded(AnimeAddedEvent event, Emitter<AnimeListState> emit) async {
@ -82,4 +83,12 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
), ),
); );
} }
Future<void> _onAnimesFiltered(AnimeFilterChangedEvent event, Emitter<AnimeListState> emit) async {
emit(
state.copyWith(
filterState: event.filterState,
),
);
}
} }

View File

@ -17,6 +17,7 @@ final _privateConstructorUsedError = UnsupportedError(
/// @nodoc /// @nodoc
mixin _$AnimeListState { mixin _$AnimeListState {
List<AnimeTrackingData> get animes => throw _privateConstructorUsedError; List<AnimeTrackingData> get animes => throw _privateConstructorUsedError;
AnimeTrackingState get filterState => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
$AnimeListStateCopyWith<AnimeListState> get copyWith => $AnimeListStateCopyWith<AnimeListState> get copyWith =>
@ -28,7 +29,7 @@ abstract class $AnimeListStateCopyWith<$Res> {
factory $AnimeListStateCopyWith( factory $AnimeListStateCopyWith(
AnimeListState value, $Res Function(AnimeListState) then) = AnimeListState value, $Res Function(AnimeListState) then) =
_$AnimeListStateCopyWithImpl<$Res>; _$AnimeListStateCopyWithImpl<$Res>;
$Res call({List<AnimeTrackingData> animes}); $Res call({List<AnimeTrackingData> animes, AnimeTrackingState filterState});
} }
/// @nodoc /// @nodoc
@ -43,12 +44,17 @@ class _$AnimeListStateCopyWithImpl<$Res>
@override @override
$Res call({ $Res call({
Object? animes = freezed, Object? animes = freezed,
Object? filterState = freezed,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
animes: animes == freezed animes: animes == freezed
? _value.animes ? _value.animes
: animes // ignore: cast_nullable_to_non_nullable : animes // ignore: cast_nullable_to_non_nullable
as List<AnimeTrackingData>, as List<AnimeTrackingData>,
filterState: filterState == freezed
? _value.filterState
: filterState // ignore: cast_nullable_to_non_nullable
as AnimeTrackingState,
)); ));
} }
} }
@ -60,7 +66,7 @@ abstract class _$$_AnimeListStateCopyWith<$Res>
_$_AnimeListState value, $Res Function(_$_AnimeListState) then) = _$_AnimeListState value, $Res Function(_$_AnimeListState) then) =
__$$_AnimeListStateCopyWithImpl<$Res>; __$$_AnimeListStateCopyWithImpl<$Res>;
@override @override
$Res call({List<AnimeTrackingData> animes}); $Res call({List<AnimeTrackingData> animes, AnimeTrackingState filterState});
} }
/// @nodoc /// @nodoc
@ -77,12 +83,17 @@ class __$$_AnimeListStateCopyWithImpl<$Res>
@override @override
$Res call({ $Res call({
Object? animes = freezed, Object? animes = freezed,
Object? filterState = freezed,
}) { }) {
return _then(_$_AnimeListState( return _then(_$_AnimeListState(
animes: animes == freezed animes: animes == freezed
? _value._animes ? _value._animes
: animes // ignore: cast_nullable_to_non_nullable : animes // ignore: cast_nullable_to_non_nullable
as List<AnimeTrackingData>, as List<AnimeTrackingData>,
filterState: filterState == freezed
? _value.filterState
: filterState // ignore: cast_nullable_to_non_nullable
as AnimeTrackingState,
)); ));
} }
} }
@ -90,7 +101,9 @@ class __$$_AnimeListStateCopyWithImpl<$Res>
/// @nodoc /// @nodoc
class _$_AnimeListState implements _AnimeListState { class _$_AnimeListState implements _AnimeListState {
_$_AnimeListState({final List<AnimeTrackingData> animes = const []}) _$_AnimeListState(
{final List<AnimeTrackingData> animes = const [],
this.filterState = AnimeTrackingState.watching})
: _animes = animes; : _animes = animes;
final List<AnimeTrackingData> _animes; final List<AnimeTrackingData> _animes;
@ -101,9 +114,13 @@ class _$_AnimeListState implements _AnimeListState {
return EqualUnmodifiableListView(_animes); return EqualUnmodifiableListView(_animes);
} }
@override
@JsonKey()
final AnimeTrackingState filterState;
@override @override
String toString() { String toString() {
return 'AnimeListState(animes: $animes)'; return 'AnimeListState(animes: $animes, filterState: $filterState)';
} }
@override @override
@ -111,12 +128,16 @@ class _$_AnimeListState implements _AnimeListState {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$_AnimeListState && other is _$_AnimeListState &&
const DeepCollectionEquality().equals(other._animes, _animes)); const DeepCollectionEquality().equals(other._animes, _animes) &&
const DeepCollectionEquality()
.equals(other.filterState, filterState));
} }
@override @override
int get hashCode => int get hashCode => Object.hash(
Object.hash(runtimeType, const DeepCollectionEquality().hash(_animes)); runtimeType,
const DeepCollectionEquality().hash(_animes),
const DeepCollectionEquality().hash(filterState));
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@ -125,12 +146,15 @@ class _$_AnimeListState implements _AnimeListState {
} }
abstract class _AnimeListState implements AnimeListState { abstract class _AnimeListState implements AnimeListState {
factory _AnimeListState({final List<AnimeTrackingData> animes}) = factory _AnimeListState(
_$_AnimeListState; {final List<AnimeTrackingData> animes,
final AnimeTrackingState filterState}) = _$_AnimeListState;
@override @override
List<AnimeTrackingData> get animes; List<AnimeTrackingData> get animes;
@override @override
AnimeTrackingState get filterState;
@override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$_AnimeListStateCopyWith<_$_AnimeListState> get copyWith => _$$_AnimeListStateCopyWith<_$_AnimeListState> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;

View File

@ -25,3 +25,11 @@ class AnimeAddedEvent extends AnimeListEvent {
/// Triggered when animes are to be loaded from the database /// Triggered when animes are to be loaded from the database
class AnimesLoadedEvent extends AnimeListEvent {} class AnimesLoadedEvent extends AnimeListEvent {}
/// Triggered when the filter is changed
class AnimeFilterChangedEvent extends AnimeListEvent {
AnimeFilterChangedEvent(this.filterState);
/// The state to filter
final AnimeTrackingState filterState;
}

View File

@ -4,5 +4,6 @@ part of 'anime_list_bloc.dart';
class AnimeListState with _$AnimeListState { class AnimeListState with _$AnimeListState {
factory AnimeListState({ factory AnimeListState({
@Default([]) List<AnimeTrackingData> animes, @Default([]) List<AnimeTrackingData> animes,
@Default(AnimeTrackingState.watching) AnimeTrackingState filterState,
}) = _AnimeListState; }) = _AnimeListState;
} }

View File

@ -21,12 +21,52 @@ class AnimeListPage extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('Animes'), title: Text('Animes'),
actions: [
PopupMenuButton(
icon: Icon(
Icons.filter_list,
),
initialValue: state.filterState,
onSelected: (filterState) {
context.read<AnimeListBloc>().add(
AnimeFilterChangedEvent(filterState),
);
},
itemBuilder: (_) => [
const PopupMenuItem<AnimeTrackingState>(
value: AnimeTrackingState.watching,
child: Text('Watching'),
),
const PopupMenuItem<AnimeTrackingState>(
value: AnimeTrackingState.completed,
child: Text('Completed'),
),
const PopupMenuItem<AnimeTrackingState>(
value: AnimeTrackingState.planToWatch,
child: Text('Plan to watch'),
),
const PopupMenuItem<AnimeTrackingState>(
value: AnimeTrackingState.dropped,
child: Text('Dropped'),
),
const PopupMenuItem<AnimeTrackingState>(
value: AnimeTrackingState.all,
child: Text('All'),
),
],
),
],
), ),
body: ListView.builder( body: ListView.builder(
itemCount: state.animes.length, itemCount: state.animes.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final anime = state.animes[index];
if (state.filterState != AnimeTrackingState.all) {
if (anime.state != state.filterState) return Container();
}
return AnimeListWidget( return AnimeListWidget(
data: state.animes[index], data: anime,
onLeftSwipe: () { onLeftSwipe: () {
context.read<AnimeListBloc>().add( context.read<AnimeListBloc>().add(
AnimeEpisodeDecrementedEvent(state.animes[index].id), AnimeEpisodeDecrementedEvent(state.animes[index].id),