feat(ui): Implement filtering
This commit is contained in:
parent
632be66702
commit
d273a6deb2
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
Loading…
Reference in New Issue
Block a user