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
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;
}
}
}

View File

@ -15,6 +15,7 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
on<AnimeEpisodeIncrementedEvent>(_onIncremented);
on<AnimeEpisodeDecrementedEvent>(_onDecremented);
on<AnimesLoadedEvent>(_onAnimesLoaded);
on<AnimeFilterChangedEvent>(_onAnimesFiltered);
}
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
mixin _$AnimeListState {
List<AnimeTrackingData> get animes => throw _privateConstructorUsedError;
AnimeTrackingState get filterState => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$AnimeListStateCopyWith<AnimeListState> get copyWith =>
@ -28,7 +29,7 @@ abstract class $AnimeListStateCopyWith<$Res> {
factory $AnimeListStateCopyWith(
AnimeListState value, $Res Function(AnimeListState) then) =
_$AnimeListStateCopyWithImpl<$Res>;
$Res call({List<AnimeTrackingData> animes});
$Res call({List<AnimeTrackingData> 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<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) =
__$$_AnimeListStateCopyWithImpl<$Res>;
@override
$Res call({List<AnimeTrackingData> animes});
$Res call({List<AnimeTrackingData> 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<AnimeTrackingData>,
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<AnimeTrackingData> animes = const []})
_$_AnimeListState(
{final List<AnimeTrackingData> animes = const [],
this.filterState = AnimeTrackingState.watching})
: _animes = animes;
final List<AnimeTrackingData> _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<AnimeTrackingData> animes}) =
_$_AnimeListState;
factory _AnimeListState(
{final List<AnimeTrackingData> animes,
final AnimeTrackingState filterState}) = _$_AnimeListState;
@override
List<AnimeTrackingData> get animes;
@override
AnimeTrackingState get filterState;
@override
@JsonKey(ignore: true)
_$$_AnimeListStateCopyWith<_$_AnimeListState> get copyWith =>
throw _privateConstructorUsedError;

View File

@ -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;
}

View File

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

View File

@ -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<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(
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<AnimeListBloc>().add(
AnimeEpisodeDecrementedEvent(state.animes[index].id),