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

315 lines
15 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, this.initialTabIndex = 0});
final int? initialTabIndex;
@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: FeedScreenContent(initialTabIndex: initialTabIndex),
);
}
}
class FeedScreenContent extends StatefulWidget {
const FeedScreenContent({super.key, required this.initialTabIndex});
final int? initialTabIndex;
@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;
late int _selectedChipIndex;
final blogPostsScrollController = ScrollController();
final recipesScrollController = ScrollController();
final dietsScrollController = ScrollController();
final plansScrollController = ScrollController();
final consultantsScrollController = ScrollController();
@override
void initState() {
_selectedChipIndex = widget.initialTabIndex ?? 0;
chipsLabelList = hidePlans ? ['Все', 'Консультанты', 'Рецепты', 'Диеты'] : ['Все', 'Планы питания', 'Консультанты', 'Рецепты', 'Диеты'];
consultantsScrollController.addListener(_onScrollConsultants);
_checkRegistrationDate(context);
// Инициализация глобальных кубитов при первом открытии экрана
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
final feedCubit = context.read<FeedCubit>();
final state = feedCubit.state;
// Загружаем данные, если они еще не загружены
if (state is! FeedStateLoaded) {
feedCubit.loadFeedBlogPostList();
}
});
super.initState();
}
@override
void didUpdateWidget(FeedScreenContent oldWidget) {
super.didUpdateWidget(oldWidget);
final initialTabIndex = widget.initialTabIndex;
setState(() {
if (initialTabIndex != null) {
_selectedChipIndex = initialTabIndex;
}
});
}
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 == 2 && hidePlans) || (_selectedChipIndex == 3 && !hidePlans))
? 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 when !hidePlans => plansScrollController,
1 when hidePlans => consultantsScrollController,
2 when !hidePlans => consultantsScrollController,
2 when hidePlans => recipesScrollController,
3 when !hidePlans => recipesScrollController,
3 when hidePlans => dietsScrollController,
4 when !hidePlans => dietsScrollController,
_ => 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 when !hidePlans => plansController,
1 when hidePlans => consultantsController,
2 when !hidePlans => consultantsController,
2 when hidePlans => recipesController,
3 when !hidePlans => recipesController,
3 when hidePlans => dietsController,
4 when !hidePlans => dietsController,
_ => 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 when !hidePlans:
context.read<PlansCubit>().loadPlans(query: query);
break;
case 1 when hidePlans:
case 2 when !hidePlans:
context.read<ConsultantsCubit>().getConsultants();
break;
case 2 when hidePlans:
case 3 when !hidePlans:
context.read<RecipesCubit>().loadRecipesList(query: query);
break;
case 3 when hidePlans:
case 4 when !hidePlans:
context.read<DietsCubit>().loadDiets(query: query);
break;
}
});
} else {
EasyDebounce.debounce('searchFeed', const Duration(milliseconds: 500), () {
switch (_selectedChipIndex) {
case 0:
context.read<FeedCubit>().loadFeedBlogPostList();
break;
case 1 when !hidePlans:
context.read<PlansCubit>().loadPlans();
break;
case 1 when hidePlans:
case 2 when !hidePlans:
context.read<ConsultantsCubit>().getConsultants();
break;
case 2 when hidePlans:
case 3 when !hidePlans:
context.read<RecipesCubit>().loadRecipesList();
break;
case 3 when hidePlans:
case 4 when !hidePlans:
context.read<DietsCubit>().loadDiets();
break;
}
});
}
},
),
const SizedBox(width: 12),
switch (_selectedChipIndex) {
1 when !hidePlans => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false ? () => GoRouter.of(context).pushNamed(CreatePlanScreen.name) : null,
),
2 when hidePlans => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false ? () => GoRouter.of(context).pushNamed(RecipeCreateScreen.name) : null,
),
3 when !hidePlans => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false ? () => GoRouter.of(context).pushNamed(RecipeCreateScreen.name) : null,
),
3 when hidePlans => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false
? () async {
await GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: {'isEditing': false});
}
: null,
),
4 when !hidePlans => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false
? () async {
await GoRouter.of(context).pushNamed(CreateDietScreen.name);
}
: null,
),
_ => const AddItemButton(key: AppKeys.feedScreenBlogPostCreationButton),
},
],
),
);
},
),
];
},
body: switch (_selectedChipIndex) {
0 => BlogPosts(handleVisibility: context.read<ViewsCubit>().handleVisibility),
1 when !hidePlans => Plans(handleVisibility: context.read<ViewsCubit>().handleVisibility),
1 when hidePlans => const Consultants(),
2 when !hidePlans => const Consultants(),
2 when hidePlans => Recipes(handleVisibility: context.read<ViewsCubit>().handleVisibility),
3 when !hidePlans => Recipes(handleVisibility: context.read<ViewsCubit>().handleVisibility),
3 when hidePlans => Diets(handleVisibility: context.read<ViewsCubit>().handleVisibility),
4 when !hidePlans => Diets(handleVisibility: context.read<ViewsCubit>().handleVisibility),
_ => 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',
};
}