diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart index 1aa7c0da..9e509b03 100644 --- a/lib/features/diary/presentation/bloc/diary_cubit.dart +++ b/lib/features/diary/presentation/bloc/diary_cubit.dart @@ -164,4 +164,15 @@ class DiaryCubit extends Cubit { carbohydrates: carbohydrates, ); } + + fillTime({ + required int diaryId, + required String time, + }) async { + return await diaryRepository.fillTime( + diaryId: diaryId, + mealId: selectedMeal, + time: time, + ); + } } diff --git a/lib/features/diary/presentation/widgets/add_product_button_widget.dart b/lib/features/diary/presentation/widgets/add_product_button_widget.dart index 38ae5ab3..2e01eeed 100644 --- a/lib/features/diary/presentation/widgets/add_product_button_widget.dart +++ b/lib/features/diary/presentation/widgets/add_product_button_widget.dart @@ -1,5 +1,9 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/add_product_modal.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class AddProductButtonWidget extends StatefulWidget { const AddProductButtonWidget({super.key}); @@ -9,31 +13,61 @@ class AddProductButtonWidget extends StatefulWidget { } class _AddProductButtonWidgetState extends State { + late final DiaryCubit diaryCubit; + @override - Widget build(BuildContext context) { - return InkWell( - onTap: () {}, // todo open modal for product add - borderRadius: BorderRadius.circular(20), - child: Ink( - padding: EdgeInsets.symmetric(horizontal: 16), - width: MediaQuery.of(context).size.width - 32, - decoration: BoxDecoration( - color: appTheme.blueA400, - borderRadius: BorderRadius.circular(20), - ), - child: Container( - padding: EdgeInsets.all(16), - alignment: Alignment.center, - child: Text( - 'Добавить', - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), - ), + void initState() { + super.initState(); + diaryCubit = context.read(); + } + + addProductInMeal(DiaryStateLoaded state, [int? portionId]) { + return showModalBottomSheet( + useRootNavigator: true, + context: context, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(12)), + ), + isScrollControlled: true, + builder: (context) => AddProductModal( + state: state, + diaryCubit: diaryCubit, ), ); } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is DiaryStateLoaded) { + return InkWell( + onTap: () => addProductInMeal(state), + borderRadius: BorderRadius.circular(20), + child: Ink( + padding: EdgeInsets.symmetric(horizontal: 16), + width: MediaQuery.of(context).size.width - 32, + decoration: BoxDecoration( + color: appTheme.blueA400, + borderRadius: BorderRadius.circular(20), + ), + child: Container( + padding: EdgeInsets.all(16), + alignment: Alignment.center, + child: Text( + 'Добавить', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ); + } + return const SizedBox.shrink(); + }, + ); + } } diff --git a/lib/features/diary/presentation/widgets/add_product_in_meal_modal_widget.dart b/lib/features/diary/presentation/widgets/add_product_in_meal_modal_widget.dart deleted file mode 100644 index 17a609c8..00000000 --- a/lib/features/diary/presentation/widgets/add_product_in_meal_modal_widget.dart +++ /dev/null @@ -1,220 +0,0 @@ -// import 'package:flutter/material.dart'; -// -// class AddProductInMealWidget extends StatefulWidget { -// const AddProductInMealWidget({super.key}); -// -// @override -// State createState() => _AddProductInMealWidgetState(); -// } -// -// class _AddProductInMealWidgetState extends State { -// addProductInMeal(DiaryDayModel diaryDay, [int? portionId]) { -// return showModalBottomSheet( -// useRootNavigator: true, -// context: context, -// shape: RoundedRectangleBorder( -// borderRadius: BorderRadius.vertical(top: Radius.circular(12)), -// ), -// isScrollControlled: true, -// builder: (context) { -// return StatefulBuilder( -// builder: (BuildContext context, void Function(void Function()) setState) { -// return Padding( -// padding: const EdgeInsets.all(16) + -// EdgeInsets.only( -// bottom: MediaQuery.of(context).viewInsets.bottom, -// ), -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// mainAxisSize: MainAxisSize.min, -// children: [ -// 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: TypeAheadField( -// controller: productNameController, -// itemBuilder: (context, glossary) { -// return Padding( -// padding: const EdgeInsets.all(8.0), -// child: Text( -// glossary.name, -// style: TextStyle( -// height: 1, -// fontSize: 16, -// ), -// ), -// ); -// }, -// 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 diaryCubit.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), -// 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 -// ? 'гр' -// : 'шт', -// ), -// ), -// ], -// ), -// const SizedBox(height: 16), -// Row( -// children: [ -// if (portionId != null) -// InkWell( -// onTap: () { -// diaryCubit.deleteProductFromMeal( -// diaryId: diaryDay.id, -// mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, -// portionId: portionId, -// date: _selectedDay.toString(), -// ); -// Navigator.of(context).pop(); -// }, -// borderRadius: BorderRadius.circular(20), -// child: Ink( -// width: 52, -// height: 52, -// decoration: BoxDecoration( -// borderRadius: BorderRadius.circular(20), -// color: Color(0xFFF6F6F6), -// ), -// child: Icon( -// Icons.delete_outline_rounded, -// color: Colors.red, -// ), -// ), -// ), -// if (portionId != null) const SizedBox(width: 8), -// InkWell( -// onTap: () { -// if (portionId == null) { -// diaryCubit.addProductToMeal( -// diaryId: diaryDay.id, -// mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, -// productName: productNameController.text, -// portion: selectedGlossary == null -// ? 'gr' -// : selectedGlossary!.grInPortion == 0 -// ? 'gr' -// : 'pcs', -// quantity: int.parse(quantityController.text), -// date: _selectedDay.toString(), -// ); -// } else { -// diaryCubit.updateProductInMeal( -// diaryId: diaryDay.id, -// mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, -// portionId: portionId, -// productName: productNameController.text, -// portion: selectedGlossary == null -// ? 'gr' -// : selectedGlossary!.grInPortion == 0 -// ? 'gr' -// : 'pcs', -// quantity: int.parse(quantityController.text), -// date: _selectedDay.toString(), -// ); -// } -// 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: diaryCubit, -// ), -// ), -// ); -// } -// }, -// borderRadius: BorderRadius.circular(20), -// child: Ink( -// width: MediaQuery.of(context).size.width - (portionId == null ? 32 : 92), -// height: 52, -// decoration: BoxDecoration( -// borderRadius: BorderRadius.circular(20), -// color: appTheme.blueA400, -// ), -// child: Center( -// child: Text( -// portionId == null ? 'Добавить' : 'Сохранить', -// style: TextStyle( -// fontSize: 18, -// fontWeight: FontWeight.w600, -// color: Colors.white, -// ), -// ), -// ), -// ), -// ), -// ], -// ), -// ], -// ), -// ); -// }, -// ); -// }, -// ); -// } -// -// @override -// Widget build(BuildContext context) { -// return const Placeholder(); -// } -// } diff --git a/lib/features/diary/presentation/widgets/add_product_modal.dart b/lib/features/diary/presentation/widgets/add_product_modal.dart new file mode 100644 index 00000000..3cc84551 --- /dev/null +++ b/lib/features/diary/presentation/widgets/add_product_modal.dart @@ -0,0 +1,234 @@ +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/diary/models/diary_day_model.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart'; +import 'package:eatvio_app/features/glossary/models/glossary_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_typeahead/flutter_typeahead.dart'; + +class AddProductModal extends StatefulWidget { + final Portion? portion; + final DiaryStateLoaded state; + final DiaryCubit diaryCubit; + + const AddProductModal({ + super.key, + this.portion, + required this.state, + required this.diaryCubit, + }); + + @override + State createState() => _AddProductModalState(); +} + +class _AddProductModalState extends State { + final productNameController = TextEditingController(); + final quantityController = TextEditingController(); + GlossaryModel? selectedGlossary; + + @override + void initState() { + super.initState(); + if (widget.portion != null) { + productNameController.text = widget.portion!.productName; + quantityController.text = '${widget.portion!.quantity}'; + } + } + + @override + Widget build(BuildContext context) { + return StatefulBuilder( + builder: (BuildContext context, void Function(void Function()) setState) { + return Padding( + padding: const EdgeInsets.all(16) + + EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + 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: TypeAheadField( + controller: productNameController, + itemBuilder: (context, glossary) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + glossary.name, + style: TextStyle( + height: 1, + fontSize: 16, + ), + ), + ); + }, + itemSeparatorBuilder: (context, index) => CustomDivider(), + onSelected: (newSelectedGlossary) async { + setState(() { + selectedGlossary = newSelectedGlossary; + productNameController.text = selectedGlossary!.name; + }); + }, + suggestionsCallback: (query) async { + if (query.length < 1) return []; + final suggestionsList = await widget.diaryCubit.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), + 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 + ? 'гр' + : 'шт', + ), + ), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + if (widget.portion != null) + InkWell( + onTap: () { + widget.diaryCubit.deleteProductFromMeal( + diaryId: widget.state.diary.id, + mealId: widget.state.selectedMeal, + portionId: widget.portion!.id, + ); + Navigator.of(context).pop(); + }, + borderRadius: BorderRadius.circular(20), + child: Ink( + width: 52, + height: 52, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Color(0xFFF6F6F6), + ), + child: Icon( + Icons.delete_outline_rounded, + color: Colors.red, + ), + ), + ), + if (widget.portion != null) const SizedBox(width: 8), + InkWell( + onTap: () { + if (widget.portion == null) { + widget.diaryCubit.addProductToMeal( + diaryId: widget.state.diary.id, + mealId: widget.state.selectedMeal, + productName: productNameController.text, + portion: selectedGlossary == null + ? 'gr' + : selectedGlossary!.grInPortion == 0 + ? 'gr' + : 'pcs', + quantity: int.parse(quantityController.text), + ); + } else { + widget.diaryCubit.updateProductInMeal( + diaryId: widget.state.diary.id, + mealId: widget.state.selectedMeal, + portionId: widget.portion!.id, + productName: productNameController.text, + portion: selectedGlossary == null + ? 'gr' + : selectedGlossary!.grInPortion == 0 + ? 'gr' + : 'pcs', + quantity: int.parse(quantityController.text), + ); + } + 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: widget.diaryCubit, + ), + ), + ); + } + }, + borderRadius: BorderRadius.circular(20), + child: Ink( + width: MediaQuery.of(context).size.width - (widget.portion == null ? 32 : 92), + height: 52, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: appTheme.blueA400, + ), + child: Center( + child: Text( + widget.portion == null ? 'Добавить' : 'Сохранить', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ), + ), + ], + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/diary/presentation/widgets/meal_widget.dart b/lib/features/diary/presentation/widgets/meal_widget.dart index 3b2acdc4..1314410d 100644 --- a/lib/features/diary/presentation/widgets/meal_widget.dart +++ b/lib/features/diary/presentation/widgets/meal_widget.dart @@ -2,6 +2,7 @@ import 'package:eatvio_app/features/diary/presentation/widgets/add_product_butto import 'package:eatvio_app/features/diary/presentation/widgets/meal_button_widget.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/photo_gallery_widget.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/products_list_widget.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/time_of_meal_picker_widget.dart'; import 'package:flutter/material.dart'; class MealWidget extends StatefulWidget { @@ -42,10 +43,12 @@ class _MealWidgetState extends State { ], ), const SizedBox(height: 12), - // TimeOfMealPickerWidget(), // TODO endpoint for save time of meal + TimeOfMealPickerWidget(), + const SizedBox(height: 12), ProductsListWidget(), const SizedBox(height: 12), AddProductButtonWidget(), + const SizedBox(height: 12), PhotoGalleryWidget(), ], ), diff --git a/lib/features/diary/presentation/widgets/photo_gallery_widget.dart b/lib/features/diary/presentation/widgets/photo_gallery_widget.dart index a56b6695..3c0521aa 100644 --- a/lib/features/diary/presentation/widgets/photo_gallery_widget.dart +++ b/lib/features/diary/presentation/widgets/photo_gallery_widget.dart @@ -20,8 +20,15 @@ class PhotoGalleryWidget extends StatefulWidget { } class _PhotoGalleryWidgetState extends State { + late final DiaryCubit diaryCubit; int currentSelectedPhotoIndex = 0; + @override + void initState() { + super.initState(); + diaryCubit = context.read(); + } + showImagePicker(DiaryDayModel diaryDay) { showCupertinoModalPopup( context: context, @@ -33,7 +40,7 @@ class _PhotoGalleryWidgetState extends State { final picker = ImagePicker(); XFile? image = await picker.pickImage(source: ImageSource.camera); if (image != null) - context.read().uploadPhotos( + diaryCubit.uploadPhotos( files: [File(image.path)], diaryId: diaryDay.id, ); @@ -52,10 +59,10 @@ class _PhotoGalleryWidgetState extends State { final picker = ImagePicker(); List? images = await picker.pickMultiImage(); if (images.isNotEmpty) - context.read().uploadPhotos( - files: images.map((f) => File(f.path)).toList(), - diaryId: diaryDay.id, - ); + diaryCubit.uploadPhotos( + files: images.map((f) => File(f.path)).toList(), + diaryId: diaryDay.id, + ); }, child: const Row( children: const [ @@ -80,7 +87,8 @@ class _PhotoGalleryWidgetState extends State { return BlocBuilder( builder: (context, state) { if (state is DiaryStateLoaded) { - if (state.diary.meals[state.selectedMeal].photos.isEmpty) { + int mealIndex = state.selectedMeal - 1; + if (state.diary.meals[mealIndex].photos.isEmpty) { return GestureDetector( onTap: () => showImagePicker(state.diary), child: SvgPicture.asset( @@ -99,7 +107,7 @@ class _PhotoGalleryWidgetState extends State { ClipRRect( borderRadius: BorderRadius.circular(12), child: CachedNetworkImage( - imageUrl: baseUrl + '/storage/' + state.diary.meals[state.selectedMeal].photos[currentSelectedPhotoIndex].path, + imageUrl: baseUrl + '/storage/' + state.diary.meals[mealIndex].photos[currentSelectedPhotoIndex].path, ), ), Padding( @@ -110,13 +118,13 @@ class _PhotoGalleryWidgetState extends State { elevation: 0, ), onPressed: () async { - await context.read().deletePhoto( - photo: state.diary.meals[state.selectedMeal].photos[currentSelectedPhotoIndex], + await diaryCubit.deletePhoto( + photo: state.diary.meals[mealIndex].photos[currentSelectedPhotoIndex], diaryId: state.diary.id, ); setState(() { - if (currentSelectedPhotoIndex == state.diary.meals[state.selectedMeal].photos.length - 1 && currentSelectedPhotoIndex != 0) { - currentSelectedPhotoIndex = state.diary.meals[state.selectedMeal].photos.length - 2; + if (currentSelectedPhotoIndex == state.diary.meals[mealIndex].photos.length - 1 && currentSelectedPhotoIndex != 0) { + currentSelectedPhotoIndex = state.diary.meals[mealIndex].photos.length - 2; } }); }, @@ -136,8 +144,8 @@ class _PhotoGalleryWidgetState extends State { mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, children: [ - for (int i = 0; i < state.diary.meals[state.selectedMeal].photos.length + 1; i++) ...[ - if (i == state.diary.meals[state.selectedMeal].photos.length) + for (int i = 0; i < state.diary.meals[mealIndex].photos.length + 1; i++) ...[ + if (i == state.diary.meals[mealIndex].photos.length) Padding( padding: const EdgeInsets.only(left: 8), child: InkWell( @@ -157,7 +165,7 @@ class _PhotoGalleryWidgetState extends State { ), ), ), - if (i < state.diary.meals[state.selectedMeal].photos.length) + if (i < state.diary.meals[mealIndex].photos.length) GestureDetector( onTap: () { setState(() { @@ -170,13 +178,13 @@ class _PhotoGalleryWidgetState extends State { height: 52, width: 52, child: CachedNetworkImage( - imageUrl: baseUrl + '/storage/' + state.diary.meals[state.selectedMeal].photos[i].path, + imageUrl: baseUrl + '/storage/' + state.diary.meals[mealIndex].photos[i].path, fit: BoxFit.fitWidth, ), ), ), ), - if (i < state.diary.meals[state.selectedMeal].photos.length - 1) const SizedBox(width: 8), + if (i < state.diary.meals[mealIndex].photos.length - 1) const SizedBox(width: 8), ], ], ), diff --git a/lib/features/diary/presentation/widgets/product_widget.dart b/lib/features/diary/presentation/widgets/product_widget.dart index e097391c..c7b8fc0a 100644 --- a/lib/features/diary/presentation/widgets/product_widget.dart +++ b/lib/features/diary/presentation/widgets/product_widget.dart @@ -1,13 +1,15 @@ +import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/add_product_modal.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class ProductWidget extends StatefulWidget { - final String product, portion; - final int quantity; + final Portion portion; const ProductWidget({ super.key, - required this.product, - required this.quantity, required this.portion, }); @@ -16,17 +18,48 @@ class ProductWidget extends StatefulWidget { } class _ProductWidgetState extends State { + late final DiaryCubit diaryCubit; + + @override + void initState() { + super.initState(); + diaryCubit = context.read(); + } + + updateProductInMeal(DiaryStateLoaded state, Portion portion) { + return showModalBottomSheet( + useRootNavigator: true, + context: context, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(12)), + ), + isScrollControlled: true, + builder: (context) => AddProductModal( + state: state, + portion: portion, + diaryCubit: diaryCubit, + ), + ); + } + @override Widget build(BuildContext context) { - return ListTile( - onTap: () {}, // todo change product in meal - trailing: Icon(Icons.more_vert_rounded), - title: Text(widget.product), - subtitle: Text('${widget.quantity} ${widget.portion == 'gr' ? 'гр' : 'шт'}'), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - tileColor: Color(0xFFF6FAFF), + return BlocBuilder( + builder: (context, state) { + if (state is DiaryStateLoaded) { + return ListTile( + onTap: () => updateProductInMeal(state, widget.portion), + trailing: Icon(Icons.more_vert_rounded), + title: Text(widget.portion.productName), + subtitle: Text('${widget.portion.quantity} ${widget.portion == 'gr' ? 'гр' : 'шт'}'), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + tileColor: Color(0xFFF6FAFF), + ); + } + return const SizedBox.shrink(); + }, ); } } diff --git a/lib/features/diary/presentation/widgets/products_list_widget.dart b/lib/features/diary/presentation/widgets/products_list_widget.dart index ad8c0ab7..b1775f53 100644 --- a/lib/features/diary/presentation/widgets/products_list_widget.dart +++ b/lib/features/diary/presentation/widgets/products_list_widget.dart @@ -17,16 +17,13 @@ class _ProductsListWidgetState extends State { return BlocBuilder( builder: (BuildContext context, DiaryState state) { if (state is DiaryStateLoaded) { + int mealIndex = state.selectedMeal - 1; return Column( mainAxisSize: MainAxisSize.min, children: [ - for (int i = 0; i < state.diary.meals[state.selectedMeal].portions.length; i++) ...[ - ProductWidget( - product: state.diary.meals[state.selectedMeal].portions[i].productName, - quantity: state.diary.meals[state.selectedMeal].portions[i].quantity, - portion: state.diary.meals[state.selectedMeal].portions[i].portion, - ), - i < state.diary.meals[state.selectedMeal].portions.length - 1 ? const SizedBox(height: 8) : const SizedBox.shrink(), + for (int i = 0; i < state.diary.meals[mealIndex].portions.length; i++) ...[ + ProductWidget(portion: state.diary.meals[mealIndex].portions[i]), + i < state.diary.meals[mealIndex].portions.length - 1 ? const SizedBox(height: 8) : const SizedBox.shrink(), ], ], ); diff --git a/lib/features/diary/presentation/widgets/time_of_meal_picker_widget.dart b/lib/features/diary/presentation/widgets/time_of_meal_picker_widget.dart index 7a1ffe24..0fec0688 100644 --- a/lib/features/diary/presentation/widgets/time_of_meal_picker_widget.dart +++ b/lib/features/diary/presentation/widgets/time_of_meal_picker_widget.dart @@ -1,7 +1,10 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/widgets/custom_divider.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class TimeOfMealPickerWidget extends StatefulWidget { const TimeOfMealPickerWidget({super.key}); @@ -11,10 +14,17 @@ class TimeOfMealPickerWidget extends StatefulWidget { } class _TimeOfMealPickerWidgetState extends State { + late final DiaryCubit diaryCubit; String time = "${DateTime.now().hour.toString().padLeft(2, '0')}:${DateTime.now().minute.toString().padLeft(2, '0')}"; DateTime _time = DateTime.now(); - showTimePicker() { + @override + void initState() { + super.initState(); + diaryCubit = context.read(); + } + + showTimePicker(int diaryId) { showModalBottomSheet( context: context, useRootNavigator: true, @@ -38,6 +48,10 @@ class _TimeOfMealPickerWidgetState extends State { InkWell( onTap: () { Navigator.of(context).pop(); + diaryCubit.fillTime( + diaryId: diaryId, + time: '$time:00', + ); }, borderRadius: BorderRadius.circular(20), child: Ink( @@ -67,50 +81,65 @@ class _TimeOfMealPickerWidgetState extends State { @override Widget build(BuildContext context) { - return Column( - children: [ - CustomDivider(), - Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + return BlocConsumer( + listener: (BuildContext context, DiaryState state) { + if (state is DiaryStateLoaded) { + setState(() { + time = state.diary.meals[state.selectedMeal - 1].time; + _time = DateTime.parse('0000-00-00 ${state.diary.meals[state.selectedMeal - 1].time}'); + }); + } + }, + builder: (context, state) { + if (state is DiaryStateLoaded) { + return Column( children: [ - Text( - 'Время приема пищи', - style: TextStyle( - color: Color(0xFF323F4B), - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - InkWell( - onTap: () => showTimePicker(), - borderRadius: BorderRadius.circular(100), - child: Ink( - height: 32, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(100), - color: Color(0xFFF1F2F6), - ), - child: Container( - alignment: Alignment.center, - padding: EdgeInsets.symmetric(horizontal: 16), - child: Text( - time, + CustomDivider(), + Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Время приема пищи', style: TextStyle( - color: Color(0xFF6D6F71), - fontSize: 17, + color: Color(0xFF323F4B), + fontSize: 16, fontWeight: FontWeight.w500, ), ), - ), + InkWell( + onTap: () => showTimePicker(state.diary.id), + borderRadius: BorderRadius.circular(100), + child: Ink( + height: 32, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: Color(0xFFF1F2F6), + ), + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.symmetric(horizontal: 16), + child: Text( + time, + style: TextStyle( + color: Color(0xFF6D6F71), + fontSize: 17, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ], ), ), + CustomDivider(), ], - ), - ), - CustomDivider(), - ], + ); + } + return const SizedBox.shrink(); + }, ); } } diff --git a/lib/features/diary/repositories/diary_repository.dart b/lib/features/diary/repositories/diary_repository.dart index 5af684c2..dd4af116 100644 --- a/lib/features/diary/repositories/diary_repository.dart +++ b/lib/features/diary/repositories/diary_repository.dart @@ -21,7 +21,7 @@ class DiaryRepository { } Future getNutritionData(int index) async { - return await diaryService.getNutrition(index: index); + return await diaryService.getNutrition(diary: index); } Future addPortionToMeal({ @@ -32,8 +32,8 @@ class DiaryRepository { required int quantity, }) async { return await diaryService.addProductToMeal( - diaryId: diaryId, - mealId: mealId, + diary: diaryId, + typeNo: mealId, productName: productName, portion: portion, quantity: quantity, @@ -49,9 +49,9 @@ class DiaryRepository { required int quantity, }) async { return await diaryService.updateProductInMeal( - diaryId: diaryId, - mealId: mealId, - portionId: portionId, + diary: diaryId, + typeNo: mealId, + portionModel: portionId, productInMeal: ProductInMeal( productName: productName, portion: portion, @@ -66,9 +66,9 @@ class DiaryRepository { required int portionId, }) async { return await diaryService.deleteProductFromMeal( - diaryId: diaryId, - mealId: mealId, - portionId: portionId, + diary: diaryId, + typeNo: mealId, + portionModel: portionId, ); } @@ -80,8 +80,8 @@ class DiaryRepository { try { for (int i = 0; i < files.length; i++) { await diaryService.uploadPhotoOfMeal( - diaryId: diaryId, - mealId: mealId, + diary: diaryId, + typeNo: mealId, photo: files[i], ); } @@ -98,9 +98,8 @@ class DiaryRepository { try { await diaryService.deletePhotoOfMeal( diaryId: diaryId, - mealId: mealId, - id: photo.id, - path: photo.path, + typeNo: mealId, + userAttachment: photo.id, ); } catch (e) { rethrow; @@ -114,7 +113,7 @@ class DiaryRepository { required String comment, }) async { return await diaryService.finishDiaryDay( - diaryId: diaryId, + diary: diaryId, finishDiaryDayModel: FinishDiaryDayModel( wakeUp: wakeUp, retire: retire, @@ -143,11 +142,31 @@ class DiaryRepository { Future fillSatiety({ required int diaryId, - required int meal, + required int mealId, required int satiety, }) async { try { - return await diaryService.fillSatiety(diaryId: diaryId, meal: meal, satiety: satiety); + return await diaryService.fillSatiety( + diary: diaryId, + typeNo: mealId, + satiety: satiety, + ); + } catch (e) { + rethrow; + } + } + + Future fillTime({ + required int diaryId, + required int mealId, + required String time, + }) async { + try { + return await diaryService.fillTime( + diary: diaryId, + typeNo: mealId, + time: time, + ); } catch (e) { rethrow; } diff --git a/lib/features/diary/services/diary_service.dart b/lib/features/diary/services/diary_service.dart index ee22dccc..ee477983 100644 --- a/lib/features/diary/services/diary_service.dart +++ b/lib/features/diary/services/diary_service.dart @@ -16,69 +16,75 @@ abstract class DiaryService { @Path('date') required String date, }); - @GET('/api/diary/{diary_id}/show') + @GET('/api/diary/{diary}/show') Future getDiaryDay({ - @Path('diary_id') required int index, + @Path('diary') required int diary, }); - @GET('/api/diary/{diary_id}/dailyNutritionData') + @GET('/api/diary/{diary}/dailyNutritionData') Future getNutrition({ - @Path('diary_id') required int index, + @Path('diary') required int diary, }); - @POST('/api/diary/{diary_id}/meal/{meal_id}/photo/upload') + @POST('/api/diary/{diary}/meals/{type_no}/photo/upload') @MultiPart() Future uploadPhotoOfMeal({ - @Path('diary_id') required int diaryId, - @Path('meal_id') required int mealId, + @Path('diary') required int diary, + @Path('type_no') required int typeNo, @Part(name: 'photo') required File photo, }); - @DELETE('/api/diary/{diary_id}/meal/{meal_id}/photo/delete') + @DELETE('/api/diary/{diary}/meals/{type_no}/photo/{user_attachment}/delete') Future deletePhotoOfMeal({ - @Path('diary_id') required int diaryId, - @Path('meal_id') required int mealId, - @Query('id') required int id, - @Query('path') required String path, + @Path('diary') required int diaryId, + @Path('type_no') required int typeNo, + @Path('user_attachment') required int userAttachment, }); - @POST('/api/diary/{diary_id}/meal/{meal_id}/portions/create') + @POST('/api/diary/{diary}/meals/{type_no}/portion/create') Future addProductToMeal({ - @Path('diary_id') required int diaryId, - @Path('meal_id') required int mealId, + @Path('diary') required int diary, + @Path('type_no') required int typeNo, @Part(name: 'product_name') required String productName, @Part(name: 'portion') required String portion, @Part(name: 'quantity') required int quantity, }); - @PUT('/api/diary/{diary_id}/meal/{meal_id}/portions/{portion_id}/update') + @PUT('/api/diary/{diary}/meals/{type_no}/portions/{portionModel}/update') Future updateProductInMeal({ - @Path('diary_id') required int diaryId, - @Path('meal_id') required int mealId, - @Path('portion_id') required int portionId, + @Path('diary') required int diary, + @Path('type_no') required int typeNo, + @Path('portionModel') required int portionModel, @Body() required ProductInMeal productInMeal, }); - @DELETE('/api/diary/{diary_id}/meal/{meal_id}/portions/{portion_id}/delete') + @DELETE('/api/diary/{diary}/meals/{type_no}/portions/{portionModel}/delete') Future deleteProductFromMeal({ - @Path('diary_id') required int diaryId, - @Path('meal_id') required int mealId, - @Path('portion_id') required int portionId, + @Path('diary') required int diary, + @Path('type_no') required int typeNo, + @Path('portionModel') required int portionModel, }); - @POST('/api/diary/{diary_id}/finishDay') + @POST('/api/diary/{diary}/finishDay') Future finishDiaryDay({ - @Path('diary_id') required int diaryId, + @Path('diary') required int diary, @Body() required FinishDiaryDayModel finishDiaryDayModel, }); - @POST('/api/diary/{diary_id}/meal/{meal}/satiety/fill') + @POST('/api/diary/{diary}/meals/{type_no}/satiety/fill') Future fillSatiety({ - @Path('diary_id') required int diaryId, - @Path('meal') required int meal, + @Path('diary') required int diary, + @Path('type_no') required int typeNo, @Field() required int satiety, }); + @POST('/api/diary/{diary}/meals/{type_no}/time/fill') + Future fillTime({ + @Path('diary') required int diary, + @Path('type_no') required int typeNo, + @Field() required String time, + }); + @POST('/api/diary/product_nutritional_values/sendToModeration') Future sendProductToModeration({ @Body() required Map data, diff --git a/lib/features/diary/services/diary_service.g.dart b/lib/features/diary/services/diary_service.g.dart index e7bde5a4..2223c8d7 100644 --- a/lib/features/diary/services/diary_service.g.dart +++ b/lib/features/diary/services/diary_service.g.dart @@ -58,7 +58,7 @@ class _DiaryService implements DiaryService { } @override - Future getDiaryDay({required int index}) async { + Future getDiaryDay({required int diary}) async { final _extra = {}; final queryParameters = {}; final _headers = {}; @@ -70,7 +70,7 @@ class _DiaryService implements DiaryService { ) .compose( _dio.options, - '/api/diary/${index}/show', + '/api/diary/${diary}/show', queryParameters: queryParameters, data: _data, ) @@ -91,7 +91,7 @@ class _DiaryService implements DiaryService { } @override - Future getNutrition({required int index}) async { + Future getNutrition({required int diary}) async { final _extra = {}; final queryParameters = {}; final _headers = {}; @@ -103,7 +103,7 @@ class _DiaryService implements DiaryService { ) .compose( _dio.options, - '/api/diary/${index}/dailyNutritionData', + '/api/diary/${diary}/dailyNutritionData', queryParameters: queryParameters, data: _data, ) @@ -125,8 +125,8 @@ class _DiaryService implements DiaryService { @override Future uploadPhotoOfMeal({ - required int diaryId, - required int mealId, + required int diary, + required int typeNo, required File photo, }) async { final _extra = {}; @@ -148,7 +148,7 @@ class _DiaryService implements DiaryService { ) .compose( _dio.options, - '/api/diary/${diaryId}/meal/${mealId}/photo/upload', + '/api/diary/${diary}/meals/${typeNo}/photo/upload', queryParameters: queryParameters, data: _data, ) @@ -163,15 +163,11 @@ class _DiaryService implements DiaryService { @override Future deletePhotoOfMeal({ required int diaryId, - required int mealId, - required int id, - required String path, + required int typeNo, + required int userAttachment, }) async { final _extra = {}; - final queryParameters = { - r'id': id, - r'path': path, - }; + final queryParameters = {}; final _headers = {}; const Map? _data = null; final _options = _setStreamType(Options( @@ -181,7 +177,7 @@ class _DiaryService implements DiaryService { ) .compose( _dio.options, - '/api/diary/${diaryId}/meal/${mealId}/photo/delete', + '/api/diary/${diaryId}/meals/${typeNo}/photo/${userAttachment}/delete', queryParameters: queryParameters, data: _data, ) @@ -195,8 +191,8 @@ class _DiaryService implements DiaryService { @override Future addProductToMeal({ - required int diaryId, - required int mealId, + required int diary, + required int typeNo, required String productName, required String portion, required int quantity, @@ -224,7 +220,7 @@ class _DiaryService implements DiaryService { ) .compose( _dio.options, - '/api/diary/${diaryId}/meal/${mealId}/portions/create', + '/api/diary/${diary}/meals/${typeNo}/portion/create', queryParameters: queryParameters, data: _data, ) @@ -238,9 +234,9 @@ class _DiaryService implements DiaryService { @override Future updateProductInMeal({ - required int diaryId, - required int mealId, - required int portionId, + required int diary, + required int typeNo, + required int portionModel, required ProductInMeal productInMeal, }) async { final _extra = {}; @@ -254,7 +250,7 @@ class _DiaryService implements DiaryService { ) .compose( _dio.options, - '/api/diary/${diaryId}/meal/${mealId}/portions/${portionId}/update', + '/api/diary/${diary}/meals/${typeNo}/portions/${portionModel}/update', queryParameters: queryParameters, data: _data, ) @@ -268,9 +264,9 @@ class _DiaryService implements DiaryService { @override Future deleteProductFromMeal({ - required int diaryId, - required int mealId, - required int portionId, + required int diary, + required int typeNo, + required int portionModel, }) async { final _extra = {}; final queryParameters = {}; @@ -283,7 +279,7 @@ class _DiaryService implements DiaryService { ) .compose( _dio.options, - '/api/diary/${diaryId}/meal/${mealId}/portions/${portionId}/delete', + '/api/diary/${diary}/meals/${typeNo}/portions/${portionModel}/delete', queryParameters: queryParameters, data: _data, ) @@ -297,7 +293,7 @@ class _DiaryService implements DiaryService { @override Future finishDiaryDay({ - required int diaryId, + required int diary, required FinishDiaryDayModel finishDiaryDayModel, }) async { final _extra = {}; @@ -311,7 +307,7 @@ class _DiaryService implements DiaryService { ) .compose( _dio.options, - '/api/diary/${diaryId}/finishDay', + '/api/diary/${diary}/finishDay', queryParameters: queryParameters, data: _data, ) @@ -325,8 +321,8 @@ class _DiaryService implements DiaryService { @override Future fillSatiety({ - required int diaryId, - required int meal, + required int diary, + required int typeNo, required int satiety, }) async { final _extra = {}; @@ -340,7 +336,36 @@ class _DiaryService implements DiaryService { ) .compose( _dio.options, - '/api/diary/${diaryId}/meal/${meal}/satiety/fill', + '/api/diary/${diary}/meals/${typeNo}/satiety/fill', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + await _dio.fetch(_options); + } + + @override + Future fillTime({ + required int diary, + required int typeNo, + required String time, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = {'time': time}; + final _options = _setStreamType(Options( + method: 'POST', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/diary/${diary}/meals/${typeNo}/time/fill', queryParameters: queryParameters, data: _data, ) diff --git a/lib/main.dart b/lib/main.dart index 509614fd..d32df6a1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -17,8 +17,7 @@ void main() async { WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); await di.init(); - await Future.delayed(Duration(milliseconds: 250)); - FlutterNativeSplash.remove(); + await Future.delayed(Duration(seconds: 1), () => FlutterNativeSplash.remove()); runApp(MyApp()); } diff --git a/pubspec.yaml b/pubspec.yaml index d2509a5c..5c121e67 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: eatvio_app description: A new Flutter project. -version: 1.0.7+22 +version: 1.0.8+23 publish_to: "none" environment: