forked from eatvio/eatvio_app
my plans and my diets
This commit is contained in:
@@ -22,6 +22,8 @@ import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/pages/menu_screen.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/pages/notifications_screen.dart';
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
|
||||
@@ -116,7 +118,7 @@ class AppRouter {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
return AboutPlanScreen(
|
||||
plan: data['plan'] as PlanModel,
|
||||
index: data['index'] as int,
|
||||
index: data['index'] as int?,
|
||||
navigateToComments: data['navigateToComments'] as bool,
|
||||
);
|
||||
},
|
||||
@@ -162,7 +164,7 @@ class AppRouter {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
return AboutDietScreen(
|
||||
diet: data['diet'] as Diet,
|
||||
index: data['index'] as int,
|
||||
index: data['index'] as int?,
|
||||
navigateToComments: data['navigateToComments'] as bool,
|
||||
);
|
||||
},
|
||||
@@ -290,6 +292,16 @@ class AppRouter {
|
||||
name: CreatePlanScreen.name,
|
||||
builder: (context, state) => CreatePlanScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: MyDietsScreen.path,
|
||||
name: MyDietsScreen.name,
|
||||
builder: (context, state) => MyDietsScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: MyPlansScreen.path,
|
||||
name: MyPlansScreen.name,
|
||||
builder: (context, state) => MyPlansScreen(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,12 @@ import 'package:eatvio_app/features/feed/services/feed_service.dart';
|
||||
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
||||
import 'package:eatvio_app/features/glossary/services/glossary_service.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_diets/repositories/my_diets_repository.dart';
|
||||
import 'package:eatvio_app/features/my_diets/services/my_diets_service.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_plans/repositories/my_diets_repository.dart';
|
||||
import 'package:eatvio_app/features/my_plans/services/my_plans_service.dart';
|
||||
import 'package:eatvio_app/features/plans/repositories/plans_repository.dart';
|
||||
import 'package:eatvio_app/features/plans/services/plans_service.dart';
|
||||
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
|
||||
@@ -90,6 +96,8 @@ Future<void> init() async {
|
||||
sl.registerFactory<CreatePlanCubit>(() => CreatePlanCubit());
|
||||
sl.registerFactory<FeedProfileCubit>(() => FeedProfileCubit(profileRepository: sl()));
|
||||
sl.registerFactory<AboutPlanCubit>(() => AboutPlanCubit(planRepository: sl()));
|
||||
sl.registerFactory<MyDietsCubit>(() => MyDietsCubit(myDietsRepository: sl()));
|
||||
sl.registerFactory<MyPlansCubit>(() => MyPlansCubit(myPlansRepository: sl()));
|
||||
|
||||
// Repository
|
||||
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
|
||||
@@ -107,6 +115,8 @@ Future<void> init() async {
|
||||
sl.registerLazySingleton<CreateDietRepository>(() => CreateDietRepository(createDietService: sl()));
|
||||
sl.registerLazySingleton<PlansRepository>(() => PlansRepository(plansService: sl()));
|
||||
sl.registerLazySingleton<PlanRepository>(() => PlanRepository(planService: sl()));
|
||||
sl.registerLazySingleton<MyDietsRepository>(() => MyDietsRepository(myDietsService: sl()));
|
||||
sl.registerLazySingleton<MyPlansRepository>(() => MyPlansRepository(myPlansService: sl()));
|
||||
|
||||
// Services
|
||||
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
|
||||
@@ -124,6 +134,8 @@ Future<void> init() async {
|
||||
sl.registerLazySingleton<CreateDietService>(() => CreateDietService(sl()));
|
||||
sl.registerLazySingleton<PlansService>(() => PlansService(sl()));
|
||||
sl.registerLazySingleton<PlanService>(() => PlanService(sl()));
|
||||
sl.registerLazySingleton<MyDietsService>(() => MyDietsService(sl()));
|
||||
sl.registerLazySingleton<MyPlansService>(() => MyPlansService(sl()));
|
||||
|
||||
// External
|
||||
sl.registerLazySingleton(
|
||||
|
||||
@@ -224,7 +224,7 @@ abstract class TextStyles {
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.255,
|
||||
);
|
||||
);
|
||||
|
||||
static const textFS10FW500 = TextStyle(
|
||||
fontSize: 12,
|
||||
|
||||
30
lib/core/widgets/add_item_button.dart
Normal file
30
lib/core/widgets/add_item_button.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class AddItemButton extends StatelessWidget {
|
||||
const AddItemButton({super.key, this.onTap});
|
||||
final Function()? onTap;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,11 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AnimatedOptionButton extends StatelessWidget {
|
||||
const AnimatedOptionButton({required this.text, required this.isSelected, required this.onTap});
|
||||
const AnimatedOptionButton({required this.text, required this.isSelected, required this.onTap, this.textStyle});
|
||||
final String text;
|
||||
final bool isSelected;
|
||||
final void Function() onTap;
|
||||
|
||||
final TextStyle? textStyle;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
@@ -22,9 +22,12 @@ class AnimatedOptionButton extends StatelessWidget {
|
||||
child: Center(
|
||||
child: AnimatedDefaultTextStyle(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
style: TextStyles.textMSemibold.copyWith(
|
||||
color: isSelected ? appTheme.whiteA700 : appTheme.black,
|
||||
),
|
||||
style: textStyle?.copyWith(
|
||||
color: isSelected ? appTheme.whiteA700 : appTheme.black,
|
||||
) ??
|
||||
TextStyles.textMSemibold.copyWith(
|
||||
color: isSelected ? appTheme.whiteA700 : appTheme.black,
|
||||
),
|
||||
child: Text(text),
|
||||
),
|
||||
),
|
||||
|
||||
45
lib/core/widgets/bottom_sheet_with_blur.dart
Normal file
45
lib/core/widgets/bottom_sheet_with_blur.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Future<void> showBottomSheetWithBlur({
|
||||
required BuildContext context,
|
||||
required Widget child,
|
||||
double height = 200,
|
||||
bool hasBlur = true,
|
||||
bool enableDrag = true,
|
||||
}) {
|
||||
return showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
barrierColor: appTheme.transparentBlack,
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
useSafeArea: true,
|
||||
enableDrag: enableDrag,
|
||||
builder: (context) => GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: hasBlur ? 24 : 0,
|
||||
sigmaY: hasBlur ? 24 : 0,
|
||||
tileMode: TileMode.mirror,
|
||||
),
|
||||
child: SizedBox(
|
||||
child: Container(
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
color: appTheme.whiteA700,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -69,7 +69,7 @@ class CustomOutlinedButton extends BaseButton {
|
||||
width: this.width ?? double.maxFinite,
|
||||
margin: margin,
|
||||
decoration: decoration,
|
||||
child: OutlinedButton(
|
||||
child: OutlinedButton(
|
||||
style: buttonStyle!,
|
||||
onPressed: isDisabled ?? false ? null : onPressed ?? () {},
|
||||
child: Row(
|
||||
|
||||
59
lib/core/widgets/custom_search_bar.dart
Normal file
59
lib/core/widgets/custom_search_bar.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomSearchBar extends StatelessWidget {
|
||||
const CustomSearchBar({super.key, required this.searchController, required this.onChanged, this.backgroundColor, this.borderRadius, this.enableIcon = true, this.width});
|
||||
final Color? backgroundColor;
|
||||
final double? width;
|
||||
final double? borderRadius;
|
||||
final TextEditingController searchController;
|
||||
final void Function(String) onChanged;
|
||||
final bool enableIcon;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 40,
|
||||
width: width ?? MediaQuery.of(context).size.width - 84,
|
||||
child: TextFormField(
|
||||
controller: searchController,
|
||||
autofocus: false,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
),
|
||||
onChanged: onChanged,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Поиск',
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 12,
|
||||
color: appTheme.gray600,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
prefixIcon: enableIcon
|
||||
? Icon(
|
||||
Icons.search,
|
||||
color: appTheme.gray600,
|
||||
size: 16,
|
||||
)
|
||||
: null,
|
||||
isDense: false,
|
||||
contentPadding: enableIcon ? EdgeInsets.all(8) : EdgeInsets.only(top: 8, left: 16, right: 8, bottom: 8),
|
||||
fillColor: backgroundColor ?? appTheme.gray100,
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(borderRadius ?? 16),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(borderRadius ?? 16),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(borderRadius ?? 16),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
|
||||
@@ -47,17 +46,92 @@ class CreateDietScreenContent extends StatelessWidget {
|
||||
if (state.published) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
context.pop();
|
||||
showPublishSuccessBottomSheet(
|
||||
showBottomSheetWithBlur(
|
||||
context: context,
|
||||
text: '${state.dietData.name ?? 'Диета'}',
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
BottomSheetHandlerBar(),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
'${state.dietData.name ?? 'Диета'}',
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(
|
||||
'опубликованна',
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: CustomOutlinedButton(
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
},
|
||||
text: 'Хорошо',
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
} else if (state.drafted) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
context.pop();
|
||||
showDraftSuccessBottomSheet(
|
||||
showBottomSheetWithBlur(
|
||||
context: context,
|
||||
text: '${state.dietData.name ?? 'Диета'} сохраненна в черновиках',
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
BottomSheetHandlerBar(),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
'${state.dietData.name ?? 'Диета'} сохраненна в черновиках',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: CustomOutlinedButton(
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
},
|
||||
text: 'Хорошо',
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -208,160 +282,5 @@ class CreateDietScreenContent extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> showDraftSuccessBottomSheet({
|
||||
required BuildContext context,
|
||||
required String text,
|
||||
bool hasBlur = true,
|
||||
bool enableDrag = true,
|
||||
}) {
|
||||
return showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
barrierColor: appTheme.transparentBlack,
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
useSafeArea: true,
|
||||
enableDrag: enableDrag,
|
||||
builder: (context) => GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: hasBlur ? 24 : 0,
|
||||
sigmaY: hasBlur ? 24 : 0,
|
||||
tileMode: TileMode.mirror,
|
||||
),
|
||||
child: SizedBox(
|
||||
child: Container(
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
color: appTheme.whiteA700,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
BottomSheetHandlerBar(),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
text,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: CustomOutlinedButton(
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
},
|
||||
text: 'Хорошо',
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> showPublishSuccessBottomSheet({
|
||||
required BuildContext context,
|
||||
required String text,
|
||||
bool hasBlur = true,
|
||||
bool enableDrag = true,
|
||||
}) {
|
||||
return showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
barrierColor: appTheme.transparentBlack,
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
useSafeArea: true,
|
||||
enableDrag: enableDrag,
|
||||
builder: (context) => GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: hasBlur ? 24 : 0,
|
||||
sigmaY: hasBlur ? 24 : 0,
|
||||
tileMode: TileMode.mirror,
|
||||
),
|
||||
child: SizedBox(
|
||||
child: Container(
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
color: appTheme.whiteA700,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
BottomSheetHandlerBar(),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
text,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(
|
||||
'опубликованна',
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: CustomOutlinedButton(
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
},
|
||||
text: 'Хорошо',
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
import 'package:eatvio_app/core/utils/string_extentions.dart';
|
||||
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
import 'package:eatvio_app/core/widgets/diet_info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/info_row.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart';
|
||||
import 'package:eatvio_app/core/widgets/cpfc_container.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/widgets/expandable_text.dart';
|
||||
|
||||
@@ -32,7 +32,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> likeDiet(int id, int dietIndexInFeed) async {
|
||||
Future<void> likeDiet(int id, int? dietIndexInFeed) async {
|
||||
if (state is! DietStateLoaded) return;
|
||||
|
||||
final currentDiet = (state as DietStateLoaded).diet;
|
||||
@@ -42,15 +42,18 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
||||
totalLikes: currentDiet.totalLikes - 1,
|
||||
wasLikedByMe: false,
|
||||
);
|
||||
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedDiet.wasLikedByMe, totalLikes: updatedDiet.totalLikes, index: dietIndexInFeed));
|
||||
if (dietIndexInFeed != null) {
|
||||
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedDiet.wasLikedByMe, totalLikes: updatedDiet.totalLikes, index: dietIndexInFeed));
|
||||
}
|
||||
emit(AboutDietState.loaded(updatedDiet));
|
||||
} else {
|
||||
final updatedDiet = currentDiet.copyWith(
|
||||
totalLikes: currentDiet.totalLikes + 1,
|
||||
wasLikedByMe: true,
|
||||
);
|
||||
|
||||
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedDiet.wasLikedByMe, totalLikes: updatedDiet.totalLikes, index: dietIndexInFeed));
|
||||
if (dietIndexInFeed != null) {
|
||||
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedDiet.wasLikedByMe, totalLikes: updatedDiet.totalLikes, index: dietIndexInFeed));
|
||||
}
|
||||
emit(AboutDietState.loaded(updatedDiet));
|
||||
}
|
||||
|
||||
@@ -62,14 +65,17 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
||||
await dietsRepository.likeDiet(id);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: currentDiet.wasLikedByMe, totalLikes: currentDiet.totalLikes, index: dietIndexInFeed));
|
||||
if (dietIndexInFeed != null) {
|
||||
dietsRepository
|
||||
.emitLikesStateToFeed(LikesState(wasLikedByMe: currentDiet.wasLikedByMe, totalLikes: currentDiet.totalLikes, index: dietIndexInFeed));
|
||||
}
|
||||
emit(AboutDietState.loaded(currentDiet));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> addToFavorite(int id, int dietIndex) async {
|
||||
Future<void> addToFavorite(int id, int? dietIndex) async {
|
||||
if (state is! DietStateLoaded) return;
|
||||
|
||||
final currentDiet = (state as DietStateLoaded).diet;
|
||||
@@ -77,7 +83,9 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
||||
final updatedDiet = currentDiet.copyWith(
|
||||
isFavorite: true,
|
||||
);
|
||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: updatedDiet.isFavorite, index: dietIndex));
|
||||
if (dietIndex != null) {
|
||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: updatedDiet.isFavorite, index: dietIndex));
|
||||
}
|
||||
emit(AboutDietState.loaded(updatedDiet));
|
||||
|
||||
EasyDebounce.debounce(
|
||||
@@ -89,14 +97,16 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentDiet.isFavorite, index: dietIndex));
|
||||
if (dietIndex != null) {
|
||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentDiet.isFavorite, index: dietIndex));
|
||||
}
|
||||
emit(AboutDietState.loaded(currentDiet));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> removeFromFavorite(int id, int dietIndex) async {
|
||||
Future<void> removeFromFavorite(int id, int? dietIndex) async {
|
||||
if (state is! DietStateLoaded) return;
|
||||
|
||||
final currentDiet = (state as DietStateLoaded).diet;
|
||||
@@ -104,7 +114,9 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
||||
final updatedDiet = currentDiet.copyWith(
|
||||
isFavorite: false,
|
||||
);
|
||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: updatedDiet.isFavorite, index: dietIndex));
|
||||
if (dietIndex != null) {
|
||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: updatedDiet.isFavorite, index: dietIndex));
|
||||
}
|
||||
emit(AboutDietState.loaded(updatedDiet));
|
||||
|
||||
EasyDebounce.debounce(
|
||||
@@ -116,7 +128,9 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentDiet.isFavorite, index: dietIndex));
|
||||
if (dietIndex != null) {
|
||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentDiet.isFavorite, index: dietIndex));
|
||||
}
|
||||
emit(AboutDietState.loaded(currentDiet));
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
import 'package:eatvio_app/core/utils/string_extentions.dart';
|
||||
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
import 'package:eatvio_app/core/widgets/diet_info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_cubit.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_state.dart';
|
||||
@@ -32,7 +32,7 @@ import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class AboutDietScreen extends StatefulWidget {
|
||||
final Diet diet;
|
||||
final int index;
|
||||
final int? index;
|
||||
final bool navigateToComments;
|
||||
late final List<int> selectedIndexes;
|
||||
|
||||
@@ -132,10 +132,11 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
||||
),
|
||||
backgroundColor: appTheme.greenA700,
|
||||
onPressed: () {
|
||||
final cubit = context.read<AboutDietCubit>();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => StartDietBottomSheet(
|
||||
aboutDietCubit: context.read<AboutDietCubit>(),
|
||||
aboutDietCubit: cubit,
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -150,7 +151,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
||||
BookmarkButton(
|
||||
isFavorite: dietState.diet.isFavorite,
|
||||
onTap: () {
|
||||
if (dietState.diet.isFavorite) {
|
||||
if (dietState.diet.isFavorite) {
|
||||
dietsCubit.removeFromFavorite(widget.diet.id, widget.index);
|
||||
} else {
|
||||
dietsCubit.addToFavorite(widget.diet.id, widget.index);
|
||||
|
||||
@@ -57,8 +57,8 @@ class _ExpandableTextState extends State<ExpandableText> {
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.white.withOpacity(0.0),
|
||||
Colors.white.withOpacity(0.6),
|
||||
Colors.white.withValues(alpha: 0.0),
|
||||
Colors.white.withValues(alpha: 0.6),
|
||||
Colors.white,
|
||||
],
|
||||
),
|
||||
|
||||
@@ -2,14 +2,15 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class InfoBouble extends StatelessWidget {
|
||||
const InfoBouble({super.key, required this.children, this.backgroundColor, this.radius = 100});
|
||||
const InfoBouble({super.key, required this.children, this.backgroundColor, this.radius = 100, this.padding});
|
||||
final List<Widget> children;
|
||||
final Color? backgroundColor;
|
||||
final double radius;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8.5),
|
||||
padding: padding ?? EdgeInsets.symmetric(horizontal: 12, vertical: 8.5),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor ?? appTheme.gray50,
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
|
||||
@@ -26,7 +26,7 @@ class PlanModel with _$PlanModel {
|
||||
@JsonKey(name: "image") required String image,
|
||||
@JsonKey(name: "duration") required int duration,
|
||||
@JsonKey(name: "state") required String state,
|
||||
@JsonKey(name: "type") required PlanType type,
|
||||
@JsonKey(name: "type", fromJson: planTypeFromJson) required PlanType type,
|
||||
@JsonKey(name: "details") required String details,
|
||||
@JsonKey(name: "packets_count") required int packetsCount,
|
||||
@JsonKey(name: "packet1_options") PacketOptions? packet1Options,
|
||||
@@ -37,12 +37,21 @@ class PlanModel with _$PlanModel {
|
||||
@JsonKey(name: "total_views") required int totalViews,
|
||||
@JsonKey(name: "total_comments") required int totalComments,
|
||||
@JsonKey(name: "is_favorite") required bool isFavorite,
|
||||
@JsonKey(name: "min_price_of_options") required double minPriceOfOptions,
|
||||
@JsonKey(name: "max_price_of_options") required double maxPriceOfOptions,
|
||||
@Default(false) bool wasLikedByMe,
|
||||
}) = _PlanModel;
|
||||
|
||||
factory PlanModel.fromJson(Map<String, dynamic> json) => _$PlanModelFromJson(json);
|
||||
}
|
||||
|
||||
PlanType planTypeFromJson(dynamic json) {
|
||||
if (json is List<dynamic>) {
|
||||
return PlanType(fit: false, slim: false, behavior: false, wellness: false);
|
||||
}
|
||||
return PlanType.fromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class PacketOptions with _$PacketOptions {
|
||||
const factory PacketOptions({
|
||||
|
||||
@@ -272,7 +272,7 @@ mixin _$PlanModel {
|
||||
int get duration => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "state")
|
||||
String get state => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "type")
|
||||
@JsonKey(name: "type", fromJson: planTypeFromJson)
|
||||
PlanType get type => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "details")
|
||||
String get details => throw _privateConstructorUsedError;
|
||||
@@ -294,6 +294,10 @@ mixin _$PlanModel {
|
||||
int get totalComments => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "is_favorite")
|
||||
bool get isFavorite => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "min_price_of_options")
|
||||
double get minPriceOfOptions => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "max_price_of_options")
|
||||
double get maxPriceOfOptions => throw _privateConstructorUsedError;
|
||||
bool get wasLikedByMe => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this PlanModel to a JSON map.
|
||||
@@ -318,7 +322,7 @@ abstract class $PlanModelCopyWith<$Res> {
|
||||
@JsonKey(name: "image") String image,
|
||||
@JsonKey(name: "duration") int duration,
|
||||
@JsonKey(name: "state") String state,
|
||||
@JsonKey(name: "type") PlanType type,
|
||||
@JsonKey(name: "type", fromJson: planTypeFromJson) PlanType type,
|
||||
@JsonKey(name: "details") String details,
|
||||
@JsonKey(name: "packets_count") int packetsCount,
|
||||
@JsonKey(name: "packet1_options") PacketOptions? packet1Options,
|
||||
@@ -329,6 +333,8 @@ abstract class $PlanModelCopyWith<$Res> {
|
||||
@JsonKey(name: "total_views") int totalViews,
|
||||
@JsonKey(name: "total_comments") int totalComments,
|
||||
@JsonKey(name: "is_favorite") bool isFavorite,
|
||||
@JsonKey(name: "min_price_of_options") double minPriceOfOptions,
|
||||
@JsonKey(name: "max_price_of_options") double maxPriceOfOptions,
|
||||
bool wasLikedByMe});
|
||||
|
||||
$PlanTypeCopyWith<$Res> get type;
|
||||
@@ -370,6 +376,8 @@ class _$PlanModelCopyWithImpl<$Res, $Val extends PlanModel>
|
||||
Object? totalViews = null,
|
||||
Object? totalComments = null,
|
||||
Object? isFavorite = null,
|
||||
Object? minPriceOfOptions = null,
|
||||
Object? maxPriceOfOptions = null,
|
||||
Object? wasLikedByMe = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
@@ -441,6 +449,14 @@ class _$PlanModelCopyWithImpl<$Res, $Val extends PlanModel>
|
||||
? _value.isFavorite
|
||||
: isFavorite // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
minPriceOfOptions: null == minPriceOfOptions
|
||||
? _value.minPriceOfOptions
|
||||
: minPriceOfOptions // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
maxPriceOfOptions: null == maxPriceOfOptions
|
||||
? _value.maxPriceOfOptions
|
||||
: maxPriceOfOptions // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
wasLikedByMe: null == wasLikedByMe
|
||||
? _value.wasLikedByMe
|
||||
: wasLikedByMe // ignore: cast_nullable_to_non_nullable
|
||||
@@ -526,7 +542,7 @@ abstract class _$$PlanModelImplCopyWith<$Res>
|
||||
@JsonKey(name: "image") String image,
|
||||
@JsonKey(name: "duration") int duration,
|
||||
@JsonKey(name: "state") String state,
|
||||
@JsonKey(name: "type") PlanType type,
|
||||
@JsonKey(name: "type", fromJson: planTypeFromJson) PlanType type,
|
||||
@JsonKey(name: "details") String details,
|
||||
@JsonKey(name: "packets_count") int packetsCount,
|
||||
@JsonKey(name: "packet1_options") PacketOptions? packet1Options,
|
||||
@@ -537,6 +553,8 @@ abstract class _$$PlanModelImplCopyWith<$Res>
|
||||
@JsonKey(name: "total_views") int totalViews,
|
||||
@JsonKey(name: "total_comments") int totalComments,
|
||||
@JsonKey(name: "is_favorite") bool isFavorite,
|
||||
@JsonKey(name: "min_price_of_options") double minPriceOfOptions,
|
||||
@JsonKey(name: "max_price_of_options") double maxPriceOfOptions,
|
||||
bool wasLikedByMe});
|
||||
|
||||
@override
|
||||
@@ -581,6 +599,8 @@ class __$$PlanModelImplCopyWithImpl<$Res>
|
||||
Object? totalViews = null,
|
||||
Object? totalComments = null,
|
||||
Object? isFavorite = null,
|
||||
Object? minPriceOfOptions = null,
|
||||
Object? maxPriceOfOptions = null,
|
||||
Object? wasLikedByMe = null,
|
||||
}) {
|
||||
return _then(_$PlanModelImpl(
|
||||
@@ -652,6 +672,14 @@ class __$$PlanModelImplCopyWithImpl<$Res>
|
||||
? _value.isFavorite
|
||||
: isFavorite // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
minPriceOfOptions: null == minPriceOfOptions
|
||||
? _value.minPriceOfOptions
|
||||
: minPriceOfOptions // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
maxPriceOfOptions: null == maxPriceOfOptions
|
||||
? _value.maxPriceOfOptions
|
||||
: maxPriceOfOptions // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
wasLikedByMe: null == wasLikedByMe
|
||||
? _value.wasLikedByMe
|
||||
: wasLikedByMe // ignore: cast_nullable_to_non_nullable
|
||||
@@ -670,7 +698,7 @@ class _$PlanModelImpl implements _PlanModel {
|
||||
@JsonKey(name: "image") required this.image,
|
||||
@JsonKey(name: "duration") required this.duration,
|
||||
@JsonKey(name: "state") required this.state,
|
||||
@JsonKey(name: "type") required this.type,
|
||||
@JsonKey(name: "type", fromJson: planTypeFromJson) required this.type,
|
||||
@JsonKey(name: "details") required this.details,
|
||||
@JsonKey(name: "packets_count") required this.packetsCount,
|
||||
@JsonKey(name: "packet1_options") this.packet1Options,
|
||||
@@ -681,6 +709,8 @@ class _$PlanModelImpl implements _PlanModel {
|
||||
@JsonKey(name: "total_views") required this.totalViews,
|
||||
@JsonKey(name: "total_comments") required this.totalComments,
|
||||
@JsonKey(name: "is_favorite") required this.isFavorite,
|
||||
@JsonKey(name: "min_price_of_options") required this.minPriceOfOptions,
|
||||
@JsonKey(name: "max_price_of_options") required this.maxPriceOfOptions,
|
||||
this.wasLikedByMe = false});
|
||||
|
||||
factory _$PlanModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
@@ -705,7 +735,7 @@ class _$PlanModelImpl implements _PlanModel {
|
||||
@JsonKey(name: "state")
|
||||
final String state;
|
||||
@override
|
||||
@JsonKey(name: "type")
|
||||
@JsonKey(name: "type", fromJson: planTypeFromJson)
|
||||
final PlanType type;
|
||||
@override
|
||||
@JsonKey(name: "details")
|
||||
@@ -738,12 +768,18 @@ class _$PlanModelImpl implements _PlanModel {
|
||||
@JsonKey(name: "is_favorite")
|
||||
final bool isFavorite;
|
||||
@override
|
||||
@JsonKey(name: "min_price_of_options")
|
||||
final double minPriceOfOptions;
|
||||
@override
|
||||
@JsonKey(name: "max_price_of_options")
|
||||
final double maxPriceOfOptions;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool wasLikedByMe;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PlanModel(id: $id, slug: $slug, name: $name, image: $image, duration: $duration, state: $state, type: $type, details: $details, packetsCount: $packetsCount, packet1Options: $packet1Options, packet2Options: $packet2Options, packet3Options: $packet3Options, author: $author, totalLikes: $totalLikes, totalViews: $totalViews, totalComments: $totalComments, isFavorite: $isFavorite, wasLikedByMe: $wasLikedByMe)';
|
||||
return 'PlanModel(id: $id, slug: $slug, name: $name, image: $image, duration: $duration, state: $state, type: $type, details: $details, packetsCount: $packetsCount, packet1Options: $packet1Options, packet2Options: $packet2Options, packet3Options: $packet3Options, author: $author, totalLikes: $totalLikes, totalViews: $totalViews, totalComments: $totalComments, isFavorite: $isFavorite, minPriceOfOptions: $minPriceOfOptions, maxPriceOfOptions: $maxPriceOfOptions, wasLikedByMe: $wasLikedByMe)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -777,32 +813,39 @@ class _$PlanModelImpl implements _PlanModel {
|
||||
other.totalComments == totalComments) &&
|
||||
(identical(other.isFavorite, isFavorite) ||
|
||||
other.isFavorite == isFavorite) &&
|
||||
(identical(other.minPriceOfOptions, minPriceOfOptions) ||
|
||||
other.minPriceOfOptions == minPriceOfOptions) &&
|
||||
(identical(other.maxPriceOfOptions, maxPriceOfOptions) ||
|
||||
other.maxPriceOfOptions == maxPriceOfOptions) &&
|
||||
(identical(other.wasLikedByMe, wasLikedByMe) ||
|
||||
other.wasLikedByMe == wasLikedByMe));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
id,
|
||||
slug,
|
||||
name,
|
||||
image,
|
||||
duration,
|
||||
state,
|
||||
type,
|
||||
details,
|
||||
packetsCount,
|
||||
packet1Options,
|
||||
packet2Options,
|
||||
packet3Options,
|
||||
author,
|
||||
totalLikes,
|
||||
totalViews,
|
||||
totalComments,
|
||||
isFavorite,
|
||||
wasLikedByMe);
|
||||
int get hashCode => Object.hashAll([
|
||||
runtimeType,
|
||||
id,
|
||||
slug,
|
||||
name,
|
||||
image,
|
||||
duration,
|
||||
state,
|
||||
type,
|
||||
details,
|
||||
packetsCount,
|
||||
packet1Options,
|
||||
packet2Options,
|
||||
packet3Options,
|
||||
author,
|
||||
totalLikes,
|
||||
totalViews,
|
||||
totalComments,
|
||||
isFavorite,
|
||||
minPriceOfOptions,
|
||||
maxPriceOfOptions,
|
||||
wasLikedByMe
|
||||
]);
|
||||
|
||||
/// Create a copy of PlanModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -828,7 +871,8 @@ abstract class _PlanModel implements PlanModel {
|
||||
@JsonKey(name: "image") required final String image,
|
||||
@JsonKey(name: "duration") required final int duration,
|
||||
@JsonKey(name: "state") required final String state,
|
||||
@JsonKey(name: "type") required final PlanType type,
|
||||
@JsonKey(name: "type", fromJson: planTypeFromJson)
|
||||
required final PlanType type,
|
||||
@JsonKey(name: "details") required final String details,
|
||||
@JsonKey(name: "packets_count") required final int packetsCount,
|
||||
@JsonKey(name: "packet1_options") final PacketOptions? packet1Options,
|
||||
@@ -839,6 +883,10 @@ abstract class _PlanModel implements PlanModel {
|
||||
@JsonKey(name: "total_views") required final int totalViews,
|
||||
@JsonKey(name: "total_comments") required final int totalComments,
|
||||
@JsonKey(name: "is_favorite") required final bool isFavorite,
|
||||
@JsonKey(name: "min_price_of_options")
|
||||
required final double minPriceOfOptions,
|
||||
@JsonKey(name: "max_price_of_options")
|
||||
required final double maxPriceOfOptions,
|
||||
final bool wasLikedByMe}) = _$PlanModelImpl;
|
||||
|
||||
factory _PlanModel.fromJson(Map<String, dynamic> json) =
|
||||
@@ -863,7 +911,7 @@ abstract class _PlanModel implements PlanModel {
|
||||
@JsonKey(name: "state")
|
||||
String get state;
|
||||
@override
|
||||
@JsonKey(name: "type")
|
||||
@JsonKey(name: "type", fromJson: planTypeFromJson)
|
||||
PlanType get type;
|
||||
@override
|
||||
@JsonKey(name: "details")
|
||||
@@ -896,6 +944,12 @@ abstract class _PlanModel implements PlanModel {
|
||||
@JsonKey(name: "is_favorite")
|
||||
bool get isFavorite;
|
||||
@override
|
||||
@JsonKey(name: "min_price_of_options")
|
||||
double get minPriceOfOptions;
|
||||
@override
|
||||
@JsonKey(name: "max_price_of_options")
|
||||
double get maxPriceOfOptions;
|
||||
@override
|
||||
bool get wasLikedByMe;
|
||||
|
||||
/// Create a copy of PlanModel
|
||||
|
||||
@@ -32,7 +32,7 @@ _$PlanModelImpl _$$PlanModelImplFromJson(Map<String, dynamic> json) =>
|
||||
image: json['image'] as String,
|
||||
duration: (json['duration'] as num).toInt(),
|
||||
state: json['state'] as String,
|
||||
type: PlanType.fromJson(json['type'] as Map<String, dynamic>),
|
||||
type: planTypeFromJson(json['type']),
|
||||
details: json['details'] as String,
|
||||
packetsCount: (json['packets_count'] as num).toInt(),
|
||||
packet1Options: json['packet1_options'] == null
|
||||
@@ -52,6 +52,8 @@ _$PlanModelImpl _$$PlanModelImplFromJson(Map<String, dynamic> json) =>
|
||||
totalViews: (json['total_views'] as num).toInt(),
|
||||
totalComments: (json['total_comments'] as num).toInt(),
|
||||
isFavorite: json['is_favorite'] as bool,
|
||||
minPriceOfOptions: (json['min_price_of_options'] as num).toDouble(),
|
||||
maxPriceOfOptions: (json['max_price_of_options'] as num).toDouble(),
|
||||
wasLikedByMe: json['wasLikedByMe'] as bool? ?? false,
|
||||
);
|
||||
|
||||
@@ -74,6 +76,8 @@ Map<String, dynamic> _$$PlanModelImplToJson(_$PlanModelImpl instance) =>
|
||||
'total_views': instance.totalViews,
|
||||
'total_comments': instance.totalComments,
|
||||
'is_favorite': instance.isFavorite,
|
||||
'min_price_of_options': instance.minPriceOfOptions,
|
||||
'max_price_of_options': instance.maxPriceOfOptions,
|
||||
'wasLikedByMe': instance.wasLikedByMe,
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_sta
|
||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class DietsCubit extends Cubit<DietsState> {
|
||||
class DietsCubit extends Cubit<DietsState>{
|
||||
final FeedRepository feedRepository;
|
||||
final DietRepository dietsRepository;
|
||||
int currentPage = 1;
|
||||
@@ -52,6 +52,7 @@ class DietsCubit extends Cubit<DietsState> {
|
||||
super.close();
|
||||
}
|
||||
|
||||
|
||||
void changeTypeFilter(int index) {
|
||||
emit(state.copyWith(filterState: state.filterState.copyWith(typeIndex: index), errorMessage: null));
|
||||
EasyDebounce.debounce(
|
||||
@@ -63,6 +64,7 @@ class DietsCubit extends Cubit<DietsState> {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void changeSortingType(int index) {
|
||||
emit(state.copyWith(filterState: state.filterState.copyWith(sortingIndex: index), errorMessage: null));
|
||||
EasyDebounce.debounce(
|
||||
@@ -74,6 +76,7 @@ class DietsCubit extends Cubit<DietsState> {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void changeDurationFilter(int index) {
|
||||
emit(state.copyWith(filterState: state.filterState.copyWith(durationIndex: index), errorMessage: null));
|
||||
EasyDebounce.debounce(
|
||||
@@ -101,7 +104,11 @@ class DietsCubit extends Cubit<DietsState> {
|
||||
);
|
||||
lastPage = dietsPagination.meta.lastPage;
|
||||
emit(
|
||||
state.copyWith(dietsList: dietsPagination.data, isLoading: isLoading, errorMessage: null,),
|
||||
state.copyWith(
|
||||
dietsList: dietsPagination.data,
|
||||
isLoading: isLoading,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
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/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/create_plan_screen.dart';
|
||||
@@ -163,16 +165,63 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
bottom: _chipGroup(),
|
||||
title: Row(
|
||||
children: [
|
||||
_searchBar(),
|
||||
CustomSearchBar(
|
||||
searchController: searchController,
|
||||
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;
|
||||
case 3:
|
||||
plansCubit.loadPlans(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();
|
||||
break;
|
||||
case 3:
|
||||
plansCubit.loadPlans();
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
switch (_selectedChipIndex) {
|
||||
2 => _addItemButton(
|
||||
2 => AddItemButton(
|
||||
onTap: state.profile?.role == 'consultant' ? () => GoRouter.of(context).pushNamed(CreateDietScreen.name) : null,
|
||||
),
|
||||
3 => _addItemButton(
|
||||
3 => AddItemButton(
|
||||
onTap: state.profile?.role == 'consultant' ? () => GoRouter.of(context).pushNamed(CreatePlanScreen.name) : null,
|
||||
),
|
||||
_ => _addItemButton()
|
||||
_ => AddItemButton()
|
||||
},
|
||||
],
|
||||
),
|
||||
@@ -181,21 +230,13 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
),
|
||||
];
|
||||
},
|
||||
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]));
|
||||
}
|
||||
},
|
||||
),
|
||||
body: switch (_selectedChipIndex) {
|
||||
0 => _buildAllBlogPosts(),
|
||||
1 => _buildAllRecipes(),
|
||||
2 => _buildDiets(),
|
||||
3 => _buildPlans(),
|
||||
_ => Center(child: Text(chipsLabelList[_selectedChipIndex])),
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -473,111 +514,4 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_addItemButton({void Function()? onTap}) {
|
||||
return InkWell(
|
||||
onTap: 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,
|
||||
autofocus: false,
|
||||
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;
|
||||
case 3:
|
||||
plansCubit.loadPlans(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 == '' ? null : 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,16 @@ import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cub
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/duration_choice_filter_widget.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/sorting_drop_down_filter.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/type_choice_filter_widget.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DietFilterBottomSheet extends StatelessWidget {
|
||||
const DietFilterBottomSheet({super.key, required this.dietsCubit});
|
||||
final DietsCubit dietsCubit;
|
||||
const DietFilterBottomSheet({super.key, this.dietsCubit, this.myDietsCubit});
|
||||
final DietsCubit? dietsCubit;
|
||||
final MyDietsCubit? myDietsCubit;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
@@ -32,7 +34,8 @@ class DietFilterBottomSheet extends StatelessWidget {
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
SortingDropDownFilter(
|
||||
cubit: dietsCubit,
|
||||
dietsCubit: dietsCubit,
|
||||
myDietsCubit: myDietsCubit,
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
@@ -50,6 +53,7 @@ class DietFilterBottomSheet extends StatelessWidget {
|
||||
),
|
||||
TypeChoiceFilterWidget(
|
||||
dietsCubit: dietsCubit,
|
||||
myDietsCubit: myDietsCubit,
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
@@ -67,6 +71,7 @@ class DietFilterBottomSheet extends StatelessWidget {
|
||||
),
|
||||
DurationChoiceFilterWidget(
|
||||
dietCubit: dietsCubit,
|
||||
myDietsCubit: myDietsCubit,
|
||||
),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/diet_info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/feed_icon_button.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
|
||||
|
||||
@@ -5,14 +5,20 @@ import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_sta
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_state.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/choice_chip_widget.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class DurationChoiceFilterWidget extends StatefulWidget {
|
||||
const DurationChoiceFilterWidget({super.key, this.dietCubit, this.plansCubit});
|
||||
const DurationChoiceFilterWidget({super.key, this.dietCubit, this.plansCubit, this.myDietsCubit, this.myPlansCubit});
|
||||
final DietsCubit? dietCubit;
|
||||
final PlansCubit? plansCubit;
|
||||
final MyDietsCubit? myDietsCubit;
|
||||
final MyPlansCubit? myPlansCubit;
|
||||
@override
|
||||
State<DurationChoiceFilterWidget> createState() => _DurationChoiceFilterWidgetState();
|
||||
}
|
||||
@@ -39,102 +45,102 @@ class _DurationChoiceFilterWidgetState extends State<DurationChoiceFilterWidget>
|
||||
builder: (context, state) {
|
||||
return Wrap(
|
||||
children: [
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 0,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietCubit?.changeDurationFilter(0);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 1,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietCubit?.changeDurationFilter(1);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 2,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietCubit?.changeDurationFilter(2);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 3,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietCubit?.changeDurationFilter(3);
|
||||
},
|
||||
...List.generate(
|
||||
chipsLabelList.length,
|
||||
(index) => Padding(
|
||||
padding: EdgeInsets.only(right: index != chipsLabelList.length - 1 ? 8 : 0),
|
||||
child: ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: index,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietCubit?.changeDurationFilter(index);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
} else if (widget.plansCubit != null) {
|
||||
return BlocBuilder<PlansCubit, PlansState>(
|
||||
bloc: widget.plansCubit!,
|
||||
bloc: widget.plansCubit,
|
||||
builder: (context, state) {
|
||||
return Wrap(
|
||||
children: [
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 0,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeDurationFilter(0);
|
||||
},
|
||||
...List.generate(
|
||||
chipsLabelList.length,
|
||||
(index) => Padding(
|
||||
padding: EdgeInsets.only(right: index != chipsLabelList.length - 1 ? 8 : 0),
|
||||
child: ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: index,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeDurationFilter(index);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 1,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeDurationFilter(1);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 2,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeDurationFilter(2);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 3,
|
||||
selectedChipIndex: state.filterState.durationIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeDurationFilter(3);
|
||||
},
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else if (widget.myPlansCubit != null) {
|
||||
return BlocBuilder<MyPlansCubit, MyPlansState>(
|
||||
bloc: widget.myPlansCubit,
|
||||
builder: (context, state) {
|
||||
return Wrap(
|
||||
children: [
|
||||
...List.generate(
|
||||
chipsLabelList.length,
|
||||
(index) => Padding(
|
||||
padding: EdgeInsets.only(right: index != chipsLabelList.length - 1 ? 8 : 0),
|
||||
child: ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: index,
|
||||
selectedChipIndex: switch (state.selectedStateIndex) {
|
||||
1 => state.draftFilterState.durationIndex,
|
||||
_ => state.activeFilterState.durationIndex
|
||||
},
|
||||
onSelected: (selectedValue) {
|
||||
widget.myPlansCubit?.changeDurationFilter(index);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
return BlocBuilder<MyDietsCubit, MyDietsState>(
|
||||
bloc: widget.myDietsCubit,
|
||||
builder: (context, state) {
|
||||
return Wrap(
|
||||
children: [
|
||||
...List.generate(
|
||||
chipsLabelList.length,
|
||||
(index) => Padding(
|
||||
padding: EdgeInsets.only(right: index != chipsLabelList.length - 1 ? 8 : 0),
|
||||
child: ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: index,
|
||||
selectedChipIndex: switch (state.selectedStateIndex) {
|
||||
1 => state.draftFilterState.durationIndex,
|
||||
2 => state.personalFilterState.durationIndex,
|
||||
_ => state.activeFilterState.durationIndex
|
||||
},
|
||||
onSelected: (selectedValue) {
|
||||
widget.myDietsCubit?.changeDurationFilter(index);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/diet_info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/feed_icon_button.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
@@ -31,6 +33,7 @@ class PlanWidget extends StatefulWidget {
|
||||
class _PlanWidgetState extends State<PlanWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
@@ -117,8 +120,61 @@ class _PlanWidgetState extends State<PlanWidget> {
|
||||
],
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, top: 12),
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
child: ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: Container(
|
||||
width: 46,
|
||||
height: 46,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
image: DecorationImage(
|
||||
image: NetworkImage(widget.plan.author.authorProfileIcon),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: Row(
|
||||
children: [
|
||||
InfoBouble(
|
||||
radius: 12,
|
||||
backgroundColor: appTheme.gray50,
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 10),
|
||||
children: [
|
||||
Text(
|
||||
'${widget.plan.author.authorPlaceInRating}-й в рейтинге',
|
||||
style: TextStyles.textXSSemibold.copyWith(
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Spacer(),
|
||||
Text(
|
||||
'${widget.plan.packetsCount == 1 ? widget.plan.packet1Options?.price : '${s.from} ${widget.plan.minPriceOfOptions.toStringAsFixed(0)}'}₽',
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.greenA700),
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: Text(
|
||||
widget.plan.author.authorName,
|
||||
style: TextStyles.textSSemibold.copyWith(
|
||||
color: appTheme.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
FeedIconButton(
|
||||
|
||||
@@ -4,14 +4,16 @@ import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cub
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/duration_choice_filter_widget.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/price_choice_filter_widget.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/type_choice_filter_widget.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PlansFilterBottomSheet extends StatelessWidget {
|
||||
const PlansFilterBottomSheet({super.key, required this.plansCubit});
|
||||
final PlansCubit plansCubit;
|
||||
const PlansFilterBottomSheet({super.key, this.plansCubit, this.myPlansCubit});
|
||||
final PlansCubit? plansCubit;
|
||||
final MyPlansCubit? myPlansCubit;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
@@ -30,7 +32,7 @@ class PlansFilterBottomSheet extends StatelessWidget {
|
||||
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
PriceChoiceFilterWidget(plansCubit: plansCubit),
|
||||
PriceChoiceFilterWidget(plansCubit: plansCubit, myPlansCubit: myPlansCubit,),
|
||||
SizedBox(height: 16),
|
||||
CustomDivider(),
|
||||
SizedBox(
|
||||
@@ -45,6 +47,7 @@ class PlansFilterBottomSheet extends StatelessWidget {
|
||||
),
|
||||
DurationChoiceFilterWidget(
|
||||
plansCubit: plansCubit,
|
||||
myPlansCubit: myPlansCubit,
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
@@ -62,6 +65,7 @@ class PlansFilterBottomSheet extends StatelessWidget {
|
||||
),
|
||||
TypeChoiceFilterWidget(
|
||||
plansCubit: plansCubit,
|
||||
myPlansCubit: myPlansCubit,
|
||||
),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
|
||||
@@ -3,12 +3,19 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_state.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class PriceChoiceFilterWidget extends StatefulWidget {
|
||||
PriceChoiceFilterWidget({super.key, required this.plansCubit});
|
||||
final PlansCubit plansCubit;
|
||||
PriceChoiceFilterWidget({
|
||||
super.key,
|
||||
required this.plansCubit,
|
||||
this.myPlansCubit,
|
||||
});
|
||||
final PlansCubit? plansCubit;
|
||||
final MyPlansCubit? myPlansCubit;
|
||||
|
||||
@override
|
||||
State<PriceChoiceFilterWidget> createState() => _PriceChoiceFilterWidgetState();
|
||||
@@ -44,6 +51,164 @@ class _PriceChoiceFilterWidgetState extends State<PriceChoiceFilterWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.myPlansCubit != null) {
|
||||
return BlocBuilder<MyPlansCubit, MyPlansState>(
|
||||
bloc: widget.myPlansCubit,
|
||||
builder: (context, state) {
|
||||
final minPlanPrice = switch (state.selectedStateIndex) {
|
||||
1 => state.minDraftPlanPrice,
|
||||
_ => state.minActivePlanPrice,
|
||||
};
|
||||
|
||||
final maxPlanPrice = switch (state.selectedStateIndex) {
|
||||
1 => state.maxDraftPlanPrice,
|
||||
_ => state.maxActivePlanPrice,
|
||||
};
|
||||
|
||||
final minPriceFilter = switch (state.selectedStateIndex) {
|
||||
1 => state.draftFilterState.priceFilter.$1,
|
||||
_ => state.activeFilterState.priceFilter.$1,
|
||||
};
|
||||
|
||||
final maxPriceFilter = switch (state.selectedStateIndex) {
|
||||
1 => state.draftFilterState.priceFilter.$2,
|
||||
_ => state.activeFilterState.priceFilter.$2,
|
||||
};
|
||||
|
||||
prevMinPrice ??= minPlanPrice;
|
||||
prevMaxPrice ??= maxPlanPrice;
|
||||
|
||||
minPriceController.text = minPriceFilter?.toStringAsFixed(0) ?? '₽';
|
||||
maxPriceController.text = maxPriceFilter?.toStringAsFixed(0) ?? '₽';
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomTextFormField(
|
||||
keyboardType: TextInputType.number,
|
||||
controller: minPriceController,
|
||||
onChanged: (value) {
|
||||
final price = int.tryParse(value.replaceAll('₽', ''));
|
||||
if (price == null) {
|
||||
EasyDebounce.cancel('changeMinPriceFilter');
|
||||
minPriceController.value = TextEditingValue(
|
||||
text: '₽',
|
||||
selection: TextSelection.collapsed(offset: 0),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (price < minPlanPrice) {
|
||||
minPriceController.value = TextEditingValue(
|
||||
text: '${prevMinPrice ?? ''}₽',
|
||||
selection: TextSelection.collapsed(offset: prevMinPrice.toString().length),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (price > (maxPriceFilter ?? maxPlanPrice)) {
|
||||
minPriceController.value = TextEditingValue(
|
||||
text: '${prevMinPrice ?? ''}₽',
|
||||
selection: TextSelection.collapsed(offset: prevMinPrice.toString().length),
|
||||
);
|
||||
EasyDebounce.cancel('changeMinPriceFilter');
|
||||
widget.myPlansCubit?.changePriceFilter(
|
||||
prevMinPrice ?? minPlanPrice,
|
||||
maxPriceFilter ?? maxPlanPrice,
|
||||
);
|
||||
return;
|
||||
}
|
||||
prevMinPrice = price;
|
||||
EasyDebounce.debounce(
|
||||
'changeMinPriceFilter',
|
||||
Duration(seconds: 2),
|
||||
() => widget.myPlansCubit?.changePriceFilter(
|
||||
price,
|
||||
maxPriceFilter ?? maxPlanPrice,
|
||||
disableDebounce: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: CustomTextFormField(
|
||||
onChanged: (value) {
|
||||
final price = int.tryParse(value.replaceAll('₽', ''));
|
||||
if (price == null) {
|
||||
EasyDebounce.cancel('changeMaxPriceFilter');
|
||||
maxPriceController.value = TextEditingValue(
|
||||
text: '₽',
|
||||
selection: TextSelection.collapsed(offset: 0),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (price > maxPlanPrice) {
|
||||
maxPriceController.value = TextEditingValue(
|
||||
text: '${prevMaxPrice ?? ''}₽',
|
||||
selection: TextSelection.collapsed(offset: prevMaxPrice.toString().length),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (price < (minPriceFilter ?? minPlanPrice)) {
|
||||
maxPriceController.value = TextEditingValue(
|
||||
text: '${prevMaxPrice ?? ''}₽',
|
||||
selection: TextSelection.collapsed(offset: prevMaxPrice.toString().length),
|
||||
);
|
||||
EasyDebounce.cancel('changeMaxPriceFilter');
|
||||
widget.myPlansCubit?.changePriceFilter(
|
||||
minPriceFilter ?? minPlanPrice,
|
||||
prevMaxPrice ?? maxPlanPrice,
|
||||
);
|
||||
return;
|
||||
}
|
||||
prevMaxPrice = price;
|
||||
EasyDebounce.debounce(
|
||||
'changeMaxPriceFilter',
|
||||
Duration(seconds: 2),
|
||||
() => widget.myPlansCubit?.changePriceFilter(
|
||||
minPriceFilter ?? minPlanPrice,
|
||||
price,
|
||||
disableDebounce: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
keyboardType: TextInputType.number,
|
||||
controller: maxPriceController,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
sliderTheme: SliderThemeData(
|
||||
overlayShape: SliderComponentShape.noOverlay,
|
||||
),
|
||||
),
|
||||
child: RangeSlider(
|
||||
activeColor: appTheme.blueA400,
|
||||
inactiveColor: appTheme.blueA400,
|
||||
min: minPlanPrice.toDouble(),
|
||||
max: maxPlanPrice.toDouble(),
|
||||
onChanged: (values) {
|
||||
if (values.start <= values.end) {
|
||||
widget.myPlansCubit?.changePriceFilter(values.start.toInt(), values.end.toInt());
|
||||
}
|
||||
},
|
||||
values: RangeValues(
|
||||
minPriceFilter?.toDouble() ?? minPlanPrice.toDouble(),
|
||||
maxPriceFilter?.toDouble() ?? maxPlanPrice.toDouble(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
return BlocBuilder<PlansCubit, PlansState>(
|
||||
bloc: widget.plansCubit,
|
||||
builder: (context, state) {
|
||||
@@ -84,7 +249,7 @@ class _PriceChoiceFilterWidgetState extends State<PriceChoiceFilterWidget> {
|
||||
selection: TextSelection.collapsed(offset: prevMinPrice.toString().length),
|
||||
);
|
||||
EasyDebounce.cancel('changeMinPriceFilter');
|
||||
widget.plansCubit.changePriceFilter(
|
||||
widget.plansCubit?.changePriceFilter(
|
||||
prevMinPrice ?? state.minPlanPrice,
|
||||
state.filterState.priceFilter.$2 ?? state.maxPlanPrice,
|
||||
);
|
||||
@@ -94,7 +259,7 @@ class _PriceChoiceFilterWidgetState extends State<PriceChoiceFilterWidget> {
|
||||
EasyDebounce.debounce(
|
||||
'changeMinPriceFilter',
|
||||
Duration(seconds: 2),
|
||||
() => widget.plansCubit.changePriceFilter(
|
||||
() => widget.plansCubit?.changePriceFilter(
|
||||
price,
|
||||
state.filterState.priceFilter.$2 ?? state.maxPlanPrice,
|
||||
disableDebounce: true,
|
||||
@@ -129,7 +294,7 @@ class _PriceChoiceFilterWidgetState extends State<PriceChoiceFilterWidget> {
|
||||
selection: TextSelection.collapsed(offset: prevMaxPrice.toString().length),
|
||||
);
|
||||
EasyDebounce.cancel('changeMaxPriceFilter');
|
||||
widget.plansCubit.changePriceFilter(
|
||||
widget.plansCubit?.changePriceFilter(
|
||||
state.filterState.priceFilter.$1 ?? state.minPlanPrice,
|
||||
prevMaxPrice ?? state.maxPlanPrice,
|
||||
);
|
||||
@@ -139,7 +304,7 @@ class _PriceChoiceFilterWidgetState extends State<PriceChoiceFilterWidget> {
|
||||
EasyDebounce.debounce(
|
||||
'changeMaxPriceFilter',
|
||||
Duration(seconds: 2),
|
||||
() => widget.plansCubit.changePriceFilter(
|
||||
() => widget.plansCubit?.changePriceFilter(
|
||||
state.filterState.priceFilter.$1 ?? state.minPlanPrice,
|
||||
price,
|
||||
disableDebounce: true,
|
||||
@@ -166,7 +331,7 @@ class _PriceChoiceFilterWidgetState extends State<PriceChoiceFilterWidget> {
|
||||
max: state.maxPlanPrice.toDouble(),
|
||||
onChanged: (values) {
|
||||
if (values.start <= values.end) {
|
||||
widget.plansCubit.changePriceFilter(values.start.toInt(), values.end.toInt());
|
||||
widget.plansCubit?.changePriceFilter(values.start.toInt(), values.end.toInt());
|
||||
}
|
||||
},
|
||||
values: RangeValues(
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_drop_down_button.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/my_diets/presentation/cubit/my_diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -9,8 +11,9 @@ import 'package:collection/collection.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class SortingDropDownFilter extends StatefulWidget {
|
||||
const SortingDropDownFilter({super.key, required this.cubit});
|
||||
final DietsCubit cubit;
|
||||
const SortingDropDownFilter({super.key, this.dietsCubit, this.myDietsCubit});
|
||||
final DietsCubit? dietsCubit;
|
||||
final MyDietsCubit? myDietsCubit;
|
||||
@override
|
||||
State<SortingDropDownFilter> createState() => _SortingDropDownFilterState();
|
||||
}
|
||||
@@ -33,9 +36,40 @@ class _SortingDropDownFilterState extends State<SortingDropDownFilter> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<DietsCubit, DietsState>(
|
||||
bloc: widget.cubit,
|
||||
if (widget.dietsCubit != null) {
|
||||
return BlocBuilder<DietsCubit, DietsState>(
|
||||
bloc: widget.dietsCubit!,
|
||||
builder: (context, state) {
|
||||
return CustomDropDownButton(
|
||||
items: sortingTypes
|
||||
.mapIndexed(
|
||||
(index, value) => DropdownMenuItem(
|
||||
child: Text(
|
||||
sortingTypes[index],
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
value: sortingTypes[index],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
value: sortingTypes[state.filterState.sortingIndex],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
widget.dietsCubit!.changeSortingType(sortingTypes.indexOf(value));
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
return BlocBuilder<MyDietsCubit, MyDietsState>(
|
||||
bloc: widget.myDietsCubit!,
|
||||
builder: (context, state) {
|
||||
final sortingIndex = switch (state.selectedStateIndex) {
|
||||
1 => state.draftFilterState.sortingIndex,
|
||||
2 => state.personalFilterState.sortingIndex,
|
||||
_ => state.activeFilterState.sortingIndex
|
||||
};
|
||||
return CustomDropDownButton(
|
||||
items: sortingTypes
|
||||
.mapIndexed(
|
||||
@@ -48,10 +82,10 @@ class _SortingDropDownFilterState extends State<SortingDropDownFilter> {
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
value: sortingTypes[state.filterState.sortingIndex],
|
||||
value: sortingTypes[sortingIndex],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
widget.cubit.changeSortingType(sortingTypes.indexOf(value));
|
||||
widget.myDietsCubit!.changeSortingType(sortingTypes.indexOf(value));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -3,6 +3,10 @@ import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_sta
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_state.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/choice_chip_widget.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -12,9 +16,13 @@ class TypeChoiceFilterWidget extends StatefulWidget {
|
||||
super.key,
|
||||
this.dietsCubit,
|
||||
this.plansCubit,
|
||||
this.myDietsCubit,
|
||||
this.myPlansCubit,
|
||||
});
|
||||
final DietsCubit? dietsCubit;
|
||||
final PlansCubit? plansCubit;
|
||||
final MyDietsCubit? myDietsCubit;
|
||||
final MyPlansCubit? myPlansCubit;
|
||||
@override
|
||||
State<TypeChoiceFilterWidget> createState() => _TypeChoiceFilterWidgetState();
|
||||
}
|
||||
@@ -25,7 +33,7 @@ class _TypeChoiceFilterWidgetState extends State<TypeChoiceFilterWidget> {
|
||||
void didChangeDependencies() {
|
||||
final s = S.of(context);
|
||||
super.didChangeDependencies();
|
||||
if (widget.dietsCubit != null) {
|
||||
if (widget.dietsCubit != null || widget.myDietsCubit != null) {
|
||||
if (chipsLabelList.isEmpty) {
|
||||
chipsLabelList.add(s.all);
|
||||
chipsLabelList.add(s.vegan_type);
|
||||
@@ -52,124 +60,102 @@ class _TypeChoiceFilterWidgetState extends State<TypeChoiceFilterWidget> {
|
||||
builder: (context, state) {
|
||||
return Wrap(
|
||||
children: [
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 0,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeTypeFilter(0);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 1,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeTypeFilter(1);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 2,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeTypeFilter(2);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 3,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeTypeFilter(3);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 4,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeTypeFilter(4);
|
||||
},
|
||||
...List.generate(
|
||||
chipsLabelList.length,
|
||||
(index) => Padding(
|
||||
padding: EdgeInsets.only(right: index != chipsLabelList.length - 1 ? 8.0 : 0),
|
||||
child: ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: index,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.plansCubit?.changeTypeFilter(index);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
} else if (widget.dietsCubit != null) {
|
||||
return BlocBuilder<DietsCubit, DietsState>(
|
||||
bloc: widget.dietsCubit,
|
||||
builder: (context, state) {
|
||||
return Wrap(
|
||||
children: [
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 0,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietsCubit?.changeTypeFilter(0);
|
||||
},
|
||||
...List.generate(
|
||||
chipsLabelList.length,
|
||||
(index) => Padding(
|
||||
padding: EdgeInsets.only(right: index != chipsLabelList.length - 1 ? 8.0 : 0),
|
||||
child: ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: index,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietsCubit?.changeTypeFilter(index);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 1,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietsCubit?.changeTypeFilter(1);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 2,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietsCubit?.changeTypeFilter(2);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 3,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietsCubit?.changeTypeFilter(3);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: 4,
|
||||
selectedChipIndex: state.filterState.typeIndex,
|
||||
onSelected: (selectedValue) {
|
||||
widget.dietsCubit?.changeTypeFilter(4);
|
||||
},
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else if (widget.myPlansCubit != null) {
|
||||
return BlocBuilder<MyPlansCubit, MyPlansState>(
|
||||
bloc: widget.myPlansCubit,
|
||||
builder: (context, state) {
|
||||
return Wrap(
|
||||
children: [
|
||||
...List.generate(
|
||||
chipsLabelList.length,
|
||||
(index) => Padding(
|
||||
padding: EdgeInsets.only(right: index != chipsLabelList.length - 1 ? 8.0 : 0),
|
||||
child: ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: index,
|
||||
selectedChipIndex: switch (state.selectedStateIndex) {
|
||||
1 => state.draftFilterState.typeIndex,
|
||||
_ => state.activeFilterState.typeIndex
|
||||
},
|
||||
onSelected: (selectedValue) {
|
||||
widget.myPlansCubit?.changeTypeFilter(index);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
return BlocBuilder<MyDietsCubit, MyDietsState>(
|
||||
bloc: widget.myDietsCubit,
|
||||
builder: (context, state) {
|
||||
return Wrap(
|
||||
children: [
|
||||
...List.generate(
|
||||
chipsLabelList.length,
|
||||
(index) => Padding(
|
||||
padding: EdgeInsets.only(right: index != chipsLabelList.length - 1 ? 8.0 : 0),
|
||||
child: ChoiceChipWidget(
|
||||
chipsLabelList: chipsLabelList,
|
||||
index: index,
|
||||
selectedChipIndex: switch (state.selectedStateIndex) {
|
||||
1 => state.draftFilterState.typeIndex,
|
||||
2 => state.personalFilterState.typeIndex,
|
||||
_ => state.activeFilterState.typeIndex
|
||||
},
|
||||
onSelected: (selectedValue) {
|
||||
widget.myDietsCubit?.changeTypeFilter(index);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/core/utils/image_constant.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -36,13 +38,13 @@ class MenuScreen extends StatelessWidget {
|
||||
_tile(
|
||||
leading: 'assets/icons/diets.svg',
|
||||
title: 'Мои диеты',
|
||||
onTap: () => GoRouter.of(context).push('/diet-select-screen'),
|
||||
onTap: () => GoRouter.of(context).push(MyDietsScreen.path),
|
||||
),
|
||||
_divider(context),
|
||||
_tile(
|
||||
leading: ImageConstant.icnPlans,
|
||||
title: 'Мои планы',
|
||||
onTap: () => GoRouter.of(context).push('/plan-select-screen'),
|
||||
onTap: () => GoRouter.of(context).push(MyPlansScreen.path),
|
||||
),
|
||||
_divider(context),
|
||||
_tile(
|
||||
|
||||
250
lib/features/my_diets/presentation/cubit/my_diets_cubit.dart
Normal file
250
lib/features/my_diets/presentation/cubit/my_diets_cubit.dart
Normal file
@@ -0,0 +1,250 @@
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart';
|
||||
import 'package:eatvio_app/features/my_diets/repositories/my_diets_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MyDietsCubit extends Cubit<MyDietsState> {
|
||||
final MyDietsRepository myDietsRepository;
|
||||
|
||||
int currentDraftPage = 1;
|
||||
int currentPersonalPage = 1;
|
||||
int currentActivePage = 1;
|
||||
late int lastDraftPage;
|
||||
late int lastPersonalPage;
|
||||
late int lastActivePage;
|
||||
bool isLoadingDraft = false;
|
||||
bool isLoadingPersonal = false;
|
||||
bool isLoadingActive = false;
|
||||
String? _activeQuery;
|
||||
String? _personalQuery;
|
||||
String? _draftQuery;
|
||||
|
||||
static const List<String?> _sortingValues = [
|
||||
null,
|
||||
'chosen_diets_count:desc',
|
||||
'created_at:desc',
|
||||
'created_at',
|
||||
'name',
|
||||
'name:desc',
|
||||
];
|
||||
static const List<int?> _durationValues = [null, ...durationValues];
|
||||
static const List<String?> _types = [null, ...dietTypeValues];
|
||||
|
||||
MyDietsCubit({required this.myDietsRepository}) : super(MyDietsState());
|
||||
|
||||
void changeTypeFilter(int index) {
|
||||
if (state.selectedStateIndex == 0) {
|
||||
emit(state.copyWith(activeFilterState: state.activeFilterState.copyWith(typeIndex: index), error: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadDiets',
|
||||
Duration(seconds: 1),
|
||||
() => loadDiets(
|
||||
query: _activeQuery,
|
||||
),
|
||||
);
|
||||
} else if (state.selectedStateIndex == 1) {
|
||||
emit(state.copyWith(draftFilterState: state.draftFilterState.copyWith(typeIndex: index), error: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadDiets',
|
||||
Duration(seconds: 1),
|
||||
() => loadDiets(
|
||||
query: _draftQuery,
|
||||
),
|
||||
);
|
||||
} else if (state.selectedStateIndex == 2) {
|
||||
emit(state.copyWith(personalFilterState: state.personalFilterState.copyWith(typeIndex: index), error: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadDiets',
|
||||
Duration(seconds: 1),
|
||||
() => loadDiets(
|
||||
query: _personalQuery,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void changeSortingType(int index) {
|
||||
if (state.selectedStateIndex == 0) {
|
||||
emit(state.copyWith(activeFilterState: state.activeFilterState.copyWith(sortingIndex: index), error: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadDiets',
|
||||
Duration(seconds: 1),
|
||||
() => loadDiets(
|
||||
query: _activeQuery,
|
||||
),
|
||||
);
|
||||
} else if (state.selectedStateIndex == 1) {
|
||||
emit(state.copyWith(draftFilterState: state.draftFilterState.copyWith(sortingIndex: index), error: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadDiets',
|
||||
Duration(seconds: 1),
|
||||
() => loadDiets(
|
||||
query: _draftQuery,
|
||||
),
|
||||
);
|
||||
} else if (state.selectedStateIndex == 2) {
|
||||
emit(state.copyWith(personalFilterState: state.personalFilterState.copyWith(sortingIndex: index), error: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadDiets',
|
||||
Duration(seconds: 1),
|
||||
() => loadDiets(
|
||||
query: _personalQuery,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void changeDurationFilter(int index) {
|
||||
if (state.selectedStateIndex == 0) {
|
||||
emit(state.copyWith(activeFilterState: state.activeFilterState.copyWith(durationIndex: index), error: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadDiets',
|
||||
Duration(seconds: 1),
|
||||
() => loadDiets(
|
||||
query: _activeQuery,
|
||||
),
|
||||
);
|
||||
} else if (state.selectedStateIndex == 1) {
|
||||
emit(state.copyWith(draftFilterState: state.draftFilterState.copyWith(durationIndex: index), error: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadDiets',
|
||||
Duration(seconds: 1),
|
||||
() => loadDiets(
|
||||
query: _draftQuery,
|
||||
),
|
||||
);
|
||||
} else if (state.selectedStateIndex == 2) {
|
||||
emit(state.copyWith(personalFilterState: state.personalFilterState.copyWith(durationIndex: index), error: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadDiets',
|
||||
Duration(seconds: 1),
|
||||
() => loadDiets(
|
||||
query: _personalQuery,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void changeSelectedStateIndex(int index) {
|
||||
emit(state.copyWith(selectedStateIndex: index));
|
||||
}
|
||||
|
||||
Future<void> loadDiets({
|
||||
String? query,
|
||||
}) async {
|
||||
switch (state.selectedStateIndex) {
|
||||
case 0:
|
||||
await _loadActiveDiets(query: query);
|
||||
break;
|
||||
case 1:
|
||||
await _loadDraftDiets(query: query);
|
||||
break;
|
||||
case 2:
|
||||
await _loadPersonalDiets(query: query);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadAllDiets() async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
await _loadActiveDiets();
|
||||
await _loadDraftDiets();
|
||||
await _loadPersonalDiets();
|
||||
emit(state.copyWith(isLoading: false));
|
||||
}
|
||||
|
||||
Future<void> _loadActiveDiets({String? query}) async {
|
||||
try {
|
||||
_activeQuery = query;
|
||||
final type = _types[state.activeFilterState.typeIndex];
|
||||
final duration = _durationValues[state.activeFilterState.durationIndex];
|
||||
final sorting = _sortingValues[state.activeFilterState.sortingIndex];
|
||||
final dietsPagination = await myDietsRepository.getDiets(
|
||||
query: query,
|
||||
types: type,
|
||||
duration: duration,
|
||||
sorting: sorting,
|
||||
);
|
||||
lastActivePage = dietsPagination.meta.lastPage;
|
||||
emit(
|
||||
state.copyWith(
|
||||
activeDiets: dietsPagination.data,
|
||||
isLoading: isLoadingActive,
|
||||
error: null,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(error: e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadDraftDiets({String? query}) async {
|
||||
try {
|
||||
_draftQuery = query;
|
||||
final type = _types[state.draftFilterState.typeIndex];
|
||||
final duration = _durationValues[state.draftFilterState.durationIndex];
|
||||
final sorting = _sortingValues[state.draftFilterState.sortingIndex];
|
||||
final dietsPagination = await myDietsRepository.getDiets(
|
||||
query: query,
|
||||
types: type,
|
||||
duration: duration,
|
||||
sorting: sorting,
|
||||
);
|
||||
lastActivePage = dietsPagination.meta.lastPage;
|
||||
emit(
|
||||
state.copyWith(
|
||||
draftDiets: dietsPagination.data,
|
||||
isLoading: isLoadingDraft,
|
||||
error: null,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(error: e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadPersonalDiets({String? query}) async {
|
||||
try {
|
||||
_personalQuery = query;
|
||||
final type = _types[state.personalFilterState.typeIndex];
|
||||
final duration = _durationValues[state.personalFilterState.durationIndex];
|
||||
final sorting = _sortingValues[state.personalFilterState.sortingIndex];
|
||||
final dietsPagination = await myDietsRepository.getDiets(
|
||||
query: query,
|
||||
types: type,
|
||||
duration: duration,
|
||||
sorting: sorting,
|
||||
);
|
||||
lastActivePage = dietsPagination.meta.lastPage;
|
||||
emit(
|
||||
state.copyWith(
|
||||
personalDiets: dietsPagination.data,
|
||||
isLoading: isLoadingPersonal,
|
||||
error: null,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(error: e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> deleteDiet(int dietId) async {
|
||||
try {
|
||||
await myDietsRepository.deleteDiet(dietId);
|
||||
await loadAllDiets();
|
||||
return true;
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(error: e.toString()),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
lib/features/my_diets/presentation/cubit/my_diets_state.dart
Normal file
41
lib/features/my_diets/presentation/cubit/my_diets_state.dart
Normal file
@@ -0,0 +1,41 @@
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'my_diets_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class MyDietsState with _$MyDietsState {
|
||||
factory MyDietsState({
|
||||
@Default([]) List<Diet> activeDiets,
|
||||
@Default([]) List<Diet> draftDiets,
|
||||
@Default([]) List<Diet> personalDiets,
|
||||
@Default(false) bool isLoading,
|
||||
@Default(null) String? error,
|
||||
@Default(0) int selectedStateIndex,
|
||||
@Default(
|
||||
const DietsFilterState(
|
||||
durationIndex: 0,
|
||||
sortingIndex: 0,
|
||||
typeIndex: 0,
|
||||
),
|
||||
)
|
||||
DietsFilterState activeFilterState,
|
||||
@Default(
|
||||
const DietsFilterState(
|
||||
durationIndex: 0,
|
||||
sortingIndex: 0,
|
||||
typeIndex: 0,
|
||||
),
|
||||
)
|
||||
DietsFilterState draftFilterState,
|
||||
@Default(
|
||||
const DietsFilterState(
|
||||
durationIndex: 0,
|
||||
sortingIndex: 0,
|
||||
typeIndex: 0,
|
||||
),
|
||||
)
|
||||
DietsFilterState personalFilterState,
|
||||
}) = _MyDietsState;
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'my_diets_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$MyDietsState {
|
||||
List<Diet> get activeDiets => throw _privateConstructorUsedError;
|
||||
List<Diet> get draftDiets => throw _privateConstructorUsedError;
|
||||
List<Diet> get personalDiets => throw _privateConstructorUsedError;
|
||||
bool get isLoading => throw _privateConstructorUsedError;
|
||||
String? get error => throw _privateConstructorUsedError;
|
||||
int get selectedStateIndex => throw _privateConstructorUsedError;
|
||||
DietsFilterState get activeFilterState => throw _privateConstructorUsedError;
|
||||
DietsFilterState get draftFilterState => throw _privateConstructorUsedError;
|
||||
DietsFilterState get personalFilterState =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of MyDietsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$MyDietsStateCopyWith<MyDietsState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $MyDietsStateCopyWith<$Res> {
|
||||
factory $MyDietsStateCopyWith(
|
||||
MyDietsState value, $Res Function(MyDietsState) then) =
|
||||
_$MyDietsStateCopyWithImpl<$Res, MyDietsState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{List<Diet> activeDiets,
|
||||
List<Diet> draftDiets,
|
||||
List<Diet> personalDiets,
|
||||
bool isLoading,
|
||||
String? error,
|
||||
int selectedStateIndex,
|
||||
DietsFilterState activeFilterState,
|
||||
DietsFilterState draftFilterState,
|
||||
DietsFilterState personalFilterState});
|
||||
|
||||
$DietsFilterStateCopyWith<$Res> get activeFilterState;
|
||||
$DietsFilterStateCopyWith<$Res> get draftFilterState;
|
||||
$DietsFilterStateCopyWith<$Res> get personalFilterState;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$MyDietsStateCopyWithImpl<$Res, $Val extends MyDietsState>
|
||||
implements $MyDietsStateCopyWith<$Res> {
|
||||
_$MyDietsStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of MyDietsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? activeDiets = null,
|
||||
Object? draftDiets = null,
|
||||
Object? personalDiets = null,
|
||||
Object? isLoading = null,
|
||||
Object? error = freezed,
|
||||
Object? selectedStateIndex = null,
|
||||
Object? activeFilterState = null,
|
||||
Object? draftFilterState = null,
|
||||
Object? personalFilterState = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
activeDiets: null == activeDiets
|
||||
? _value.activeDiets
|
||||
: activeDiets // ignore: cast_nullable_to_non_nullable
|
||||
as List<Diet>,
|
||||
draftDiets: null == draftDiets
|
||||
? _value.draftDiets
|
||||
: draftDiets // ignore: cast_nullable_to_non_nullable
|
||||
as List<Diet>,
|
||||
personalDiets: null == personalDiets
|
||||
? _value.personalDiets
|
||||
: personalDiets // ignore: cast_nullable_to_non_nullable
|
||||
as List<Diet>,
|
||||
isLoading: null == isLoading
|
||||
? _value.isLoading
|
||||
: isLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
error: freezed == error
|
||||
? _value.error
|
||||
: error // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
selectedStateIndex: null == selectedStateIndex
|
||||
? _value.selectedStateIndex
|
||||
: selectedStateIndex // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
activeFilterState: null == activeFilterState
|
||||
? _value.activeFilterState
|
||||
: activeFilterState // ignore: cast_nullable_to_non_nullable
|
||||
as DietsFilterState,
|
||||
draftFilterState: null == draftFilterState
|
||||
? _value.draftFilterState
|
||||
: draftFilterState // ignore: cast_nullable_to_non_nullable
|
||||
as DietsFilterState,
|
||||
personalFilterState: null == personalFilterState
|
||||
? _value.personalFilterState
|
||||
: personalFilterState // ignore: cast_nullable_to_non_nullable
|
||||
as DietsFilterState,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
/// Create a copy of MyDietsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$DietsFilterStateCopyWith<$Res> get activeFilterState {
|
||||
return $DietsFilterStateCopyWith<$Res>(_value.activeFilterState, (value) {
|
||||
return _then(_value.copyWith(activeFilterState: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a copy of MyDietsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$DietsFilterStateCopyWith<$Res> get draftFilterState {
|
||||
return $DietsFilterStateCopyWith<$Res>(_value.draftFilterState, (value) {
|
||||
return _then(_value.copyWith(draftFilterState: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a copy of MyDietsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$DietsFilterStateCopyWith<$Res> get personalFilterState {
|
||||
return $DietsFilterStateCopyWith<$Res>(_value.personalFilterState, (value) {
|
||||
return _then(_value.copyWith(personalFilterState: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$MyDietsStateImplCopyWith<$Res>
|
||||
implements $MyDietsStateCopyWith<$Res> {
|
||||
factory _$$MyDietsStateImplCopyWith(
|
||||
_$MyDietsStateImpl value, $Res Function(_$MyDietsStateImpl) then) =
|
||||
__$$MyDietsStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{List<Diet> activeDiets,
|
||||
List<Diet> draftDiets,
|
||||
List<Diet> personalDiets,
|
||||
bool isLoading,
|
||||
String? error,
|
||||
int selectedStateIndex,
|
||||
DietsFilterState activeFilterState,
|
||||
DietsFilterState draftFilterState,
|
||||
DietsFilterState personalFilterState});
|
||||
|
||||
@override
|
||||
$DietsFilterStateCopyWith<$Res> get activeFilterState;
|
||||
@override
|
||||
$DietsFilterStateCopyWith<$Res> get draftFilterState;
|
||||
@override
|
||||
$DietsFilterStateCopyWith<$Res> get personalFilterState;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$MyDietsStateImplCopyWithImpl<$Res>
|
||||
extends _$MyDietsStateCopyWithImpl<$Res, _$MyDietsStateImpl>
|
||||
implements _$$MyDietsStateImplCopyWith<$Res> {
|
||||
__$$MyDietsStateImplCopyWithImpl(
|
||||
_$MyDietsStateImpl _value, $Res Function(_$MyDietsStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of MyDietsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? activeDiets = null,
|
||||
Object? draftDiets = null,
|
||||
Object? personalDiets = null,
|
||||
Object? isLoading = null,
|
||||
Object? error = freezed,
|
||||
Object? selectedStateIndex = null,
|
||||
Object? activeFilterState = null,
|
||||
Object? draftFilterState = null,
|
||||
Object? personalFilterState = null,
|
||||
}) {
|
||||
return _then(_$MyDietsStateImpl(
|
||||
activeDiets: null == activeDiets
|
||||
? _value._activeDiets
|
||||
: activeDiets // ignore: cast_nullable_to_non_nullable
|
||||
as List<Diet>,
|
||||
draftDiets: null == draftDiets
|
||||
? _value._draftDiets
|
||||
: draftDiets // ignore: cast_nullable_to_non_nullable
|
||||
as List<Diet>,
|
||||
personalDiets: null == personalDiets
|
||||
? _value._personalDiets
|
||||
: personalDiets // ignore: cast_nullable_to_non_nullable
|
||||
as List<Diet>,
|
||||
isLoading: null == isLoading
|
||||
? _value.isLoading
|
||||
: isLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
error: freezed == error
|
||||
? _value.error
|
||||
: error // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
selectedStateIndex: null == selectedStateIndex
|
||||
? _value.selectedStateIndex
|
||||
: selectedStateIndex // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
activeFilterState: null == activeFilterState
|
||||
? _value.activeFilterState
|
||||
: activeFilterState // ignore: cast_nullable_to_non_nullable
|
||||
as DietsFilterState,
|
||||
draftFilterState: null == draftFilterState
|
||||
? _value.draftFilterState
|
||||
: draftFilterState // ignore: cast_nullable_to_non_nullable
|
||||
as DietsFilterState,
|
||||
personalFilterState: null == personalFilterState
|
||||
? _value.personalFilterState
|
||||
: personalFilterState // ignore: cast_nullable_to_non_nullable
|
||||
as DietsFilterState,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$MyDietsStateImpl implements _MyDietsState {
|
||||
_$MyDietsStateImpl(
|
||||
{final List<Diet> activeDiets = const [],
|
||||
final List<Diet> draftDiets = const [],
|
||||
final List<Diet> personalDiets = const [],
|
||||
this.isLoading = false,
|
||||
this.error = null,
|
||||
this.selectedStateIndex = 0,
|
||||
this.activeFilterState = const DietsFilterState(
|
||||
durationIndex: 0, sortingIndex: 0, typeIndex: 0),
|
||||
this.draftFilterState = const DietsFilterState(
|
||||
durationIndex: 0, sortingIndex: 0, typeIndex: 0),
|
||||
this.personalFilterState = const DietsFilterState(
|
||||
durationIndex: 0, sortingIndex: 0, typeIndex: 0)})
|
||||
: _activeDiets = activeDiets,
|
||||
_draftDiets = draftDiets,
|
||||
_personalDiets = personalDiets;
|
||||
|
||||
final List<Diet> _activeDiets;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<Diet> get activeDiets {
|
||||
if (_activeDiets is EqualUnmodifiableListView) return _activeDiets;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_activeDiets);
|
||||
}
|
||||
|
||||
final List<Diet> _draftDiets;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<Diet> get draftDiets {
|
||||
if (_draftDiets is EqualUnmodifiableListView) return _draftDiets;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_draftDiets);
|
||||
}
|
||||
|
||||
final List<Diet> _personalDiets;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<Diet> get personalDiets {
|
||||
if (_personalDiets is EqualUnmodifiableListView) return _personalDiets;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_personalDiets);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isLoading;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String? error;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int selectedStateIndex;
|
||||
@override
|
||||
@JsonKey()
|
||||
final DietsFilterState activeFilterState;
|
||||
@override
|
||||
@JsonKey()
|
||||
final DietsFilterState draftFilterState;
|
||||
@override
|
||||
@JsonKey()
|
||||
final DietsFilterState personalFilterState;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'MyDietsState(activeDiets: $activeDiets, draftDiets: $draftDiets, personalDiets: $personalDiets, isLoading: $isLoading, error: $error, selectedStateIndex: $selectedStateIndex, activeFilterState: $activeFilterState, draftFilterState: $draftFilterState, personalFilterState: $personalFilterState)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$MyDietsStateImpl &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._activeDiets, _activeDiets) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._draftDiets, _draftDiets) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._personalDiets, _personalDiets) &&
|
||||
(identical(other.isLoading, isLoading) ||
|
||||
other.isLoading == isLoading) &&
|
||||
(identical(other.error, error) || other.error == error) &&
|
||||
(identical(other.selectedStateIndex, selectedStateIndex) ||
|
||||
other.selectedStateIndex == selectedStateIndex) &&
|
||||
(identical(other.activeFilterState, activeFilterState) ||
|
||||
other.activeFilterState == activeFilterState) &&
|
||||
(identical(other.draftFilterState, draftFilterState) ||
|
||||
other.draftFilterState == draftFilterState) &&
|
||||
(identical(other.personalFilterState, personalFilterState) ||
|
||||
other.personalFilterState == personalFilterState));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
const DeepCollectionEquality().hash(_activeDiets),
|
||||
const DeepCollectionEquality().hash(_draftDiets),
|
||||
const DeepCollectionEquality().hash(_personalDiets),
|
||||
isLoading,
|
||||
error,
|
||||
selectedStateIndex,
|
||||
activeFilterState,
|
||||
draftFilterState,
|
||||
personalFilterState);
|
||||
|
||||
/// Create a copy of MyDietsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$MyDietsStateImplCopyWith<_$MyDietsStateImpl> get copyWith =>
|
||||
__$$MyDietsStateImplCopyWithImpl<_$MyDietsStateImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _MyDietsState implements MyDietsState {
|
||||
factory _MyDietsState(
|
||||
{final List<Diet> activeDiets,
|
||||
final List<Diet> draftDiets,
|
||||
final List<Diet> personalDiets,
|
||||
final bool isLoading,
|
||||
final String? error,
|
||||
final int selectedStateIndex,
|
||||
final DietsFilterState activeFilterState,
|
||||
final DietsFilterState draftFilterState,
|
||||
final DietsFilterState personalFilterState}) = _$MyDietsStateImpl;
|
||||
|
||||
@override
|
||||
List<Diet> get activeDiets;
|
||||
@override
|
||||
List<Diet> get draftDiets;
|
||||
@override
|
||||
List<Diet> get personalDiets;
|
||||
@override
|
||||
bool get isLoading;
|
||||
@override
|
||||
String? get error;
|
||||
@override
|
||||
int get selectedStateIndex;
|
||||
@override
|
||||
DietsFilterState get activeFilterState;
|
||||
@override
|
||||
DietsFilterState get draftFilterState;
|
||||
@override
|
||||
DietsFilterState get personalFilterState;
|
||||
|
||||
/// Create a copy of MyDietsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$MyDietsStateImplCopyWith<_$MyDietsStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
194
lib/features/my_diets/presentation/my_diets_screen.dart
Normal file
194
lib/features/my_diets/presentation/my_diets_screen.dart
Normal file
@@ -0,0 +1,194 @@
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.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/loader.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/diet_filter_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/filter_badge.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/widgets/my_diet_widget.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/widgets/my_diets_tab.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class MyDietsScreen extends StatelessWidget {
|
||||
const MyDietsScreen({super.key});
|
||||
static const name = 'myDietsScreen';
|
||||
static const path = '/my-diets-screen';
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(create: (context) => sl<MyDietsCubit>()..loadAllDiets(), child: MyDietsScreenContent());
|
||||
}
|
||||
}
|
||||
|
||||
class MyDietsScreenContent extends StatelessWidget {
|
||||
MyDietsScreenContent({super.key});
|
||||
|
||||
final activeDietsScrollController = ScrollController();
|
||||
final draftDietsScrollController = ScrollController();
|
||||
final personalDietsScrollController = ScrollController();
|
||||
|
||||
final activeDietsSearchController = SearchController();
|
||||
final draftDietsSearchController = SearchController();
|
||||
final personalDietsSearchController = SearchController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Scaffold(
|
||||
backgroundColor: appTheme.gray100,
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
s.my_diets,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
body: BlocBuilder<MyDietsCubit, MyDietsState>(
|
||||
buildWhen: (previous, current) => previous.selectedStateIndex != current.selectedStateIndex,
|
||||
builder: (context, state) {
|
||||
return NestedScrollView(
|
||||
controller: switch (state.selectedStateIndex) {
|
||||
1 => draftDietsScrollController,
|
||||
2 => personalDietsScrollController,
|
||||
_ => activeDietsScrollController
|
||||
},
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverAppBar(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
|
||||
),
|
||||
backgroundColor: appTheme.gray100,
|
||||
foregroundColor: appTheme.gray100,
|
||||
automaticallyImplyLeading: false,
|
||||
stretchTriggerOffset: 1,
|
||||
floating: true,
|
||||
primary: false,
|
||||
snap: true,
|
||||
scrolledUnderElevation: 0,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: Size(
|
||||
MediaQuery.sizeOf(context).width,
|
||||
64,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: MyDietsTab(),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: CustomSearchBar(
|
||||
enableIcon: false,
|
||||
backgroundColor: appTheme.whiteA700,
|
||||
borderRadius: 100,
|
||||
searchController: switch (state.selectedStateIndex) {
|
||||
1 => draftDietsSearchController,
|
||||
2 => personalDietsSearchController,
|
||||
_ => activeDietsSearchController
|
||||
},
|
||||
onChanged: (query) {
|
||||
EasyDebounce.debounce(
|
||||
'searchDiets',
|
||||
Duration(milliseconds: 500),
|
||||
() {
|
||||
context.read<MyDietsCubit>().loadDiets(query: query == '' ? null : query);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
AddItemButton(
|
||||
onTap: () => GoRouter.of(context).pushNamed(CreateDietScreen.name),
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () => context.read<MyDietsCubit>().loadDiets(),
|
||||
child: BlocBuilder<MyDietsCubit, MyDietsState>(
|
||||
builder: (BuildContext context, MyDietsState state) {
|
||||
if (state.isLoading) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: const Loader(),
|
||||
);
|
||||
} else if (state.error != null) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Text(state.error ?? ''),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final diets = switch (state.selectedStateIndex) {
|
||||
1 => state.draftDiets,
|
||||
2 => state.personalDiets,
|
||||
_ => state.activeDiets,
|
||||
};
|
||||
return Stack(
|
||||
children: [
|
||||
SizedBox.expand(),
|
||||
ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return MyDietWidget(diet: diets[index]);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(height: 20);
|
||||
},
|
||||
itemCount: diets.length,
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 74,
|
||||
child: FilterBadge(
|
||||
filterBottomSheet: DietFilterBottomSheet(
|
||||
myDietsCubit: context.read<MyDietsCubit>(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
252
lib/features/my_diets/presentation/widgets/my_diet_widget.dart
Normal file
252
lib/features/my_diets/presentation/widgets/my_diet_widget.dart
Normal file
@@ -0,0 +1,252 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/info_row.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class MyDietWidget extends StatefulWidget {
|
||||
final Diet diet;
|
||||
const MyDietWidget({
|
||||
super.key,
|
||||
required this.diet,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MyDietWidget> createState() => _MyDietWidgetState();
|
||||
}
|
||||
|
||||
class _MyDietWidgetState extends State<MyDietWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Colors.white,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () => GoRouter.of(context).pushNamed('aboutDietScreen', extra: {'diet': widget.diet, 'navigateToComments': false}),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: baseUrl + '/storage/' + widget.diet.image,
|
||||
fit: BoxFit.fitWidth,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
child: SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
child: InfoRow(
|
||||
diet: widget.diet,
|
||||
itemsBackgroundColor: appTheme.whiteA700,
|
||||
disableDescriptionOfTypes: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
widget.diet.name,
|
||||
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
if (widget.diet.details.isNotEmpty) ...[
|
||||
SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Html(
|
||||
data: widget.diet.details.length > 200 ? widget.diet.details.substring(0, 200) + '...' : widget.diet.details,
|
||||
style: {
|
||||
"*": Style(
|
||||
margin: Margins.zero,
|
||||
color: appTheme.black,
|
||||
fontSize: FontSize(TextStyles.textSMedium.fontSize ?? 14),
|
||||
fontWeight: TextStyles.textSMedium.fontWeight ?? FontWeight.w400,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
BlocBuilder<MyDietsCubit, MyDietsState>(
|
||||
builder: (context, state) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomOutlinedButton(
|
||||
text: switch (state.selectedStateIndex) {
|
||||
1 => s.publish,
|
||||
2 => s.toOffer,
|
||||
_ => s.edit,
|
||||
},
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (state.selectedStateIndex == 0) ...[
|
||||
SizedBox(width: 6),
|
||||
CustomOutlinedButton(
|
||||
onPressed: () async {
|
||||
await Share.shareUri(
|
||||
Uri.parse(
|
||||
baseUrl + '/ru/diet/' + widget.diet.id.toString(),
|
||||
),
|
||||
);
|
||||
},
|
||||
height: 52,
|
||||
width: 52,
|
||||
leftIcon: SvgPicture.asset(
|
||||
'assets/icons/share2.svg',
|
||||
width: 20,
|
||||
height: 20,
|
||||
colorFilter: ColorFilter.mode(appTheme.whiteA700, BlendMode.srcIn),
|
||||
),
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.red),
|
||||
buttonStyle: ButtonStyle(
|
||||
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
text: '',
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomOutlinedButton(
|
||||
text: switch (state.selectedStateIndex) {
|
||||
0 => s.removeFromActive,
|
||||
_ => s.edit,
|
||||
},
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||
buttonStyle: ButtonStyle(
|
||||
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.lightGray0),
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 6),
|
||||
CustomOutlinedButton(
|
||||
onPressed: () async {
|
||||
final result = await context.read<MyDietsCubit>().deleteDiet(widget.diet.id);
|
||||
if (result)
|
||||
Future.delayed(Duration(seconds: 3), () {
|
||||
showBottomSheetWithBlur(
|
||||
context: context,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
BottomSheetHandlerBar(),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
'${widget.diet.name}',
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(
|
||||
s.deleted,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: CustomOutlinedButton(
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
},
|
||||
text: 'Хорошо',
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
height: 52,
|
||||
width: 52,
|
||||
leftIcon: SvgPicture.asset(
|
||||
'assets/icons/trash_can.svg',
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.red),
|
||||
buttonStyle: ButtonStyle(
|
||||
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.lightGray0),
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
text: '',
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
74
lib/features/my_diets/presentation/widgets/my_diets_tab.dart
Normal file
74
lib/features/my_diets/presentation/widgets/my_diets_tab.dart
Normal file
@@ -0,0 +1,74 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/animated_option_button.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MyDietsTab extends StatefulWidget {
|
||||
const MyDietsTab({super.key, this.additionalOnTap});
|
||||
final void Function()? additionalOnTap;
|
||||
@override
|
||||
State<MyDietsTab> createState() => _MyDietsTabState();
|
||||
}
|
||||
|
||||
class _MyDietsTabState extends State<MyDietsTab> {
|
||||
final List<String> tabTitles = [];
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
final s = S.of(context);
|
||||
if (tabTitles.isEmpty) {
|
||||
tabTitles.add(s.published);
|
||||
tabTitles.add(s.drafted);
|
||||
tabTitles.add(s.personal);
|
||||
}
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<MyDietsCubit, MyDietsState>(
|
||||
builder: (context, state) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 484,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.whiteA700,
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Row(
|
||||
children: [
|
||||
...List.generate(
|
||||
3,
|
||||
(index) => Expanded(
|
||||
child: AnimatedOptionButton(
|
||||
textStyle: TextStyles.textSMedium,
|
||||
isSelected: state.selectedStateIndex == index,
|
||||
text: tabTitles[index],
|
||||
onTap: () {
|
||||
widget.additionalOnTap?.call();
|
||||
context.read<MyDietsCubit>().changeSelectedStateIndex(index);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
36
lib/features/my_diets/repositories/my_diets_repository.dart
Normal file
36
lib/features/my_diets/repositories/my_diets_repository.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/my_diets/services/my_diets_service.dart';
|
||||
|
||||
class MyDietsRepository {
|
||||
final MyDietsService myDietsService;
|
||||
|
||||
MyDietsRepository({required this.myDietsService});
|
||||
|
||||
Future<void> deleteDiet(int dietId) async {
|
||||
try {
|
||||
await myDietsService.deleteDiet(dietId: dietId);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<DietsPagination> getDiets({
|
||||
int page = 1,
|
||||
String? query,
|
||||
String? types,
|
||||
int? duration,
|
||||
String? sorting,
|
||||
}) async {
|
||||
try {
|
||||
return (await myDietsService.getDiets(
|
||||
page: page,
|
||||
query: query,
|
||||
types: types,
|
||||
duration: duration,
|
||||
sorting: sorting,
|
||||
));
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
lib/features/my_diets/services/my_diets_service.dart
Normal file
27
lib/features/my_diets/services/my_diets_service.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:retrofit/retrofit.dart';
|
||||
|
||||
part 'my_diets_service.g.dart';
|
||||
|
||||
@RestApi()
|
||||
abstract class MyDietsService {
|
||||
|
||||
factory MyDietsService(Dio dio, {String baseUrl}) = _MyDietsService;
|
||||
|
||||
@GET('/api/diets/index')
|
||||
Future<DietsPagination> getDiets({
|
||||
@Query('page') required int page,
|
||||
@Query('filters[name][\$startsWith]') String? query,
|
||||
@Query('filters[type][\$contains]') String? types,
|
||||
@Query('filters[duration][\$eq]') int? duration,
|
||||
@Query('sort') String? sorting,
|
||||
});
|
||||
|
||||
@POST('/api/diets/delete/{dietId}')
|
||||
Future<void> deleteDiet({
|
||||
@Path() required int dietId,
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
124
lib/features/my_diets/services/my_diets_service.g.dart
Normal file
124
lib/features/my_diets/services/my_diets_service.g.dart
Normal file
@@ -0,0 +1,124 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'my_diets_service.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RetrofitGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations
|
||||
|
||||
class _MyDietsService implements MyDietsService {
|
||||
_MyDietsService(
|
||||
this._dio, {
|
||||
this.baseUrl,
|
||||
this.errorLogger,
|
||||
});
|
||||
|
||||
final Dio _dio;
|
||||
|
||||
String? baseUrl;
|
||||
|
||||
final ParseErrorLogger? errorLogger;
|
||||
|
||||
@override
|
||||
Future<DietsPagination> getDiets({
|
||||
required int page,
|
||||
String? query,
|
||||
String? types,
|
||||
int? duration,
|
||||
String? sorting,
|
||||
}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{
|
||||
r'page': page,
|
||||
r'filters[name][$startsWith]': query,
|
||||
r'filters[type][$contains]': types,
|
||||
r'filters[duration][$eq]': duration,
|
||||
r'sort': sorting,
|
||||
};
|
||||
queryParameters.removeWhere((k, v) => v == null);
|
||||
final _headers = <String, dynamic>{};
|
||||
const Map<String, dynamic>? _data = null;
|
||||
final _options = _setStreamType<DietsPagination>(Options(
|
||||
method: 'GET',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/diets/index',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
)));
|
||||
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
|
||||
late DietsPagination _value;
|
||||
try {
|
||||
_value = DietsPagination.fromJson(_result.data!);
|
||||
} on Object catch (e, s) {
|
||||
errorLogger?.logError(e, s, _options);
|
||||
rethrow;
|
||||
}
|
||||
return _value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteDiet({required int dietId}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
const Map<String, dynamic>? _data = null;
|
||||
final _options = _setStreamType<void>(Options(
|
||||
method: 'POST',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/diets/delete/${dietId}',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
)));
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
|
||||
if (T != dynamic &&
|
||||
!(requestOptions.responseType == ResponseType.bytes ||
|
||||
requestOptions.responseType == ResponseType.stream)) {
|
||||
if (T == String) {
|
||||
requestOptions.responseType = ResponseType.plain;
|
||||
} else {
|
||||
requestOptions.responseType = ResponseType.json;
|
||||
}
|
||||
}
|
||||
return requestOptions;
|
||||
}
|
||||
|
||||
String _combineBaseUrls(
|
||||
String dioBaseUrl,
|
||||
String? baseUrl,
|
||||
) {
|
||||
if (baseUrl == null || baseUrl.trim().isEmpty) {
|
||||
return dioBaseUrl;
|
||||
}
|
||||
|
||||
final url = Uri.parse(baseUrl);
|
||||
|
||||
if (url.isAbsolute) {
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
return Uri.parse(dioBaseUrl).resolveUri(url).toString();
|
||||
}
|
||||
}
|
||||
207
lib/features/my_plans/presentation/cubit/my_plans_cubit.dart
Normal file
207
lib/features/my_plans/presentation/cubit/my_plans_cubit.dart
Normal file
@@ -0,0 +1,207 @@
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_state.dart';
|
||||
import 'package:eatvio_app/features/my_plans/repositories/my_diets_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MyPlansCubit extends Cubit<MyPlansState> {
|
||||
final MyPlansRepository myPlansRepository;
|
||||
|
||||
int currentDraftPage = 1;
|
||||
int currentActivePage = 1;
|
||||
late int lastDraftPage;
|
||||
late int lastPersonalPage;
|
||||
late int lastActivePage;
|
||||
bool isLoadingDraft = false;
|
||||
bool isLoadingActive = false;
|
||||
String? _activeQuery;
|
||||
String? _draftQuery;
|
||||
|
||||
static const List<int?> _durationValues = [null, ...durationValues];
|
||||
static const List<String?> _types = [null, ...planTypeValues];
|
||||
|
||||
MyPlansCubit({required this.myPlansRepository}) : super(MyPlansState());
|
||||
|
||||
void changePriceFilter(int minPrice, int maxPrice, {bool disableDebounce = false}) {
|
||||
if (state.selectedStateIndex == 0) {
|
||||
emit(state.copyWith(activeFilterState: state.activeFilterState.copyWith(priceFilter: (minPrice, maxPrice)), error: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadPlans',
|
||||
Duration(seconds: disableDebounce ? 0 : 1),
|
||||
() => loadPlans(
|
||||
query: _activeQuery,
|
||||
),
|
||||
);
|
||||
} else if (state.selectedStateIndex == 1) {
|
||||
emit(state.copyWith(draftFilterState: state.draftFilterState.copyWith(priceFilter: (minPrice, maxPrice)), error: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadPlans',
|
||||
Duration(seconds: disableDebounce ? 0 : 1),
|
||||
() => loadPlans(
|
||||
query: _draftQuery,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void changeTypeFilter(int index) {
|
||||
if (state.selectedStateIndex == 0) {
|
||||
emit(state.copyWith(activeFilterState: state.activeFilterState.copyWith(typeIndex: index), error: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadPlans',
|
||||
Duration(seconds: 1),
|
||||
() => loadPlans(
|
||||
query: _activeQuery,
|
||||
),
|
||||
);
|
||||
} else if (state.selectedStateIndex == 1) {
|
||||
emit(state.copyWith(draftFilterState: state.draftFilterState.copyWith(typeIndex: index), error: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadPlans',
|
||||
Duration(seconds: 1),
|
||||
() => loadPlans(
|
||||
query: _draftQuery,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void changeDurationFilter(int index) {
|
||||
if (state.selectedStateIndex == 0) {
|
||||
emit(state.copyWith(activeFilterState: state.activeFilterState.copyWith(durationIndex: index), error: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadPlans',
|
||||
Duration(seconds: 1),
|
||||
() => loadPlans(
|
||||
query: _activeQuery,
|
||||
),
|
||||
);
|
||||
} else if (state.selectedStateIndex == 1) {
|
||||
emit(state.copyWith(draftFilterState: state.draftFilterState.copyWith(durationIndex: index), error: null));
|
||||
EasyDebounce.debounce(
|
||||
'loadPlans',
|
||||
Duration(seconds: 1),
|
||||
() => loadPlans(
|
||||
query: _draftQuery,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void changeSelectedStateIndex(int index) {
|
||||
emit(state.copyWith(selectedStateIndex: index));
|
||||
}
|
||||
|
||||
Future<void> loadPlans({
|
||||
String? query,
|
||||
}) async {
|
||||
switch (state.selectedStateIndex) {
|
||||
case 0:
|
||||
await _loadActivePlans(query: query);
|
||||
break;
|
||||
case 1:
|
||||
await _loadDraftPlans(query: query);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadAllPlans() async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
await _loadActivePlans();
|
||||
await _loadDraftPlans();
|
||||
emit(state.copyWith(isLoading: false));
|
||||
}
|
||||
|
||||
Future<void> _loadActivePlans({String? query}) async {
|
||||
try {
|
||||
_activeQuery = query;
|
||||
final type = _types[state.activeFilterState.typeIndex];
|
||||
final duration = _durationValues[state.activeFilterState.durationIndex];
|
||||
final minPrice = state.activeFilterState.priceFilter.$1;
|
||||
final maxPrice = state.activeFilterState.priceFilter.$2;
|
||||
final plansPagination = await myPlansRepository.getPlans(
|
||||
query: query,
|
||||
duration: duration,
|
||||
maxPrice: maxPrice,
|
||||
minPrice: minPrice,
|
||||
types: type,
|
||||
);
|
||||
lastActivePage = plansPagination.meta.lastPage;
|
||||
final minPlanPrice = plansPagination.meta.totalMinPrice ?? 0;
|
||||
final maxPlanPrice = plansPagination.meta.totalMaxPrice ?? 0;
|
||||
if (state.activeFilterState.priceFilter.$1 == null || state.activeFilterState.priceFilter.$2 == null) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
activeFilterState: state.activeFilterState.copyWith(priceFilter: (minPlanPrice, maxPlanPrice)),
|
||||
),
|
||||
);
|
||||
}
|
||||
emit(
|
||||
state.copyWith(
|
||||
activePlans: plansPagination.data,
|
||||
isLoading: isLoadingActive,
|
||||
error: null,
|
||||
minActivePlanPrice: minPlanPrice,
|
||||
maxActivePlanPrice: maxPlanPrice,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(error: e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadDraftPlans({String? query}) async {
|
||||
try {
|
||||
_draftQuery = query;
|
||||
final type = _types[state.draftFilterState.typeIndex];
|
||||
final duration = _durationValues[state.draftFilterState.durationIndex];
|
||||
final minPrice = state.draftFilterState.priceFilter.$1;
|
||||
final maxPrice = state.draftFilterState.priceFilter.$2;
|
||||
final plansPagination = await myPlansRepository.getPlans(
|
||||
query: query,
|
||||
duration: duration,
|
||||
maxPrice: maxPrice,
|
||||
minPrice: minPrice,
|
||||
types: type,
|
||||
);
|
||||
lastDraftPage = plansPagination.meta.lastPage;
|
||||
final minPlanPrice = plansPagination.meta.totalMinPrice ?? 0;
|
||||
final maxPlanPrice = plansPagination.meta.totalMaxPrice ?? 0;
|
||||
if (state.draftFilterState.priceFilter.$1 == null || state.draftFilterState.priceFilter.$2 == null) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
draftFilterState: state.draftFilterState.copyWith(priceFilter: (minPlanPrice, maxPlanPrice)),
|
||||
),
|
||||
);
|
||||
}
|
||||
emit(
|
||||
state.copyWith(
|
||||
draftPlans: plansPagination.data,
|
||||
isLoading: isLoadingDraft,
|
||||
error: null,
|
||||
minDraftPlanPrice: minPlanPrice,
|
||||
maxDraftPlanPrice: maxPlanPrice,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(error: e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> deletePlan(String slug) async {
|
||||
try {
|
||||
await myPlansRepository.deletePlan(slug);
|
||||
await loadAllPlans();
|
||||
return true;
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(error: e.toString()),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
lib/features/my_plans/presentation/cubit/my_plans_state.dart
Normal file
36
lib/features/my_plans/presentation/cubit/my_plans_state.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_state.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'my_plans_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class MyPlansState with _$MyPlansState {
|
||||
factory MyPlansState({
|
||||
@Default([]) List<PlanModel> activePlans,
|
||||
@Default([]) List<PlanModel> draftPlans,
|
||||
@Default(false) bool isLoading,
|
||||
@Default(null) String? error,
|
||||
@Default(0) int selectedStateIndex,
|
||||
@Default(0) int minActivePlanPrice,
|
||||
@Default(0) int maxActivePlanPrice,
|
||||
@Default(0) int minDraftPlanPrice,
|
||||
@Default(0) int maxDraftPlanPrice,
|
||||
@Default(
|
||||
const PlansFilterState(
|
||||
priceFilter: (null, null),
|
||||
durationIndex: 0,
|
||||
typeIndex: 0,
|
||||
),
|
||||
)
|
||||
PlansFilterState activeFilterState,
|
||||
@Default(
|
||||
const PlansFilterState(
|
||||
priceFilter: (null, null),
|
||||
durationIndex: 0,
|
||||
typeIndex: 0,
|
||||
),
|
||||
)
|
||||
PlansFilterState draftFilterState,
|
||||
}) = _MyPlansState;
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'my_plans_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$MyPlansState {
|
||||
List<PlanModel> get activePlans => throw _privateConstructorUsedError;
|
||||
List<PlanModel> get draftPlans => throw _privateConstructorUsedError;
|
||||
bool get isLoading => throw _privateConstructorUsedError;
|
||||
String? get error => throw _privateConstructorUsedError;
|
||||
int get selectedStateIndex => throw _privateConstructorUsedError;
|
||||
int get minActivePlanPrice => throw _privateConstructorUsedError;
|
||||
int get maxActivePlanPrice => throw _privateConstructorUsedError;
|
||||
int get minDraftPlanPrice => throw _privateConstructorUsedError;
|
||||
int get maxDraftPlanPrice => throw _privateConstructorUsedError;
|
||||
PlansFilterState get activeFilterState => throw _privateConstructorUsedError;
|
||||
PlansFilterState get draftFilterState => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of MyPlansState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$MyPlansStateCopyWith<MyPlansState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $MyPlansStateCopyWith<$Res> {
|
||||
factory $MyPlansStateCopyWith(
|
||||
MyPlansState value, $Res Function(MyPlansState) then) =
|
||||
_$MyPlansStateCopyWithImpl<$Res, MyPlansState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{List<PlanModel> activePlans,
|
||||
List<PlanModel> draftPlans,
|
||||
bool isLoading,
|
||||
String? error,
|
||||
int selectedStateIndex,
|
||||
int minActivePlanPrice,
|
||||
int maxActivePlanPrice,
|
||||
int minDraftPlanPrice,
|
||||
int maxDraftPlanPrice,
|
||||
PlansFilterState activeFilterState,
|
||||
PlansFilterState draftFilterState});
|
||||
|
||||
$PlansFilterStateCopyWith<$Res> get activeFilterState;
|
||||
$PlansFilterStateCopyWith<$Res> get draftFilterState;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$MyPlansStateCopyWithImpl<$Res, $Val extends MyPlansState>
|
||||
implements $MyPlansStateCopyWith<$Res> {
|
||||
_$MyPlansStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of MyPlansState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? activePlans = null,
|
||||
Object? draftPlans = null,
|
||||
Object? isLoading = null,
|
||||
Object? error = freezed,
|
||||
Object? selectedStateIndex = null,
|
||||
Object? minActivePlanPrice = null,
|
||||
Object? maxActivePlanPrice = null,
|
||||
Object? minDraftPlanPrice = null,
|
||||
Object? maxDraftPlanPrice = null,
|
||||
Object? activeFilterState = null,
|
||||
Object? draftFilterState = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
activePlans: null == activePlans
|
||||
? _value.activePlans
|
||||
: activePlans // ignore: cast_nullable_to_non_nullable
|
||||
as List<PlanModel>,
|
||||
draftPlans: null == draftPlans
|
||||
? _value.draftPlans
|
||||
: draftPlans // ignore: cast_nullable_to_non_nullable
|
||||
as List<PlanModel>,
|
||||
isLoading: null == isLoading
|
||||
? _value.isLoading
|
||||
: isLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
error: freezed == error
|
||||
? _value.error
|
||||
: error // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
selectedStateIndex: null == selectedStateIndex
|
||||
? _value.selectedStateIndex
|
||||
: selectedStateIndex // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
minActivePlanPrice: null == minActivePlanPrice
|
||||
? _value.minActivePlanPrice
|
||||
: minActivePlanPrice // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
maxActivePlanPrice: null == maxActivePlanPrice
|
||||
? _value.maxActivePlanPrice
|
||||
: maxActivePlanPrice // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
minDraftPlanPrice: null == minDraftPlanPrice
|
||||
? _value.minDraftPlanPrice
|
||||
: minDraftPlanPrice // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
maxDraftPlanPrice: null == maxDraftPlanPrice
|
||||
? _value.maxDraftPlanPrice
|
||||
: maxDraftPlanPrice // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
activeFilterState: null == activeFilterState
|
||||
? _value.activeFilterState
|
||||
: activeFilterState // ignore: cast_nullable_to_non_nullable
|
||||
as PlansFilterState,
|
||||
draftFilterState: null == draftFilterState
|
||||
? _value.draftFilterState
|
||||
: draftFilterState // ignore: cast_nullable_to_non_nullable
|
||||
as PlansFilterState,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
/// Create a copy of MyPlansState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$PlansFilterStateCopyWith<$Res> get activeFilterState {
|
||||
return $PlansFilterStateCopyWith<$Res>(_value.activeFilterState, (value) {
|
||||
return _then(_value.copyWith(activeFilterState: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a copy of MyPlansState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$PlansFilterStateCopyWith<$Res> get draftFilterState {
|
||||
return $PlansFilterStateCopyWith<$Res>(_value.draftFilterState, (value) {
|
||||
return _then(_value.copyWith(draftFilterState: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$MyPlansStateImplCopyWith<$Res>
|
||||
implements $MyPlansStateCopyWith<$Res> {
|
||||
factory _$$MyPlansStateImplCopyWith(
|
||||
_$MyPlansStateImpl value, $Res Function(_$MyPlansStateImpl) then) =
|
||||
__$$MyPlansStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{List<PlanModel> activePlans,
|
||||
List<PlanModel> draftPlans,
|
||||
bool isLoading,
|
||||
String? error,
|
||||
int selectedStateIndex,
|
||||
int minActivePlanPrice,
|
||||
int maxActivePlanPrice,
|
||||
int minDraftPlanPrice,
|
||||
int maxDraftPlanPrice,
|
||||
PlansFilterState activeFilterState,
|
||||
PlansFilterState draftFilterState});
|
||||
|
||||
@override
|
||||
$PlansFilterStateCopyWith<$Res> get activeFilterState;
|
||||
@override
|
||||
$PlansFilterStateCopyWith<$Res> get draftFilterState;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$MyPlansStateImplCopyWithImpl<$Res>
|
||||
extends _$MyPlansStateCopyWithImpl<$Res, _$MyPlansStateImpl>
|
||||
implements _$$MyPlansStateImplCopyWith<$Res> {
|
||||
__$$MyPlansStateImplCopyWithImpl(
|
||||
_$MyPlansStateImpl _value, $Res Function(_$MyPlansStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of MyPlansState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? activePlans = null,
|
||||
Object? draftPlans = null,
|
||||
Object? isLoading = null,
|
||||
Object? error = freezed,
|
||||
Object? selectedStateIndex = null,
|
||||
Object? minActivePlanPrice = null,
|
||||
Object? maxActivePlanPrice = null,
|
||||
Object? minDraftPlanPrice = null,
|
||||
Object? maxDraftPlanPrice = null,
|
||||
Object? activeFilterState = null,
|
||||
Object? draftFilterState = null,
|
||||
}) {
|
||||
return _then(_$MyPlansStateImpl(
|
||||
activePlans: null == activePlans
|
||||
? _value._activePlans
|
||||
: activePlans // ignore: cast_nullable_to_non_nullable
|
||||
as List<PlanModel>,
|
||||
draftPlans: null == draftPlans
|
||||
? _value._draftPlans
|
||||
: draftPlans // ignore: cast_nullable_to_non_nullable
|
||||
as List<PlanModel>,
|
||||
isLoading: null == isLoading
|
||||
? _value.isLoading
|
||||
: isLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
error: freezed == error
|
||||
? _value.error
|
||||
: error // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
selectedStateIndex: null == selectedStateIndex
|
||||
? _value.selectedStateIndex
|
||||
: selectedStateIndex // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
minActivePlanPrice: null == minActivePlanPrice
|
||||
? _value.minActivePlanPrice
|
||||
: minActivePlanPrice // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
maxActivePlanPrice: null == maxActivePlanPrice
|
||||
? _value.maxActivePlanPrice
|
||||
: maxActivePlanPrice // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
minDraftPlanPrice: null == minDraftPlanPrice
|
||||
? _value.minDraftPlanPrice
|
||||
: minDraftPlanPrice // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
maxDraftPlanPrice: null == maxDraftPlanPrice
|
||||
? _value.maxDraftPlanPrice
|
||||
: maxDraftPlanPrice // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
activeFilterState: null == activeFilterState
|
||||
? _value.activeFilterState
|
||||
: activeFilterState // ignore: cast_nullable_to_non_nullable
|
||||
as PlansFilterState,
|
||||
draftFilterState: null == draftFilterState
|
||||
? _value.draftFilterState
|
||||
: draftFilterState // ignore: cast_nullable_to_non_nullable
|
||||
as PlansFilterState,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$MyPlansStateImpl implements _MyPlansState {
|
||||
_$MyPlansStateImpl(
|
||||
{final List<PlanModel> activePlans = const [],
|
||||
final List<PlanModel> draftPlans = const [],
|
||||
this.isLoading = false,
|
||||
this.error = null,
|
||||
this.selectedStateIndex = 0,
|
||||
this.minActivePlanPrice = 0,
|
||||
this.maxActivePlanPrice = 0,
|
||||
this.minDraftPlanPrice = 0,
|
||||
this.maxDraftPlanPrice = 0,
|
||||
this.activeFilterState = const PlansFilterState(
|
||||
priceFilter: (null, null), durationIndex: 0, typeIndex: 0),
|
||||
this.draftFilterState = const PlansFilterState(
|
||||
priceFilter: (null, null), durationIndex: 0, typeIndex: 0)})
|
||||
: _activePlans = activePlans,
|
||||
_draftPlans = draftPlans;
|
||||
|
||||
final List<PlanModel> _activePlans;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<PlanModel> get activePlans {
|
||||
if (_activePlans is EqualUnmodifiableListView) return _activePlans;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_activePlans);
|
||||
}
|
||||
|
||||
final List<PlanModel> _draftPlans;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<PlanModel> get draftPlans {
|
||||
if (_draftPlans is EqualUnmodifiableListView) return _draftPlans;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_draftPlans);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isLoading;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String? error;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int selectedStateIndex;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int minActivePlanPrice;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int maxActivePlanPrice;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int minDraftPlanPrice;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int maxDraftPlanPrice;
|
||||
@override
|
||||
@JsonKey()
|
||||
final PlansFilterState activeFilterState;
|
||||
@override
|
||||
@JsonKey()
|
||||
final PlansFilterState draftFilterState;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'MyPlansState(activePlans: $activePlans, draftPlans: $draftPlans, isLoading: $isLoading, error: $error, selectedStateIndex: $selectedStateIndex, minActivePlanPrice: $minActivePlanPrice, maxActivePlanPrice: $maxActivePlanPrice, minDraftPlanPrice: $minDraftPlanPrice, maxDraftPlanPrice: $maxDraftPlanPrice, activeFilterState: $activeFilterState, draftFilterState: $draftFilterState)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$MyPlansStateImpl &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._activePlans, _activePlans) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._draftPlans, _draftPlans) &&
|
||||
(identical(other.isLoading, isLoading) ||
|
||||
other.isLoading == isLoading) &&
|
||||
(identical(other.error, error) || other.error == error) &&
|
||||
(identical(other.selectedStateIndex, selectedStateIndex) ||
|
||||
other.selectedStateIndex == selectedStateIndex) &&
|
||||
(identical(other.minActivePlanPrice, minActivePlanPrice) ||
|
||||
other.minActivePlanPrice == minActivePlanPrice) &&
|
||||
(identical(other.maxActivePlanPrice, maxActivePlanPrice) ||
|
||||
other.maxActivePlanPrice == maxActivePlanPrice) &&
|
||||
(identical(other.minDraftPlanPrice, minDraftPlanPrice) ||
|
||||
other.minDraftPlanPrice == minDraftPlanPrice) &&
|
||||
(identical(other.maxDraftPlanPrice, maxDraftPlanPrice) ||
|
||||
other.maxDraftPlanPrice == maxDraftPlanPrice) &&
|
||||
(identical(other.activeFilterState, activeFilterState) ||
|
||||
other.activeFilterState == activeFilterState) &&
|
||||
(identical(other.draftFilterState, draftFilterState) ||
|
||||
other.draftFilterState == draftFilterState));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
const DeepCollectionEquality().hash(_activePlans),
|
||||
const DeepCollectionEquality().hash(_draftPlans),
|
||||
isLoading,
|
||||
error,
|
||||
selectedStateIndex,
|
||||
minActivePlanPrice,
|
||||
maxActivePlanPrice,
|
||||
minDraftPlanPrice,
|
||||
maxDraftPlanPrice,
|
||||
activeFilterState,
|
||||
draftFilterState);
|
||||
|
||||
/// Create a copy of MyPlansState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$MyPlansStateImplCopyWith<_$MyPlansStateImpl> get copyWith =>
|
||||
__$$MyPlansStateImplCopyWithImpl<_$MyPlansStateImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _MyPlansState implements MyPlansState {
|
||||
factory _MyPlansState(
|
||||
{final List<PlanModel> activePlans,
|
||||
final List<PlanModel> draftPlans,
|
||||
final bool isLoading,
|
||||
final String? error,
|
||||
final int selectedStateIndex,
|
||||
final int minActivePlanPrice,
|
||||
final int maxActivePlanPrice,
|
||||
final int minDraftPlanPrice,
|
||||
final int maxDraftPlanPrice,
|
||||
final PlansFilterState activeFilterState,
|
||||
final PlansFilterState draftFilterState}) = _$MyPlansStateImpl;
|
||||
|
||||
@override
|
||||
List<PlanModel> get activePlans;
|
||||
@override
|
||||
List<PlanModel> get draftPlans;
|
||||
@override
|
||||
bool get isLoading;
|
||||
@override
|
||||
String? get error;
|
||||
@override
|
||||
int get selectedStateIndex;
|
||||
@override
|
||||
int get minActivePlanPrice;
|
||||
@override
|
||||
int get maxActivePlanPrice;
|
||||
@override
|
||||
int get minDraftPlanPrice;
|
||||
@override
|
||||
int get maxDraftPlanPrice;
|
||||
@override
|
||||
PlansFilterState get activeFilterState;
|
||||
@override
|
||||
PlansFilterState get draftFilterState;
|
||||
|
||||
/// Create a copy of MyPlansState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$MyPlansStateImplCopyWith<_$MyPlansStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
189
lib/features/my_plans/presentation/my_plans_screen.dart
Normal file
189
lib/features/my_plans/presentation/my_plans_screen.dart
Normal file
@@ -0,0 +1,189 @@
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.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/loader.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/create_plan_screen.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/filter_badge.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/plans_filter_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_state.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/widgets/my_plan_widget.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/widgets/my_plans_tab.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class MyPlansScreen extends StatelessWidget {
|
||||
const MyPlansScreen({super.key});
|
||||
static const name = 'myPlansScreen';
|
||||
static const path = '/my-plans-screen';
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(create: (context) => sl<MyPlansCubit>()..loadAllPlans(), child: MyPlansScreenContent());
|
||||
}
|
||||
}
|
||||
|
||||
class MyPlansScreenContent extends StatelessWidget {
|
||||
MyPlansScreenContent({super.key});
|
||||
|
||||
final activePlansScrollController = ScrollController();
|
||||
final draftPlansScrollController = ScrollController();
|
||||
|
||||
final activePlansSearchController = SearchController();
|
||||
final draftPlansSearchController = SearchController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Scaffold(
|
||||
backgroundColor: appTheme.gray100,
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
s.my_plans,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
body: BlocBuilder<MyPlansCubit, MyPlansState>(
|
||||
buildWhen: (previous, current) => previous.selectedStateIndex != current.selectedStateIndex,
|
||||
builder: (context, state) {
|
||||
return NestedScrollView(
|
||||
controller: switch (state.selectedStateIndex) {
|
||||
1 => draftPlansScrollController,
|
||||
_ => activePlansScrollController,
|
||||
},
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverAppBar(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
|
||||
),
|
||||
backgroundColor: appTheme.gray100,
|
||||
foregroundColor: appTheme.gray100,
|
||||
automaticallyImplyLeading: false,
|
||||
stretchTriggerOffset: 1,
|
||||
floating: true,
|
||||
primary: false,
|
||||
snap: true,
|
||||
scrolledUnderElevation: 0,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: Size(
|
||||
MediaQuery.sizeOf(context).width,
|
||||
64,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: MyPlansTab(),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: CustomSearchBar(
|
||||
enableIcon: false,
|
||||
backgroundColor: appTheme.whiteA700,
|
||||
borderRadius: 100,
|
||||
searchController: switch (state.selectedStateIndex) {
|
||||
1 => draftPlansSearchController,
|
||||
_ => activePlansSearchController,
|
||||
},
|
||||
onChanged: (query) {
|
||||
EasyDebounce.debounce(
|
||||
'searchPlans',
|
||||
Duration(milliseconds: 500),
|
||||
() {
|
||||
context.read<MyPlansCubit>().loadPlans(query: query == '' ? null : query);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
AddItemButton(
|
||||
onTap: () => GoRouter.of(context).pushNamed(CreatePlanScreen.name),
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () => context.read<MyPlansCubit>().loadPlans(),
|
||||
child: BlocBuilder<MyPlansCubit, MyPlansState>(
|
||||
builder: (BuildContext context, MyPlansState state) {
|
||||
if (state.isLoading) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: const Loader(),
|
||||
);
|
||||
} else if (state.error != null) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Text(state.error ?? ''),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final plans = switch (state.selectedStateIndex) {
|
||||
1 => state.draftPlans,
|
||||
_ => state.activePlans,
|
||||
};
|
||||
return Stack(
|
||||
children: [
|
||||
SizedBox.expand(),
|
||||
ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return MyPlanWidget(plan: plans[index]);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(height: 20);
|
||||
},
|
||||
itemCount: plans.length,
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 74,
|
||||
child: FilterBadge(
|
||||
filterBottomSheet: PlansFilterBottomSheet(
|
||||
myPlansCubit: context.read<MyPlansCubit>(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
259
lib/features/my_plans/presentation/widgets/my_plan_widget.dart
Normal file
259
lib/features/my_plans/presentation/widgets/my_plan_widget.dart
Normal file
@@ -0,0 +1,259 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/info_row.dart';
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class MyPlanWidget extends StatefulWidget {
|
||||
final PlanModel plan;
|
||||
const MyPlanWidget({
|
||||
super.key,
|
||||
required this.plan,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MyPlanWidget> createState() => _MyPlanWidgetState();
|
||||
}
|
||||
|
||||
class _MyPlanWidgetState extends State<MyPlanWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Colors.white,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () => GoRouter.of(context).pushNamed('aboutPlanScreen', extra: {'plan': widget.plan, 'navigateToComments': false}),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: baseUrl + '/storage/' + widget.plan.image,
|
||||
fit: BoxFit.fitWidth,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
child: SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
child: InfoRow(
|
||||
plan: widget.plan,
|
||||
itemsBackgroundColor: appTheme.whiteA700,
|
||||
disableDescriptionOfTypes: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
widget.plan.name,
|
||||
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
if (widget.plan.details.isNotEmpty) ...[
|
||||
SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Html(
|
||||
data: widget.plan.details.length > 200 ? widget.plan.details.substring(0, 200) + '...' : widget.plan.details,
|
||||
style: {
|
||||
"*": Style(
|
||||
margin: Margins.zero,
|
||||
color: appTheme.black,
|
||||
fontSize: FontSize(TextStyles.textSMedium.fontSize ?? 14),
|
||||
fontWeight: TextStyles.textSMedium.fontWeight ?? FontWeight.w400,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 16),
|
||||
child: Text(
|
||||
'${widget.plan.packetsCount == 1 ? widget.plan.packet1Options?.price : '${s.from} ${widget.plan.minPriceOfOptions.toStringAsFixed(0)}'}₽',
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.greenA700),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
BlocBuilder<MyPlansCubit, MyPlansState>(
|
||||
builder: (context, state) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomOutlinedButton(
|
||||
text: switch (state.selectedStateIndex) {
|
||||
1 => s.publish,
|
||||
_ => s.edit,
|
||||
},
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (state.selectedStateIndex == 0) ...[
|
||||
SizedBox(width: 6),
|
||||
CustomOutlinedButton(
|
||||
onPressed: () async {
|
||||
await Share.shareUri(
|
||||
Uri.parse(
|
||||
baseUrl + '/ru/plan/' + widget.plan.id.toString(),
|
||||
),
|
||||
);
|
||||
},
|
||||
height: 52,
|
||||
width: 52,
|
||||
leftIcon: SvgPicture.asset(
|
||||
'assets/icons/share2.svg',
|
||||
width: 20,
|
||||
height: 20,
|
||||
colorFilter: ColorFilter.mode(appTheme.whiteA700, BlendMode.srcIn),
|
||||
),
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.red),
|
||||
buttonStyle: ButtonStyle(
|
||||
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
text: '',
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomOutlinedButton(
|
||||
text: switch (state.selectedStateIndex) {
|
||||
0 => s.removeFromActive,
|
||||
_ => s.edit,
|
||||
},
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||
buttonStyle: ButtonStyle(
|
||||
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.lightGray0),
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 6),
|
||||
CustomOutlinedButton(
|
||||
onPressed: () async {
|
||||
final result = await context.read<MyPlansCubit>().deletePlan(widget.plan.slug);
|
||||
if (result)
|
||||
Future.delayed(Duration(seconds: 3), () {
|
||||
showBottomSheetWithBlur(
|
||||
context: context,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
BottomSheetHandlerBar(),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
'${widget.plan.name}',
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(
|
||||
s.deleted,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: CustomOutlinedButton(
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
},
|
||||
text: 'Хорошо',
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
height: 52,
|
||||
width: 52,
|
||||
leftIcon: SvgPicture.asset(
|
||||
'assets/icons/trash_can.svg',
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.red),
|
||||
buttonStyle: ButtonStyle(
|
||||
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.lightGray0),
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
text: '',
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
71
lib/features/my_plans/presentation/widgets/my_plans_tab.dart
Normal file
71
lib/features/my_plans/presentation/widgets/my_plans_tab.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/animated_option_button.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MyPlansTab extends StatefulWidget {
|
||||
const MyPlansTab({super.key, this.additionalOnTap});
|
||||
final void Function()? additionalOnTap;
|
||||
@override
|
||||
State<MyPlansTab> createState() => _MyPlansTabState();
|
||||
}
|
||||
|
||||
class _MyPlansTabState extends State<MyPlansTab> {
|
||||
final List<String> tabTitles = [];
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
final s = S.of(context);
|
||||
if (tabTitles.isEmpty) {
|
||||
tabTitles.add(s.published);
|
||||
tabTitles.add(s.drafted);
|
||||
}
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<MyPlansCubit, MyPlansState>(
|
||||
builder: (context, state) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.whiteA700,
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Row(
|
||||
children: [
|
||||
...List.generate(
|
||||
2,
|
||||
(index) => Expanded(
|
||||
child: AnimatedOptionButton(
|
||||
textStyle: TextStyles.textSMedium,
|
||||
isSelected: state.selectedStateIndex == index,
|
||||
text: tabTitles[index],
|
||||
onTap: () {
|
||||
widget.additionalOnTap?.call();
|
||||
context.read<MyPlansCubit>().changeSelectedStateIndex(index);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
39
lib/features/my_plans/repositories/my_diets_repository.dart
Normal file
39
lib/features/my_plans/repositories/my_diets_repository.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/my_plans/services/my_plans_service.dart';
|
||||
|
||||
class MyPlansRepository {
|
||||
final MyPlansService myPlansService;
|
||||
|
||||
MyPlansRepository({required this.myPlansService});
|
||||
|
||||
Future<void> deletePlan(String slug) async {
|
||||
try {
|
||||
await myPlansService.deletePlan(slug: slug);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<PlanPaginationModel> getPlans({
|
||||
int page = 1,
|
||||
String? query,
|
||||
String? types,
|
||||
int? duration,
|
||||
int? minPrice,
|
||||
int? maxPrice,
|
||||
}) async {
|
||||
try {
|
||||
return (await myPlansService.getPlans(
|
||||
page: page,
|
||||
query: query,
|
||||
types: types,
|
||||
duration: duration,
|
||||
minPrice: minPrice,
|
||||
maxPrice: maxPrice,
|
||||
));
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
28
lib/features/my_plans/services/my_plans_service.dart
Normal file
28
lib/features/my_plans/services/my_plans_service.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||
import 'package:retrofit/retrofit.dart';
|
||||
|
||||
part 'my_plans_service.g.dart';
|
||||
|
||||
@RestApi()
|
||||
abstract class MyPlansService {
|
||||
|
||||
factory MyPlansService(Dio dio, {String baseUrl}) = _MyPlansService;
|
||||
|
||||
@GET('/api/plans/index')
|
||||
Future<PlanPaginationModel> getPlans({
|
||||
@Query('page') required int page,
|
||||
@Query('filters[name][\$startsWith]') String? query,
|
||||
@Query('filters[type][\$contains]') String? types,
|
||||
@Query('filters[duration][\$eq]') int? duration,
|
||||
@Query('filters[min_price_of_options][\$between][0]') int? minPrice,
|
||||
@Query('filters[min_price_of_options][\$between][1]') int? maxPrice,
|
||||
});
|
||||
|
||||
@POST('/api/plans/delete/{slug}')
|
||||
Future<void> deletePlan({
|
||||
@Path() required String slug,
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
126
lib/features/my_plans/services/my_plans_service.g.dart
Normal file
126
lib/features/my_plans/services/my_plans_service.g.dart
Normal file
@@ -0,0 +1,126 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'my_plans_service.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RetrofitGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations
|
||||
|
||||
class _MyPlansService implements MyPlansService {
|
||||
_MyPlansService(
|
||||
this._dio, {
|
||||
this.baseUrl,
|
||||
this.errorLogger,
|
||||
});
|
||||
|
||||
final Dio _dio;
|
||||
|
||||
String? baseUrl;
|
||||
|
||||
final ParseErrorLogger? errorLogger;
|
||||
|
||||
@override
|
||||
Future<PlanPaginationModel> getPlans({
|
||||
required int page,
|
||||
String? query,
|
||||
String? types,
|
||||
int? duration,
|
||||
int? minPrice,
|
||||
int? maxPrice,
|
||||
}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{
|
||||
r'page': page,
|
||||
r'filters[name][$startsWith]': query,
|
||||
r'filters[type][$contains]': types,
|
||||
r'filters[duration][$eq]': duration,
|
||||
r'filters[min_price_of_options][$between][0]': minPrice,
|
||||
r'filters[min_price_of_options][$between][1]': maxPrice,
|
||||
};
|
||||
queryParameters.removeWhere((k, v) => v == null);
|
||||
final _headers = <String, dynamic>{};
|
||||
const Map<String, dynamic>? _data = null;
|
||||
final _options = _setStreamType<PlanPaginationModel>(Options(
|
||||
method: 'GET',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/plans/index',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
)));
|
||||
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
|
||||
late PlanPaginationModel _value;
|
||||
try {
|
||||
_value = PlanPaginationModel.fromJson(_result.data!);
|
||||
} on Object catch (e, s) {
|
||||
errorLogger?.logError(e, s, _options);
|
||||
rethrow;
|
||||
}
|
||||
return _value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deletePlan({required String slug}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
const Map<String, dynamic>? _data = null;
|
||||
final _options = _setStreamType<void>(Options(
|
||||
method: 'POST',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/plans/delete/${slug}',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
)));
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
|
||||
if (T != dynamic &&
|
||||
!(requestOptions.responseType == ResponseType.bytes ||
|
||||
requestOptions.responseType == ResponseType.stream)) {
|
||||
if (T == String) {
|
||||
requestOptions.responseType = ResponseType.plain;
|
||||
} else {
|
||||
requestOptions.responseType = ResponseType.json;
|
||||
}
|
||||
}
|
||||
return requestOptions;
|
||||
}
|
||||
|
||||
String _combineBaseUrls(
|
||||
String dioBaseUrl,
|
||||
String? baseUrl,
|
||||
) {
|
||||
if (baseUrl == null || baseUrl.trim().isEmpty) {
|
||||
return dioBaseUrl;
|
||||
}
|
||||
|
||||
final url = Uri.parse(baseUrl);
|
||||
|
||||
if (url.isAbsolute) {
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
return Uri.parse(dioBaseUrl).resolveUri(url).toString();
|
||||
}
|
||||
}
|
||||
@@ -42,15 +42,19 @@ class AboutPlanCubit extends Cubit<AboutPlanState> {
|
||||
emit(state.copyWith(chosenOptionIndex: index));
|
||||
}
|
||||
|
||||
Future<void> likePlan({required int index, required String slug}) async {
|
||||
Future<void> likePlan({required int? index, required String slug}) async {
|
||||
final currentPlan = state.planModel;
|
||||
if (currentPlan == null) return;
|
||||
if (currentPlan.wasLikedByMe) {
|
||||
emit(state.copyWith(planModel: currentPlan.copyWith(wasLikedByMe: false, totalLikes: currentPlan.totalLikes - 1), error: null));
|
||||
planRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: false, totalLikes: currentPlan.totalLikes - 1, index: index));
|
||||
if (index != null) {
|
||||
planRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: false, totalLikes: currentPlan.totalLikes - 1, index: index));
|
||||
}
|
||||
} else {
|
||||
emit(state.copyWith(planModel: currentPlan.copyWith(wasLikedByMe: true, totalLikes: currentPlan.totalLikes + 1), error: null));
|
||||
planRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: true, totalLikes: currentPlan.totalLikes + 1, index: index));
|
||||
if (index != null) {
|
||||
planRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: true, totalLikes: currentPlan.totalLikes + 1, index: index));
|
||||
}
|
||||
}
|
||||
|
||||
EasyDebounce.debounce(
|
||||
@@ -61,19 +65,23 @@ class AboutPlanCubit extends Cubit<AboutPlanState> {
|
||||
await planRepository.likePlan(slug);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
planRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: currentPlan.wasLikedByMe, totalLikes: currentPlan.totalLikes, index: index));
|
||||
if (index != null) {
|
||||
planRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: currentPlan.wasLikedByMe, totalLikes: currentPlan.totalLikes, index: index));
|
||||
}
|
||||
emit(state.copyWith(planModel: currentPlan, error: null));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> addToFavorite({required String slug, required int index}) async {
|
||||
Future<void> addToFavorite({required String slug, required int? index}) async {
|
||||
final currentPlan = state.planModel;
|
||||
if (currentPlan == null) return;
|
||||
|
||||
emit(state.copyWith(planModel: currentPlan.copyWith(isFavorite: true)));
|
||||
planRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: true, index: index));
|
||||
if (index != null) {
|
||||
planRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: true, index: index));
|
||||
}
|
||||
EasyDebounce.debounce(
|
||||
'add_or_remove_plan_to_favorites',
|
||||
Duration(seconds: 1),
|
||||
@@ -83,19 +91,23 @@ class AboutPlanCubit extends Cubit<AboutPlanState> {
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||
planRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentPlan.isFavorite, index: index));
|
||||
if (index != null) {
|
||||
planRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentPlan.isFavorite, index: index));
|
||||
}
|
||||
emit(state.copyWith(planModel: currentPlan, error: null));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> removeFromFavorite({required String slug, required int index}) async {
|
||||
Future<void> removeFromFavorite({required String slug, required int? index}) async {
|
||||
final currentPlan = state.planModel;
|
||||
if (currentPlan == null) return;
|
||||
|
||||
emit(state.copyWith(planModel: currentPlan.copyWith(isFavorite: false)));
|
||||
planRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: false, index: index));
|
||||
if (index != null) {
|
||||
planRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: false, index: index));
|
||||
}
|
||||
EasyDebounce.debounce(
|
||||
'add_or_remove_plan_to_favorites',
|
||||
Duration(seconds: 1),
|
||||
@@ -105,7 +117,9 @@ class AboutPlanCubit extends Cubit<AboutPlanState> {
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
|
||||
planRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentPlan.isFavorite, index: currentPlan.id));
|
||||
if (index != null) {
|
||||
planRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentPlan.isFavorite, index: index));
|
||||
}
|
||||
emit(state.copyWith(planModel: currentPlan, error: null));
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
import 'package:eatvio_app/core/utils/string_extentions.dart';
|
||||
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/diet_info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/widgets/expandable_text.dart';
|
||||
@@ -34,7 +34,7 @@ class AboutPlanScreen extends StatefulWidget {
|
||||
AboutPlanScreen({super.key, required this.plan, required this.navigateToComments, required this.index});
|
||||
final PlanModel plan;
|
||||
final bool navigateToComments;
|
||||
final int index;
|
||||
final int? index;
|
||||
@override
|
||||
State<AboutPlanScreen> createState() => _AboutPlanScreenState();
|
||||
}
|
||||
|
||||
@@ -88,7 +88,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"date_of_birth": MessageLookupByLibrary.simpleMessage("Дата рождения"),
|
||||
"day": MessageLookupByLibrary.simpleMessage("День"),
|
||||
"days": MessageLookupByLibrary.simpleMessage("дней"),
|
||||
"deleted": MessageLookupByLibrary.simpleMessage("Удалена"),
|
||||
"diary": MessageLookupByLibrary.simpleMessage("Дневник"),
|
||||
"diet": MessageLookupByLibrary.simpleMessage("Диета"),
|
||||
"diet_author": MessageLookupByLibrary.simpleMessage("Автор диеты"),
|
||||
"diet_plan": MessageLookupByLibrary.simpleMessage("План диеты"),
|
||||
"diet_state": MessageLookupByLibrary.simpleMessage("Статус диеты"),
|
||||
@@ -102,6 +104,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"down_pressure":
|
||||
MessageLookupByLibrary.simpleMessage("Нижние показатели давления:"),
|
||||
"draft": MessageLookupByLibrary.simpleMessage("Черновик"),
|
||||
"drafted": MessageLookupByLibrary.simpleMessage("Черновики"),
|
||||
"eating_behavior":
|
||||
MessageLookupByLibrary.simpleMessage("Пищевое поведение"),
|
||||
"eating_behavior_type": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -109,6 +112,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"eating_behavior_type_desc": MessageLookupByLibrary.simpleMessage(
|
||||
"Формирование здорового пищевого поведения — это процесс, который включает в себя изменение привычек и отношения к еде. Он направлен на то, чтобы помочь людям научиться правильно питаться и поддерживать здоровый образ жизни.Основные принципы формирования здорового пищевого поведения: \n● Разнообразие продуктов; \n● Сбалансированный рацион; \n● Питьевой режим; \n● Ограничение вредных продуктов; \n● Регулярные приёмы пищи; \n● Умеренность;Осознанное питание; \n● Физическая активность;"),
|
||||
"economy": MessageLookupByLibrary.simpleMessage("Эконом"),
|
||||
"edit": MessageLookupByLibrary.simpleMessage("Редактировать"),
|
||||
"email": MessageLookupByLibrary.simpleMessage("Элетронная почта"),
|
||||
"email_empty": MessageLookupByLibrary.simpleMessage(
|
||||
"Необходимо ввести адрес электронной почты"),
|
||||
@@ -142,6 +146,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"fit_type_desc": MessageLookupByLibrary.simpleMessage(
|
||||
"План питания для поддержания формы сбалансированный и разнообразный, чтобы обеспечить организм всеми необходимыми питательными веществами. Общие принципы: \n● Дробное питание; \n● Разнообразие продуктов; \n● Контроль порций; \n● Питьевой режим; \n● Ограничение вредных продуктов;"),
|
||||
"fitness": MessageLookupByLibrary.simpleMessage("Поддержка формы"),
|
||||
"from": MessageLookupByLibrary.simpleMessage("от"),
|
||||
"from_a_to_z": MessageLookupByLibrary.simpleMessage("Название: А-Я"),
|
||||
"from_z_to_a": MessageLookupByLibrary.simpleMessage("Название: Я-А"),
|
||||
"gallery": MessageLookupByLibrary.simpleMessage("Галерея"),
|
||||
@@ -187,6 +192,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"msg_something_went_wrong":
|
||||
MessageLookupByLibrary.simpleMessage("Что то пошло не так!"),
|
||||
"my_blog": MessageLookupByLibrary.simpleMessage("Мой блог"),
|
||||
"my_diets": MessageLookupByLibrary.simpleMessage("Мои диеты"),
|
||||
"my_plans": MessageLookupByLibrary.simpleMessage("Мои планы"),
|
||||
"my_settings": MessageLookupByLibrary.simpleMessage("Мои настройки"),
|
||||
"name_empty": MessageLookupByLibrary.simpleMessage(
|
||||
"Имя пользователя обязательно для заполнения"),
|
||||
@@ -220,6 +227,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"password_short": MessageLookupByLibrary.simpleMessage(
|
||||
"В пароле должно быть минимум 6 символов"),
|
||||
"pcs": MessageLookupByLibrary.simpleMessage("шт"),
|
||||
"personal": MessageLookupByLibrary.simpleMessage("Персональные"),
|
||||
"personal_diet_state":
|
||||
MessageLookupByLibrary.simpleMessage("Персональная"),
|
||||
"photo": MessageLookupByLibrary.simpleMessage("Фото"),
|
||||
@@ -249,6 +257,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"profile": MessageLookupByLibrary.simpleMessage("Профиль"),
|
||||
"proteins": MessageLookupByLibrary.simpleMessage("Белки"),
|
||||
"publish": MessageLookupByLibrary.simpleMessage("Опубликовать"),
|
||||
"published": MessageLookupByLibrary.simpleMessage("Опубликованные"),
|
||||
"removeFromActive":
|
||||
MessageLookupByLibrary.simpleMessage("Снять с публикации"),
|
||||
"save": MessageLookupByLibrary.simpleMessage("Сохранить"),
|
||||
"services": MessageLookupByLibrary.simpleMessage("Оказание услуг"),
|
||||
"settings": MessageLookupByLibrary.simpleMessage("Настройки"),
|
||||
@@ -264,6 +275,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Расскажите в свободной форме о том как просыпаетесь, когда едите, когда ложитесь спать:"),
|
||||
"three_options": MessageLookupByLibrary.simpleMessage("3 опции"),
|
||||
"time": MessageLookupByLibrary.simpleMessage("Время прохождения"),
|
||||
"toOffer": MessageLookupByLibrary.simpleMessage("Предложить"),
|
||||
"to_draft": MessageLookupByLibrary.simpleMessage("В черновик"),
|
||||
"unfavorite_foods":
|
||||
MessageLookupByLibrary.simpleMessage("Нелюбимые продукты"),
|
||||
|
||||
@@ -2059,6 +2059,116 @@ class S {
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Мои диеты`
|
||||
String get my_diets {
|
||||
return Intl.message(
|
||||
'Мои диеты',
|
||||
name: 'my_diets',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Опубликованные`
|
||||
String get published {
|
||||
return Intl.message(
|
||||
'Опубликованные',
|
||||
name: 'published',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Черновики`
|
||||
String get drafted {
|
||||
return Intl.message(
|
||||
'Черновики',
|
||||
name: 'drafted',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Персональные`
|
||||
String get personal {
|
||||
return Intl.message(
|
||||
'Персональные',
|
||||
name: 'personal',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Редактировать`
|
||||
String get edit {
|
||||
return Intl.message(
|
||||
'Редактировать',
|
||||
name: 'edit',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Предложить`
|
||||
String get toOffer {
|
||||
return Intl.message(
|
||||
'Предложить',
|
||||
name: 'toOffer',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Снять с публикации`
|
||||
String get removeFromActive {
|
||||
return Intl.message(
|
||||
'Снять с публикации',
|
||||
name: 'removeFromActive',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Диета`
|
||||
String get diet {
|
||||
return Intl.message(
|
||||
'Диета',
|
||||
name: 'diet',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Удалена`
|
||||
String get deleted {
|
||||
return Intl.message(
|
||||
'Удалена',
|
||||
name: 'deleted',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Мои планы`
|
||||
String get my_plans {
|
||||
return Intl.message(
|
||||
'Мои планы',
|
||||
name: 'my_plans',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `от`
|
||||
String get from {
|
||||
return Intl.message(
|
||||
'от',
|
||||
name: 'from',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<S> {
|
||||
|
||||
@@ -199,7 +199,16 @@
|
||||
"wellness_type_desc": "Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы.",
|
||||
"eating_behavior_type": "Формирование пищевого поведения",
|
||||
"eating_behavior_type_desc": "Формирование здорового пищевого поведения — это процесс, который включает в себя изменение привычек и отношения к еде. Он направлен на то, чтобы помочь людям научиться правильно питаться и поддерживать здоровый образ жизни.Основные принципы формирования здорового пищевого поведения: \n● Разнообразие продуктов; \n● Сбалансированный рацион; \n● Питьевой режим; \n● Ограничение вредных продуктов; \n● Регулярные приёмы пищи; \n● Умеренность;Осознанное питание; \n● Физическая активность;",
|
||||
"price": "Цена"
|
||||
|
||||
|
||||
"price": "Цена",
|
||||
"my_diets": "Мои диеты",
|
||||
"published": "Опубликованные",
|
||||
"drafted": "Черновики",
|
||||
"personal": "Персональные",
|
||||
"edit": "Редактировать",
|
||||
"toOffer": "Предложить",
|
||||
"removeFromActive": "Снять с публикации",
|
||||
"diet": "Диета",
|
||||
"deleted": "Удалена",
|
||||
"my_plans": "Мои планы",
|
||||
"from": "от"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user