plan creation screen added

This commit is contained in:
Username1548
2024-12-02 15:55:57 +07:00
parent a1d9e13514
commit 1688ac1dc8
55 changed files with 3506 additions and 350 deletions

View File

@@ -1,6 +1,5 @@
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
@@ -34,6 +33,14 @@ class _AddMealWidgetState extends State<AddMealWidget> {
GlossaryModel? selectedGlossary;
final productNameController = TextEditingController();
final quantityController = TextEditingController();
@override
void initState() {
super.initState();
productNameController.text = widget.productNameControllerText ?? '';
quantityController.text = widget.quantityControllerText ?? '';
}
@override
Widget build(BuildContext context) {
return Padding(
@@ -128,6 +135,11 @@ class _AddMealWidgetState extends State<AddMealWidget> {
if (widget.portionIndex != null)
InkWell(
onTap: () {
widget.createDietCubit.removePortion(
day: widget.dayIndex,
meal: widget.mealIndex,
portionIndex: widget.portionIndex!,
);
Navigator.of(context).pop();
},
borderRadius: BorderRadius.circular(20),
@@ -146,55 +158,7 @@ class _AddMealWidgetState extends State<AddMealWidget> {
),
if (widget.portionIndex != null) const SizedBox(width: 8),
InkWell(
onTap: () {
if (widget.portionIndex == null) {
// final glossary = selectedGlossary;
// if (glossary != null) {
// final measurementUnits = glossary.grInPortion != 0 ? Portion.PCS : Portion.GRAMS ;
// final portion = DailySchedule(
// quantity: int.parse(quantityController.text),
// name: glossary.name,
// uniq: glossary.id.toString(),
// portion: measurementUnits,
// );
// widget.createDietCubit.addPortion(
// dayIndex: widget.dayIndex,
// mealIndex: widget.mealIndex,
// portion: portion,
// );
// }
} else {
// final portion = DailySchedule(
// quantity: int.parse(quantityController.text),
// name: glossary.name,
// uniq: glossary.id.toString(),
// portion: measurementUnits,
// );
// widget.createDietCubit.updatePortion(
// day: widget.dayIndex,
// meal: widget.mealIndex,
// portionIndex: widget.portionIndex!,
// portion: portion,
// );
}
Navigator.of(context).pop();
if (selectedGlossary == null) {
showModalBottomSheet(
isScrollControlled: true,
useRootNavigator: true,
context: context,
builder: (context) => Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: GlossaryModerationRequestModalWidget(
productName: productNameController.text,
diaryCubit: sl<DiaryCubit>(),
),
),
);
}
},
onTap: addPortion,
borderRadius: BorderRadius.circular(20),
child: Ink(
width: MediaQuery.of(context).size.width - (widget.portionIndex == null ? 32 : 92),
@@ -222,6 +186,101 @@ class _AddMealWidgetState extends State<AddMealWidget> {
);
}
Future<void> addPortion() async {
final portionIndex = widget.portionIndex;
final measurementUnits = selectedGlossary?.grInPortion == null
? Portion.PCS
: selectedGlossary?.grInPortion == 0
? Portion.GRAMS
: Portion.PCS;
final glossary = selectedGlossary;
if (glossary != null) {
final [calories, carbohydrates, proteins, fats] = _getCPFC(glossary, measurementUnits);
final portion = DailySchedule(
dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates),
quantity: int.parse(quantityController.text),
name: glossary.name,
uniq: '',
productNutritionalValueId: glossary.id.toString(),
portion: measurementUnits,
);
if (portionIndex == null) {
widget.createDietCubit.addPortion(
dayIndex: widget.dayIndex,
mealIndex: widget.mealIndex,
portion: portion,
);
} else {
widget.createDietCubit.updatePortion(
dayIndex: widget.dayIndex,
mealIndex: widget.mealIndex,
portionIndex: portionIndex,
portion: portion,
);
}
} else {
Navigator.of(context).pop();
final newGlossary = await showModalBottomSheet<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) {
final [calories, carbohydrates, proteins, fats] = _getCPFC(newGlossary, Portion.GRAMS);
final portion = DailySchedule(
dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates),
quantity: int.parse(quantityController.text),
name: newGlossary.name,
uniq: '',
productNutritionalValueId: newGlossary.id.toString(),
portion: measurementUnits,
);
if (portionIndex == null) {
widget.createDietCubit.addPortion(
dayIndex: widget.dayIndex,
mealIndex: widget.mealIndex,
portion: portion,
);
} else {
widget.createDietCubit.updatePortion(
dayIndex: widget.dayIndex,
mealIndex: widget.mealIndex,
portion: portion,
portionIndex: portionIndex,
);
}
}
}
}
List<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];
}
_divider(BuildContext context) {
return Container(
height: 0.4,

View File

@@ -1,9 +1,10 @@
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/core/widgets/cpfc_container.dart';
import 'package:eatvio_app/core/widgets/custom_divider.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart';
@@ -13,10 +14,9 @@ import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
class DayWidget extends StatefulWidget {
const DayWidget({
DayWidget({
super.key,
required this.index,
});
@@ -34,175 +34,250 @@ class _DayWidgetState extends State<DayWidget> {
return BlocBuilder<CreateDietCubit, CreateDietState>(
builder: (context, state) {
return MainBackgroundBouble(
child: Padding(
padding: EdgeInsets.only(
left: 16,
right: 16,
top: 16,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 16, right: 16, top: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
s.day + ' ${widget.index + 1}',
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
Spacer(),
GestureDetector(
onTap: () {
context.read<CreateDietCubit>().copyDay(widget.index);
showInfoToast('${s.day} ${widget.index + 1} ${s.copied}');
},
child: SvgPicture.asset(
'assets/icons/copy2.svg',
width: 20,
height: 24,
colorFilter: ColorFilter.mode(appTheme.black, BlendMode.srcIn),
),
),
],
),
SizedBox(
height: 12,
),
CustomDivider(),
SizedBox(
height: 12,
),
Text(
s.day + ' ${widget.index + 1}',
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
s.meal,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
Spacer(),
SvgPicture.asset(
'assets/icons/copy2.svg',
width: 20,
height: 24,
colorFilter: ColorFilter.mode(appTheme.black, BlendMode.srcIn),
),
],
),
SizedBox(
height: 12,
),
CustomDivider(),
SizedBox(
height: 12,
),
Text(
s.meal,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
SizedBox(height: 16),
Row(
children: [
ChoiceChipWidget(
chipsLabelList: chipsLabelList,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
fontSize: TextStyles.textMMedium.fontSize,
fontWeight: TextStyles.textMMedium.fontWeight,
index: 0,
selectedChipIndex: _selectedMealIndex,
onSelected: (selectedValue) {
setState(() {
_selectedMealIndex = 0;
});
},
),
Spacer(),
ChoiceChipWidget(
chipsLabelList: chipsLabelList,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
fontSize: TextStyles.textMMedium.fontSize,
fontWeight: TextStyles.textMMedium.fontWeight,
index: 1,
selectedChipIndex: _selectedMealIndex,
onSelected: (selectedValue) {
setState(() {
_selectedMealIndex = 1;
});
},
),
Spacer(),
ChoiceChipWidget(
chipsLabelList: chipsLabelList,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
fontSize: TextStyles.textMMedium.fontSize,
fontWeight: TextStyles.textMMedium.fontWeight,
index: 2,
selectedChipIndex: _selectedMealIndex,
onSelected: (selectedValue) {
setState(() {
_selectedMealIndex = 2;
});
},
),
Spacer(),
ChoiceChipWidget(
chipsLabelList: chipsLabelList,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
fontSize: TextStyles.textMMedium.fontSize,
fontWeight: TextStyles.textMMedium.fontWeight,
index: 3,
selectedChipIndex: _selectedMealIndex,
onSelected: (selectedValue) {
setState(() {
_selectedMealIndex = 3;
});
},
),
Spacer(),
ChoiceChipWidget(
chipsLabelList: chipsLabelList,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
fontSize: TextStyles.textMMedium.fontSize,
fontWeight: TextStyles.textMMedium.fontWeight,
index: 4,
selectedChipIndex: _selectedMealIndex,
onSelected: (selectedValue) {
setState(() {
_selectedMealIndex = 4;
});
},
),
],
),
ListView.builder(
itemBuilder: (context, index) {
final product = state.dietData.dailySchedule?[widget.index][_selectedMealIndex][index];
if (product != null) {
return _product(
index,
product.name,
product.quantity,
getLocalizedMeasurementUnits(
s,
product.portion,
SizedBox(height: 16),
Row(
children: [
ChoiceChipWidget(
chipsLabelList: chipsLabelList,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
fontSize: TextStyles.textMMedium.fontSize,
fontWeight: TextStyles.textMMedium.fontWeight,
index: 0,
selectedChipIndex: _selectedMealIndex,
onSelected: (selectedValue) {
setState(() {
_selectedMealIndex = 0;
});
},
),
widget.index,
_selectedMealIndex,
);
}
return null;
},
itemCount: state.dietData.dailySchedule?[widget.index][_selectedMealIndex].length ?? 0,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
),
SizedBox(height: 16),
Row(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
decoration: BoxDecoration(color: appTheme.gray50, borderRadius: BorderRadius.all(Radius.circular(12))),
child: SvgPicture.asset(
'assets/icons/paste2.svg',
width: 28,
height: 24,
),
Spacer(),
ChoiceChipWidget(
chipsLabelList: chipsLabelList,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
fontSize: TextStyles.textMMedium.fontSize,
fontWeight: TextStyles.textMMedium.fontWeight,
index: 1,
selectedChipIndex: _selectedMealIndex,
onSelected: (selectedValue) {
setState(() {
_selectedMealIndex = 1;
});
},
),
Spacer(),
ChoiceChipWidget(
chipsLabelList: chipsLabelList,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
fontSize: TextStyles.textMMedium.fontSize,
fontWeight: TextStyles.textMMedium.fontWeight,
index: 2,
selectedChipIndex: _selectedMealIndex,
onSelected: (selectedValue) {
setState(() {
_selectedMealIndex = 2;
});
},
),
Spacer(),
ChoiceChipWidget(
chipsLabelList: chipsLabelList,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
fontSize: TextStyles.textMMedium.fontSize,
fontWeight: TextStyles.textMMedium.fontWeight,
index: 3,
selectedChipIndex: _selectedMealIndex,
onSelected: (selectedValue) {
setState(() {
_selectedMealIndex = 3;
});
},
),
Spacer(),
ChoiceChipWidget(
chipsLabelList: chipsLabelList,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
fontSize: TextStyles.textMMedium.fontSize,
fontWeight: TextStyles.textMMedium.fontWeight,
index: 4,
selectedChipIndex: _selectedMealIndex,
onSelected: (selectedValue) {
setState(() {
_selectedMealIndex = 4;
});
},
),
],
),
SizedBox(width: 8),
Expanded(
child: CustomOutlinedButton(
onPressed: () {
_modalBottomSheet(
dayIndex: widget.index,
mealIndex: _selectedMealIndex,
createDietCubit: context.read<CreateDietCubit>(),
ListView.separated(
separatorBuilder: (context, index) => SizedBox(
height: 8,
),
padding: EdgeInsets.all(0),
itemBuilder: (context, index) {
final product = state.dietData.dailySchedule?[widget.index][_selectedMealIndex][index];
if (product != null) {
return _product(
index,
product.name,
product.quantity,
getLocalizedMeasurementUnits(
s,
product.portion,
),
widget.index,
_selectedMealIndex,
);
},
text: s.add,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(
color: appTheme.whiteA700,
),
buttonStyle: ButtonStyle(
side: WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
),
),
}
return null;
},
itemCount: state.dietData.dailySchedule?[widget.index][_selectedMealIndex].length ?? 0,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
),
SizedBox(height: 16),
Row(
children: [
GestureDetector(
onTap: () {
context.read<CreateDietCubit>().pasteCopiedDay(widget.index);
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
decoration: BoxDecoration(color: appTheme.gray50, borderRadius: BorderRadius.all(Radius.circular(12))),
child: SvgPicture.asset(
'assets/icons/paste2.svg',
width: 28,
height: 24,
),
),
),
SizedBox(width: 8),
Expanded(
child: CustomOutlinedButton(
onPressed: () {
_modalBottomSheet(
dayIndex: widget.index,
mealIndex: _selectedMealIndex,
createDietCubit: context.read<CreateDietCubit>(),
);
},
text: s.add,
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(
color: appTheme.whiteA700,
),
buttonStyle: ButtonStyle(
side: WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
),
),
),
],
),
SizedBox(height: 20),
],
),
SizedBox(height: 20),
],
),
),
BlocBuilder<CreateDietCubit, CreateDietState>(
buildWhen: (previous, current) => previous.dietData.dailySchedule != current.dietData.dailySchedule,
builder: (context, state) {
if (state.dietData.dailySchedule != null) {
final day = state.dietData.dailySchedule?[widget.index];
final double calories = day!.fold(
0,
(previousValue, element) =>
previousValue +
element.fold(
0,
(previousValue, element) => previousValue + (element.dailyNutrition?.energy ?? 0),
),
);
final double proteins = day.fold(
0,
(previousValue, element) =>
previousValue +
element.fold(
0,
(previousValue, element) => previousValue + (element.dailyNutrition?.proteins ?? 0),
),
);
final double fats = day.fold(
0,
(previousValue, element) =>
previousValue +
element.fold(
0,
(previousValue, element) => previousValue + (element.dailyNutrition?.fats ?? 0),
),
);
final double carbohydrates = day.fold(
0,
(previousValue, element) =>
previousValue +
element.fold(
0,
(previousValue, element) => previousValue + (element.dailyNutrition?.carbohydrates ?? 0),
),
);
return CpfcContainer(
calories: calories,
proteins: proteins,
fats: fats,
carbohydrates: carbohydrates,
);
}
return CpfcContainer(
calories: 0,
proteins: 0,
fats: 0,
carbohydrates: 0,
);
},
),
],
),
);
},
@@ -210,24 +285,26 @@ class _DayWidgetState extends State<DayWidget> {
}
_product(int index, String product, int quantity, String portion, int dayIndex, int mealIndex) {
return ListTile(
onTap: () {
_modalBottomSheet(
mealIndex: mealIndex,
dayIndex: dayIndex,
createDietCubit: context.read<CreateDietCubit>(),
portionIndex: index,
productNameControllerText: product,
quantityControllerText: quantity.toString(),
);
},
trailing: Icon(Icons.more_vert_rounded),
title: Text(product),
subtitle: Text('$quantity $portion'),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
return Material(
child: ListTile(
onTap: () {
_modalBottomSheet(
mealIndex: mealIndex,
dayIndex: dayIndex,
createDietCubit: context.read<CreateDietCubit>(),
portionIndex: index,
productNameControllerText: product,
quantityControllerText: quantity.toString(),
);
},
trailing: Icon(Icons.more_vert_rounded),
title: Text(product),
subtitle: Text('$quantity $portion'),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
tileColor: appTheme.gray50,
),
tileColor: Color(0xFFF6FAFF),
);
}

View File

@@ -10,14 +10,14 @@ import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class DurationDropDownWidget extends StatefulWidget {
const DurationDropDownWidget({super.key});
class DietDurationDropDownWidget extends StatefulWidget {
const DietDurationDropDownWidget({super.key});
@override
State<DurationDropDownWidget> createState() => _DurationDropDownWidgetState();
State<DietDurationDropDownWidget> createState() => _DietDurationDropDownWidgetState();
}
class _DurationDropDownWidgetState extends State<DurationDropDownWidget> {
class _DietDurationDropDownWidgetState extends State<DietDurationDropDownWidget> {
final List<String> durationDropDownTitles = [];
@override
@@ -38,9 +38,9 @@ class _DurationDropDownWidgetState extends State<DurationDropDownWidget> {
builder: (context, state) {
return CustomDropDownButton(
hint: Text(
s.choose_diet_duration,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
s.choose_diet_duration,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
onChanged: (value) {
if (value != null) {
context.read<CreateDietCubit>().setDuration(durationDropDownTitles.indexOf(value));

View File

@@ -12,14 +12,14 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:image_picker/image_picker.dart';
class ImagePickerWidget extends StatefulWidget {
const ImagePickerWidget({super.key});
class DietImagePickerWidget extends StatefulWidget {
const DietImagePickerWidget({super.key});
@override
State<ImagePickerWidget> createState() => _ImagePickerWidgetState();
State<DietImagePickerWidget> createState() => _DietImagePickerWidgetState();
}
class _ImagePickerWidgetState extends State<ImagePickerWidget> {
class _DietImagePickerWidgetState extends State<DietImagePickerWidget> {
void _showImagePickerOptions(S s, CreateDietCubit cubit) {
showCupertinoModalPopup<void>(
context: context,
@@ -84,7 +84,30 @@ class _ImagePickerWidgetState extends State<ImagePickerWidget> {
width: MediaQuery.sizeOf(context).width,
padding: state.dietData.image != null ? null : EdgeInsets.all(8),
child: state.dietData.image != null
? null
? Padding(
padding: const EdgeInsets.only(top: 6, right: 6),
child: GestureDetector(
onTap: () {
context.read<CreateDietCubit>().setImage(null);
},
child: Align(
alignment: Alignment.topRight,
child: Container(
width: 34,
height: 34,
decoration: BoxDecoration(
color: appTheme.dividerColor,
shape: BoxShape.circle,
),
child: Icon(
Icons.close_rounded,
color: Colors.grey,
size: 16,
),
),
),
),
)
: Stack(
children: [
DottedBorder(

View File

@@ -7,14 +7,14 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
class TypeChoiceWidget extends StatefulWidget {
TypeChoiceWidget({super.key});
class DietTypeChoiceWidget extends StatefulWidget {
DietTypeChoiceWidget({super.key});
@override
State<TypeChoiceWidget> createState() => _TypeChoiceWidgetState();
State<DietTypeChoiceWidget> createState() => _DietTypeChoiceWidgetState();
}
class _TypeChoiceWidgetState extends State<TypeChoiceWidget> {
class _DietTypeChoiceWidgetState extends State<DietTypeChoiceWidget> {
final List<(String, String)> choiceContent = [];
@override
void didChangeDependencies() {

View File

@@ -24,7 +24,6 @@ class _StatusDropDownWidgetState extends State<StatusDropDownWidget> {
final s = S.of(context);
if (dietStateTitles.isEmpty) {
dietStateTitles.add(s.active_diet_state);
dietStateTitles.add(s.inactive_diet_state);
dietStateTitles.add(s.personal_diet_state);
}
}