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/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'; class AnimeListPage extends StatelessWidget { AnimeListPage({ super.key, }); static MaterialPageRoute get route => MaterialPageRoute( builder: (_) => AnimeListPage(), settings: const RouteSettings( name: animeListRoute, ), ); final PageController _controller = PageController(); String _getPageTitle(TrackingMediumType type) { switch (type) { case TrackingMediumType.anime: return 'Anime'; case TrackingMediumType.manga: return 'Manga'; } } List> _getPopupButtonItems(TrackingMediumType type) { return [ PopupMenuItem( value: MediumTrackingState.ongoing, child: Text(MediumTrackingState.ongoing.toNameString(type)), ), PopupMenuItem( value: MediumTrackingState.completed, child: Text(MediumTrackingState.completed.toNameString(type)), ), PopupMenuItem( value: MediumTrackingState.planned, child: Text(MediumTrackingState.planned.toNameString(type)), ), PopupMenuItem( value: MediumTrackingState.dropped, child: Text(MediumTrackingState.dropped.toNameString(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: Drawer( child: ListView( children: [ const DrawerHeader( decoration: BoxDecoration( color: Color(0xffcf4aff), ), child: Text( 'AniTrack', style: TextStyle( color: Color(0xff232323), fontSize: 24, ), ), ), ListTile( leading: const Icon(Icons.info), title: const Text('About'), onTap: () { Navigator.of(context).pushNamed(aboutRoute); }, ), ], ), ), 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, 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: FloatingActionButton( onPressed: () { context.read().add( AnimeSearchRequestedEvent(state.trackingType), ); }, tooltip: 'Increment', 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: const [ BottomBarItem( icon: Icon(Icons.tv), title: Text('Anime'), activeColor: Colors.blue, ), BottomBarItem( icon: Icon(Icons.auto_stories), title: Text('Manga'), activeColor: Colors.red, ), ], ), ); }, ); } }