Files
eatvio_app/lib/features/feed/presentation/pages/feed_screen.dart

279 lines
13 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/app_keys.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/cubit/marketing/marketing_cubit.dart';
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
import 'package:eatvio_app/core/widgets/add_item_button.dart';
import 'package:eatvio_app/core/widgets/custom_search_bar.dart';
import 'package:eatvio_app/core/widgets/marketing_bottom_sheet.dart';
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
import 'package:eatvio_app/features/create_plan/presentation/create_plan_screen.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.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/profile_cubit/profile_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_state.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/blog_posts.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/chip_group.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/consultants.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/diets.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/plans.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/tags_modal_widget.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:go_router/go_router.dart';
import '../widgets/recipes.dart';
class FeedScreen extends StatelessWidget {
const FeedScreen({super.key});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<RecipesCubit>(create: (context) => sl<RecipesCubit>()..loadRecipesList()),
BlocProvider<FeedProfileCubit>(create: (context) => sl<FeedProfileCubit>()..getProfile()),
BlocProvider<DietsCubit>(create: (context) => sl<DietsCubit>()..loadDiets()),
if (!hidePlans) BlocProvider<PlansCubit>(create: (context) => sl<PlansCubit>()..loadPlans()),
BlocProvider<ConsultantsCubit>(create: (context) => sl<ConsultantsCubit>()..getConsultants()),
],
child: const FeedScreenContent(),
);
}
}
class FeedScreenContent extends StatefulWidget {
const FeedScreenContent({super.key});
@override
State<FeedScreenContent> createState() => _FeedScreenContentState();
}
class _FeedScreenContentState extends State<FeedScreenContent> {
final postsController = TextEditingController();
final recipesController = TextEditingController();
final dietsController = TextEditingController();
final plansController = TextEditingController();
final consultantsController = TextEditingController();
late final List<String> chipsLabelList;
int _selectedChipIndex = 0;
final blogPostsScrollController = ScrollController();
final recipesScrollController = ScrollController();
final dietsScrollController = ScrollController();
final plansScrollController = ScrollController();
final consultantsScrollController = ScrollController();
@override
void initState() {
chipsLabelList = hidePlans ? ['Все', 'Рецепты', 'Диеты', 'Консультанты'] : ['Все', 'Рецепты', 'Диеты', 'Планы питания', 'Консультанты'];
consultantsScrollController.addListener(_onScrollConsultants);
_checkRegistrationDate(context);
// Инициализация глобальных кубитов при первом открытии экрана
WidgetsBinding.instance.addPostFrameCallback((_) {
final feedCubit = context.read<FeedCubit>();
final state = feedCubit.state;
if (state is! FeedStateLoaded && state is! FeedStateLoading) {
feedCubit.loadFeedBlogPostList();
}
});
super.initState();
}
void _onScrollConsultants() {
if (consultantsScrollController.offset >= consultantsScrollController.position.maxScrollExtent && !consultantsScrollController.position.outOfRange) {}
}
void _checkRegistrationDate(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
final needToShowMarketingBottomSheet = await context.read<MarketingCubit>().checkRegistrationDate();
if (needToShowMarketingBottomSheet && context.mounted) {
showMarketingBottomSheet(context);
}
});
}
@override
void dispose() {
blogPostsScrollController.dispose();
recipesScrollController.dispose();
dietsScrollController.dispose();
plansScrollController.dispose();
consultantsScrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
FlutterNativeSplash.remove();
return Scaffold(
floatingActionButton: _selectedChipIndex == 1
? FloatingActionButton.small(
heroTag: 'recipesTagsFilter',
backgroundColor: appTheme.blueA400,
shape: const CircleBorder(),
child: const Icon(Icons.filter_list_rounded, color: Colors.white),
onPressed: () async {
final recipesCubit = context.read<RecipesCubit>();
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: switch (_selectedChipIndex) {
0 => blogPostsScrollController,
1 => recipesScrollController,
2 => dietsScrollController,
3 when !hidePlans => plansScrollController,
3 when hidePlans => consultantsScrollController,
4 when !hidePlans => consultantsScrollController,
_ => blogPostsScrollController,
},
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
BlocBuilder<FeedProfileCubit, ProfileState>(
builder: (context, state) {
return SliverAppBar(
shape: const 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: PreferredSize(
preferredSize: Size(MediaQuery.of(context).size.width, 32),
child: ChipGroup(
selectedChipIndex: _selectedChipIndex,
chipsLabelList: chipsLabelList,
setSelectedChipIndex: (index) {
setState(() {
_selectedChipIndex = index;
});
},
),
),
title: Row(
children: [
CustomSearchBar(
searchController: switch (_selectedChipIndex) {
1 => recipesController,
2 => dietsController,
3 when !hidePlans => plansController,
3 when hidePlans => consultantsController,
4 when !hidePlans => consultantsController,
_ => postsController,
},
onChanged: (query) {
if (query.length > 2) {
EasyDebounce.debounce('searchFeed', const Duration(milliseconds: 500), () {
switch (_selectedChipIndex) {
case 0:
context.read<FeedCubit>().loadFeedBlogPostList(query: query);
break;
case 1:
context.read<RecipesCubit>().loadRecipesList(query: query);
break;
case 2:
context.read<DietsCubit>().loadDiets(query: query);
break;
case 3 when !hidePlans:
context.read<PlansCubit>().loadPlans(query: query);
break;
case 3 when hidePlans:
case 4 when !hidePlans:
context.read<ConsultantsCubit>().getConsultants();
break;
}
});
} else {
EasyDebounce.debounce('searchFeed', const Duration(milliseconds: 500), () {
switch (_selectedChipIndex) {
case 0:
context.read<FeedCubit>().loadFeedBlogPostList();
break;
case 1:
context.read<RecipesCubit>().loadRecipesList();
break;
case 2:
context.read<DietsCubit>().loadDiets();
break;
case 3 when !hidePlans:
context.read<PlansCubit>().loadPlans();
break;
case 3 when hidePlans:
case 4 when !hidePlans:
context.read<ConsultantsCubit>().getConsultants();
break;
}
});
}
},
),
const SizedBox(width: 12),
switch (_selectedChipIndex) {
1 => AddItemButton(onTap: () => GoRouter.of(context).pushNamed(RecipeCreateScreen.name)),
2 => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false
? () async {
await GoRouter.of(context).pushNamed(CreateDietScreen.name);
}
: null,
),
3 when !hidePlans => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false ? () => GoRouter.of(context).pushNamed(CreatePlanScreen.name) : null,
),
_ => const AddItemButton(key: AppKeys.feedScreenBlogPostCreationButton),
},
],
),
);
},
),
];
},
body: switch (_selectedChipIndex) {
0 => BlogPosts(handleVisibility: context.read<ViewsCubit>().handleVisibility),
1 => Recipes(handleVisibility: context.read<ViewsCubit>().handleVisibility),
2 => Diets(handleVisibility: context.read<ViewsCubit>().handleVisibility),
3 when !hidePlans => Plans(handleVisibility: context.read<ViewsCubit>().handleVisibility),
3 when hidePlans => const Consultants(),
4 when !hidePlans => const Consultants(),
_ => Center(child: Text(chipsLabelList[_selectedChipIndex])),
},
),
);
}
}
enum ViewableContentType {
plans,
posts,
diets,
recipes;
String getStringifiedVersion() => switch (this) {
ViewableContentType.plans => 'plans',
ViewableContentType.posts => 'blog_posts',
ViewableContentType.diets => 'diets',
ViewableContentType.recipes => 'recipes',
};
}