diff --git a/lib/src/data/anime.dart b/lib/src/data/anime.dart index 1a3bd02..0b4a40c 100644 --- a/lib/src/data/anime.dart +++ b/lib/src/data/anime.dart @@ -9,15 +9,19 @@ enum AnimeTrackingState { completed, // 1 planToWatch, // 2 dropped, // 3 + /// This is a pseudo state, i.e. it should never be set + all, // -1 } extension AnimeTrackStateExtension on AnimeTrackingState { int toInteger() { + assert(this != AnimeTrackingState.all, 'AnimeTrackingState.all must not be serialized'); switch (this) { case AnimeTrackingState.watching: return 0; case AnimeTrackingState.completed: return 1; case AnimeTrackingState.planToWatch: return 2; case AnimeTrackingState.dropped: return 3; + case AnimeTrackingState.all: return -1; } } } diff --git a/lib/src/ui/bloc/anime_list_bloc.dart b/lib/src/ui/bloc/anime_list_bloc.dart index a2bc4df..d142560 100644 --- a/lib/src/ui/bloc/anime_list_bloc.dart +++ b/lib/src/ui/bloc/anime_list_bloc.dart @@ -15,6 +15,7 @@ class AnimeListBloc extends Bloc { on(_onIncremented); on(_onDecremented); on(_onAnimesLoaded); + on(_onAnimesFiltered); } Future _onAnimeAdded(AnimeAddedEvent event, Emitter emit) async { @@ -82,4 +83,12 @@ class AnimeListBloc extends Bloc { ), ); } + + Future _onAnimesFiltered(AnimeFilterChangedEvent event, Emitter emit) async { + emit( + state.copyWith( + filterState: event.filterState, + ), + ); + } } diff --git a/lib/src/ui/bloc/anime_list_bloc.freezed.dart b/lib/src/ui/bloc/anime_list_bloc.freezed.dart index aef9169..31ef361 100644 --- a/lib/src/ui/bloc/anime_list_bloc.freezed.dart +++ b/lib/src/ui/bloc/anime_list_bloc.freezed.dart @@ -17,6 +17,7 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$AnimeListState { List get animes => throw _privateConstructorUsedError; + AnimeTrackingState get filterState => throw _privateConstructorUsedError; @JsonKey(ignore: true) $AnimeListStateCopyWith get copyWith => @@ -28,7 +29,7 @@ abstract class $AnimeListStateCopyWith<$Res> { factory $AnimeListStateCopyWith( AnimeListState value, $Res Function(AnimeListState) then) = _$AnimeListStateCopyWithImpl<$Res>; - $Res call({List animes}); + $Res call({List animes, AnimeTrackingState filterState}); } /// @nodoc @@ -43,12 +44,17 @@ class _$AnimeListStateCopyWithImpl<$Res> @override $Res call({ Object? animes = freezed, + Object? filterState = freezed, }) { return _then(_value.copyWith( animes: animes == freezed ? _value.animes : animes // ignore: cast_nullable_to_non_nullable as List, + 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) = __$$_AnimeListStateCopyWithImpl<$Res>; @override - $Res call({List animes}); + $Res call({List animes, AnimeTrackingState filterState}); } /// @nodoc @@ -77,12 +83,17 @@ class __$$_AnimeListStateCopyWithImpl<$Res> @override $Res call({ Object? animes = freezed, + Object? filterState = freezed, }) { return _then(_$_AnimeListState( animes: animes == freezed ? _value._animes : animes // ignore: cast_nullable_to_non_nullable as List, + filterState: filterState == freezed + ? _value.filterState + : filterState // ignore: cast_nullable_to_non_nullable + as AnimeTrackingState, )); } } @@ -90,7 +101,9 @@ class __$$_AnimeListStateCopyWithImpl<$Res> /// @nodoc class _$_AnimeListState implements _AnimeListState { - _$_AnimeListState({final List animes = const []}) + _$_AnimeListState( + {final List animes = const [], + this.filterState = AnimeTrackingState.watching}) : _animes = animes; final List _animes; @@ -101,9 +114,13 @@ class _$_AnimeListState implements _AnimeListState { return EqualUnmodifiableListView(_animes); } + @override + @JsonKey() + final AnimeTrackingState filterState; + @override String toString() { - return 'AnimeListState(animes: $animes)'; + return 'AnimeListState(animes: $animes, filterState: $filterState)'; } @override @@ -111,12 +128,16 @@ class _$_AnimeListState implements _AnimeListState { return identical(this, other) || (other.runtimeType == runtimeType && other is _$_AnimeListState && - const DeepCollectionEquality().equals(other._animes, _animes)); + const DeepCollectionEquality().equals(other._animes, _animes) && + const DeepCollectionEquality() + .equals(other.filterState, filterState)); } @override - int get hashCode => - Object.hash(runtimeType, const DeepCollectionEquality().hash(_animes)); + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_animes), + const DeepCollectionEquality().hash(filterState)); @JsonKey(ignore: true) @override @@ -125,12 +146,15 @@ class _$_AnimeListState implements _AnimeListState { } abstract class _AnimeListState implements AnimeListState { - factory _AnimeListState({final List animes}) = - _$_AnimeListState; + factory _AnimeListState( + {final List animes, + final AnimeTrackingState filterState}) = _$_AnimeListState; @override List get animes; @override + AnimeTrackingState get filterState; + @override @JsonKey(ignore: true) _$$_AnimeListStateCopyWith<_$_AnimeListState> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/src/ui/bloc/anime_list_event.dart b/lib/src/ui/bloc/anime_list_event.dart index d9fd539..a2066a6 100644 --- a/lib/src/ui/bloc/anime_list_event.dart +++ b/lib/src/ui/bloc/anime_list_event.dart @@ -25,3 +25,11 @@ class AnimeAddedEvent extends AnimeListEvent { /// Triggered when animes are to be loaded from the database class AnimesLoadedEvent extends AnimeListEvent {} + +/// Triggered when the filter is changed +class AnimeFilterChangedEvent extends AnimeListEvent { + AnimeFilterChangedEvent(this.filterState); + + /// The state to filter + final AnimeTrackingState filterState; +} diff --git a/lib/src/ui/bloc/anime_list_state.dart b/lib/src/ui/bloc/anime_list_state.dart index 4770672..3734262 100644 --- a/lib/src/ui/bloc/anime_list_state.dart +++ b/lib/src/ui/bloc/anime_list_state.dart @@ -4,5 +4,6 @@ part of 'anime_list_bloc.dart'; class AnimeListState with _$AnimeListState { factory AnimeListState({ @Default([]) List animes, + @Default(AnimeTrackingState.watching) AnimeTrackingState filterState, }) = _AnimeListState; } diff --git a/lib/src/ui/pages/anime_list.dart b/lib/src/ui/pages/anime_list.dart index 6ec6047..e36b9b7 100644 --- a/lib/src/ui/pages/anime_list.dart +++ b/lib/src/ui/pages/anime_list.dart @@ -21,12 +21,52 @@ class AnimeListPage extends StatelessWidget { return Scaffold( appBar: AppBar( title: Text('Animes'), + actions: [ + PopupMenuButton( + icon: Icon( + Icons.filter_list, + ), + initialValue: state.filterState, + onSelected: (filterState) { + context.read().add( + AnimeFilterChangedEvent(filterState), + ); + }, + itemBuilder: (_) => [ + const PopupMenuItem( + value: AnimeTrackingState.watching, + child: Text('Watching'), + ), + const PopupMenuItem( + value: AnimeTrackingState.completed, + child: Text('Completed'), + ), + const PopupMenuItem( + value: AnimeTrackingState.planToWatch, + child: Text('Plan to watch'), + ), + const PopupMenuItem( + value: AnimeTrackingState.dropped, + child: Text('Dropped'), + ), + const PopupMenuItem( + value: AnimeTrackingState.all, + child: Text('All'), + ), + ], + ), + ], ), body: ListView.builder( itemCount: state.animes.length, itemBuilder: (context, index) { + final anime = state.animes[index]; + if (state.filterState != AnimeTrackingState.all) { + if (anime.state != state.filterState) return Container(); + } + return AnimeListWidget( - data: state.animes[index], + data: anime, onLeftSwipe: () { context.read().add( AnimeEpisodeDecrementedEvent(state.animes[index].id),