import 'package:easy_debounce/easy_debounce.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_state.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/blog_post_widget.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/choice_chip_widget.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/diet_filter_bottom_sheet.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/diets_widget.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/filter_badge.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/plan_widget.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/recipes_widget.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/tags_modal_widget.dart'; import 'package:eatvio_app/features/plans/presentation/bloc/plans_cubit/plans_cubit.dart'; import 'package:eatvio_app/features/plans/presentation/bloc/plans_cubit/plans_state.dart'; import 'package:eatvio_app/locator_service.dart'; import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:rive/rive.dart'; final bucket = PageStorageBucket(); class FeedScreen extends StatefulWidget { const FeedScreen({super.key}); @override State createState() => _FeedScreenState(); } class _FeedScreenState extends State { final FeedCubit feedCubit = sl()..loadFeedBlogPostList(); final RecipesCubit recipesCubit = sl()..loadRecipesList(); final DietsCubit dietsCubit = sl()..loadDiets(); final PlansCubit plansCubit = sl()..loadPlans(); final searchController = TextEditingController(); final List chipsLabelList = ['Все', 'Рецепты', 'Диеты', 'Планы питания', 'Консультанты']; int _selectedChipIndex = 0; final blogPostsScrollController = ScrollController(); final recipesScrollController = ScrollController(); final dietsScrollController = ScrollController(); final plansScrollController = ScrollController(); @override void initState() { blogPostsScrollController.addListener(_onScrollBlogPosts); recipesScrollController.addListener(_onScrollRecipes); dietsScrollController.addListener(_onScrollDiets); plansScrollController.addListener(_onScrollPlans); super.initState(); } _onScrollBlogPosts() { if (blogPostsScrollController.offset >= blogPostsScrollController.position.maxScrollExtent && !blogPostsScrollController.position.outOfRange) { feedCubit.loadNextPage(); } } _onScrollRecipes() { if (recipesScrollController.offset >= recipesScrollController.position.maxScrollExtent && !recipesScrollController.position.outOfRange) { recipesCubit.loadNextPage(); } } _onScrollDiets() { if (dietsScrollController.offset >= dietsScrollController.position.maxScrollExtent && !dietsScrollController.position.outOfRange) { dietsCubit.loadNextPage(); } } _onScrollPlans() { if (plansScrollController.offset >= plansScrollController.position.maxScrollExtent && !plansScrollController.position.outOfRange) { // plansCubit.loadNextPage(); } } @override void dispose() { blogPostsScrollController.dispose(); recipesScrollController.dispose(); dietsScrollController.dispose(); plansScrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: _selectedChipIndex == 1 ? FloatingActionButton.small( heroTag: 'recipesTagsFilter', backgroundColor: appTheme.blueA400, shape: CircleBorder(), child: Icon( Icons.filter_list_rounded, color: Colors.white, ), onPressed: () async { await showModalBottomSheet( useRootNavigator: true, context: context, builder: (context) => TagsModalWidget( recipesCubit: recipesCubit, ), ); recipesCubit.loadRecipesList(); }, ) : null, appBar: AppBar( backgroundColor: Colors.white, toolbarHeight: 0, ), backgroundColor: Colors.grey.shade200, body: NestedScrollView( controller: _selectedChipIndex == 0 ? blogPostsScrollController : _selectedChipIndex == 1 ? recipesScrollController : dietsScrollController, headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return [ SliverAppBar( shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)), ), backgroundColor: Colors.white, foregroundColor: Colors.white, automaticallyImplyLeading: false, stretchTriggerOffset: 1, floating: true, snap: true, scrolledUnderElevation: 0, bottom: _chipGroup(), title: Row( children: [ _searchBar(), SizedBox(width: 12), _addBlogPostButton(), ], ), ), ]; }, body: Builder( builder: (BuildContext context) { if (_selectedChipIndex == 0) { return _buildAllBlogPosts(); } else if (_selectedChipIndex == 1) { return _buildAllRecipes(); } else if (_selectedChipIndex == 2) { return _buildDiets(); } else if (_selectedChipIndex == 3) { return _buildPlans(); } else { return Center(child: Text(chipsLabelList[_selectedChipIndex])); } }, ), ), ); } _buildAllBlogPosts() { return BlocProvider( create: (context) => feedCubit, child: RefreshIndicator( onRefresh: () => feedCubit.loadFeedBlogPostList(), child: BlocBuilder( builder: (BuildContext context, FeedState state) { if (state is FeedStateLoaded) { return ListView.separated( padding: EdgeInsets.symmetric(vertical: 8), shrinkWrap: true, physics: BouncingScrollPhysics(), itemBuilder: (context, index) { if (index == state.feedBlogPostList.length) { return Container( alignment: Alignment.center, child: SizedBox( height: 32, width: 32, child: RiveAnimation.asset( 'assets/rive_animations/loading.riv', ), ), ); } return BlogPostWidget(feedBlogPostModel: state.feedBlogPostList[index]); }, separatorBuilder: (context, index) { return SizedBox(height: 8); }, itemCount: state.isLoading ? state.feedBlogPostList.length + 1 : state.feedBlogPostList.length, ); } else if (state is FeedStateError) { return Scaffold( backgroundColor: Colors.grey.shade200, body: Padding( padding: const EdgeInsets.all(16), child: Center( child: Text(state.errorMessage), ), ), ); } else { return Scaffold( backgroundColor: Colors.grey.shade200, body: Center( child: SizedBox( height: 32, width: 32, child: RiveAnimation.asset( 'assets/rive_animations/loading.riv', ), ), ), ); } }, ), ), ); } _buildAllRecipes() { return BlocProvider( create: (context) => recipesCubit, child: RefreshIndicator( onRefresh: () => recipesCubit.loadRecipesList(), child: BlocBuilder( builder: (BuildContext context, RecipesState state) { if (state is RecipesStateLoaded) { return ListView.separated( padding: EdgeInsets.symmetric(vertical: 8), shrinkWrap: true, physics: BouncingScrollPhysics(), itemBuilder: (context, index) { return RecipesWidget(recipes: state.recipesList[index]); }, separatorBuilder: (context, index) { return SizedBox(height: 8); }, itemCount: state.recipesList.length, ); } else if (state is RecipesStateError) { return Scaffold( backgroundColor: Colors.grey.shade200, body: Padding( padding: const EdgeInsets.all(16), child: Center( child: Text(state.errorMessage), ), ), ); } else { return Scaffold( backgroundColor: Colors.grey.shade200, body: Center( child: SizedBox( height: 32, width: 32, child: RiveAnimation.asset( 'assets/rive_animations/loading.riv', ), ), ), ); } }, ), ), ); } _buildDiets() { return BlocProvider( create: (context) => dietsCubit, child: RefreshIndicator( onRefresh: () => dietsCubit.loadDiets(), child: BlocBuilder( builder: (BuildContext context, DietsState state) { if (state is DietsStateLoaded) { return Stack( children: [ ListView.separated( padding: EdgeInsets.symmetric(vertical: 8), shrinkWrap: true, physics: BouncingScrollPhysics(), itemBuilder: (context, index) { return DietsWidget(diet: state.dietsList[index], index: index); }, separatorBuilder: (context, index) { return SizedBox(height: 8); }, itemCount: state.dietsList.length, ), Positioned( right: 0, bottom: 74, child: FilterBadge( filterBottomSheet: DietFilterBottomSheet(), ), ), ], ); } else if (state is DietsStateError) { return Scaffold( backgroundColor: Colors.grey.shade200, body: Padding( padding: const EdgeInsets.all(16), child: Center( child: Text(state.errorMessage), ), ), ); } else { return Scaffold( backgroundColor: Colors.grey.shade200, body: Center( child: SizedBox( height: 32, width: 32, child: RiveAnimation.asset( 'assets/rive_animations/loading.riv', ), ), ), ); } }, ), ), ); } _buildPlans() { return BlocProvider( create: (context) => plansCubit, child: RefreshIndicator( onRefresh: () => plansCubit.loadPlans(), child: BlocBuilder( builder: (BuildContext context, PlansState state) { if (state is PlansStateLoaded) { return ListView.separated( padding: EdgeInsets.symmetric(vertical: 8), shrinkWrap: true, physics: BouncingScrollPhysics(), itemBuilder: (context, index) { return PlanWidget(planModel: state.plansModel.data[index]); }, separatorBuilder: (context, index) { return SizedBox(height: 8); }, itemCount: state.plansModel.data.length, ); } else if (state is PlansStateError) { return Scaffold( backgroundColor: Colors.grey.shade200, body: Padding( padding: const EdgeInsets.all(16), child: Center( child: Text(state.errorMessage), ), ), ); } else { return Scaffold( backgroundColor: Colors.grey.shade200, body: Center( child: SizedBox( height: 32, width: 32, child: RiveAnimation.asset( 'assets/rive_animations/loading.riv', ), ), ), ); } }, ), ), ); } _chipGroup() { return PreferredSize( preferredSize: Size(MediaQuery.of(context).size.width, 32), child: Align( alignment: Alignment.centerLeft, child: SingleChildScrollView( padding: EdgeInsets.symmetric(horizontal: 16), scrollDirection: Axis.horizontal, child: Row( children: [ ChoiceChipWidget( index: 0, selectedChipIndex: _selectedChipIndex, chipsLabelList: chipsLabelList, onSelected: (bool selected) { setState(() { _selectedChipIndex = 0; searchController.clear(); FocusScope.of(context).requestFocus(new FocusNode()); }); }, ), SizedBox(width: 8), ChoiceChipWidget( index: 1, selectedChipIndex: _selectedChipIndex, chipsLabelList: chipsLabelList, onSelected: (bool selected) { setState(() { _selectedChipIndex = 1; searchController.clear(); FocusScope.of(context).requestFocus(new FocusNode()); }); }, ), SizedBox(width: 8), ChoiceChipWidget( index: 2, selectedChipIndex: _selectedChipIndex, chipsLabelList: chipsLabelList, onSelected: (bool selected) { setState(() { _selectedChipIndex = 2; searchController.clear(); FocusScope.of(context).requestFocus(new FocusNode()); }); }, ), SizedBox(width: 8), ChoiceChipWidget( index: 3, selectedChipIndex: _selectedChipIndex, chipsLabelList: chipsLabelList, onSelected: (bool selected) { setState(() { _selectedChipIndex = 3; searchController.clear(); FocusScope.of(context).requestFocus(new FocusNode()); }); }, ), SizedBox(width: 8), ChoiceChipWidget( index: 4, selectedChipIndex: _selectedChipIndex, chipsLabelList: chipsLabelList, onSelected: (bool selected) { setState(() { _selectedChipIndex = 4; searchController.clear(); FocusScope.of(context).requestFocus(new FocusNode()); }); }, ), ], ), ), ), ); } _addBlogPostButton() { return InkWell( onTap: () => GoRouter.of(context).pushNamed('blogPostCreateScreen'), borderRadius: BorderRadius.circular(64), child: Ink( decoration: BoxDecoration( color: appTheme.blueA400, shape: BoxShape.circle, ), child: Icon( Icons.add_rounded, color: appTheme.gray100, ), width: 40, height: 40, ), ); } _searchBar() { return SizedBox( height: 40, width: MediaQuery.of(context).size.width - 84, child: TextFormField( controller: searchController, style: TextStyle( fontSize: 12, ), onChanged: (query) { if (query.length > 2) { EasyDebounce.debounce( 'searchFeed', Duration(milliseconds: 500), () { switch (_selectedChipIndex) { case 0: feedCubit.loadFeedBlogPostList(query: query); break; case 1: recipesCubit.loadRecipesList(query: query); break; case 2: dietsCubit.loadDiets(query: query); break; } }, ); } else { EasyDebounce.debounce( 'searchFeed', Duration(milliseconds: 500), () { switch (_selectedChipIndex) { case 0: feedCubit.loadFeedBlogPostList(); break; case 1: recipesCubit.loadRecipesList(); break; case 2: dietsCubit.loadDiets(query: query); break; } }, ); } }, decoration: InputDecoration( hintText: 'Поиск', hintStyle: TextStyle( fontSize: 12, color: appTheme.gray600, fontWeight: FontWeight.w400, ), prefixIcon: Icon( Icons.search, color: appTheme.gray600, size: 16, ), isDense: false, contentPadding: EdgeInsets.all(8), fillColor: appTheme.gray100, filled: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none, ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none, ), ), ), ); } }