import 'package:anitrack/i18n/strings.g.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/bloc/details_bloc.dart'; import 'package:anitrack/src/ui/constants.dart'; import 'package:anitrack/src/ui/helpers.dart'; import 'package:anitrack/src/ui/widgets/grid_item.dart'; import 'package:anitrack/src/ui/widgets/image.dart'; import 'package:bottom_bar/bottom_bar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; class AnimeListPage extends StatefulWidget { const AnimeListPage({ super.key, }); static MaterialPageRoute get route => MaterialPageRoute( builder: (_) => const AnimeListPage(), settings: const RouteSettings( name: animeListRoute, ), ); @override AnimeListPageState createState() => AnimeListPageState(); } class AnimeListPageState extends State { final PageController _controller = PageController(); final ScrollController _animeScrollController = ScrollController(); @override void initState() { super.initState(); _animeScrollController.addListener(_onAnimeListScrolled); } void _onAnimeListScrolled() { //print(_animeScrollController.position.maxScrollExtent); final bloc = GetIt.I.get(); if (_animeScrollController.offset + 20 >= _animeScrollController.position.maxScrollExtent) { if (bloc.state.buttonVisibility) { bloc.add( AddButtonVisibilitySetEvent(false), ); } } else { if (!bloc.state.buttonVisibility) { bloc.add( AddButtonVisibilitySetEvent(true), ); } } } String _getPageTitle(TrackingMediumType type) { switch (type) { case TrackingMediumType.anime: return t.content.anime; case TrackingMediumType.manga: return t.content.manga; } } List> _getPopupButtonItems( TrackingMediumType type, ) { return [ PopupMenuItem( value: MediumTrackingState.ongoing, child: Text(MediumTrackingState.ongoing.getName(type)), ), PopupMenuItem( value: MediumTrackingState.completed, child: Text(MediumTrackingState.completed.getName(type)), ), PopupMenuItem( value: MediumTrackingState.planned, child: Text(MediumTrackingState.planned.getName(type)), ), PopupMenuItem( value: MediumTrackingState.dropped, child: Text(MediumTrackingState.dropped.getName(type)), ), PopupMenuItem( value: MediumTrackingState.paused, child: Text(MediumTrackingState.paused.getName(type)), ), const PopupMenuItem( value: MediumTrackingState.all, child: Text('All'), ), ]; } Widget _getPopupButton(BuildContext context, AnimeListState state) { switch (state.trackingType) { case TrackingMediumType.anime: return PopupMenuButton( icon: const Icon( Icons.filter_list, ), initialValue: state.animeFilterState, onSelected: (filterState) { context.read().add( AnimeFilterChangedEvent(filterState), ); }, itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.anime), ); case TrackingMediumType.manga: return PopupMenuButton( icon: const Icon( Icons.filter_list, ), initialValue: state.mangaFilterState, onSelected: (filterState) { context.read().add( MangaFilterChangedEvent(filterState), ); }, itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.manga), ); } } @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { return Scaffold( appBar: AppBar( title: Text( _getPageTitle(state.trackingType), ), actions: [ _getPopupButton(context, state), ], ), drawer: getDrawer(context), body: PageView( // Prevent swiping between pages // (https://github.com/flutter/flutter/issues/37510#issuecomment-612663656) physics: const NeverScrollableScrollPhysics(), controller: _controller, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisSpacing: 8, crossAxisSpacing: 8, childAspectRatio: 120 / (100 * (16 / 9)), ), itemCount: state.animes.length, controller: _animeScrollController, itemBuilder: (context, index) { final anime = state.animes[index]; return GridItem( minusCallback: () { context.read().add( AnimeEpisodeDecrementedEvent( anime.id, ), ); }, plusCallback: () { context.read().add( AnimeEpisodeIncrementedEvent( anime.id, ), ); }, child: AnimeCoverImage( url: anime.thumbnailUrl, hero: anime.id, onTap: () { context.read().add( AnimeDetailsRequestedEvent(anime), ); }, extra: Align( alignment: Alignment.centerRight, child: Padding( padding: const EdgeInsets.only(right: 8), child: Text( '${anime.episodesWatched}/${anime.episodesTotal ?? "???"}', style: Theme.of(context).textTheme.titleMedium, ), ), ), ), ); }, ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisSpacing: 8, crossAxisSpacing: 8, childAspectRatio: 120 / (100 * (16 / 9)), ), itemCount: state.mangas.length, itemBuilder: (context, index) { final manga = state.mangas[index]; return GridItem( minusCallback: () { context.read().add( MangaChapterDecrementedEvent( manga.id, ), ); }, plusCallback: () { context.read().add( MangaChapterIncrementedEvent( manga.id, ), ); }, child: AnimeCoverImage( hero: manga.id, url: manga.thumbnailUrl, onTap: () { context.read().add( MangaDetailsRequestedEvent(manga), ); }, extra: Align( alignment: Alignment.centerRight, child: Padding( padding: const EdgeInsets.only(right: 8), child: Text( '${manga.chaptersRead}/${manga.chaptersTotal ?? "???"}', style: Theme.of(context).textTheme.titleMedium, ), ), ), ), ); }, ), ), ], ), floatingActionButton: BlocBuilder( buildWhen: (prev, next) => prev.buttonVisibility != next.buttonVisibility, builder: (context, state) { return AnimatedScale( duration: const Duration(milliseconds: 250), scale: state.buttonVisibility ? 1 : 0, curve: Curves.easeInOutQuint, child: FloatingActionButton( onPressed: () { context.read().add( AnimeSearchRequestedEvent(state.trackingType), ); }, tooltip: t.tooltips.addNewItem, child: const Icon(Icons.add), ), ); }, ), bottomNavigationBar: BottomBar( selectedIndex: state.trackingType == TrackingMediumType.anime ? 0 : 1, onTap: (int index) { context.read().add( AnimeTrackingTypeChanged( index == 0 ? TrackingMediumType.anime : TrackingMediumType.manga, ), ); _controller.jumpToPage(index); }, items: [ BottomBarItem( icon: const Icon(Icons.tv), title: Text(t.content.anime), activeColor: Colors.blue, ), BottomBarItem( icon: const Icon(Icons.auto_stories), title: Text(t.content.manga), activeColor: Colors.red, ), ], ), ); }, ); } }