Files
eatvio_app/lib/features/feed/presentation/pages/feed_screen.dart
2025-01-23 14:21:05 +07:00

334 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/core/widgets/add_item_button.dart';
import 'package:eatvio_app/core/widgets/custom_search_bar.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/dev_tools/presentation/dev_tools_screen.dart';
import 'package:eatvio_app/features/dev_tools/test_cases.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/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/choice_chip_widget.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:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import '../widgets/recipes.dart';
final bucket = PageStorageBucket();
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<FeedCubit>(
create: (context) => sl<FeedCubit>()..loadFeedBlogPostList(),
),
BlocProvider<DietsCubit>(
create: (context) => sl<DietsCubit>()..loadDiets(),
),
BlocProvider<PlansCubit>(
create: (context) => sl<PlansCubit>()..loadPlans(),
),
],
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 List<String> 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) {
context.read<FeedCubit>().loadNextPage();
}
}
_onScrollRecipes() {
if (recipesScrollController.offset >= recipesScrollController.position.maxScrollExtent && !recipesScrollController.position.outOfRange) {
context.read<RecipesCubit>().loadNextPage();
}
}
_onScrollDiets() {
if (dietsScrollController.offset >= dietsScrollController.position.maxScrollExtent && !dietsScrollController.position.outOfRange) {
context.read<DietsCubit>().loadNextPage();
}
}
_onScrollPlans() {
if (plansScrollController.offset >= plansScrollController.position.maxScrollExtent && !plansScrollController.position.outOfRange) {
context.read<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 {
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 => plansScrollController,
_ => blogPostsScrollController
},
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
BlocBuilder<FeedProfileCubit, ProfileState>(
builder: (context, state) {
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: [
CustomSearchBar(
searchController: switch (_selectedChipIndex) {
1 => recipesController,
2 => dietsController,
3 => plansController,
_ => postsController,
},
onChanged: (query) {
if (query.length > 2) {
EasyDebounce.debounce(
'searchFeed',
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:
context.read<PlansCubit>().loadPlans(query: query);
break;
}
},
);
} else {
EasyDebounce.debounce(
'searchFeed',
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:
context.read<PlansCubit>().loadPlans();
break;
}
},
);
}
},
),
SizedBox(width: 12),
switch (_selectedChipIndex) {
1 => AddItemButton(
onTap: () => GoRouter.of(context).pushNamed('recipeCreateScreen'),
),
2 => AddItemButton(
onTap: state.profile?.role == 'consultant' ? () => GoRouter.of(context).pushNamed(CreateDietScreen.name) : null,
),
3 => AddItemButton(
onTap: state.profile?.role == 'consultant' ? () => GoRouter.of(context).pushNamed(CreatePlanScreen.name) : null,
),
_ => AddItemButton()
},
],
),
);
},
),
];
},
body: switch (_selectedChipIndex) {
0 => BlogPosts(),
1 => Recipes(),
2 => Diets(),
3 => Plans(),
_ => Center(child: Text(chipsLabelList[_selectedChipIndex])),
},
),
);
}
_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;
FocusScope.of(context).requestFocus(new FocusNode());
});
},
),
SizedBox(width: 8),
ChoiceChipWidget(
index: 1,
selectedChipIndex: _selectedChipIndex,
chipsLabelList: chipsLabelList,
onSelected: (bool selected) {
setState(() {
_selectedChipIndex = 1;
FocusScope.of(context).requestFocus(new FocusNode());
});
},
),
SizedBox(width: 8),
ChoiceChipWidget(
index: 2,
selectedChipIndex: _selectedChipIndex,
chipsLabelList: chipsLabelList,
onSelected: (bool selected) {
setState(() {
_selectedChipIndex = 2;
FocusScope.of(context).requestFocus(new FocusNode());
});
},
),
SizedBox(width: 8),
ChoiceChipWidget(
index: 3,
selectedChipIndex: _selectedChipIndex,
chipsLabelList: chipsLabelList,
onSelected: (bool selected) {
setState(() {
_selectedChipIndex = 3;
FocusScope.of(context).requestFocus(new FocusNode());
});
},
),
SizedBox(width: 8),
ChoiceChipWidget(
index: 4,
selectedChipIndex: _selectedChipIndex,
chipsLabelList: chipsLabelList,
onSelected: (bool selected) {
setState(() {
_selectedChipIndex = 4;
FocusScope.of(context).requestFocus(new FocusNode());
});
},
),
],
),
),
),
);
}
}