From 7e0ca28ede128b25375d3d3b6a62e467c1aa0b1a Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 30 Nov 2024 18:33:56 +0300 Subject: [PATCH] feat: diary add update --- .../diary/presentation/bloc/diary_cubit.dart | 4 + .../widgets/add_product_modal.dart | 109 +++++++++++------- .../presentation/widgets/product_widget.dart | 4 +- .../diary/repositories/diary_repository.dart | 15 ++- .../diary/services/diary_service.dart | 3 +- .../diary/services/diary_service.g.dart | 13 ++- 6 files changed, 99 insertions(+), 49 deletions(-) diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart index 9e509b03..115b8f50 100644 --- a/lib/features/diary/presentation/bloc/diary_cubit.dart +++ b/lib/features/diary/presentation/bloc/diary_cubit.dart @@ -78,6 +78,7 @@ class DiaryCubit extends Cubit { required String productName, required String portion, required int quantity, + int? productNutritionalValueId, }) async { await diaryRepository.addPortionToMeal( diaryId: diaryId, @@ -85,6 +86,7 @@ class DiaryCubit extends Cubit { productName: productName, portion: portion, quantity: quantity, + productNutritionalValueId: productNutritionalValueId, ); await fetchDiary(); } @@ -96,6 +98,7 @@ class DiaryCubit extends Cubit { required String productName, required String portion, required int quantity, + int? productNutritionalValueId, }) async { await diaryRepository.updateProductInMeal( diaryId: diaryId, @@ -104,6 +107,7 @@ class DiaryCubit extends Cubit { productName: productName, portion: portion, quantity: quantity, + productNutritionalValueId: productNutritionalValueId, ); await fetchDiary(); } diff --git a/lib/features/diary/presentation/widgets/add_product_modal.dart b/lib/features/diary/presentation/widgets/add_product_modal.dart index 3cc84551..1ff48abd 100644 --- a/lib/features/diary/presentation/widgets/add_product_modal.dart +++ b/lib/features/diary/presentation/widgets/add_product_modal.dart @@ -1,3 +1,4 @@ +import 'package:easy_debounce/easy_debounce.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'; @@ -7,7 +8,6 @@ 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; @@ -29,6 +29,7 @@ class _AddProductModalState extends State { final productNameController = TextEditingController(); final quantityController = TextEditingController(); GlossaryModel? selectedGlossary; + List glossariesList = []; @override void initState() { @@ -36,6 +37,11 @@ class _AddProductModalState extends State { if (widget.portion != null) { productNameController.text = widget.portion!.productName; quantityController.text = '${widget.portion!.quantity}'; + selectedGlossary = GlossaryModel( + id: widget.portion!.id, + name: widget.portion!.productName, + grInPortion: widget.portion!.portion == 'gr' ? 0 : 1, + ); } } @@ -66,47 +72,20 @@ class _AddProductModalState extends State { children: [ SizedBox( width: MediaQuery.of(context).size.width * 0.8 - 32, - child: TypeAheadField( + child: CustomTextFormField( controller: productNameController, - itemBuilder: (context, glossary) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - glossary.name, - style: TextStyle( - height: 1, - fontSize: 16, - ), - ), + autofocus: true, + hintText: 'Введите название продукта/блюда', + onChanged: (query) { + EasyDebounce.debounce( + 'client-typing', + Duration(milliseconds: 250), + () async { + glossariesList = await widget.diaryCubit.getGlossaryListByQuery(query); + setState(() {}); + }, ); }, - 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), @@ -129,7 +108,55 @@ class _AddProductModalState extends State { ), ], ), - const SizedBox(height: 16), + glossariesList.isEmpty ? const SizedBox(height: 16) : const SizedBox.shrink(), + Container( + constraints: BoxConstraints( + maxHeight: 256, + ), + child: ListView.separated( + physics: const BouncingScrollPhysics(), + shrinkWrap: true, + itemBuilder: (context, i) { + return ListTile( + onTap: () async { + setState(() { + selectedGlossary = glossariesList[i]; + productNameController.text = selectedGlossary!.name; + }); + glossariesList = await widget.diaryCubit.getGlossaryListByQuery(glossariesList[i].name); + setState(() {}); + }, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + title: Text( + glossariesList[i].name, + style: TextStyle( + height: 1, + ), + ), + subtitle: glossariesList[i].grInPortion == 0 + ? null + : Text( + '${glossariesList[i].grInPortion} г', + style: TextStyle( + height: 1, + color: Colors.grey.shade600, + ), + ), + trailing: Icon( + Icons.keyboard_arrow_right_rounded, + color: Colors.grey.shade400, + ), + contentPadding: EdgeInsets.only(left: 12), + ); + }, + itemCount: glossariesList.length, + separatorBuilder: (BuildContext context, int index) { + return CustomDivider(); + }, + ), + ), Row( children: [ if (widget.portion != null) @@ -170,6 +197,7 @@ class _AddProductModalState extends State { ? 'gr' : 'pcs', quantity: int.parse(quantityController.text), + productNutritionalValueId: selectedGlossary?.id, ); } else { widget.diaryCubit.updateProductInMeal( @@ -183,6 +211,7 @@ class _AddProductModalState extends State { ? 'gr' : 'pcs', quantity: int.parse(quantityController.text), + productNutritionalValueId: selectedGlossary?.id, ); } Navigator.of(context).pop(); diff --git a/lib/features/diary/presentation/widgets/product_widget.dart b/lib/features/diary/presentation/widgets/product_widget.dart index c7b8fc0a..05e0c2b6 100644 --- a/lib/features/diary/presentation/widgets/product_widget.dart +++ b/lib/features/diary/presentation/widgets/product_widget.dart @@ -19,11 +19,13 @@ class ProductWidget extends StatefulWidget { class _ProductWidgetState extends State { late final DiaryCubit diaryCubit; + late String portionName; @override void initState() { super.initState(); diaryCubit = context.read(); + portionName = widget.portion == 'gr' ? 'гр' : 'шт'; } updateProductInMeal(DiaryStateLoaded state, Portion portion) { @@ -51,7 +53,7 @@ class _ProductWidgetState extends State { onTap: () => updateProductInMeal(state, widget.portion), trailing: Icon(Icons.more_vert_rounded), title: Text(widget.portion.productName), - subtitle: Text('${widget.portion.quantity} ${widget.portion == 'gr' ? 'гр' : 'шт'}'), + subtitle: Text('${widget.portion.quantity} ${widget.portion.portion == 'gr' ? 'гр' : 'шт'}'), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), diff --git a/lib/features/diary/repositories/diary_repository.dart b/lib/features/diary/repositories/diary_repository.dart index dd4af116..6839722a 100644 --- a/lib/features/diary/repositories/diary_repository.dart +++ b/lib/features/diary/repositories/diary_repository.dart @@ -30,6 +30,7 @@ class DiaryRepository { required String productName, required String portion, required int quantity, + int? productNutritionalValueId, }) async { return await diaryService.addProductToMeal( diary: diaryId, @@ -37,6 +38,7 @@ class DiaryRepository { productName: productName, portion: portion, quantity: quantity, + productNutritionalValueId: productNutritionalValueId, ); } @@ -47,16 +49,19 @@ class DiaryRepository { required String productName, required String portion, required int quantity, + int? productNutritionalValueId, }) async { + Map data = ProductInMeal( + productName: productName, + portion: portion, + quantity: quantity, + ).toJson(); + data['product_nutritional_value_id'] = productNutritionalValueId; return await diaryService.updateProductInMeal( diary: diaryId, typeNo: mealId, portionModel: portionId, - productInMeal: ProductInMeal( - productName: productName, - portion: portion, - quantity: quantity, - ), + productInMeal: data, ); } diff --git a/lib/features/diary/services/diary_service.dart b/lib/features/diary/services/diary_service.dart index ee477983..e484ee08 100644 --- a/lib/features/diary/services/diary_service.dart +++ b/lib/features/diary/services/diary_service.dart @@ -48,6 +48,7 @@ abstract class DiaryService { @Part(name: 'product_name') required String productName, @Part(name: 'portion') required String portion, @Part(name: 'quantity') required int quantity, + @Part(name: 'product_nutritional_value_id') int? productNutritionalValueId, }); @PUT('/api/diary/{diary}/meals/{type_no}/portions/{portionModel}/update') @@ -55,7 +56,7 @@ abstract class DiaryService { @Path('diary') required int diary, @Path('type_no') required int typeNo, @Path('portionModel') required int portionModel, - @Body() required ProductInMeal productInMeal, + @Body() required Map productInMeal, }); @DELETE('/api/diary/{diary}/meals/{type_no}/portions/{portionModel}/delete') diff --git a/lib/features/diary/services/diary_service.g.dart b/lib/features/diary/services/diary_service.g.dart index 2223c8d7..19253e5c 100644 --- a/lib/features/diary/services/diary_service.g.dart +++ b/lib/features/diary/services/diary_service.g.dart @@ -196,9 +196,11 @@ class _DiaryService implements DiaryService { required String productName, required String portion, required int quantity, + int? productNutritionalValueId, }) async { final _extra = {}; final queryParameters = {}; + queryParameters.removeWhere((k, v) => v == null); final _headers = {}; final _data = FormData(); _data.fields.add(MapEntry( @@ -213,6 +215,12 @@ class _DiaryService implements DiaryService { 'quantity', quantity.toString(), )); + if (productNutritionalValueId != null) { + _data.fields.add(MapEntry( + 'product_nutritional_value_id', + productNutritionalValueId.toString(), + )); + } final _options = _setStreamType(Options( method: 'POST', headers: _headers, @@ -237,12 +245,13 @@ class _DiaryService implements DiaryService { required int diary, required int typeNo, required int portionModel, - required ProductInMeal productInMeal, + required Map productInMeal, }) async { final _extra = {}; final queryParameters = {}; final _headers = {}; - final _data = productInMeal; + final _data = {}; + _data.addAll(productInMeal); final _options = _setStreamType(Options( method: 'PUT', headers: _headers,