forked from eatvio/eatvio_app
fix(plans, recipes): added new end points for plans and recipes creation
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
String baseUrl = 'https://eatvio.ru';
|
// String baseUrl = 'https://eatvio.ru';
|
||||||
// String baseUrl = 'https://dev2.eatvio.com';
|
String baseUrl = 'https://dev2.eatvio.com';
|
||||||
|
|
||||||
const List<int> durationValues = [7, 14, 21];
|
const List<int> durationValues = [7, 14, 21];
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ Future<void> init() async {
|
|||||||
sl.registerFactory<CreatePlanCubit>(() => CreatePlanCubit(createPlanRepository: sl()));
|
sl.registerFactory<CreatePlanCubit>(() => CreatePlanCubit(createPlanRepository: sl()));
|
||||||
sl.registerFactory<FeedProfileCubit>(() => FeedProfileCubit(profileRepository: sl()));
|
sl.registerFactory<FeedProfileCubit>(() => FeedProfileCubit(profileRepository: sl()));
|
||||||
sl.registerFactory<AboutPlanCubit>(() => AboutPlanCubit(planRepository: sl()));
|
sl.registerFactory<AboutPlanCubit>(() => AboutPlanCubit(planRepository: sl()));
|
||||||
sl.registerFactory<CreateRecipeCubit>(() => CreateRecipeCubit(recipeRepository: sl()));
|
sl.registerFactory<CreateRecipeCubit>(() => CreateRecipeCubit(recipeRepository: sl(), glossaryRepository: sl()));
|
||||||
sl.registerFactory<MyDietsCubit>(() => MyDietsCubit(myDietsRepository: sl(), createDietRepository: sl()));
|
sl.registerFactory<MyDietsCubit>(() => MyDietsCubit(myDietsRepository: sl(), createDietRepository: sl()));
|
||||||
sl.registerFactory<MyPlansCubit>(() => MyPlansCubit(myPlansRepository: sl()));
|
sl.registerFactory<MyPlansCubit>(() => MyPlansCubit(myPlansRepository: sl()));
|
||||||
sl.registerFactory<UserDetailsCubit>(() => UserDetailsCubit(profileRepository: sl()));
|
sl.registerFactory<UserDetailsCubit>(() => UserDetailsCubit(profileRepository: sl()));
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
|
||||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
@@ -20,20 +16,5 @@ class CreatePlanModel with _$CreatePlanModel {
|
|||||||
required List<bool> type,
|
required List<bool> type,
|
||||||
XFile? image,
|
XFile? image,
|
||||||
required List<PacketOptions> options,
|
required List<PacketOptions> options,
|
||||||
}) = _CreatePlanModel;
|
}) = _CreatePlanModel;
|
||||||
|
|
||||||
Future<Map<String, dynamic>> toCreationJson() async => {
|
|
||||||
'name': name,
|
|
||||||
'duration': duration,
|
|
||||||
'state': 'active',
|
|
||||||
'description_md': details,
|
|
||||||
'type[slim]': type[0] ? '1' : '0',
|
|
||||||
'type[fit]': type[1] ? '1' : '0',
|
|
||||||
'type[wellness]': type[2] ? '1' : '0',
|
|
||||||
'type[behavior]': type[3] ? '1' : '0',
|
|
||||||
'image': image != null ? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image!.path))).path, filename: image!.name) : null,
|
|
||||||
};
|
|
||||||
|
|
||||||
Map<String, dynamic> toOptionCreationJson() => {'packet1_options': options[0]};
|
|
||||||
Map<String, dynamic> toOptionsCreationJson() => {'packet1_options': options[0], 'packet2_options': options[1], 'packet3_options': options[2]};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/core/const.dart';
|
import 'package:eatvio_app/core/const.dart';
|
||||||
|
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
||||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
|
||||||
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
|
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -386,17 +391,63 @@ class CreatePlanCubit extends Cubit<CreatePlanState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> publishPlan() async {
|
Future<void> publishPlan() async {
|
||||||
if (validateAllFields()) {
|
try {
|
||||||
emit(state.copyWith(isLoading: true));
|
if (validateAllFields()) {
|
||||||
final planModel = await state.planData.toCreationJson();
|
emit(state.copyWith(isLoading: true));
|
||||||
final plan = await createPlanRepository.createPlan(model: planModel);
|
final planMap = <String, dynamic>{};
|
||||||
if (state.planData.options[1].short.isNotEmpty) {
|
|
||||||
final options = state.planData.toOptionsCreationJson();
|
planMap.addAll({
|
||||||
await createPlanRepository.createPacketOptions(slug: plan.slug, model: options);
|
'name': state.planData.name,
|
||||||
} else {
|
'duration': state.planData.duration,
|
||||||
final option = state.planData.toOptionCreationJson();
|
'state': 'active',
|
||||||
await createPlanRepository.createPacketOption(slug: plan.slug, model: option);
|
'description_md': state.planData.details,
|
||||||
|
'type[slim]': state.planData.type[0] ? '1' : '0',
|
||||||
|
'type[fit]': state.planData.type[1] ? '1' : '0',
|
||||||
|
'type[wellness]': state.planData.type[2] ? '1' : '0',
|
||||||
|
'type[behavior]': state.planData.type[3] ? '1' : '0',
|
||||||
|
'image': state.planData.image != null
|
||||||
|
? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(state.planData.image!.path))).path,
|
||||||
|
filename: state.planData.image!.name)
|
||||||
|
: null
|
||||||
|
});
|
||||||
|
if (state.planData.options[1].short.isNotEmpty) {
|
||||||
|
planMap.addAll({
|
||||||
|
'packets_count': 3,
|
||||||
|
'packet1_options': {
|
||||||
|
'short': state.planData.options[0].short,
|
||||||
|
'price': int.parse((state.planData.options[0].price as double).toStringAsFixed(0)),
|
||||||
|
'description_md': state.planData.options[0].description,
|
||||||
|
'items': Map.fromIterable(state.planData.options[0].items),
|
||||||
|
},
|
||||||
|
'packet2_options': {
|
||||||
|
'short': state.planData.options[1].short,
|
||||||
|
'price': int.parse((state.planData.options[1].price as double).toStringAsFixed(0)),
|
||||||
|
'description_md': state.planData.options[1].description,
|
||||||
|
'items': Map.fromIterable(state.planData.options[1].items),
|
||||||
|
},
|
||||||
|
'packet3_options': {
|
||||||
|
'short': state.planData.options[2].short,
|
||||||
|
'price': int.parse((state.planData.options[2].price as double).toStringAsFixed(0)),
|
||||||
|
'description_md': state.planData.options[2].description,
|
||||||
|
'items': Map.fromIterable(state.planData.options[2].items),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
planMap.addAll({
|
||||||
|
'packets_count': 1,
|
||||||
|
'packet1_options': {
|
||||||
|
'short': state.planData.options[0].short,
|
||||||
|
'price': int.parse((state.planData.options[0].price as double).toStringAsFixed(0)),
|
||||||
|
'description_md': state.planData.options[0].description,
|
||||||
|
'items': Map.fromIterable(state.planData.options[0].items),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await createPlanRepository.createPlan(model: planMap);
|
||||||
|
emit(state.copyWith(isLoading: false, planCreated: true));
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(e.toString());
|
||||||
emit(state.copyWith(isLoading: false, planCreated: true));
|
emit(state.copyWith(isLoading: false, planCreated: true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/features/create_plan/models/plan_slug_model.dart';
|
|
||||||
import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart';
|
import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart';
|
||||||
|
|
||||||
class CreatePlanRepository {
|
class CreatePlanRepository {
|
||||||
@@ -7,24 +6,10 @@ class CreatePlanRepository {
|
|||||||
|
|
||||||
CreatePlanRepository({required this.createPlanService});
|
CreatePlanRepository({required this.createPlanService});
|
||||||
|
|
||||||
Future<PlanSlugModel> createPlan({
|
Future<void> createPlan({
|
||||||
required Map<String, dynamic> model,
|
required Map<String, dynamic> model,
|
||||||
}) async {
|
}) async {
|
||||||
final formData = FormData.fromMap(model);
|
final formData = FormData.fromMap(model);
|
||||||
return await createPlanService.createPlan(formData: formData);
|
return await createPlanService.createPlan(formData: formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> createPacketOption({
|
|
||||||
required String slug,
|
|
||||||
required Map<String, dynamic> model,
|
|
||||||
}) async {
|
|
||||||
await createPlanService.createPacketOption(slug: slug, model: model);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> createPacketOptions({
|
|
||||||
required String slug,
|
|
||||||
required Map<String, dynamic> model,
|
|
||||||
}) async {
|
|
||||||
await createPlanService.createPacketOptions(slug: slug, model: model);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/features/create_plan/models/plan_slug_model.dart';
|
|
||||||
import 'package:retrofit/retrofit.dart';
|
import 'package:retrofit/retrofit.dart';
|
||||||
|
|
||||||
part 'create_plan_service.g.dart';
|
part 'create_plan_service.g.dart';
|
||||||
@@ -8,21 +7,10 @@ part 'create_plan_service.g.dart';
|
|||||||
abstract class CreatePlanService {
|
abstract class CreatePlanService {
|
||||||
factory CreatePlanService(Dio dio, {String baseUrl}) = _CreatePlanService;
|
factory CreatePlanService(Dio dio, {String baseUrl}) = _CreatePlanService;
|
||||||
|
|
||||||
@POST('/api/plans/create')
|
@POST('/api/plans/create/published')
|
||||||
@MultiPart()
|
@MultiPart()
|
||||||
Future<PlanSlugModel> createPlan({
|
Future<void> createPlan({
|
||||||
@Body() required FormData formData,
|
@Body() required FormData formData,
|
||||||
});
|
});
|
||||||
|
|
||||||
@POST('/api/plans/create/{slug}/packet_option')
|
|
||||||
Future<void> createPacketOption({
|
|
||||||
@Path() required String slug,
|
|
||||||
@Body() required Map<String, dynamic> model,
|
|
||||||
});
|
|
||||||
|
|
||||||
@POST('/api/plans/create/{slug}/packet_options')
|
|
||||||
Future<void> createPacketOptions({
|
|
||||||
@Path() required String slug,
|
|
||||||
@Body() required Map<String, dynamic> model,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class Ingredient with _$Ingredient {
|
|||||||
required String name,
|
required String name,
|
||||||
required String unit,
|
required String unit,
|
||||||
required String portion,
|
required String portion,
|
||||||
|
@JsonKey(name: 'product_nutritional_id') required int productId,
|
||||||
}) = _Ingredient;
|
}) = _Ingredient;
|
||||||
|
|
||||||
factory Ingredient.fromJson(Map<String, Object?> json) => _$IngredientFromJson(json);
|
factory Ingredient.fromJson(Map<String, Object?> json) => _$IngredientFromJson(json);
|
||||||
|
|||||||
@@ -5,12 +5,17 @@ import 'package:eatvio_app/features/create_recipe/entities/recipe_create_entity.
|
|||||||
import 'package:eatvio_app/features/create_recipe/models/ingredient.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/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
|
||||||
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
|
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
|
||||||
|
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
||||||
final RecipeRepository recipeRepository;
|
final RecipeRepository recipeRepository;
|
||||||
CreateRecipeCubit({required this.recipeRepository})
|
final GlossaryRepository glossaryRepository;
|
||||||
: super(
|
CreateRecipeCubit({
|
||||||
|
required this.recipeRepository,
|
||||||
|
required this.glossaryRepository,
|
||||||
|
}) : super(
|
||||||
const CreateRecipeState(
|
const CreateRecipeState(
|
||||||
entity: RecipeCreateEntity(
|
entity: RecipeCreateEntity(
|
||||||
typeOfRecipe: TypeOfRecipe.firstMeals,
|
typeOfRecipe: TypeOfRecipe.firstMeals,
|
||||||
@@ -56,6 +61,12 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
|||||||
emit(state.copyWith.entity(ingredients: ingredients));
|
emit(state.copyWith.entity(ingredients: ingredients));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateIngredient({required int index, required Ingredient ingredient}) {
|
||||||
|
final ingredients = [...state.entity.ingredients];
|
||||||
|
ingredients[index] = ingredient;
|
||||||
|
emit(state.copyWith.entity(ingredients: ingredients));
|
||||||
|
}
|
||||||
|
|
||||||
replaceIngredientPosition(int oldIndex, int newIndex) {
|
replaceIngredientPosition(int oldIndex, int newIndex) {
|
||||||
List<Ingredient> ingredients = [...state.entity.ingredients];
|
List<Ingredient> ingredients = [...state.entity.ingredients];
|
||||||
final item = ingredients.removeAt(oldIndex);
|
final item = ingredients.removeAt(oldIndex);
|
||||||
@@ -239,4 +250,8 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
|
||||||
|
return await glossaryRepository.getGlossaryListByQuery(query);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,314 @@
|
|||||||
|
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_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/diary/presentation/bloc/diary_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart';
|
||||||
|
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||||
|
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AddPortionWidget extends StatefulWidget {
|
||||||
|
const AddPortionWidget({
|
||||||
|
super.key,
|
||||||
|
required this.createRecipeCubit,
|
||||||
|
this.portionIndex,
|
||||||
|
this.productNameControllerText,
|
||||||
|
this.quantityControllerText,
|
||||||
|
});
|
||||||
|
final CreateRecipeCubit createRecipeCubit;
|
||||||
|
final String? productNameControllerText;
|
||||||
|
final String? quantityControllerText;
|
||||||
|
final int? portionIndex;
|
||||||
|
@override
|
||||||
|
State<AddPortionWidget> createState() => _AddPortionWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AddPortionWidgetState extends State<AddPortionWidget> {
|
||||||
|
GlossaryModel? selectedGlossary;
|
||||||
|
final productNameController = TextEditingController();
|
||||||
|
final quantityController = TextEditingController();
|
||||||
|
List<GlossaryModel> glossariesList = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
productNameController.text = widget.productNameControllerText ?? '';
|
||||||
|
quantityController.text = widget.quantityControllerText ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16) +
|
||||||
|
EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Добавление блюда',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF323F4B),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.8 - 32,
|
||||||
|
child: CustomTextFormField(
|
||||||
|
controller: productNameController,
|
||||||
|
autofocus: true,
|
||||||
|
hintText: 'Введите название продукта/блюда',
|
||||||
|
onChanged: (query) {
|
||||||
|
EasyDebounce.debounce(
|
||||||
|
'glossary-search',
|
||||||
|
const Duration(milliseconds: 250),
|
||||||
|
() async {
|
||||||
|
selectedGlossary = null;
|
||||||
|
if (query.isEmpty) {
|
||||||
|
glossariesList = [];
|
||||||
|
selectedGlossary = null;
|
||||||
|
setState(() {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
glossariesList = await widget.createRecipeCubit.getGlossaryListByQuery(query);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.2 - 4,
|
||||||
|
child: CustomTextFormField(
|
||||||
|
controller: quantityController,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
hintText: selectedGlossary == null
|
||||||
|
? '100'
|
||||||
|
: selectedGlossary!.grInPortion == 0
|
||||||
|
? '100'
|
||||||
|
: '1',
|
||||||
|
suffixText: selectedGlossary == null
|
||||||
|
? 'гр'
|
||||||
|
: selectedGlossary?.grInPortion == 0
|
||||||
|
? 'гр'
|
||||||
|
: 'шт',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
glossariesList.isEmpty ? const SizedBox(height: 16) : const SizedBox.shrink(),
|
||||||
|
Container(
|
||||||
|
constraints: const 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: const 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: const EdgeInsets.only(left: 12),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: glossariesList.length,
|
||||||
|
separatorBuilder: (BuildContext context, int index) {
|
||||||
|
return const CustomDivider();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (widget.portionIndex != null)
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
widget.createRecipeCubit.removeIngredient(widget.portionIndex!);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
child: Ink(
|
||||||
|
width: 52,
|
||||||
|
height: 52,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
color: const Color(0xFFF6F6F6),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.delete_outline_rounded,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.portionIndex != null) const SizedBox(width: 8),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => addPortion(context),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
child: Ink(
|
||||||
|
width: MediaQuery.of(context).size.width - (widget.portionIndex == null ? 32 : 92),
|
||||||
|
height: 52,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
color: appTheme.blueA400,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
widget.portionIndex == null ? 'Добавить' : 'Сохранить',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addPortion(BuildContext context) async {
|
||||||
|
final portionIndex = widget.portionIndex;
|
||||||
|
var measurementUnits = selectedGlossary?.grInPortion == null
|
||||||
|
? Portion.pcs
|
||||||
|
: selectedGlossary?.grInPortion == 0
|
||||||
|
? Portion.grams
|
||||||
|
: Portion.pcs;
|
||||||
|
final glossary = selectedGlossary;
|
||||||
|
if (glossary != null) {
|
||||||
|
final [calories, carbohydrates, proteins, fats] = _getCPFC(glossary, measurementUnits);
|
||||||
|
final portion = DailySchedule(
|
||||||
|
dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates),
|
||||||
|
quantity: int.parse(quantityController.text),
|
||||||
|
name: glossary.name,
|
||||||
|
productNutritionalValueId: glossary.id.toString(),
|
||||||
|
portion: measurementUnits,
|
||||||
|
);
|
||||||
|
if (portionIndex == null) {
|
||||||
|
widget.createRecipeCubit.addIngredient(
|
||||||
|
Ingredient(
|
||||||
|
name: portion.name,
|
||||||
|
unit: portion.portion.getValue(),
|
||||||
|
portion: portion.quantity.toString(),
|
||||||
|
productId: glossary.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
} else {}
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
final newGlossary = await showModalBottomSheet<GlossaryModel>(
|
||||||
|
isScrollControlled: true,
|
||||||
|
useRootNavigator: true,
|
||||||
|
context: context,
|
||||||
|
builder: (context) => Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||||
|
),
|
||||||
|
child: GlossaryModerationRequestModalWidget(
|
||||||
|
productName: productNameController.text,
|
||||||
|
diaryCubit: sl<DiaryCubit>(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (newGlossary != null) {
|
||||||
|
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),
|
||||||
|
name: newGlossary.name,
|
||||||
|
productNutritionalValueId: newGlossary.id.toString(),
|
||||||
|
portion: measurementUnits,
|
||||||
|
);
|
||||||
|
if (portionIndex == null) {
|
||||||
|
widget.createRecipeCubit.addIngredient(
|
||||||
|
Ingredient(
|
||||||
|
name: portion.name,
|
||||||
|
unit: portion.portion.getValue(),
|
||||||
|
portion: portion.quantity.toString(),
|
||||||
|
productId: newGlossary.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
widget.createRecipeCubit.updateIngredient(
|
||||||
|
index: portionIndex,
|
||||||
|
ingredient: Ingredient(
|
||||||
|
name: portion.name,
|
||||||
|
unit: portion.portion.getValue(),
|
||||||
|
portion: portion.quantity.toString(),
|
||||||
|
productId: newGlossary.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> _getCPFC(GlossaryModel glossary, Portion measurementUnits) {
|
||||||
|
final double calories;
|
||||||
|
final double carbohydrates;
|
||||||
|
final double proteins;
|
||||||
|
final double fats;
|
||||||
|
if (measurementUnits == Portion.grams) {
|
||||||
|
calories = (glossary.energyOn100Gr * int.parse(quantityController.text) / 100);
|
||||||
|
carbohydrates = (glossary.carbohydratesOn100Gr * int.parse(quantityController.text) / 100);
|
||||||
|
proteins = (glossary.proteinsOn100Gr * int.parse(quantityController.text) / 100);
|
||||||
|
fats = (glossary.fatsOn100Gr * int.parse(quantityController.text) / 100);
|
||||||
|
} else {
|
||||||
|
calories = (glossary.energyOnPortion * int.parse(quantityController.text));
|
||||||
|
carbohydrates = (glossary.carbohydratesOnPortion * int.parse(quantityController.text));
|
||||||
|
proteins = (glossary.proteinsOnPortion * int.parse(quantityController.text));
|
||||||
|
fats = (glossary.fatsOnPortion * int.parse(quantityController.text));
|
||||||
|
}
|
||||||
|
return [calories, carbohydrates, proteins, fats];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
|
||||||
import 'package:eatvio_app/features/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_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/bloc/create_recipe_cubit/create_recipe_state.dart';
|
||||||
|
import 'package:eatvio_app/features/create_recipe/presentation/widgets/add_portion_widget.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
@@ -87,116 +86,8 @@ class _IngredientsWidgetState extends State<IngredientsWidget> {
|
|||||||
nameController.clear();
|
nameController.clear();
|
||||||
gramsController.clear();
|
gramsController.clear();
|
||||||
pcsController.clear();
|
pcsController.clear();
|
||||||
showModalBottomSheet(
|
_modalBottomSheet(
|
||||||
useRootNavigator: true,
|
createRecipeCubit: createRecipeCubit,
|
||||||
context: context,
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
|
|
||||||
),
|
|
||||||
isScrollControlled: true,
|
|
||||||
builder: (context) {
|
|
||||||
return StatefulBuilder(
|
|
||||||
builder: (BuildContext context, void Function(void Function()) setStateInner) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(16) +
|
|
||||||
EdgeInsets.only(
|
|
||||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Добавление ингредиента',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Color(0xFF323F4B),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width - 32,
|
|
||||||
child: CustomTextFormField(
|
|
||||||
autofocus: true,
|
|
||||||
controller: nameController,
|
|
||||||
hintText: 'Введите название продукта',
|
|
||||||
onChanged: (query) {},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width / 2 - 20,
|
|
||||||
child: CustomTextFormField(
|
|
||||||
enabled: pcsController.text.isEmpty ? true : false,
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
controller: gramsController,
|
|
||||||
hintText: pcsController.text.isEmpty ? '100' : null,
|
|
||||||
suffixText: 'гр',
|
|
||||||
onChanged: (value) {
|
|
||||||
setStateInner(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width / 2 - 20,
|
|
||||||
child: CustomTextFormField(
|
|
||||||
enabled: gramsController.text.isEmpty ? true : false,
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
controller: pcsController,
|
|
||||||
onChanged: (value) {
|
|
||||||
setStateInner(() {});
|
|
||||||
},
|
|
||||||
hintText: gramsController.text.isEmpty ? '1' : null,
|
|
||||||
suffixText: 'шт',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
if (nameController.text.isNotEmpty && (pcsController.text.isNotEmpty || gramsController.text.isNotEmpty)) {
|
|
||||||
createRecipeCubit.addIngredient(
|
|
||||||
Ingredient(
|
|
||||||
name: nameController.text,
|
|
||||||
unit: gramsController.text.isEmpty ? 'pcs' : 'gr',
|
|
||||||
portion: gramsController.text.isEmpty ? pcsController.text : gramsController.text,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
child: Ink(
|
|
||||||
width: MediaQuery.of(context).size.width - 32,
|
|
||||||
height: 52,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
color: appTheme.blueA400,
|
|
||||||
),
|
|
||||||
child: const Center(
|
|
||||||
child: Text(
|
|
||||||
'Добавить',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
@@ -220,4 +111,28 @@ class _IngredientsWidgetState extends State<IngredientsWidget> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_modalBottomSheet({
|
||||||
|
required CreateRecipeCubit createRecipeCubit,
|
||||||
|
int? portionIndex,
|
||||||
|
String? productNameControllerText,
|
||||||
|
String? quantityControllerText,
|
||||||
|
}) {
|
||||||
|
return showModalBottomSheet(
|
||||||
|
useRootNavigator: true,
|
||||||
|
context: context,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
|
||||||
|
),
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) {
|
||||||
|
return AddPortionWidget(
|
||||||
|
createRecipeCubit: createRecipeCubit,
|
||||||
|
portionIndex: portionIndex,
|
||||||
|
productNameControllerText: productNameControllerText,
|
||||||
|
quantityControllerText: quantityControllerText,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,12 @@ enum Portion {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getValue() => switch (this) {
|
||||||
|
Portion.grams => 'gr',
|
||||||
|
Portion.milliliters => 'milliliters',
|
||||||
|
Portion.pcs => 'pcs',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:ffi';
|
||||||
|
|
||||||
import 'package:eatvio_app/core/models/pagination_model.dart';
|
import 'package:eatvio_app/core/models/pagination_model.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||||
import 'package:eatvio_app/features/my_finance/models/card_model.dart';
|
import 'package:eatvio_app/features/my_finance/models/card_model.dart';
|
||||||
@@ -63,7 +65,7 @@ PlanType planTypeFromJson(dynamic json) {
|
|||||||
@freezed
|
@freezed
|
||||||
class PacketOptions with _$PacketOptions {
|
class PacketOptions with _$PacketOptions {
|
||||||
const factory PacketOptions({
|
const factory PacketOptions({
|
||||||
@JsonKey(name: "items") required List<String> items,
|
@JsonKey(name: "items", fromJson: _parseItems) required List<String> items,
|
||||||
@JsonKey(name: "price") required dynamic price,
|
@JsonKey(name: "price") required dynamic price,
|
||||||
@JsonKey(name: "short") required String short,
|
@JsonKey(name: "short") required String short,
|
||||||
@JsonKey(name: "description_md") required String description,
|
@JsonKey(name: "description_md") required String description,
|
||||||
@@ -71,7 +73,14 @@ class PacketOptions with _$PacketOptions {
|
|||||||
|
|
||||||
factory PacketOptions.fromJson(Map<String, dynamic> json) => _$PacketOptionsFromJson(json);
|
factory PacketOptions.fromJson(Map<String, dynamic> json) => _$PacketOptionsFromJson(json);
|
||||||
|
|
||||||
factory PacketOptions.empty() => const PacketOptions(items: [], price: 1200, short: '', description: '');
|
factory PacketOptions.empty() => PacketOptions(items: [], price: double.parse('1200'), short: '', description: '');
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _parseItems(json) {
|
||||||
|
if (json is Map<String, dynamic>) {
|
||||||
|
return json.values.map((e) => e as String).toList();
|
||||||
|
}
|
||||||
|
return (json as List<dynamic>).map((e) => e as String).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
|||||||
@@ -35,19 +35,21 @@ class IngredientsWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Builder(
|
Expanded(
|
||||||
builder: (context) {
|
child: Builder(
|
||||||
final portion = Portion.getPortionFromValue(recipe.ingredients![index].unit ?? '');
|
builder: (context) {
|
||||||
|
final portion = Portion.getPortionFromValue(recipe.ingredients![index].unit ?? '');
|
||||||
|
|
||||||
String? portionName;
|
String? portionName;
|
||||||
if (portion != null) {
|
if (portion != null) {
|
||||||
portionName = getLocalizedMeasurementUnits(s, portion);
|
portionName = getLocalizedMeasurementUnits(s, portion);
|
||||||
}
|
}
|
||||||
return Text(
|
return Text(
|
||||||
'${recipe.ingredients![index].name} - ${recipe.ingredients![index].portion} $portionName',
|
'${recipe.ingredients![index].name} - ${recipe.ingredients![index].portion} $portionName',
|
||||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user