my diets have been update

This commit is contained in:
Username1548
2025-02-17 14:40:31 +07:00
parent 070d7e1047
commit 291947bd32
98 changed files with 4969 additions and 2670 deletions

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.92 11L10.82 8.5C11.0067 8.38 11.1 8.21333 11.1 8C11.1 7.78667 11.0067 7.62 10.82 7.5L6.92 5C6.72 4.86667 6.51667 4.85667 6.31 4.97C6.10333 5.08333 6 5.26 6 5.5V10.5C6 10.74 6.10333 10.9167 6.31 11.03C6.51667 11.1433 6.72 11.1333 6.92 11ZM8 16C6.89333 16 5.85333 15.79 4.88 15.37C3.90667 14.95 3.06 14.38 2.34 13.66C1.62 12.94 1.05 12.0933 0.63 11.12C0.21 10.1467 0 9.10667 0 8C0 6.89333 0.21 5.85333 0.63 4.88C1.05 3.90667 1.62 3.06 2.34 2.34C3.06 1.62 3.90667 1.05 4.88 0.63C5.85333 0.21 6.89333 0 8 0C9.10667 0 10.1467 0.21 11.12 0.63C12.0933 1.05 12.94 1.62 13.66 2.34C14.38 3.06 14.95 3.90667 15.37 4.88C15.79 5.85333 16 6.89333 16 8C16 9.10667 15.79 10.1467 15.37 11.12C14.95 12.0933 14.38 12.94 13.66 13.66C12.94 14.38 12.0933 14.95 11.12 15.37C10.1467 15.79 9.10667 16 8 16ZM8 14.4C9.78667 14.4 11.3 13.78 12.54 12.54C13.78 11.3 14.4 9.78667 14.4 8C14.4 6.21333 13.78 4.7 12.54 3.46C11.3 2.22 9.78667 1.6 8 1.6C6.21333 1.6 4.7 2.22 3.46 3.46C2.22 4.7 1.6 6.21333 1.6 8C1.6 9.78667 2.22 11.3 3.46 12.54C4.7 13.78 6.21333 14.4 8 14.4Z" fill="#2F80ED"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -30,8 +30,8 @@ import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
import 'package:eatvio_app/features/recipes/presentation/pages/about_recipes_screen.dart';
import 'package:eatvio_app/features/recipes/presentation/pages/recipe_create_screen.dart';
import 'package:eatvio_app/features/recipe/presentation/about_recipe_screen.dart';
import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart';
import 'package:eatvio_app/features/settings/entities/settings_entity.dart';
import 'package:eatvio_app/features/settings/presentation/pages/change_email_screen.dart';
@@ -58,6 +58,8 @@ class AppRouter {
path: '/auth-screen',
name: 'authScreen',
redirect: (context, state) async {
// TokenStorage.removeTokenInStorage();
// await TokenStorage.saveTokenInStorage('1|laravel_sanctum_zaU2133RQ0wbxDYdH7IQxTpThmilWtKhzxbSt5X1065fcae3');
if (await TokenStorage.haveTokenInStorage()) {
return '/my-profile-screen';
}
@@ -128,9 +130,14 @@ class AppRouter {
GoRoute(
path: '/about-recipes-screen',
name: 'aboutRecipesScreen',
builder: (context, state) => AboutRecipesScreen(
recipes: state.extra as Recipes,
),
builder: (context, state) {
final data = state.extra as Map<String, dynamic>;
return AboutRecipeScreen(
recipe: data['recipe'] as Recipe,
index: data['index'] as int?,
navigateToComments: data['navigateToComments'] as bool,
);
},
),
GoRoute(
path: RecipeCreateScreen.path,

View File

@@ -54,9 +54,12 @@ import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/prof
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:eatvio_app/features/profile/services/profile_service.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/recipes/reporitories/recipe_repository.dart';
import 'package:eatvio_app/features/recipes/services/recipe_service.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart';
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart';
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
import 'package:eatvio_app/features/registration/services/registration_service.dart';
@@ -80,7 +83,7 @@ Future<void> init() async {
// Cubit factories
sl.registerFactory<RegistrationCubit>(() => RegistrationCubit(registrationRepository: sl()));
sl.registerFactory<ChatRoomCubit>(() => ChatRoomCubit(chatRepository: sl(), wsManager: sl()));
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl()));
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl()));
sl.registerFactory<CalendarCubit>(() => CalendarCubit(calendarRepository: sl()));
sl.registerFactory<ProfileCubit>(() => ProfileCubit(profileRepository: sl(), chatRepository: sl()));
sl.registerFactory<profilePlans.PlansCubit>(() => profilePlans.PlansCubit(plansRepository: sl()));
@@ -99,10 +102,11 @@ Future<void> init() async {
sl.registerFactory<FeedProfileCubit>(() => FeedProfileCubit(profileRepository: sl()));
sl.registerFactory<AboutPlanCubit>(() => AboutPlanCubit(planRepository: sl()));
sl.registerFactory<CreateRecipeCubit>(() => CreateRecipeCubit(recipeRepository: sl()));
sl.registerFactory<MyDietsCubit>(() => MyDietsCubit(myDietsRepository: sl()));
sl.registerFactory<MyDietsCubit>(() => MyDietsCubit(myDietsRepository: sl(), createDietRepository: sl()));
sl.registerFactory<MyPlansCubit>(() => MyPlansCubit(myPlansRepository: sl()));
sl.registerFactory<UserDetailsCubit>(() => UserDetailsCubit(profileRepository: sl()));
sl.registerFactory<ConsultantsCubit>(() => ConsultantsCubit(feedRepository: sl()));
sl.registerFactory<AboutRecipeCubit>(() => AboutRecipeCubit(recipeRepository: sl(), profileRepository: sl()));
// Repository
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
@@ -122,6 +126,7 @@ Future<void> init() async {
sl.registerLazySingleton<PlanRepository>(() => PlanRepository(planService: sl()));
sl.registerLazySingleton<MyDietsRepository>(() => MyDietsRepository(myDietsService: sl()));
sl.registerLazySingleton<MyPlansRepository>(() => MyPlansRepository(myPlansService: sl()));
sl.registerLazySingleton<AboutRecipeRepository>(() => AboutRecipeRepository(recipeService: sl()));
// Services
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
@@ -129,7 +134,7 @@ Future<void> init() async {
sl.registerLazySingleton<RegistrationService>(() => RegistrationService(sl()));
sl.registerLazySingleton<CalendarService>(() => CalendarService(sl()));
sl.registerLazySingleton<ProfileService>(() => ProfileService(sl()));
sl.registerLazySingleton<RecipeService>(() => RecipeService(sl()));
sl.registerLazySingleton<CreateRecipeService>(() => CreateRecipeService(sl()));
sl.registerLazySingleton<SettingsService>(() => SettingsService(sl()));
sl.registerLazySingleton<ConsultantService>(() => ConsultantService(sl()));
sl.registerLazySingleton<DiaryService>(() => DiaryService(sl()));
@@ -142,6 +147,7 @@ Future<void> init() async {
sl.registerLazySingleton<MyDietsService>(() => MyDietsService(sl()));
sl.registerLazySingleton<MyPlansService>(() => MyPlansService(sl()));
sl.registerLazySingleton<WsService>(() => WsService(sl()));
sl.registerLazySingleton<AboutRecipeService>(() => AboutRecipeService(sl()));
// External
sl.registerLazySingleton(

View File

@@ -0,0 +1,5 @@
enum ProductType {
diet,
plan,
recipe
}

View File

@@ -41,3 +41,33 @@ String getCommentForm(int number, S s) {
return s.comments_plural;
}
}
String getMinuteForm(int number) {
if (number % 10 == 1 && number % 100 != 11) {
return S.current.minute;
} else if ([2, 3, 4].contains(number % 10) && ![12, 13, 14].contains(number % 100)) {
return S.current.minutes;
} else {
return S.current.another_form_of_minute;
}
}
String getHourForm(int number) {
if (number % 10 == 1 && number % 100 != 11) {
return S.current.hour;
} else if ([2, 3, 4].contains(number % 10) && ![12, 13, 14].contains(number % 100)) {
return S.current.another_form_of_hour;
} else {
return S.current.hours;
}
}
String getStepForm(int number) {
if (number % 10 == 1 && number % 100 != 11) {
return S.current.stage;
} else if ([2, 3, 4].contains(number % 10) && ![12, 13, 14].contains(number % 100)) {
return S.current.another_form_of_stage;
} else {
return S.current.stages;
}
}

View File

@@ -1,9 +1,11 @@
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/utils/enums.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/type_badges.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
@@ -11,21 +13,35 @@ import 'package:flutter_svg/svg.dart';
class InfoRow extends StatelessWidget {
const InfoRow({
required this.type,
super.key,
this.diet,
this.itemsBackgroundColor,
this.disableDescriptionOfTypes = false,
this.plan,
this.recipe,
});
final ProductType type;
final Color? itemsBackgroundColor;
final Diet? diet;
final PlanModel? plan;
final Recipe? recipe;
final bool disableDescriptionOfTypes;
@override
Widget build(BuildContext context) {
final s = S.of(context);
String? timeTitle;
if (recipe != null) {
final hours = recipe!.cookingTime ~/ 60;
final minutes = recipe!.cookingTime % 60;
if (hours > 0) {
timeTitle = '$hours ${getHourForm(hours)} $minutes ${getMinuteForm(minutes)}';
} else {
timeTitle = '$minutes ${getMinuteForm(minutes)}';
}
}
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
@@ -41,7 +57,11 @@ class InfoRow extends StatelessWidget {
),
SizedBox(width: 4),
Text(
diet == null ? '${plan?.duration} ${getDayForm(plan?.duration ?? 7, s)}' : '${diet?.duration} ${getDayForm(diet?.duration ?? 7, s)}',
switch (type) {
ProductType.diet => '${diet?.duration} ${getDayForm(diet?.duration ?? 7, s)}',
ProductType.plan => '${plan?.duration} ${getDayForm(plan?.duration ?? 7, s)}',
ProductType.recipe => '${timeTitle ?? ''}',
},
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
@@ -51,18 +71,42 @@ class InfoRow extends StatelessWidget {
backgroundColor: itemsBackgroundColor,
children: [
SvgPicture.asset(
'assets/icons/people.svg',
switch (type) {
ProductType.diet => 'assets/icons/people.svg',
ProductType.plan => 'assets/icons/people.svg',
ProductType.recipe => 'assets/icons/calories.svg',
},
width: 13.3,
height: 13.3,
),
SizedBox(width: 4),
Text(
'80 человек прошли',
switch (type) {
ProductType.diet => '80 человек прошли',
ProductType.plan => '80 человек прошли',
ProductType.recipe => (recipe?.calories.toStringAsFixed(0) ?? '0') + ' ккал',
},
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
),
SizedBox(width: 12),
if (type == ProductType.recipe)
InfoBouble(
backgroundColor: itemsBackgroundColor,
children: [
SvgPicture.asset(
'assets/icons/steps.svg',
width: 13.3,
height: 13.3,
),
SizedBox(width: 4),
Text(
'${recipe?.steps?.length ?? 0} ${getStepForm(recipe?.steps?.length ?? 0)}',
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
),
if (plan?.type.hasAtLeastOneType() == true || diet?.type.hasAtLeastOneType() == true)
InfoBouble(
backgroundColor: itemsBackgroundColor,

View File

@@ -19,5 +19,3 @@ class CreateDietModel with _$CreateDietModel {
List<List<List<DailySchedule>>>? dailySchedule,
}) = _CreateDietModel;
}

View File

@@ -233,7 +233,13 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
else
CustomOutlinedButton(
onPressed: () {
context.read<CreateDietCubit>().publishDiet();
if (widget.diet != null) {
context.read<CreateDietCubit>().updateDiet(
widget.diet!.id,
);
} else {
context.read<CreateDietCubit>().createDiet();
}
},
text: widget.diet != null ? s.save : s.publish,
height: 52,

View File

@@ -18,6 +18,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
final ProfileRepository profileRepository;
final GlossaryRepository glossaryRepository;
String? tempImagePath;
int? dietId;
static const _dietStateValues = DietState.values;
CreateDietCubit({required this.profileRepository, required this.createDietRepository, required this.glossaryRepository}) : super(CreateDietState.initial());
@@ -25,10 +26,12 @@ class CreateDietCubit extends Cubit<CreateDietState> {
Future<void> setInitialData(Diet? diet) async {
if (diet == null) return;
emit(state.copyWith(isLoading: true, isDietBeingEdited: true));
final countOfDays = diet.dailySchedule.length;
dietId = diet.id;
final dailyScheduleWithNutrition = await createDietRepository.getDietPortionsNutrition(diet.id);
final countOfDays = dailyScheduleWithNutrition.length;
final List<List<List<DailySchedule>>> dailySchedule = List.generate(countOfDays, (index) => [[], [], [], [], []]);
for (final dayEntry in diet.dailySchedule.entries) {
for (final dayEntry in dailyScheduleWithNutrition.entries) {
for (final mealEntry in dayEntry.value.entries) {
for (final product in mealEntry.value.values) {
dailySchedule[dayEntry.key - 1][mealEntry.key - 1].add(product);
@@ -318,7 +321,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
return diet;
}
Future<String?> publishDiet() async {
Future<String?> createDiet() async {
emit(state.copyWith(isDietPublishing: true));
if (!_isFromValid()) return null;
try {
@@ -347,6 +350,33 @@ class CreateDietCubit extends Cubit<CreateDietState> {
return null;
}
Future<String?> updateDiet(int dietId) async {
if (!_isFromValid()) return null;
try {
File? image;
if (state.dietData.image != null) {
image = await ImageCompressUtility.compressFile(File(state.dietData.image!.path));
}
await createDietRepository.updateDiet(
dietId: dietId,
name: state.dietData.name,
duration: state.dietData.duration,
state: state.dietData.state ?? DietState.active,
image: image,
meatType: state.dietData.type['meat'] == true ? '1' : '0',
carbType: state.dietData.type['carb'] == true ? '1' : '0',
vegType: state.dietData.type['veg'] == true ? '1' : '0',
lightType: state.dietData.type['light'] == true ? '1' : '0',
details: state.dietData.details,
dailySchedule: state.dietData.dailySchedule,
);
return null;
} catch (e, st) {
log(e.toString(), stackTrace: st);
return 'Произошла непредвиденная ошибка';
}
}
bool _isFromValid() {
bool result = true;
if ((state.dietData.name?.length ?? 0) < 3) {

View File

@@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:eatvio_app/core/utils/enums.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/core/utils/string_extentions.dart';
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
@@ -300,7 +301,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
),
),
SizedBox(height: 12),
InfoRow(diet: widget.diet),
InfoRow(diet: widget.diet, type: ProductType.diet,),
ExpandableText(
text: widget.diet.details,
),

View File

@@ -1,5 +1,7 @@
import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/custom_divider.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';
@@ -33,6 +35,7 @@ class _AddMealWidgetState extends State<AddMealWidget> {
GlossaryModel? selectedGlossary;
final productNameController = TextEditingController();
final quantityController = TextEditingController();
List<GlossaryModel> glossariesList = [];
@override
void initState() {
@@ -66,47 +69,27 @@ class _AddMealWidgetState extends State<AddMealWidget> {
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.8 - 32,
child: TypeAheadField<GlossaryModel>(
child: CustomTextFormField(
controller: productNameController,
itemBuilder: (context, glossary) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
glossary.name,
style: TextStyle(
height: 1,
fontSize: 16,
),
),
autofocus: true,
hintText: 'Введите название продукта/блюда',
onChanged: (query) {
EasyDebounce.debounce(
'glossary-search',
Duration(milliseconds: 250),
() async {
selectedGlossary = null;
if (query.isEmpty) {
glossariesList = [];
selectedGlossary = null;
setState(() {});
return;
}
glossariesList = await widget.createDietCubit.getGlossaryListByQuery(query);
setState(() {});
},
);
},
itemSeparatorBuilder: (context, index) => _divider(context),
onSelected: (newSelectedGlossary) async {
setState(() {
selectedGlossary = newSelectedGlossary;
productNameController.text = selectedGlossary!.name;
});
},
suggestionsCallback: (query) async {
if (query.length < 1) return [];
final suggestionsList = await widget.createDietCubit.getGlossaryListByQuery(query);
return suggestionsList;
},
builder: (context, controller, focusNode) {
return CustomTextFormField(
controller: controller,
autofocus: true,
focusNode: focusNode,
hintText: 'Введите название продукта/блюда',
);
},
emptyBuilder: (context) {
return const SizedBox.shrink();
},
errorBuilder: (context, object) => Padding(
padding: const EdgeInsets.all(8.0),
child: Text('Нет подходящих вариантов'),
),
),
),
const SizedBox(width: 4),
@@ -129,7 +112,56 @@ class _AddMealWidgetState extends State<AddMealWidget> {
),
],
),
const SizedBox(height: 16),
glossariesList.isEmpty ? const SizedBox(height: 16) : const SizedBox.shrink(),
Container(
constraints: BoxConstraints(
maxHeight: 256,
),
child: ListView.separated(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, i) {
return ListTile(
onTap: () async {
setState(() {
selectedGlossary = glossariesList[i];
productNameController.text = selectedGlossary!.name;
});
glossariesList = [];
setState(() {});
FocusManager.instance.primaryFocus?.nextFocus();
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
title: Text(
glossariesList[i].name,
style: TextStyle(
height: 1,
),
),
subtitle: glossariesList[i].grInPortion == 0
? null
: Text(
'${glossariesList[i].grInPortion} г',
style: TextStyle(
height: 1,
color: Colors.grey.shade600,
),
),
trailing: Icon(
Icons.add_rounded,
color: Colors.grey.shade400,
),
contentPadding: EdgeInsets.only(left: 12),
);
},
itemCount: glossariesList.length,
separatorBuilder: (BuildContext context, int index) {
return CustomDivider();
},
),
),
Row(
children: [
if (widget.portionIndex != null)
@@ -188,7 +220,7 @@ class _AddMealWidgetState extends State<AddMealWidget> {
Future<void> addPortion() async {
final portionIndex = widget.portionIndex;
final measurementUnits = selectedGlossary?.grInPortion == null
var measurementUnits = selectedGlossary?.grInPortion == null
? Portion.PCS
: selectedGlossary?.grInPortion == 0
? Portion.GRAMS
@@ -199,7 +231,7 @@ class _AddMealWidgetState extends State<AddMealWidget> {
final portion = DailySchedule(
dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates),
quantity: int.parse(quantityController.text),
name: glossary.name,
name: glossary.name,
productNutritionalValueId: glossary.id.toString(),
portion: measurementUnits,
);
@@ -234,7 +266,8 @@ class _AddMealWidgetState extends State<AddMealWidget> {
),
);
if (newGlossary != null) {
final [calories, carbohydrates, proteins, fats] = _getCPFC(newGlossary, Portion.GRAMS);
measurementUnits = Portion.GRAMS;
final [calories, carbohydrates, proteins, fats] = _getCPFC(newGlossary, measurementUnits);
final portion = DailySchedule(
dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates),
quantity: int.parse(quantityController.text),

View File

@@ -58,4 +58,97 @@ class CreateDietRepository {
return createDietService.createPersonalDiet(formData: formData);
}
}
Future<Map<int, Map<int, Map<int, DailySchedule>>>> getDietPortionsNutrition(int dietId) async {
try {
final response = await createDietService.getDietPortionsNutrition(diet: dietId);
final data = response.data;
final dailySchedule = (data as Map<String, dynamic>).map((key, value) {
return MapEntry(
int.parse(key),
(value as Map<String, dynamic>).map((key, value) {
return MapEntry(
int.parse(key),
(value as Map<String, dynamic>).map((key, value) {
return MapEntry(int.parse(key), DailySchedule.fromJson(value as Map<String, dynamic>));
}),
);
}),
);
});
return dailySchedule;
} catch (e) {
rethrow;
}
}
Future<void> updateDiet({
required int dietId,
required DietState state,
String? name,
int? duration,
String? meatType,
String? carbType,
String? vegType,
String? lightType,
String? details,
File? image,
List<List<List<DailySchedule>>>? dailySchedule,
}) {
final dietMap = <String, dynamic>{};
if (dailySchedule != null) {
for (final (dayIndex, day) in dailySchedule.indexed) {
for (final (mealIndex, meal) in day.indexed) {
for (final (productIndex, product) in meal.indexed) {
final jsonProduct = product.toJson();
dietMap.addAll({
'daily_schedule[${dayIndex + 1}][${mealIndex + 1}][${productIndex}][name]': jsonProduct['name'],
'daily_schedule[${dayIndex + 1}][${mealIndex + 1}][${productIndex}][portion]': jsonProduct['portion'],
'daily_schedule[${dayIndex + 1}][${mealIndex + 1}][${productIndex}][quantity]': jsonProduct['quantity'],
'daily_schedule[${dayIndex + 1}][${mealIndex + 1}][${productIndex}][product_nutritional_value_id]': jsonProduct['product_nutritional_value_id'],
});
}
}
}
}
if (name != null) {
dietMap.addAll({'name': name});
}
if (details != null) {
dietMap.addAll({'details': details});
}
if (duration != null) {
dietMap.addAll({'duration': duration});
}
if (meatType != null) {
dietMap.addAll({'type[meat]': meatType});
}
if (carbType != null) {
dietMap.addAll({'type[carb]': carbType});
}
if (vegType != null) {
dietMap.addAll({'type[veg]': vegType});
}
if (lightType != null) {
dietMap.addAll({'type[light]': lightType});
}
if (image != null) {
dietMap.addAll({
'image': MultipartFile.fromFileSync(
image.path,
filename: image.path.split(Platform.pathSeparator).last,
),
});
}
final formData = FormData.fromMap(dietMap);
switch (state) {
case DietState.active:
return createDietService.updatePublished(diet: dietId, formData: formData);
case DietState.draft:
return createDietService.updateDrafted(diet: dietId, formData: formData);
case DietState.personal:
return createDietService.updatePersonal(diet: dietId, formData: formData);
}
}
}

View File

@@ -25,4 +25,30 @@ abstract class CreateDietService {
Future<CreatedDiet> createPersonalDiet({
@Body() required FormData formData,
});
@PUT('/api/diets/update/published/{diet}')
@MultiPart()
Future<void> updatePublished({
@Path() required int diet,
@Body() required FormData formData,
});
@PUT('/api/diets/update/drafted/{diet}')
@MultiPart()
Future<void> updateDrafted({
@Path() required int diet,
@Body() required FormData formData,
});
@PUT('/api/diets/update/personalized/{diet}')
@MultiPart()
Future<void> updatePersonal({
@Path() required int diet,
@Body() required FormData formData,
});
@GET('/api/diets/show/{diet}/per_portion_nutritions')
Future<HttpResponse> getDietPortionsNutrition({
@Path() required int diet,
});
}

View File

@@ -113,6 +113,111 @@ class _CreateDietService implements CreateDietService {
return _value;
}
@override
Future<void> updatePublished({
required int diet,
required FormData formData,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = formData;
final _options = _setStreamType<void>(
Options(
method: 'PUT',
headers: _headers,
extra: _extra,
contentType: 'multipart/form-data',
)
.compose(
_dio.options,
'/api/diets/update/published/${diet}',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
await _dio.fetch<void>(_options);
}
@override
Future<void> updateDrafted({
required int diet,
required FormData formData,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = formData;
final _options = _setStreamType<void>(
Options(
method: 'PUT',
headers: _headers,
extra: _extra,
contentType: 'multipart/form-data',
)
.compose(
_dio.options,
'/api/diets/update/drafted/${diet}',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
await _dio.fetch<void>(_options);
}
@override
Future<void> updatePersonal({
required int diet,
required FormData formData,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = formData;
final _options = _setStreamType<void>(
Options(
method: 'PUT',
headers: _headers,
extra: _extra,
contentType: 'multipart/form-data',
)
.compose(
_dio.options,
'/api/diets/update/personalized/${diet}',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
await _dio.fetch<void>(_options);
}
@override
Future<HttpResponse<dynamic>> getDietPortionsNutrition({
required int diet,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<HttpResponse<dynamic>>(
Options(method: 'GET', headers: _headers, extra: _extra)
.compose(
_dio.options,
'/api/diets/show/${diet}/per_portion_nutritions',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
final _result = await _dio.fetch(_options);
final _value = _result.data;
final httpResponse = HttpResponse(_value, _result);
return httpResponse;
}
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes ||

View File

@@ -1,8 +1,8 @@
import 'dart:io';
import 'package:eatvio_app/features/recipes/models/ingredient.dart';
import 'package:eatvio_app/features/recipes/models/recipe_model.dart';
import 'package:eatvio_app/features/recipes/models/step.dart';
import 'package:eatvio_app/features/create_recipe/models/ingredient.dart';
import 'package:eatvio_app/features/create_recipe/models/recipe_model.dart';
import 'package:eatvio_app/features/create_recipe/models/step.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'recipe_create_entity.freezed.dart';

View File

@@ -1,5 +1,5 @@
import 'package:eatvio_app/features/recipes/models/ingredient.dart';
import 'package:eatvio_app/features/recipes/models/step.dart';
import 'package:eatvio_app/features/create_recipe/models/ingredient.dart';
import 'package:eatvio_app/features/create_recipe/models/step.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'recipe_create_model.freezed.dart';

View File

@@ -1,5 +1,5 @@
import 'package:eatvio_app/features/recipes/models/ingredient.dart';
import 'package:eatvio_app/features/recipes/models/step.dart';
import 'package:eatvio_app/features/create_recipe/models/ingredient.dart';
import 'package:eatvio_app/features/create_recipe/models/step.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'recipe_model.freezed.dart';

View File

@@ -1,10 +1,10 @@
import 'dart:developer';
import 'dart:io';
import 'package:eatvio_app/features/recipes/entities/recipe_create_entity.dart';
import 'package:eatvio_app/features/recipes/models/ingredient.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:eatvio_app/features/recipes/reporitories/recipe_repository.dart';
import 'package:eatvio_app/features/create_recipe/entities/recipe_create_entity.dart';
import 'package:eatvio_app/features/create_recipe/models/ingredient.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CreateRecipeCubit extends Cubit<CreateRecipeState> {

View File

@@ -1,4 +1,4 @@
import 'package:eatvio_app/features/recipes/entities/recipe_create_entity.dart';
import 'package:eatvio_app/features/create_recipe/entities/recipe_create_entity.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'create_recipe_state.freezed.dart';

View File

@@ -1,11 +1,11 @@
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/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:eatvio_app/features/recipes/presentation/widgets/info_about_recipe_widget.dart';
import 'package:eatvio_app/features/recipes/presentation/widgets/ingredients_widget.dart';
import 'package:eatvio_app/features/recipes/presentation/widgets/steps_widget.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:eatvio_app/features/create_recipe/presentation/widgets/info_about_recipe_widget.dart';
import 'package:eatvio_app/features/create_recipe/presentation/widgets/ingredients_widget.dart';
import 'package:eatvio_app/features/create_recipe/presentation/widgets/steps_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

View File

@@ -1,6 +1,6 @@
import 'dart:io';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

View File

@@ -3,13 +3,13 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/custom_drop_down.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/features/recipes/entities/recipe_create_entity.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:eatvio_app/features/recipes/presentation/widgets/image_widget.dart';
import 'package:eatvio_app/features/recipes/presentation/widgets/portion_widget.dart';
import 'package:eatvio_app/features/recipes/presentation/widgets/tags_widget.dart';
import 'package:eatvio_app/features/recipes/presentation/widgets/time_of_cook_widget.dart';
import 'package:eatvio_app/features/create_recipe/entities/recipe_create_entity.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:eatvio_app/features/create_recipe/presentation/widgets/image_widget.dart';
import 'package:eatvio_app/features/create_recipe/presentation/widgets/portion_widget.dart';
import 'package:eatvio_app/features/create_recipe/presentation/widgets/tags_widget.dart';
import 'package:eatvio_app/features/create_recipe/presentation/widgets/time_of_cook_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

View File

@@ -1,8 +1,8 @@
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/recipes/models/ingredient.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:eatvio_app/features/create_recipe/models/ingredient.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

View File

@@ -1,7 +1,7 @@
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

View File

@@ -1,6 +1,6 @@
import 'dart:io';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

View File

@@ -1,8 +1,8 @@
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/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:eatvio_app/features/recipes/presentation/widgets/step_image_widget.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:eatvio_app/features/create_recipe/presentation/widgets/step_image_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

View File

@@ -1,8 +1,8 @@
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/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

View File

@@ -1,16 +1,17 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
import 'package:eatvio_app/features/recipes/models/recipe_model.dart';
import 'package:eatvio_app/features/recipes/models/recipe_tag_model.dart';
import 'package:eatvio_app/features/recipes/models/step.dart';
import 'package:eatvio_app/features/recipes/services/recipe_service.dart';
import 'package:eatvio_app/features/recipes/models/product.dart';
import 'package:eatvio_app/features/create_recipe/models/recipe_model.dart';
import 'package:eatvio_app/features/create_recipe/models/recipe_tag_model.dart';
import 'package:eatvio_app/features/create_recipe/models/step.dart';
import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
import 'package:eatvio_app/features/create_recipe/models/product.dart';
class RecipeRepository {
final RecipeService recipeService;
final CreateRecipeService recipeService;
RecipeRepository({required this.recipeService});
@@ -99,5 +100,5 @@ class RecipeRepository {
} catch (e) {
rethrow;
}
}
}
}

View File

@@ -0,0 +1,47 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/create_recipe/models/recipe_model.dart';
import 'package:eatvio_app/features/create_recipe/models/recipe_tag_model.dart';
import 'package:retrofit/retrofit.dart';
import 'package:eatvio_app/features/create_recipe/models/product.dart';
part 'recipe_service.g.dart';
@RestApi()
abstract class CreateRecipeService {
factory CreateRecipeService(Dio dio, {String baseUrl}) = _CreateRecipeService;
@POST('/api/recipes/{slug}/like')
Future<void> likeRecipe({
@Path() required String slug,
});
@POST('/api/recipes/{slug}/add_to_favorite')
Future<void> addToFavorite({
@Path() required String slug,
});
@POST('/api/recipes/{slug}/remove_from_favorite')
Future<void> removeFromFavorite({
@Path() required String slug,
});
@GET('/api/recipes/show/{id}')
Future<RecipeModel> getRecipe(@Path() String id);
@GET('/api/diary/product_nutritional_values/filter')
Future<Map<String, List<Product>>> getInputProduct(@Query('name') String name);
@POST('/api/recipes/create')
@MultiPart()
Future<void> publishRecipe({
@Body() required FormData formData,
});
@POST('/api/recipes/update/{id}')
Future<RecipeModel> editRecipe(@Part() Map<String, Object?> recipe, String id);
@GET('/api/recipes/tags')
Future<RecipeTagModel> getTags();
}

View File

@@ -8,8 +8,8 @@ part of 'recipe_service.dart';
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations
class _RecipeService implements RecipeService {
_RecipeService(this._dio, {this.baseUrl, this.errorLogger});
class _CreateRecipeService implements CreateRecipeService {
_CreateRecipeService(this._dio, {this.baseUrl, this.errorLogger});
final Dio _dio;
@@ -17,6 +17,63 @@ class _RecipeService implements RecipeService {
final ParseErrorLogger? errorLogger;
@override
Future<void> likeRecipe({required String slug}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<void>(
Options(method: 'POST', headers: _headers, extra: _extra)
.compose(
_dio.options,
'/api/recipes/${slug}/like',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
await _dio.fetch<void>(_options);
}
@override
Future<void> addToFavorite({required String slug}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<void>(
Options(method: 'POST', headers: _headers, extra: _extra)
.compose(
_dio.options,
'/api/recipes/${slug}/add_to_favorite',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
await _dio.fetch<void>(_options);
}
@override
Future<void> removeFromFavorite({required String slug}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<void>(
Options(method: 'POST', headers: _headers, extra: _extra)
.compose(
_dio.options,
'/api/recipes/${slug}/remove_from_favorite',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
await _dio.fetch<void>(_options);
}
@override
Future<RecipeModel> getRecipe(String id) async {
final _extra = <String, dynamic>{};

View File

@@ -8,7 +8,7 @@ import 'package:eatvio_app/features/chat/services/chat_service.dart';
import 'package:eatvio_app/features/diets/services/diet_service.dart';
import 'package:eatvio_app/features/feed/services/feed_service.dart';
import 'package:eatvio_app/features/profile/services/profile_service.dart';
import 'package:eatvio_app/features/recipes/services/recipe_service.dart';
import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
sealed class TestCases {
String get name => throw UnimplementedError();
@@ -247,7 +247,7 @@ class GetRecipes implements TestCases {
try {
final dio = sl<Dio>();
final feedService = FeedService(dio, baseUrl: baseUrl);
final recipeService = RecipeService(dio, baseUrl: baseUrl);
final recipeService = CreateRecipeService(dio, baseUrl: baseUrl);
final tagsModel = await recipeService.getTags();
final tags = tagsModel.data;
final tagsIds = tags.map((e) => e.id).toList();
@@ -431,7 +431,7 @@ class GetRecipesComments implements TestCases {
try {
final dio = sl<Dio>();
final feedService = FeedService(dio, baseUrl: baseUrl);
final recipeService = RecipeService(dio, baseUrl: baseUrl);
final recipeService = CreateRecipeService(dio, baseUrl: baseUrl);
final tagsModel = await recipeService.getTags();
final tags = tagsModel.data;
final tagsIds = tags.map((e) => e.id).toList();

View File

@@ -89,6 +89,12 @@ class _AddProductModalState extends State<AddProductModal> {
'glossary-search',
Duration(milliseconds: 250),
() async {
selectedGlossary = null;
if (query.isEmpty) {
glossariesList = [];
setState(() {});
return;
}
glossariesList = await widget.diaryCubit.getGlossaryListByQuery(query);
setState(() {});
},
@@ -131,7 +137,7 @@ class _AddProductModalState extends State<AddProductModal> {
selectedGlossary = glossariesList[i];
productNameController.text = selectedGlossary!.name;
});
glossariesList = await widget.diaryCubit.getGlossaryListByQuery(glossariesList[i].name);
glossariesList = [];
setState(() {});
FocusManager.instance.primaryFocus?.nextFocus();
},

View File

@@ -3,6 +3,7 @@ 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:go_router/go_router.dart';
import 'package:lottie/lottie.dart';
class GlossaryModerationRequestModalWidget extends StatefulWidget {
@@ -31,13 +32,13 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
GlossaryModel? glossary;
@override
Widget build(BuildContext context) {
if (glossaryState == GlossaryState.alert) {
return _alert();
} else if (glossaryState == GlossaryState.success) {
return _success();
} else {
return _addNewGlossaryProduct();
}
return PopScope(
child: switch (glossaryState) {
GlossaryState.alert => _alert(),
GlossaryState.addNewProduct => _addNewGlossaryProduct(),
GlossaryState.success => _success(),
},
);
}
@override

View File

@@ -2,6 +2,7 @@ import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/utils/enums.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/core/utils/string_extentions.dart';
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
@@ -419,7 +420,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
),
),
SizedBox(height: 12),
InfoRow(diet: widget.diet),
InfoRow(diet: widget.diet, type: ProductType.diet,),
ExpandableText(
text: widget.diet.details,
),
@@ -466,7 +467,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
padding: EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.centerLeft,
child: Text(
dietState.diet.totalComments.toString() + ' ' + getCommentForm(widget.diet.totalComments, s),
dietState.diet.totalComments.toString() + ' ' + getCommentForm(dietState.diet.totalComments, s),
style: TextStyles.textMMedium.copyWith(
color: appTheme.gray500,
),

View File

@@ -40,6 +40,7 @@ class _ExpandableTextState extends State<ExpandableText> {
data: widget.text,
style: {
"*": Style(
margin: Margins.all(0),
color: Colors.black87,
maxLines: isExpanded ? null : widget.maxLines,
textOverflow: TextOverflow.clip,

View File

@@ -96,7 +96,7 @@ class DailySchedule with _$DailySchedule {
@JsonKey(name: "name") required String name,
@JsonKey(name: "portion") required Portion portion,
@JsonKey(name: "quantity", fromJson: _parseQuantity) required int quantity,
@JsonKey(includeToJson: false, includeFromJson: false) DailyNutrition? dailyNutrition,
@JsonKey(name: 'nutrition', includeToJson: false) DailyNutrition? dailyNutrition,
@JsonKey(name: 'product_nutritional_value_id', includeToJson: true, includeFromJson: false, includeIfNull: false) String? productNutritionalValueId,
}) = _DailySchedule;
@@ -117,10 +117,21 @@ enum Portion {
@JsonValue("milliliters")
MILLILITERS,
@JsonValue("pcs")
PCS
}
PCS;
final portionValues = EnumValues({"grams": Portion.GRAMS, "milliliters": Portion.MILLILITERS, "pcs": Portion.PCS});
static Portion? getPortionFromValue(String value) {
switch (value) {
case 'gr':
return Portion.GRAMS;
case 'milliliters':
return Portion.MILLILITERS;
case 'pcs':
return Portion.PCS;
default:
return null;
}
}
}
@freezed
class DietType with _$DietType {

View File

@@ -1246,7 +1246,7 @@ mixin _$DailySchedule {
Portion get portion => throw _privateConstructorUsedError;
@JsonKey(name: "quantity", fromJson: _parseQuantity)
int get quantity => throw _privateConstructorUsedError;
@JsonKey(includeToJson: false, includeFromJson: false)
@JsonKey(name: 'nutrition', includeToJson: false)
DailyNutrition? get dailyNutrition => throw _privateConstructorUsedError;
@JsonKey(
name: 'product_nutritional_value_id',
@@ -1275,7 +1275,7 @@ abstract class $DailyScheduleCopyWith<$Res> {
{@JsonKey(name: "name") String name,
@JsonKey(name: "portion") Portion portion,
@JsonKey(name: "quantity", fromJson: _parseQuantity) int quantity,
@JsonKey(includeToJson: false, includeFromJson: false)
@JsonKey(name: 'nutrition', includeToJson: false)
DailyNutrition? dailyNutrition,
@JsonKey(
name: 'product_nutritional_value_id',
@@ -1359,7 +1359,7 @@ abstract class _$$DailyScheduleImplCopyWith<$Res>
{@JsonKey(name: "name") String name,
@JsonKey(name: "portion") Portion portion,
@JsonKey(name: "quantity", fromJson: _parseQuantity) int quantity,
@JsonKey(includeToJson: false, includeFromJson: false)
@JsonKey(name: 'nutrition', includeToJson: false)
DailyNutrition? dailyNutrition,
@JsonKey(
name: 'product_nutritional_value_id',
@@ -1424,8 +1424,7 @@ class _$DailyScheduleImpl implements _DailySchedule {
@JsonKey(name: "portion") required this.portion,
@JsonKey(name: "quantity", fromJson: _parseQuantity)
required this.quantity,
@JsonKey(includeToJson: false, includeFromJson: false)
this.dailyNutrition,
@JsonKey(name: 'nutrition', includeToJson: false) this.dailyNutrition,
@JsonKey(
name: 'product_nutritional_value_id',
includeToJson: true,
@@ -1446,7 +1445,7 @@ class _$DailyScheduleImpl implements _DailySchedule {
@JsonKey(name: "quantity", fromJson: _parseQuantity)
final int quantity;
@override
@JsonKey(includeToJson: false, includeFromJson: false)
@JsonKey(name: 'nutrition', includeToJson: false)
final DailyNutrition? dailyNutrition;
@override
@JsonKey(
@@ -1504,7 +1503,7 @@ abstract class _DailySchedule implements DailySchedule {
@JsonKey(name: "portion") required final Portion portion,
@JsonKey(name: "quantity", fromJson: _parseQuantity)
required final int quantity,
@JsonKey(includeToJson: false, includeFromJson: false)
@JsonKey(name: 'nutrition', includeToJson: false)
final DailyNutrition? dailyNutrition,
@JsonKey(
name: 'product_nutritional_value_id',
@@ -1526,7 +1525,7 @@ abstract class _DailySchedule implements DailySchedule {
@JsonKey(name: "quantity", fromJson: _parseQuantity)
int get quantity;
@override
@JsonKey(includeToJson: false, includeFromJson: false)
@JsonKey(name: 'nutrition', includeToJson: false)
DailyNutrition? get dailyNutrition;
@override
@JsonKey(

View File

@@ -117,6 +117,9 @@ _$DailyScheduleImpl _$$DailyScheduleImplFromJson(Map<String, dynamic> json) =>
name: json['name'] as String,
portion: $enumDecode(_$PortionEnumMap, json['portion']),
quantity: _parseQuantity(json['quantity']),
dailyNutrition: json['nutrition'] == null
? null
: DailyNutrition.fromJson(json['nutrition'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$DailyScheduleImplToJson(_$DailyScheduleImpl instance) =>

View File

@@ -5,12 +5,13 @@ part 'recipes_model.freezed.dart';
part 'recipes_model.g.dart';
@freezed
class Recipes with _$Recipes {
const factory Recipes({
class Recipe with _$Recipe {
const factory Recipe({
required int id,
required String image,
required String state,
@JsonKey(name: 'cooking_time') required int cookingTime,
@JsonKey(name: 'active_time') required int activeCookingTime,
required List<Ingredient>? ingredients,
required String description,
required List<Step>? steps,
@@ -22,13 +23,14 @@ class Recipes with _$Recipes {
required String slug,
@JsonKey(name: 'total_comments') required int commentsCount,
@JsonKey(name: 'was_liked_by_me') required bool wasLikedByMe,
@JsonKey(name: 'is_favorite') required bool isFavorite,
@JsonKey(name: 'energy_per_serving') required double calories,
@JsonKey(name: 'total_likes') required int likesCount,
@JsonKey(name: 'total_views') required int viewsCount,
required Author author,
}) = _Recipes;
factory Recipes.fromJson(Map<String, Object?> json) => _$RecipesFromJson(json);
factory Recipe.fromJson(Map<String, Object?> json) => _$RecipeFromJson(json);
}
@freezed

View File

@@ -14,17 +14,19 @@ T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
Recipes _$RecipesFromJson(Map<String, dynamic> json) {
Recipe _$RecipeFromJson(Map<String, dynamic> json) {
return _Recipes.fromJson(json);
}
/// @nodoc
mixin _$Recipes {
mixin _$Recipe {
int get id => throw _privateConstructorUsedError;
String get image => throw _privateConstructorUsedError;
String get state => throw _privateConstructorUsedError;
@JsonKey(name: 'cooking_time')
int get cookingTime => throw _privateConstructorUsedError;
@JsonKey(name: 'active_time')
int get activeCookingTime => throw _privateConstructorUsedError;
List<Ingredient>? get ingredients => throw _privateConstructorUsedError;
String get description => throw _privateConstructorUsedError;
List<Step>? get steps => throw _privateConstructorUsedError;
@@ -41,6 +43,8 @@ mixin _$Recipes {
int get commentsCount => throw _privateConstructorUsedError;
@JsonKey(name: 'was_liked_by_me')
bool get wasLikedByMe => throw _privateConstructorUsedError;
@JsonKey(name: 'is_favorite')
bool get isFavorite => throw _privateConstructorUsedError;
@JsonKey(name: 'energy_per_serving')
double get calories => throw _privateConstructorUsedError;
@JsonKey(name: 'total_likes')
@@ -49,25 +53,26 @@ mixin _$Recipes {
int get viewsCount => throw _privateConstructorUsedError;
Author get author => throw _privateConstructorUsedError;
/// Serializes this Recipes to a JSON map.
/// Serializes this Recipe to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of Recipes
/// Create a copy of Recipe
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$RecipesCopyWith<Recipes> get copyWith => throw _privateConstructorUsedError;
$RecipeCopyWith<Recipe> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $RecipesCopyWith<$Res> {
factory $RecipesCopyWith(Recipes value, $Res Function(Recipes) then) =
_$RecipesCopyWithImpl<$Res, Recipes>;
abstract class $RecipeCopyWith<$Res> {
factory $RecipeCopyWith(Recipe value, $Res Function(Recipe) then) =
_$RecipeCopyWithImpl<$Res, Recipe>;
@useResult
$Res call(
{int id,
String image,
String state,
@JsonKey(name: 'cooking_time') int cookingTime,
@JsonKey(name: 'active_time') int activeCookingTime,
List<Ingredient>? ingredients,
String description,
List<Step>? steps,
@@ -79,6 +84,7 @@ abstract class $RecipesCopyWith<$Res> {
String slug,
@JsonKey(name: 'total_comments') int commentsCount,
@JsonKey(name: 'was_liked_by_me') bool wasLikedByMe,
@JsonKey(name: 'is_favorite') bool isFavorite,
@JsonKey(name: 'energy_per_serving') double calories,
@JsonKey(name: 'total_likes') int likesCount,
@JsonKey(name: 'total_views') int viewsCount,
@@ -88,16 +94,16 @@ abstract class $RecipesCopyWith<$Res> {
}
/// @nodoc
class _$RecipesCopyWithImpl<$Res, $Val extends Recipes>
implements $RecipesCopyWith<$Res> {
_$RecipesCopyWithImpl(this._value, this._then);
class _$RecipeCopyWithImpl<$Res, $Val extends Recipe>
implements $RecipeCopyWith<$Res> {
_$RecipeCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Recipes
/// Create a copy of Recipe
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
@@ -106,6 +112,7 @@ class _$RecipesCopyWithImpl<$Res, $Val extends Recipes>
Object? image = null,
Object? state = null,
Object? cookingTime = null,
Object? activeCookingTime = null,
Object? ingredients = freezed,
Object? description = null,
Object? steps = freezed,
@@ -117,6 +124,7 @@ class _$RecipesCopyWithImpl<$Res, $Val extends Recipes>
Object? slug = null,
Object? commentsCount = null,
Object? wasLikedByMe = null,
Object? isFavorite = null,
Object? calories = null,
Object? likesCount = null,
Object? viewsCount = null,
@@ -139,6 +147,10 @@ class _$RecipesCopyWithImpl<$Res, $Val extends Recipes>
? _value.cookingTime
: cookingTime // ignore: cast_nullable_to_non_nullable
as int,
activeCookingTime: null == activeCookingTime
? _value.activeCookingTime
: activeCookingTime // ignore: cast_nullable_to_non_nullable
as int,
ingredients: freezed == ingredients
? _value.ingredients
: ingredients // ignore: cast_nullable_to_non_nullable
@@ -183,6 +195,10 @@ class _$RecipesCopyWithImpl<$Res, $Val extends Recipes>
? _value.wasLikedByMe
: wasLikedByMe // ignore: cast_nullable_to_non_nullable
as bool,
isFavorite: null == isFavorite
? _value.isFavorite
: isFavorite // ignore: cast_nullable_to_non_nullable
as bool,
calories: null == calories
? _value.calories
: calories // ignore: cast_nullable_to_non_nullable
@@ -202,7 +218,7 @@ class _$RecipesCopyWithImpl<$Res, $Val extends Recipes>
) as $Val);
}
/// Create a copy of Recipes
/// Create a copy of Recipe
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
@@ -214,7 +230,7 @@ class _$RecipesCopyWithImpl<$Res, $Val extends Recipes>
}
/// @nodoc
abstract class _$$RecipesImplCopyWith<$Res> implements $RecipesCopyWith<$Res> {
abstract class _$$RecipesImplCopyWith<$Res> implements $RecipeCopyWith<$Res> {
factory _$$RecipesImplCopyWith(
_$RecipesImpl value, $Res Function(_$RecipesImpl) then) =
__$$RecipesImplCopyWithImpl<$Res>;
@@ -225,6 +241,7 @@ abstract class _$$RecipesImplCopyWith<$Res> implements $RecipesCopyWith<$Res> {
String image,
String state,
@JsonKey(name: 'cooking_time') int cookingTime,
@JsonKey(name: 'active_time') int activeCookingTime,
List<Ingredient>? ingredients,
String description,
List<Step>? steps,
@@ -236,6 +253,7 @@ abstract class _$$RecipesImplCopyWith<$Res> implements $RecipesCopyWith<$Res> {
String slug,
@JsonKey(name: 'total_comments') int commentsCount,
@JsonKey(name: 'was_liked_by_me') bool wasLikedByMe,
@JsonKey(name: 'is_favorite') bool isFavorite,
@JsonKey(name: 'energy_per_serving') double calories,
@JsonKey(name: 'total_likes') int likesCount,
@JsonKey(name: 'total_views') int viewsCount,
@@ -247,13 +265,13 @@ abstract class _$$RecipesImplCopyWith<$Res> implements $RecipesCopyWith<$Res> {
/// @nodoc
class __$$RecipesImplCopyWithImpl<$Res>
extends _$RecipesCopyWithImpl<$Res, _$RecipesImpl>
extends _$RecipeCopyWithImpl<$Res, _$RecipesImpl>
implements _$$RecipesImplCopyWith<$Res> {
__$$RecipesImplCopyWithImpl(
_$RecipesImpl _value, $Res Function(_$RecipesImpl) _then)
: super(_value, _then);
/// Create a copy of Recipes
/// Create a copy of Recipe
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
@@ -262,6 +280,7 @@ class __$$RecipesImplCopyWithImpl<$Res>
Object? image = null,
Object? state = null,
Object? cookingTime = null,
Object? activeCookingTime = null,
Object? ingredients = freezed,
Object? description = null,
Object? steps = freezed,
@@ -273,6 +292,7 @@ class __$$RecipesImplCopyWithImpl<$Res>
Object? slug = null,
Object? commentsCount = null,
Object? wasLikedByMe = null,
Object? isFavorite = null,
Object? calories = null,
Object? likesCount = null,
Object? viewsCount = null,
@@ -295,6 +315,10 @@ class __$$RecipesImplCopyWithImpl<$Res>
? _value.cookingTime
: cookingTime // ignore: cast_nullable_to_non_nullable
as int,
activeCookingTime: null == activeCookingTime
? _value.activeCookingTime
: activeCookingTime // ignore: cast_nullable_to_non_nullable
as int,
ingredients: freezed == ingredients
? _value._ingredients
: ingredients // ignore: cast_nullable_to_non_nullable
@@ -339,6 +363,10 @@ class __$$RecipesImplCopyWithImpl<$Res>
? _value.wasLikedByMe
: wasLikedByMe // ignore: cast_nullable_to_non_nullable
as bool,
isFavorite: null == isFavorite
? _value.isFavorite
: isFavorite // ignore: cast_nullable_to_non_nullable
as bool,
calories: null == calories
? _value.calories
: calories // ignore: cast_nullable_to_non_nullable
@@ -367,6 +395,7 @@ class _$RecipesImpl implements _Recipes {
required this.image,
required this.state,
@JsonKey(name: 'cooking_time') required this.cookingTime,
@JsonKey(name: 'active_time') required this.activeCookingTime,
required final List<Ingredient>? ingredients,
required this.description,
required final List<Step>? steps,
@@ -378,6 +407,7 @@ class _$RecipesImpl implements _Recipes {
required this.slug,
@JsonKey(name: 'total_comments') required this.commentsCount,
@JsonKey(name: 'was_liked_by_me') required this.wasLikedByMe,
@JsonKey(name: 'is_favorite') required this.isFavorite,
@JsonKey(name: 'energy_per_serving') required this.calories,
@JsonKey(name: 'total_likes') required this.likesCount,
@JsonKey(name: 'total_views') required this.viewsCount,
@@ -397,6 +427,9 @@ class _$RecipesImpl implements _Recipes {
@override
@JsonKey(name: 'cooking_time')
final int cookingTime;
@override
@JsonKey(name: 'active_time')
final int activeCookingTime;
final List<Ingredient>? _ingredients;
@override
List<Ingredient>? get ingredients {
@@ -441,6 +474,9 @@ class _$RecipesImpl implements _Recipes {
@JsonKey(name: 'was_liked_by_me')
final bool wasLikedByMe;
@override
@JsonKey(name: 'is_favorite')
final bool isFavorite;
@override
@JsonKey(name: 'energy_per_serving')
final double calories;
@override
@@ -454,7 +490,7 @@ class _$RecipesImpl implements _Recipes {
@override
String toString() {
return 'Recipes(id: $id, image: $image, state: $state, cookingTime: $cookingTime, ingredients: $ingredients, description: $description, steps: $steps, videoUrl: $videoUrl, createdAt: $createdAt, updatedAt: $updatedAt, title: $title, type: $type, slug: $slug, commentsCount: $commentsCount, wasLikedByMe: $wasLikedByMe, calories: $calories, likesCount: $likesCount, viewsCount: $viewsCount, author: $author)';
return 'Recipe(id: $id, image: $image, state: $state, cookingTime: $cookingTime, activeCookingTime: $activeCookingTime, ingredients: $ingredients, description: $description, steps: $steps, videoUrl: $videoUrl, createdAt: $createdAt, updatedAt: $updatedAt, title: $title, type: $type, slug: $slug, commentsCount: $commentsCount, wasLikedByMe: $wasLikedByMe, isFavorite: $isFavorite, calories: $calories, likesCount: $likesCount, viewsCount: $viewsCount, author: $author)';
}
@override
@@ -467,6 +503,8 @@ class _$RecipesImpl implements _Recipes {
(identical(other.state, state) || other.state == state) &&
(identical(other.cookingTime, cookingTime) ||
other.cookingTime == cookingTime) &&
(identical(other.activeCookingTime, activeCookingTime) ||
other.activeCookingTime == activeCookingTime) &&
const DeepCollectionEquality()
.equals(other._ingredients, _ingredients) &&
(identical(other.description, description) ||
@@ -485,6 +523,8 @@ class _$RecipesImpl implements _Recipes {
other.commentsCount == commentsCount) &&
(identical(other.wasLikedByMe, wasLikedByMe) ||
other.wasLikedByMe == wasLikedByMe) &&
(identical(other.isFavorite, isFavorite) ||
other.isFavorite == isFavorite) &&
(identical(other.calories, calories) ||
other.calories == calories) &&
(identical(other.likesCount, likesCount) ||
@@ -502,6 +542,7 @@ class _$RecipesImpl implements _Recipes {
image,
state,
cookingTime,
activeCookingTime,
const DeepCollectionEquality().hash(_ingredients),
description,
const DeepCollectionEquality().hash(_steps),
@@ -513,13 +554,14 @@ class _$RecipesImpl implements _Recipes {
slug,
commentsCount,
wasLikedByMe,
isFavorite,
calories,
likesCount,
viewsCount,
author
]);
/// Create a copy of Recipes
/// Create a copy of Recipe
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@@ -535,12 +577,13 @@ class _$RecipesImpl implements _Recipes {
}
}
abstract class _Recipes implements Recipes {
abstract class _Recipes implements Recipe {
const factory _Recipes(
{required final int id,
required final String image,
required final String state,
@JsonKey(name: 'cooking_time') required final int cookingTime,
@JsonKey(name: 'active_time') required final int activeCookingTime,
required final List<Ingredient>? ingredients,
required final String description,
required final List<Step>? steps,
@@ -552,6 +595,7 @@ abstract class _Recipes implements Recipes {
required final String slug,
@JsonKey(name: 'total_comments') required final int commentsCount,
@JsonKey(name: 'was_liked_by_me') required final bool wasLikedByMe,
@JsonKey(name: 'is_favorite') required final bool isFavorite,
@JsonKey(name: 'energy_per_serving') required final double calories,
@JsonKey(name: 'total_likes') required final int likesCount,
@JsonKey(name: 'total_views') required final int viewsCount,
@@ -569,6 +613,9 @@ abstract class _Recipes implements Recipes {
@JsonKey(name: 'cooking_time')
int get cookingTime;
@override
@JsonKey(name: 'active_time')
int get activeCookingTime;
@override
List<Ingredient>? get ingredients;
@override
String get description;
@@ -596,6 +643,9 @@ abstract class _Recipes implements Recipes {
@JsonKey(name: 'was_liked_by_me')
bool get wasLikedByMe;
@override
@JsonKey(name: 'is_favorite')
bool get isFavorite;
@override
@JsonKey(name: 'energy_per_serving')
double get calories;
@override
@@ -607,7 +657,7 @@ abstract class _Recipes implements Recipes {
@override
Author get author;
/// Create a copy of Recipes
/// Create a copy of Recipe
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)

View File

@@ -12,6 +12,7 @@ _$RecipesImpl _$$RecipesImplFromJson(Map<String, dynamic> json) =>
image: json['image'] as String,
state: json['state'] as String,
cookingTime: (json['cooking_time'] as num).toInt(),
activeCookingTime: (json['active_time'] as num).toInt(),
ingredients: (json['ingredients'] as List<dynamic>?)
?.map((e) => Ingredient.fromJson(e as Map<String, dynamic>))
.toList(),
@@ -27,6 +28,7 @@ _$RecipesImpl _$$RecipesImplFromJson(Map<String, dynamic> json) =>
slug: json['slug'] as String,
commentsCount: (json['total_comments'] as num).toInt(),
wasLikedByMe: json['was_liked_by_me'] as bool,
isFavorite: json['is_favorite'] as bool,
calories: (json['energy_per_serving'] as num).toDouble(),
likesCount: (json['total_likes'] as num).toInt(),
viewsCount: (json['total_views'] as num).toInt(),
@@ -39,6 +41,7 @@ Map<String, dynamic> _$$RecipesImplToJson(_$RecipesImpl instance) =>
'image': instance.image,
'state': instance.state,
'cooking_time': instance.cookingTime,
'active_time': instance.activeCookingTime,
'ingredients': instance.ingredients,
'description': instance.description,
'steps': instance.steps,
@@ -50,6 +53,7 @@ Map<String, dynamic> _$$RecipesImplToJson(_$RecipesImpl instance) =>
'slug': instance.slug,
'total_comments': instance.commentsCount,
'was_liked_by_me': instance.wasLikedByMe,
'is_favorite': instance.isFavorite,
'energy_per_serving': instance.calories,
'total_likes': instance.likesCount,
'total_views': instance.viewsCount,

View File

@@ -8,7 +8,7 @@ part 'recipes_pagination_model.g.dart';
@freezed
class RecipesPagination with _$RecipesPagination {
const factory RecipesPagination({
@JsonKey(name: 'data') required List<Recipes> recipesList,
@JsonKey(name: 'data') required List<Recipe> recipesList,
// @JsonKey(name: "current_page") required int currentPage,
// @JsonKey(name: "first_page_url") required String firstPageUrl,
// required int from,

View File

@@ -21,7 +21,7 @@ RecipesPagination _$RecipesPaginationFromJson(Map<String, dynamic> json) {
/// @nodoc
mixin _$RecipesPagination {
@JsonKey(name: 'data')
List<Recipes> get recipesList => throw _privateConstructorUsedError;
List<Recipe> get recipesList => throw _privateConstructorUsedError;
/// Serializes this RecipesPagination to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -39,7 +39,7 @@ abstract class $RecipesPaginationCopyWith<$Res> {
RecipesPagination value, $Res Function(RecipesPagination) then) =
_$RecipesPaginationCopyWithImpl<$Res, RecipesPagination>;
@useResult
$Res call({@JsonKey(name: 'data') List<Recipes> recipesList});
$Res call({@JsonKey(name: 'data') List<Recipe> recipesList});
}
/// @nodoc
@@ -63,7 +63,7 @@ class _$RecipesPaginationCopyWithImpl<$Res, $Val extends RecipesPagination>
recipesList: null == recipesList
? _value.recipesList
: recipesList // ignore: cast_nullable_to_non_nullable
as List<Recipes>,
as List<Recipe>,
) as $Val);
}
}
@@ -76,7 +76,7 @@ abstract class _$$RecipesPaginationImplCopyWith<$Res>
__$$RecipesPaginationImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({@JsonKey(name: 'data') List<Recipes> recipesList});
$Res call({@JsonKey(name: 'data') List<Recipe> recipesList});
}
/// @nodoc
@@ -98,7 +98,7 @@ class __$$RecipesPaginationImplCopyWithImpl<$Res>
recipesList: null == recipesList
? _value._recipesList
: recipesList // ignore: cast_nullable_to_non_nullable
as List<Recipes>,
as List<Recipe>,
));
}
}
@@ -107,16 +107,16 @@ class __$$RecipesPaginationImplCopyWithImpl<$Res>
@JsonSerializable()
class _$RecipesPaginationImpl implements _RecipesPagination {
const _$RecipesPaginationImpl(
{@JsonKey(name: 'data') required final List<Recipes> recipesList})
{@JsonKey(name: 'data') required final List<Recipe> recipesList})
: _recipesList = recipesList;
factory _$RecipesPaginationImpl.fromJson(Map<String, dynamic> json) =>
_$$RecipesPaginationImplFromJson(json);
final List<Recipes> _recipesList;
final List<Recipe> _recipesList;
@override
@JsonKey(name: 'data')
List<Recipes> get recipesList {
List<Recipe> get recipesList {
if (_recipesList is EqualUnmodifiableListView) return _recipesList;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_recipesList);
@@ -160,7 +160,7 @@ class _$RecipesPaginationImpl implements _RecipesPagination {
abstract class _RecipesPagination implements RecipesPagination {
const factory _RecipesPagination(
{@JsonKey(name: 'data') required final List<Recipes> recipesList}) =
{@JsonKey(name: 'data') required final List<Recipe> recipesList}) =
_$RecipesPaginationImpl;
factory _RecipesPagination.fromJson(Map<String, dynamic> json) =
@@ -168,7 +168,7 @@ abstract class _RecipesPagination implements RecipesPagination {
@override
@JsonKey(name: 'data')
List<Recipes> get recipesList;
List<Recipe> get recipesList;
/// Create a copy of RecipesPagination
/// with the given fields replaced by the non-null parameter values.

View File

@@ -10,7 +10,7 @@ _$RecipesPaginationImpl _$$RecipesPaginationImplFromJson(
Map<String, dynamic> json) =>
_$RecipesPaginationImpl(
recipesList: (json['data'] as List<dynamic>)
.map((e) => Recipes.fromJson(e as Map<String, dynamic>))
.map((e) => Recipe.fromJson(e as Map<String, dynamic>))
.toList(),
);

View File

@@ -1,23 +1,46 @@
import 'dart:async';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_state.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:eatvio_app/features/recipes/models/recipe_tag_model.dart';
import 'package:eatvio_app/features/recipes/reporitories/recipe_repository.dart';
import 'package:eatvio_app/features/create_recipe/models/recipe_tag_model.dart';
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'dart:developer';
import 'package:go_router/go_router.dart';
class RecipesCubit extends Cubit<RecipesState> {
final FeedRepository feedRepository;
final RecipeRepository recipeRepository;
final AboutRecipeRepository aboutRecipeRepository;
Set<int> _selectedTags = {};
int currentPage = 1;
late int lastPage;
bool isLoading = false;
String? _query;
late final StreamSubscription likesStateSubscription;
late final StreamSubscription favoriteStateSubscription;
late final StreamSubscription commentsStateSubscription;
RecipesCubit({
required this.feedRepository,
required this.recipeRepository,
}) : super(const RecipesState.loading());
required this.aboutRecipeRepository,
}) : super(RecipesState.initial()) {
likesStateSubscription = aboutRecipeRepository.recipeLikeState.listen((likesState) {
_changeRecipeLikeState(likesState);
});
favoriteStateSubscription = aboutRecipeRepository.recipeFavoriteState.listen((favoriteState) {
_changeRecipeFavoriteState(favoriteState);
});
commentsStateSubscription = aboutRecipeRepository.recipeCommentsState.listen((commentsState) {
_changeRecipeCommentsState(commentsState);
});
}
loadRecipesList({
String? query,
@@ -28,9 +51,10 @@ class RecipesCubit extends Cubit<RecipesState> {
tags: _selectedTags.toList(),
query: query,
);
final recipesTags = (await recipeRepository.getRecipesTags());
emit(
RecipesState.loaded(
state.copyWith(
recipesList: recipesList,
recipesTags: recipesTags,
selectedTags: _selectedTags,
@@ -41,7 +65,7 @@ class RecipesCubit extends Cubit<RecipesState> {
lastPage = currentPage;
} catch (e, st) {
log(e.toString(), stackTrace: st);
emit(RecipesState.error(errorMessage: e.toString()));
emit(state.copyWith(errorMessage: e.toString()));
}
}
@@ -49,7 +73,7 @@ class RecipesCubit extends Cubit<RecipesState> {
if (currentPage < lastPage && !isLoading) {
isLoading = true;
emit((state as RecipesStateLoaded).copyWith(isLoading: isLoading));
emit(state.copyWith(isLoading: isLoading));
try {
if (currentPage < lastPage) {
@@ -61,9 +85,9 @@ class RecipesCubit extends Cubit<RecipesState> {
);
isLoading = false;
emit(
(state as RecipesStateLoaded).copyWith(
state.copyWith(
recipesList: [
...(state as RecipesStateLoaded).recipesList,
...state.recipesList,
...recipesList,
],
isLoading: isLoading,
@@ -72,7 +96,7 @@ class RecipesCubit extends Cubit<RecipesState> {
}
lastPage = currentPage;
} catch (e) {
emit(RecipesState.error(errorMessage: e.toString()));
emit(state.copyWith(errorMessage: e.toString()));
}
}
}
@@ -80,14 +104,202 @@ class RecipesCubit extends Cubit<RecipesState> {
updateTags(RecipeTag tag) {
_selectedTags.contains(tag.id) ? _selectedTags.remove(tag.id) : _selectedTags.add(tag.id);
emit(
(state as RecipesStateLoaded).copyWith(
state.copyWith(
selectedTags: _selectedTags,
selectedTagsCounter: _selectedTags.length,
),
);
}
likeRecipe(String slug) async => await feedRepository.likeRecipe(slug: slug);
Future<void> addRecipeToFavorite(String slug, int index) async {
final currentRecipes = state.recipesList;
final recipesForModification = [...currentRecipes];
final currentRecipe = currentRecipes[index];
unlikeRecipe(String slug) async => await feedRepository.unlikeRecipe(slug: slug);
recipesForModification[index] = currentRecipe.copyWith(
isFavorite: true,
);
emit(
state.copyWith(
recipesList: [
...recipesForModification,
],
isLoading: isLoading,
errorMessage: null,
),
);
EasyDebounce.debounce(
'add_or_remove_plan_from_favorite',
Duration(milliseconds: 200),
() async {
try {
await aboutRecipeRepository.addToFavorite(slug);
} catch (e) {
log(e.toString());
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
emit(
state.copyWith(
recipesList: [
...currentRecipes,
],
isLoading: isLoading,
errorMessage: null,
),
);
}
},
);
}
Future<void> removeRecipeFromFavorite(String slug, int index) async {
final currentRecipes = state.recipesList;
final recipesForModification = [...currentRecipes];
final currentRecipe = currentRecipes[index];
recipesForModification[index] = currentRecipe.copyWith(
isFavorite: false,
);
emit(
state.copyWith(
recipesList: [
...recipesForModification,
],
isLoading: isLoading,
errorMessage: null,
),
);
EasyDebounce.debounce(
'add_or_remove_plan_from_favorite',
Duration(milliseconds: 200),
() async {
try {
await aboutRecipeRepository.removeFromFavorite(slug);
} catch (e) {
log(e.toString());
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
emit(
state.copyWith(
recipesList: [
...currentRecipes,
],
isLoading: isLoading,
errorMessage: null,
),
);
}
},
);
}
Future<void> likeRecipe(String slug, int index) async {
final currentRecipes = state.recipesList;
final recipesForModification = [...currentRecipes];
final currentRecipe = currentRecipes[index];
if (currentRecipe.wasLikedByMe) {
recipesForModification[index] = currentRecipe.copyWith(
likesCount: currentRecipe.likesCount - 1,
wasLikedByMe: false,
);
emit(
state.copyWith(
recipesList: [
...recipesForModification,
],
isLoading: isLoading,
errorMessage: null,
),
);
} else {
recipesForModification[index] = currentRecipe.copyWith(
likesCount: currentRecipe.likesCount + 1,
wasLikedByMe: true,
);
emit(
state.copyWith(
recipesList: [
...recipesForModification,
],
isLoading: isLoading,
errorMessage: null,
),
);
}
EasyDebounce.debounce(
'like_plan',
Duration(milliseconds: 200),
() async {
try {
await aboutRecipeRepository.likeRecipe(slug);
} catch (e) {
log(e.toString());
emit(
state.copyWith(
recipesList: [
...currentRecipes,
],
isLoading: isLoading,
),
);
}
},
);
}
void _changeRecipeLikeState(LikesState likesState) {
final currentRecipes = state.recipesList;
final recipesForModification = [...currentRecipes];
final currentRecipe = currentRecipes[likesState.index];
recipesForModification[likesState.index] = currentRecipe.copyWith(
likesCount: likesState.totalLikes,
wasLikedByMe: likesState.wasLikedByMe,
);
emit(
state.copyWith(
recipesList: [
...recipesForModification,
],
isLoading: isLoading,
errorMessage: null,
),
);
}
void _changeRecipeFavoriteState(FavoriteState favoriteState) {
final currentRecipes = state.recipesList;
final recipesForModification = [...currentRecipes];
final currentRecipe = currentRecipes[favoriteState.index];
recipesForModification[favoriteState.index] = currentRecipe.copyWith(
isFavorite: favoriteState.isFavorite,
);
emit(
state.copyWith(
recipesList: [
...recipesForModification,
],
isLoading: isLoading,
errorMessage: null,
),
);
}
void _changeRecipeCommentsState(CommentsState commentsState) {
final currentRecipes = state.recipesList;
final recipesForModification = [...currentRecipes];
final currentRecipe = currentRecipes[commentsState.index];
recipesForModification[commentsState.index] = currentRecipe.copyWith(
commentsCount: commentsState.totalComments,
);
emit(
state.copyWith(
recipesList: [
...recipesForModification,
],
isLoading: isLoading,
errorMessage: null,
),
);
}
}

View File

@@ -1,24 +1,26 @@
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/features/recipes/models/recipe_tag_model.dart';
import 'package:eatvio_app/features/create_recipe/models/recipe_tag_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'recipes_state.freezed.dart';
@freezed
abstract class RecipesState with _$RecipesState {
const factory RecipesState() = _RecipesState;
const factory RecipesState.loading() = RecipesStateLoading;
const factory RecipesState.loaded({
required List<Recipes> recipesList,
const factory RecipesState({
required List<Recipe> recipesList,
required List<RecipeTag> recipesTags,
required Set<int> selectedTags,
required int selectedTagsCounter,
required bool isLoading,
}) = RecipesStateLoaded;
String? errorMessage,
}) = _RecipesState;
const factory RecipesState.error({
required String errorMessage,
}) = RecipesStateError;
factory RecipesState.initial() => RecipesState(
recipesList: [],
recipesTags: [],
selectedTags: {},
selectedTagsCounter: 0,
isLoading: true,
errorMessage: null,
);
}

View File

@@ -16,65 +16,17 @@ final _privateConstructorUsedError = UnsupportedError(
/// @nodoc
mixin _$RecipesState {
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(
List<Recipes> recipesList,
List<RecipeTag> recipesTags,
Set<int> selectedTags,
int selectedTagsCounter,
bool isLoading)
loaded,
required TResult Function(String errorMessage) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(List<Recipes> recipesList, List<RecipeTag> recipesTags,
Set<int> selectedTags, int selectedTagsCounter, bool isLoading)?
loaded,
TResult? Function(String errorMessage)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(List<Recipes> recipesList, List<RecipeTag> recipesTags,
Set<int> selectedTags, int selectedTagsCounter, bool isLoading)?
loaded,
TResult Function(String errorMessage)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_RecipesState value) $default, {
required TResult Function(RecipesStateLoading value) loading,
required TResult Function(RecipesStateLoaded value) loaded,
required TResult Function(RecipesStateError value) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_RecipesState value)? $default, {
TResult? Function(RecipesStateLoading value)? loading,
TResult? Function(RecipesStateLoaded value)? loaded,
TResult? Function(RecipesStateError value)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_RecipesState value)? $default, {
TResult Function(RecipesStateLoading value)? loading,
TResult Function(RecipesStateLoaded value)? loaded,
TResult Function(RecipesStateError value)? error,
required TResult orElse(),
}) =>
List<Recipe> get recipesList => throw _privateConstructorUsedError;
List<RecipeTag> get recipesTags => throw _privateConstructorUsedError;
Set<int> get selectedTags => throw _privateConstructorUsedError;
int get selectedTagsCounter => throw _privateConstructorUsedError;
bool get isLoading => throw _privateConstructorUsedError;
String? get errorMessage => throw _privateConstructorUsedError;
/// Create a copy of RecipesState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$RecipesStateCopyWith<RecipesState> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -83,6 +35,14 @@ abstract class $RecipesStateCopyWith<$Res> {
factory $RecipesStateCopyWith(
RecipesState value, $Res Function(RecipesState) then) =
_$RecipesStateCopyWithImpl<$Res, RecipesState>;
@useResult
$Res call(
{List<Recipe> recipesList,
List<RecipeTag> recipesTags,
Set<int> selectedTags,
int selectedTagsCounter,
bool isLoading,
String? errorMessage});
}
/// @nodoc
@@ -97,13 +57,60 @@ class _$RecipesStateCopyWithImpl<$Res, $Val extends RecipesState>
/// Create a copy of RecipesState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? recipesList = null,
Object? recipesTags = null,
Object? selectedTags = null,
Object? selectedTagsCounter = null,
Object? isLoading = null,
Object? errorMessage = freezed,
}) {
return _then(_value.copyWith(
recipesList: null == recipesList
? _value.recipesList
: recipesList // ignore: cast_nullable_to_non_nullable
as List<Recipe>,
recipesTags: null == recipesTags
? _value.recipesTags
: recipesTags // ignore: cast_nullable_to_non_nullable
as List<RecipeTag>,
selectedTags: null == selectedTags
? _value.selectedTags
: selectedTags // ignore: cast_nullable_to_non_nullable
as Set<int>,
selectedTagsCounter: null == selectedTagsCounter
? _value.selectedTagsCounter
: selectedTagsCounter // ignore: cast_nullable_to_non_nullable
as int,
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?,
) as $Val);
}
}
/// @nodoc
abstract class _$$RecipesStateImplCopyWith<$Res> {
abstract class _$$RecipesStateImplCopyWith<$Res>
implements $RecipesStateCopyWith<$Res> {
factory _$$RecipesStateImplCopyWith(
_$RecipesStateImpl value, $Res Function(_$RecipesStateImpl) then) =
__$$RecipesStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{List<Recipe> recipesList,
List<RecipeTag> recipesTags,
Set<int> selectedTags,
int selectedTagsCounter,
bool isLoading,
String? errorMessage});
}
/// @nodoc
@@ -116,268 +123,6 @@ class __$$RecipesStateImplCopyWithImpl<$Res>
/// Create a copy of RecipesState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$RecipesStateImpl implements _RecipesState {
const _$RecipesStateImpl();
@override
String toString() {
return 'RecipesState()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$RecipesStateImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(
List<Recipes> recipesList,
List<RecipeTag> recipesTags,
Set<int> selectedTags,
int selectedTagsCounter,
bool isLoading)
loaded,
required TResult Function(String errorMessage) error,
}) {
return $default();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(List<Recipes> recipesList, List<RecipeTag> recipesTags,
Set<int> selectedTags, int selectedTagsCounter, bool isLoading)?
loaded,
TResult? Function(String errorMessage)? error,
}) {
return $default?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(List<Recipes> recipesList, List<RecipeTag> recipesTags,
Set<int> selectedTags, int selectedTagsCounter, bool isLoading)?
loaded,
TResult Function(String errorMessage)? error,
required TResult orElse(),
}) {
if ($default != null) {
return $default();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_RecipesState value) $default, {
required TResult Function(RecipesStateLoading value) loading,
required TResult Function(RecipesStateLoaded value) loaded,
required TResult Function(RecipesStateError value) error,
}) {
return $default(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_RecipesState value)? $default, {
TResult? Function(RecipesStateLoading value)? loading,
TResult? Function(RecipesStateLoaded value)? loaded,
TResult? Function(RecipesStateError value)? error,
}) {
return $default?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_RecipesState value)? $default, {
TResult Function(RecipesStateLoading value)? loading,
TResult Function(RecipesStateLoaded value)? loaded,
TResult Function(RecipesStateError value)? error,
required TResult orElse(),
}) {
if ($default != null) {
return $default(this);
}
return orElse();
}
}
abstract class _RecipesState implements RecipesState {
const factory _RecipesState() = _$RecipesStateImpl;
}
/// @nodoc
abstract class _$$RecipesStateLoadingImplCopyWith<$Res> {
factory _$$RecipesStateLoadingImplCopyWith(_$RecipesStateLoadingImpl value,
$Res Function(_$RecipesStateLoadingImpl) then) =
__$$RecipesStateLoadingImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$RecipesStateLoadingImplCopyWithImpl<$Res>
extends _$RecipesStateCopyWithImpl<$Res, _$RecipesStateLoadingImpl>
implements _$$RecipesStateLoadingImplCopyWith<$Res> {
__$$RecipesStateLoadingImplCopyWithImpl(_$RecipesStateLoadingImpl _value,
$Res Function(_$RecipesStateLoadingImpl) _then)
: super(_value, _then);
/// Create a copy of RecipesState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$RecipesStateLoadingImpl implements RecipesStateLoading {
const _$RecipesStateLoadingImpl();
@override
String toString() {
return 'RecipesState.loading()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$RecipesStateLoadingImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(
List<Recipes> recipesList,
List<RecipeTag> recipesTags,
Set<int> selectedTags,
int selectedTagsCounter,
bool isLoading)
loaded,
required TResult Function(String errorMessage) error,
}) {
return loading();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(List<Recipes> recipesList, List<RecipeTag> recipesTags,
Set<int> selectedTags, int selectedTagsCounter, bool isLoading)?
loaded,
TResult? Function(String errorMessage)? error,
}) {
return loading?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(List<Recipes> recipesList, List<RecipeTag> recipesTags,
Set<int> selectedTags, int selectedTagsCounter, bool isLoading)?
loaded,
TResult Function(String errorMessage)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_RecipesState value) $default, {
required TResult Function(RecipesStateLoading value) loading,
required TResult Function(RecipesStateLoaded value) loaded,
required TResult Function(RecipesStateError value) error,
}) {
return loading(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_RecipesState value)? $default, {
TResult? Function(RecipesStateLoading value)? loading,
TResult? Function(RecipesStateLoaded value)? loaded,
TResult? Function(RecipesStateError value)? error,
}) {
return loading?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_RecipesState value)? $default, {
TResult Function(RecipesStateLoading value)? loading,
TResult Function(RecipesStateLoaded value)? loaded,
TResult Function(RecipesStateError value)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading(this);
}
return orElse();
}
}
abstract class RecipesStateLoading implements RecipesState {
const factory RecipesStateLoading() = _$RecipesStateLoadingImpl;
}
/// @nodoc
abstract class _$$RecipesStateLoadedImplCopyWith<$Res> {
factory _$$RecipesStateLoadedImplCopyWith(_$RecipesStateLoadedImpl value,
$Res Function(_$RecipesStateLoadedImpl) then) =
__$$RecipesStateLoadedImplCopyWithImpl<$Res>;
@useResult
$Res call(
{List<Recipes> recipesList,
List<RecipeTag> recipesTags,
Set<int> selectedTags,
int selectedTagsCounter,
bool isLoading});
}
/// @nodoc
class __$$RecipesStateLoadedImplCopyWithImpl<$Res>
extends _$RecipesStateCopyWithImpl<$Res, _$RecipesStateLoadedImpl>
implements _$$RecipesStateLoadedImplCopyWith<$Res> {
__$$RecipesStateLoadedImplCopyWithImpl(_$RecipesStateLoadedImpl _value,
$Res Function(_$RecipesStateLoadedImpl) _then)
: super(_value, _then);
/// Create a copy of RecipesState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@@ -386,12 +131,13 @@ class __$$RecipesStateLoadedImplCopyWithImpl<$Res>
Object? selectedTags = null,
Object? selectedTagsCounter = null,
Object? isLoading = null,
Object? errorMessage = freezed,
}) {
return _then(_$RecipesStateLoadedImpl(
return _then(_$RecipesStateImpl(
recipesList: null == recipesList
? _value._recipesList
: recipesList // ignore: cast_nullable_to_non_nullable
as List<Recipes>,
as List<Recipe>,
recipesTags: null == recipesTags
? _value._recipesTags
: recipesTags // ignore: cast_nullable_to_non_nullable
@@ -408,26 +154,31 @@ class __$$RecipesStateLoadedImplCopyWithImpl<$Res>
? _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?,
));
}
}
/// @nodoc
class _$RecipesStateLoadedImpl implements RecipesStateLoaded {
const _$RecipesStateLoadedImpl(
{required final List<Recipes> recipesList,
class _$RecipesStateImpl implements _RecipesState {
const _$RecipesStateImpl(
{required final List<Recipe> recipesList,
required final List<RecipeTag> recipesTags,
required final Set<int> selectedTags,
required this.selectedTagsCounter,
required this.isLoading})
required this.isLoading,
this.errorMessage})
: _recipesList = recipesList,
_recipesTags = recipesTags,
_selectedTags = selectedTags;
final List<Recipes> _recipesList;
final List<Recipe> _recipesList;
@override
List<Recipes> get recipesList {
List<Recipe> get recipesList {
if (_recipesList is EqualUnmodifiableListView) return _recipesList;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_recipesList);
@@ -453,17 +204,19 @@ class _$RecipesStateLoadedImpl implements RecipesStateLoaded {
final int selectedTagsCounter;
@override
final bool isLoading;
@override
final String? errorMessage;
@override
String toString() {
return 'RecipesState.loaded(recipesList: $recipesList, recipesTags: $recipesTags, selectedTags: $selectedTags, selectedTagsCounter: $selectedTagsCounter, isLoading: $isLoading)';
return 'RecipesState(recipesList: $recipesList, recipesTags: $recipesTags, selectedTags: $selectedTags, selectedTagsCounter: $selectedTagsCounter, isLoading: $isLoading, errorMessage: $errorMessage)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$RecipesStateLoadedImpl &&
other is _$RecipesStateImpl &&
const DeepCollectionEquality()
.equals(other._recipesList, _recipesList) &&
const DeepCollectionEquality()
@@ -473,7 +226,9 @@ class _$RecipesStateLoadedImpl implements RecipesStateLoaded {
(identical(other.selectedTagsCounter, selectedTagsCounter) ||
other.selectedTagsCounter == selectedTagsCounter) &&
(identical(other.isLoading, isLoading) ||
other.isLoading == isLoading));
other.isLoading == isLoading) &&
(identical(other.errorMessage, errorMessage) ||
other.errorMessage == errorMessage));
}
@override
@@ -483,287 +238,44 @@ class _$RecipesStateLoadedImpl implements RecipesStateLoaded {
const DeepCollectionEquality().hash(_recipesTags),
const DeepCollectionEquality().hash(_selectedTags),
selectedTagsCounter,
isLoading);
isLoading,
errorMessage);
/// Create a copy of RecipesState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$RecipesStateLoadedImplCopyWith<_$RecipesStateLoadedImpl> get copyWith =>
__$$RecipesStateLoadedImplCopyWithImpl<_$RecipesStateLoadedImpl>(
this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(
List<Recipes> recipesList,
List<RecipeTag> recipesTags,
Set<int> selectedTags,
int selectedTagsCounter,
bool isLoading)
loaded,
required TResult Function(String errorMessage) error,
}) {
return loaded(
recipesList, recipesTags, selectedTags, selectedTagsCounter, isLoading);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(List<Recipes> recipesList, List<RecipeTag> recipesTags,
Set<int> selectedTags, int selectedTagsCounter, bool isLoading)?
loaded,
TResult? Function(String errorMessage)? error,
}) {
return loaded?.call(
recipesList, recipesTags, selectedTags, selectedTagsCounter, isLoading);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(List<Recipes> recipesList, List<RecipeTag> recipesTags,
Set<int> selectedTags, int selectedTagsCounter, bool isLoading)?
loaded,
TResult Function(String errorMessage)? error,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(recipesList, recipesTags, selectedTags, selectedTagsCounter,
isLoading);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_RecipesState value) $default, {
required TResult Function(RecipesStateLoading value) loading,
required TResult Function(RecipesStateLoaded value) loaded,
required TResult Function(RecipesStateError value) error,
}) {
return loaded(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_RecipesState value)? $default, {
TResult? Function(RecipesStateLoading value)? loading,
TResult? Function(RecipesStateLoaded value)? loaded,
TResult? Function(RecipesStateError value)? error,
}) {
return loaded?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_RecipesState value)? $default, {
TResult Function(RecipesStateLoading value)? loading,
TResult Function(RecipesStateLoaded value)? loaded,
TResult Function(RecipesStateError value)? error,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(this);
}
return orElse();
}
_$$RecipesStateImplCopyWith<_$RecipesStateImpl> get copyWith =>
__$$RecipesStateImplCopyWithImpl<_$RecipesStateImpl>(this, _$identity);
}
abstract class RecipesStateLoaded implements RecipesState {
const factory RecipesStateLoaded(
{required final List<Recipes> recipesList,
abstract class _RecipesState implements RecipesState {
const factory _RecipesState(
{required final List<Recipe> recipesList,
required final List<RecipeTag> recipesTags,
required final Set<int> selectedTags,
required final int selectedTagsCounter,
required final bool isLoading}) = _$RecipesStateLoadedImpl;
required final bool isLoading,
final String? errorMessage}) = _$RecipesStateImpl;
List<Recipes> get recipesList;
@override
List<Recipe> get recipesList;
@override
List<RecipeTag> get recipesTags;
@override
Set<int> get selectedTags;
@override
int get selectedTagsCounter;
@override
bool get isLoading;
@override
String? get errorMessage;
/// Create a copy of RecipesState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$RecipesStateLoadedImplCopyWith<_$RecipesStateLoadedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$RecipesStateErrorImplCopyWith<$Res> {
factory _$$RecipesStateErrorImplCopyWith(_$RecipesStateErrorImpl value,
$Res Function(_$RecipesStateErrorImpl) then) =
__$$RecipesStateErrorImplCopyWithImpl<$Res>;
@useResult
$Res call({String errorMessage});
}
/// @nodoc
class __$$RecipesStateErrorImplCopyWithImpl<$Res>
extends _$RecipesStateCopyWithImpl<$Res, _$RecipesStateErrorImpl>
implements _$$RecipesStateErrorImplCopyWith<$Res> {
__$$RecipesStateErrorImplCopyWithImpl(_$RecipesStateErrorImpl _value,
$Res Function(_$RecipesStateErrorImpl) _then)
: super(_value, _then);
/// Create a copy of RecipesState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? errorMessage = null,
}) {
return _then(_$RecipesStateErrorImpl(
errorMessage: null == errorMessage
? _value.errorMessage
: errorMessage // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
class _$RecipesStateErrorImpl implements RecipesStateError {
const _$RecipesStateErrorImpl({required this.errorMessage});
@override
final String errorMessage;
@override
String toString() {
return 'RecipesState.error(errorMessage: $errorMessage)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$RecipesStateErrorImpl &&
(identical(other.errorMessage, errorMessage) ||
other.errorMessage == errorMessage));
}
@override
int get hashCode => Object.hash(runtimeType, errorMessage);
/// Create a copy of RecipesState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$RecipesStateErrorImplCopyWith<_$RecipesStateErrorImpl> get copyWith =>
__$$RecipesStateErrorImplCopyWithImpl<_$RecipesStateErrorImpl>(
this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(
List<Recipes> recipesList,
List<RecipeTag> recipesTags,
Set<int> selectedTags,
int selectedTagsCounter,
bool isLoading)
loaded,
required TResult Function(String errorMessage) error,
}) {
return error(errorMessage);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(List<Recipes> recipesList, List<RecipeTag> recipesTags,
Set<int> selectedTags, int selectedTagsCounter, bool isLoading)?
loaded,
TResult? Function(String errorMessage)? error,
}) {
return error?.call(errorMessage);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(List<Recipes> recipesList, List<RecipeTag> recipesTags,
Set<int> selectedTags, int selectedTagsCounter, bool isLoading)?
loaded,
TResult Function(String errorMessage)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(errorMessage);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_RecipesState value) $default, {
required TResult Function(RecipesStateLoading value) loading,
required TResult Function(RecipesStateLoaded value) loaded,
required TResult Function(RecipesStateError value) error,
}) {
return error(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_RecipesState value)? $default, {
TResult? Function(RecipesStateLoading value)? loading,
TResult? Function(RecipesStateLoaded value)? loaded,
TResult? Function(RecipesStateError value)? error,
}) {
return error?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_RecipesState value)? $default, {
TResult Function(RecipesStateLoading value)? loading,
TResult Function(RecipesStateLoaded value)? loaded,
TResult Function(RecipesStateError value)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(this);
}
return orElse();
}
}
abstract class RecipesStateError implements RecipesState {
const factory RecipesStateError({required final String errorMessage}) =
_$RecipesStateErrorImpl;
String get errorMessage;
/// Create a copy of RecipesState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$RecipesStateErrorImplCopyWith<_$RecipesStateErrorImpl> get copyWith =>
_$$RecipesStateImplCopyWith<_$RecipesStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -20,7 +20,7 @@ import 'package:eatvio_app/features/feed/presentation/widgets/tags_modal_widget.
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/recipes/presentation/pages/recipe_create_screen.dart';
import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';

View File

@@ -1,5 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/utils/enums.dart';
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
import 'package:eatvio_app/core/widgets/info_row.dart';
import 'package:eatvio_app/core/widgets/feed_icon_button.dart';
@@ -30,21 +31,27 @@ class DietsWidget extends StatefulWidget {
class _DietsWidgetState extends State<DietsWidget> {
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
return InkWell(
onTap: () => GoRouter.of(context).pushNamed(
'aboutDietScreen',
extra: {
'diet': widget.diet,
'index': widget.index,
'navigateToComments': false,
},
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
InkWell(
onTap: () =>
GoRouter.of(context).pushNamed('aboutDietScreen', extra: {'diet': widget.diet, 'index': widget.index, 'navigateToComments': false}),
child: ClipRRect(
child: Ink(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
child: CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + widget.diet.image,
@@ -52,123 +59,124 @@ class _DietsWidgetState extends State<DietsWidget> {
width: MediaQuery.of(context).size.width,
),
),
),
Positioned(
right: 14,
child: BookmarkButton(
onTap: () async {
if (widget.diet.isFavorite) {
await context.read<DietsCubit>().removeDietFromFavorite(widget.diet.id, widget.index);
} else {
await context.read<DietsCubit>().addDietToFavorite(widget.diet.id, widget.index);
}
},
color: appTheme.whiteA700,
isFavorite: widget.diet.isFavorite,
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
width: 40,
height: 44,
size: 19,
),
),
Positioned(
bottom: 16,
child: SizedBox(
width: MediaQuery.sizeOf(context).width,
child: InfoRow(
diet: widget.diet,
itemsBackgroundColor: appTheme.whiteA700,
disableDescriptionOfTypes: true,
),
),
),
],
),
SizedBox(height: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
widget.diet.name,
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
),
),
if (widget.diet.details.isNotEmpty) ...[
SizedBox(
height: 12,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Html(
data: widget.diet.details.length > 200 ? widget.diet.details.substring(0, 200) + '...' : widget.diet.details,
style: {
"*": Style(
margin: Margins.zero,
color: appTheme.black,
fontSize: FontSize(TextStyles.textSMedium.fontSize ?? 14),
fontWeight: TextStyles.textSMedium.fontWeight ?? FontWeight.w400,
),
Positioned(
right: 14,
child: BookmarkButton(
onTap: () async {
if (widget.diet.isFavorite) {
await context.read<DietsCubit>().removeDietFromFavorite(widget.diet.id, widget.index);
} else {
await context.read<DietsCubit>().addDietToFavorite(widget.diet.id, widget.index);
}
},
color: appTheme.whiteA700,
isFavorite: widget.diet.isFavorite,
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
width: 40,
height: 44,
size: 19,
),
),
],
],
),
Padding(
padding: const EdgeInsets.only(left: 16, top: 12),
child: Row(
children: [
FeedIconButton(
icon: widget.diet.wasLikedByMe ? Icons.favorite_rounded : Icons.favorite_border_rounded,
text: widget.diet.totalLikes.toString(),
iconColor: widget.diet.wasLikedByMe ? appTheme.blueA400 : null,
backgroundColor: widget.diet.wasLikedByMe ? appTheme.transparentBlue : null,
onTap: () async {
await context.read<DietsCubit>().likeDiet(widget.diet.id, widget.index);
},
),
SizedBox(
width: 12,
),
FeedIconButton(
icon: Icons.chat_bubble_outline,
text: widget.diet.totalComments.toString(),
onTap: () =>
GoRouter.of(context).pushNamed('aboutDietScreen', extra: {'diet': widget.diet, 'index': widget.index, 'navigateToComments': true}),
),
SizedBox(
width: 12,
),
FeedIconButton(
svgIcon: SvgPicture.asset('assets/icons/repost.svg'),
onTap: () async {
final box = context.findRenderObject() as RenderBox?;
final result = await Share.shareUri(
Uri.parse(
baseUrl + '/ru/diet/' + widget.diet.id.toString(),
),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
);
switch (result.status) {
case ShareResultStatus.success:
case ShareResultStatus.dismissed:
case ShareResultStatus.unavailable:
}
},
),
Spacer(),
FeedIconButton(
svgIcon: SvgPicture.asset('assets/icons/eye2.svg'),
text: widget.diet.totalViews.toString(),
backgroundColor: Colors.transparent,
Positioned(
bottom: 16,
child: SizedBox(
width: MediaQuery.sizeOf(context).width,
child: InfoRow(
diet: widget.diet,
itemsBackgroundColor: appTheme.whiteA700,
disableDescriptionOfTypes: true,
type: ProductType.diet
),
),
),
],
),
),
SizedBox(height: 12),
],
SizedBox(height: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
widget.diet.name,
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
),
),
if (widget.diet.details.isNotEmpty) ...[
SizedBox(
height: 12,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Html(
data: widget.diet.details.length > 200 ? widget.diet.details.substring(0, 200) + '...' : widget.diet.details,
style: {
"*": Style(
margin: Margins.zero,
color: appTheme.black,
fontSize: FontSize(TextStyles.textSMedium.fontSize ?? 14),
fontWeight: TextStyles.textSMedium.fontWeight ?? FontWeight.w400,
),
},
),
),
],
],
),
Padding(
padding: const EdgeInsets.only(left: 16, top: 12),
child: Row(
children: [
FeedIconButton(
icon: widget.diet.wasLikedByMe ? Icons.favorite_rounded : Icons.favorite_border_rounded,
text: widget.diet.totalLikes.toString(),
iconColor: widget.diet.wasLikedByMe ? appTheme.blueA400 : null,
backgroundColor: widget.diet.wasLikedByMe ? appTheme.transparentBlue : null,
onTap: () async {
await context.read<DietsCubit>().likeDiet(widget.diet.id, widget.index);
},
),
SizedBox(
width: 12,
),
FeedIconButton(
icon: Icons.chat_bubble_outline,
text: widget.diet.totalComments.toString(),
onTap: () =>
GoRouter.of(context).pushNamed('aboutDietScreen', extra: {'diet': widget.diet, 'index': widget.index, 'navigateToComments': true}),
),
SizedBox(
width: 12,
),
FeedIconButton(
svgIcon: SvgPicture.asset('assets/icons/repost.svg'),
onTap: () async {
final box = context.findRenderObject() as RenderBox?;
final result = await Share.shareUri(
Uri.parse(
baseUrl + '/ru/diet/' + widget.diet.id.toString(),
),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
);
switch (result.status) {
case ShareResultStatus.success:
case ShareResultStatus.dismissed:
case ShareResultStatus.unavailable:
}
},
),
Spacer(),
FeedIconButton(
svgIcon: SvgPicture.asset('assets/icons/eye2.svg'),
text: widget.diet.totalViews.toString(),
backgroundColor: Colors.transparent,
),
],
),
),
SizedBox(height: 12),
],
),
),
);
}

View File

@@ -1,5 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/utils/enums.dart';
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
import 'package:eatvio_app/core/widgets/info_row.dart';
import 'package:eatvio_app/core/widgets/feed_icon_button.dart';
@@ -33,21 +34,27 @@ class _PlanWidgetState extends State<PlanWidget> {
@override
Widget build(BuildContext context) {
final s = S.of(context);
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
return InkWell(
onTap: () => GoRouter.of(context).pushNamed(
'aboutPlanScreen',
extra: {
'plan': widget.plan,
'index': widget.index,
'navigateToComments': false,
},
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
InkWell(
onTap: () =>
GoRouter.of(context).pushNamed('aboutPlanScreen', extra: {'plan': widget.plan, 'index': widget.index, 'navigateToComments': false}),
child: ClipRRect(
child: Ink(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
child: CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + widget.plan.image,
@@ -55,176 +62,177 @@ class _PlanWidgetState extends State<PlanWidget> {
width: MediaQuery.of(context).size.width,
),
),
),
Positioned(
right: 14,
child: BookmarkButton(
onTap: () async {
if (widget.plan.isFavorite) {
await context.read<PlansCubit>().removePlanFromFavorite(widget.plan.slug, widget.index);
} else {
await context.read<PlansCubit>().addPlanToFavorite(widget.plan.slug, widget.index);
}
},
color: appTheme.whiteA700,
isFavorite: widget.plan.isFavorite,
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
width: 40,
height: 44,
size: 19,
),
),
Positioned(
bottom: 16,
child: SizedBox(
width: MediaQuery.sizeOf(context).width,
child: InfoRow(
plan: widget.plan,
itemsBackgroundColor: appTheme.whiteA700,
disableDescriptionOfTypes: true,
Positioned(
right: 14,
child: BookmarkButton(
onTap: () async {
if (widget.plan.isFavorite) {
await context.read<PlansCubit>().removePlanFromFavorite(widget.plan.slug, widget.index);
} else {
await context.read<PlansCubit>().addPlanToFavorite(widget.plan.slug, widget.index);
}
},
color: appTheme.whiteA700,
isFavorite: widget.plan.isFavorite,
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
width: 40,
height: 44,
size: 19,
),
),
),
],
),
SizedBox(height: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
widget.plan.name,
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
),
),
if (widget.plan.details.isNotEmpty) ...[
SizedBox(
height: 12,
Positioned(
bottom: 16,
child: SizedBox(
width: MediaQuery.sizeOf(context).width,
child: InfoRow(
plan: widget.plan,
itemsBackgroundColor: appTheme.whiteA700,
disableDescriptionOfTypes: true,
type: ProductType.plan
),
),
),
],
),
SizedBox(height: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Html(
data: widget.plan.details.length > 200 ? widget.plan.details.substring(0, 200) + '...' : widget.plan.details,
style: {
"*": Style(
margin: Margins.zero,
color: appTheme.black,
fontSize: FontSize(TextStyles.textSMedium.fontSize ?? 14),
fontWeight: TextStyles.textSMedium.fontWeight ?? FontWeight.w400,
),
child: Text(
widget.plan.name,
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
),
),
if (widget.plan.details.isNotEmpty) ...[
SizedBox(
height: 12,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Html(
data: widget.plan.details.length > 200 ? widget.plan.details.substring(0, 200) + '...' : widget.plan.details,
style: {
"*": Style(
margin: Margins.zero,
color: appTheme.black,
fontSize: FontSize(TextStyles.textSMedium.fontSize ?? 14),
fontWeight: TextStyles.textSMedium.fontWeight ?? FontWeight.w400,
),
},
),
),
],
],
),
SizedBox(height: 16),
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: ListTile(
contentPadding: EdgeInsets.zero,
leading: Container(
width: 46,
height: 46,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(widget.plan.author.authorProfileIcon),
fit: BoxFit.cover,
),
),
),
subtitle: Row(
children: [
InfoBouble(
radius: 12,
backgroundColor: appTheme.gray50,
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 10),
children: [
Text(
'${widget.plan.author.authorPlaceInRating}-й в рейтинге',
style: TextStyles.textXSSemibold.copyWith(
color: appTheme.blueA400,
),
),
],
),
Spacer(),
Text(
'${widget.plan.packetsCount == 1 ? widget.plan.packet1Options?.price : '${s.from} ${widget.plan.minPriceOfOptions.toStringAsFixed(0)}'}₽',
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.greenA700),
),
SizedBox(width: 16),
],
),
title: Padding(
padding: const EdgeInsets.only(bottom: 4.0),
child: Text(
widget.plan.author.authorName,
style: TextStyles.textSSemibold.copyWith(
color: appTheme.black,
),
),
),
),
),
SizedBox(height: 16),
Padding(
padding: const EdgeInsets.only(
left: 16,
),
child: Row(
children: [
FeedIconButton(
icon: widget.plan.wasLikedByMe ? Icons.favorite_rounded : Icons.favorite_border_rounded,
text: widget.plan.totalLikes.toString(),
iconColor: widget.plan.wasLikedByMe ? appTheme.blueA400 : null,
backgroundColor: widget.plan.wasLikedByMe ? appTheme.transparentBlue : null,
onTap: () async {
await context.read<PlansCubit>().likePlan(widget.plan.slug, widget.index);
},
),
),
],
],
),
SizedBox(height: 16),
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: ListTile(
contentPadding: EdgeInsets.zero,
leading: Container(
width: 46,
height: 46,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(widget.plan.author.authorProfileIcon),
fit: BoxFit.cover,
SizedBox(
width: 12,
),
),
),
subtitle: Row(
children: [
InfoBouble(
radius: 12,
backgroundColor: appTheme.gray50,
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 10),
children: [
Text(
'${widget.plan.author.authorPlaceInRating}-й в рейтинге',
style: TextStyles.textXSSemibold.copyWith(
color: appTheme.blueA400,
FeedIconButton(
icon: Icons.chat_bubble_outline,
text: widget.plan.totalComments.toString(),
onTap: () =>
GoRouter.of(context).pushNamed('aboutPlanScreen', extra: {'plan': widget.plan, 'index': widget.index, 'navigateToComments': true}),
),
SizedBox(
width: 12,
),
FeedIconButton(
svgIcon: SvgPicture.asset('assets/icons/repost.svg'),
onTap: () async {
final box = context.findRenderObject() as RenderBox?;
final result = await Share.shareUri(
Uri.parse(
baseUrl + '/ru/diet/' + widget.plan.id.toString(),
),
),
],
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
);
switch (result.status) {
case ShareResultStatus.success:
case ShareResultStatus.dismissed:
case ShareResultStatus.unavailable:
}
},
),
Spacer(),
Text(
'${widget.plan.packetsCount == 1 ? widget.plan.packet1Options?.price : '${s.from} ${widget.plan.minPriceOfOptions.toStringAsFixed(0)}'}₽',
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.greenA700),
FeedIconButton(
svgIcon: SvgPicture.asset('assets/icons/eye2.svg'),
text: widget.plan.totalViews.toString(),
backgroundColor: Colors.transparent,
),
SizedBox(width: 16),
],
),
title: Padding(
padding: const EdgeInsets.only(bottom: 4.0),
child: Text(
widget.plan.author.authorName,
style: TextStyles.textSSemibold.copyWith(
color: appTheme.black,
),
),
),
),
),
SizedBox(height: 16),
Padding(
padding: const EdgeInsets.only(
left: 16,
),
child: Row(
children: [
FeedIconButton(
icon: widget.plan.wasLikedByMe ? Icons.favorite_rounded : Icons.favorite_border_rounded,
text: widget.plan.totalLikes.toString(),
iconColor: widget.plan.wasLikedByMe ? appTheme.blueA400 : null,
backgroundColor: widget.plan.wasLikedByMe ? appTheme.transparentBlue : null,
onTap: () async {
await context.read<PlansCubit>().likePlan(widget.plan.slug, widget.index);
},
),
SizedBox(
width: 12,
),
FeedIconButton(
icon: Icons.chat_bubble_outline,
text: widget.plan.totalComments.toString(),
onTap: () =>
GoRouter.of(context).pushNamed('aboutPlanScreen', extra: {'plan': widget.plan, 'index': widget.index, 'navigateToComments': true}),
),
SizedBox(
width: 12,
),
FeedIconButton(
svgIcon: SvgPicture.asset('assets/icons/repost.svg'),
onTap: () async {
final box = context.findRenderObject() as RenderBox?;
final result = await Share.shareUri(
Uri.parse(
baseUrl + '/ru/diet/' + widget.plan.id.toString(),
),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
);
switch (result.status) {
case ShareResultStatus.success:
case ShareResultStatus.dismissed:
case ShareResultStatus.unavailable:
}
},
),
Spacer(),
FeedIconButton(
svgIcon: SvgPicture.asset('assets/icons/eye2.svg'),
text: widget.plan.totalViews.toString(),
backgroundColor: Colors.transparent,
),
],
),
),
SizedBox(height: 12),
],
SizedBox(height: 12),
],
),
),
);
}

View File

@@ -14,38 +14,39 @@ class Recipes extends StatelessWidget {
onRefresh: () => context.read<RecipesCubit>().loadRecipesList(),
child: BlocBuilder<RecipesCubit, RecipesState>(
builder: (BuildContext context, RecipesState state) {
if (state is RecipesStateLoaded) {
return ListView.separated(
padding: EdgeInsets.symmetric(vertical: 8),
shrinkWrap: true,
physics: BouncingScrollPhysics(),
itemBuilder: (context, index) {
return RecipesWidget(
recipes: state.recipesList[index],
index: index,
);
},
separatorBuilder: (context, index) {
return SizedBox(height: 8);
},
itemCount: state.recipesList.length,
);
} else if (state is RecipesStateError) {
return Scaffold(
backgroundColor: Colors.grey.shade200,
body: Padding(
padding: const EdgeInsets.all(16),
child: Center(
child: Text(state.errorMessage),
),
),
);
} else {
if (state.isLoading) {
return Scaffold(
backgroundColor: Colors.grey.shade200,
body: const Loader(),
);
}
if (state.errorMessage != null) {
return Scaffold(
backgroundColor: Colors.grey.shade200,
body: Padding(
padding: const EdgeInsets.all(16),
child: Center(
child: Text(state.errorMessage ?? ''),
),
),
);
}
return ListView.separated(
padding: EdgeInsets.symmetric(vertical: 8),
shrinkWrap: true,
physics: BouncingScrollPhysics(),
itemBuilder: (context, index) {
return RecipesWidget(
recipe: state.recipesList[index],
index: index,
);
},
separatorBuilder: (context, index) {
return SizedBox(height: 8);
},
itemCount: state.recipesList.length,
);
},
),
);

View File

@@ -2,11 +2,15 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
import 'package:eatvio_app/core/widgets/feed_icon_button.dart';
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_state.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_html/flutter_html.dart';
@@ -16,11 +20,11 @@ import 'package:share_plus/share_plus.dart';
class RecipesWidget extends StatefulWidget {
final int index;
final Recipes recipes;
final Recipe recipe;
const RecipesWidget({
super.key,
required this.recipes,
required this.recipe,
required this.index,
});
@@ -29,177 +33,207 @@ class RecipesWidget extends StatefulWidget {
}
class _RecipesWidgetState extends State<RecipesWidget> {
late bool isLiked;
late int totalLikes;
@override
void initState() {
super.initState();
isLiked = false;
}
@override
Widget build(BuildContext context) {
final s = S.of(context);
return BlocBuilder<RecipesCubit, RecipesState>(
builder: (BuildContext context, RecipesState state) {
if (state is RecipesStateLoaded) {
isLiked = state.recipesList[widget.index].wasLikedByMe;
totalLikes = state.recipesList[widget.index].likesCount;
return InkWell(
borderRadius: BorderRadius.circular(12),
onTap: () => GoRouter.of(context).pushNamed('aboutRecipesScreen', extra: widget.recipes),
child: Ink(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
child: CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + widget.recipes.image,
fit: BoxFit.fitWidth,
width: MediaQuery.of(context).size.width,
),
),
Positioned(
left: 8,
bottom: 8,
child: Wrap(
runSpacing: 8,
children: [
_iconButton('assets/icons/clock.svg', widget.recipes.cookingTime.toString() + ' минут'),
SizedBox(width: 8),
_iconButton('assets/icons/steps.svg', '${widget.recipes.steps?.length.toString()}' + ' этапов'),
SizedBox(width: 8),
_iconButton('assets/icons/calories.svg', widget.recipes.calories.toStringAsFixed(0) + ' ккал'),
],
),
),
],
),
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text(
widget.recipes.title ?? '',
style: TextStyle(
fontWeight: FontWeight.w600,
color: Colors.black87,
final isLiked = widget.recipe.wasLikedByMe;
final totalLikes = widget.recipe.likesCount;
final isFavorite = widget.recipe.isFavorite;
final hours = widget.recipe.cookingTime ~/ 60;
final minutes = widget.recipe.cookingTime % 60;
final String timeTitle;
if (hours > 0) {
timeTitle = '$hours ${getHourForm(hours)} $minutes ${getMinuteForm(minutes)}';
} else {
timeTitle = '$minutes ${getMinuteForm(minutes)}';
}
return InkWell(
borderRadius: BorderRadius.circular(12),
onTap: () => GoRouter.of(context).pushNamed(
'aboutRecipesScreen',
extra: {
'recipe': widget.recipe,
'index': widget.index,
'navigateToComments': false,
},
),
child: Ink(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
child: CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + widget.recipe.image,
fit: BoxFit.fitWidth,
width: MediaQuery.of(context).size.width,
),
),
),
if (widget.recipes.description.isNotEmpty)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Html(
data: widget.recipes.description.length > 200 ? widget.recipes.description.substring(0, 200) : widget.recipes.description,
style: {
"*": Style(
color: Colors.black87,
),
Positioned(
left: 8,
bottom: 8,
child: Wrap(
runSpacing: 8,
children: [
_iconButton('assets/icons/clock.svg', timeTitle),
SizedBox(width: 8),
_iconButton('assets/icons/steps.svg', '${widget.recipe.steps?.length ?? 0} ${getStepForm(widget.recipe.steps?.length ?? 0)}'),
SizedBox(width: 8),
_iconButton('assets/icons/calories.svg', widget.recipe.calories.toStringAsFixed(0) + ' ккал'),
],
),
),
Positioned(
right: 14,
child: BookmarkButton(
onTap: () async {
if (isFavorite) {
await context.read<RecipesCubit>().removeRecipeFromFavorite(widget.recipe.slug, widget.index);
} else {
await context.read<RecipesCubit>().addRecipeToFavorite(widget.recipe.slug, widget.index);
}
},
color: appTheme.whiteA700,
isFavorite: isFavorite,
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
width: 40,
height: 44,
size: 19,
),
),
if (widget.recipes.description.length > 200)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
'Читать полностью...',
style: TextStyle(
height: 1,
color: appTheme.blueA400,
decoration: TextDecoration.underline,
),
),
),
],
),
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
widget.recipe.title ?? '',
style: TextStyles.textMSemibold.copyWith(color: appTheme.black),
),
),
if (widget.recipe.videoUrl != null && widget.recipe.videoUrl!.isNotEmpty) ...[
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
padding: const EdgeInsets.only(left: 15),
child: Row(
children: [
FeedIconButton(
icon: isLiked ? Icons.favorite_rounded : Icons.favorite_border_rounded,
text: widget.recipes.likesCount.toString(),
iconColor: isLiked ? Colors.red.withValues(alpha: 0.8) : null,
backgroundColor: isLiked ? Colors.red.withValues(alpha: 0.1) : null,
textColor: isLiked ? Colors.red.withValues(alpha: 0.8) : null,
onTap: () async {
setState(() {
isLiked = !isLiked;
isLiked ? totalLikes++ : totalLikes--;
});
isLiked
? await context.read<RecipesCubit>().likeRecipe(state.recipesList[widget.index].slug)
: await context.read<RecipesCubit>().unlikeRecipe(state.recipesList[widget.index].slug);
await context.read<RecipesCubit>().loadRecipesList();
},
),
SvgPicture.asset('assets/icons/play_icon.svg', width: 16, height: 16),
SizedBox(width: 8),
FeedIconButton(
svgIcon: SvgPicture.asset(
'assets/icons/comment.svg',
height: 20,
width: 20,
colorFilter: ColorFilter.mode(
appTheme.gray600,
BlendMode.srcIn,
),
),
text: widget.recipes.commentsCount.toString(),
onTap: () => GoRouter.of(context).pushNamed(
'aboutRecipesScreen',
extra: widget.recipes,
),
),
SizedBox(width: 8),
FeedIconButton(
svgIcon: SvgPicture.asset(
height: 20,
width: 20,
'assets/icons/share.svg',
colorFilter: ColorFilter.mode(appTheme.gray600, BlendMode.srcIn),
),
onTap: () {
final box = context.findRenderObject() as RenderBox?;
Share.shareUri(
Uri.parse(
baseUrl + '/ru/blog_post/' + widget.recipes.id.toString(),
),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
);
},
),
Spacer(),
Icon(
Icons.visibility_rounded,
size: 16,
color: appTheme.gray500,
),
SizedBox(width: 4),
Text(
widget.recipes.viewsCount.toString(),
style: TextStyle(
fontSize: 12,
color: appTheme.gray500,
fontWeight: FontWeight.w400,
),
s.there_is_a_video,
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
),
),
SizedBox(height: 12),
],
),
SizedBox(height: 12),
if (widget.recipe.description.isNotEmpty)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Html(
data: widget.recipe.description.length > 200 ? widget.recipe.description.substring(0, 200) : widget.recipe.description,
style: {
"*": Style(
color: appTheme.black,
margin: Margins.all(0),
fontFamily: TextStyles.textSMedium.fontFamily,
fontSize: FontSize(TextStyles.textSMedium.fontSize ?? 14),
fontWeight: TextStyles.textSMedium.fontWeight,
),
},
),
),
if (widget.recipe.description.length > 200)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
'Читать полностью...',
style: TextStyle(
height: 1,
color: appTheme.blueA400,
decoration: TextDecoration.underline,
),
),
),
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
FeedIconButton(
icon: isLiked ? Icons.favorite_rounded : Icons.favorite_border_rounded,
text: totalLikes.toString(),
iconColor: isLiked ? Colors.red.withValues(alpha: 0.8) : null,
backgroundColor: isLiked ? Colors.red.withValues(alpha: 0.1) : null,
textColor: isLiked ? Colors.red.withValues(alpha: 0.8) : null,
onTap: () async {
await context.read<RecipesCubit>().likeRecipe(widget.recipe.slug, widget.index);
},
),
SizedBox(width: 8),
FeedIconButton(
icon: Icons.chat_bubble_outline,
text: widget.recipe.commentsCount.toString(),
onTap: () => GoRouter.of(context).pushNamed(
'aboutRecipesScreen',
extra: {
'recipe': widget.recipe,
'index': widget.index,
'navigateToComments': true,
},
),
),
SizedBox(width: 8),
FeedIconButton(
svgIcon: SvgPicture.asset(
height: 15,
width: 12,
'assets/icons/repost.svg',
colorFilter: ColorFilter.mode(appTheme.gray600, BlendMode.srcIn),
),
onTap: () {
final box = context.findRenderObject() as RenderBox?;
Share.shareUri(
Uri.parse(
baseUrl + '/ru/blog_post/' + widget.recipe.id.toString(),
),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
);
},
),
Spacer(),
Icon(
Icons.visibility_rounded,
size: 16,
color: appTheme.gray500,
),
SizedBox(width: 4),
Text(
widget.recipe.viewsCount.toString(),
style: TextStyle(
fontSize: 12,
color: appTheme.gray500,
fontWeight: FontWeight.w400,
),
),
],
),
),
SizedBox(height: 12),
],
),
);
}
return Container();
),
);
},
);
}
@@ -208,7 +242,7 @@ class _RecipesWidgetState extends State<RecipesWidget> {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.8),
color: appTheme.whiteA700,
borderRadius: BorderRadius.circular(16),
),
height: 32,

View File

@@ -1,6 +1,6 @@
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_state.dart';
import 'package:eatvio_app/features/recipes/models/recipe_tag_model.dart';
import 'package:eatvio_app/features/create_recipe/models/recipe_tag_model.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -23,25 +23,21 @@ class _TagsModalWidgetState extends State<TagsModalWidget> {
return BlocBuilder<RecipesCubit, RecipesState>(
bloc: widget.recipesCubit,
builder: (BuildContext context, RecipesState state) {
if (state is RecipesStateLoaded) {
return Container(
padding: EdgeInsets.all(16),
width: MediaQuery.of(context).size.width,
child: Wrap(
spacing: 8,
runSpacing: -8,
children: [
for (int i = 0; i < state.recipesTags.length; i++)
_tagChip(
tag: state.recipesTags[i],
isSelected: state.selectedTags.contains(state.recipesTags[i].id),
),
],
),
);
} else {
return Container();
}
return Container(
padding: EdgeInsets.all(16),
width: MediaQuery.of(context).size.width,
child: Wrap(
spacing: 8,
runSpacing: -8,
children: [
for (int i = 0; i < state.recipesTags.length; i++)
_tagChip(
tag: state.recipesTags[i],
isSelected: state.selectedTags.contains(state.recipesTags[i].id),
),
],
),
);
},
);
}

View File

@@ -27,7 +27,7 @@ class FeedRepository {
}
}
Future<List<Recipes>> getRecipes({
Future<List<Recipe>> getRecipes({
String? query,
List<int>? tags,
int page = 1,
@@ -64,7 +64,7 @@ class FeedRepository {
}
}
Future<PlanPaginationModel> getPlans({
Future<PlanPaginationModel> getPlans({
int page = 1,
String? query,
String? types,
@@ -85,7 +85,7 @@ class FeedRepository {
rethrow;
}
}
Future<CommentModel> getBlogPostComments({
required String id,
}) async {
@@ -217,24 +217,4 @@ class FeedRepository {
rethrow;
}
}
Future<void> likeRecipe({
required String slug,
}) async {
try {
await feedService.likeRecipe(slug: slug);
} catch (e) {
rethrow;
}
}
Future<void> unlikeRecipe({
required String slug,
}) async {
try {
await feedService.unlikeRecipe(slug: slug);
} catch (e) {
rethrow;
}
}
}

View File

@@ -103,16 +103,6 @@ abstract class FeedService {
@Path() required int id,
});
@POST('/api/recipes/{slug}/like')
Future<void> likeRecipe({
@Path() required String slug,
});
@POST('/api/recipes/{slug}/unliked')
Future<void> unlikeRecipe({
@Path() required String slug,
});
@GET('/api/consultants/index')
Future<FeedConsultantPaginationModel> getConsultants();
}

View File

@@ -427,44 +427,6 @@ class _FeedService implements FeedService {
await _dio.fetch<void>(_options);
}
@override
Future<void> likeRecipe({required String slug}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<void>(
Options(method: 'POST', headers: _headers, extra: _extra)
.compose(
_dio.options,
'/api/recipes/${slug}/like',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
await _dio.fetch<void>(_options);
}
@override
Future<void> unlikeRecipe({required String slug}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<void>(
Options(method: 'POST', headers: _headers, extra: _extra)
.compose(
_dio.options,
'/api/recipes/${slug}/unliked',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
await _dio.fetch<void>(_options);
}
@override
Future<FeedConsultantPaginationModel> getConsultants() async {
final _extra = <String, dynamic>{};

View File

@@ -2,6 +2,7 @@ import 'dart:developer';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/core/const.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/my_diets/presentation/cubit/my_diets_state.dart';
import 'package:eatvio_app/features/my_diets/repositories/my_diets_repository.dart';
@@ -9,6 +10,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
class MyDietsCubit extends Cubit<MyDietsState> {
final MyDietsRepository myDietsRepository;
final CreateDietRepository createDietRepository;
int currentDraftPage = 1;
int currentPersonalPage = 1;
@@ -35,7 +37,10 @@ class MyDietsCubit extends Cubit<MyDietsState> {
static const List<int?> _durationValues = [null, ...durationValues];
static const List<String?> _types = [null, ...dietTypeValues];
MyDietsCubit({required this.myDietsRepository}) : super(MyDietsState());
MyDietsCubit({
required this.myDietsRepository,
required this.createDietRepository,
}) : super(MyDietsState());
void changeTypeFilter(int index) {
if (state.selectedStateIndex == 0) {
@@ -283,4 +288,17 @@ class MyDietsCubit extends Cubit<MyDietsState> {
return false;
}
}
Future<String?> publishDiet(int dietId) async {
try {
await createDietRepository.updateDiet(
dietId: dietId,
state: DietState.active,
);
return null;
} catch (e, st) {
log(e.toString(), stackTrace: st);
return 'Произошла непредвиденная ошибка';
}
}
}

View File

@@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/enums.dart';
import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
@@ -63,6 +64,7 @@ class _MyDietWidgetState extends State<MyDietWidget> {
diet: widget.diet,
itemsBackgroundColor: appTheme.whiteA700,
disableDescriptionOfTypes: true,
type: ProductType.diet
),
),
),
@@ -113,6 +115,7 @@ class _MyDietWidgetState extends State<MyDietWidget> {
child: CustomOutlinedButton(
onPressed: () {
if (state.selectedStateIndex == 1) {
context.read<MyDietsCubit>().publishDiet(widget.diet.id);
} else if (state.selectedStateIndex == 2) {
} else {
GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: widget.diet);

View File

@@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/enums.dart';
import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
@@ -62,6 +63,7 @@ class _MyPlanWidgetState extends State<MyPlanWidget> {
plan: widget.plan,
itemsBackgroundColor: appTheme.whiteA700,
disableDescriptionOfTypes: true,
type: ProductType.plan,
),
),
),

View File

@@ -2,6 +2,7 @@ import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/utils/enums.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/core/utils/string_extentions.dart';
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
@@ -400,9 +401,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
),
),
SizedBox(height: 12),
/// TODO глянуть как заменить
InfoRow(plan: state.planModel),
InfoRow(plan: state.planModel, type: ProductType.plan),
ExpandableText(
text: widget.plan.details,
),

View File

@@ -10,11 +10,12 @@ class ItemWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Row(
children: [
Text(
text,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
Expanded(
child: Text(
text,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
),
Spacer(),
SvgPicture.asset(
'assets/icons/ok.svg',
width: 20,

View File

@@ -567,6 +567,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
Uri.parse(
state.profile.educationResource?.vkLink ?? '',
),
mode: LaunchMode.externalApplication,
);
},
'assets/icons/vk.svg',
@@ -577,8 +578,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
() {
launchUrl(
Uri.parse(
state.profile.educationResource?.vkLink ?? '',
state.profile.educationResource?.vkLink ?? '',
),
mode: LaunchMode.externalApplication,
);
},
'assets/icons/telegram.svg',
@@ -591,6 +593,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
Uri.parse(
state.profile.educationResource?.personalSiteLink ?? '',
),
mode: LaunchMode.externalApplication,
);
},
'assets/icons/web.svg',

View File

@@ -0,0 +1,473 @@
import 'dart:developer';
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/utils/enums.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/core/utils/string_extentions.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
import 'package:eatvio_app/core/widgets/info_row.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/expandable_text.dart';
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/recipes_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_state.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart';
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_state.dart';
import 'package:eatvio_app/features/recipe/presentation/widgets/ingredients_widget.dart';
import 'package:eatvio_app/features/recipe/presentation/widgets/steps_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:flutter_svg/svg.dart';
import 'package:url_launcher/url_launcher.dart';
class AboutRecipeScreen extends StatefulWidget {
final Recipe recipe;
final int? index;
final bool navigateToComments;
AboutRecipeScreen({super.key, required this.recipe, required this.index, required this.navigateToComments});
@override
State<AboutRecipeScreen> createState() => _AboutRecipeScreenState();
}
class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
final CommentsCubit commentsCubit = sl<CommentsCubit>();
final AboutRecipeCubit recipeCubit = sl<AboutRecipeCubit>();
int? replyId;
final ScrollController scrollController = ScrollController();
final textEditingController = TextEditingController();
bool isMoreOptionsVisible = false;
bool _jumped = false;
@override
void initState() {
super.initState();
scrollController.addListener(() {
if (isMoreOptionsVisible) {
setState(() {
isMoreOptionsVisible = false;
});
}
});
}
void _scrollToBottom() {
_jumped = true;
scrollController.jumpTo(
scrollController.position.maxScrollExtent,
);
}
@override
Widget build(BuildContext context) {
final s = S.of(context);
return BlocProvider(
create: (context) => recipeCubit..getRecipe(widget.recipe.slug),
child: BlocProvider<CommentsCubit>(
create: (BuildContext context) => commentsCubit
..getComments(
widget.recipe.slug.toString(),
CommentSourceType.recipe,
true,
),
child: BlocConsumer<CommentsCubit, CommentsState>(
listener: (BuildContext context, CommentsState commentsState) {
if (commentsState is CommentsStateLoaded) {
setState(() {
replyId = commentsState.replyId;
});
}
},
builder: (BuildContext context, CommentsState state) {
return BlocBuilder<AboutRecipeCubit, AboutRecipeState>(
builder: (context, recipeState) {
if (recipeState is AboutRecipeStateLoaded) {
Future.delayed(Duration(milliseconds: 500), () {
if (widget.navigateToComments && !_jumped) {
_scrollToBottom();
}
});
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.withValues(alpha: 0.1),
),
),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (replyId != null)
GestureDetector(
onTap: () {
context.read<CommentsCubit>().setReplyId(null);
setState(() {
replyId = null;
textEditingController.clear();
});
FocusManager.instance.primaryFocus?.unfocus();
},
child: Container(
decoration: BoxDecoration(
color: appTheme.blueA400,
shape: BoxShape.circle,
),
height: 32,
width: 32,
child: Icon(
Icons.close_rounded,
size: 18,
color: Colors.white,
),
),
),
if (replyId != null) SizedBox(width: 8),
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: () {
if (textEditingController.text.length > 2) {
commentsCubit.sendComment(
id: widget.recipe.slug,
commentText: textEditingController.text,
parentCommentId: replyId,
commentSourceType: CommentSourceType.recipe,
);
context.read<CommentsCubit>().setReplyId(null);
setState(() {
textEditingController.clear();
replyId = null;
});
Future.delayed(Duration(milliseconds: 500), () {
recipeCubit.getRecipe(widget.recipe.slug, index: widget.index);
});
FocusManager.instance.primaryFocus?.unfocus();
}
},
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.recipe.title ?? '',
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
PortalTarget(
visible: isMoreOptionsVisible,
portalFollower: MoreOptions(
path: 'recipe/${widget.recipe.slug}',
),
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: [
CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + widget.recipe.image,
height: 300,
width: MediaQuery.sizeOf(context).width,
fit: BoxFit.fill,
),
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.recipe.viewsCount.toString(),
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
backgroundColor: appTheme.whiteA700,
),
SizedBox(width: 12),
GestureDetector(
onTap: () {
recipeCubit.likeRecipe(widget.recipe.slug, widget.index);
},
child: InfoBouble(
children: [
SvgPicture.asset(
'assets/icons/heart.svg',
width: 14,
height: 12,
colorFilter: recipeState.recipe.wasLikedByMe ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null,
),
SizedBox(width: 8),
Text(
recipeState.recipe.likesCount.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(
recipeState.recipe.commentsCount.toString(),
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
backgroundColor: appTheme.whiteA700,
),
],
),
),
Positioned(
right: 14,
child: BookmarkButton(
onTap: () async {
if (recipeState.recipe.isFavorite) {
await context.read<AboutRecipeCubit>().removeFromFavorite(widget.recipe.slug, widget.index);
} else {
await context.read<AboutRecipeCubit>().addToFavorite(widget.recipe.slug, widget.index);
}
},
color: appTheme.whiteA700,
isFavorite: recipeState.recipe.isFavorite,
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
width: 40,
height: 44,
size: 19,
),
),
],
),
SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
s.about_recipe,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
),
SizedBox(height: 12),
InfoRow(
recipe: widget.recipe,
type: ProductType.recipe,
),
if (widget.recipe.videoUrl != null)
Padding(
padding: const EdgeInsets.only(left: 16, bottom: 12, top: 12),
child: GestureDetector(
onTap: () async {
try {
final uri = Uri.parse(widget.recipe.videoUrl!);
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
} catch (e) {
log(e.toString());
showErrorToast(s.could_not_launch_video);
}
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
s.check_out_video_recipe,
style: TextStyles.textMSemibold.copyWith(
color: appTheme.black,
),
),
SizedBox(width: 12),
SvgPicture.asset('assets/icons/play_icon.svg', width: 16, height: 16),
],
),
),
),
ExpandableText(
text: widget.recipe.description,
),
SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: AuthorCard(
author: widget.recipe.author,
title: s.recipe_author,
),
),
SizedBox(height: 16),
],
),
),
SizedBox(height: 12),
if (widget.recipe.ingredients?.isNotEmpty == true)
IngredientsWidget(
recipe: widget.recipe,
),
if (widget.recipe.steps?.isNotEmpty == true) ...[
SizedBox(height: 12),
StepsWidget(
recipe: widget.recipe,
),
],
SizedBox(height: 8),
MainBackgroundBouble(
padding: EdgeInsets.symmetric(vertical: 16),
child: Column(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.centerLeft,
child: Text(
recipeState.recipe.commentsCount.toString() + ' ' + getCommentForm(widget.recipe.commentsCount, s),
style: TextStyles.textMMedium.copyWith(
color: appTheme.gray500,
),
),
),
SizedBox(height: 16),
if (state is CommentsStateLoading) const Loader(),
if (state is CommentsStateLoaded)
CommentsWidget(
id: widget.recipe.slug,
commentSourceType: CommentSourceType.recipe,
comment: state.comment,
replyId: replyId,
),
],
),
),
],
),
),
);
}
return Scaffold(
body: const Loader(),
);
},
);
},
),
),
);
}
}

View File

@@ -0,0 +1,149 @@
import 'dart:async';
import 'dart:developer';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class AboutRecipeCubit extends Cubit<AboutRecipeState> {
AboutRecipeCubit({required this.recipeRepository, required this.profileRepository })
: super(AboutRecipeState.initial());
final AboutRecipeRepository recipeRepository;
final ProfileRepository profileRepository;
bool isCurrentDietStarted = false;
bool hasStartedDiet = false;
String? userStartedDietName;
Future<void> getRecipe(String id, {int? index}) async {
try {
final recipe = await recipeRepository.getRecipe(id);
if (index != null) {
recipeRepository.emitCommentsStateToFeed(CommentsState(index: index, totalComments: recipe.commentsCount));
}
emit(AboutRecipeState.loaded(recipe: recipe));
} catch (e) {
log(e.toString());
}
}
Future<void> initRecipe(String id, {int? index}) async {
try {
emit(AboutRecipeState.loading());
final recipe = await recipeRepository.getRecipe(id);
if (index != null) {
recipeRepository.emitCommentsStateToFeed(CommentsState(index: index, totalComments: recipe.commentsCount));
}
emit(AboutRecipeState.loaded(recipe: recipe));
} catch (e) {
log(e.toString());
}
}
Future<void> likeRecipe(String id, int? recipeIndexInFeed) async {
if (state is! AboutRecipeStateLoaded) return;
final currentRecipe = (state as AboutRecipeStateLoaded).recipe;
if (currentRecipe.wasLikedByMe) {
final updatedRecipe = currentRecipe.copyWith(
likesCount: currentRecipe.likesCount - 1,
wasLikedByMe: false,
);
if (recipeIndexInFeed != null) {
recipeRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedRecipe.wasLikedByMe, totalLikes: updatedRecipe.likesCount, index: recipeIndexInFeed));
}
emit(AboutRecipeState.loaded(recipe: updatedRecipe));
} else {
final updatedRecipe = currentRecipe.copyWith(
likesCount: currentRecipe.likesCount + 1,
wasLikedByMe: true,
);
if (recipeIndexInFeed != null) {
recipeRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedRecipe.wasLikedByMe, totalLikes: updatedRecipe.likesCount, index: recipeIndexInFeed));
}
emit(AboutRecipeState.loaded(recipe: updatedRecipe));
}
EasyDebounce.debounce(
'like_recipe',
Duration(seconds: 1),
() async {
try {
await recipeRepository.likeRecipe(id);
} catch (e) {
log(e.toString());
if (recipeIndexInFeed != null) {
recipeRepository
.emitLikesStateToFeed(LikesState(wasLikedByMe: currentRecipe.wasLikedByMe, totalLikes: currentRecipe.likesCount, index: recipeIndexInFeed));
}
emit(AboutRecipeState.loaded(recipe: currentRecipe));
}
},
);
}
Future<void> addToFavorite(String id, int? recipeIndex) async {
if (state is! AboutRecipeStateLoaded) return;
final currentRecipe = (state as AboutRecipeStateLoaded).recipe;
final updatedRecipe = currentRecipe.copyWith(
isFavorite: true,
);
if (recipeIndex != null) {
recipeRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: updatedRecipe.isFavorite, index: recipeIndex));
}
emit(AboutRecipeState.loaded(recipe: updatedRecipe));
EasyDebounce.debounce(
'add_or_remove_diet_to_favorites',
Duration(seconds: 1),
() async {
try {
await recipeRepository.addToFavorite(id);
} catch (e) {
log(e.toString());
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
if (recipeIndex != null) {
recipeRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentRecipe.isFavorite, index: recipeIndex));
}
emit(AboutRecipeState.loaded(recipe: currentRecipe));
}
},
);
}
Future<void> removeFromFavorite(String id, int? recipeIndex) async {
if (state is! AboutRecipeStateLoaded) return;
final currentRecipe = (state as AboutRecipeStateLoaded).recipe;
final updatedRecipe = currentRecipe.copyWith(
isFavorite: false,
);
if (recipeIndex != null) {
recipeRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: updatedRecipe.isFavorite, index: recipeIndex));
}
emit(AboutRecipeState.loaded(recipe: updatedRecipe));
EasyDebounce.debounce(
'add_or_remove_diet_to_favorites',
Duration(seconds: 1),
() async {
try {
await recipeRepository.removeFromFavorite(id);
} catch (e) {
log(e.toString());
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
if (recipeIndex != null) {
recipeRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentRecipe.isFavorite, index: recipeIndex));
}
emit(AboutRecipeState.loaded(recipe: currentRecipe));
}
},
);
}
}

View File

@@ -0,0 +1,16 @@
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'about_recipe_state.freezed.dart';
@freezed
class AboutRecipeState with _$AboutRecipeState {
const factory AboutRecipeState() = _AboutRecipeState;
const factory AboutRecipeState.initial() = AboutRecipeStateInitial;
const factory AboutRecipeState.loading() = AboutRecipeStateLoading;
const factory AboutRecipeState.loaded({
required Recipe recipe,
}) = AboutRecipeStateLoaded;
const factory AboutRecipeState.error({required String message}) = RecipeStateError;
}

View File

@@ -0,0 +1,805 @@
// 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 'about_recipe_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$AboutRecipeState {
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function() $default, {
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(Recipe recipe) loaded,
required TResult Function(String message) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(Recipe recipe)? loaded,
TResult? Function(String message)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? initial,
TResult Function()? loading,
TResult Function(Recipe recipe)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_AboutRecipeState value) $default, {
required TResult Function(AboutRecipeStateInitial value) initial,
required TResult Function(AboutRecipeStateLoading value) loading,
required TResult Function(AboutRecipeStateLoaded value) loaded,
required TResult Function(RecipeStateError value) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_AboutRecipeState value)? $default, {
TResult? Function(AboutRecipeStateInitial value)? initial,
TResult? Function(AboutRecipeStateLoading value)? loading,
TResult? Function(AboutRecipeStateLoaded value)? loaded,
TResult? Function(RecipeStateError value)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_AboutRecipeState value)? $default, {
TResult Function(AboutRecipeStateInitial value)? initial,
TResult Function(AboutRecipeStateLoading value)? loading,
TResult Function(AboutRecipeStateLoaded value)? loaded,
TResult Function(RecipeStateError value)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $AboutRecipeStateCopyWith<$Res> {
factory $AboutRecipeStateCopyWith(
AboutRecipeState value, $Res Function(AboutRecipeState) then) =
_$AboutRecipeStateCopyWithImpl<$Res, AboutRecipeState>;
}
/// @nodoc
class _$AboutRecipeStateCopyWithImpl<$Res, $Val extends AboutRecipeState>
implements $AboutRecipeStateCopyWith<$Res> {
_$AboutRecipeStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of AboutRecipeState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$AboutRecipeStateImplCopyWith<$Res> {
factory _$$AboutRecipeStateImplCopyWith(_$AboutRecipeStateImpl value,
$Res Function(_$AboutRecipeStateImpl) then) =
__$$AboutRecipeStateImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$AboutRecipeStateImplCopyWithImpl<$Res>
extends _$AboutRecipeStateCopyWithImpl<$Res, _$AboutRecipeStateImpl>
implements _$$AboutRecipeStateImplCopyWith<$Res> {
__$$AboutRecipeStateImplCopyWithImpl(_$AboutRecipeStateImpl _value,
$Res Function(_$AboutRecipeStateImpl) _then)
: super(_value, _then);
/// Create a copy of AboutRecipeState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$AboutRecipeStateImpl implements _AboutRecipeState {
const _$AboutRecipeStateImpl();
@override
String toString() {
return 'AboutRecipeState()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$AboutRecipeStateImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function() $default, {
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(Recipe recipe) loaded,
required TResult Function(String message) error,
}) {
return $default();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(Recipe recipe)? loaded,
TResult? Function(String message)? error,
}) {
return $default?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? initial,
TResult Function()? loading,
TResult Function(Recipe recipe)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if ($default != null) {
return $default();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_AboutRecipeState value) $default, {
required TResult Function(AboutRecipeStateInitial value) initial,
required TResult Function(AboutRecipeStateLoading value) loading,
required TResult Function(AboutRecipeStateLoaded value) loaded,
required TResult Function(RecipeStateError value) error,
}) {
return $default(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_AboutRecipeState value)? $default, {
TResult? Function(AboutRecipeStateInitial value)? initial,
TResult? Function(AboutRecipeStateLoading value)? loading,
TResult? Function(AboutRecipeStateLoaded value)? loaded,
TResult? Function(RecipeStateError value)? error,
}) {
return $default?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_AboutRecipeState value)? $default, {
TResult Function(AboutRecipeStateInitial value)? initial,
TResult Function(AboutRecipeStateLoading value)? loading,
TResult Function(AboutRecipeStateLoaded value)? loaded,
TResult Function(RecipeStateError value)? error,
required TResult orElse(),
}) {
if ($default != null) {
return $default(this);
}
return orElse();
}
}
abstract class _AboutRecipeState implements AboutRecipeState {
const factory _AboutRecipeState() = _$AboutRecipeStateImpl;
}
/// @nodoc
abstract class _$$AboutRecipeStateInitialImplCopyWith<$Res> {
factory _$$AboutRecipeStateInitialImplCopyWith(
_$AboutRecipeStateInitialImpl value,
$Res Function(_$AboutRecipeStateInitialImpl) then) =
__$$AboutRecipeStateInitialImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$AboutRecipeStateInitialImplCopyWithImpl<$Res>
extends _$AboutRecipeStateCopyWithImpl<$Res, _$AboutRecipeStateInitialImpl>
implements _$$AboutRecipeStateInitialImplCopyWith<$Res> {
__$$AboutRecipeStateInitialImplCopyWithImpl(
_$AboutRecipeStateInitialImpl _value,
$Res Function(_$AboutRecipeStateInitialImpl) _then)
: super(_value, _then);
/// Create a copy of AboutRecipeState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$AboutRecipeStateInitialImpl implements AboutRecipeStateInitial {
const _$AboutRecipeStateInitialImpl();
@override
String toString() {
return 'AboutRecipeState.initial()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$AboutRecipeStateInitialImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function() $default, {
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(Recipe recipe) loaded,
required TResult Function(String message) error,
}) {
return initial();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(Recipe recipe)? loaded,
TResult? Function(String message)? error,
}) {
return initial?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? initial,
TResult Function()? loading,
TResult Function(Recipe recipe)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_AboutRecipeState value) $default, {
required TResult Function(AboutRecipeStateInitial value) initial,
required TResult Function(AboutRecipeStateLoading value) loading,
required TResult Function(AboutRecipeStateLoaded value) loaded,
required TResult Function(RecipeStateError value) error,
}) {
return initial(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_AboutRecipeState value)? $default, {
TResult? Function(AboutRecipeStateInitial value)? initial,
TResult? Function(AboutRecipeStateLoading value)? loading,
TResult? Function(AboutRecipeStateLoaded value)? loaded,
TResult? Function(RecipeStateError value)? error,
}) {
return initial?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_AboutRecipeState value)? $default, {
TResult Function(AboutRecipeStateInitial value)? initial,
TResult Function(AboutRecipeStateLoading value)? loading,
TResult Function(AboutRecipeStateLoaded value)? loaded,
TResult Function(RecipeStateError value)? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial(this);
}
return orElse();
}
}
abstract class AboutRecipeStateInitial implements AboutRecipeState {
const factory AboutRecipeStateInitial() = _$AboutRecipeStateInitialImpl;
}
/// @nodoc
abstract class _$$AboutRecipeStateLoadingImplCopyWith<$Res> {
factory _$$AboutRecipeStateLoadingImplCopyWith(
_$AboutRecipeStateLoadingImpl value,
$Res Function(_$AboutRecipeStateLoadingImpl) then) =
__$$AboutRecipeStateLoadingImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$AboutRecipeStateLoadingImplCopyWithImpl<$Res>
extends _$AboutRecipeStateCopyWithImpl<$Res, _$AboutRecipeStateLoadingImpl>
implements _$$AboutRecipeStateLoadingImplCopyWith<$Res> {
__$$AboutRecipeStateLoadingImplCopyWithImpl(
_$AboutRecipeStateLoadingImpl _value,
$Res Function(_$AboutRecipeStateLoadingImpl) _then)
: super(_value, _then);
/// Create a copy of AboutRecipeState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$AboutRecipeStateLoadingImpl implements AboutRecipeStateLoading {
const _$AboutRecipeStateLoadingImpl();
@override
String toString() {
return 'AboutRecipeState.loading()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$AboutRecipeStateLoadingImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function() $default, {
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(Recipe recipe) loaded,
required TResult Function(String message) error,
}) {
return loading();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(Recipe recipe)? loaded,
TResult? Function(String message)? error,
}) {
return loading?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? initial,
TResult Function()? loading,
TResult Function(Recipe recipe)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_AboutRecipeState value) $default, {
required TResult Function(AboutRecipeStateInitial value) initial,
required TResult Function(AboutRecipeStateLoading value) loading,
required TResult Function(AboutRecipeStateLoaded value) loaded,
required TResult Function(RecipeStateError value) error,
}) {
return loading(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_AboutRecipeState value)? $default, {
TResult? Function(AboutRecipeStateInitial value)? initial,
TResult? Function(AboutRecipeStateLoading value)? loading,
TResult? Function(AboutRecipeStateLoaded value)? loaded,
TResult? Function(RecipeStateError value)? error,
}) {
return loading?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_AboutRecipeState value)? $default, {
TResult Function(AboutRecipeStateInitial value)? initial,
TResult Function(AboutRecipeStateLoading value)? loading,
TResult Function(AboutRecipeStateLoaded value)? loaded,
TResult Function(RecipeStateError value)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading(this);
}
return orElse();
}
}
abstract class AboutRecipeStateLoading implements AboutRecipeState {
const factory AboutRecipeStateLoading() = _$AboutRecipeStateLoadingImpl;
}
/// @nodoc
abstract class _$$AboutRecipeStateLoadedImplCopyWith<$Res> {
factory _$$AboutRecipeStateLoadedImplCopyWith(
_$AboutRecipeStateLoadedImpl value,
$Res Function(_$AboutRecipeStateLoadedImpl) then) =
__$$AboutRecipeStateLoadedImplCopyWithImpl<$Res>;
@useResult
$Res call({Recipe recipe});
$RecipeCopyWith<$Res> get recipe;
}
/// @nodoc
class __$$AboutRecipeStateLoadedImplCopyWithImpl<$Res>
extends _$AboutRecipeStateCopyWithImpl<$Res, _$AboutRecipeStateLoadedImpl>
implements _$$AboutRecipeStateLoadedImplCopyWith<$Res> {
__$$AboutRecipeStateLoadedImplCopyWithImpl(
_$AboutRecipeStateLoadedImpl _value,
$Res Function(_$AboutRecipeStateLoadedImpl) _then)
: super(_value, _then);
/// Create a copy of AboutRecipeState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? recipe = null,
}) {
return _then(_$AboutRecipeStateLoadedImpl(
recipe: null == recipe
? _value.recipe
: recipe // ignore: cast_nullable_to_non_nullable
as Recipe,
));
}
/// Create a copy of AboutRecipeState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$RecipeCopyWith<$Res> get recipe {
return $RecipeCopyWith<$Res>(_value.recipe, (value) {
return _then(_value.copyWith(recipe: value));
});
}
}
/// @nodoc
class _$AboutRecipeStateLoadedImpl implements AboutRecipeStateLoaded {
const _$AboutRecipeStateLoadedImpl({required this.recipe});
@override
final Recipe recipe;
@override
String toString() {
return 'AboutRecipeState.loaded(recipe: $recipe)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$AboutRecipeStateLoadedImpl &&
(identical(other.recipe, recipe) || other.recipe == recipe));
}
@override
int get hashCode => Object.hash(runtimeType, recipe);
/// Create a copy of AboutRecipeState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$AboutRecipeStateLoadedImplCopyWith<_$AboutRecipeStateLoadedImpl>
get copyWith => __$$AboutRecipeStateLoadedImplCopyWithImpl<
_$AboutRecipeStateLoadedImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function() $default, {
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(Recipe recipe) loaded,
required TResult Function(String message) error,
}) {
return loaded(recipe);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(Recipe recipe)? loaded,
TResult? Function(String message)? error,
}) {
return loaded?.call(recipe);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? initial,
TResult Function()? loading,
TResult Function(Recipe recipe)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(recipe);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_AboutRecipeState value) $default, {
required TResult Function(AboutRecipeStateInitial value) initial,
required TResult Function(AboutRecipeStateLoading value) loading,
required TResult Function(AboutRecipeStateLoaded value) loaded,
required TResult Function(RecipeStateError value) error,
}) {
return loaded(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_AboutRecipeState value)? $default, {
TResult? Function(AboutRecipeStateInitial value)? initial,
TResult? Function(AboutRecipeStateLoading value)? loading,
TResult? Function(AboutRecipeStateLoaded value)? loaded,
TResult? Function(RecipeStateError value)? error,
}) {
return loaded?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_AboutRecipeState value)? $default, {
TResult Function(AboutRecipeStateInitial value)? initial,
TResult Function(AboutRecipeStateLoading value)? loading,
TResult Function(AboutRecipeStateLoaded value)? loaded,
TResult Function(RecipeStateError value)? error,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(this);
}
return orElse();
}
}
abstract class AboutRecipeStateLoaded implements AboutRecipeState {
const factory AboutRecipeStateLoaded({required final Recipe recipe}) =
_$AboutRecipeStateLoadedImpl;
Recipe get recipe;
/// Create a copy of AboutRecipeState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$AboutRecipeStateLoadedImplCopyWith<_$AboutRecipeStateLoadedImpl>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$RecipeStateErrorImplCopyWith<$Res> {
factory _$$RecipeStateErrorImplCopyWith(_$RecipeStateErrorImpl value,
$Res Function(_$RecipeStateErrorImpl) then) =
__$$RecipeStateErrorImplCopyWithImpl<$Res>;
@useResult
$Res call({String message});
}
/// @nodoc
class __$$RecipeStateErrorImplCopyWithImpl<$Res>
extends _$AboutRecipeStateCopyWithImpl<$Res, _$RecipeStateErrorImpl>
implements _$$RecipeStateErrorImplCopyWith<$Res> {
__$$RecipeStateErrorImplCopyWithImpl(_$RecipeStateErrorImpl _value,
$Res Function(_$RecipeStateErrorImpl) _then)
: super(_value, _then);
/// Create a copy of AboutRecipeState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? message = null,
}) {
return _then(_$RecipeStateErrorImpl(
message: null == message
? _value.message
: message // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
class _$RecipeStateErrorImpl implements RecipeStateError {
const _$RecipeStateErrorImpl({required this.message});
@override
final String message;
@override
String toString() {
return 'AboutRecipeState.error(message: $message)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$RecipeStateErrorImpl &&
(identical(other.message, message) || other.message == message));
}
@override
int get hashCode => Object.hash(runtimeType, message);
/// Create a copy of AboutRecipeState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$RecipeStateErrorImplCopyWith<_$RecipeStateErrorImpl> get copyWith =>
__$$RecipeStateErrorImplCopyWithImpl<_$RecipeStateErrorImpl>(
this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function() $default, {
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(Recipe recipe) loaded,
required TResult Function(String message) error,
}) {
return error(message);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(Recipe recipe)? loaded,
TResult? Function(String message)? error,
}) {
return error?.call(message);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? initial,
TResult Function()? loading,
TResult Function(Recipe recipe)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(message);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_AboutRecipeState value) $default, {
required TResult Function(AboutRecipeStateInitial value) initial,
required TResult Function(AboutRecipeStateLoading value) loading,
required TResult Function(AboutRecipeStateLoaded value) loaded,
required TResult Function(RecipeStateError value) error,
}) {
return error(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_AboutRecipeState value)? $default, {
TResult? Function(AboutRecipeStateInitial value)? initial,
TResult? Function(AboutRecipeStateLoading value)? loading,
TResult? Function(AboutRecipeStateLoaded value)? loaded,
TResult? Function(RecipeStateError value)? error,
}) {
return error?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_AboutRecipeState value)? $default, {
TResult Function(AboutRecipeStateInitial value)? initial,
TResult Function(AboutRecipeStateLoading value)? loading,
TResult Function(AboutRecipeStateLoaded value)? loaded,
TResult Function(RecipeStateError value)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(this);
}
return orElse();
}
}
abstract class RecipeStateError implements AboutRecipeState {
const factory RecipeStateError({required final String message}) =
_$RecipeStateErrorImpl;
String get message;
/// Create a copy of AboutRecipeState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$RecipeStateErrorImplCopyWith<_$RecipeStateErrorImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -0,0 +1,59 @@
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/main_background_bouble.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
class IngredientsWidget extends StatelessWidget {
const IngredientsWidget({super.key, required this.recipe});
final Recipe recipe;
@override
Widget build(BuildContext context) {
final s = S.of(context);
return MainBackgroundBouble(
child: ExpansionTile(
initiallyExpanded: true,
shape: Border(),
title: Text(
s.ingredients,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
children: List.generate(recipe.ingredients!.length, (index) {
return Padding(
padding: const EdgeInsets.only(left: 16, bottom: 8),
child: Row(
children: [
Container(
width: 4,
height: 4,
decoration: BoxDecoration(
color: appTheme.black,
shape: BoxShape.circle,
),
),
SizedBox(width: 4),
Builder(
builder: (context) {
final portion = Portion.getPortionFromValue(recipe.ingredients![index].unit ?? '');
String? portionName;
if (portion != null) {
portionName = getLocalizedMeasurementUnits(s, portion);
}
return Text(
'${recipe.ingredients![index].name} - ${recipe.ingredients![index].portion} ${portionName}',
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
);
},
),
],
),
);
}),
),
);
}
}

View File

@@ -0,0 +1,63 @@
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/widgets.dart';
class StepsWidget extends StatelessWidget {
const StepsWidget({super.key, required this.recipe});
final Recipe recipe;
@override
Widget build(BuildContext context) {
final s = S.of(context);
return MainBackgroundBouble(
padding: EdgeInsets.all(16),
child: Column(
spacing: 16,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
s.step_by_step_photo_recipe,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
...List.generate(recipe.steps?.length ?? 0, (index) {
return Column(
spacing: 8,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: appTheme.blueA400,
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Text(
'${s.step} ${index + 1}',
style: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
),
),
if (recipe.steps?[index].image != null && recipe.steps![index].image.isNotEmpty)
AspectRatio(
aspectRatio: 16 / 12,
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.network(
baseUrl + '/storage/' + recipe.steps![index].image,
fit: BoxFit.cover,
),
),
),
Text(
recipe.steps![index].description,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
],
);
}),
],
),
);
}
}

View File

@@ -0,0 +1,53 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:async';
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart';
class AboutRecipeRepository {
final AboutRecipeService recipeService;
final _recipeLikeStateStreamController = StreamController<LikesState>.broadcast();
final _recipeFavoriteStateStreamController = StreamController<FavoriteState>.broadcast();
final _recipeCommentsStateStreamController = StreamController<CommentsState>.broadcast();
Stream get recipeLikeState => _recipeLikeStateStreamController.stream;
Stream get recipeFavoriteState => _recipeFavoriteStateStreamController.stream;
Stream get recipeCommentsState => _recipeCommentsStateStreamController.stream;
AboutRecipeRepository({required this.recipeService});
Future<Recipe> getRecipe(String recipeID) async {
try {
return await recipeService.getRecipe(recipeID);
} catch (e) {
rethrow;
}
}
Future<void> likeRecipe(String slug) async {
try {
await recipeService.likeRecipe(slug: slug);
} catch (e) {
rethrow;
}
}
Future<void> addToFavorite(String slug) async {
try {
await recipeService.addToFavorite(slug: slug);
} catch (e) {
rethrow;
}
}
Future<void> removeFromFavorite(String slug) async {
try {
await recipeService.removeFromFavorite(slug: slug);
} catch (e) {
rethrow;
}
}
void emitLikesStateToFeed(LikesState state) => _recipeLikeStateStreamController.add(state);
void emitFavoriteStateToFeed(FavoriteState state) => _recipeFavoriteStateStreamController.add(state);
void emitCommentsStateToFeed(CommentsState state) => _recipeCommentsStateStreamController.add(state);
}

View File

@@ -0,0 +1,28 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:retrofit/retrofit.dart';
part 'about_recipe_service.g.dart';
@RestApi()
abstract class AboutRecipeService {
factory AboutRecipeService(Dio dio, {String baseUrl}) = _AboutRecipeService;
@POST('/api/recipes/{slug}/like')
Future<void> likeRecipe({
@Path() required String slug,
});
@POST('/api/recipes/{slug}/add_to_favorite')
Future<void> addToFavorite({
@Path() required String slug,
});
@POST('/api/recipes/{slug}/remove_from_favorite')
Future<void> removeFromFavorite({
@Path() required String slug,
});
@GET('/api/recipes/show/{id}')
Future<Recipe> getRecipe(@Path() String id);
}

View File

@@ -0,0 +1,130 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'about_recipe_service.dart';
// **************************************************************************
// RetrofitGenerator
// **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations
class _AboutRecipeService implements AboutRecipeService {
_AboutRecipeService(this._dio, {this.baseUrl, this.errorLogger});
final Dio _dio;
String? baseUrl;
final ParseErrorLogger? errorLogger;
@override
Future<void> likeRecipe({required String slug}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<void>(
Options(method: 'POST', headers: _headers, extra: _extra)
.compose(
_dio.options,
'/api/recipes/${slug}/like',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
await _dio.fetch<void>(_options);
}
@override
Future<void> addToFavorite({required String slug}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<void>(
Options(method: 'POST', headers: _headers, extra: _extra)
.compose(
_dio.options,
'/api/recipes/${slug}/add_to_favorite',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
await _dio.fetch<void>(_options);
}
@override
Future<void> removeFromFavorite({required String slug}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<void>(
Options(method: 'POST', headers: _headers, extra: _extra)
.compose(
_dio.options,
'/api/recipes/${slug}/remove_from_favorite',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
await _dio.fetch<void>(_options);
}
@override
Future<Recipe> getRecipe(String id) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<Recipe>(
Options(method: 'GET', headers: _headers, extra: _extra)
.compose(
_dio.options,
'/api/recipes/show/${id}',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
);
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late Recipe _value;
try {
_value = Recipe.fromJson(_result.data!);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
return _value;
}
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes ||
requestOptions.responseType == ResponseType.stream)) {
if (T == String) {
requestOptions.responseType = ResponseType.plain;
} else {
requestOptions.responseType = ResponseType.json;
}
}
return requestOptions;
}
String _combineBaseUrls(String dioBaseUrl, String? baseUrl) {
if (baseUrl == null || baseUrl.trim().isEmpty) {
return dioBaseUrl;
}
final url = Uri.parse(baseUrl);
if (url.isAbsolute) {
return url.toString();
}
return Uri.parse(dioBaseUrl).resolveUri(url).toString();
}
}

View File

@@ -1,430 +0,0 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/feed/models/recipes_model.dart' as step;
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_state.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:go_router/go_router.dart';
class AboutRecipesScreen extends StatefulWidget {
final Recipes recipes;
const AboutRecipesScreen({super.key, required this.recipes});
@override
State<AboutRecipesScreen> createState() => _AboutRecipesScreenState();
}
class _AboutRecipesScreenState extends State<AboutRecipesScreen> {
final CommentsCubit commentsCubit = sl<CommentsCubit>();
final textEditingController = TextEditingController();
int? replyId;
@override
Widget build(BuildContext context) {
return BlocProvider<CommentsCubit>(
create: (BuildContext context) => commentsCubit
..getComments(
widget.recipes.slug,
CommentSourceType.recipe,
true,
),
child: BlocConsumer<CommentsCubit, CommentsState>(
listener: (BuildContext context, CommentsState state) {
if (state is CommentsStateLoaded) {
setState(() {
replyId = state.replyId;
});
}
},
builder: (BuildContext context, CommentsState state) {
return Scaffold(
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.withValues(alpha: 0.1),
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (replyId != null)
GestureDetector(
onTap: () {
context.read<CommentsCubit>().setReplyId(null);
setState(() {
replyId = null;
textEditingController.clear();
});
FocusManager.instance.primaryFocus?.unfocus();
},
child: Container(
decoration: BoxDecoration(
color: appTheme.blueA400,
shape: BoxShape.circle,
),
height: 32,
width: 32,
child: Icon(
Icons.close_rounded,
size: 18,
color: Colors.white,
),
),
),
if (replyId != null) SizedBox(width: 8),
SizedBox(
width: MediaQuery.of(context).size.width - (replyId == null ? 64 : 104),
child: TextFormField(
onChanged: (_) {
setState(() {});
},
controller: textEditingController,
maxLines: 5,
minLines: 1,
keyboardType: TextInputType.multiline,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: Colors.black,
height: 1,
),
decoration: InputDecoration(
isDense: true,
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
hintText: 'Комментарий',
hintStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
height: 1,
color: Color(0xFFAEAEB2),
),
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: () {
if (textEditingController.text.length > 2) {
commentsCubit.sendComment(
id: widget.recipes.slug,
commentText: textEditingController.text,
parentCommentId: replyId,
commentSourceType: CommentSourceType.recipe,
);
context.read<CommentsCubit>().setReplyId(null);
setState(() {
textEditingController.clear();
replyId = null;
});
FocusManager.instance.primaryFocus?.unfocus();
}
},
child: Container(
decoration: BoxDecoration(
color: textEditingController.text.length > 2 ? appTheme.blueA400 : Colors.grey,
shape: BoxShape.circle,
),
height: 32,
width: 32,
child: Icon(
Icons.send_rounded,
size: 18,
color: Colors.white,
),
),
),
],
),
),
appBar: AppBar(
actions: [
InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () => GoRouter.of(context).pushNamed('profileScreen', extra: widget.recipes.author),
child: Ink(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleAvatar(
maxRadius: 16,
minRadius: 16,
backgroundImage: NetworkImage(widget.recipes.author.authorProfileIcon),
),
SizedBox(height: 4),
Text(
widget.recipes.author.authorName,
style: TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1,
),
),
],
),
),
),
],
),
body: SingleChildScrollView(
padding: EdgeInsets.only(bottom: 80),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
widget.recipes.title ?? '',
style: TextStyle(
color: Colors.black,
fontSize: 24,
fontWeight: FontWeight.w500,
height: 1,
),
),
),
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
Icon(Icons.timer_outlined),
SizedBox(width: 4),
Text(
widget.recipes.cookingTime.toString() + ' минут',
style: TextStyle(
color: Colors.black,
fontSize: 12,
fontWeight: FontWeight.w400,
height: 1,
),
),
SizedBox(width: 16),
Icon(Icons.fast_forward_outlined),
SizedBox(width: 4),
Text(
'${widget.recipes.steps?.length ?? 0}' + ' этапов',
style: TextStyle(
color: Colors.black,
fontSize: 12,
fontWeight: FontWeight.w400,
height: 1,
),
),
],
),
),
if (widget.recipes.description.isNotEmpty)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Html(
data: widget.recipes.description,
style: {
"*": Style(
color: Colors.black87,
),
},
),
),
SizedBox(height: 12),
CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + widget.recipes.image,
),
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
'Ингредиенты',
style: TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w500,
height: 1,
),
),
),
SizedBox(height: 12),
ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 16),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
if (widget.recipes.ingredients != null) {
return _ingredient(widget.recipes.ingredients![index]);
}
return SizedBox.shrink();
},
separatorBuilder: (context, index) => SizedBox(height: 4),
itemCount: widget.recipes.ingredients?.length ?? 0,
),
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
'Пошаговый рецепт',
style: TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w500,
height: 1,
),
),
),
SizedBox(height: 12),
ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
if (widget.recipes.steps == null) return SizedBox.shrink();
return _step(widget.recipes.steps![index], index + 1);
},
separatorBuilder: (context, index) => Padding(
padding: const EdgeInsets.all(16),
child: Container(
height: 0.4,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(16),
),
),
),
itemCount: widget.recipes.steps?.length ?? 0,
),
SizedBox(height: 8),
Padding(
padding: const EdgeInsets.all(16),
child: Container(
height: 0.4,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(16),
),
),
),
SizedBox(height: 8),
Container(
padding: EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.centerLeft,
child: Text(
widget.recipes.commentsCount.toString() + ' КОММЕНТАРИЕВ',
style: TextStyle(
fontSize: 12,
color: appTheme.gray500,
fontWeight: FontWeight.w400,
),
),
),
SizedBox(height: 16),
if (state is CommentsStateLoading)
Padding(
padding: const EdgeInsets.all(32),
child: const Loader(),
),
if (state is CommentsStateLoaded)
CommentsWidget(
id: widget.recipes.slug,
commentSourceType: CommentSourceType.recipe,
comment: state.comment,
replyId: replyId,
),
],
),
),
);
},
),
);
}
_ingredient(Ingredient ingredient) {
return Text(
'• ' + ingredient.name + ' ' + ingredient.portion + ' ' + (ingredient.unit ?? ''),
style: TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.2,
),
);
}
_step(step.Step step, int stepNumber) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
'Шаг ' + stepNumber.toString(),
style: TextStyle(
color: Colors.black54,
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1,
),
),
),
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
step.description,
style: TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.2,
),
),
),
SizedBox(height: 8),
if (step.image.length > 1)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: CachedNetworkImage(imageUrl: baseUrl + '/storage/' + step.image),
),
),
],
);
}
}

View File

@@ -1,30 +0,0 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/recipes/models/recipe_model.dart';
import 'package:eatvio_app/features/recipes/models/recipe_tag_model.dart';
import 'package:retrofit/retrofit.dart';
import 'package:eatvio_app/features/recipes/models/product.dart';
part 'recipe_service.g.dart';
@RestApi()
abstract class RecipeService {
factory RecipeService(Dio dio, {String baseUrl}) = _RecipeService;
@GET('/api/recipes/show/{id}')
Future<RecipeModel> getRecipe(@Path() String id);
@GET('/api/diary/product_nutritional_values/filter')
Future<Map<String, List<Product>>> getInputProduct(@Query('name') String name);
@POST('/api/recipes/create')
@MultiPart()
Future<void> publishRecipe({
@Body() required FormData formData,
});
@POST('/api/recipes/update/{id}')
Future<RecipeModel> editRecipe(@Part() Map<String, Object?> recipe, String id);
@GET('/api/recipes/tags')
Future<RecipeTagModel> getTags();
}

View File

@@ -24,7 +24,10 @@ class SupportScreen extends StatelessWidget {
),
InkWell(
onTap: () {
launchUrl(Uri.parse('https://t.me/eatvio'));
launchUrl(
Uri.parse('https://t.me/eatvio'),
mode: LaunchMode.externalApplication,
);
},
borderRadius: BorderRadius.circular(16),
child: Ink(
@@ -53,7 +56,10 @@ class SupportScreen extends StatelessWidget {
const SizedBox(height: 8),
InkWell(
onTap: () {
launchUrl(Uri.parse('mailto:help@eatv.io'));
launchUrl(
Uri.parse('mailto:help@eatv.io'),
mode: LaunchMode.externalApplication,
);
},
borderRadius: BorderRadius.circular(16),
child: Ink(

View File

@@ -39,10 +39,8 @@ MessageLookupByLibrary? _findExact(String localeName) {
/// User programs should call this before using [localeName] for messages.
Future<bool> initializeMessages(String localeName) {
var availableLocale = Intl.verifiedLocale(
localeName,
(locale) => _deferredLibraries[locale] != null,
onFailure: (_) => null,
);
localeName, (locale) => _deferredLibraries[locale] != null,
onFailure: (_) => null);
if (availableLocale == null) {
return new SynchronousFuture(false);
}
@@ -62,11 +60,8 @@ bool _messagesExistFor(String locale) {
}
MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) {
var actualLocale = Intl.verifiedLocale(
locale,
_messagesExistFor,
onFailure: (_) => null,
);
var actualLocale =
Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null);
if (actualLocale == null) return null;
return _findExact(actualLocale);
}

View File

@@ -22,375 +22,350 @@ class MessageLookup extends MessageLookupByLibrary {
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"Moscow": MessageLookupByLibrary.simpleMessage("Москва"),
"Russia": MessageLookupByLibrary.simpleMessage("Россия"),
"about_diet": MessageLookupByLibrary.simpleMessage("Об этой диете"),
"about_plan": MessageLookupByLibrary.simpleMessage("Об этом плане"),
"active": MessageLookupByLibrary.simpleMessage("Активен"),
"active_diet_state": MessageLookupByLibrary.simpleMessage("Активная"),
"add": MessageLookupByLibrary.simpleMessage("Добавить"),
"add_cover": MessageLookupByLibrary.simpleMessage("Добавьте обложку"),
"add_measure": MessageLookupByLibrary.simpleMessage("Добавить замер"),
"adding_plan": MessageLookupByLibrary.simpleMessage("Добавление плана"),
"additional_information": MessageLookupByLibrary.simpleMessage(
"Дополнительная информация",
),
"admin": MessageLookupByLibrary.simpleMessage("Админ"),
"all": MessageLookupByLibrary.simpleMessage("Все"),
"allergies_presence": MessageLookupByLibrary.simpleMessage(
"Наличие аллергии",
),
"another_form_of_comment": MessageLookupByLibrary.simpleMessage(
"комментария",
),
"another_form_of_day": MessageLookupByLibrary.simpleMessage("дня"),
"answer": MessageLookupByLibrary.simpleMessage("Ответить"),
"archived": MessageLookupByLibrary.simpleMessage("В архиве"),
"authentificate": MessageLookupByLibrary.simpleMessage("Войти"),
"average_pressure": MessageLookupByLibrary.simpleMessage(
"Средние показатели давления:",
),
"bodymass_chart": MessageLookupByLibrary.simpleMessage("График ИМТ"),
"by_default": MessageLookupByLibrary.simpleMessage("По умолчянию"),
"calories": MessageLookupByLibrary.simpleMessage("Калории"),
"carb_type": MessageLookupByLibrary.simpleMessage("Мучное"),
"carb_type_desc": MessageLookupByLibrary.simpleMessage(
"Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы.",
),
"carbohydrates": MessageLookupByLibrary.simpleMessage("Углеводы"),
"chat": MessageLookupByLibrary.simpleMessage("Чат"),
"chest": MessageLookupByLibrary.simpleMessage("Грудь"),
"chest_circumference": MessageLookupByLibrary.simpleMessage("Обхват груди"),
"choose_diet_duration": MessageLookupByLibrary.simpleMessage(
"Выберите срок диеты",
),
"choose_plan_duration": MessageLookupByLibrary.simpleMessage(
"Выберите срок плана",
),
"choose_start_diet_date": MessageLookupByLibrary.simpleMessage(
"Выберите дату старты диеты. Она отобразиться у Вас в дневнике",
),
"circumference_empty": MessageLookupByLibrary.simpleMessage(
"Все замеры обязательны к заполнению",
),
"circumference_is_not_a_number": MessageLookupByLibrary.simpleMessage(
"Все замеры должны быть числами",
),
"circumferences_chart": MessageLookupByLibrary.simpleMessage(
"График обхватов",
),
"city": MessageLookupByLibrary.simpleMessage("Город"),
"city_of_living": MessageLookupByLibrary.simpleMessage("Город проживания"),
"comfort": MessageLookupByLibrary.simpleMessage("Комфорт"),
"comment": MessageLookupByLibrary.simpleMessage("комментарий"),
"comments": MessageLookupByLibrary.simpleMessage("Комментарии"),
"comments_plural": MessageLookupByLibrary.simpleMessage("комментариев"),
"complain": MessageLookupByLibrary.simpleMessage("Пожаловаться"),
"continueButton": MessageLookupByLibrary.simpleMessage("Продолжить"),
"continue_": MessageLookupByLibrary.simpleMessage("Продолжить"),
"copied": MessageLookupByLibrary.simpleMessage("cкопирован"),
"copy_link": MessageLookupByLibrary.simpleMessage("Скопировать ссылку"),
"country": MessageLookupByLibrary.simpleMessage("Страна"),
"create_diet": MessageLookupByLibrary.simpleMessage("Создание диеты"),
"customer_price": MessageLookupByLibrary.simpleMessage(
"Цена для покупателя",
),
"date_of_birth": MessageLookupByLibrary.simpleMessage("Дата рождения"),
"day": MessageLookupByLibrary.simpleMessage("День"),
"days": MessageLookupByLibrary.simpleMessage("дней"),
"deleted": MessageLookupByLibrary.simpleMessage("Удалена"),
"diary": MessageLookupByLibrary.simpleMessage("Дневник"),
"diet": MessageLookupByLibrary.simpleMessage("Диета"),
"diet_author": MessageLookupByLibrary.simpleMessage("Автор диеты"),
"diet_plan": MessageLookupByLibrary.simpleMessage("План диеты"),
"diet_state": MessageLookupByLibrary.simpleMessage("Статус диеты"),
"diet_type": MessageLookupByLibrary.simpleMessage("Тип диеты"),
"do_you_have_hypertension_tendency": MessageLookupByLibrary.simpleMessage(
"Есть ли у вас гипертония?",
),
"do_you_have_hypotension_tendency": MessageLookupByLibrary.simpleMessage(
"Есть ли у вас гипотония?",
),
"do_you_take_pills": MessageLookupByLibrary.simpleMessage(
"Принимаете ли вы лекарства или бады?",
),
"down_pressure": MessageLookupByLibrary.simpleMessage(
"Нижние показатели давления:",
),
"draft": MessageLookupByLibrary.simpleMessage("Черновик"),
"drafted": MessageLookupByLibrary.simpleMessage("Черновики"),
"eating_behavior": MessageLookupByLibrary.simpleMessage(
"Пищевое поведение",
),
"eating_behavior_type": MessageLookupByLibrary.simpleMessage(
"Формирование пищевого поведения",
),
"eating_behavior_type_desc": MessageLookupByLibrary.simpleMessage(
"Формирование здорового пищевого поведения — это процесс, который включает в себя изменение привычек и отношения к еде. Он направлен на то, чтобы помочь людям научиться правильно питаться и поддерживать здоровый образ жизни.Основные принципы формирования здорового пищевого поведения: \n● Разнообразие продуктов; \n● Сбалансированный рацион; \n● Питьевой режим; \n● Ограничение вредных продуктов; \n● Регулярные приёмы пищи; \n● Умеренность;Осознанное питание; \n● Физическая активность;",
),
"economy": MessageLookupByLibrary.simpleMessage("Эконом"),
"edit": MessageLookupByLibrary.simpleMessage("Редактировать"),
"email": MessageLookupByLibrary.simpleMessage("Электронная почта"),
"email_empty": MessageLookupByLibrary.simpleMessage(
"Необходимо ввести адрес электронной почты",
),
"email_invalid": MessageLookupByLibrary.simpleMessage(
"Некорректный адрес электронной почты",
),
"enter_additional_information": MessageLookupByLibrary.simpleMessage(
"Введите любую дополнительную информацию на своё усмотрение:",
),
"enter_diet_description": MessageLookupByLibrary.simpleMessage(
"Введите описание диеты",
),
"enter_diet_name": MessageLookupByLibrary.simpleMessage(
"Выведите название диеты",
),
"enter_information": MessageLookupByLibrary.simpleMessage(
"Введите информацию...",
),
"exit": MessageLookupByLibrary.simpleMessage("Выйти"),
"expand": MessageLookupByLibrary.simpleMessage("Показать полностью"),
"fats": MessageLookupByLibrary.simpleMessage("Жиры"),
"favorite_foods": MessageLookupByLibrary.simpleMessage("Любимые продукты"),
"favorite_unfavorite_foods": MessageLookupByLibrary.simpleMessage(
"Ваши любимые/нелюбимые продукты питания?",
),
"female": MessageLookupByLibrary.simpleMessage("Женский"),
"fill_in_information": MessageLookupByLibrary.simpleMessage(
"Заполните информацию:",
),
"finish": MessageLookupByLibrary.simpleMessage("Завершить"),
"first_name": MessageLookupByLibrary.simpleMessage("Имя"),
"first_name_empty": MessageLookupByLibrary.simpleMessage(
"Имя обязательно для заполнения",
),
"first_name_short": MessageLookupByLibrary.simpleMessage(
"В имени должен быть минимум 1 символ",
),
"fit_type": MessageLookupByLibrary.simpleMessage("Поддержание формы"),
"fit_type_desc": MessageLookupByLibrary.simpleMessage(
"План питания для поддержания формы сбалансированный и разнообразный, чтобы обеспечить организм всеми необходимыми питательными веществами. Общие принципы: \n● Дробное питание; \n● Разнообразие продуктов; \n● Контроль порций; \n● Питьевой режим; \n● Ограничение вредных продуктов;",
),
"fitness": MessageLookupByLibrary.simpleMessage("Поддержка формы"),
"from": MessageLookupByLibrary.simpleMessage("от"),
"from_a_to_z": MessageLookupByLibrary.simpleMessage("Название: А-Я"),
"from_z_to_a": MessageLookupByLibrary.simpleMessage("Название: Я-А"),
"gallery": MessageLookupByLibrary.simpleMessage("Галерея"),
"general_info": MessageLookupByLibrary.simpleMessage("Общая информация"),
"gr": MessageLookupByLibrary.simpleMessage("гр"),
"grams": MessageLookupByLibrary.simpleMessage("гр"),
"has_alergy": MessageLookupByLibrary.simpleMessage("Наличие аллергии"),
"has_hyper": MessageLookupByLibrary.simpleMessage(
"Склонность к гипертонии",
),
"has_hypo": MessageLookupByLibrary.simpleMessage("Склонность к гипотонии"),
"height": MessageLookupByLibrary.simpleMessage("Рост"),
"height_measurements": MessageLookupByLibrary.simpleMessage("Замер роста"),
"hide": MessageLookupByLibrary.simpleMessage("Свернуть"),
"hip_circumference": MessageLookupByLibrary.simpleMessage("Обхват бедер"),
"hips": MessageLookupByLibrary.simpleMessage("Бедра"),
"in_progress": MessageLookupByLibrary.simpleMessage("В процессе"),
"inactive_diet_state": MessageLookupByLibrary.simpleMessage("Черновик"),
"initial_measurements": MessageLookupByLibrary.simpleMessage(
"Первичные замеры",
),
"label": MessageLookupByLibrary.simpleMessage("Название"),
"language": MessageLookupByLibrary.simpleMessage("Язык"),
"last_name": MessageLookupByLibrary.simpleMessage("Фамилия"),
"last_name_empty": MessageLookupByLibrary.simpleMessage(
"Фамилия обязательна для заполнения",
),
"last_name_short": MessageLookupByLibrary.simpleMessage(
"В фамилии должен быть минимум 6 символов",
),
"lbl_english": MessageLookupByLibrary.simpleMessage("English"),
"light_type": MessageLookupByLibrary.simpleMessage("Легкая"),
"light_type_desc": MessageLookupByLibrary.simpleMessage(
"С некоторыми видами диет можно употреблять алкоголь в умеренных количествах. Рекомендуется совмещать вместе с низкокалорийными закусками.",
),
"liked_groceries": MessageLookupByLibrary.simpleMessage("Любимые продукты"),
"link_was_copied": MessageLookupByLibrary.simpleMessage(
"Ссылка успешно скопирована",
),
"loading": MessageLookupByLibrary.simpleMessage("Загрузка..."),
"lower": MessageLookupByLibrary.simpleMessage("Нижнее"),
"male": MessageLookupByLibrary.simpleMessage("Мужской"),
"meal": MessageLookupByLibrary.simpleMessage("Приём пищи"),
"meat": MessageLookupByLibrary.simpleMessage("Мясо"),
"meat_type": MessageLookupByLibrary.simpleMessage("С мясом"),
"meat_type_desc": MessageLookupByLibrary.simpleMessage(
"Диета с мясом полезная и сбалансированная. Включает в себя нежирное мясо: курицу, индейку, кролика или говядину. В сочетании с овощами получаете все необходимые витамины и минералы. Не забывайте о порциях и старайтесь есть медленно, прислушиваясь к своему организму.",
),
"milliliter": MessageLookupByLibrary.simpleMessage("мл"),
"minimal_customer_price": MessageLookupByLibrary.simpleMessage(
"Минимальная цена для покупателя: 1200₽",
),
"most_new": MessageLookupByLibrary.simpleMessage("Самые новые"),
"most_old": MessageLookupByLibrary.simpleMessage("Самые старые"),
"msg_network_err": MessageLookupByLibrary.simpleMessage("Сетевая ошибка!"),
"msg_something_went_wrong": MessageLookupByLibrary.simpleMessage(
"Что то пошло не так!",
),
"my_blog": MessageLookupByLibrary.simpleMessage("Мой блог"),
"my_diets": MessageLookupByLibrary.simpleMessage("Мои диеты"),
"my_plans": MessageLookupByLibrary.simpleMessage("Мои планы"),
"my_settings": MessageLookupByLibrary.simpleMessage("Мои настройки"),
"name_empty": MessageLookupByLibrary.simpleMessage(
"Имя пользователя обязательно для заполнения",
),
"name_short": MessageLookupByLibrary.simpleMessage(
"В имени пользователя должно быть минимум 6 символов",
),
"no": MessageLookupByLibrary.simpleMessage("Нет"),
"no_account": MessageLookupByLibrary.simpleMessage("У меня нет аккаунта"),
"not_liked_groceries": MessageLookupByLibrary.simpleMessage(
"Не любимые продукты",
),
"one_option": MessageLookupByLibrary.simpleMessage("1 опция"),
"option": MessageLookupByLibrary.simpleMessage("Опция"),
"option_content": MessageLookupByLibrary.simpleMessage("Содержание опции"),
"option_content_hint_text": MessageLookupByLibrary.simpleMessage(
"На связи 7 дней в неделю",
),
"option_desc": MessageLookupByLibrary.simpleMessage("Описание опции"),
"option_desc_hint_text": MessageLookupByLibrary.simpleMessage(
"Этот план питания для похудения на 7 дней сбалансированный и разнообразный",
),
"option_duration": MessageLookupByLibrary.simpleMessage(
"Продолжительность опции",
),
"option_personal_diet_desc": MessageLookupByLibrary.simpleMessage(
"Опция подразумевает создание индивидуальной диеты",
),
"option_title": MessageLookupByLibrary.simpleMessage("Заголовок опции"),
"order": MessageLookupByLibrary.simpleMessage("Порядок"),
"other_info": MessageLookupByLibrary.simpleMessage("Прочая информация"),
"password": MessageLookupByLibrary.simpleMessage("Пароль"),
"password_confirmation": MessageLookupByLibrary.simpleMessage(
"Подтвердите пароль",
),
"password_confirmation_mismatch": MessageLookupByLibrary.simpleMessage(
"Пароли не совпадают",
),
"password_empty": MessageLookupByLibrary.simpleMessage(
"Пароль обязателен для заполнения",
),
"password_short": MessageLookupByLibrary.simpleMessage(
"В пароле должно быть минимум 6 символов",
),
"pcs": MessageLookupByLibrary.simpleMessage("шт"),
"personal": MessageLookupByLibrary.simpleMessage("Персональные"),
"personal_diet_state": MessageLookupByLibrary.simpleMessage("Персональная"),
"photo": MessageLookupByLibrary.simpleMessage("Фото"),
"physical_fitness": MessageLookupByLibrary.simpleMessage(
"Физическая форма",
),
"place_of_living": MessageLookupByLibrary.simpleMessage("Место проживания"),
"plan_author": MessageLookupByLibrary.simpleMessage("Автор плана"),
"plan_desc": MessageLookupByLibrary.simpleMessage("Описание плана"),
"plan_desc_example": MessageLookupByLibrary.simpleMessage(
"Большое подробное, привлекательное описание плана",
),
"plan_duration": MessageLookupByLibrary.simpleMessage("Срок плана"),
"plan_label_desc": MessageLookupByLibrary.simpleMessage(
"План для поддержания формы",
),
"plan_type": MessageLookupByLibrary.simpleMessage("Тип плана"),
"plans_complete": MessageLookupByLibrary.simpleMessage("Пройденные планы"),
"popular": MessageLookupByLibrary.simpleMessage("Популярные"),
"preferred_language": MessageLookupByLibrary.simpleMessage(
"Предпочитаемый язык общения",
),
"pressure": MessageLookupByLibrary.simpleMessage("Давление"),
"pressure_empty": MessageLookupByLibrary.simpleMessage(
"Все показатели давления обязательны к заполнению",
),
"pressure_is_not_a_number": MessageLookupByLibrary.simpleMessage(
"Все показатели давления должны быть числами",
),
"preview": MessageLookupByLibrary.simpleMessage("Превью"),
"price": MessageLookupByLibrary.simpleMessage("Цена"),
"price_explanation": MessageLookupByLibrary.simpleMessage(
"Разница в ценах, это комиссия Итвио",
),
"profile": MessageLookupByLibrary.simpleMessage("Профиль"),
"proteins": MessageLookupByLibrary.simpleMessage("Белки"),
"publish": MessageLookupByLibrary.simpleMessage("Опубликовать"),
"published": MessageLookupByLibrary.simpleMessage("Опубликованные"),
"removeFromActive": MessageLookupByLibrary.simpleMessage(
"Снять с публикации",
),
"save": MessageLookupByLibrary.simpleMessage("Сохранить"),
"services": MessageLookupByLibrary.simpleMessage("Оказание услуг"),
"settings": MessageLookupByLibrary.simpleMessage("Настройки"),
"sex": MessageLookupByLibrary.simpleMessage("Пол"),
"share": MessageLookupByLibrary.simpleMessage("Поделиться"),
"skip": MessageLookupByLibrary.simpleMessage("Пропустить"),
"slim": MessageLookupByLibrary.simpleMessage("Похудение"),
"slim_type": MessageLookupByLibrary.simpleMessage("Похудение"),
"slim_type_desc": MessageLookupByLibrary.simpleMessage(
"План питания для похудения должен быть разработан с учётом индивидуальных особенностей организма, возраста, пола, уровня физической активности и состояния здоровья. Общие принципы: \n● Снижение калорийности; \n● Сбалансированный рацион; \n● Разнообразие продуктов; \n● Питьевой режим; \n● Ограничение вредных продуктов;",
),
"sport_hobbies": MessageLookupByLibrary.simpleMessage(
"Спортивные увлечения",
),
"standard": MessageLookupByLibrary.simpleMessage("Стандарт"),
"startDiet": MessageLookupByLibrary.simpleMessage("Начать диету"),
"takes_pills": MessageLookupByLibrary.simpleMessage("Принимает лекарства"),
"tell_your_schedule": MessageLookupByLibrary.simpleMessage(
"Расскажите в свободной форме о том как просыпаетесь, когда едите, когда ложитесь спать:",
),
"three_options": MessageLookupByLibrary.simpleMessage("3 опции"),
"time": MessageLookupByLibrary.simpleMessage("Время прохождения"),
"toOffer": MessageLookupByLibrary.simpleMessage("Предложить"),
"to_draft": MessageLookupByLibrary.simpleMessage("В черновик"),
"unfavorite_foods": MessageLookupByLibrary.simpleMessage(
"Нелюбимые продукты",
),
"up_pressure": MessageLookupByLibrary.simpleMessage(
"Верхние показатели давления:",
),
"upper": MessageLookupByLibrary.simpleMessage("Верхнее"),
"user_authorization": MessageLookupByLibrary.simpleMessage(
"Аутентификация",
),
"user_data": MessageLookupByLibrary.simpleMessage("Данные пользователя"),
"user_registration": MessageLookupByLibrary.simpleMessage(
"Регистрация пользователя",
),
"username": MessageLookupByLibrary.simpleMessage("Имя пользователя"),
"vegan": MessageLookupByLibrary.simpleMessage("Веган."),
"vegan_type": MessageLookupByLibrary.simpleMessage("Вегетарианская"),
"vegan_type_desc": MessageLookupByLibrary.simpleMessage(
"Диета основана на потреблении растительной пищи и отказе от продуктов животного происхождения. Такой тип питания может быть разнообразным и вкусным, а также полезным для здоровья.",
),
"waist": MessageLookupByLibrary.simpleMessage("Талия"),
"waist_circumference": MessageLookupByLibrary.simpleMessage("Обхват талии"),
"weight": MessageLookupByLibrary.simpleMessage("Вес"),
"weight_chart": MessageLookupByLibrary.simpleMessage("График веса"),
"weight_measurements": MessageLookupByLibrary.simpleMessage("Замер веса"),
"wellness": MessageLookupByLibrary.simpleMessage("Улучшение самочувствия"),
"wellness_type": MessageLookupByLibrary.simpleMessage(
"Улучшение самочувствия",
),
"wellness_type_desc": MessageLookupByLibrary.simpleMessage(
"Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы.",
),
"what_is_your_name": MessageLookupByLibrary.simpleMessage("Как вас зовут?"),
"wrong_credentials": MessageLookupByLibrary.simpleMessage(
"Неверные данные пользователя",
),
"you_will_get": MessageLookupByLibrary.simpleMessage("Вы получите"),
"your_blood_pressure": MessageLookupByLibrary.simpleMessage(
"Ваше артериальное давление?",
),
"your_favorite_sports": MessageLookupByLibrary.simpleMessage(
"Ваши спортивные увлечения?",
),
"your_gender": MessageLookupByLibrary.simpleMessage("Ваш пол?"),
"your_height": MessageLookupByLibrary.simpleMessage("Ваш рост:"),
"your_schedule": MessageLookupByLibrary.simpleMessage("Ваш режим дня?"),
"your_weight": MessageLookupByLibrary.simpleMessage("Ваш вес:"),
};
"Moscow": MessageLookupByLibrary.simpleMessage("Москва"),
"Russia": MessageLookupByLibrary.simpleMessage("Россия"),
"about_diet": MessageLookupByLibrary.simpleMessage("Об этой диете"),
"about_plan": MessageLookupByLibrary.simpleMessage("Об этом плане"),
"about_recipe": MessageLookupByLibrary.simpleMessage("Об этом рецепте"),
"active": MessageLookupByLibrary.simpleMessage("Активен"),
"active_diet_state": MessageLookupByLibrary.simpleMessage("Активная"),
"add": MessageLookupByLibrary.simpleMessage("Добавить"),
"add_cover": MessageLookupByLibrary.simpleMessage("Добавьте обложку"),
"add_measure": MessageLookupByLibrary.simpleMessage("Добавить замер"),
"adding_plan": MessageLookupByLibrary.simpleMessage("Добавление плана"),
"additional_information":
MessageLookupByLibrary.simpleMessage("Дополнительная информация"),
"admin": MessageLookupByLibrary.simpleMessage("Админ"),
"all": MessageLookupByLibrary.simpleMessage("Все"),
"allergies_presence":
MessageLookupByLibrary.simpleMessage("Наличие аллергии"),
"another_form_of_comment":
MessageLookupByLibrary.simpleMessage("комментария"),
"another_form_of_day": MessageLookupByLibrary.simpleMessage("дня"),
"another_form_of_hour": MessageLookupByLibrary.simpleMessage("часа"),
"another_form_of_minute": MessageLookupByLibrary.simpleMessage("минут"),
"another_form_of_stage": MessageLookupByLibrary.simpleMessage("этапа"),
"answer": MessageLookupByLibrary.simpleMessage("Ответить"),
"archived": MessageLookupByLibrary.simpleMessage("В архиве"),
"authentificate": MessageLookupByLibrary.simpleMessage("Войти"),
"average_pressure": MessageLookupByLibrary.simpleMessage(
"Средние показатели давления:"),
"bodymass_chart": MessageLookupByLibrary.simpleMessage("График ИМТ"),
"by_default": MessageLookupByLibrary.simpleMessage("По умолчянию"),
"calories": MessageLookupByLibrary.simpleMessage("Калории"),
"cancel_diet": MessageLookupByLibrary.simpleMessage("Отменить диету?"),
"carb_type": MessageLookupByLibrary.simpleMessage("Мучное"),
"carb_type_desc": MessageLookupByLibrary.simpleMessage(
"Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы."),
"carbohydrates": MessageLookupByLibrary.simpleMessage("Углеводы"),
"chat": MessageLookupByLibrary.simpleMessage("Чат"),
"check_out_video_recipe":
MessageLookupByLibrary.simpleMessage("Посмотреть видео рецепт"),
"chest": MessageLookupByLibrary.simpleMessage("Грудь"),
"chest_circumference":
MessageLookupByLibrary.simpleMessage("Обхват груди"),
"choose_diet_duration":
MessageLookupByLibrary.simpleMessage("Выберите срок диеты"),
"choose_plan_duration":
MessageLookupByLibrary.simpleMessage("Выберите срок плана"),
"choose_start_diet_date": MessageLookupByLibrary.simpleMessage(
"Выберите дату старты диеты. Она отобразиться у Вас в дневнике"),
"circumference_empty": MessageLookupByLibrary.simpleMessage(
"Все замеры обязательны к заполнению"),
"circumference_is_not_a_number": MessageLookupByLibrary.simpleMessage(
"Все замеры должны быть числами"),
"circumferences_chart":
MessageLookupByLibrary.simpleMessage("График обхватов"),
"city": MessageLookupByLibrary.simpleMessage("Город"),
"city_of_living":
MessageLookupByLibrary.simpleMessage("Город проживания"),
"comfort": MessageLookupByLibrary.simpleMessage("Комфорт"),
"comment": MessageLookupByLibrary.simpleMessage("комментарий"),
"comments": MessageLookupByLibrary.simpleMessage("Комментарии"),
"comments_plural": MessageLookupByLibrary.simpleMessage("комментариев"),
"complain": MessageLookupByLibrary.simpleMessage("Пожаловаться"),
"confirm": MessageLookupByLibrary.simpleMessage("Подтвердить"),
"continueButton": MessageLookupByLibrary.simpleMessage("Продолжить"),
"continue_": MessageLookupByLibrary.simpleMessage("Продолжить"),
"copied": MessageLookupByLibrary.simpleMessage("cкопирован"),
"copy_link": MessageLookupByLibrary.simpleMessage("Скопировать ссылку"),
"could_not_cancel_diet":
MessageLookupByLibrary.simpleMessage("Не удалось отменить диету"),
"could_not_launch_video":
MessageLookupByLibrary.simpleMessage("Не удалось запустить видео"),
"country": MessageLookupByLibrary.simpleMessage("Страна"),
"create_diet": MessageLookupByLibrary.simpleMessage("Создание диеты"),
"customer_price":
MessageLookupByLibrary.simpleMessage("Цена для покупателя"),
"date_of_birth": MessageLookupByLibrary.simpleMessage("Дата рождения"),
"day": MessageLookupByLibrary.simpleMessage("День"),
"days": MessageLookupByLibrary.simpleMessage("дней"),
"deleted": MessageLookupByLibrary.simpleMessage("Удалена"),
"diary": MessageLookupByLibrary.simpleMessage("Дневник"),
"diet": MessageLookupByLibrary.simpleMessage("Диета"),
"diet_author": MessageLookupByLibrary.simpleMessage("Автор диеты"),
"diet_plan": MessageLookupByLibrary.simpleMessage("План диеты"),
"diet_started":
MessageLookupByLibrary.simpleMessage("Диета уже начата"),
"diet_state": MessageLookupByLibrary.simpleMessage("Статус диеты"),
"diet_type": MessageLookupByLibrary.simpleMessage("Тип диеты"),
"do_you_have_hypertension_tendency":
MessageLookupByLibrary.simpleMessage("Есть ли у вас гипертония?"),
"do_you_have_hypotension_tendency":
MessageLookupByLibrary.simpleMessage("Есть ли у вас гипотония?"),
"do_you_take_pills": MessageLookupByLibrary.simpleMessage(
"Принимаете ли вы лекарства или бады?"),
"down_pressure":
MessageLookupByLibrary.simpleMessage("Нижние показатели давления:"),
"draft": MessageLookupByLibrary.simpleMessage("Черновик"),
"drafted": MessageLookupByLibrary.simpleMessage("Черновики"),
"eating_behavior":
MessageLookupByLibrary.simpleMessage("Пищевое поведение"),
"eating_behavior_type": MessageLookupByLibrary.simpleMessage(
"Формирование пищевого поведения"),
"eating_behavior_type_desc": MessageLookupByLibrary.simpleMessage(
"Формирование здорового пищевого поведения — это процесс, который включает в себя изменение привычек и отношения к еде. Он направлен на то, чтобы помочь людям научиться правильно питаться и поддерживать здоровый образ жизни.Основные принципы формирования здорового пищевого поведения: \n● Разнообразие продуктов; \n● Сбалансированный рацион; \n● Питьевой режим; \n● Ограничение вредных продуктов; \n● Регулярные приёмы пищи; \n● Умеренность;Осознанное питание; \n● Физическая активность;"),
"economy": MessageLookupByLibrary.simpleMessage("Эконом"),
"edit": MessageLookupByLibrary.simpleMessage("Редактировать"),
"email": MessageLookupByLibrary.simpleMessage("Электронная почта"),
"email_empty": MessageLookupByLibrary.simpleMessage(
"Необходимо ввести адрес электронной почты"),
"email_invalid": MessageLookupByLibrary.simpleMessage(
"Некорректный адрес электронной почты"),
"enter_additional_information": MessageLookupByLibrary.simpleMessage(
"Введите любую дополнительную информацию на своё усмотрение:"),
"enter_diet_description":
MessageLookupByLibrary.simpleMessage("Введите описание диеты"),
"enter_diet_name":
MessageLookupByLibrary.simpleMessage("Выведите название диеты"),
"enter_information":
MessageLookupByLibrary.simpleMessage("Введите информацию..."),
"exit": MessageLookupByLibrary.simpleMessage("Выйти"),
"expand": MessageLookupByLibrary.simpleMessage("Показать полностью"),
"fats": MessageLookupByLibrary.simpleMessage("Жиры"),
"favorite_foods":
MessageLookupByLibrary.simpleMessage("Любимые продукты"),
"favorite_unfavorite_foods": MessageLookupByLibrary.simpleMessage(
"Ваши любимые/нелюбимые продукты питания?"),
"female": MessageLookupByLibrary.simpleMessage("Женский"),
"fill_in_information":
MessageLookupByLibrary.simpleMessage("Заполните информацию:"),
"finish": MessageLookupByLibrary.simpleMessage("Завершить"),
"first_name": MessageLookupByLibrary.simpleMessage("Имя"),
"first_name_empty": MessageLookupByLibrary.simpleMessage(
"Имя обязательно для заполнения"),
"first_name_short": MessageLookupByLibrary.simpleMessage(
"В имени должен быть минимум 1 символ"),
"fit_type": MessageLookupByLibrary.simpleMessage("Поддержание формы"),
"fit_type_desc": MessageLookupByLibrary.simpleMessage(
"План питания для поддержания формы сбалансированный и разнообразный, чтобы обеспечить организм всеми необходимыми питательными веществами. Общие принципы: \n● Дробное питание; \n● Разнообразие продуктов; \n● Контроль порций; \n● Питьевой режим; \n● Ограничение вредных продуктов;"),
"fitness": MessageLookupByLibrary.simpleMessage("Поддержка формы"),
"from": MessageLookupByLibrary.simpleMessage("от"),
"from_a_to_z": MessageLookupByLibrary.simpleMessage("Название: А-Я"),
"from_z_to_a": MessageLookupByLibrary.simpleMessage("Название: Я-А"),
"gallery": MessageLookupByLibrary.simpleMessage("Галерея"),
"general_info":
MessageLookupByLibrary.simpleMessage("Общая информация"),
"gr": MessageLookupByLibrary.simpleMessage("гр"),
"grams": MessageLookupByLibrary.simpleMessage("гр"),
"has_alergy": MessageLookupByLibrary.simpleMessage("Наличие аллергии"),
"has_hyper":
MessageLookupByLibrary.simpleMessage("Склонность к гипертонии"),
"has_hypo":
MessageLookupByLibrary.simpleMessage("Склонность к гипотонии"),
"height": MessageLookupByLibrary.simpleMessage("Рост"),
"height_measurements":
MessageLookupByLibrary.simpleMessage("Замер роста"),
"hide": MessageLookupByLibrary.simpleMessage("Свернуть"),
"hip_circumference":
MessageLookupByLibrary.simpleMessage("Обхват бедер"),
"hips": MessageLookupByLibrary.simpleMessage("Бедра"),
"hour": MessageLookupByLibrary.simpleMessage("час"),
"hours": MessageLookupByLibrary.simpleMessage("часов"),
"in_progress": MessageLookupByLibrary.simpleMessage("В процессе"),
"inactive_diet_state": MessageLookupByLibrary.simpleMessage("Черновик"),
"ingredients": MessageLookupByLibrary.simpleMessage("Ингредиенты"),
"initial_measurements":
MessageLookupByLibrary.simpleMessage("Первичные замеры"),
"label": MessageLookupByLibrary.simpleMessage("Название"),
"language": MessageLookupByLibrary.simpleMessage("Язык"),
"last_name": MessageLookupByLibrary.simpleMessage("Фамилия"),
"last_name_empty": MessageLookupByLibrary.simpleMessage(
"Фамилия обязательна для заполнения"),
"last_name_short": MessageLookupByLibrary.simpleMessage(
"В фамилии должен быть минимум 6 символов"),
"lbl_english": MessageLookupByLibrary.simpleMessage("English"),
"light_type": MessageLookupByLibrary.simpleMessage("Легкая"),
"light_type_desc": MessageLookupByLibrary.simpleMessage(
"С некоторыми видами диет можно употреблять алкоголь в умеренных количествах. Рекомендуется совмещать вместе с низкокалорийными закусками."),
"liked_groceries":
MessageLookupByLibrary.simpleMessage("Любимые продукты"),
"link_was_copied":
MessageLookupByLibrary.simpleMessage("Ссылка успешно скопирована"),
"loading": MessageLookupByLibrary.simpleMessage("Загрузка..."),
"lower": MessageLookupByLibrary.simpleMessage("Нижнее"),
"male": MessageLookupByLibrary.simpleMessage("Мужской"),
"meal": MessageLookupByLibrary.simpleMessage("Приём пищи"),
"meat": MessageLookupByLibrary.simpleMessage("Мясо"),
"meat_type": MessageLookupByLibrary.simpleMessage("С мясом"),
"meat_type_desc": MessageLookupByLibrary.simpleMessage(
"Диета с мясом полезная и сбалансированная. Включает в себя нежирное мясо: курицу, индейку, кролика или говядину. В сочетании с овощами получаете все необходимые витамины и минералы. Не забывайте о порциях и старайтесь есть медленно, прислушиваясь к своему организму."),
"milliliter": MessageLookupByLibrary.simpleMessage("мл"),
"minimal_customer_price": MessageLookupByLibrary.simpleMessage(
"Минимальная цена для покупателя: 1200₽"),
"minute": MessageLookupByLibrary.simpleMessage("минута"),
"minutes": MessageLookupByLibrary.simpleMessage("минуты"),
"most_new": MessageLookupByLibrary.simpleMessage("Самые новые"),
"most_old": MessageLookupByLibrary.simpleMessage("Самые старые"),
"msg_network_err":
MessageLookupByLibrary.simpleMessage("Сетевая ошибка!"),
"msg_something_went_wrong":
MessageLookupByLibrary.simpleMessage("Что то пошло не так!"),
"my_blog": MessageLookupByLibrary.simpleMessage("Мой блог"),
"my_diets": MessageLookupByLibrary.simpleMessage("Мои диеты"),
"my_plans": MessageLookupByLibrary.simpleMessage("Мои планы"),
"my_settings": MessageLookupByLibrary.simpleMessage("Мои настройки"),
"name_empty": MessageLookupByLibrary.simpleMessage(
"Имя пользователя обязательно для заполнения"),
"name_short": MessageLookupByLibrary.simpleMessage(
"В имени пользователя должно быть минимум 6 символов"),
"no": MessageLookupByLibrary.simpleMessage("Нет"),
"no_account":
MessageLookupByLibrary.simpleMessage("У меня нет аккаунта"),
"not_liked_groceries":
MessageLookupByLibrary.simpleMessage("Не любимые продукты"),
"one_option": MessageLookupByLibrary.simpleMessage("1 опция"),
"option": MessageLookupByLibrary.simpleMessage("Опция"),
"option_content":
MessageLookupByLibrary.simpleMessage("Содержание опции"),
"option_content_hint_text":
MessageLookupByLibrary.simpleMessage("На связи 7 дней в неделю"),
"option_desc": MessageLookupByLibrary.simpleMessage("Описание опции"),
"option_desc_hint_text": MessageLookupByLibrary.simpleMessage(
"Этот план питания для похудения на 7 дней сбалансированный и разнообразный"),
"option_duration":
MessageLookupByLibrary.simpleMessage("Продолжительность опции"),
"option_personal_diet_desc": MessageLookupByLibrary.simpleMessage(
"Опция подразумевает создание индивидуальной диеты"),
"option_title": MessageLookupByLibrary.simpleMessage("Заголовок опции"),
"order": MessageLookupByLibrary.simpleMessage("Порядок"),
"other_info": MessageLookupByLibrary.simpleMessage("Прочая информация"),
"password": MessageLookupByLibrary.simpleMessage("Пароль"),
"password_confirmation":
MessageLookupByLibrary.simpleMessage("Подтвердите пароль"),
"password_confirmation_mismatch":
MessageLookupByLibrary.simpleMessage("Пароли не совпадают"),
"password_empty": MessageLookupByLibrary.simpleMessage(
"Пароль обязателен для заполнения"),
"password_short": MessageLookupByLibrary.simpleMessage(
"В пароле должно быть минимум 6 символов"),
"pcs": MessageLookupByLibrary.simpleMessage("шт"),
"personal": MessageLookupByLibrary.simpleMessage("Персональные"),
"personal_diet_state":
MessageLookupByLibrary.simpleMessage("Персональная"),
"photo": MessageLookupByLibrary.simpleMessage("Фото"),
"physical_fitness":
MessageLookupByLibrary.simpleMessage("Физическая форма"),
"place_of_living":
MessageLookupByLibrary.simpleMessage("Место проживания"),
"plan_author": MessageLookupByLibrary.simpleMessage("Автор плана"),
"plan_desc": MessageLookupByLibrary.simpleMessage("Описание плана"),
"plan_desc_example": MessageLookupByLibrary.simpleMessage(
"Большое подробное, привлекательное описание плана"),
"plan_duration": MessageLookupByLibrary.simpleMessage("Срок плана"),
"plan_label_desc":
MessageLookupByLibrary.simpleMessage("План для поддержания формы"),
"plan_type": MessageLookupByLibrary.simpleMessage("Тип плана"),
"plans_complete":
MessageLookupByLibrary.simpleMessage("Пройденные планы"),
"popular": MessageLookupByLibrary.simpleMessage("Популярные"),
"preferred_language":
MessageLookupByLibrary.simpleMessage("Предпочитаемый язык общения"),
"pressure": MessageLookupByLibrary.simpleMessage("Давление"),
"pressure_empty": MessageLookupByLibrary.simpleMessage(
"Все показатели давления обязательны к заполнению"),
"pressure_is_not_a_number": MessageLookupByLibrary.simpleMessage(
"Все показатели давления должны быть числами"),
"preview": MessageLookupByLibrary.simpleMessage("Превью"),
"price": MessageLookupByLibrary.simpleMessage("Цена"),
"price_explanation": MessageLookupByLibrary.simpleMessage(
"Разница в ценах, это комиссия Итвио"),
"profile": MessageLookupByLibrary.simpleMessage("Профиль"),
"proteins": MessageLookupByLibrary.simpleMessage("Белки"),
"publish": MessageLookupByLibrary.simpleMessage("Опубликовать"),
"published": MessageLookupByLibrary.simpleMessage("Опубликованные"),
"recipe_author": MessageLookupByLibrary.simpleMessage("Автор рецепта"),
"removeFromActive":
MessageLookupByLibrary.simpleMessage("Снять с публикации"),
"save": MessageLookupByLibrary.simpleMessage("Сохранить"),
"services": MessageLookupByLibrary.simpleMessage("Оказание услуг"),
"settings": MessageLookupByLibrary.simpleMessage("Настройки"),
"sex": MessageLookupByLibrary.simpleMessage("Пол"),
"share": MessageLookupByLibrary.simpleMessage("Поделиться"),
"skip": MessageLookupByLibrary.simpleMessage("Пропустить"),
"slim": MessageLookupByLibrary.simpleMessage("Похудение"),
"slim_type": MessageLookupByLibrary.simpleMessage("Похудение"),
"slim_type_desc": MessageLookupByLibrary.simpleMessage(
"План питания для похудения должен быть разработан с учётом индивидуальных особенностей организма, возраста, пола, уровня физической активности и состояния здоровья. Общие принципы: \n● Снижение калорийности; \n● Сбалансированный рацион; \n● Разнообразие продуктов; \n● Питьевой режим; \n● Ограничение вредных продуктов;"),
"sport_hobbies":
MessageLookupByLibrary.simpleMessage("Спортивные увлечения"),
"stage": MessageLookupByLibrary.simpleMessage("этап"),
"stages": MessageLookupByLibrary.simpleMessage("этапов"),
"standard": MessageLookupByLibrary.simpleMessage("Стандарт"),
"startDiet": MessageLookupByLibrary.simpleMessage("Начать диету"),
"step": MessageLookupByLibrary.simpleMessage("Шаг"),
"step_by_step_photo_recipe":
MessageLookupByLibrary.simpleMessage("Пошаговый фоторецепт"),
"takes_pills":
MessageLookupByLibrary.simpleMessage("Принимает лекарства"),
"tell_your_schedule": MessageLookupByLibrary.simpleMessage(
"Расскажите в свободной форме о том как просыпаетесь, когда едите, когда ложитесь спать:"),
"there_is_a_video": MessageLookupByLibrary.simpleMessage("Есть видео"),
"three_options": MessageLookupByLibrary.simpleMessage("3 опции"),
"time": MessageLookupByLibrary.simpleMessage("Время прохождения"),
"toOffer": MessageLookupByLibrary.simpleMessage("Предложить"),
"to_draft": MessageLookupByLibrary.simpleMessage("В черновик"),
"unfavorite_foods":
MessageLookupByLibrary.simpleMessage("Нелюбимые продукты"),
"up_pressure": MessageLookupByLibrary.simpleMessage(
"Верхние показатели давления:"),
"upper": MessageLookupByLibrary.simpleMessage("Верхнее"),
"user_authorization":
MessageLookupByLibrary.simpleMessage("Аутентификация"),
"user_data":
MessageLookupByLibrary.simpleMessage("Данные пользователя"),
"user_registration":
MessageLookupByLibrary.simpleMessage("Регистрация пользователя"),
"username": MessageLookupByLibrary.simpleMessage("Имя пользователя"),
"vegan": MessageLookupByLibrary.simpleMessage("Веган."),
"vegan_type": MessageLookupByLibrary.simpleMessage("Вегетарианская"),
"vegan_type_desc": MessageLookupByLibrary.simpleMessage(
"Диета основана на потреблении растительной пищи и отказе от продуктов животного происхождения. Такой тип питания может быть разнообразным и вкусным, а также полезным для здоровья."),
"waist": MessageLookupByLibrary.simpleMessage("Талия"),
"waist_circumference":
MessageLookupByLibrary.simpleMessage("Обхват талии"),
"weight": MessageLookupByLibrary.simpleMessage("Вес"),
"weight_chart": MessageLookupByLibrary.simpleMessage("График веса"),
"weight_measurements":
MessageLookupByLibrary.simpleMessage("Замер веса"),
"wellness":
MessageLookupByLibrary.simpleMessage("Улучшение самочувствия"),
"wellness_type":
MessageLookupByLibrary.simpleMessage("Улучшение самочувствия"),
"wellness_type_desc": MessageLookupByLibrary.simpleMessage(
"Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы."),
"what_is_your_name":
MessageLookupByLibrary.simpleMessage("Как вас зовут?"),
"wrong_credentials": MessageLookupByLibrary.simpleMessage(
"Неверные данные пользователя"),
"you_will_get": MessageLookupByLibrary.simpleMessage("Вы получите"),
"your_blood_pressure":
MessageLookupByLibrary.simpleMessage("Ваше артериальное давление?"),
"your_favorite_sports":
MessageLookupByLibrary.simpleMessage("Ваши спортивные увлечения?"),
"your_gender": MessageLookupByLibrary.simpleMessage("Ваш пол?"),
"your_height": MessageLookupByLibrary.simpleMessage("Ваш рост:"),
"your_schedule": MessageLookupByLibrary.simpleMessage("Ваш режим дня?"),
"your_weight": MessageLookupByLibrary.simpleMessage("Ваш вес:")
};
}

View File

@@ -22,350 +22,299 @@ class MessageLookup extends MessageLookupByLibrary {
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"Moscow": MessageLookupByLibrary.simpleMessage("Москва"),
"Russia": MessageLookupByLibrary.simpleMessage("Россия"),
"about_diet": MessageLookupByLibrary.simpleMessage("Об этой диете"),
"about_plan": MessageLookupByLibrary.simpleMessage("Об этом плане"),
"active": MessageLookupByLibrary.simpleMessage("Активен"),
"active_diet_state": MessageLookupByLibrary.simpleMessage("Активная"),
"add": MessageLookupByLibrary.simpleMessage("Добавить"),
"add_cover": MessageLookupByLibrary.simpleMessage("Добавьте обложку"),
"add_measure": MessageLookupByLibrary.simpleMessage("Добавить замер"),
"adding_plan": MessageLookupByLibrary.simpleMessage("Добавление плана"),
"additional_information": MessageLookupByLibrary.simpleMessage(
"Дополнительная информация",
),
"admin": MessageLookupByLibrary.simpleMessage("Админ"),
"all": MessageLookupByLibrary.simpleMessage("Все"),
"allergies_presence": MessageLookupByLibrary.simpleMessage(
"Наличие аллергии",
),
"another_form_of_comment": MessageLookupByLibrary.simpleMessage(
"комментария",
),
"another_form_of_day": MessageLookupByLibrary.simpleMessage("дня"),
"answer": MessageLookupByLibrary.simpleMessage("Ответить"),
"archived": MessageLookupByLibrary.simpleMessage("В архиве"),
"authentificate": MessageLookupByLibrary.simpleMessage("Войти"),
"average_pressure": MessageLookupByLibrary.simpleMessage(
"Средние показатели давления:",
),
"bodymass_chart": MessageLookupByLibrary.simpleMessage("График ИМТ"),
"by_default": MessageLookupByLibrary.simpleMessage("По умолчянию"),
"calories": MessageLookupByLibrary.simpleMessage("Калории"),
"carb_type": MessageLookupByLibrary.simpleMessage("Мучное"),
"carb_type_desc": MessageLookupByLibrary.simpleMessage(
"Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы.",
),
"carbohydrates": MessageLookupByLibrary.simpleMessage("Углеводы"),
"chat": MessageLookupByLibrary.simpleMessage("Чат"),
"chest": MessageLookupByLibrary.simpleMessage("Грудь"),
"chest_circumference": MessageLookupByLibrary.simpleMessage("Обхват груди"),
"choose_diet_duration": MessageLookupByLibrary.simpleMessage(
"Выберите срок диеты",
),
"choose_plan_duration": MessageLookupByLibrary.simpleMessage(
"Выберите срок плана",
),
"choose_start_diet_date": MessageLookupByLibrary.simpleMessage(
"Выберите дату старты диеты. Она отобразиться у Вас в дневнике",
),
"circumference_empty": MessageLookupByLibrary.simpleMessage(
"Все замеры обязательны к заполнению",
),
"circumference_is_not_a_number": MessageLookupByLibrary.simpleMessage(
"Все замеры должны быть числами",
),
"circumferences_chart": MessageLookupByLibrary.simpleMessage(
"График обхватов",
),
"city": MessageLookupByLibrary.simpleMessage("Город"),
"city_of_living": MessageLookupByLibrary.simpleMessage("Город проживания"),
"comfort": MessageLookupByLibrary.simpleMessage("Комфорт"),
"comment": MessageLookupByLibrary.simpleMessage("комментарий"),
"comments": MessageLookupByLibrary.simpleMessage("Комментарии"),
"comments_plural": MessageLookupByLibrary.simpleMessage("комментариев"),
"complain": MessageLookupByLibrary.simpleMessage("Пожаловаться"),
"continueButton": MessageLookupByLibrary.simpleMessage("Продолжить"),
"continue_": MessageLookupByLibrary.simpleMessage("Продолжить"),
"copied": MessageLookupByLibrary.simpleMessage("cкопирован"),
"copy_link": MessageLookupByLibrary.simpleMessage("Скопировать ссылку"),
"country": MessageLookupByLibrary.simpleMessage("Страна"),
"create_diet": MessageLookupByLibrary.simpleMessage("Создание диеты"),
"customer_price": MessageLookupByLibrary.simpleMessage(
"Цена для покупателя",
),
"date_of_birth": MessageLookupByLibrary.simpleMessage("Дата рождения"),
"day": MessageLookupByLibrary.simpleMessage("День"),
"days": MessageLookupByLibrary.simpleMessage("дней"),
"deleted": MessageLookupByLibrary.simpleMessage("Удалена"),
"diary": MessageLookupByLibrary.simpleMessage("Дневник"),
"diet": MessageLookupByLibrary.simpleMessage("Диета"),
"diet_author": MessageLookupByLibrary.simpleMessage("Автор диеты"),
"diet_plan": MessageLookupByLibrary.simpleMessage("План диеты"),
"diet_state": MessageLookupByLibrary.simpleMessage("Статус диеты"),
"diet_type": MessageLookupByLibrary.simpleMessage("Тип диеты"),
"do_you_have_hypertension_tendency": MessageLookupByLibrary.simpleMessage(
"Есть ли у вас гипертония?",
),
"do_you_have_hypotension_tendency": MessageLookupByLibrary.simpleMessage(
"Есть ли у вас гипотония?",
),
"do_you_take_pills": MessageLookupByLibrary.simpleMessage(
"Принимаете ли вы лекарства или бады?",
),
"down_pressure": MessageLookupByLibrary.simpleMessage(
"Нижние показатели давления:",
),
"draft": MessageLookupByLibrary.simpleMessage("Черновик"),
"drafted": MessageLookupByLibrary.simpleMessage("Черновики"),
"eating_behavior": MessageLookupByLibrary.simpleMessage(
"Пищевое поведение",
),
"eating_behavior_type": MessageLookupByLibrary.simpleMessage(
"Формирование пищевого поведения",
),
"eating_behavior_type_desc": MessageLookupByLibrary.simpleMessage(
"Формирование здорового пищевого поведения — это процесс, который включает в себя изменение привычек и отношения к еде. Он направлен на то, чтобы помочь людям научиться правильно питаться и поддерживать здоровый образ жизни.Основные принципы формирования здорового пищевого поведения: \n● Разнообразие продуктов; \n● Сбалансированный рацион; \n● Питьевой режим; \n● Ограничение вредных продуктов; \n● Регулярные приёмы пищи; \n● Умеренность;Осознанное питание; \n● Физическая активность;",
),
"economy": MessageLookupByLibrary.simpleMessage("Эконом"),
"edit": MessageLookupByLibrary.simpleMessage("Редактировать"),
"email": MessageLookupByLibrary.simpleMessage("Электронная почта"),
"email_empty": MessageLookupByLibrary.simpleMessage(
"Необходимо ввести адрес электронной почты",
),
"email_invalid": MessageLookupByLibrary.simpleMessage(
"Некорректный адрес электронной почты",
),
"enter_additional_information": MessageLookupByLibrary.simpleMessage(
"Введите любую дополнительную информацию на своё усмотрение:",
),
"enter_diet_description": MessageLookupByLibrary.simpleMessage(
"Введите описание диеты",
),
"enter_diet_name": MessageLookupByLibrary.simpleMessage(
"Выведите название диеты",
),
"enter_information": MessageLookupByLibrary.simpleMessage(
"Введите информацию...",
),
"exit": MessageLookupByLibrary.simpleMessage("Выйти"),
"expand": MessageLookupByLibrary.simpleMessage("Показать полностью"),
"fats": MessageLookupByLibrary.simpleMessage("Жиры"),
"favorite_foods": MessageLookupByLibrary.simpleMessage("Любимые продукты"),
"favorite_unfavorite_foods": MessageLookupByLibrary.simpleMessage(
"Ваши любимые/нелюбимые продукты питания?",
),
"female": MessageLookupByLibrary.simpleMessage("Женский"),
"fill_in_information": MessageLookupByLibrary.simpleMessage(
"Заполните информацию:",
),
"finish": MessageLookupByLibrary.simpleMessage("Завершить"),
"first_name": MessageLookupByLibrary.simpleMessage("Имя"),
"first_name_empty": MessageLookupByLibrary.simpleMessage(
"Имя обязательно для заполнения",
),
"first_name_short": MessageLookupByLibrary.simpleMessage(
"В имени должен быть минимум 1 символ",
),
"fit_type": MessageLookupByLibrary.simpleMessage("Поддержание формы"),
"fit_type_desc": MessageLookupByLibrary.simpleMessage(
"План питания для поддержания формы сбалансированный и разнообразный, чтобы обеспечить организм всеми необходимыми питательными веществами. Общие принципы: \n● Дробное питание; \n● Разнообразие продуктов; \n● Контроль порций; \n● Питьевой режим; \n● Ограничение вредных продуктов;",
),
"fitness": MessageLookupByLibrary.simpleMessage("Поддержка формы"),
"from": MessageLookupByLibrary.simpleMessage("от"),
"from_a_to_z": MessageLookupByLibrary.simpleMessage("Название: А-Я"),
"from_z_to_a": MessageLookupByLibrary.simpleMessage("Название: Я-А"),
"gallery": MessageLookupByLibrary.simpleMessage("Галерея"),
"gr": MessageLookupByLibrary.simpleMessage("гр"),
"grams": MessageLookupByLibrary.simpleMessage("гр"),
"height_measurements": MessageLookupByLibrary.simpleMessage("Замер роста"),
"hide": MessageLookupByLibrary.simpleMessage("Свернуть"),
"hip_circumference": MessageLookupByLibrary.simpleMessage("Обхват бедер"),
"hips": MessageLookupByLibrary.simpleMessage("Бедра"),
"in_progress": MessageLookupByLibrary.simpleMessage("В процессе"),
"inactive_diet_state": MessageLookupByLibrary.simpleMessage("Черновик"),
"initial_measurements": MessageLookupByLibrary.simpleMessage(
"Первичные замеры",
),
"label": MessageLookupByLibrary.simpleMessage("Название"),
"language": MessageLookupByLibrary.simpleMessage("Язык"),
"last_name": MessageLookupByLibrary.simpleMessage("Фамилия"),
"last_name_empty": MessageLookupByLibrary.simpleMessage(
"Фамилия обязательна для заполнения",
),
"last_name_short": MessageLookupByLibrary.simpleMessage(
"В фамилии должен быть минимум 6 символов",
),
"lbl_english": MessageLookupByLibrary.simpleMessage("English"),
"light_type": MessageLookupByLibrary.simpleMessage("Легкая"),
"light_type_desc": MessageLookupByLibrary.simpleMessage(
"С некоторыми видами диет можно употреблять алкоголь в умеренных количествах. Рекомендуется совмещать вместе с низкокалорийными закусками.",
),
"link_was_copied": MessageLookupByLibrary.simpleMessage(
"Ссылка успешно скопирована",
),
"loading": MessageLookupByLibrary.simpleMessage("Загрузка..."),
"male": MessageLookupByLibrary.simpleMessage("Мужской"),
"meal": MessageLookupByLibrary.simpleMessage("Приём пищи"),
"meat": MessageLookupByLibrary.simpleMessage("Мясо"),
"meat_type": MessageLookupByLibrary.simpleMessage("С мясом"),
"meat_type_desc": MessageLookupByLibrary.simpleMessage(
"Диета с мясом полезная и сбалансированная. Включает в себя нежирное мясо: курицу, индейку, кролика или говядину. В сочетании с овощами получаете все необходимые витамины и минералы. Не забывайте о порциях и старайтесь есть медленно, прислушиваясь к своему организму.",
),
"milliliter": MessageLookupByLibrary.simpleMessage("мл"),
"minimal_customer_price": MessageLookupByLibrary.simpleMessage(
"Минимальная цена для покупателя: 1200₽",
),
"most_new": MessageLookupByLibrary.simpleMessage("Самые новые"),
"most_old": MessageLookupByLibrary.simpleMessage("Самые старые"),
"msg_network_err": MessageLookupByLibrary.simpleMessage("Сетевая ошибка!"),
"msg_something_went_wrong": MessageLookupByLibrary.simpleMessage(
"Что то пошло не так!",
),
"my_blog": MessageLookupByLibrary.simpleMessage("Мой блог"),
"my_diets": MessageLookupByLibrary.simpleMessage("Мои диеты"),
"my_plans": MessageLookupByLibrary.simpleMessage("Мои планы"),
"my_settings": MessageLookupByLibrary.simpleMessage("Мои настройки"),
"name_empty": MessageLookupByLibrary.simpleMessage(
"Имя пользователя обязательно для заполнения",
),
"name_short": MessageLookupByLibrary.simpleMessage(
"В имени пользователя должно быть минимум 6 символов",
),
"no": MessageLookupByLibrary.simpleMessage("Нет"),
"no_account": MessageLookupByLibrary.simpleMessage("У меня нет аккаунта"),
"one_option": MessageLookupByLibrary.simpleMessage("1 опция"),
"option": MessageLookupByLibrary.simpleMessage("Опция"),
"option_content": MessageLookupByLibrary.simpleMessage("Содержание опции"),
"option_content_hint_text": MessageLookupByLibrary.simpleMessage(
"На связи 7 дней в неделю",
),
"option_desc": MessageLookupByLibrary.simpleMessage("Описание опции"),
"option_desc_hint_text": MessageLookupByLibrary.simpleMessage(
"Этот план питания для похудения на 7 дней сбалансированный и разнообразный",
),
"option_duration": MessageLookupByLibrary.simpleMessage(
"Продолжительность опции",
),
"option_personal_diet_desc": MessageLookupByLibrary.simpleMessage(
"Опция подразумевает создание индивидуальной диеты",
),
"option_title": MessageLookupByLibrary.simpleMessage("Заголовок опции"),
"order": MessageLookupByLibrary.simpleMessage("Порядок"),
"password": MessageLookupByLibrary.simpleMessage("Пароль"),
"password_confirmation": MessageLookupByLibrary.simpleMessage(
"Подтвердите пароль",
),
"password_confirmation_mismatch": MessageLookupByLibrary.simpleMessage(
"Пароли не совпадают",
),
"password_empty": MessageLookupByLibrary.simpleMessage(
"Пароль обязателен для заполнения",
),
"password_short": MessageLookupByLibrary.simpleMessage(
"В пароле должно быть минимум 6 символов",
),
"pcs": MessageLookupByLibrary.simpleMessage("шт"),
"personal": MessageLookupByLibrary.simpleMessage("Персональные"),
"personal_diet_state": MessageLookupByLibrary.simpleMessage("Персональная"),
"photo": MessageLookupByLibrary.simpleMessage("Фото"),
"place_of_living": MessageLookupByLibrary.simpleMessage("Место проживания"),
"plan_author": 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("Популярные"),
"preferred_language": MessageLookupByLibrary.simpleMessage(
"Предпочитаемый язык общения",
),
"pressure_empty": MessageLookupByLibrary.simpleMessage(
"Все показатели давления обязательны к заполнению",
),
"pressure_is_not_a_number": MessageLookupByLibrary.simpleMessage(
"Все показатели давления должны быть числами",
),
"preview": MessageLookupByLibrary.simpleMessage("Превью"),
"price": MessageLookupByLibrary.simpleMessage("Цена"),
"price_explanation": MessageLookupByLibrary.simpleMessage(
"Разница в ценах, это комиссия Итвио",
),
"profile": MessageLookupByLibrary.simpleMessage("Профиль"),
"proteins": MessageLookupByLibrary.simpleMessage("Белки"),
"publish": MessageLookupByLibrary.simpleMessage("Опубликовать"),
"published": MessageLookupByLibrary.simpleMessage("Опубликованные"),
"removeFromActive": MessageLookupByLibrary.simpleMessage(
"Снять с публикации",
),
"save": MessageLookupByLibrary.simpleMessage("Сохранить"),
"services": MessageLookupByLibrary.simpleMessage("Оказание услуг"),
"settings": MessageLookupByLibrary.simpleMessage("Настройки"),
"share": MessageLookupByLibrary.simpleMessage("Поделиться"),
"skip": MessageLookupByLibrary.simpleMessage("Пропустить"),
"slim": MessageLookupByLibrary.simpleMessage("Похудение"),
"slim_type": MessageLookupByLibrary.simpleMessage("Похудение"),
"slim_type_desc": MessageLookupByLibrary.simpleMessage(
"План питания для похудения должен быть разработан с учётом индивидуальных особенностей организма, возраста, пола, уровня физической активности и состояния здоровья. Общие принципы: \n● Снижение калорийности; \n● Сбалансированный рацион; \n● Разнообразие продуктов; \n● Питьевой режим; \n● Ограничение вредных продуктов;",
),
"standard": MessageLookupByLibrary.simpleMessage("Стандарт"),
"startDiet": MessageLookupByLibrary.simpleMessage("Начать диету"),
"tell_your_schedule": MessageLookupByLibrary.simpleMessage(
"Расскажите в свободной форме о том как просыпаетесь, когда едите, когда ложитесь спать:",
),
"three_options": MessageLookupByLibrary.simpleMessage("3 опции"),
"toOffer": MessageLookupByLibrary.simpleMessage("Предложить"),
"to_draft": MessageLookupByLibrary.simpleMessage("В черновик"),
"unfavorite_foods": MessageLookupByLibrary.simpleMessage(
"Нелюбимые продукты",
),
"up_pressure": MessageLookupByLibrary.simpleMessage(
"Верхние показатели давления:",
),
"user_authorization": MessageLookupByLibrary.simpleMessage(
"Аутентификация",
),
"user_data": MessageLookupByLibrary.simpleMessage("Данные пользователя"),
"user_registration": MessageLookupByLibrary.simpleMessage(
"Регистрация пользователя",
),
"username": MessageLookupByLibrary.simpleMessage("Имя пользователя"),
"vegan": MessageLookupByLibrary.simpleMessage("Веган."),
"vegan_type": MessageLookupByLibrary.simpleMessage("Вегетарианская"),
"vegan_type_desc": MessageLookupByLibrary.simpleMessage(
"Диета основана на потреблении растительной пищи и отказе от продуктов животного происхождения. Такой тип питания может быть разнообразным и вкусным, а также полезным для здоровья.",
),
"waist": MessageLookupByLibrary.simpleMessage("Талия"),
"waist_circumference": MessageLookupByLibrary.simpleMessage("Обхват талии"),
"weight": MessageLookupByLibrary.simpleMessage("Вес"),
"weight_chart": MessageLookupByLibrary.simpleMessage("График веса"),
"weight_measurements": MessageLookupByLibrary.simpleMessage("Замер веса"),
"wellness": MessageLookupByLibrary.simpleMessage("Улучшение самочувствия"),
"wellness_type": MessageLookupByLibrary.simpleMessage(
"Улучшение самочувствия",
),
"wellness_type_desc": MessageLookupByLibrary.simpleMessage(
"Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы.",
),
"what_is_your_name": MessageLookupByLibrary.simpleMessage("Как вас зовут?"),
"wrong_credentials": MessageLookupByLibrary.simpleMessage(
"Неверные данные пользователя",
),
"you_will_get": MessageLookupByLibrary.simpleMessage("Вы получите"),
"your_blood_pressure": MessageLookupByLibrary.simpleMessage(
"Ваше артериальное давление?",
),
"your_favorite_sports": MessageLookupByLibrary.simpleMessage(
"Ваши спортивные увлечения?",
),
"your_gender": MessageLookupByLibrary.simpleMessage("Ваш пол?"),
"your_height": MessageLookupByLibrary.simpleMessage("Ваш рост:"),
"your_schedule": MessageLookupByLibrary.simpleMessage("Ваш режим дня?"),
"your_weight": MessageLookupByLibrary.simpleMessage("Ваш вес:"),
};
"Moscow": MessageLookupByLibrary.simpleMessage("Москва"),
"Russia": MessageLookupByLibrary.simpleMessage("Россия"),
"about_diet": MessageLookupByLibrary.simpleMessage("Об этой диете"),
"about_plan": MessageLookupByLibrary.simpleMessage("Об этом плане"),
"active": MessageLookupByLibrary.simpleMessage("Активен"),
"active_diet_state": MessageLookupByLibrary.simpleMessage("Активная"),
"add": MessageLookupByLibrary.simpleMessage("Добавить"),
"add_cover": MessageLookupByLibrary.simpleMessage("Добавьте обложку"),
"add_measure": MessageLookupByLibrary.simpleMessage("Добавить замер"),
"adding_plan": MessageLookupByLibrary.simpleMessage("Добавление плана"),
"additional_information":
MessageLookupByLibrary.simpleMessage("Дополнительная информация"),
"admin": MessageLookupByLibrary.simpleMessage("Админ"),
"all": MessageLookupByLibrary.simpleMessage("Все"),
"allergies_presence":
MessageLookupByLibrary.simpleMessage("Наличие аллергии"),
"another_form_of_comment":
MessageLookupByLibrary.simpleMessage("комментария"),
"another_form_of_day": MessageLookupByLibrary.simpleMessage("дня"),
"answer": MessageLookupByLibrary.simpleMessage("Ответить"),
"archived": MessageLookupByLibrary.simpleMessage("В архиве"),
"authentificate": MessageLookupByLibrary.simpleMessage("Войти"),
"average_pressure": MessageLookupByLibrary.simpleMessage(
"Средние показатели давления:"),
"bodymass_chart": MessageLookupByLibrary.simpleMessage("График ИМТ"),
"by_default": MessageLookupByLibrary.simpleMessage("По умолчянию"),
"calories": MessageLookupByLibrary.simpleMessage("Калории"),
"carb_type": MessageLookupByLibrary.simpleMessage("Мучное"),
"carb_type_desc": MessageLookupByLibrary.simpleMessage(
"Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы."),
"carbohydrates": MessageLookupByLibrary.simpleMessage("Углеводы"),
"chat": MessageLookupByLibrary.simpleMessage("Чат"),
"chest": MessageLookupByLibrary.simpleMessage("Грудь"),
"chest_circumference":
MessageLookupByLibrary.simpleMessage("Обхват груди"),
"choose_diet_duration":
MessageLookupByLibrary.simpleMessage("Выберите срок диеты"),
"choose_plan_duration":
MessageLookupByLibrary.simpleMessage("Выберите срок плана"),
"choose_start_diet_date": MessageLookupByLibrary.simpleMessage(
"Выберите дату старты диеты. Она отобразиться у Вас в дневнике"),
"circumference_empty": MessageLookupByLibrary.simpleMessage(
"Все замеры обязательны к заполнению"),
"circumference_is_not_a_number": MessageLookupByLibrary.simpleMessage(
"Все замеры должны быть числами"),
"circumferences_chart":
MessageLookupByLibrary.simpleMessage("График обхватов"),
"city": MessageLookupByLibrary.simpleMessage("Город"),
"city_of_living":
MessageLookupByLibrary.simpleMessage("Город проживания"),
"comfort": MessageLookupByLibrary.simpleMessage("Комфорт"),
"comment": MessageLookupByLibrary.simpleMessage("комментарий"),
"comments": MessageLookupByLibrary.simpleMessage("Комментарии"),
"comments_plural": MessageLookupByLibrary.simpleMessage("комментариев"),
"complain": MessageLookupByLibrary.simpleMessage("Пожаловаться"),
"continueButton": MessageLookupByLibrary.simpleMessage("Продолжить"),
"continue_": MessageLookupByLibrary.simpleMessage("Продолжить"),
"copied": MessageLookupByLibrary.simpleMessage("cкопирован"),
"copy_link": MessageLookupByLibrary.simpleMessage("Скопировать ссылку"),
"country": MessageLookupByLibrary.simpleMessage("Страна"),
"create_diet": MessageLookupByLibrary.simpleMessage("Создание диеты"),
"customer_price":
MessageLookupByLibrary.simpleMessage("Цена для покупателя"),
"date_of_birth": MessageLookupByLibrary.simpleMessage("Дата рождения"),
"day": MessageLookupByLibrary.simpleMessage("День"),
"days": MessageLookupByLibrary.simpleMessage("дней"),
"deleted": MessageLookupByLibrary.simpleMessage("Удалена"),
"diary": MessageLookupByLibrary.simpleMessage("Дневник"),
"diet": MessageLookupByLibrary.simpleMessage("Диета"),
"diet_author": MessageLookupByLibrary.simpleMessage("Автор диеты"),
"diet_plan": MessageLookupByLibrary.simpleMessage("План диеты"),
"diet_state": MessageLookupByLibrary.simpleMessage("Статус диеты"),
"diet_type": MessageLookupByLibrary.simpleMessage("Тип диеты"),
"do_you_have_hypertension_tendency":
MessageLookupByLibrary.simpleMessage("Есть ли у вас гипертония?"),
"do_you_have_hypotension_tendency":
MessageLookupByLibrary.simpleMessage("Есть ли у вас гипотония?"),
"do_you_take_pills": MessageLookupByLibrary.simpleMessage(
"Принимаете ли вы лекарства или бады?"),
"down_pressure":
MessageLookupByLibrary.simpleMessage("Нижние показатели давления:"),
"draft": MessageLookupByLibrary.simpleMessage("Черновик"),
"drafted": MessageLookupByLibrary.simpleMessage("Черновики"),
"eating_behavior":
MessageLookupByLibrary.simpleMessage("Пищевое поведение"),
"eating_behavior_type": MessageLookupByLibrary.simpleMessage(
"Формирование пищевого поведения"),
"eating_behavior_type_desc": MessageLookupByLibrary.simpleMessage(
"Формирование здорового пищевого поведения — это процесс, который включает в себя изменение привычек и отношения к еде. Он направлен на то, чтобы помочь людям научиться правильно питаться и поддерживать здоровый образ жизни.Основные принципы формирования здорового пищевого поведения: \n● Разнообразие продуктов; \n● Сбалансированный рацион; \n● Питьевой режим; \n● Ограничение вредных продуктов; \n● Регулярные приёмы пищи; \n● Умеренность;Осознанное питание; \n● Физическая активность;"),
"economy": MessageLookupByLibrary.simpleMessage("Эконом"),
"edit": MessageLookupByLibrary.simpleMessage("Редактировать"),
"email": MessageLookupByLibrary.simpleMessage("Электронная почта"),
"email_empty": MessageLookupByLibrary.simpleMessage(
"Необходимо ввести адрес электронной почты"),
"email_invalid": MessageLookupByLibrary.simpleMessage(
"Некорректный адрес электронной почты"),
"enter_additional_information": MessageLookupByLibrary.simpleMessage(
"Введите любую дополнительную информацию на своё усмотрение:"),
"enter_diet_description":
MessageLookupByLibrary.simpleMessage("Введите описание диеты"),
"enter_diet_name":
MessageLookupByLibrary.simpleMessage("Выведите название диеты"),
"enter_information":
MessageLookupByLibrary.simpleMessage("Введите информацию..."),
"exit": MessageLookupByLibrary.simpleMessage("Выйти"),
"expand": MessageLookupByLibrary.simpleMessage("Показать полностью"),
"fats": MessageLookupByLibrary.simpleMessage("Жиры"),
"favorite_foods":
MessageLookupByLibrary.simpleMessage("Любимые продукты"),
"favorite_unfavorite_foods": MessageLookupByLibrary.simpleMessage(
"Ваши любимые/нелюбимые продукты питания?"),
"female": MessageLookupByLibrary.simpleMessage("Женский"),
"fill_in_information":
MessageLookupByLibrary.simpleMessage("Заполните информацию:"),
"finish": MessageLookupByLibrary.simpleMessage("Завершить"),
"first_name": MessageLookupByLibrary.simpleMessage("Имя"),
"first_name_empty": MessageLookupByLibrary.simpleMessage(
"Имя обязательно для заполнения"),
"first_name_short": MessageLookupByLibrary.simpleMessage(
"В имени должен быть минимум 1 символ"),
"fit_type": MessageLookupByLibrary.simpleMessage("Поддержание формы"),
"fit_type_desc": MessageLookupByLibrary.simpleMessage(
"План питания для поддержания формы сбалансированный и разнообразный, чтобы обеспечить организм всеми необходимыми питательными веществами. Общие принципы: \n● Дробное питание; \n● Разнообразие продуктов; \n● Контроль порций; \n● Питьевой режим; \n● Ограничение вредных продуктов;"),
"fitness": MessageLookupByLibrary.simpleMessage("Поддержка формы"),
"from": MessageLookupByLibrary.simpleMessage("от"),
"from_a_to_z": MessageLookupByLibrary.simpleMessage("Название: А-Я"),
"from_z_to_a": MessageLookupByLibrary.simpleMessage("Название: Я-А"),
"gallery": MessageLookupByLibrary.simpleMessage("Галерея"),
"gr": MessageLookupByLibrary.simpleMessage("гр"),
"grams": MessageLookupByLibrary.simpleMessage("гр"),
"height_measurements":
MessageLookupByLibrary.simpleMessage("Замер роста"),
"hide": MessageLookupByLibrary.simpleMessage("Свернуть"),
"hip_circumference":
MessageLookupByLibrary.simpleMessage("Обхват бедер"),
"hips": MessageLookupByLibrary.simpleMessage("Бедра"),
"in_progress": MessageLookupByLibrary.simpleMessage("В процессе"),
"inactive_diet_state": MessageLookupByLibrary.simpleMessage("Черновик"),
"initial_measurements":
MessageLookupByLibrary.simpleMessage("Первичные замеры"),
"label": MessageLookupByLibrary.simpleMessage("Название"),
"language": MessageLookupByLibrary.simpleMessage("Язык"),
"last_name": MessageLookupByLibrary.simpleMessage("Фамилия"),
"last_name_empty": MessageLookupByLibrary.simpleMessage(
"Фамилия обязательна для заполнения"),
"last_name_short": MessageLookupByLibrary.simpleMessage(
"В фамилии должен быть минимум 6 символов"),
"lbl_english": MessageLookupByLibrary.simpleMessage("English"),
"light_type": MessageLookupByLibrary.simpleMessage("Легкая"),
"light_type_desc": MessageLookupByLibrary.simpleMessage(
"С некоторыми видами диет можно употреблять алкоголь в умеренных количествах. Рекомендуется совмещать вместе с низкокалорийными закусками."),
"link_was_copied":
MessageLookupByLibrary.simpleMessage("Ссылка успешно скопирована"),
"loading": MessageLookupByLibrary.simpleMessage("Загрузка..."),
"male": MessageLookupByLibrary.simpleMessage("Мужской"),
"meal": MessageLookupByLibrary.simpleMessage("Приём пищи"),
"meat": MessageLookupByLibrary.simpleMessage("Мясо"),
"meat_type": MessageLookupByLibrary.simpleMessage("С мясом"),
"meat_type_desc": MessageLookupByLibrary.simpleMessage(
"Диета с мясом полезная и сбалансированная. Включает в себя нежирное мясо: курицу, индейку, кролика или говядину. В сочетании с овощами получаете все необходимые витамины и минералы. Не забывайте о порциях и старайтесь есть медленно, прислушиваясь к своему организму."),
"milliliter": MessageLookupByLibrary.simpleMessage("мл"),
"minimal_customer_price": MessageLookupByLibrary.simpleMessage(
"Минимальная цена для покупателя: 1200₽"),
"most_new": MessageLookupByLibrary.simpleMessage("Самые новые"),
"most_old": MessageLookupByLibrary.simpleMessage("Самые старые"),
"msg_network_err":
MessageLookupByLibrary.simpleMessage("Сетевая ошибка!"),
"msg_something_went_wrong":
MessageLookupByLibrary.simpleMessage("Что то пошло не так!"),
"my_blog": MessageLookupByLibrary.simpleMessage("Мой блог"),
"my_diets": MessageLookupByLibrary.simpleMessage("Мои диеты"),
"my_plans": MessageLookupByLibrary.simpleMessage("Мои планы"),
"my_settings": MessageLookupByLibrary.simpleMessage("Мои настройки"),
"name_empty": MessageLookupByLibrary.simpleMessage(
"Имя пользователя обязательно для заполнения"),
"name_short": MessageLookupByLibrary.simpleMessage(
"В имени пользователя должно быть минимум 6 символов"),
"no": MessageLookupByLibrary.simpleMessage("Нет"),
"no_account":
MessageLookupByLibrary.simpleMessage("У меня нет аккаунта"),
"one_option": MessageLookupByLibrary.simpleMessage("1 опция"),
"option": MessageLookupByLibrary.simpleMessage("Опция"),
"option_content":
MessageLookupByLibrary.simpleMessage("Содержание опции"),
"option_content_hint_text":
MessageLookupByLibrary.simpleMessage("На связи 7 дней в неделю"),
"option_desc": MessageLookupByLibrary.simpleMessage("Описание опции"),
"option_desc_hint_text": MessageLookupByLibrary.simpleMessage(
"Этот план питания для похудения на 7 дней сбалансированный и разнообразный"),
"option_duration":
MessageLookupByLibrary.simpleMessage("Продолжительность опции"),
"option_personal_diet_desc": MessageLookupByLibrary.simpleMessage(
"Опция подразумевает создание индивидуальной диеты"),
"option_title": MessageLookupByLibrary.simpleMessage("Заголовок опции"),
"order": MessageLookupByLibrary.simpleMessage("Порядок"),
"password": MessageLookupByLibrary.simpleMessage("Пароль"),
"password_confirmation":
MessageLookupByLibrary.simpleMessage("Подтвердите пароль"),
"password_confirmation_mismatch":
MessageLookupByLibrary.simpleMessage("Пароли не совпадают"),
"password_empty": MessageLookupByLibrary.simpleMessage(
"Пароль обязателен для заполнения"),
"password_short": MessageLookupByLibrary.simpleMessage(
"В пароле должно быть минимум 6 символов"),
"pcs": MessageLookupByLibrary.simpleMessage("шт"),
"personal": MessageLookupByLibrary.simpleMessage("Персональные"),
"personal_diet_state":
MessageLookupByLibrary.simpleMessage("Персональная"),
"photo": MessageLookupByLibrary.simpleMessage("Фото"),
"place_of_living":
MessageLookupByLibrary.simpleMessage("Место проживания"),
"plan_author": 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("Популярные"),
"preferred_language":
MessageLookupByLibrary.simpleMessage("Предпочитаемый язык общения"),
"pressure_empty": MessageLookupByLibrary.simpleMessage(
"Все показатели давления обязательны к заполнению"),
"pressure_is_not_a_number": MessageLookupByLibrary.simpleMessage(
"Все показатели давления должны быть числами"),
"preview": MessageLookupByLibrary.simpleMessage("Превью"),
"price": MessageLookupByLibrary.simpleMessage("Цена"),
"price_explanation": MessageLookupByLibrary.simpleMessage(
"Разница в ценах, это комиссия Итвио"),
"profile": MessageLookupByLibrary.simpleMessage("Профиль"),
"proteins": MessageLookupByLibrary.simpleMessage("Белки"),
"publish": MessageLookupByLibrary.simpleMessage("Опубликовать"),
"published": MessageLookupByLibrary.simpleMessage("Опубликованные"),
"removeFromActive":
MessageLookupByLibrary.simpleMessage("Снять с публикации"),
"save": MessageLookupByLibrary.simpleMessage("Сохранить"),
"services": MessageLookupByLibrary.simpleMessage("Оказание услуг"),
"settings": MessageLookupByLibrary.simpleMessage("Настройки"),
"share": MessageLookupByLibrary.simpleMessage("Поделиться"),
"skip": MessageLookupByLibrary.simpleMessage("Пропустить"),
"slim": MessageLookupByLibrary.simpleMessage("Похудение"),
"slim_type": MessageLookupByLibrary.simpleMessage("Похудение"),
"slim_type_desc": MessageLookupByLibrary.simpleMessage(
"План питания для похудения должен быть разработан с учётом индивидуальных особенностей организма, возраста, пола, уровня физической активности и состояния здоровья. Общие принципы: \n● Снижение калорийности; \n● Сбалансированный рацион; \n● Разнообразие продуктов; \n● Питьевой режим; \n● Ограничение вредных продуктов;"),
"standard": MessageLookupByLibrary.simpleMessage("Стандарт"),
"startDiet": MessageLookupByLibrary.simpleMessage("Начать диету"),
"tell_your_schedule": MessageLookupByLibrary.simpleMessage(
"Расскажите в свободной форме о том как просыпаетесь, когда едите, когда ложитесь спать:"),
"three_options": MessageLookupByLibrary.simpleMessage("3 опции"),
"toOffer": MessageLookupByLibrary.simpleMessage("Предложить"),
"to_draft": MessageLookupByLibrary.simpleMessage("В черновик"),
"unfavorite_foods":
MessageLookupByLibrary.simpleMessage("Нелюбимые продукты"),
"up_pressure": MessageLookupByLibrary.simpleMessage(
"Верхние показатели давления:"),
"user_authorization":
MessageLookupByLibrary.simpleMessage("Аутентификация"),
"user_data":
MessageLookupByLibrary.simpleMessage("Данные пользователя"),
"user_registration":
MessageLookupByLibrary.simpleMessage("Регистрация пользователя"),
"username": MessageLookupByLibrary.simpleMessage("Имя пользователя"),
"vegan": MessageLookupByLibrary.simpleMessage("Веган."),
"vegan_type": MessageLookupByLibrary.simpleMessage("Вегетарианская"),
"vegan_type_desc": MessageLookupByLibrary.simpleMessage(
"Диета основана на потреблении растительной пищи и отказе от продуктов животного происхождения. Такой тип питания может быть разнообразным и вкусным, а также полезным для здоровья."),
"waist": MessageLookupByLibrary.simpleMessage("Талия"),
"waist_circumference":
MessageLookupByLibrary.simpleMessage("Обхват талии"),
"weight": MessageLookupByLibrary.simpleMessage("Вес"),
"weight_chart": MessageLookupByLibrary.simpleMessage("График веса"),
"weight_measurements":
MessageLookupByLibrary.simpleMessage("Замер веса"),
"wellness":
MessageLookupByLibrary.simpleMessage("Улучшение самочувствия"),
"wellness_type":
MessageLookupByLibrary.simpleMessage("Улучшение самочувствия"),
"wellness_type_desc": MessageLookupByLibrary.simpleMessage(
"Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы."),
"what_is_your_name":
MessageLookupByLibrary.simpleMessage("Как вас зовут?"),
"wrong_credentials": MessageLookupByLibrary.simpleMessage(
"Неверные данные пользователя"),
"you_will_get": MessageLookupByLibrary.simpleMessage("Вы получите"),
"your_blood_pressure":
MessageLookupByLibrary.simpleMessage("Ваше артериальное давление?"),
"your_favorite_sports":
MessageLookupByLibrary.simpleMessage("Ваши спортивные увлечения?"),
"your_gender": MessageLookupByLibrary.simpleMessage("Ваш пол?"),
"your_height": MessageLookupByLibrary.simpleMessage("Ваш рост:"),
"your_schedule": MessageLookupByLibrary.simpleMessage("Ваш режим дня?"),
"your_weight": MessageLookupByLibrary.simpleMessage("Ваш вес:")
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -230,5 +230,22 @@
"diet_started": "Диета уже начата",
"cancel_diet": "Отменить диету?",
"confirm": "Подтвердить",
"could_not_cancel_diet": "Не удалось отменить диету"
"could_not_cancel_diet": "Не удалось отменить диету",
"hours": "часов",
"hour": "час",
"another_form_of_hour": "часа",
"minutes": "минуты",
"minute": "минута",
"another_form_of_minute": "минут",
"there_is_a_video": "Есть видео",
"recipe_author": "Автор рецепта",
"about_recipe": "Об этом рецепте",
"stages": "этапов",
"stage": "этап",
"another_form_of_stage": "этапа",
"check_out_video_recipe": "Посмотреть видео рецепт",
"could_not_launch_video": "Не удалось запустить видео",
"ingredients": "Ингредиенты",
"step": "Шаг",
"step_by_step_photo_recipe": "Пошаговый фоторецепт"
}