diff --git a/assets/icons/diet.png b/assets/icons/diet.png new file mode 100644 index 00000000..81ebb78e Binary files /dev/null and b/assets/icons/diet.png differ diff --git a/assets/icons/fitness.png b/assets/icons/fitness.png new file mode 100644 index 00000000..ef127b81 Binary files /dev/null and b/assets/icons/fitness.png differ diff --git a/assets/icons/slim.png b/assets/icons/slim.png new file mode 100644 index 00000000..e428ede8 Binary files /dev/null and b/assets/icons/slim.png differ diff --git a/assets/icons/yoga-pose.png b/assets/icons/yoga-pose.png new file mode 100644 index 00000000..57b9e680 Binary files /dev/null and b/assets/icons/yoga-pose.png differ diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 77635f79..557a8aa3 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -12,6 +12,8 @@ import 'package:eatvio_app/features/complaining/presentation/complaining_screen. import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/become_consultant_screen.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart'; import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart'; +import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart'; +import 'package:eatvio_app/features/create_plan/presentation/create_plan_screen.dart'; import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart'; import 'package:eatvio_app/features/diets/presentation/pages/about_diet_screen.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; @@ -264,6 +266,21 @@ class AppRouter { name: CreateDietScreen.name, builder: (context, state) => CreateDietScreen(), ), + GoRoute( + path: DietPreviewScreen.path, + name: DietPreviewScreen.name, + builder: (context, state) { + final data = state.extra as Diet; + return DietPreviewScreen( + diet: data, + ); + }, + ), + GoRoute( + path: CreatePlanScreen.path, + name: CreatePlanScreen.name, + builder: (context, state) => CreatePlanScreen(), + ), ], ); } diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index 567fe833..8d6994f5 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -3,6 +3,7 @@ import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart'; import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart'; import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart'; +import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart'; import 'package:eatvio_app/features/diets/repositories/diet_repository.dart'; import 'package:eatvio_app/features/diets/services/diet_service.dart'; import 'package:eatvio_app/core/initialization_cubit/initialization_cubit.dart'; @@ -79,7 +80,8 @@ Future init() async { sl.registerFactory(() => AboutDietCubit(dietsRepository: sl())); sl.registerFactory(() => BlogPostCreateCubit(blogPostRepository: sl())); sl.registerFactory(() => SubCubit(profileRepository: sl())); - sl.registerFactory(() => CreateDietCubit(createDietRepository: sl(), glossaryRepository: sl())); + sl.registerFactory(() => CreateDietCubit( profileRepository: sl(),createDietRepository: sl(), glossaryRepository: sl())); + sl.registerFactory(() => CreatePlanCubit()); // Repository diff --git a/lib/core/theme/custom_text_style.dart b/lib/core/theme/custom_text_style.dart index 91ddffa2..7f00ad9b 100644 --- a/lib/core/theme/custom_text_style.dart +++ b/lib/core/theme/custom_text_style.dart @@ -225,4 +225,10 @@ abstract class TextStyles { fontWeight: FontWeight.w500, height: 1.255, ); + + static const textFS10FW500 = TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + height: 1.255, + ); } diff --git a/lib/features/diets/presentation/widgets/cpfc_container.dart b/lib/core/widgets/cpfc_container.dart similarity index 82% rename from lib/features/diets/presentation/widgets/cpfc_container.dart rename to lib/core/widgets/cpfc_container.dart index d4663910..d7e53361 100644 --- a/lib/features/diets/presentation/widgets/cpfc_container.dart +++ b/lib/core/widgets/cpfc_container.dart @@ -4,10 +4,10 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:flutter/material.dart'; class CpfcContainer extends StatelessWidget { - const CpfcContainer({super.key, required this.calories, required this.protein, required this.fat, required this.carbohydrates}); + const CpfcContainer({super.key, required this.calories, required this.proteins, required this.fats, required this.carbohydrates}); final double calories; - final double protein; - final double fat; + final double proteins; + final double fats; final double carbohydrates; final int roundDigit = 1; @override @@ -27,13 +27,13 @@ class CpfcContainer extends StatelessWidget { ), Column( children: [ - Text('${protein.toStringAsFixed(roundDigit)}', style: TextStyles.textMSemibold.copyWith(color: appTheme.black)), + Text('${proteins.toStringAsFixed(roundDigit)}', style: TextStyles.textMSemibold.copyWith(color: appTheme.black)), Text(s.proteins, style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)), ], ), Column( children: [ - Text('${fat.toStringAsFixed(roundDigit)}', style: TextStyles.textMSemibold.copyWith(color: appTheme.black)), + Text('${fats.toStringAsFixed(roundDigit)}', style: TextStyles.textMSemibold.copyWith(color: appTheme.black)), Text(s.fats, style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)), ], ), diff --git a/lib/core/widgets/custom_text_form_field.dart b/lib/core/widgets/custom_text_form_field.dart index 17e961a2..77e7086a 100644 --- a/lib/core/widgets/custom_text_form_field.dart +++ b/lib/core/widgets/custom_text_form_field.dart @@ -37,9 +37,10 @@ class CustomTextFormField extends StatelessWidget { this.maxLength, this.expands = false, this.textAlignVertical, + this.counter = const SizedBox.shrink(), super.key, }); - + final Widget? counter; final Alignment? alignment; final double? width; final TextEditingController? controller; @@ -77,7 +78,7 @@ class CustomTextFormField extends StatelessWidget { Widget build(BuildContext context) { return Align( alignment: alignment ?? Alignment.center, - child: TextFormField( + child: TextFormField( textAlignVertical: textAlignVertical, expands: expands, minLines: minLines, @@ -97,6 +98,7 @@ class CustomTextFormField extends StatelessWidget { initialValue: initialValue, cursorColor: appTheme.blueA400, decoration: InputDecoration( + counter: counter, suffixText: suffixText, hintText: hintText ?? "", hintStyle: TextStyle( diff --git a/lib/features/create_diet/models/create_diet_model.dart b/lib/features/create_diet/models/create_diet_model.dart index 35635552..5a53754b 100644 --- a/lib/features/create_diet/models/create_diet_model.dart +++ b/lib/features/create_diet/models/create_diet_model.dart @@ -9,7 +9,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'create_diet_model.freezed.dart'; @freezed -class CreateDietModel with _$CreateDietModel{ +class CreateDietModel with _$CreateDietModel { const CreateDietModel._(); const factory CreateDietModel({ @@ -23,15 +23,15 @@ class CreateDietModel with _$CreateDietModel{ }) = _CreateDietModel; Future> toCreationJson() async => { - 'name': name, - 'duration': duration, - 'state': state, - 'type': type, - 'details': details, - 'image': image != null ? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image!.path))).path, filename: image!.name) : null, - }; + 'name': name, + 'duration': duration, + 'state': state, + 'type': type, + 'details': details, + 'image': image != null ? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image!.path))).path, filename: image!.name) : null, + }; Map toDailyScheduleCreationJson() => { - 'daily_schedule': dailySchedule, - }; -} \ No newline at end of file + 'daily_schedule': dailySchedule?.map((element) => element.map((element) => element.map((element) => element.toJson()).toList()).toList()).toList(), + }; +} diff --git a/lib/features/create_diet/presentation/create_diet_screen.dart b/lib/features/create_diet/presentation/create_diet_screen.dart index 1b882176..ed4fd233 100644 --- a/lib/features/create_diet/presentation/create_diet_screen.dart +++ b/lib/features/create_diet/presentation/create_diet_screen.dart @@ -2,19 +2,21 @@ 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/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'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart'; import 'package:eatvio_app/features/create_diet/presentation/widgets/day_widget.dart'; -import 'package:eatvio_app/features/create_diet/presentation/widgets/duration_drop_down_widget.dart'; -import 'package:eatvio_app/features/create_diet/presentation/widgets/image_picker_widget.dart'; +import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart'; +import 'package:eatvio_app/features/create_diet/presentation/widgets/diet_duration_drop_down_widget.dart'; +import 'package:eatvio_app/features/create_diet/presentation/widgets/diet_image_picker_widget.dart'; import 'package:eatvio_app/features/create_diet/presentation/widgets/status_drop_down_widget.dart'; -import 'package:eatvio_app/features/create_diet/presentation/widgets/type_choice_widget.dart'; +import 'package:eatvio_app/features/create_diet/presentation/widgets/diet_type_choice_widget.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; class CreateDietScreen extends StatelessWidget { static const name = 'createDietScreen'; @@ -78,13 +80,13 @@ class CreateDietScreenContent extends StatelessWidget { ), ), SizedBox(height: 12), - DurationDropDownWidget(), + DietDurationDropDownWidget(), SizedBox(height: 12), StatusDropDownWidget(), SizedBox(height: 16), - TypeChoiceWidget(), + DietTypeChoiceWidget(), SizedBox(height: 16), - ImagePickerWidget(), + DietImagePickerWidget(), SizedBox(height: 16), ], ), @@ -108,9 +110,66 @@ class CreateDietScreenContent extends StatelessWidget { }, ), SizedBox(height: 8), - DayWidget( - index: 0, - ), + BlocBuilder( + builder: (context, state) { + return ListView.separated( + padding: const EdgeInsets.all(0), + itemBuilder: (context, index) => DayWidget(index: index), + itemCount: state.dietData.dailySchedule?.length ?? 0, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + separatorBuilder: (context, index) => SizedBox(height: 8), + ); + }, + ), + SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + children: [ + CustomOutlinedButton( + onPressed: () { + context.read().setDietState(2); + }, + text: s.to_draft, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray), + buttonStyle: ButtonStyle( + side: WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder), + ), + ), + SizedBox(height: 12), + CustomOutlinedButton( + onPressed: () async { + final diet = await context.read().getDietForPreview(); + GoRouter.of(context).pushNamed(DietPreviewScreen.name, extra: diet); + }, + text: s.preview, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray), + buttonStyle: ButtonStyle( + side: WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder), + ), + ), + SizedBox(height: 12), + CustomOutlinedButton( + onPressed: () { + context.read().publishDiet(); + }, + text: s.publish, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle( + side: WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + ), + ), + ], + ), + ), + SizedBox(height: 20), ], ), ), diff --git a/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart b/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart index 43ccedf4..949241af 100644 --- a/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart +++ b/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart @@ -1,17 +1,20 @@ +import 'dart:convert'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart'; import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/glossary/models/glossary_model.dart'; import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart'; +import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; class CreateDietCubit extends Cubit { final CreateDietRepository createDietRepository; + final ProfileRepository profileRepository; final GlossaryRepository glossaryRepository; - static const _dietStateValues = ['active', 'draft', 'personal']; - CreateDietCubit({required this.createDietRepository, required this.glossaryRepository}) : super(CreateDietState.initial()); + static const _dietStateValues = ['active', 'personal', 'draft']; + CreateDietCubit({required this.profileRepository, required this.createDietRepository, required this.glossaryRepository}) : super(CreateDietState.initial()); void setDetails(String text) { emit(state.copyWith(dietData: state.dietData.copyWith(details: text))); @@ -22,6 +25,44 @@ class CreateDietCubit extends Cubit { } void setDuration(int index) { + final selectedDaysValue = durationValues[index]; + final currentDaysValue = state.dietData.dailySchedule?.length; + final dailySchedule = state.dietData.dailySchedule; + + if (currentDaysValue != null && dailySchedule != null) { + final deepCopyOfDailySchedule = dailySchedule + .map( + (day) => day.map((meal) => [...meal]).toList(), + ) + .toList(); + + if (currentDaysValue > selectedDaysValue) { + final difference = currentDaysValue - selectedDaysValue; + for (int i = 0; i < difference; i++) { + deepCopyOfDailySchedule.removeLast(); + } + } else if (selectedDaysValue > currentDaysValue) { + final difference = selectedDaysValue - currentDaysValue; + for (int i = 0; i < difference; i++) { + deepCopyOfDailySchedule.add([[], [], [], [], []]); + } + } + emit( + state.copyWith( + dietData: state.dietData.copyWith( + dailySchedule: deepCopyOfDailySchedule, + ), + ), + ); + } else { + final List>> dailySchedule = List.generate(selectedDaysValue, (index) => [[], [], [], [], []]); + + emit( + state.copyWith( + dietData: state.dietData.copyWith(dailySchedule: dailySchedule), + ), + ); + } emit( state.copyWith( durationIndex: index, @@ -52,15 +93,174 @@ class CreateDietCubit extends Cubit { emit(state.copyWith(dietData: state.dietData.copyWith(type: type))); } - void addPortion({required int dayIndex,required int mealIndex, required DailySchedule portion}) { - final currentSchedule = state.dietData.dailySchedule; - if (currentSchedule != null) { - + void addPortion({required int dayIndex, required int mealIndex, required DailySchedule portion}) { + final dailySchedule = state.dietData.dailySchedule; + if (dailySchedule != null) { + final deepCopyOfDailySchedule = dailySchedule + .map( + (day) => day.map((meal) => [...meal]).toList(), + ) + .toList(); + deepCopyOfDailySchedule[dayIndex][mealIndex].add(portion); + + final newDietData = state.dietData.copyWith(dailySchedule: deepCopyOfDailySchedule); + emit(state.copyWith(dietData: newDietData)); } } - void removePortion({required int day,required int meal,required int portionIndex, }) {} + void removePortion({ + required int day, + required int meal, + required int portionIndex, + }) { + final dailySchedule = state.dietData.dailySchedule; + if (dailySchedule != null) { + final deepCopyOfDailySchedule = dailySchedule + .map( + (day) => day.map((meal) => [...meal]).toList(), + ) + .toList(); + deepCopyOfDailySchedule[day][meal].removeAt(portionIndex); - void updatePortion({required int day,required int meal,required int portionIndex, required DailySchedule portion}) {} + final newDietData = state.dietData.copyWith(dailySchedule: deepCopyOfDailySchedule); + emit(state.copyWith(dietData: newDietData)); + } + } + void updatePortion({required int dayIndex, required int mealIndex, required int portionIndex, required DailySchedule portion}) { + final dailySchedule = state.dietData.dailySchedule; + if (dailySchedule != null) { + final deepCopyOfDailySchedule = dailySchedule + .map( + (day) => day.map((meal) => [...meal]).toList(), + ) + .toList(); + deepCopyOfDailySchedule[dayIndex][mealIndex][portionIndex] = portion; + + final newDietData = state.dietData.copyWith(dailySchedule: deepCopyOfDailySchedule); + emit(state.copyWith(dietData: newDietData)); + } + } + + void copyDay(int dayIndex) { + final dailySchedule = state.dietData.dailySchedule; + if (dailySchedule != null) { + final deepCopyOfDailySchedule = dailySchedule + .map( + (day) => day.map((meal) => [...meal]).toList(), + ) + .toList(); + final copiedDay = deepCopyOfDailySchedule[dayIndex]; + emit(state.copyWith(copiedDay: copiedDay)); + } + } + + void pasteCopiedDay(int dayIndex) { + final dailySchedule = state.dietData.dailySchedule; + final copiedDay = state.copiedDay; + if (dailySchedule != null && copiedDay != null) { + final deepCopyOfDailySchedule = dailySchedule + .map( + (day) => day.map((meal) => [...meal]).toList(), + ) + .toList(); + deepCopyOfDailySchedule[dayIndex] = copiedDay; + final newDietData = state.dietData.copyWith(dailySchedule: deepCopyOfDailySchedule); + emit(state.copyWith(dietData: newDietData)); + } + } + + Future getDietForPreview() async { + final authorProfile = await profileRepository.getMyProfile(); + final imageBytes = (await state.dietData.image?.readAsBytes()) ?? []; + final imageInBase64 = base64.encode(imageBytes); + final carbs = state.dietData.dailySchedule?.map((element) { + return element.fold(0, (prev, element) { + return prev + + element.fold(0, (prev, element) { + return prev + (element.dailyNutrition?.carbohydrates ?? 0); + }); + }); + }).toList(); + + final calories = state.dietData.dailySchedule?.map((element) { + return element.fold(0, (prev, element) { + return prev + + element.fold(0, (prev, element) { + return prev + (element.dailyNutrition?.energy ?? 0); + }); + }); + }).toList(); + + final fats = state.dietData.dailySchedule?.map((element) { + return element.fold(0, (prev, element) { + return prev + + element.fold(0, (prev, element) { + return prev + (element.dailyNutrition?.fats ?? 0); + }); + }); + }).toList(); + + final proteins = state.dietData.dailySchedule?.map((element) { + return element.fold(0, (prev, element) { + return prev + + element.fold(0, (prev, element) { + return prev + (element.dailyNutrition?.proteins ?? 0); + }); + }); + }).toList(); + final dailyNutrition = []; + if (carbs != null && calories != null && fats != null && proteins != null) { + for (int i = 0; i < carbs.length; i++) { + dailyNutrition.add( + DailyNutrition( + carbohydrates: carbs[i], + energy: calories[i], + fats: fats[i], + proteins: proteins[i], + ), + ); + } + } + final diet = Diet( + id: 0, + name: state.dietData.name ?? '', + image: imageInBase64, + duration: state.dietData.duration ?? 0, + state: state.dietData.state ?? '', + type: DietType( + meat: state.dietData.type[0], + veg: state.dietData.type[1], + carb: state.dietData.type[2], + light: state.dietData.type[3], + ), + details: state.dietData.details ?? '', + dailySchedule: state.dietData.dailySchedule ?? [], + author: Author(authorSlug: authorProfile.slug, authorName: authorProfile.name, authorProfileIcon: authorProfile.profilePhotoUrl, authorIsMe: false), + totalLikes: 0, + totalViews: 0, + totalComments: 0, + wasLikedByMe: false, + isFavorite: false, + dailyNutrition: dailyNutrition, + ); + return diet; + } + + Future publishDiet() async { + // бек судя по всему с багом + // try { + // final model = await state.dietData.toCreationJson(); + // model.remove('image'); + // final dailySchedule = await state.dietData.toDailyScheduleCreationJson(); + // final dio = sl(); + // final qwe = await dio.post('/api/diets/create', data: model); + // // final diet = await createDietRepository.createDiet(model: model); + // // final qwe2 = createDietRepository.createDietDailySchedule(id: diet.id, model: dailySchedule); + // } catch (e, stackTrace) { + // print(e); + // print('---------------------------------'); + // print(stackTrace); + // } + } } diff --git a/lib/features/create_diet/presentation/cubit/create_diet_state.dart b/lib/features/create_diet/presentation/cubit/create_diet_state.dart index 8fdcaee6..df571110 100644 --- a/lib/features/create_diet/presentation/cubit/create_diet_state.dart +++ b/lib/features/create_diet/presentation/cubit/create_diet_state.dart @@ -1,14 +1,16 @@ import 'package:eatvio_app/features/create_diet/models/create_diet_model.dart'; +import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'create_diet_state.freezed.dart'; @freezed class CreateDietState with _$CreateDietState { - const factory CreateDietState({ + const factory CreateDietState({ required bool isLoading, required String? errorMessage, - required CreateDietModel dietData, + required CreateDietModel dietData, + List>? copiedDay, int? durationIndex, int? dietStateIndex, }) = _CreateDietState; diff --git a/lib/features/create_diet/presentation/cubit/create_diet_state.freezed.dart b/lib/features/create_diet/presentation/cubit/create_diet_state.freezed.dart index 8f6182d8..c58a70e9 100644 --- a/lib/features/create_diet/presentation/cubit/create_diet_state.freezed.dart +++ b/lib/features/create_diet/presentation/cubit/create_diet_state.freezed.dart @@ -19,6 +19,8 @@ mixin _$CreateDietState { bool get isLoading => throw _privateConstructorUsedError; String? get errorMessage => throw _privateConstructorUsedError; CreateDietModel get dietData => throw _privateConstructorUsedError; + List>? get copiedDay => + throw _privateConstructorUsedError; int? get durationIndex => throw _privateConstructorUsedError; int? get dietStateIndex => throw _privateConstructorUsedError; @@ -39,6 +41,7 @@ abstract class $CreateDietStateCopyWith<$Res> { {bool isLoading, String? errorMessage, CreateDietModel dietData, + List>? copiedDay, int? durationIndex, int? dietStateIndex}); @@ -63,6 +66,7 @@ class _$CreateDietStateCopyWithImpl<$Res, $Val extends CreateDietState> Object? isLoading = null, Object? errorMessage = freezed, Object? dietData = null, + Object? copiedDay = freezed, Object? durationIndex = freezed, Object? dietStateIndex = freezed, }) { @@ -79,6 +83,10 @@ class _$CreateDietStateCopyWithImpl<$Res, $Val extends CreateDietState> ? _value.dietData : dietData // ignore: cast_nullable_to_non_nullable as CreateDietModel, + copiedDay: freezed == copiedDay + ? _value.copiedDay + : copiedDay // ignore: cast_nullable_to_non_nullable + as List>?, durationIndex: freezed == durationIndex ? _value.durationIndex : durationIndex // ignore: cast_nullable_to_non_nullable @@ -113,6 +121,7 @@ abstract class _$$CreateDietStateImplCopyWith<$Res> {bool isLoading, String? errorMessage, CreateDietModel dietData, + List>? copiedDay, int? durationIndex, int? dietStateIndex}); @@ -136,6 +145,7 @@ class __$$CreateDietStateImplCopyWithImpl<$Res> Object? isLoading = null, Object? errorMessage = freezed, Object? dietData = null, + Object? copiedDay = freezed, Object? durationIndex = freezed, Object? dietStateIndex = freezed, }) { @@ -152,6 +162,10 @@ class __$$CreateDietStateImplCopyWithImpl<$Res> ? _value.dietData : dietData // ignore: cast_nullable_to_non_nullable as CreateDietModel, + copiedDay: freezed == copiedDay + ? _value._copiedDay + : copiedDay // ignore: cast_nullable_to_non_nullable + as List>?, durationIndex: freezed == durationIndex ? _value.durationIndex : durationIndex // ignore: cast_nullable_to_non_nullable @@ -171,8 +185,10 @@ class _$CreateDietStateImpl implements _CreateDietState { {required this.isLoading, required this.errorMessage, required this.dietData, + final List>? copiedDay, this.durationIndex, - this.dietStateIndex}); + this.dietStateIndex}) + : _copiedDay = copiedDay; @override final bool isLoading; @@ -180,6 +196,16 @@ class _$CreateDietStateImpl implements _CreateDietState { final String? errorMessage; @override final CreateDietModel dietData; + final List>? _copiedDay; + @override + List>? get copiedDay { + final value = _copiedDay; + if (value == null) return null; + if (_copiedDay is EqualUnmodifiableListView) return _copiedDay; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + @override final int? durationIndex; @override @@ -187,7 +213,7 @@ class _$CreateDietStateImpl implements _CreateDietState { @override String toString() { - return 'CreateDietState(isLoading: $isLoading, errorMessage: $errorMessage, dietData: $dietData, durationIndex: $durationIndex, dietStateIndex: $dietStateIndex)'; + return 'CreateDietState(isLoading: $isLoading, errorMessage: $errorMessage, dietData: $dietData, copiedDay: $copiedDay, durationIndex: $durationIndex, dietStateIndex: $dietStateIndex)'; } @override @@ -201,6 +227,8 @@ class _$CreateDietStateImpl implements _CreateDietState { other.errorMessage == errorMessage) && (identical(other.dietData, dietData) || other.dietData == dietData) && + const DeepCollectionEquality() + .equals(other._copiedDay, _copiedDay) && (identical(other.durationIndex, durationIndex) || other.durationIndex == durationIndex) && (identical(other.dietStateIndex, dietStateIndex) || @@ -208,8 +236,14 @@ class _$CreateDietStateImpl implements _CreateDietState { } @override - int get hashCode => Object.hash(runtimeType, isLoading, errorMessage, - dietData, durationIndex, dietStateIndex); + int get hashCode => Object.hash( + runtimeType, + isLoading, + errorMessage, + dietData, + const DeepCollectionEquality().hash(_copiedDay), + durationIndex, + dietStateIndex); /// Create a copy of CreateDietState /// with the given fields replaced by the non-null parameter values. @@ -226,6 +260,7 @@ abstract class _CreateDietState implements CreateDietState { {required final bool isLoading, required final String? errorMessage, required final CreateDietModel dietData, + final List>? copiedDay, final int? durationIndex, final int? dietStateIndex}) = _$CreateDietStateImpl; @@ -236,6 +271,8 @@ abstract class _CreateDietState implements CreateDietState { @override CreateDietModel get dietData; @override + List>? get copiedDay; + @override int? get durationIndex; @override int? get dietStateIndex; diff --git a/lib/features/create_diet/presentation/diet_preview_screen.dart b/lib/features/create_diet/presentation/diet_preview_screen.dart new file mode 100644 index 00000000..2da26a46 --- /dev/null +++ b/lib/features/create_diet/presentation/diet_preview_screen.dart @@ -0,0 +1,474 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; +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/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'; +import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart'; +import 'package:eatvio_app/core/widgets/main_background_bouble.dart'; +import 'package:eatvio_app/features/diets/presentation/widgets/more_options.dart'; +import 'package:eatvio_app/features/feed/models/diets_pagination_model.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'; +import 'package:flutter_portal/flutter_portal.dart'; +import 'package:flutter_svg/svg.dart'; + +class DietPreviewScreen extends StatefulWidget { + final Diet diet; + late final List selectedIndexes; + static const name = 'createDietScreenPreview'; + static const path = '/create-diet-screen/preview'; + DietPreviewScreen({ + super.key, + required this.diet, + }) { + selectedIndexes = List.generate(diet.dailySchedule.length, (_) => 0); + } + + @override + State createState() => _DietPreviewScreenState(); +} + +class _DietPreviewScreenState extends State { + final ScrollController scrollController = ScrollController(); + final textEditingController = TextEditingController(); + bool isMoreOptionsVisible = false; + late final Uint8List imageData; + @override + void initState() { + super.initState(); + imageData = base64.decode(widget.diet.image); + scrollController.addListener(() { + if (isMoreOptionsVisible) { + setState(() { + isMoreOptionsVisible = false; + }); + } + }); + } + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Scaffold( + backgroundColor: appTheme.gray100, + resizeToAvoidBottomInset: true, + bottomSheet: Container( + padding: EdgeInsets.symmetric(vertical: 12) + (Platform.isIOS ? const EdgeInsets.only(bottom: 16) : EdgeInsets.zero), + decoration: BoxDecoration( + color: Colors.white, + border: Border( + top: BorderSide( + color: Colors.black.withOpacity(0.1), + ), + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded( + child: SizedBox( + child: FloatingActionButton.extended( + heroTag: 'nextButtonRegistration', + highlightElevation: 0, + elevation: 0, + focusElevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + backgroundColor: appTheme.greenA700, + onPressed: () {}, + label: Text( + s.startDiet, + style: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + ), + ), + ), + ), + SizedBox(width: 8), + BookmarkButton( + isFavorite: widget.diet.isFavorite, + onTap: () {}, + width: 52, + height: 52, + size: 24, + shape: BoxShape.circle, + color: appTheme.gray50, + ), + ], + ), + SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: SizedBox( + child: TextFormField( + onChanged: (_) { + setState(() {}); + }, + controller: textEditingController, + maxLines: 5, + minLines: 1, + keyboardType: TextInputType.multiline, + style: TextStyles.textMMedium.copyWith(color: appTheme.black), + decoration: InputDecoration( + isDense: true, + contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + hintText: s.comment.capitalize(), + hintStyle: TextStyles.textMMedium.copyWith(color: appTheme.lightGray600), + filled: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide( + color: Color(0xFFD1D1D6), + width: 1, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide( + color: Color(0xFFD1D1D6), + width: 1, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide( + color: Color(0xFFD1D1D6), + width: 1, + ), + ), + ), + ), + ), + ), + SizedBox(width: 8), + GestureDetector( + onTap: () {}, + child: Container( + decoration: BoxDecoration( + color: textEditingController.text.length > 2 ? appTheme.blueA400 : appTheme.gray50, + shape: BoxShape.circle, + ), + height: 32, + width: 32, + child: Icon( + Icons.send_rounded, + size: 18, + color: textEditingController.text.length > 2 ? appTheme.whiteA700 : appTheme.black, + ), + ), + ), + ], + ), + ], + ), + ), + ), + appBar: AppBar( + centerTitle: true, + title: Text( + widget.diet.name, + maxLines: 2, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + softWrap: true, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + actions: [ + PortalTarget( + visible: isMoreOptionsVisible, + portalFollower: MoreOptions(disableActions: true, id: widget.diet.id), + anchor: Aligned( + shiftToWithinBound: AxisFlag(x: true, y: true), + follower: Alignment.topRight, + target: Alignment.bottomRight, + ), + child: IconButton( + onPressed: () { + setState(() { + isMoreOptionsVisible = !isMoreOptionsVisible; + }); + }, + icon: SvgPicture.asset( + 'assets/icons/more_button.svg', + width: 20, + height: 20, + ), + ), + ), + ], + ), + body: SingleChildScrollView( + controller: scrollController, + padding: EdgeInsets.only(bottom: 160), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 8), + MainBackgroundBouble( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Stack( + children: [ + Image.memory( + imageData, + height: 300, + width: MediaQuery.sizeOf(context).width, + fit: BoxFit.fill, + ), + // CachedNetworkImage( + // imageUrl: baseUrl + '/storage/' + widget.diet.image, + // ), + Positioned( + bottom: 16, + left: 0, + right: 0, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InfoBouble( + children: [ + SvgPicture.asset('assets/icons/eye.svg', width: 18, height: 12), + SizedBox(width: 8), + Text( + widget.diet.totalViews.toString(), + style: TextStyles.textXSSemibold.copyWith(color: appTheme.black), + ), + ], + backgroundColor: appTheme.whiteA700, + ), + SizedBox(width: 12), + GestureDetector( + onTap: () {}, + child: InfoBouble( + children: [ + SvgPicture.asset( + 'assets/icons/heart.svg', + width: 14, + height: 12, + colorFilter: widget.diet.wasLikedByMe ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null, + ), + SizedBox(width: 8), + Text( + widget.diet.totalLikes.toString(), + style: TextStyles.textXSSemibold.copyWith(color: appTheme.black), + ), + ], + backgroundColor: appTheme.whiteA700, + ), + ), + SizedBox(width: 12), + InfoBouble( + children: [ + SvgPicture.asset('assets/icons/comment2.svg', width: 13, height: 12), + SizedBox(width: 8), + Text( + widget.diet.totalComments.toString(), + style: TextStyles.textXSSemibold.copyWith(color: appTheme.black), + ), + ], + backgroundColor: appTheme.whiteA700, + ), + ], + ), + ), + ], + ), + SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + s.about_diet, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + ), + SizedBox(height: 12), + DietInfoRow(diet: widget.diet), + ExpandableText( + text: widget.diet.details, + ), + SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: AuthorCard( + author: widget.diet.author, + ), + ), + SizedBox(height: 16), + ], + ), + ), + SizedBox(height: 16), + MainBackgroundBouble( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Center( + child: Text( + s.diet_plan + '(${widget.diet.duration.toString()} ' + getDayForm(widget.diet.duration, s) + ')', + style: TextStyles.headingXLSemibold.copyWith( + color: appTheme.black, + ), + ), + ), + ), + ), + SizedBox(height: 8), + ListView.builder( + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemBuilder: (context, index) { + return _dayOfPlan(widget.diet.dailySchedule[index], index, s); + }, + itemCount: widget.diet.dailySchedule.length, + ), + MainBackgroundBouble( + padding: EdgeInsets.symmetric(vertical: 16), + child: Column( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 16), + alignment: Alignment.centerLeft, + child: Text( + '0' + ' ' + getCommentForm(widget.diet.totalComments, s), + style: TextStyles.textMMedium.copyWith( + color: appTheme.gray500, + ), + ), + ), + SizedBox(height: 16), + ], + ), + ), + ], + ), + ), + ); + } + + _dayOfPlan(List> day, int dayNumber, S s) { + _choiceChipWidget(int index) { + return ChoiceChip( + selectedColor: appTheme.blueA400, + pressElevation: 0, + showCheckmark: false, + side: BorderSide.none, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + label: Text((index + 1).toString()), + labelStyle: TextStyles.textXSSemibold.copyWith( + color: index == widget.selectedIndexes[dayNumber] ? Colors.white : appTheme.gray600, + ), + selected: widget.selectedIndexes[dayNumber] == index, + backgroundColor: appTheme.gray100, + onSelected: (bool selected) { + setState(() { + widget.selectedIndexes[dayNumber] = index; + }); + }, + ); + } + + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: MainBackgroundBouble( + padding: EdgeInsets.only( + top: 16, + ), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + s.day + ' ' + (dayNumber + 1).toString(), + style: TextStyles.headingXLSemibold.copyWith( + color: appTheme.black, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: CustomDivider(), + ), + Text( + s.meal, + style: TextStyles.textMMedium.copyWith( + color: appTheme.black, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _choiceChipWidget(0), + _choiceChipWidget(1), + _choiceChipWidget(2), + _choiceChipWidget(3), + _choiceChipWidget(4), + ], + ), + SizedBox(height: 8), + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => _daily(day[widget.selectedIndexes[dayNumber]][index], s), + itemCount: day[widget.selectedIndexes[dayNumber]].length, + ), + SizedBox(height: 8), + ], + ), + ), + CpfcContainer( + calories: widget.diet.dailyNutrition[dayNumber].energy, + proteins: widget.diet.dailyNutrition[dayNumber].proteins, + fats: widget.diet.dailyNutrition[dayNumber].fats, + carbohydrates: widget.diet.dailyNutrition[dayNumber].carbohydrates, + ), + ], + ), + ), + ); + } + + _daily(DailySchedule daily, S s) { + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Row( + children: [ + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.6, + child: Text( + daily.name, + style: TextStyles.textMMedium.copyWith( + color: appTheme.black, + ), + ), + ), + Spacer(), + Text( + daily.quantity.toString() + ' ' + getLocalizedMeasurementUnits(s, daily.portion), + style: TextStyles.textMSemibold.copyWith( + color: appTheme.black, + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/create_diet/presentation/widgets/add_meal_widget.dart b/lib/features/create_diet/presentation/widgets/add_meal_widget.dart index 67e41566..f705adb4 100644 --- a/lib/features/create_diet/presentation/widgets/add_meal_widget.dart +++ b/lib/features/create_diet/presentation/widgets/add_meal_widget.dart @@ -1,6 +1,5 @@ import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; -import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; @@ -34,6 +33,14 @@ class _AddMealWidgetState extends State { GlossaryModel? selectedGlossary; final productNameController = TextEditingController(); final quantityController = TextEditingController(); + + @override + void initState() { + super.initState(); + productNameController.text = widget.productNameControllerText ?? ''; + quantityController.text = widget.quantityControllerText ?? ''; + } + @override Widget build(BuildContext context) { return Padding( @@ -128,6 +135,11 @@ class _AddMealWidgetState extends State { if (widget.portionIndex != null) InkWell( onTap: () { + widget.createDietCubit.removePortion( + day: widget.dayIndex, + meal: widget.mealIndex, + portionIndex: widget.portionIndex!, + ); Navigator.of(context).pop(); }, borderRadius: BorderRadius.circular(20), @@ -146,55 +158,7 @@ class _AddMealWidgetState extends State { ), if (widget.portionIndex != null) const SizedBox(width: 8), InkWell( - onTap: () { - if (widget.portionIndex == null) { - // final glossary = selectedGlossary; - // if (glossary != null) { - // final measurementUnits = glossary.grInPortion != 0 ? Portion.PCS : Portion.GRAMS ; - // final portion = DailySchedule( - // quantity: int.parse(quantityController.text), - // name: glossary.name, - // uniq: glossary.id.toString(), - // portion: measurementUnits, - // ); - // widget.createDietCubit.addPortion( - // dayIndex: widget.dayIndex, - // mealIndex: widget.mealIndex, - // portion: portion, - // ); - // } - } else { - // final portion = DailySchedule( - // quantity: int.parse(quantityController.text), - // name: glossary.name, - // uniq: glossary.id.toString(), - // portion: measurementUnits, - // ); - // widget.createDietCubit.updatePortion( - // day: widget.dayIndex, - // meal: widget.mealIndex, - // portionIndex: widget.portionIndex!, - // portion: portion, - // ); - } - Navigator.of(context).pop(); - if (selectedGlossary == null) { - showModalBottomSheet( - isScrollControlled: true, - useRootNavigator: true, - context: context, - builder: (context) => Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom, - ), - child: GlossaryModerationRequestModalWidget( - productName: productNameController.text, - diaryCubit: sl(), - ), - ), - ); - } - }, + onTap: addPortion, borderRadius: BorderRadius.circular(20), child: Ink( width: MediaQuery.of(context).size.width - (widget.portionIndex == null ? 32 : 92), @@ -222,6 +186,101 @@ class _AddMealWidgetState extends State { ); } + Future addPortion() async { + final portionIndex = widget.portionIndex; + final measurementUnits = selectedGlossary?.grInPortion == null + ? Portion.PCS + : selectedGlossary?.grInPortion == 0 + ? Portion.GRAMS + : Portion.PCS; + final glossary = selectedGlossary; + if (glossary != null) { + final [calories, carbohydrates, proteins, fats] = _getCPFC(glossary, measurementUnits); + final portion = DailySchedule( + dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates), + quantity: int.parse(quantityController.text), + name: glossary.name, + uniq: '', + productNutritionalValueId: glossary.id.toString(), + portion: measurementUnits, + ); + if (portionIndex == null) { + widget.createDietCubit.addPortion( + dayIndex: widget.dayIndex, + mealIndex: widget.mealIndex, + portion: portion, + ); + } else { + widget.createDietCubit.updatePortion( + dayIndex: widget.dayIndex, + mealIndex: widget.mealIndex, + portionIndex: portionIndex, + portion: portion, + ); + } + } else { + Navigator.of(context).pop(); + final newGlossary = await showModalBottomSheet( + isScrollControlled: true, + useRootNavigator: true, + context: context, + builder: (context) => Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: GlossaryModerationRequestModalWidget( + productName: productNameController.text, + diaryCubit: sl(), + ), + ), + ); + if (newGlossary != null) { + final [calories, carbohydrates, proteins, fats] = _getCPFC(newGlossary, Portion.GRAMS); + final portion = DailySchedule( + dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates), + quantity: int.parse(quantityController.text), + name: newGlossary.name, + uniq: '', + productNutritionalValueId: newGlossary.id.toString(), + portion: measurementUnits, + ); + if (portionIndex == null) { + widget.createDietCubit.addPortion( + dayIndex: widget.dayIndex, + mealIndex: widget.mealIndex, + portion: portion, + ); + } else { + widget.createDietCubit.updatePortion( + dayIndex: widget.dayIndex, + mealIndex: widget.mealIndex, + portion: portion, + portionIndex: portionIndex, + ); + } + } + } + } + + List _getCPFC(GlossaryModel glossary, Portion measurementUnits) { + final double calories; + final double carbohydrates; + final double proteins; + final double fats; + if (measurementUnits == Portion.GRAMS) { + calories = (glossary.energyOn100Gr * int.parse(quantityController.text) / 100); + carbohydrates = (glossary.carbohydratesOn100Gr * int.parse(quantityController.text) / 100); + proteins = (glossary.proteinsOn100Gr * int.parse(quantityController.text) / 100); + fats = (glossary.fatsOn100Gr * int.parse(quantityController.text) / 100); + } else { + calories = (glossary.energyOnPortion * int.parse(quantityController.text)); + carbohydrates = (glossary.carbohydratesOnPortion * int.parse(quantityController.text)); + proteins = (glossary.proteinsOnPortion * int.parse(quantityController.text)); + fats = (glossary.fatsOnPortion * int.parse(quantityController.text)); + } + return [calories, carbohydrates, proteins, fats]; + } + _divider(BuildContext context) { return Container( height: 0.4, diff --git a/lib/features/create_diet/presentation/widgets/day_widget.dart b/lib/features/create_diet/presentation/widgets/day_widget.dart index 4d9c470c..732d6a7a 100644 --- a/lib/features/create_diet/presentation/widgets/day_widget.dart +++ b/lib/features/create_diet/presentation/widgets/day_widget.dart @@ -1,9 +1,10 @@ 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/utils/toasts.dart'; +import 'package:eatvio_app/core/widgets/cpfc_container.dart'; import 'package:eatvio_app/core/widgets/custom_divider.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'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart'; @@ -13,10 +14,9 @@ import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:flutter_typeahead/flutter_typeahead.dart'; class DayWidget extends StatefulWidget { - const DayWidget({ + DayWidget({ super.key, required this.index, }); @@ -34,175 +34,250 @@ class _DayWidgetState extends State { return BlocBuilder( builder: (context, state) { return MainBackgroundBouble( - child: Padding( - padding: EdgeInsets.only( - left: 16, - right: 16, - top: 16, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 16, right: 16, top: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row( + children: [ + Text( + s.day + ' ${widget.index + 1}', + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + Spacer(), + GestureDetector( + onTap: () { + context.read().copyDay(widget.index); + showInfoToast('${s.day} ${widget.index + 1} ${s.copied}'); + }, + child: SvgPicture.asset( + 'assets/icons/copy2.svg', + width: 20, + height: 24, + colorFilter: ColorFilter.mode(appTheme.black, BlendMode.srcIn), + ), + ), + ], + ), + SizedBox( + height: 12, + ), + CustomDivider(), + SizedBox( + height: 12, + ), Text( - s.day + ' ${widget.index + 1}', - style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + s.meal, + style: TextStyles.textMMedium.copyWith(color: appTheme.black), ), - Spacer(), - SvgPicture.asset( - 'assets/icons/copy2.svg', - width: 20, - height: 24, - colorFilter: ColorFilter.mode(appTheme.black, BlendMode.srcIn), - ), - ], - ), - SizedBox( - height: 12, - ), - CustomDivider(), - SizedBox( - height: 12, - ), - Text( - s.meal, - style: TextStyles.textMMedium.copyWith(color: appTheme.black), - ), - SizedBox(height: 16), - Row( - children: [ - ChoiceChipWidget( - chipsLabelList: chipsLabelList, - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6), - fontSize: TextStyles.textMMedium.fontSize, - fontWeight: TextStyles.textMMedium.fontWeight, - index: 0, - selectedChipIndex: _selectedMealIndex, - onSelected: (selectedValue) { - setState(() { - _selectedMealIndex = 0; - }); - }, - ), - Spacer(), - ChoiceChipWidget( - chipsLabelList: chipsLabelList, - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6), - fontSize: TextStyles.textMMedium.fontSize, - fontWeight: TextStyles.textMMedium.fontWeight, - index: 1, - selectedChipIndex: _selectedMealIndex, - onSelected: (selectedValue) { - setState(() { - _selectedMealIndex = 1; - }); - }, - ), - Spacer(), - ChoiceChipWidget( - chipsLabelList: chipsLabelList, - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6), - fontSize: TextStyles.textMMedium.fontSize, - fontWeight: TextStyles.textMMedium.fontWeight, - index: 2, - selectedChipIndex: _selectedMealIndex, - onSelected: (selectedValue) { - setState(() { - _selectedMealIndex = 2; - }); - }, - ), - Spacer(), - ChoiceChipWidget( - chipsLabelList: chipsLabelList, - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6), - fontSize: TextStyles.textMMedium.fontSize, - fontWeight: TextStyles.textMMedium.fontWeight, - index: 3, - selectedChipIndex: _selectedMealIndex, - onSelected: (selectedValue) { - setState(() { - _selectedMealIndex = 3; - }); - }, - ), - Spacer(), - ChoiceChipWidget( - chipsLabelList: chipsLabelList, - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6), - fontSize: TextStyles.textMMedium.fontSize, - fontWeight: TextStyles.textMMedium.fontWeight, - index: 4, - selectedChipIndex: _selectedMealIndex, - onSelected: (selectedValue) { - setState(() { - _selectedMealIndex = 4; - }); - }, - ), - ], - ), - ListView.builder( - itemBuilder: (context, index) { - final product = state.dietData.dailySchedule?[widget.index][_selectedMealIndex][index]; - if (product != null) { - return _product( - index, - product.name, - product.quantity, - getLocalizedMeasurementUnits( - s, - product.portion, + SizedBox(height: 16), + Row( + children: [ + ChoiceChipWidget( + chipsLabelList: chipsLabelList, + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6), + fontSize: TextStyles.textMMedium.fontSize, + fontWeight: TextStyles.textMMedium.fontWeight, + index: 0, + selectedChipIndex: _selectedMealIndex, + onSelected: (selectedValue) { + setState(() { + _selectedMealIndex = 0; + }); + }, ), - widget.index, - _selectedMealIndex, - ); - } - return null; - }, - itemCount: state.dietData.dailySchedule?[widget.index][_selectedMealIndex].length ?? 0, - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - ), - SizedBox(height: 16), - Row( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 12, vertical: 14), - decoration: BoxDecoration(color: appTheme.gray50, borderRadius: BorderRadius.all(Radius.circular(12))), - child: SvgPicture.asset( - 'assets/icons/paste2.svg', - width: 28, - height: 24, - ), + Spacer(), + ChoiceChipWidget( + chipsLabelList: chipsLabelList, + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6), + fontSize: TextStyles.textMMedium.fontSize, + fontWeight: TextStyles.textMMedium.fontWeight, + index: 1, + selectedChipIndex: _selectedMealIndex, + onSelected: (selectedValue) { + setState(() { + _selectedMealIndex = 1; + }); + }, + ), + Spacer(), + ChoiceChipWidget( + chipsLabelList: chipsLabelList, + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6), + fontSize: TextStyles.textMMedium.fontSize, + fontWeight: TextStyles.textMMedium.fontWeight, + index: 2, + selectedChipIndex: _selectedMealIndex, + onSelected: (selectedValue) { + setState(() { + _selectedMealIndex = 2; + }); + }, + ), + Spacer(), + ChoiceChipWidget( + chipsLabelList: chipsLabelList, + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6), + fontSize: TextStyles.textMMedium.fontSize, + fontWeight: TextStyles.textMMedium.fontWeight, + index: 3, + selectedChipIndex: _selectedMealIndex, + onSelected: (selectedValue) { + setState(() { + _selectedMealIndex = 3; + }); + }, + ), + Spacer(), + ChoiceChipWidget( + chipsLabelList: chipsLabelList, + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6), + fontSize: TextStyles.textMMedium.fontSize, + fontWeight: TextStyles.textMMedium.fontWeight, + index: 4, + selectedChipIndex: _selectedMealIndex, + onSelected: (selectedValue) { + setState(() { + _selectedMealIndex = 4; + }); + }, + ), + ], ), - SizedBox(width: 8), - Expanded( - child: CustomOutlinedButton( - onPressed: () { - _modalBottomSheet( - dayIndex: widget.index, - mealIndex: _selectedMealIndex, - createDietCubit: context.read(), + ListView.separated( + separatorBuilder: (context, index) => SizedBox( + height: 8, + ), + padding: EdgeInsets.all(0), + itemBuilder: (context, index) { + final product = state.dietData.dailySchedule?[widget.index][_selectedMealIndex][index]; + if (product != null) { + return _product( + index, + product.name, + product.quantity, + getLocalizedMeasurementUnits( + s, + product.portion, + ), + widget.index, + _selectedMealIndex, ); - }, - text: s.add, - height: 52, - buttonTextStyle: TextStyles.textMSemibold.copyWith( - color: appTheme.whiteA700, - ), - buttonStyle: ButtonStyle( - side: WidgetStatePropertyAll(BorderSide.none), - backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), - ), - ), + } + return null; + }, + itemCount: state.dietData.dailySchedule?[widget.index][_selectedMealIndex].length ?? 0, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), ), + SizedBox(height: 16), + Row( + children: [ + GestureDetector( + onTap: () { + context.read().pasteCopiedDay(widget.index); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 14), + decoration: BoxDecoration(color: appTheme.gray50, borderRadius: BorderRadius.all(Radius.circular(12))), + child: SvgPicture.asset( + 'assets/icons/paste2.svg', + width: 28, + height: 24, + ), + ), + ), + SizedBox(width: 8), + Expanded( + child: CustomOutlinedButton( + onPressed: () { + _modalBottomSheet( + dayIndex: widget.index, + mealIndex: _selectedMealIndex, + createDietCubit: context.read(), + ); + }, + text: s.add, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith( + color: appTheme.whiteA700, + ), + buttonStyle: ButtonStyle( + side: WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + ), + ), + ), + ], + ), + SizedBox(height: 20), ], ), - SizedBox(height: 20), - ], - ), + ), + BlocBuilder( + buildWhen: (previous, current) => previous.dietData.dailySchedule != current.dietData.dailySchedule, + builder: (context, state) { + if (state.dietData.dailySchedule != null) { + final day = state.dietData.dailySchedule?[widget.index]; + final double calories = day!.fold( + 0, + (previousValue, element) => + previousValue + + element.fold( + 0, + (previousValue, element) => previousValue + (element.dailyNutrition?.energy ?? 0), + ), + ); + + final double proteins = day.fold( + 0, + (previousValue, element) => + previousValue + + element.fold( + 0, + (previousValue, element) => previousValue + (element.dailyNutrition?.proteins ?? 0), + ), + ); + + final double fats = day.fold( + 0, + (previousValue, element) => + previousValue + + element.fold( + 0, + (previousValue, element) => previousValue + (element.dailyNutrition?.fats ?? 0), + ), + ); + + final double carbohydrates = day.fold( + 0, + (previousValue, element) => + previousValue + + element.fold( + 0, + (previousValue, element) => previousValue + (element.dailyNutrition?.carbohydrates ?? 0), + ), + ); + return CpfcContainer( + calories: calories, + proteins: proteins, + fats: fats, + carbohydrates: carbohydrates, + ); + } + return CpfcContainer( + calories: 0, + proteins: 0, + fats: 0, + carbohydrates: 0, + ); + }, + ), + ], ), ); }, @@ -210,24 +285,26 @@ class _DayWidgetState extends State { } _product(int index, String product, int quantity, String portion, int dayIndex, int mealIndex) { - return ListTile( - onTap: () { - _modalBottomSheet( - mealIndex: mealIndex, - dayIndex: dayIndex, - createDietCubit: context.read(), - portionIndex: index, - productNameControllerText: product, - quantityControllerText: quantity.toString(), - ); - }, - trailing: Icon(Icons.more_vert_rounded), - title: Text(product), - subtitle: Text('$quantity $portion'), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), + return Material( + child: ListTile( + onTap: () { + _modalBottomSheet( + mealIndex: mealIndex, + dayIndex: dayIndex, + createDietCubit: context.read(), + portionIndex: index, + productNameControllerText: product, + quantityControllerText: quantity.toString(), + ); + }, + trailing: Icon(Icons.more_vert_rounded), + title: Text(product), + subtitle: Text('$quantity $portion'), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + tileColor: appTheme.gray50, ), - tileColor: Color(0xFFF6FAFF), ); } diff --git a/lib/features/create_diet/presentation/widgets/duration_drop_down_widget.dart b/lib/features/create_diet/presentation/widgets/diet_duration_drop_down_widget.dart similarity index 83% rename from lib/features/create_diet/presentation/widgets/duration_drop_down_widget.dart rename to lib/features/create_diet/presentation/widgets/diet_duration_drop_down_widget.dart index c4b1ed31..fda76a4f 100644 --- a/lib/features/create_diet/presentation/widgets/duration_drop_down_widget.dart +++ b/lib/features/create_diet/presentation/widgets/diet_duration_drop_down_widget.dart @@ -10,14 +10,14 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class DurationDropDownWidget extends StatefulWidget { - const DurationDropDownWidget({super.key}); +class DietDurationDropDownWidget extends StatefulWidget { + const DietDurationDropDownWidget({super.key}); @override - State createState() => _DurationDropDownWidgetState(); + State createState() => _DietDurationDropDownWidgetState(); } -class _DurationDropDownWidgetState extends State { +class _DietDurationDropDownWidgetState extends State { final List durationDropDownTitles = []; @override @@ -38,9 +38,9 @@ class _DurationDropDownWidgetState extends State { builder: (context, state) { return CustomDropDownButton( hint: Text( - s.choose_diet_duration, - style: TextStyles.textMMedium.copyWith(color: appTheme.black), - ), + s.choose_diet_duration, + style: TextStyles.textMMedium.copyWith(color: appTheme.black), + ), onChanged: (value) { if (value != null) { context.read().setDuration(durationDropDownTitles.indexOf(value)); diff --git a/lib/features/create_diet/presentation/widgets/image_picker_widget.dart b/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart similarity index 77% rename from lib/features/create_diet/presentation/widgets/image_picker_widget.dart rename to lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart index e6bb9a82..42ba88a2 100644 --- a/lib/features/create_diet/presentation/widgets/image_picker_widget.dart +++ b/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart @@ -12,14 +12,14 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:image_picker/image_picker.dart'; -class ImagePickerWidget extends StatefulWidget { - const ImagePickerWidget({super.key}); +class DietImagePickerWidget extends StatefulWidget { + const DietImagePickerWidget({super.key}); @override - State createState() => _ImagePickerWidgetState(); + State createState() => _DietImagePickerWidgetState(); } -class _ImagePickerWidgetState extends State { +class _DietImagePickerWidgetState extends State { void _showImagePickerOptions(S s, CreateDietCubit cubit) { showCupertinoModalPopup( context: context, @@ -84,7 +84,30 @@ class _ImagePickerWidgetState extends State { width: MediaQuery.sizeOf(context).width, padding: state.dietData.image != null ? null : EdgeInsets.all(8), child: state.dietData.image != null - ? null + ? Padding( + padding: const EdgeInsets.only(top: 6, right: 6), + child: GestureDetector( + onTap: () { + context.read().setImage(null); + }, + child: Align( + alignment: Alignment.topRight, + child: Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: appTheme.dividerColor, + shape: BoxShape.circle, + ), + child: Icon( + Icons.close_rounded, + color: Colors.grey, + size: 16, + ), + ), + ), + ), + ) : Stack( children: [ DottedBorder( diff --git a/lib/features/create_diet/presentation/widgets/type_choice_widget.dart b/lib/features/create_diet/presentation/widgets/diet_type_choice_widget.dart similarity index 92% rename from lib/features/create_diet/presentation/widgets/type_choice_widget.dart rename to lib/features/create_diet/presentation/widgets/diet_type_choice_widget.dart index 85b785e4..fd324885 100644 --- a/lib/features/create_diet/presentation/widgets/type_choice_widget.dart +++ b/lib/features/create_diet/presentation/widgets/diet_type_choice_widget.dart @@ -7,14 +7,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; -class TypeChoiceWidget extends StatefulWidget { - TypeChoiceWidget({super.key}); +class DietTypeChoiceWidget extends StatefulWidget { + DietTypeChoiceWidget({super.key}); @override - State createState() => _TypeChoiceWidgetState(); + State createState() => _DietTypeChoiceWidgetState(); } -class _TypeChoiceWidgetState extends State { +class _DietTypeChoiceWidgetState extends State { final List<(String, String)> choiceContent = []; @override void didChangeDependencies() { diff --git a/lib/features/create_diet/presentation/widgets/status_drop_down_widget.dart b/lib/features/create_diet/presentation/widgets/status_drop_down_widget.dart index 999bda84..d2db75ee 100644 --- a/lib/features/create_diet/presentation/widgets/status_drop_down_widget.dart +++ b/lib/features/create_diet/presentation/widgets/status_drop_down_widget.dart @@ -24,7 +24,6 @@ class _StatusDropDownWidgetState extends State { final s = S.of(context); if (dietStateTitles.isEmpty) { dietStateTitles.add(s.active_diet_state); - dietStateTitles.add(s.inactive_diet_state); dietStateTitles.add(s.personal_diet_state); } } diff --git a/lib/features/create_plan/models/create_plan_model.dart b/lib/features/create_plan/models/create_plan_model.dart new file mode 100644 index 00000000..c4d3ed21 --- /dev/null +++ b/lib/features/create_plan/models/create_plan_model.dart @@ -0,0 +1,36 @@ +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart'; +import 'package:eatvio_app/features/plans/models/plan_pagination_model.dart'; +import 'package:flutter_image_compress/flutter_image_compress.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'create_plan_model.freezed.dart'; + +@freezed +class CreatePlanModel with _$CreatePlanModel { + const CreatePlanModel._(); + + const factory CreatePlanModel({ + String? name, + String? details, + String? state, + int? duration, + required List type, + XFile? image, + required List options, + }) = _CreatePlanModel; + + Future> toCreationJson() async => { + 'name': name, + 'duration': duration, + 'state': state, + 'type': type, + 'details': details, + 'image': image != null ? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image!.path))).path, filename: image!.name) : null, + }; + + Map toOptionCreationJson() => {'packet1_options': options[0]}; + Map toOptionsCreationJson() => {'packet1_options': options[0], 'packet2_options': options[1], 'packet3_options': options[2]}; +} diff --git a/lib/features/create_plan/models/create_plan_model.freezed.dart b/lib/features/create_plan/models/create_plan_model.freezed.dart new file mode 100644 index 00000000..226b3072 --- /dev/null +++ b/lib/features/create_plan/models/create_plan_model.freezed.dart @@ -0,0 +1,292 @@ +// 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 'create_plan_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$CreatePlanModel { + String? get name => throw _privateConstructorUsedError; + String? get details => throw _privateConstructorUsedError; + String? get state => throw _privateConstructorUsedError; + int? get duration => throw _privateConstructorUsedError; + List get type => throw _privateConstructorUsedError; + XFile? get image => throw _privateConstructorUsedError; + List get options => throw _privateConstructorUsedError; + + /// Create a copy of CreatePlanModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CreatePlanModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CreatePlanModelCopyWith<$Res> { + factory $CreatePlanModelCopyWith( + CreatePlanModel value, $Res Function(CreatePlanModel) then) = + _$CreatePlanModelCopyWithImpl<$Res, CreatePlanModel>; + @useResult + $Res call( + {String? name, + String? details, + String? state, + int? duration, + List type, + XFile? image, + List options}); +} + +/// @nodoc +class _$CreatePlanModelCopyWithImpl<$Res, $Val extends CreatePlanModel> + implements $CreatePlanModelCopyWith<$Res> { + _$CreatePlanModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CreatePlanModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = freezed, + Object? details = freezed, + Object? state = freezed, + Object? duration = freezed, + Object? type = null, + Object? image = freezed, + Object? options = null, + }) { + return _then(_value.copyWith( + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + details: freezed == details + ? _value.details + : details // ignore: cast_nullable_to_non_nullable + as String?, + state: freezed == state + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as String?, + duration: freezed == duration + ? _value.duration + : duration // ignore: cast_nullable_to_non_nullable + as int?, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as List, + image: freezed == image + ? _value.image + : image // ignore: cast_nullable_to_non_nullable + as XFile?, + options: null == options + ? _value.options + : options // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$CreatePlanModelImplCopyWith<$Res> + implements $CreatePlanModelCopyWith<$Res> { + factory _$$CreatePlanModelImplCopyWith(_$CreatePlanModelImpl value, + $Res Function(_$CreatePlanModelImpl) then) = + __$$CreatePlanModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String? name, + String? details, + String? state, + int? duration, + List type, + XFile? image, + List options}); +} + +/// @nodoc +class __$$CreatePlanModelImplCopyWithImpl<$Res> + extends _$CreatePlanModelCopyWithImpl<$Res, _$CreatePlanModelImpl> + implements _$$CreatePlanModelImplCopyWith<$Res> { + __$$CreatePlanModelImplCopyWithImpl( + _$CreatePlanModelImpl _value, $Res Function(_$CreatePlanModelImpl) _then) + : super(_value, _then); + + /// Create a copy of CreatePlanModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = freezed, + Object? details = freezed, + Object? state = freezed, + Object? duration = freezed, + Object? type = null, + Object? image = freezed, + Object? options = null, + }) { + return _then(_$CreatePlanModelImpl( + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + details: freezed == details + ? _value.details + : details // ignore: cast_nullable_to_non_nullable + as String?, + state: freezed == state + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as String?, + duration: freezed == duration + ? _value.duration + : duration // ignore: cast_nullable_to_non_nullable + as int?, + type: null == type + ? _value._type + : type // ignore: cast_nullable_to_non_nullable + as List, + image: freezed == image + ? _value.image + : image // ignore: cast_nullable_to_non_nullable + as XFile?, + options: null == options + ? _value._options + : options // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +class _$CreatePlanModelImpl extends _CreatePlanModel { + const _$CreatePlanModelImpl( + {this.name, + this.details, + this.state, + this.duration, + required final List type, + this.image, + required final List options}) + : _type = type, + _options = options, + super._(); + + @override + final String? name; + @override + final String? details; + @override + final String? state; + @override + final int? duration; + final List _type; + @override + List get type { + if (_type is EqualUnmodifiableListView) return _type; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_type); + } + + @override + final XFile? image; + final List _options; + @override + List get options { + if (_options is EqualUnmodifiableListView) return _options; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_options); + } + + @override + String toString() { + return 'CreatePlanModel(name: $name, details: $details, state: $state, duration: $duration, type: $type, image: $image, options: $options)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CreatePlanModelImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.details, details) || other.details == details) && + (identical(other.state, state) || other.state == state) && + (identical(other.duration, duration) || + other.duration == duration) && + const DeepCollectionEquality().equals(other._type, _type) && + (identical(other.image, image) || other.image == image) && + const DeepCollectionEquality().equals(other._options, _options)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + name, + details, + state, + duration, + const DeepCollectionEquality().hash(_type), + image, + const DeepCollectionEquality().hash(_options)); + + /// Create a copy of CreatePlanModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CreatePlanModelImplCopyWith<_$CreatePlanModelImpl> get copyWith => + __$$CreatePlanModelImplCopyWithImpl<_$CreatePlanModelImpl>( + this, _$identity); +} + +abstract class _CreatePlanModel extends CreatePlanModel { + const factory _CreatePlanModel( + {final String? name, + final String? details, + final String? state, + final int? duration, + required final List type, + final XFile? image, + required final List options}) = _$CreatePlanModelImpl; + const _CreatePlanModel._() : super._(); + + @override + String? get name; + @override + String? get details; + @override + String? get state; + @override + int? get duration; + @override + List get type; + @override + XFile? get image; + @override + List get options; + + /// Create a copy of CreatePlanModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CreatePlanModelImplCopyWith<_$CreatePlanModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/create_plan/presentation/create_plan_screen.dart b/lib/features/create_plan/presentation/create_plan_screen.dart new file mode 100644 index 00000000..5eda713c --- /dev/null +++ b/lib/features/create_plan/presentation/create_plan_screen.dart @@ -0,0 +1,145 @@ +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/custom_text_form_field.dart'; +import 'package:eatvio_app/core/widgets/main_background_bouble.dart'; +import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart'; +import 'package:eatvio_app/features/create_plan/presentation/widgets/options_count_button.dart'; +import 'package:eatvio_app/features/create_plan/presentation/widgets/plan_duration_drop_down_widget.dart'; +import 'package:eatvio_app/features/create_plan/presentation/widgets/plan_image_picker_widget.dart'; +import 'package:eatvio_app/features/create_plan/presentation/widgets/plan_type_choice_widget.dart'; +import 'package:eatvio_app/features/create_plan/presentation/widgets/select_exact_option_widget.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CreatePlanScreen extends StatelessWidget { + static const name = 'createPlanScreen'; + static const path = '/create-Plan-screen'; + const CreatePlanScreen({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => sl(), + child: const CreatePlanScreenContent(), + ); + } +} + +class CreatePlanScreenContent extends StatelessWidget { + const CreatePlanScreenContent({super.key}); + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Scaffold( + backgroundColor: appTheme.gray100, + appBar: AppBar( + centerTitle: true, + title: Text( + s.adding_plan, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + softWrap: true, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + ), + body: Stack( + children: [ + SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 8), + MainBackgroundBouble( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16), + Text( + s.label, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + softWrap: true, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + CustomTextFormField( + hintText: s.plan_label_desc, + onChanged: (value) { + context.read().setName(value); + }, + maxLength: 150, + ), + SizedBox(height: 12), + Text( + s.plan_duration, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + softWrap: true, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + PlanDurationDropDownWidget(), + SizedBox(height: 20), + PlanImagePickerWidget(), + SizedBox(height: 16), + PlanTypeChoiceWidget(), + SizedBox(height: 16), + Text( + s.plan_desc, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + softWrap: true, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + SizedBox( + height: 132, + child: CustomTextFormField( + textAlignVertical: TextAlignVertical.top, + maxLength: 1000, + maxLines: null, + minLines: null, + expands: true, + hintText: s.plan_desc_example, + onChanged: (value) { + context.read().setDetails(value); + }, + ), + ), + SizedBox(height: 16), + ], + ), + ), + SizedBox(height: 20), + Padding( + padding: const EdgeInsets.only(left: 16), + child: Text( + s.plan_desc, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + softWrap: true, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.blueGray400), + ), + ), + SizedBox(height: 8), + OptionsCountButton(), + SizedBox(height: 200), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 24.0), + child: Align( + alignment: Alignment.bottomCenter, + child: SelectExactOptionWidget(), + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/create_plan/presentation/cubit/create_plan_cubit.dart b/lib/features/create_plan/presentation/cubit/create_plan_cubit.dart new file mode 100644 index 00000000..19f782dd --- /dev/null +++ b/lib/features/create_plan/presentation/cubit/create_plan_cubit.dart @@ -0,0 +1,43 @@ +import 'package:eatvio_app/core/const.dart'; +import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_image_compress/flutter_image_compress.dart'; + +class CreatePlanCubit extends Cubit { + CreatePlanCubit() : super(CreatePlanState.initial()); + + void setDetails(String text) { + emit(state.copyWith(planData: state.planData.copyWith(details: text))); + } + + void setName(String name) { + emit(state.copyWith(planData: state.planData.copyWith(name: name))); + } + + void setDuration(int index) { + emit( + state.copyWith( + durationIndex: index, + planData: state.planData.copyWith( + duration: durationValues[index], + ), + ), + ); + } + + void setImage(XFile? image) { + emit(state.copyWith(planData: state.planData.copyWith(image: image))); + } + + void setPlanState(String planState) { + emit(state.copyWith(planData: state.planData.copyWith(state: planState))); + } + + void setPlanType(List type) { + emit(state.copyWith(planData: state.planData.copyWith(type: type))); + } + + void setSelectedOptionIndex(int? index) { + emit(state.copyWith(selectedOptionIndex: index)); + } +} diff --git a/lib/features/create_plan/presentation/cubit/create_plan_state.dart b/lib/features/create_plan/presentation/cubit/create_plan_state.dart new file mode 100644 index 00000000..fc8edf18 --- /dev/null +++ b/lib/features/create_plan/presentation/cubit/create_plan_state.dart @@ -0,0 +1,25 @@ +import 'package:eatvio_app/features/create_plan/models/create_plan_model.dart'; +import 'package:eatvio_app/features/plans/models/plan_pagination_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'create_plan_state.freezed.dart'; + +@freezed +class CreatePlanState with _$CreatePlanState { + const factory CreatePlanState({ + required bool isLoading, + required String? errorMessage, + required CreatePlanModel planData, + int? durationIndex, + int? selectedOptionIndex, + }) = _CreatePlanState; + + factory CreatePlanState.initial() => CreatePlanState( + isLoading: false, + errorMessage: null, + planData: CreatePlanModel( + type: [false, false, false, false], + options: [PacketOptions.empty(), PacketOptions.empty(), PacketOptions.empty()], + ), + ); +} diff --git a/lib/features/create_plan/presentation/cubit/create_plan_state.freezed.dart b/lib/features/create_plan/presentation/cubit/create_plan_state.freezed.dart new file mode 100644 index 00000000..bb7eba17 --- /dev/null +++ b/lib/features/create_plan/presentation/cubit/create_plan_state.freezed.dart @@ -0,0 +1,249 @@ +// 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 'create_plan_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$CreatePlanState { + bool get isLoading => throw _privateConstructorUsedError; + String? get errorMessage => throw _privateConstructorUsedError; + CreatePlanModel get planData => throw _privateConstructorUsedError; + int? get durationIndex => throw _privateConstructorUsedError; + int? get selectedOptionIndex => throw _privateConstructorUsedError; + + /// Create a copy of CreatePlanState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CreatePlanStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CreatePlanStateCopyWith<$Res> { + factory $CreatePlanStateCopyWith( + CreatePlanState value, $Res Function(CreatePlanState) then) = + _$CreatePlanStateCopyWithImpl<$Res, CreatePlanState>; + @useResult + $Res call( + {bool isLoading, + String? errorMessage, + CreatePlanModel planData, + int? durationIndex, + int? selectedOptionIndex}); + + $CreatePlanModelCopyWith<$Res> get planData; +} + +/// @nodoc +class _$CreatePlanStateCopyWithImpl<$Res, $Val extends CreatePlanState> + implements $CreatePlanStateCopyWith<$Res> { + _$CreatePlanStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CreatePlanState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isLoading = null, + Object? errorMessage = freezed, + Object? planData = null, + Object? durationIndex = freezed, + Object? selectedOptionIndex = freezed, + }) { + return _then(_value.copyWith( + isLoading: null == isLoading + ? _value.isLoading + : isLoading // ignore: cast_nullable_to_non_nullable + as bool, + errorMessage: freezed == errorMessage + ? _value.errorMessage + : errorMessage // ignore: cast_nullable_to_non_nullable + as String?, + planData: null == planData + ? _value.planData + : planData // ignore: cast_nullable_to_non_nullable + as CreatePlanModel, + durationIndex: freezed == durationIndex + ? _value.durationIndex + : durationIndex // ignore: cast_nullable_to_non_nullable + as int?, + selectedOptionIndex: freezed == selectedOptionIndex + ? _value.selectedOptionIndex + : selectedOptionIndex // ignore: cast_nullable_to_non_nullable + as int?, + ) as $Val); + } + + /// Create a copy of CreatePlanState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $CreatePlanModelCopyWith<$Res> get planData { + return $CreatePlanModelCopyWith<$Res>(_value.planData, (value) { + return _then(_value.copyWith(planData: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$CreatePlanStateImplCopyWith<$Res> + implements $CreatePlanStateCopyWith<$Res> { + factory _$$CreatePlanStateImplCopyWith(_$CreatePlanStateImpl value, + $Res Function(_$CreatePlanStateImpl) then) = + __$$CreatePlanStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {bool isLoading, + String? errorMessage, + CreatePlanModel planData, + int? durationIndex, + int? selectedOptionIndex}); + + @override + $CreatePlanModelCopyWith<$Res> get planData; +} + +/// @nodoc +class __$$CreatePlanStateImplCopyWithImpl<$Res> + extends _$CreatePlanStateCopyWithImpl<$Res, _$CreatePlanStateImpl> + implements _$$CreatePlanStateImplCopyWith<$Res> { + __$$CreatePlanStateImplCopyWithImpl( + _$CreatePlanStateImpl _value, $Res Function(_$CreatePlanStateImpl) _then) + : super(_value, _then); + + /// Create a copy of CreatePlanState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isLoading = null, + Object? errorMessage = freezed, + Object? planData = null, + Object? durationIndex = freezed, + Object? selectedOptionIndex = freezed, + }) { + return _then(_$CreatePlanStateImpl( + isLoading: null == isLoading + ? _value.isLoading + : isLoading // ignore: cast_nullable_to_non_nullable + as bool, + errorMessage: freezed == errorMessage + ? _value.errorMessage + : errorMessage // ignore: cast_nullable_to_non_nullable + as String?, + planData: null == planData + ? _value.planData + : planData // ignore: cast_nullable_to_non_nullable + as CreatePlanModel, + durationIndex: freezed == durationIndex + ? _value.durationIndex + : durationIndex // ignore: cast_nullable_to_non_nullable + as int?, + selectedOptionIndex: freezed == selectedOptionIndex + ? _value.selectedOptionIndex + : selectedOptionIndex // ignore: cast_nullable_to_non_nullable + as int?, + )); + } +} + +/// @nodoc + +class _$CreatePlanStateImpl implements _CreatePlanState { + const _$CreatePlanStateImpl( + {required this.isLoading, + required this.errorMessage, + required this.planData, + this.durationIndex, + this.selectedOptionIndex}); + + @override + final bool isLoading; + @override + final String? errorMessage; + @override + final CreatePlanModel planData; + @override + final int? durationIndex; + @override + final int? selectedOptionIndex; + + @override + String toString() { + return 'CreatePlanState(isLoading: $isLoading, errorMessage: $errorMessage, planData: $planData, durationIndex: $durationIndex, selectedOptionIndex: $selectedOptionIndex)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CreatePlanStateImpl && + (identical(other.isLoading, isLoading) || + other.isLoading == isLoading) && + (identical(other.errorMessage, errorMessage) || + other.errorMessage == errorMessage) && + (identical(other.planData, planData) || + other.planData == planData) && + (identical(other.durationIndex, durationIndex) || + other.durationIndex == durationIndex) && + (identical(other.selectedOptionIndex, selectedOptionIndex) || + other.selectedOptionIndex == selectedOptionIndex)); + } + + @override + int get hashCode => Object.hash(runtimeType, isLoading, errorMessage, + planData, durationIndex, selectedOptionIndex); + + /// Create a copy of CreatePlanState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CreatePlanStateImplCopyWith<_$CreatePlanStateImpl> get copyWith => + __$$CreatePlanStateImplCopyWithImpl<_$CreatePlanStateImpl>( + this, _$identity); +} + +abstract class _CreatePlanState implements CreatePlanState { + const factory _CreatePlanState( + {required final bool isLoading, + required final String? errorMessage, + required final CreatePlanModel planData, + final int? durationIndex, + final int? selectedOptionIndex}) = _$CreatePlanStateImpl; + + @override + bool get isLoading; + @override + String? get errorMessage; + @override + CreatePlanModel get planData; + @override + int? get durationIndex; + @override + int? get selectedOptionIndex; + + /// Create a copy of CreatePlanState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CreatePlanStateImplCopyWith<_$CreatePlanStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/create_plan/presentation/widgets/option_widget.dart b/lib/features/create_plan/presentation/widgets/option_widget.dart new file mode 100644 index 00000000..8224e871 --- /dev/null +++ b/lib/features/create_plan/presentation/widgets/option_widget.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class OptionWidget extends StatelessWidget { + const OptionWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Placeholder(); + } +} \ No newline at end of file diff --git a/lib/features/create_plan/presentation/widgets/options_count_button.dart b/lib/features/create_plan/presentation/widgets/options_count_button.dart new file mode 100644 index 00000000..35140514 --- /dev/null +++ b/lib/features/create_plan/presentation/widgets/options_count_button.dart @@ -0,0 +1,82 @@ +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart'; +import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class OptionsCountButton extends StatelessWidget { + const OptionsCountButton({super.key}); + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return BlocBuilder( + builder: (context, state) { + final selectedOption = state.selectedOptionIndex; + return Container( + margin: EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: appTheme.whiteA700, + borderRadius: BorderRadius.circular(100), + ), + padding: const EdgeInsets.all(4), + child: Row( + children: [ + Expanded( + child: _AnimatedOptionButton( + isSelected: selectedOption == null, + text: s.one_option, + onTap: () { + context.read().setSelectedOptionIndex(null); + }, + ), + ), + Expanded( + child: _AnimatedOptionButton( + isSelected: selectedOption != null, + text: s.three_options, + onTap: () { + context.read().setSelectedOptionIndex(0); + }, + ), + ), + ], + ), + ); + }, + ); + } +} + +class _AnimatedOptionButton extends StatelessWidget { + const _AnimatedOptionButton({required this.text, required this.isSelected, required this.onTap}); + final String text; + final bool isSelected; + final void Function() onTap; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + height: 40, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: isSelected ? appTheme.blueA400 : appTheme.whiteA700, + ), + child: Center( + child: AnimatedDefaultTextStyle( + duration: const Duration(milliseconds: 200), + style: TextStyles.textMSemibold.copyWith( + color: isSelected ? appTheme.whiteA700 : appTheme.black, + ), + child: Text(text), + ), + ), + ), + ); + } +} diff --git a/lib/features/create_plan/presentation/widgets/plan_duration_drop_down_widget.dart b/lib/features/create_plan/presentation/widgets/plan_duration_drop_down_widget.dart new file mode 100644 index 00000000..132c2ffd --- /dev/null +++ b/lib/features/create_plan/presentation/widgets/plan_duration_drop_down_widget.dart @@ -0,0 +1,65 @@ +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/utils/localization_utils.dart'; +import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart'; +import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart'; +import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class PlanDurationDropDownWidget extends StatefulWidget { + const PlanDurationDropDownWidget({super.key}); + + @override + State createState() => _PlanDurationDropDownWidgetState(); +} + +class _PlanDurationDropDownWidgetState extends State { + final List durationDropDownTitles = []; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final s = S.of(context); + if (durationDropDownTitles.isEmpty) { + durationValues.forEach((element) { + durationDropDownTitles.add('$element ${getDayForm(element, s)}'); + }); + } + } + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return BlocBuilder( + builder: (context, state) { + return CustomDropDownButton( + hint: Text( + s.choose_plan_duration, + style: TextStyles.textMMedium.copyWith(color: appTheme.black), + ), + onChanged: (value) { + if (value != null) { + context.read().setDuration(durationDropDownTitles.indexOf(value)); + } + }, + items: durationDropDownTitles + .mapIndexed( + (index, value) => DropdownMenuItem( + child: Text( + durationDropDownTitles[index], + style: TextStyles.textMMedium.copyWith(color: appTheme.black), + ), + value: durationDropDownTitles[index], + ), + ) + .toList(), + value: state.durationIndex == null ? null : durationDropDownTitles[state.durationIndex ?? 0], + ); + }, + ); + } +} diff --git a/lib/features/create_plan/presentation/widgets/plan_image_picker_widget.dart b/lib/features/create_plan/presentation/widgets/plan_image_picker_widget.dart new file mode 100644 index 00000000..37b9e9a6 --- /dev/null +++ b/lib/features/create_plan/presentation/widgets/plan_image_picker_widget.dart @@ -0,0 +1,145 @@ +import 'dart:io'; + +import 'package:dotted_border/dotted_border.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/create_plan/presentation/cubit/create_plan_cubit.dart'; +import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:image_picker/image_picker.dart'; + +class PlanImagePickerWidget extends StatefulWidget { + const PlanImagePickerWidget({super.key}); + + @override + State createState() => _PlanImagePickerWidgetState(); +} + +class _PlanImagePickerWidgetState extends State { + void _showImagePickerOptions(S s, CreatePlanCubit cubit) { + showCupertinoModalPopup( + context: context, + builder: (context) => CupertinoActionSheet( + actions: [ + CupertinoActionSheetAction( + onPressed: () async { + final picker = ImagePicker(); + final image = await picker.pickImage(source: ImageSource.camera); + cubit.setImage(image); + Navigator.of(context).pop(); + }, + child: Row( + children: [ + Icon(Icons.photo_camera), + SizedBox(width: 8), + Text(s.photo), + ], + ), + ), + CupertinoActionSheetAction( + onPressed: () async { + final picker = ImagePicker(); + final image = await picker.pickImage(source: ImageSource.gallery); + cubit.setImage(image); + Navigator.of(context).pop(); + }, + child: Row( + children: [ + Icon(Icons.photo), + SizedBox(width: 8), + Text(s.gallery), + ], + ), + ), + ], + cancelButton: CupertinoActionSheetAction( + onPressed: () => Navigator.of(context).pop(), + isDestructiveAction: true, + child: const Text('Отмена'), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return BlocBuilder( + builder: (context, state) { + return GestureDetector( + onTap: () { + _showImagePickerOptions(s, context.read()); + }, + child: Container( + height: 240, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: state.planData.image != null ? DecorationImage(fit: BoxFit.cover, image: FileImage(File(state.planData.image?.path ?? ''))) : null, + color: appTheme.gray50, + ), + width: MediaQuery.sizeOf(context).width, + padding: state.planData.image != null ? null : EdgeInsets.all(8), + child: state.planData.image != null + ? Padding( + padding: const EdgeInsets.only(top: 6, right: 6), + child: GestureDetector( + onTap: () { + context.read().setImage(null); + }, + child: Align( + alignment: Alignment.topRight, + child: Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: appTheme.dividerColor, + shape: BoxShape.circle, + ), + child: Icon( + Icons.close_rounded, + color: Colors.grey, + size: 16, + ), + ), + ), + ), + ) + : Stack( + children: [ + DottedBorder( + stackFit: StackFit.expand, + dashPattern: [14, 14], + color: appTheme.dottedBorder, + borderType: BorderType.RRect, + radius: Radius.circular(12), + child: SizedBox.expand(), + ), + Center( + child: Column( + children: [ + SizedBox( + height: 48, + ), + SvgPicture.asset('assets/icons/eatvio_logo.svg'), + SizedBox( + height: 24, + ), + Text( + s.add_cover, + style: TextStyles.textMMedium.copyWith(color: appTheme.gray), + ), + ], + ), + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/features/create_plan/presentation/widgets/plan_type_choice_widget.dart b/lib/features/create_plan/presentation/widgets/plan_type_choice_widget.dart new file mode 100644 index 00000000..44811953 --- /dev/null +++ b/lib/features/create_plan/presentation/widgets/plan_type_choice_widget.dart @@ -0,0 +1,101 @@ +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart'; +import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class PlanTypeChoiceWidget extends StatefulWidget { + PlanTypeChoiceWidget({super.key}); + + @override + State createState() => _PlanTypeChoiceWidgetState(); +} + +class _PlanTypeChoiceWidgetState extends State { + final List<(String, String)> choiceContent = []; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final s = S.of(context); + if (choiceContent.isEmpty) { + choiceContent.add( + ( + 'assets/icons/slim.png', + s.slim, + ), + ); + choiceContent.add( + ( + 'assets/icons/fitness.png', + s.fitness, + ), + ); + choiceContent.add( + ( + 'assets/icons/yoga-pose.png', + s.wellness, + ), + ); + choiceContent.add( + ( + 'assets/icons/diet.png', + s.eating_behavior, + ), + ); + } + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Row( + children: [ + ...List.generate(4, (index) { + return Expanded( + child: GestureDetector( + onTap: () { + final planTypes = [...state.planData.type]; + planTypes[index] = !planTypes[index]; + context.read().setPlanType(planTypes); + }, + child: Container( + margin: EdgeInsets.only(right: index == 3 ? 0 : 4), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: state.planData.type[index] ? appTheme.cardBackground : appTheme.whiteA700, + border: Border.all( + width: 1, + color: state.planData.type[index] ? appTheme.blueA400 : appTheme.lightGray00, + ), + ), + height: 104, + child: Column( + children: [ + SizedBox(height: 16), + Image.asset( + choiceContent[index].$1, + height: 30, + ), + SizedBox(height: 8), + Text( + choiceContent[index].$2, + overflow: TextOverflow.clip, + textAlign: TextAlign.center, + style: TextStyles.textFS10FW500.copyWith(color: appTheme.black), + ), + ], + ), + ), + ), + ); + }), + ], + ); + }, + ); + } +} diff --git a/lib/features/create_plan/presentation/widgets/select_exact_option_widget.dart b/lib/features/create_plan/presentation/widgets/select_exact_option_widget.dart new file mode 100644 index 00000000..12cf43ff --- /dev/null +++ b/lib/features/create_plan/presentation/widgets/select_exact_option_widget.dart @@ -0,0 +1,42 @@ +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:flutter/material.dart'; + +class SelectExactOptionWidget extends StatelessWidget { + const SelectExactOptionWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + width: 140, + height: 48, + decoration: BoxDecoration( + color: appTheme.black, + borderRadius: BorderRadius.circular(100), + ), + child: Row( + children: [ + _OptionButton(isSelected: true, text: '1'), + _OptionButton(isSelected: true, text: '2'), + _OptionButton(isSelected: true, text: '3'), + ], + ), + ); + } +} + +class _OptionButton extends StatelessWidget { + const _OptionButton({required this.isSelected, required this.text}); + final bool isSelected; + final String text; + @override + Widget build(BuildContext context) { + return Container( + + decoration: BoxDecoration( + color: appTheme.whiteA700, + shape: BoxShape.circle, + ), + child: Text(text), + ); + } +} diff --git a/lib/features/create_plan/repositories/create_plan_repository.dart b/lib/features/create_plan/repositories/create_plan_repository.dart new file mode 100644 index 00000000..447f1d3a --- /dev/null +++ b/lib/features/create_plan/repositories/create_plan_repository.dart @@ -0,0 +1,27 @@ +import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart'; + +class CreatePlanRepository { + final CreatePlanService createPlanService; + + CreatePlanRepository({required this.createPlanService}); + + Future createPlan({ + required Map model, + }) async { + await createPlanService..createPlan(model: model); + } + + Future createPacketOption({ + required String slug, + required Map model, + }) async { + await createPlanService.createPacketOption(slug: slug, model: model); + } + + Future createPacketOptions({ + required String slug, + required Map model, + }) async { + await createPlanService.createPacketOptions(slug: slug, model: model); + } +} diff --git a/lib/features/create_plan/services/create_plan_service.dart b/lib/features/create_plan/services/create_plan_service.dart new file mode 100644 index 00000000..f5d1891e --- /dev/null +++ b/lib/features/create_plan/services/create_plan_service.dart @@ -0,0 +1,27 @@ +import 'package:dio/dio.dart'; +import 'package:retrofit/retrofit.dart'; + +part 'create_plan_service.g.dart'; + +@RestApi() +abstract class CreatePlanService { + factory CreatePlanService(Dio dio, {String baseUrl}) = _CreatePlanService; + + @POST('/api/plans/create') + @MultiPart() + Future createPlan({ + @Body() required Map model, + }); + + @POST('/api/plans/create/{slug}/packet_option') + Future createPacketOption({ + @Path() required String slug, + @Body() required Map model, + }); + + @POST('/api/plans/create/{slug}/packet_options') + Future createPacketOptions({ + @Path() required String slug, + @Body() required Map model, + }); +} diff --git a/lib/features/create_plan/services/create_plan_service.g.dart b/lib/features/create_plan/services/create_plan_service.g.dart new file mode 100644 index 00000000..af44f549 --- /dev/null +++ b/lib/features/create_plan/services/create_plan_service.g.dart @@ -0,0 +1,138 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'create_plan_service.dart'; + +// ************************************************************************** +// RetrofitGenerator +// ************************************************************************** + +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations + +class _CreatePlanService implements CreatePlanService { + _CreatePlanService( + this._dio, { + this.baseUrl, + this.errorLogger, + }); + + final Dio _dio; + + String? baseUrl; + + final ParseErrorLogger? errorLogger; + + @override + Future createPlan({required Map model}) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = {}; + _data.addAll(model); + final _options = _setStreamType(Options( + method: 'POST', + headers: _headers, + extra: _extra, + contentType: 'multipart/form-data', + ) + .compose( + _dio.options, + '/api/plans/create', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + await _dio.fetch(_options); + } + + @override + Future createPacketOption({ + required String slug, + required Map model, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = {}; + _data.addAll(model); + final _options = _setStreamType(Options( + method: 'POST', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/plans/create/${slug}/packet_option', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + await _dio.fetch(_options); + } + + @override + Future createPacketOptions({ + required String slug, + required Map model, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = {}; + _data.addAll(model); + final _options = _setStreamType(Options( + method: 'POST', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/plans/create/${slug}/packet_options', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + await _dio.fetch(_options); + } + + RequestOptions _setStreamType(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(); + } +} diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart index 59158554..c7ca1008 100644 --- a/lib/features/diary/presentation/bloc/diary_cubit.dart +++ b/lib/features/diary/presentation/bloc/diary_cubit.dart @@ -151,7 +151,7 @@ class DiaryCubit extends Cubit { emit((state as DiaryStateLoaded).copyWith(currentSelectedSatietyRating: satiety)); } - sendProductToModeration({ + Future sendProductToModeration({ required String name, required double energy, required double proteins, diff --git a/lib/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart b/lib/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart index 3461d687..f8f6c493 100644 --- a/lib/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart +++ b/lib/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart @@ -1,6 +1,7 @@ import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; 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/glossary/models/glossary_model.dart'; import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; @@ -10,7 +11,8 @@ class GlossaryModerationRequestModalWidget extends StatefulWidget { const GlossaryModerationRequestModalWidget({ super.key, - required this.productName, required this.diaryCubit, + required this.productName, + required this.diaryCubit, }); @override @@ -26,7 +28,7 @@ class _GlossaryModerationRequestModalWidgetState extends State sendProductToModeration({ + Future sendProductToModeration({ required String name, required double energy, required double proteins, diff --git a/lib/features/diary/services/diary_service.dart b/lib/features/diary/services/diary_service.dart index 40598e04..7b5533e3 100644 --- a/lib/features/diary/services/diary_service.dart +++ b/lib/features/diary/services/diary_service.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/nutrition_model.dart'; +import 'package:eatvio_app/features/glossary/models/glossary_model.dart'; import 'package:retrofit/retrofit.dart'; part 'diary_service.g.dart'; @@ -78,7 +79,7 @@ abstract class DiaryService { }); @POST('/api/diary/product_nutritional_values/sendToModeration') - Future sendProductToModeration({ + Future sendProductToModeration({ @Body() required Map data, }); } diff --git a/lib/features/diary/services/diary_service.g.dart b/lib/features/diary/services/diary_service.g.dart index 8c88dd36..895e3fa0 100644 --- a/lib/features/diary/services/diary_service.g.dart +++ b/lib/features/diary/services/diary_service.g.dart @@ -350,14 +350,14 @@ class _DiaryService implements DiaryService { } @override - Future sendProductToModeration( + Future sendProductToModeration( {required Map data}) async { final _extra = {}; final queryParameters = {}; final _headers = {}; final _data = {}; _data.addAll(data); - final _options = _setStreamType(Options( + final _options = _setStreamType(Options( method: 'POST', headers: _headers, extra: _extra, @@ -373,7 +373,15 @@ class _DiaryService implements DiaryService { _dio.options.baseUrl, baseUrl, ))); - await _dio.fetch(_options); + final _result = await _dio.fetch>(_options); + late GlossaryModerationModel _value; + try { + _value = GlossaryModerationModel.fromJson(_result.data!); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; } RequestOptions _setStreamType(RequestOptions requestOptions) { diff --git a/lib/features/diets/presentation/pages/about_diet_screen.dart b/lib/features/diets/presentation/pages/about_diet_screen.dart index 0e1c2299..26c723c9 100644 --- a/lib/features/diets/presentation/pages/about_diet_screen.dart +++ b/lib/features/diets/presentation/pages/about_diet_screen.dart @@ -11,7 +11,7 @@ import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/diets/presentation/bloc/about_diet_cubit.dart'; import 'package:eatvio_app/features/diets/presentation/bloc/about_diet_state.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart'; -import 'package:eatvio_app/features/diets/presentation/widgets/cpfc_container.dart'; +import 'package:eatvio_app/core/widgets/cpfc_container.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/expandable_text.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart'; import 'package:eatvio_app/core/widgets/main_background_bouble.dart'; @@ -132,7 +132,12 @@ class _AboutDietScreenState extends State { ), backgroundColor: appTheme.greenA700, onPressed: () { - showModalBottomSheet(context: context, builder: (context) => StartDietBottomSheet(aboutDietCubit: context.read(),)); + showModalBottomSheet( + context: context, + builder: (context) => StartDietBottomSheet( + aboutDietCubit: context.read(), + ), + ); }, label: Text( s.startDiet, @@ -320,6 +325,9 @@ class _AboutDietScreenState extends State { children: [ CachedNetworkImage( imageUrl: baseUrl + '/storage/' + widget.diet.image, + height: 300, + width: MediaQuery.sizeOf(context).width, + fit: BoxFit.fill, ), Positioned( bottom: 16, @@ -440,8 +448,7 @@ class _AboutDietScreenState extends State { ), ), SizedBox(height: 16), - if (state is CommentsStateLoading) - const Loader(), + if (state is CommentsStateLoading) const Loader(), if (state is CommentsStateLoaded) CommentsWidget( id: widget.diet.id.toString(), @@ -545,8 +552,8 @@ class _AboutDietScreenState extends State { ), CpfcContainer( calories: widget.diet.dailyNutrition[dayNumber].energy, - protein: widget.diet.dailyNutrition[dayNumber].proteins, - fat: widget.diet.dailyNutrition[dayNumber].fats, + proteins: widget.diet.dailyNutrition[dayNumber].proteins, + fats: widget.diet.dailyNutrition[dayNumber].fats, carbohydrates: widget.diet.dailyNutrition[dayNumber].carbohydrates, ), ], diff --git a/lib/features/diets/presentation/widgets/more_options.dart b/lib/features/diets/presentation/widgets/more_options.dart index 283e752e..b087f9f0 100644 --- a/lib/features/diets/presentation/widgets/more_options.dart +++ b/lib/features/diets/presentation/widgets/more_options.dart @@ -12,8 +12,9 @@ import 'package:go_router/go_router.dart'; import 'package:share_plus/share_plus.dart'; class MoreOptions extends StatelessWidget { - const MoreOptions({super.key, required this.id}); + const MoreOptions({super.key, required this.id, this.disableActions}); final int id; + final bool? disableActions; @override Widget build(BuildContext context) { final s = S.of(context); @@ -25,7 +26,7 @@ class MoreOptions extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ GestureDetector( - onTap: () { + onTap:disableActions == true ? null : () { Clipboard.setData(ClipboardData(text: baseUrl + '/ru/diet/' + id.toString())); showInfoToast(s.link_was_copied); }, @@ -34,7 +35,6 @@ class MoreOptions extends StatelessWidget { Text( s.copy_link, style: TextStyles.textMMedium.copyWith( - color: appTheme.black, ), ), @@ -48,7 +48,7 @@ class MoreOptions extends StatelessWidget { child: CustomDivider(), ), GestureDetector( - onTap: () => Share.shareUri( + onTap: disableActions == true ? null : () => Share.shareUri( Uri.parse( baseUrl + '/ru/diet/' + id.toString(), ), @@ -58,7 +58,6 @@ class MoreOptions extends StatelessWidget { Text( s.share, style: TextStyles.textMMedium.copyWith( - color: appTheme.black, ), ), @@ -72,7 +71,7 @@ class MoreOptions extends StatelessWidget { child: CustomDivider(), ), GestureDetector( - onTap: () { + onTap: disableActions == true ? null : () { context.push('complainingScreen'); }, child: Row( diff --git a/lib/features/feed/models/diets_pagination_model.dart b/lib/features/feed/models/diets_pagination_model.dart index 7c59ec84..c84d3b0c 100644 --- a/lib/features/feed/models/diets_pagination_model.dart +++ b/lib/features/feed/models/diets_pagination_model.dart @@ -55,9 +55,11 @@ class Author with _$Author { class DailySchedule with _$DailySchedule { const factory DailySchedule({ @JsonKey(name: "name") required String name, - @JsonKey(name: "uniq") required String uniq, + @JsonKey(name: "uniq", includeToJson: false) required String uniq, @JsonKey(name: "portion") required Portion portion, @JsonKey(name: "quantity") required int quantity, + @JsonKey(includeToJson: false, includeFromJson: false) DailyNutrition? dailyNutrition, + @JsonKey(name: 'product_nutritional_value_id', includeFromJson: false, includeIfNull: false) String? productNutritionalValueId, }) = _DailySchedule; factory DailySchedule.fromJson(Map json) => _$DailyScheduleFromJson(json); diff --git a/lib/features/feed/models/diets_pagination_model.freezed.dart b/lib/features/feed/models/diets_pagination_model.freezed.dart index a209d2cc..663ce858 100644 --- a/lib/features/feed/models/diets_pagination_model.freezed.dart +++ b/lib/features/feed/models/diets_pagination_model.freezed.dart @@ -1034,12 +1034,19 @@ DailySchedule _$DailyScheduleFromJson(Map json) { mixin _$DailySchedule { @JsonKey(name: "name") String get name => throw _privateConstructorUsedError; - @JsonKey(name: "uniq") + @JsonKey(name: "uniq", includeToJson: false) String get uniq => throw _privateConstructorUsedError; @JsonKey(name: "portion") Portion get portion => throw _privateConstructorUsedError; @JsonKey(name: "quantity") int get quantity => throw _privateConstructorUsedError; + @JsonKey(includeToJson: false, includeFromJson: false) + DailyNutrition? get dailyNutrition => throw _privateConstructorUsedError; + @JsonKey( + name: 'product_nutritional_value_id', + includeFromJson: false, + includeIfNull: false) + String? get productNutritionalValueId => throw _privateConstructorUsedError; /// Serializes this DailySchedule to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -1059,9 +1066,18 @@ abstract class $DailyScheduleCopyWith<$Res> { @useResult $Res call( {@JsonKey(name: "name") String name, - @JsonKey(name: "uniq") String uniq, + @JsonKey(name: "uniq", includeToJson: false) String uniq, @JsonKey(name: "portion") Portion portion, - @JsonKey(name: "quantity") int quantity}); + @JsonKey(name: "quantity") int quantity, + @JsonKey(includeToJson: false, includeFromJson: false) + DailyNutrition? dailyNutrition, + @JsonKey( + name: 'product_nutritional_value_id', + includeFromJson: false, + includeIfNull: false) + String? productNutritionalValueId}); + + $DailyNutritionCopyWith<$Res>? get dailyNutrition; } /// @nodoc @@ -1083,6 +1099,8 @@ class _$DailyScheduleCopyWithImpl<$Res, $Val extends DailySchedule> Object? uniq = null, Object? portion = null, Object? quantity = null, + Object? dailyNutrition = freezed, + Object? productNutritionalValueId = freezed, }) { return _then(_value.copyWith( name: null == name @@ -1101,8 +1119,30 @@ class _$DailyScheduleCopyWithImpl<$Res, $Val extends DailySchedule> ? _value.quantity : quantity // ignore: cast_nullable_to_non_nullable as int, + dailyNutrition: freezed == dailyNutrition + ? _value.dailyNutrition + : dailyNutrition // ignore: cast_nullable_to_non_nullable + as DailyNutrition?, + productNutritionalValueId: freezed == productNutritionalValueId + ? _value.productNutritionalValueId + : productNutritionalValueId // ignore: cast_nullable_to_non_nullable + as String?, ) as $Val); } + + /// Create a copy of DailySchedule + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $DailyNutritionCopyWith<$Res>? get dailyNutrition { + if (_value.dailyNutrition == null) { + return null; + } + + return $DailyNutritionCopyWith<$Res>(_value.dailyNutrition!, (value) { + return _then(_value.copyWith(dailyNutrition: value) as $Val); + }); + } } /// @nodoc @@ -1115,9 +1155,19 @@ abstract class _$$DailyScheduleImplCopyWith<$Res> @useResult $Res call( {@JsonKey(name: "name") String name, - @JsonKey(name: "uniq") String uniq, + @JsonKey(name: "uniq", includeToJson: false) String uniq, @JsonKey(name: "portion") Portion portion, - @JsonKey(name: "quantity") int quantity}); + @JsonKey(name: "quantity") int quantity, + @JsonKey(includeToJson: false, includeFromJson: false) + DailyNutrition? dailyNutrition, + @JsonKey( + name: 'product_nutritional_value_id', + includeFromJson: false, + includeIfNull: false) + String? productNutritionalValueId}); + + @override + $DailyNutritionCopyWith<$Res>? get dailyNutrition; } /// @nodoc @@ -1137,6 +1187,8 @@ class __$$DailyScheduleImplCopyWithImpl<$Res> Object? uniq = null, Object? portion = null, Object? quantity = null, + Object? dailyNutrition = freezed, + Object? productNutritionalValueId = freezed, }) { return _then(_$DailyScheduleImpl( name: null == name @@ -1155,6 +1207,14 @@ class __$$DailyScheduleImplCopyWithImpl<$Res> ? _value.quantity : quantity // ignore: cast_nullable_to_non_nullable as int, + dailyNutrition: freezed == dailyNutrition + ? _value.dailyNutrition + : dailyNutrition // ignore: cast_nullable_to_non_nullable + as DailyNutrition?, + productNutritionalValueId: freezed == productNutritionalValueId + ? _value.productNutritionalValueId + : productNutritionalValueId // ignore: cast_nullable_to_non_nullable + as String?, )); } } @@ -1164,9 +1224,16 @@ class __$$DailyScheduleImplCopyWithImpl<$Res> class _$DailyScheduleImpl implements _DailySchedule { const _$DailyScheduleImpl( {@JsonKey(name: "name") required this.name, - @JsonKey(name: "uniq") required this.uniq, + @JsonKey(name: "uniq", includeToJson: false) required this.uniq, @JsonKey(name: "portion") required this.portion, - @JsonKey(name: "quantity") required this.quantity}); + @JsonKey(name: "quantity") required this.quantity, + @JsonKey(includeToJson: false, includeFromJson: false) + this.dailyNutrition, + @JsonKey( + name: 'product_nutritional_value_id', + includeFromJson: false, + includeIfNull: false) + this.productNutritionalValueId}); factory _$DailyScheduleImpl.fromJson(Map json) => _$$DailyScheduleImplFromJson(json); @@ -1175,7 +1242,7 @@ class _$DailyScheduleImpl implements _DailySchedule { @JsonKey(name: "name") final String name; @override - @JsonKey(name: "uniq") + @JsonKey(name: "uniq", includeToJson: false) final String uniq; @override @JsonKey(name: "portion") @@ -1183,10 +1250,19 @@ class _$DailyScheduleImpl implements _DailySchedule { @override @JsonKey(name: "quantity") final int quantity; + @override + @JsonKey(includeToJson: false, includeFromJson: false) + final DailyNutrition? dailyNutrition; + @override + @JsonKey( + name: 'product_nutritional_value_id', + includeFromJson: false, + includeIfNull: false) + final String? productNutritionalValueId; @override String toString() { - return 'DailySchedule(name: $name, uniq: $uniq, portion: $portion, quantity: $quantity)'; + return 'DailySchedule(name: $name, uniq: $uniq, portion: $portion, quantity: $quantity, dailyNutrition: $dailyNutrition, productNutritionalValueId: $productNutritionalValueId)'; } @override @@ -1198,12 +1274,18 @@ class _$DailyScheduleImpl implements _DailySchedule { (identical(other.uniq, uniq) || other.uniq == uniq) && (identical(other.portion, portion) || other.portion == portion) && (identical(other.quantity, quantity) || - other.quantity == quantity)); + other.quantity == quantity) && + (identical(other.dailyNutrition, dailyNutrition) || + other.dailyNutrition == dailyNutrition) && + (identical(other.productNutritionalValueId, + productNutritionalValueId) || + other.productNutritionalValueId == productNutritionalValueId)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, name, uniq, portion, quantity); + int get hashCode => Object.hash(runtimeType, name, uniq, portion, quantity, + dailyNutrition, productNutritionalValueId); /// Create a copy of DailySchedule /// with the given fields replaced by the non-null parameter values. @@ -1223,11 +1305,17 @@ class _$DailyScheduleImpl implements _DailySchedule { abstract class _DailySchedule implements DailySchedule { const factory _DailySchedule( - {@JsonKey(name: "name") required final String name, - @JsonKey(name: "uniq") required final String uniq, - @JsonKey(name: "portion") required final Portion portion, - @JsonKey(name: "quantity") required final int quantity}) = - _$DailyScheduleImpl; + {@JsonKey(name: "name") required final String name, + @JsonKey(name: "uniq", includeToJson: false) required final String uniq, + @JsonKey(name: "portion") required final Portion portion, + @JsonKey(name: "quantity") required final int quantity, + @JsonKey(includeToJson: false, includeFromJson: false) + final DailyNutrition? dailyNutrition, + @JsonKey( + name: 'product_nutritional_value_id', + includeFromJson: false, + includeIfNull: false) + final String? productNutritionalValueId}) = _$DailyScheduleImpl; factory _DailySchedule.fromJson(Map json) = _$DailyScheduleImpl.fromJson; @@ -1236,7 +1324,7 @@ abstract class _DailySchedule implements DailySchedule { @JsonKey(name: "name") String get name; @override - @JsonKey(name: "uniq") + @JsonKey(name: "uniq", includeToJson: false) String get uniq; @override @JsonKey(name: "portion") @@ -1244,6 +1332,15 @@ abstract class _DailySchedule implements DailySchedule { @override @JsonKey(name: "quantity") int get quantity; + @override + @JsonKey(includeToJson: false, includeFromJson: false) + DailyNutrition? get dailyNutrition; + @override + @JsonKey( + name: 'product_nutritional_value_id', + includeFromJson: false, + includeIfNull: false) + String? get productNutritionalValueId; /// Create a copy of DailySchedule /// with the given fields replaced by the non-null parameter values. diff --git a/lib/features/feed/models/diets_pagination_model.g.dart b/lib/features/feed/models/diets_pagination_model.g.dart index 7d3c3de0..f5875139 100644 --- a/lib/features/feed/models/diets_pagination_model.g.dart +++ b/lib/features/feed/models/diets_pagination_model.g.dart @@ -95,7 +95,6 @@ _$DailyScheduleImpl _$$DailyScheduleImplFromJson(Map json) => Map _$$DailyScheduleImplToJson(_$DailyScheduleImpl instance) => { 'name': instance.name, - 'uniq': instance.uniq, 'portion': _$PortionEnumMap[instance.portion]!, 'quantity': instance.quantity, }; diff --git a/lib/features/feed/presentation/pages/feed_screen.dart b/lib/features/feed/presentation/pages/feed_screen.dart index ce41ea23..28312741 100644 --- a/lib/features/feed/presentation/pages/feed_screen.dart +++ b/lib/features/feed/presentation/pages/feed_screen.dart @@ -1,6 +1,7 @@ import 'package:easy_debounce/easy_debounce.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'; import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart'; @@ -439,6 +440,9 @@ class _FeedScreenState extends State { case 2: GoRouter.of(context).pushNamed(CreateDietScreen.name); break; + case 3: + GoRouter.of(context).pushNamed(CreatePlanScreen.name); + break; default: GoRouter.of(context).pushNamed('blogPostCreateScreen'); } diff --git a/lib/features/glossary/models/glossary_model.dart b/lib/features/glossary/models/glossary_model.dart index 8e437d95..c1daa13a 100644 --- a/lib/features/glossary/models/glossary_model.dart +++ b/lib/features/glossary/models/glossary_model.dart @@ -12,12 +12,29 @@ class GlossaryPaginationModel with _$GlossaryPaginationModel { factory GlossaryPaginationModel.fromJson(Map json) => _$GlossaryPaginationModelFromJson(json); } +@freezed +class GlossaryModerationModel with _$GlossaryModerationModel { + const factory GlossaryModerationModel({ + @JsonKey(name: 'product_nutritional_value') required GlossaryModel glossary, + }) = _GlossaryModerationModel; + + factory GlossaryModerationModel.fromJson(Map json) => _$GlossaryModerationModelFromJson(json); +} + @freezed class GlossaryModel with _$GlossaryModel { const factory GlossaryModel({ required int id, required String name, @JsonKey(name: 'portion_mass_gr') required int grInPortion, + @JsonKey(name: 'energy_on_100gr') required double energyOn100Gr, + @JsonKey(name: 'proteins_on_100gr') required double proteinsOn100Gr, + @JsonKey(name: 'fats_on_100gr') required double fatsOn100Gr, + @JsonKey(name: 'carbohydrates_on_100gr') required double carbohydratesOn100Gr, + @JsonKey(name: 'energy_on_portion') required double energyOnPortion, + @JsonKey(name: 'proteins_on_portion') required double proteinsOnPortion, + @JsonKey(name: 'fats_on_portion') required double fatsOnPortion, + @JsonKey(name: 'carbohydrates_on_portion') required double carbohydratesOnPortion, }) = _GlossaryModel; factory GlossaryModel.fromJson(Map json) => _$GlossaryModelFromJson(json); diff --git a/lib/features/glossary/models/glossary_model.freezed.dart b/lib/features/glossary/models/glossary_model.freezed.dart index d5e1556f..ababa94f 100644 --- a/lib/features/glossary/models/glossary_model.freezed.dart +++ b/lib/features/glossary/models/glossary_model.freezed.dart @@ -185,6 +185,185 @@ abstract class _GlossaryPaginationModel implements GlossaryPaginationModel { get copyWith => throw _privateConstructorUsedError; } +GlossaryModerationModel _$GlossaryModerationModelFromJson( + Map json) { + return _GlossaryModerationModel.fromJson(json); +} + +/// @nodoc +mixin _$GlossaryModerationModel { + @JsonKey(name: 'product_nutritional_value') + GlossaryModel get glossary => throw _privateConstructorUsedError; + + /// Serializes this GlossaryModerationModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of GlossaryModerationModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $GlossaryModerationModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $GlossaryModerationModelCopyWith<$Res> { + factory $GlossaryModerationModelCopyWith(GlossaryModerationModel value, + $Res Function(GlossaryModerationModel) then) = + _$GlossaryModerationModelCopyWithImpl<$Res, GlossaryModerationModel>; + @useResult + $Res call( + {@JsonKey(name: 'product_nutritional_value') GlossaryModel glossary}); + + $GlossaryModelCopyWith<$Res> get glossary; +} + +/// @nodoc +class _$GlossaryModerationModelCopyWithImpl<$Res, + $Val extends GlossaryModerationModel> + implements $GlossaryModerationModelCopyWith<$Res> { + _$GlossaryModerationModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of GlossaryModerationModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? glossary = null, + }) { + return _then(_value.copyWith( + glossary: null == glossary + ? _value.glossary + : glossary // ignore: cast_nullable_to_non_nullable + as GlossaryModel, + ) as $Val); + } + + /// Create a copy of GlossaryModerationModel + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $GlossaryModelCopyWith<$Res> get glossary { + return $GlossaryModelCopyWith<$Res>(_value.glossary, (value) { + return _then(_value.copyWith(glossary: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$GlossaryModerationModelImplCopyWith<$Res> + implements $GlossaryModerationModelCopyWith<$Res> { + factory _$$GlossaryModerationModelImplCopyWith( + _$GlossaryModerationModelImpl value, + $Res Function(_$GlossaryModerationModelImpl) then) = + __$$GlossaryModerationModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {@JsonKey(name: 'product_nutritional_value') GlossaryModel glossary}); + + @override + $GlossaryModelCopyWith<$Res> get glossary; +} + +/// @nodoc +class __$$GlossaryModerationModelImplCopyWithImpl<$Res> + extends _$GlossaryModerationModelCopyWithImpl<$Res, + _$GlossaryModerationModelImpl> + implements _$$GlossaryModerationModelImplCopyWith<$Res> { + __$$GlossaryModerationModelImplCopyWithImpl( + _$GlossaryModerationModelImpl _value, + $Res Function(_$GlossaryModerationModelImpl) _then) + : super(_value, _then); + + /// Create a copy of GlossaryModerationModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? glossary = null, + }) { + return _then(_$GlossaryModerationModelImpl( + glossary: null == glossary + ? _value.glossary + : glossary // ignore: cast_nullable_to_non_nullable + as GlossaryModel, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$GlossaryModerationModelImpl implements _GlossaryModerationModel { + const _$GlossaryModerationModelImpl( + {@JsonKey(name: 'product_nutritional_value') required this.glossary}); + + factory _$GlossaryModerationModelImpl.fromJson(Map json) => + _$$GlossaryModerationModelImplFromJson(json); + + @override + @JsonKey(name: 'product_nutritional_value') + final GlossaryModel glossary; + + @override + String toString() { + return 'GlossaryModerationModel(glossary: $glossary)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$GlossaryModerationModelImpl && + (identical(other.glossary, glossary) || + other.glossary == glossary)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, glossary); + + /// Create a copy of GlossaryModerationModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$GlossaryModerationModelImplCopyWith<_$GlossaryModerationModelImpl> + get copyWith => __$$GlossaryModerationModelImplCopyWithImpl< + _$GlossaryModerationModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$GlossaryModerationModelImplToJson( + this, + ); + } +} + +abstract class _GlossaryModerationModel implements GlossaryModerationModel { + const factory _GlossaryModerationModel( + {@JsonKey(name: 'product_nutritional_value') + required final GlossaryModel glossary}) = _$GlossaryModerationModelImpl; + + factory _GlossaryModerationModel.fromJson(Map json) = + _$GlossaryModerationModelImpl.fromJson; + + @override + @JsonKey(name: 'product_nutritional_value') + GlossaryModel get glossary; + + /// Create a copy of GlossaryModerationModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GlossaryModerationModelImplCopyWith<_$GlossaryModerationModelImpl> + get copyWith => throw _privateConstructorUsedError; +} + GlossaryModel _$GlossaryModelFromJson(Map json) { return _GlossaryModel.fromJson(json); } @@ -195,6 +374,22 @@ mixin _$GlossaryModel { String get name => throw _privateConstructorUsedError; @JsonKey(name: 'portion_mass_gr') int get grInPortion => throw _privateConstructorUsedError; + @JsonKey(name: 'energy_on_100gr') + double get energyOn100Gr => throw _privateConstructorUsedError; + @JsonKey(name: 'proteins_on_100gr') + double get proteinsOn100Gr => throw _privateConstructorUsedError; + @JsonKey(name: 'fats_on_100gr') + double get fatsOn100Gr => throw _privateConstructorUsedError; + @JsonKey(name: 'carbohydrates_on_100gr') + double get carbohydratesOn100Gr => throw _privateConstructorUsedError; + @JsonKey(name: 'energy_on_portion') + double get energyOnPortion => throw _privateConstructorUsedError; + @JsonKey(name: 'proteins_on_portion') + double get proteinsOnPortion => throw _privateConstructorUsedError; + @JsonKey(name: 'fats_on_portion') + double get fatsOnPortion => throw _privateConstructorUsedError; + @JsonKey(name: 'carbohydrates_on_portion') + double get carbohydratesOnPortion => throw _privateConstructorUsedError; /// Serializes this GlossaryModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -213,7 +408,18 @@ abstract class $GlossaryModelCopyWith<$Res> { _$GlossaryModelCopyWithImpl<$Res, GlossaryModel>; @useResult $Res call( - {int id, String name, @JsonKey(name: 'portion_mass_gr') int grInPortion}); + {int id, + String name, + @JsonKey(name: 'portion_mass_gr') int grInPortion, + @JsonKey(name: 'energy_on_100gr') double energyOn100Gr, + @JsonKey(name: 'proteins_on_100gr') double proteinsOn100Gr, + @JsonKey(name: 'fats_on_100gr') double fatsOn100Gr, + @JsonKey(name: 'carbohydrates_on_100gr') double carbohydratesOn100Gr, + @JsonKey(name: 'energy_on_portion') double energyOnPortion, + @JsonKey(name: 'proteins_on_portion') double proteinsOnPortion, + @JsonKey(name: 'fats_on_portion') double fatsOnPortion, + @JsonKey(name: 'carbohydrates_on_portion') + double carbohydratesOnPortion}); } /// @nodoc @@ -234,6 +440,14 @@ class _$GlossaryModelCopyWithImpl<$Res, $Val extends GlossaryModel> Object? id = null, Object? name = null, Object? grInPortion = null, + Object? energyOn100Gr = null, + Object? proteinsOn100Gr = null, + Object? fatsOn100Gr = null, + Object? carbohydratesOn100Gr = null, + Object? energyOnPortion = null, + Object? proteinsOnPortion = null, + Object? fatsOnPortion = null, + Object? carbohydratesOnPortion = null, }) { return _then(_value.copyWith( id: null == id @@ -248,6 +462,38 @@ class _$GlossaryModelCopyWithImpl<$Res, $Val extends GlossaryModel> ? _value.grInPortion : grInPortion // ignore: cast_nullable_to_non_nullable as int, + energyOn100Gr: null == energyOn100Gr + ? _value.energyOn100Gr + : energyOn100Gr // ignore: cast_nullable_to_non_nullable + as double, + proteinsOn100Gr: null == proteinsOn100Gr + ? _value.proteinsOn100Gr + : proteinsOn100Gr // ignore: cast_nullable_to_non_nullable + as double, + fatsOn100Gr: null == fatsOn100Gr + ? _value.fatsOn100Gr + : fatsOn100Gr // ignore: cast_nullable_to_non_nullable + as double, + carbohydratesOn100Gr: null == carbohydratesOn100Gr + ? _value.carbohydratesOn100Gr + : carbohydratesOn100Gr // ignore: cast_nullable_to_non_nullable + as double, + energyOnPortion: null == energyOnPortion + ? _value.energyOnPortion + : energyOnPortion // ignore: cast_nullable_to_non_nullable + as double, + proteinsOnPortion: null == proteinsOnPortion + ? _value.proteinsOnPortion + : proteinsOnPortion // ignore: cast_nullable_to_non_nullable + as double, + fatsOnPortion: null == fatsOnPortion + ? _value.fatsOnPortion + : fatsOnPortion // ignore: cast_nullable_to_non_nullable + as double, + carbohydratesOnPortion: null == carbohydratesOnPortion + ? _value.carbohydratesOnPortion + : carbohydratesOnPortion // ignore: cast_nullable_to_non_nullable + as double, ) as $Val); } } @@ -261,7 +507,18 @@ abstract class _$$GlossaryModelImplCopyWith<$Res> @override @useResult $Res call( - {int id, String name, @JsonKey(name: 'portion_mass_gr') int grInPortion}); + {int id, + String name, + @JsonKey(name: 'portion_mass_gr') int grInPortion, + @JsonKey(name: 'energy_on_100gr') double energyOn100Gr, + @JsonKey(name: 'proteins_on_100gr') double proteinsOn100Gr, + @JsonKey(name: 'fats_on_100gr') double fatsOn100Gr, + @JsonKey(name: 'carbohydrates_on_100gr') double carbohydratesOn100Gr, + @JsonKey(name: 'energy_on_portion') double energyOnPortion, + @JsonKey(name: 'proteins_on_portion') double proteinsOnPortion, + @JsonKey(name: 'fats_on_portion') double fatsOnPortion, + @JsonKey(name: 'carbohydrates_on_portion') + double carbohydratesOnPortion}); } /// @nodoc @@ -280,6 +537,14 @@ class __$$GlossaryModelImplCopyWithImpl<$Res> Object? id = null, Object? name = null, Object? grInPortion = null, + Object? energyOn100Gr = null, + Object? proteinsOn100Gr = null, + Object? fatsOn100Gr = null, + Object? carbohydratesOn100Gr = null, + Object? energyOnPortion = null, + Object? proteinsOnPortion = null, + Object? fatsOnPortion = null, + Object? carbohydratesOnPortion = null, }) { return _then(_$GlossaryModelImpl( id: null == id @@ -294,6 +559,38 @@ class __$$GlossaryModelImplCopyWithImpl<$Res> ? _value.grInPortion : grInPortion // ignore: cast_nullable_to_non_nullable as int, + energyOn100Gr: null == energyOn100Gr + ? _value.energyOn100Gr + : energyOn100Gr // ignore: cast_nullable_to_non_nullable + as double, + proteinsOn100Gr: null == proteinsOn100Gr + ? _value.proteinsOn100Gr + : proteinsOn100Gr // ignore: cast_nullable_to_non_nullable + as double, + fatsOn100Gr: null == fatsOn100Gr + ? _value.fatsOn100Gr + : fatsOn100Gr // ignore: cast_nullable_to_non_nullable + as double, + carbohydratesOn100Gr: null == carbohydratesOn100Gr + ? _value.carbohydratesOn100Gr + : carbohydratesOn100Gr // ignore: cast_nullable_to_non_nullable + as double, + energyOnPortion: null == energyOnPortion + ? _value.energyOnPortion + : energyOnPortion // ignore: cast_nullable_to_non_nullable + as double, + proteinsOnPortion: null == proteinsOnPortion + ? _value.proteinsOnPortion + : proteinsOnPortion // ignore: cast_nullable_to_non_nullable + as double, + fatsOnPortion: null == fatsOnPortion + ? _value.fatsOnPortion + : fatsOnPortion // ignore: cast_nullable_to_non_nullable + as double, + carbohydratesOnPortion: null == carbohydratesOnPortion + ? _value.carbohydratesOnPortion + : carbohydratesOnPortion // ignore: cast_nullable_to_non_nullable + as double, )); } } @@ -304,7 +601,17 @@ class _$GlossaryModelImpl implements _GlossaryModel { const _$GlossaryModelImpl( {required this.id, required this.name, - @JsonKey(name: 'portion_mass_gr') required this.grInPortion}); + @JsonKey(name: 'portion_mass_gr') required this.grInPortion, + @JsonKey(name: 'energy_on_100gr') required this.energyOn100Gr, + @JsonKey(name: 'proteins_on_100gr') required this.proteinsOn100Gr, + @JsonKey(name: 'fats_on_100gr') required this.fatsOn100Gr, + @JsonKey(name: 'carbohydrates_on_100gr') + required this.carbohydratesOn100Gr, + @JsonKey(name: 'energy_on_portion') required this.energyOnPortion, + @JsonKey(name: 'proteins_on_portion') required this.proteinsOnPortion, + @JsonKey(name: 'fats_on_portion') required this.fatsOnPortion, + @JsonKey(name: 'carbohydrates_on_portion') + required this.carbohydratesOnPortion}); factory _$GlossaryModelImpl.fromJson(Map json) => _$$GlossaryModelImplFromJson(json); @@ -316,10 +623,34 @@ class _$GlossaryModelImpl implements _GlossaryModel { @override @JsonKey(name: 'portion_mass_gr') final int grInPortion; + @override + @JsonKey(name: 'energy_on_100gr') + final double energyOn100Gr; + @override + @JsonKey(name: 'proteins_on_100gr') + final double proteinsOn100Gr; + @override + @JsonKey(name: 'fats_on_100gr') + final double fatsOn100Gr; + @override + @JsonKey(name: 'carbohydrates_on_100gr') + final double carbohydratesOn100Gr; + @override + @JsonKey(name: 'energy_on_portion') + final double energyOnPortion; + @override + @JsonKey(name: 'proteins_on_portion') + final double proteinsOnPortion; + @override + @JsonKey(name: 'fats_on_portion') + final double fatsOnPortion; + @override + @JsonKey(name: 'carbohydrates_on_portion') + final double carbohydratesOnPortion; @override String toString() { - return 'GlossaryModel(id: $id, name: $name, grInPortion: $grInPortion)'; + return 'GlossaryModel(id: $id, name: $name, grInPortion: $grInPortion, energyOn100Gr: $energyOn100Gr, proteinsOn100Gr: $proteinsOn100Gr, fatsOn100Gr: $fatsOn100Gr, carbohydratesOn100Gr: $carbohydratesOn100Gr, energyOnPortion: $energyOnPortion, proteinsOnPortion: $proteinsOnPortion, fatsOnPortion: $fatsOnPortion, carbohydratesOnPortion: $carbohydratesOnPortion)'; } @override @@ -330,12 +661,40 @@ class _$GlossaryModelImpl implements _GlossaryModel { (identical(other.id, id) || other.id == id) && (identical(other.name, name) || other.name == name) && (identical(other.grInPortion, grInPortion) || - other.grInPortion == grInPortion)); + other.grInPortion == grInPortion) && + (identical(other.energyOn100Gr, energyOn100Gr) || + other.energyOn100Gr == energyOn100Gr) && + (identical(other.proteinsOn100Gr, proteinsOn100Gr) || + other.proteinsOn100Gr == proteinsOn100Gr) && + (identical(other.fatsOn100Gr, fatsOn100Gr) || + other.fatsOn100Gr == fatsOn100Gr) && + (identical(other.carbohydratesOn100Gr, carbohydratesOn100Gr) || + other.carbohydratesOn100Gr == carbohydratesOn100Gr) && + (identical(other.energyOnPortion, energyOnPortion) || + other.energyOnPortion == energyOnPortion) && + (identical(other.proteinsOnPortion, proteinsOnPortion) || + other.proteinsOnPortion == proteinsOnPortion) && + (identical(other.fatsOnPortion, fatsOnPortion) || + other.fatsOnPortion == fatsOnPortion) && + (identical(other.carbohydratesOnPortion, carbohydratesOnPortion) || + other.carbohydratesOnPortion == carbohydratesOnPortion)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, id, name, grInPortion); + int get hashCode => Object.hash( + runtimeType, + id, + name, + grInPortion, + energyOn100Gr, + proteinsOn100Gr, + fatsOn100Gr, + carbohydratesOn100Gr, + energyOnPortion, + proteinsOnPortion, + fatsOnPortion, + carbohydratesOnPortion); /// Create a copy of GlossaryModel /// with the given fields replaced by the non-null parameter values. @@ -355,10 +714,20 @@ class _$GlossaryModelImpl implements _GlossaryModel { abstract class _GlossaryModel implements GlossaryModel { const factory _GlossaryModel( - {required final int id, - required final String name, - @JsonKey(name: 'portion_mass_gr') required final int grInPortion}) = - _$GlossaryModelImpl; + {required final int id, + required final String name, + @JsonKey(name: 'portion_mass_gr') required final int grInPortion, + @JsonKey(name: 'energy_on_100gr') required final double energyOn100Gr, + @JsonKey(name: 'proteins_on_100gr') required final double proteinsOn100Gr, + @JsonKey(name: 'fats_on_100gr') required final double fatsOn100Gr, + @JsonKey(name: 'carbohydrates_on_100gr') + required final double carbohydratesOn100Gr, + @JsonKey(name: 'energy_on_portion') required final double energyOnPortion, + @JsonKey(name: 'proteins_on_portion') + required final double proteinsOnPortion, + @JsonKey(name: 'fats_on_portion') required final double fatsOnPortion, + @JsonKey(name: 'carbohydrates_on_portion') + required final double carbohydratesOnPortion}) = _$GlossaryModelImpl; factory _GlossaryModel.fromJson(Map json) = _$GlossaryModelImpl.fromJson; @@ -370,6 +739,30 @@ abstract class _GlossaryModel implements GlossaryModel { @override @JsonKey(name: 'portion_mass_gr') int get grInPortion; + @override + @JsonKey(name: 'energy_on_100gr') + double get energyOn100Gr; + @override + @JsonKey(name: 'proteins_on_100gr') + double get proteinsOn100Gr; + @override + @JsonKey(name: 'fats_on_100gr') + double get fatsOn100Gr; + @override + @JsonKey(name: 'carbohydrates_on_100gr') + double get carbohydratesOn100Gr; + @override + @JsonKey(name: 'energy_on_portion') + double get energyOnPortion; + @override + @JsonKey(name: 'proteins_on_portion') + double get proteinsOnPortion; + @override + @JsonKey(name: 'fats_on_portion') + double get fatsOnPortion; + @override + @JsonKey(name: 'carbohydrates_on_portion') + double get carbohydratesOnPortion; /// Create a copy of GlossaryModel /// with the given fields replaced by the non-null parameter values. diff --git a/lib/features/glossary/models/glossary_model.g.dart b/lib/features/glossary/models/glossary_model.g.dart index 7fbd6b30..cd3214b4 100644 --- a/lib/features/glossary/models/glossary_model.g.dart +++ b/lib/features/glossary/models/glossary_model.g.dart @@ -20,11 +20,33 @@ Map _$$GlossaryPaginationModelImplToJson( 'data': instance.glossariesList, }; +_$GlossaryModerationModelImpl _$$GlossaryModerationModelImplFromJson( + Map json) => + _$GlossaryModerationModelImpl( + glossary: GlossaryModel.fromJson( + json['product_nutritional_value'] as Map), + ); + +Map _$$GlossaryModerationModelImplToJson( + _$GlossaryModerationModelImpl instance) => + { + 'product_nutritional_value': instance.glossary, + }; + _$GlossaryModelImpl _$$GlossaryModelImplFromJson(Map json) => _$GlossaryModelImpl( id: (json['id'] as num).toInt(), name: json['name'] as String, grInPortion: (json['portion_mass_gr'] as num).toInt(), + energyOn100Gr: (json['energy_on_100gr'] as num).toDouble(), + proteinsOn100Gr: (json['proteins_on_100gr'] as num).toDouble(), + fatsOn100Gr: (json['fats_on_100gr'] as num).toDouble(), + carbohydratesOn100Gr: (json['carbohydrates_on_100gr'] as num).toDouble(), + energyOnPortion: (json['energy_on_portion'] as num).toDouble(), + proteinsOnPortion: (json['proteins_on_portion'] as num).toDouble(), + fatsOnPortion: (json['fats_on_portion'] as num).toDouble(), + carbohydratesOnPortion: + (json['carbohydrates_on_portion'] as num).toDouble(), ); Map _$$GlossaryModelImplToJson(_$GlossaryModelImpl instance) => @@ -32,4 +54,12 @@ Map _$$GlossaryModelImplToJson(_$GlossaryModelImpl instance) => 'id': instance.id, 'name': instance.name, 'portion_mass_gr': instance.grInPortion, + 'energy_on_100gr': instance.energyOn100Gr, + 'proteins_on_100gr': instance.proteinsOn100Gr, + 'fats_on_100gr': instance.fatsOn100Gr, + 'carbohydrates_on_100gr': instance.carbohydratesOn100Gr, + 'energy_on_portion': instance.energyOnPortion, + 'proteins_on_portion': instance.proteinsOnPortion, + 'fats_on_portion': instance.fatsOnPortion, + 'carbohydrates_on_portion': instance.carbohydratesOnPortion, }; diff --git a/lib/features/plans/models/plan_pagination_model.dart b/lib/features/plans/models/plan_pagination_model.dart index 637961e3..dcd0e643 100644 --- a/lib/features/plans/models/plan_pagination_model.dart +++ b/lib/features/plans/models/plan_pagination_model.dart @@ -50,6 +50,8 @@ class PacketOptions with _$PacketOptions { }) = _PacketOptions; factory PacketOptions.fromJson(Map json) => _$PacketOptionsFromJson(json); + + factory PacketOptions.empty() => PacketOptions(items: [], price: -1, short: '', description: ''); } @freezed diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 41adcc0c..215f3a55 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -30,6 +30,7 @@ class MessageLookup extends MessageLookupByLibrary { "add": MessageLookupByLibrary.simpleMessage("Добавить"), "add_cover": MessageLookupByLibrary.simpleMessage("Добавьте обложку"), "add_measure": MessageLookupByLibrary.simpleMessage("Добавить замер"), + "adding_plan": MessageLookupByLibrary.simpleMessage("Добавление плана"), "additional_information": MessageLookupByLibrary.simpleMessage("Дополнительная информация"), "admin": MessageLookupByLibrary.simpleMessage("Админ"), @@ -57,6 +58,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Обхват груди"), "choose_diet_duration": MessageLookupByLibrary.simpleMessage("Выберите срок диеты"), + "choose_plan_duration": + MessageLookupByLibrary.simpleMessage("Выберите срок плана"), "choose_start_diet_date": MessageLookupByLibrary.simpleMessage( "Выберите дату старты диеты. Она отобразиться у Вас в дневнике"), "circumference_empty": MessageLookupByLibrary.simpleMessage( @@ -74,6 +77,7 @@ class MessageLookup extends MessageLookupByLibrary { "complain": MessageLookupByLibrary.simpleMessage("Пожаловаться"), "continueButton": MessageLookupByLibrary.simpleMessage("Продолжить"), "continue_": MessageLookupByLibrary.simpleMessage("Продолжить"), + "copied": MessageLookupByLibrary.simpleMessage("cкопирован"), "copy_link": MessageLookupByLibrary.simpleMessage("Скопировать ссылку"), "country": MessageLookupByLibrary.simpleMessage("Страна"), "create_diet": MessageLookupByLibrary.simpleMessage("Создание диеты"), @@ -95,6 +99,8 @@ class MessageLookup extends MessageLookupByLibrary { "down_pressure": MessageLookupByLibrary.simpleMessage("Нижние показатели давления:"), "draft": MessageLookupByLibrary.simpleMessage("Черновик"), + "eating_behavior": + MessageLookupByLibrary.simpleMessage("Пищевое поведение"), "email": MessageLookupByLibrary.simpleMessage("Элетронная почта"), "email_empty": MessageLookupByLibrary.simpleMessage( "Необходимо ввести адрес электронной почты"), @@ -124,6 +130,7 @@ class MessageLookup extends MessageLookupByLibrary { "Имя обязательно для заполнения"), "first_name_short": MessageLookupByLibrary.simpleMessage( "В имени должен быть минимум 1 символ"), + "fitness": MessageLookupByLibrary.simpleMessage("Поддержка формы"), "from_a_to_z": MessageLookupByLibrary.simpleMessage("Название: А-Я"), "from_z_to_a": MessageLookupByLibrary.simpleMessage("Название: Я-А"), "gallery": MessageLookupByLibrary.simpleMessage("Галерея"), @@ -139,6 +146,7 @@ class MessageLookup extends MessageLookupByLibrary { "inactive_diet_state": MessageLookupByLibrary.simpleMessage("Черновик"), "initial_measurements": MessageLookupByLibrary.simpleMessage("Первичные замеры"), + "label": MessageLookupByLibrary.simpleMessage("Название"), "language": MessageLookupByLibrary.simpleMessage("Язык"), "last_name": MessageLookupByLibrary.simpleMessage("Фамилия"), "last_name_empty": MessageLookupByLibrary.simpleMessage( @@ -174,6 +182,7 @@ class MessageLookup extends MessageLookupByLibrary { "no": MessageLookupByLibrary.simpleMessage("Нет"), "no_account": MessageLookupByLibrary.simpleMessage("У меня нет аккаунта"), + "one_option": MessageLookupByLibrary.simpleMessage("1 опция"), "order": MessageLookupByLibrary.simpleMessage("Порядок"), "password": MessageLookupByLibrary.simpleMessage("Пароль"), "password_confirmation": @@ -190,6 +199,12 @@ class MessageLookup extends MessageLookupByLibrary { "photo": MessageLookupByLibrary.simpleMessage("Фото"), "place_of_living": MessageLookupByLibrary.simpleMessage("Место проживания"), + "plan_desc": MessageLookupByLibrary.simpleMessage("Описание плана"), + "plan_desc_example": MessageLookupByLibrary.simpleMessage( + "Большое подробное, привлекательное описание плана"), + "plan_duration": MessageLookupByLibrary.simpleMessage("Срок плана"), + "plan_label_desc": + MessageLookupByLibrary.simpleMessage("План для поддержания формы"), "plans_complete": MessageLookupByLibrary.simpleMessage("Пройденные планы"), "popular": MessageLookupByLibrary.simpleMessage("Популярные"), @@ -199,15 +214,20 @@ class MessageLookup extends MessageLookupByLibrary { "Все показатели давления обязательны к заполнению"), "pressure_is_not_a_number": MessageLookupByLibrary.simpleMessage( "Все показатели давления должны быть числами"), + "preview": MessageLookupByLibrary.simpleMessage("Превью"), "profile": MessageLookupByLibrary.simpleMessage("Профиль"), "proteins": MessageLookupByLibrary.simpleMessage("Белки"), + "publish": MessageLookupByLibrary.simpleMessage("Опубликовать"), "save": MessageLookupByLibrary.simpleMessage("Сохранить"), "settings": MessageLookupByLibrary.simpleMessage("Настройки"), "share": MessageLookupByLibrary.simpleMessage("Поделиться"), "skip": MessageLookupByLibrary.simpleMessage("Пропустить"), + "slim": MessageLookupByLibrary.simpleMessage("Похудение"), "startDiet": MessageLookupByLibrary.simpleMessage("Начать диету"), "tell_your_schedule": MessageLookupByLibrary.simpleMessage( "Расскажите в свободной форме о том как просыпаетесь, когда едите, когда ложитесь спать:"), + "three_options": MessageLookupByLibrary.simpleMessage("3 опции"), + "to_draft": MessageLookupByLibrary.simpleMessage("В черновик"), "unfavorite_foods": MessageLookupByLibrary.simpleMessage("Нелюбимые продукты"), "up_pressure": MessageLookupByLibrary.simpleMessage( @@ -228,6 +248,8 @@ class MessageLookup extends MessageLookupByLibrary { "weight_chart": MessageLookupByLibrary.simpleMessage("График веса"), "weight_measurements": MessageLookupByLibrary.simpleMessage("Замер веса"), + "wellness": + MessageLookupByLibrary.simpleMessage("Улучшение самочувствия"), "what_is_your_name": MessageLookupByLibrary.simpleMessage("Как вас зовут?"), "wrong_credentials": MessageLookupByLibrary.simpleMessage( diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 4e94c27a..a0837964 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -1609,6 +1609,176 @@ class S { args: [], ); } + + /// `cкопирован` + String get copied { + return Intl.message( + 'cкопирован', + name: 'copied', + desc: '', + args: [], + ); + } + + /// `В черновик` + String get to_draft { + return Intl.message( + 'В черновик', + name: 'to_draft', + desc: '', + args: [], + ); + } + + /// `Превью` + String get preview { + return Intl.message( + 'Превью', + name: 'preview', + desc: '', + args: [], + ); + } + + /// `Опубликовать` + String get publish { + return Intl.message( + 'Опубликовать', + name: 'publish', + desc: '', + args: [], + ); + } + + /// `Добавление плана` + String get adding_plan { + return Intl.message( + 'Добавление плана', + name: 'adding_plan', + desc: '', + args: [], + ); + } + + /// `Название` + String get label { + return Intl.message( + 'Название', + name: 'label', + desc: '', + args: [], + ); + } + + /// `План для поддержания формы` + String get plan_label_desc { + return Intl.message( + 'План для поддержания формы', + name: 'plan_label_desc', + desc: '', + args: [], + ); + } + + /// `Срок плана` + String get plan_duration { + return Intl.message( + 'Срок плана', + name: 'plan_duration', + desc: '', + args: [], + ); + } + + /// `Похудение` + String get slim { + return Intl.message( + 'Похудение', + name: 'slim', + desc: '', + args: [], + ); + } + + /// `Поддержка формы` + String get fitness { + return Intl.message( + 'Поддержка формы', + name: 'fitness', + desc: '', + args: [], + ); + } + + /// `Улучшение самочувствия` + String get wellness { + return Intl.message( + 'Улучшение самочувствия', + name: 'wellness', + desc: '', + args: [], + ); + } + + /// `Пищевое поведение` + String get eating_behavior { + return Intl.message( + 'Пищевое поведение', + name: 'eating_behavior', + desc: '', + args: [], + ); + } + + /// `Выберите срок плана` + String get choose_plan_duration { + return Intl.message( + 'Выберите срок плана', + name: 'choose_plan_duration', + desc: '', + args: [], + ); + } + + /// `Описание плана` + String get plan_desc { + return Intl.message( + 'Описание плана', + name: 'plan_desc', + desc: '', + args: [], + ); + } + + /// `Большое подробное, привлекательное описание плана` + String get plan_desc_example { + return Intl.message( + 'Большое подробное, привлекательное описание плана', + name: 'plan_desc_example', + desc: '', + args: [], + ); + } + + /// `1 опция` + String get one_option { + return Intl.message( + '1 опция', + name: 'one_option', + desc: '', + args: [], + ); + } + + /// `3 опции` + String get three_options { + return Intl.message( + '3 опции', + name: 'three_options', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index e522522c..f47a166f 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -154,5 +154,23 @@ "photo": "Фото", "gallery": "Галерея", "add_cover": "Добавьте обложку", - "add": "Добавить" + "add": "Добавить", + "copied": "cкопирован", + "to_draft": "В черновик", + "preview": "Превью", + "publish": "Опубликовать", + "adding_plan": "Добавление плана", + "label": "Название", + "plan_label_desc": "План для поддержания формы", + "plan_duration": "Срок плана", + "slim": "Похудение", + "fitness": "Поддержка формы", + "wellness": "Улучшение самочувствия", + "eating_behavior": "Пищевое поведение", + "choose_plan_duration": "Выберите срок плана", + "plan_desc": "Описание плана", + "plan_desc_example": "Большое подробное, привлекательное описание плана", + "one_option": "1 опция", + "three_options": "3 опции" + } diff --git a/lib/main.dart b/lib/main.dart index c17baa7c..ddbacf93 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,7 +10,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_portal/flutter_portal.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:keyboard_dismisser/keyboard_dismisser.dart'; void main() async {