forked from eatvio/eatvio_app
Merge pull request 'main' (#1) from eatvio/eatvio_app:main into main
Reviewed-on: mso7nueh/eatvio_app#1
This commit is contained in:
@@ -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/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_state.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/widgets/add_portion_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@@ -87,116 +86,8 @@ class _IngredientsWidgetState extends State<IngredientsWidget> {
|
||||
nameController.clear();
|
||||
gramsController.clear();
|
||||
pcsController.clear();
|
||||
showModalBottomSheet(
|
||||
useRootNavigator: true,
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
_modalBottomSheet(
|
||||
createRecipeCubit: createRecipeCubit,
|
||||
);
|
||||
},
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user