From a60e9e8e8145b666c55b4631c5651931ea0ee4b8 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sat, 4 Feb 2023 11:48:24 +0100 Subject: [PATCH] feat(ui): Stub out a manga tracking page --- lib/src/data/type.dart | 5 ++ lib/src/ui/bloc/anime_list_bloc.dart | 10 +++ lib/src/ui/bloc/anime_list_bloc.freezed.dart | 41 +++++++-- lib/src/ui/bloc/anime_list_event.dart | 8 ++ lib/src/ui/bloc/anime_list_state.dart | 1 + lib/src/ui/pages/anime_list.dart | 90 +++++++++++++++----- pubspec.lock | 7 ++ pubspec.yaml | 1 + 8 files changed, 134 insertions(+), 29 deletions(-) create mode 100644 lib/src/data/type.dart diff --git a/lib/src/data/type.dart b/lib/src/data/type.dart new file mode 100644 index 0000000..2071ccf --- /dev/null +++ b/lib/src/data/type.dart @@ -0,0 +1,5 @@ +/// The type of medium we are tracking. Useful for UI stuff. +enum TrackingMediumType { + anime, + manga, +} diff --git a/lib/src/ui/bloc/anime_list_bloc.dart b/lib/src/ui/bloc/anime_list_bloc.dart index d142560..3b478d8 100644 --- a/lib/src/ui/bloc/anime_list_bloc.dart +++ b/lib/src/ui/bloc/anime_list_bloc.dart @@ -1,5 +1,6 @@ import 'dart:math'; import 'package:anitrack/src/data/anime.dart'; +import 'package:anitrack/src/data/type.dart'; import 'package:anitrack/src/service/database.dart'; import 'package:bloc/bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -16,6 +17,7 @@ class AnimeListBloc extends Bloc { on(_onDecremented); on(_onAnimesLoaded); on(_onAnimesFiltered); + on(_onTrackingTypeChanged); } Future _onAnimeAdded(AnimeAddedEvent event, Emitter emit) async { @@ -91,4 +93,12 @@ class AnimeListBloc extends Bloc { ), ); } + + Future _onTrackingTypeChanged(AnimeTrackingTypeChanged event, Emitter emit) async { + emit( + state.copyWith( + trackingType: event.type, + ), + ); + } } diff --git a/lib/src/ui/bloc/anime_list_bloc.freezed.dart b/lib/src/ui/bloc/anime_list_bloc.freezed.dart index 31ef361..a7ad45e 100644 --- a/lib/src/ui/bloc/anime_list_bloc.freezed.dart +++ b/lib/src/ui/bloc/anime_list_bloc.freezed.dart @@ -18,6 +18,7 @@ final _privateConstructorUsedError = UnsupportedError( mixin _$AnimeListState { List get animes => throw _privateConstructorUsedError; AnimeTrackingState get filterState => throw _privateConstructorUsedError; + TrackingMediumType get trackingType => throw _privateConstructorUsedError; @JsonKey(ignore: true) $AnimeListStateCopyWith get copyWith => @@ -29,7 +30,10 @@ abstract class $AnimeListStateCopyWith<$Res> { factory $AnimeListStateCopyWith( AnimeListState value, $Res Function(AnimeListState) then) = _$AnimeListStateCopyWithImpl<$Res>; - $Res call({List animes, AnimeTrackingState filterState}); + $Res call( + {List animes, + AnimeTrackingState filterState, + TrackingMediumType trackingType}); } /// @nodoc @@ -45,6 +49,7 @@ class _$AnimeListStateCopyWithImpl<$Res> $Res call({ Object? animes = freezed, Object? filterState = freezed, + Object? trackingType = freezed, }) { return _then(_value.copyWith( animes: animes == freezed @@ -55,6 +60,10 @@ class _$AnimeListStateCopyWithImpl<$Res> ? _value.filterState : filterState // ignore: cast_nullable_to_non_nullable as AnimeTrackingState, + trackingType: trackingType == freezed + ? _value.trackingType + : trackingType // ignore: cast_nullable_to_non_nullable + as TrackingMediumType, )); } } @@ -66,7 +75,10 @@ abstract class _$$_AnimeListStateCopyWith<$Res> _$_AnimeListState value, $Res Function(_$_AnimeListState) then) = __$$_AnimeListStateCopyWithImpl<$Res>; @override - $Res call({List animes, AnimeTrackingState filterState}); + $Res call( + {List animes, + AnimeTrackingState filterState, + TrackingMediumType trackingType}); } /// @nodoc @@ -84,6 +96,7 @@ class __$$_AnimeListStateCopyWithImpl<$Res> $Res call({ Object? animes = freezed, Object? filterState = freezed, + Object? trackingType = freezed, }) { return _then(_$_AnimeListState( animes: animes == freezed @@ -94,6 +107,10 @@ class __$$_AnimeListStateCopyWithImpl<$Res> ? _value.filterState : filterState // ignore: cast_nullable_to_non_nullable as AnimeTrackingState, + trackingType: trackingType == freezed + ? _value.trackingType + : trackingType // ignore: cast_nullable_to_non_nullable + as TrackingMediumType, )); } } @@ -103,7 +120,8 @@ class __$$_AnimeListStateCopyWithImpl<$Res> class _$_AnimeListState implements _AnimeListState { _$_AnimeListState( {final List animes = const [], - this.filterState = AnimeTrackingState.watching}) + this.filterState = AnimeTrackingState.watching, + this.trackingType = TrackingMediumType.anime}) : _animes = animes; final List _animes; @@ -117,10 +135,13 @@ class _$_AnimeListState implements _AnimeListState { @override @JsonKey() final AnimeTrackingState filterState; + @override + @JsonKey() + final TrackingMediumType trackingType; @override String toString() { - return 'AnimeListState(animes: $animes, filterState: $filterState)'; + return 'AnimeListState(animes: $animes, filterState: $filterState, trackingType: $trackingType)'; } @override @@ -130,14 +151,17 @@ class _$_AnimeListState implements _AnimeListState { other is _$_AnimeListState && const DeepCollectionEquality().equals(other._animes, _animes) && const DeepCollectionEquality() - .equals(other.filterState, filterState)); + .equals(other.filterState, filterState) && + const DeepCollectionEquality() + .equals(other.trackingType, trackingType)); } @override int get hashCode => Object.hash( runtimeType, const DeepCollectionEquality().hash(_animes), - const DeepCollectionEquality().hash(filterState)); + const DeepCollectionEquality().hash(filterState), + const DeepCollectionEquality().hash(trackingType)); @JsonKey(ignore: true) @override @@ -148,13 +172,16 @@ class _$_AnimeListState implements _AnimeListState { abstract class _AnimeListState implements AnimeListState { factory _AnimeListState( {final List animes, - final AnimeTrackingState filterState}) = _$_AnimeListState; + final AnimeTrackingState filterState, + final TrackingMediumType trackingType}) = _$_AnimeListState; @override List get animes; @override AnimeTrackingState get filterState; @override + TrackingMediumType get trackingType; + @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 a2066a6..3ee321b 100644 --- a/lib/src/ui/bloc/anime_list_event.dart +++ b/lib/src/ui/bloc/anime_list_event.dart @@ -33,3 +33,11 @@ class AnimeFilterChangedEvent extends AnimeListEvent { /// The state to filter final AnimeTrackingState filterState; } + +/// Triggered when the view is changed from the anime or the manga view +class AnimeTrackingTypeChanged extends AnimeListEvent { + AnimeTrackingTypeChanged(this.type); + + /// The type we switched to + final TrackingMediumType type; +} diff --git a/lib/src/ui/bloc/anime_list_state.dart b/lib/src/ui/bloc/anime_list_state.dart index 3734262..e00992e 100644 --- a/lib/src/ui/bloc/anime_list_state.dart +++ b/lib/src/ui/bloc/anime_list_state.dart @@ -5,5 +5,6 @@ class AnimeListState with _$AnimeListState { factory AnimeListState({ @Default([]) List animes, @Default(AnimeTrackingState.watching) AnimeTrackingState filterState, + @Default(TrackingMediumType.anime) TrackingMediumType trackingType, }) = _AnimeListState; } diff --git a/lib/src/ui/pages/anime_list.dart b/lib/src/ui/pages/anime_list.dart index e36b9b7..fc2b218 100644 --- a/lib/src/ui/pages/anime_list.dart +++ b/lib/src/ui/pages/anime_list.dart @@ -1,10 +1,12 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:anitrack/src/data/anime.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/constants.dart'; import 'package:anitrack/src/ui/widgets/anime.dart'; +import 'package:bottom_bar/bottom_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class AnimeListPage extends StatelessWidget { static MaterialPageRoute get route => MaterialPageRoute( @@ -14,13 +16,24 @@ class AnimeListPage extends StatelessWidget { ), ); + final PageController _controller = PageController(); + + String _getPageTitle(TrackingMediumType type) { + switch (type) { + case TrackingMediumType.anime: return 'Anime'; + case TrackingMediumType.manga: return 'Manga'; + } + } + @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { return Scaffold( appBar: AppBar( - title: Text('Animes'), + title: Text( + _getPageTitle(state.trackingType) + ), actions: [ PopupMenuButton( icon: Icon( @@ -57,28 +70,34 @@ class AnimeListPage extends StatelessWidget { ), ], ), - 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(); - } + body: PageView( + controller: _controller, + children: [ + 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: anime, - onLeftSwipe: () { - context.read().add( - AnimeEpisodeDecrementedEvent(state.animes[index].id), + return AnimeListWidget( + data: anime, + onLeftSwipe: () { + context.read().add( + AnimeEpisodeDecrementedEvent(state.animes[index].id), + ); + }, + onRightSwipe: () { + context.read().add( + AnimeEpisodeIncrementedEvent(state.animes[index].id), + ); + }, ); }, - onRightSwipe: () { - context.read().add( - AnimeEpisodeIncrementedEvent(state.animes[index].id), - ); - }, - ); - }, + ), + Placeholder(), + ], ), floatingActionButton: FloatingActionButton( onPressed: () { @@ -89,6 +108,33 @@ class AnimeListPage extends StatelessWidget { tooltip: 'Increment', child: const Icon(Icons.add), ), + bottomNavigationBar: BottomBar( + selectedIndex: state.trackingType == TrackingMediumType.anime ? + 0 : + 1, + onTap: (int index) { + _controller.jumpToPage(index); + context.read().add( + AnimeTrackingTypeChanged( + index == 0 ? + TrackingMediumType.anime : + TrackingMediumType.manga, + ), + ); + }, + items: [ + BottomBarItem( + icon: Icon(Icons.tv), + title: Text('Anime'), + activeColor: Colors.blue, + ), + BottomBarItem( + icon: Icon(Icons.auto_stories), + title: Text('Manga'), + activeColor: Colors.red, + ), + ], + ), ); }, ); diff --git a/pubspec.lock b/pubspec.lock index f14d170..d68c521 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -43,6 +43,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + bottom_bar: + dependency: "direct main" + description: + name: bottom_bar + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" build: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index cfdcfc9..5935f92 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,7 @@ environment: dependencies: bloc: ^8.1.0 + bottom_bar: ^2.0.3 cupertino_icons: ^1.0.2 flutter: sdk: flutter