From 0d19fde557eb7cdabe872595fa93aa0ef54b2bb2 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 27 Nov 2024 16:23:48 +0300 Subject: [PATCH] feat: diary refactor --- .../entities/nutrition_chart_data_entity.dart | 9 - .../diary/presentation/bloc/diary_cubit.dart | 72 +- .../diary/presentation/bloc/diary_state.dart | 3 +- .../bloc/diary_state.freezed.dart | 92 +- .../presentation/pages/diary_screen.dart | 808 +----------------- .../widgets/add_product_button_widget.dart | 39 + .../add_product_in_meal_modal_widget.dart | 220 +++++ .../presentation/widgets/calendar_widget.dart | 130 +++ .../widgets/calories_chart_widget.dart | 246 ++++++ .../widgets/calories_chat_widget.dart | 275 ------ .../widgets/finish_day_button_widget.dart | 100 +-- .../widgets/meal_button_widget.dart | 54 ++ .../presentation/widgets/meal_widget.dart | 53 ++ .../widgets/photo_gallery_widget.dart | 167 ++++ .../presentation/widgets/product_widget.dart | 32 + .../widgets/products_list_widget.dart | 38 + .../widgets/satiety_rating_widget.dart | 8 +- .../widgets/time_of_meal_picker_widget.dart | 116 +++ .../diary/repositories/diary_repository.dart | 11 +- .../diary/services/diary_service.dart | 2 + .../diary/services/diary_service.g.dart | 7 +- 21 files changed, 1253 insertions(+), 1229 deletions(-) delete mode 100644 lib/features/diary/entities/nutrition_chart_data_entity.dart create mode 100644 lib/features/diary/presentation/widgets/add_product_button_widget.dart create mode 100644 lib/features/diary/presentation/widgets/add_product_in_meal_modal_widget.dart create mode 100644 lib/features/diary/presentation/widgets/calendar_widget.dart create mode 100644 lib/features/diary/presentation/widgets/calories_chart_widget.dart delete mode 100644 lib/features/diary/presentation/widgets/calories_chat_widget.dart create mode 100644 lib/features/diary/presentation/widgets/meal_button_widget.dart create mode 100644 lib/features/diary/presentation/widgets/meal_widget.dart create mode 100644 lib/features/diary/presentation/widgets/photo_gallery_widget.dart create mode 100644 lib/features/diary/presentation/widgets/product_widget.dart create mode 100644 lib/features/diary/presentation/widgets/products_list_widget.dart create mode 100644 lib/features/diary/presentation/widgets/time_of_meal_picker_widget.dart diff --git a/lib/features/diary/entities/nutrition_chart_data_entity.dart b/lib/features/diary/entities/nutrition_chart_data_entity.dart deleted file mode 100644 index 9602d7d7..00000000 --- a/lib/features/diary/entities/nutrition_chart_data_entity.dart +++ /dev/null @@ -1,9 +0,0 @@ -class NutritionChartDataEntity { - final double intake; - final double recommendation; - - NutritionChartDataEntity( - this.intake, - this.recommendation, - ); -} diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart index 59158554..386755ad 100644 --- a/lib/features/diary/presentation/bloc/diary_cubit.dart +++ b/lib/features/diary/presentation/bloc/diary_cubit.dart @@ -10,27 +10,41 @@ import 'package:eatvio_app/features/glossary/repositories/glossary_repository.da import 'package:flutter_bloc/flutter_bloc.dart'; class DiaryCubit extends Cubit { + late final DateTime today; + late DateTime selectedDay; + late int selectedMeal; final DiaryRepository diaryRepository; final GlossaryRepository glossaryRepository; DiaryCubit({ required this.diaryRepository, required this.glossaryRepository, - }) : super(const DiaryState.loading()); + }) : super(const DiaryState.loading()) { + today = DateTime.now(); + selectedDay = today; + } - getDiaryDay(String date, [bool needLoading = true]) async { - if (needLoading) emit(const DiaryState.loading()); + setSelectedDay(DateTime day) async { + selectedDay = day; + await fetchDiary(); + } + + setSelectedMeal(int meal) { + selectedMeal = meal; + emit((state as DiaryStateLoaded).copyWith(selectedMeal: selectedMeal)); + } + + fetchDiary() async { + emit(const DiaryState.loading()); + selectedMeal = 1; try { - final diaryDay = await diaryRepository.getDiaryByDate(date); - final List satiety = []; - for (int i = 0; i < diaryDay.meals.length; i++) { - satiety.add(diaryDay.meals[i].satiety); - } + final diaryDay = await diaryRepository.getDiaryByDate('$selectedDay'); emit( DiaryState.loaded( diary: diaryDay, - nutrition: await getNutrition((await diaryRepository.getDiaryByDate(date)).id), - currentSelectedSatietyRating: satiety, + nutrition: await getNutrition((await diaryRepository.getDiaryByDate('$selectedDay')).id), + selectedDay: selectedDay, + selectedMeal: selectedMeal, ), ); } catch (e) { @@ -40,16 +54,18 @@ class DiaryCubit extends Cubit { } finishDiaryDay({ - required String date, required int diaryId, + required String wakeUp, + required String retire, + required String comment, }) async { await diaryRepository.finishDiaryDay( diaryId: diaryId, - wakeUp: '', - retire: '', - comment: '', + wakeUp: wakeUp, + retire: retire, + comment: comment, ); - await getDiaryDay(date, false); + await fetchDiary(); } Future getNutrition(int index) async { @@ -62,7 +78,6 @@ class DiaryCubit extends Cubit { required String productName, required String portion, required int quantity, - required String date, }) async { await diaryRepository.addPortionToMeal( diaryId: diaryId, @@ -71,7 +86,7 @@ class DiaryCubit extends Cubit { portion: portion, quantity: quantity, ); - await getDiaryDay(date, false); + await fetchDiary(); } updateProductInMeal({ @@ -81,7 +96,6 @@ class DiaryCubit extends Cubit { required String productName, required String portion, required int quantity, - required String date, }) async { await diaryRepository.updateProductInMeal( diaryId: diaryId, @@ -91,21 +105,20 @@ class DiaryCubit extends Cubit { portion: portion, quantity: quantity, ); - await getDiaryDay(date, false); + await fetchDiary(); } deleteProductFromMeal({ required int diaryId, required int mealId, required int portionId, - required String date, }) async { await diaryRepository.deleteProductFromMeal( diaryId: diaryId, mealId: mealId, portionId: portionId, ); - await getDiaryDay(date, false); + await fetchDiary(); } Future> getGlossaryListByQuery(String query) async { @@ -116,39 +129,26 @@ class DiaryCubit extends Cubit { required List files, required int diaryId, required int mealId, - required String date, }) async { await diaryRepository.uploadPhotos( files: await ImageCompressUtility.compressFilesList(files), diaryId: diaryId, mealId: mealId, ); - await getDiaryDay(date, false); + await fetchDiary(); } deletePhoto({ required DiaryPhotoModel photo, required int diaryId, required int mealId, - required String date, }) async { await diaryRepository.deletePhoto( photo: photo, diaryId: diaryId, mealId: mealId, ); - await getDiaryDay(date, false); - } - - updateCurrentSelectedSatietyRating({ - required int diaryId, - required int meal, - required int value, - }) { - List satiety = [...(state as DiaryStateLoaded).currentSelectedSatietyRating]; - satiety[meal] = value; - diaryRepository.fillSatiety(diaryId: diaryId, meal: meal, satiety: value.toInt() - 1); - emit((state as DiaryStateLoaded).copyWith(currentSelectedSatietyRating: satiety)); + await fetchDiary(); } sendProductToModeration({ diff --git a/lib/features/diary/presentation/bloc/diary_state.dart b/lib/features/diary/presentation/bloc/diary_state.dart index 47eeee2d..02138c7a 100644 --- a/lib/features/diary/presentation/bloc/diary_state.dart +++ b/lib/features/diary/presentation/bloc/diary_state.dart @@ -17,6 +17,7 @@ class DiaryState with _$DiaryState { const factory DiaryState.loaded({ required DiaryDayModel diary, required NutritionDataModel nutrition, - required List currentSelectedSatietyRating, + required DateTime selectedDay, + required int selectedMeal, }) = DiaryStateLoaded; } diff --git a/lib/features/diary/presentation/bloc/diary_state.freezed.dart b/lib/features/diary/presentation/bloc/diary_state.freezed.dart index 6e958949..176be0c9 100644 --- a/lib/features/diary/presentation/bloc/diary_state.freezed.dart +++ b/lib/features/diary/presentation/bloc/diary_state.freezed.dart @@ -22,7 +22,7 @@ mixin _$DiaryState { required TResult Function() loading, required TResult Function(String errorMessage) error, required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - List currentSelectedSatietyRating) + DateTime selectedDay, int selectedMeal) loaded, }) => throw _privateConstructorUsedError; @@ -32,7 +32,7 @@ mixin _$DiaryState { TResult? Function()? loading, TResult? Function(String errorMessage)? error, TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition, - List currentSelectedSatietyRating)? + DateTime selectedDay, int selectedMeal)? loaded, }) => throw _privateConstructorUsedError; @@ -42,7 +42,7 @@ mixin _$DiaryState { TResult Function()? loading, TResult Function(String errorMessage)? error, TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - List currentSelectedSatietyRating)? + DateTime selectedDay, int selectedMeal)? loaded, required TResult orElse(), }) => @@ -140,7 +140,7 @@ class _$DiaryStateImpl implements _DiaryState { required TResult Function() loading, required TResult Function(String errorMessage) error, required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - List currentSelectedSatietyRating) + DateTime selectedDay, int selectedMeal) loaded, }) { return $default(); @@ -153,7 +153,7 @@ class _$DiaryStateImpl implements _DiaryState { TResult? Function()? loading, TResult? Function(String errorMessage)? error, TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition, - List currentSelectedSatietyRating)? + DateTime selectedDay, int selectedMeal)? loaded, }) { return $default?.call(); @@ -166,7 +166,7 @@ class _$DiaryStateImpl implements _DiaryState { TResult Function()? loading, TResult Function(String errorMessage)? error, TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - List currentSelectedSatietyRating)? + DateTime selectedDay, int selectedMeal)? loaded, required TResult orElse(), }) { @@ -263,7 +263,7 @@ class _$DiaryStateLoadingImpl implements DiaryStateLoading { required TResult Function() loading, required TResult Function(String errorMessage) error, required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - List currentSelectedSatietyRating) + DateTime selectedDay, int selectedMeal) loaded, }) { return loading(); @@ -276,7 +276,7 @@ class _$DiaryStateLoadingImpl implements DiaryStateLoading { TResult? Function()? loading, TResult? Function(String errorMessage)? error, TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition, - List currentSelectedSatietyRating)? + DateTime selectedDay, int selectedMeal)? loaded, }) { return loading?.call(); @@ -289,7 +289,7 @@ class _$DiaryStateLoadingImpl implements DiaryStateLoading { TResult Function()? loading, TResult Function(String errorMessage)? error, TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - List currentSelectedSatietyRating)? + DateTime selectedDay, int selectedMeal)? loaded, required TResult orElse(), }) { @@ -415,7 +415,7 @@ class _$DiaryStateErrorImpl implements DiaryStateError { required TResult Function() loading, required TResult Function(String errorMessage) error, required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - List currentSelectedSatietyRating) + DateTime selectedDay, int selectedMeal) loaded, }) { return error(errorMessage); @@ -428,7 +428,7 @@ class _$DiaryStateErrorImpl implements DiaryStateError { TResult? Function()? loading, TResult? Function(String errorMessage)? error, TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition, - List currentSelectedSatietyRating)? + DateTime selectedDay, int selectedMeal)? loaded, }) { return error?.call(errorMessage); @@ -441,7 +441,7 @@ class _$DiaryStateErrorImpl implements DiaryStateError { TResult Function()? loading, TResult Function(String errorMessage)? error, TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - List currentSelectedSatietyRating)? + DateTime selectedDay, int selectedMeal)? loaded, required TResult orElse(), }) { @@ -511,7 +511,8 @@ abstract class _$$DiaryStateLoadedImplCopyWith<$Res> { $Res call( {DiaryDayModel diary, NutritionDataModel nutrition, - List currentSelectedSatietyRating}); + DateTime selectedDay, + int selectedMeal}); $DiaryDayModelCopyWith<$Res> get diary; $NutritionDataModelCopyWith<$Res> get nutrition; @@ -532,7 +533,8 @@ class __$$DiaryStateLoadedImplCopyWithImpl<$Res> $Res call({ Object? diary = null, Object? nutrition = null, - Object? currentSelectedSatietyRating = null, + Object? selectedDay = null, + Object? selectedMeal = null, }) { return _then(_$DiaryStateLoadedImpl( diary: null == diary @@ -543,10 +545,14 @@ class __$$DiaryStateLoadedImplCopyWithImpl<$Res> ? _value.nutrition : nutrition // ignore: cast_nullable_to_non_nullable as NutritionDataModel, - currentSelectedSatietyRating: null == currentSelectedSatietyRating - ? _value._currentSelectedSatietyRating - : currentSelectedSatietyRating // ignore: cast_nullable_to_non_nullable - as List, + selectedDay: null == selectedDay + ? _value.selectedDay + : selectedDay // ignore: cast_nullable_to_non_nullable + as DateTime, + selectedMeal: null == selectedMeal + ? _value.selectedMeal + : selectedMeal // ignore: cast_nullable_to_non_nullable + as int, )); } @@ -577,25 +583,21 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded { const _$DiaryStateLoadedImpl( {required this.diary, required this.nutrition, - required final List currentSelectedSatietyRating}) - : _currentSelectedSatietyRating = currentSelectedSatietyRating; + required this.selectedDay, + required this.selectedMeal}); @override final DiaryDayModel diary; @override final NutritionDataModel nutrition; - final List _currentSelectedSatietyRating; @override - List get currentSelectedSatietyRating { - if (_currentSelectedSatietyRating is EqualUnmodifiableListView) - return _currentSelectedSatietyRating; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_currentSelectedSatietyRating); - } + final DateTime selectedDay; + @override + final int selectedMeal; @override String toString() { - return 'DiaryState.loaded(diary: $diary, nutrition: $nutrition, currentSelectedSatietyRating: $currentSelectedSatietyRating)'; + return 'DiaryState.loaded(diary: $diary, nutrition: $nutrition, selectedDay: $selectedDay, selectedMeal: $selectedMeal)'; } @override @@ -606,14 +608,15 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded { (identical(other.diary, diary) || other.diary == diary) && (identical(other.nutrition, nutrition) || other.nutrition == nutrition) && - const DeepCollectionEquality().equals( - other._currentSelectedSatietyRating, - _currentSelectedSatietyRating)); + (identical(other.selectedDay, selectedDay) || + other.selectedDay == selectedDay) && + (identical(other.selectedMeal, selectedMeal) || + other.selectedMeal == selectedMeal)); } @override - int get hashCode => Object.hash(runtimeType, diary, nutrition, - const DeepCollectionEquality().hash(_currentSelectedSatietyRating)); + int get hashCode => + Object.hash(runtimeType, diary, nutrition, selectedDay, selectedMeal); /// Create a copy of DiaryState /// with the given fields replaced by the non-null parameter values. @@ -631,10 +634,10 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded { required TResult Function() loading, required TResult Function(String errorMessage) error, required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - List currentSelectedSatietyRating) + DateTime selectedDay, int selectedMeal) loaded, }) { - return loaded(diary, nutrition, currentSelectedSatietyRating); + return loaded(diary, nutrition, selectedDay, selectedMeal); } @override @@ -644,10 +647,10 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded { TResult? Function()? loading, TResult? Function(String errorMessage)? error, TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition, - List currentSelectedSatietyRating)? + DateTime selectedDay, int selectedMeal)? loaded, }) { - return loaded?.call(diary, nutrition, currentSelectedSatietyRating); + return loaded?.call(diary, nutrition, selectedDay, selectedMeal); } @override @@ -657,12 +660,12 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded { TResult Function()? loading, TResult Function(String errorMessage)? error, TResult Function(DiaryDayModel diary, NutritionDataModel nutrition, - List currentSelectedSatietyRating)? + DateTime selectedDay, int selectedMeal)? loaded, required TResult orElse(), }) { if (loaded != null) { - return loaded(diary, nutrition, currentSelectedSatietyRating); + return loaded(diary, nutrition, selectedDay, selectedMeal); } return orElse(); } @@ -707,14 +710,15 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded { abstract class DiaryStateLoaded implements DiaryState { const factory DiaryStateLoaded( - {required final DiaryDayModel diary, - required final NutritionDataModel nutrition, - required final List currentSelectedSatietyRating}) = - _$DiaryStateLoadedImpl; + {required final DiaryDayModel diary, + required final NutritionDataModel nutrition, + required final DateTime selectedDay, + required final int selectedMeal}) = _$DiaryStateLoadedImpl; DiaryDayModel get diary; NutritionDataModel get nutrition; - List get currentSelectedSatietyRating; + DateTime get selectedDay; + int get selectedMeal; /// Create a copy of DiaryState /// with the given fields replaced by the non-null parameter values. diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart index fc222600..7d0d09fa 100644 --- a/lib/features/diary/presentation/pages/diary_screen.dart +++ b/lib/features/diary/presentation/pages/diary_screen.dart @@ -1,25 +1,12 @@ -import 'dart:io'; -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/widgets/loader.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/calories_chat_widget.dart'; -import 'package:eatvio_app/features/diary/presentation/widgets/finish_day_button_widget.dart'; -import 'package:eatvio_app/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart'; -import 'package:eatvio_app/features/diary/presentation/widgets/satiety_rating_widget.dart'; -import 'package:eatvio_app/features/glossary/models/glossary_model.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/calendar_widget.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_text_form_field.dart'; -import 'package:flutter/cupertino.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/calories_chart_widget.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/meal_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:flutter_typeahead/flutter_typeahead.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:table_calendar/table_calendar.dart'; class DiaryScreen extends StatefulWidget { const DiaryScreen({super.key}); @@ -29,88 +16,11 @@ class DiaryScreen extends StatefulWidget { } class _DiaryScreenState extends State { - final DiaryCubit diaryCubit = sl(); - CalendarFormat _calendarFormat = CalendarFormat.month; - DateTime _focusedDay = DateTime.now(); - DateTime? _selectedDay = DateTime.now(); - late final DateTime? today; - int currentSelectedMealNumber = 1; - GlossaryModel? selectedGlossary; - final productNameController = TextEditingController(); - final quantityController = TextEditingController(); - int currentSelectedPhotoIndex = 0; - String time = "${DateTime.now().hour.toString().padLeft(2, '0')}:${DateTime.now().minute.toString().padLeft(2, '0')}"; - DateTime _time = DateTime.now(); - - @override - void initState() { - super.initState(); - today = _selectedDay; - } - - _showDatePicker() { - showModalBottomSheet( - context: context, - useRootNavigator: true, - builder: (context) => SizedBox( - height: MediaQuery.of(context).copyWith().size.height / 3, - child: Column( - children: [ - Expanded( - child: CupertinoDatePicker( - mode: CupertinoDatePickerMode.time, - use24hFormat: true, - initialDateTime: DateTime.now(), - onDateTimeChanged: (newTime) { - setState(() { - _time = newTime; - time = "${_time.hour.toString().padLeft(2, '0')}:${_time.minute.toString().padLeft(2, '0')}"; - }); - }, - ), - ), - InkWell( - onTap: () { - Navigator.of(context).pop(); - }, - borderRadius: BorderRadius.circular(20), - child: Ink( - width: MediaQuery.of(context).size.width - 16, - height: 52, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: appTheme.blueA400, - ), - child: Center( - child: Text( - 'Сохранить', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - ), - ), - ), - ], - ), - ), - ); - } - @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => diaryCubit..getDiaryDay(_selectedDay!.toString()), - child: BlocConsumer( - listener: (BuildContext context, DiaryState state) { - if (state is DiaryStateLoaded) { - setState(() { - _calendarFormat = CalendarFormat.week; - }); - } - }, + create: (BuildContext context) => sl()..fetchDiary(), + child: BlocBuilder( builder: (BuildContext context, DiaryState state) { return Scaffold( backgroundColor: Colors.grey.shade200, @@ -121,23 +31,10 @@ class _DiaryScreenState extends State { physics: const BouncingScrollPhysics(), child: Column( children: [ - _calendar(state), + CalendarWidget(), if (state is DiaryStateLoading) const Loader(), - if (state is DiaryStateLoaded) - _meals( - state.diary, - state.currentSelectedSatietyRating, - ), - if (state is DiaryStateLoaded && _selectedDay!.compareTo(today!) != 1) - CaloriesChatWidget( - nutrition: state.nutrition, - ), - if (state is DiaryStateLoaded && state.diary.closed == 0 && _selectedDay == today) - Padding( - padding: const EdgeInsets.only(top: 12), - child: FinishDayButtonWidget(diaryDay: state.diary), - ), - const SizedBox(height: 32), + if (state is DiaryStateLoaded) MealWidget(), + CaloriesChartWidget(), ], ), ), @@ -146,693 +43,4 @@ class _DiaryScreenState extends State { ), ); } - - _timeOfMeal(String time) { - return InkWell( - onTap: () => _showDatePicker(), - 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, - ), - ), - ), - ), - ); - } - - _nextCalendarFormat() { - setState(() { - _calendarFormat = CalendarFormat.values[(_calendarFormat.index - 1) % CalendarFormat.values.length]; - }); - } - - _arrowDown() { - return GestureDetector( - behavior: HitTestBehavior.translucent, - onVerticalDragStart: (_) { - _nextCalendarFormat(); - }, - onTap: () { - _nextCalendarFormat(); - }, - child: Container( - width: double.infinity, - padding: const EdgeInsets.only(top: 8, bottom: 16), - child: Icon( - _calendarFormat == CalendarFormat.month ? Icons.keyboard_arrow_up_rounded : Icons.keyboard_arrow_down_rounded, - color: Colors.grey.shade600, - ), - ), - ); - } - - _divider(BuildContext context) { - return Container( - height: 0.4, - width: MediaQuery.of(context).size.width - 32, - decoration: BoxDecoration( - color: Colors.black26, - borderRadius: BorderRadius.circular(16), - ), - ); - } - - _products(List portions, DiaryDayModel diaryDay) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - for (int i = 0; i < portions.length; i++) ...[ - _product( - diaryDay, - portions[i].id, - portions[i].productName, - portions[i].quantity, - portions[i].portion, - ), - i < portions.length - 1 ? const SizedBox(height: 8) : const SizedBox.shrink(), - ], - ], - ); - } - - _product(DiaryDayModel diaryDay, int id, String product, int quantity, String portion) { - portion == 'gr' ? portion = 'г' : portion = 'шт'; - return ListTile( - onTap: () { - setState(() { - productNameController.text = product; - quantityController.text = quantity.toString(); - }); - _modalBottomSheet(diaryDay, id); - }, - trailing: Icon(Icons.more_vert_rounded), - title: Text(product), - subtitle: Text('$quantity $portion'), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - tileColor: Color(0xFFF6FAFF), - ); - } - - _meals( - DiaryDayModel diaryDay, - List satietyRating, - ) { - return Material( - borderRadius: BorderRadius.circular(20), - color: Colors.white, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Прием пищи', - style: TextStyle( - color: Color(0xFF323F4B), - fontSize: 18, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _mealButton(1), - _mealButton(2), - _mealButton(3), - _mealButton(4), - _mealButton(5), - ], - ), - _selectedDay!.compareTo(today!) == 1 ? const SizedBox.shrink() : const SizedBox(height: 12), - _selectedDay!.compareTo(today!) == 1 ? const SizedBox.shrink() : _divider(context), - _selectedDay!.compareTo(today!) == 1 ? const SizedBox.shrink() : const SizedBox(height: 12), - _selectedDay!.compareTo(today!) == 1 - ? const SizedBox.shrink() - : Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Время приема пищи', - style: TextStyle( - color: Color(0xFF323F4B), - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - _timeOfMeal(time), - ], - ), - const SizedBox(height: 12), - _divider(context), - const SizedBox(height: 12), - _products(diaryDay.meals[currentSelectedMealNumber - 1].portions, diaryDay), - const SizedBox(height: 16), - _addProductButton(diaryDay), - const SizedBox(height: 24), - if (diaryDay.meals[currentSelectedMealNumber - 1].photos.isEmpty) - _selectedDay!.compareTo(today!) == 1 - ? const SizedBox.shrink() - : GestureDetector( - onTap: () => _showImagePicker(diaryDay), - child: SvgPicture.asset( - width: MediaQuery.of(context).size.width - 32, - 'assets/no_one_image_was_added.svg', - ), - ), - if (diaryDay.meals[currentSelectedMealNumber - 1].photos.isNotEmpty) - _photoGallery( - diaryDay.meals[currentSelectedMealNumber - 1].photos, - diaryDay, - ), - _selectedDay!.compareTo(today!) == 1 - ? const SizedBox.shrink() - : SatietyRatingWidget( - currentSelectedSatietyRating: satietyRating, - diaryId: diaryDay.id, - meal: currentSelectedMealNumber, - ), - ], - ), - ), - ); - } - - _showImagePicker(DiaryDayModel diaryDay) { - showCupertinoModalPopup( - context: context, - builder: (context) => CupertinoActionSheet( - actions: [ - CupertinoActionSheetAction( - onPressed: () async { - Navigator.of(context).pop(); - final picker = ImagePicker(); - XFile? image = await picker.pickImage(source: ImageSource.camera); - if (image != null) - diaryCubit.uploadPhotos( - files: [File(image.path)], - diaryId: diaryDay.id, - mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, - date: _selectedDay.toString(), - ); - }, - child: const Row( - children: const [ - Icon(Icons.photo_camera), - SizedBox(width: 8), - Text('Фото'), - ], - ), - ), - CupertinoActionSheetAction( - onPressed: () async { - Navigator.of(context).pop(); - final picker = ImagePicker(); - List? images = await picker.pickMultiImage(); - if (images.isNotEmpty) - diaryCubit.uploadPhotos( - files: images.map((f) => File(f.path)).toList(), - diaryId: diaryDay.id, - mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, - date: _selectedDay.toString(), - ); - }, - child: const Row( - children: const [ - Icon(Icons.photo), - SizedBox(width: 8), - Text('Галерея'), - ], - ), - ), - ], - cancelButton: CupertinoActionSheetAction( - onPressed: () => Navigator.of(context).pop(), - isDestructiveAction: true, - child: const Text('Отмена'), - ), - ), - ); - } - - _photoGallery(List photos, DiaryDayModel diaryDay) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Stack( - alignment: Alignment.topRight, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(12), - child: CachedNetworkImage( - imageUrl: baseUrl + '/storage/' + photos[currentSelectedPhotoIndex].path, - ), - ), - Padding( - padding: const EdgeInsets.all(12), - child: IconButton.filled( - style: IconButton.styleFrom( - backgroundColor: Colors.white, - elevation: 0, - ), - onPressed: () async { - await diaryCubit.deletePhoto( - photo: photos[currentSelectedPhotoIndex], - diaryId: diaryDay.id, - mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, - date: _selectedDay.toString(), - ); - setState(() { - if (currentSelectedPhotoIndex == photos.length - 1 && currentSelectedPhotoIndex != 0) { - currentSelectedPhotoIndex = photos.length - 2; - } - }); - }, - icon: Icon( - Icons.close_rounded, - color: Colors.grey, - ), - ), - ), - ], - ), - const SizedBox(height: 8), - SingleChildScrollView( - physics: const BouncingScrollPhysics(), - scrollDirection: Axis.horizontal, - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - for (int i = 0; i < photos.length + 1; i++) ...[ - if (i == photos.length) - Padding( - padding: const EdgeInsets.only(left: 8), - child: InkWell( - onTap: () => _showImagePicker(diaryDay), - borderRadius: BorderRadius.circular(8), - child: Ink( - height: 52, - width: 52, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: Color(0xFFF1F2F6), - ), - child: Icon( - Icons.add_rounded, - color: appTheme.blueA400, - ), - ), - ), - ), - if (i < photos.length) - GestureDetector( - onTap: () { - setState(() { - currentSelectedPhotoIndex = i; - }); - }, - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: SizedBox( - height: 52, - width: 52, - child: CachedNetworkImage( - imageUrl: baseUrl + '/storage/' + photos[i].path, - fit: BoxFit.fitWidth, - ), - ), - ), - ), - if (i < photos.length - 1) const SizedBox(width: 8), - ], - ], - ), - ), - ], - ); - } - - _mealButton(int mealNumber) { - bool isActive = currentSelectedMealNumber == mealNumber; - - return InkWell( - onTap: () { - setState(() { - if (currentSelectedMealNumber == mealNumber) return; - currentSelectedMealNumber = mealNumber; - }); - }, - borderRadius: BorderRadius.circular(100), - child: Ink( - width: MediaQuery.of(context).size.width / 5 - (96 / 5), - height: 32, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(100), - color: isActive ? appTheme.blueA400 : Color(0xFFF1F2F6), - ), - child: Center( - child: Text( - '$mealNumber', - style: TextStyle( - color: isActive ? Colors.white : Color(0xFF6D6F71), - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - ), - ), - ); - } - - _calendar(DiaryState state) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(top: 16), - child: TableCalendar( - firstDay: DateTime.utc(2010, 10, 16), - lastDay: DateTime.utc(2030, 3, 14), - locale: 'ru_RU', - startingDayOfWeek: StartingDayOfWeek.monday, - focusedDay: _focusedDay, - calendarFormat: _calendarFormat, - headerStyle: HeaderStyle( - titleCentered: true, - formatButtonVisible: false, - leftChevronVisible: false, - rightChevronVisible: false, - ), - selectedDayPredicate: (day) { - return isSameDay(_selectedDay, day); - }, - onDaySelected: (selectedDay, focusedDay) { - if (state is! DiaryStateLoaded) return; - if (!isSameDay(_selectedDay, selectedDay)) { - setState(() { - _selectedDay = selectedDay; - _focusedDay = focusedDay; - currentSelectedMealNumber = 1; - }); - diaryCubit.getDiaryDay(_selectedDay!.toString()); - } - }, - onFormatChanged: (format) { - if (_calendarFormat != format) { - setState(() { - _calendarFormat = format; - }); - } - }, - onPageChanged: (focusedDay) { - _focusedDay = focusedDay; - }, - calendarStyle: CalendarStyle( - weekendDecoration: BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - ), - defaultDecoration: BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - ), - selectedDecoration: BoxDecoration( - color: appTheme.blueA400, - shape: BoxShape.circle, - ), - todayTextStyle: TextStyle( - color: Colors.black, - ), - todayDecoration: BoxDecoration( - border: Border.all( - color: appTheme.blueA400, - width: 2, - ), - color: Colors.white, - shape: BoxShape.circle, - ), - ), - ), - ), - _arrowDown(), - ], - ); - } - - _modalBottomSheet(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, - ), - ), - ), - ), - ), - ], - ), - ], - ), - ); - }, - ); - }, - ); - } - - _addProductButton(DiaryDayModel diaryDay) { - if (_selectedDay!.compareTo(today!) == 1) return const SizedBox.shrink(); - return InkWell( - onTap: () { - setState(() { - selectedGlossary = null; - productNameController.clear(); - quantityController.clear(); - }); - _modalBottomSheet(diaryDay); - }, - 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, - ), - ), - ), - ), - ); - } } diff --git a/lib/features/diary/presentation/widgets/add_product_button_widget.dart b/lib/features/diary/presentation/widgets/add_product_button_widget.dart new file mode 100644 index 00000000..38ae5ab3 --- /dev/null +++ b/lib/features/diary/presentation/widgets/add_product_button_widget.dart @@ -0,0 +1,39 @@ +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:flutter/material.dart'; + +class AddProductButtonWidget extends StatefulWidget { + const AddProductButtonWidget({super.key}); + + @override + State createState() => _AddProductButtonWidgetState(); +} + +class _AddProductButtonWidgetState extends State { + @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, + ), + ), + ), + ), + ); + } +} 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 new file mode 100644 index 00000000..17a609c8 --- /dev/null +++ b/lib/features/diary/presentation/widgets/add_product_in_meal_modal_widget.dart @@ -0,0 +1,220 @@ +// 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/calendar_widget.dart b/lib/features/diary/presentation/widgets/calendar_widget.dart new file mode 100644 index 00000000..7a224546 --- /dev/null +++ b/lib/features/diary/presentation/widgets/calendar_widget.dart @@ -0,0 +1,130 @@ +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:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:table_calendar/table_calendar.dart'; + +class CalendarWidget extends StatefulWidget { + const CalendarWidget({super.key}); + + @override + State createState() => _CalendarWidgetState(); +} + +class _CalendarWidgetState extends State { + CalendarFormat _calendarFormat = CalendarFormat.week; + DateTime _focusedDay = DateTime.now(); + DateTime _selectedDay = DateTime.now(); + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (BuildContext context, DiaryState state) { + if (state is DiaryStateLoaded) { + setState(() { + _selectedDay = state.selectedDay; + }); + } + }, + builder: (BuildContext context, DiaryState state) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(top: 16), + child: TableCalendar( + focusedDay: _focusedDay, + firstDay: DateTime.utc(2020, 10, 16), + lastDay: DateTime.utc(2030, 3, 14), + locale: 'ru_RU', + startingDayOfWeek: StartingDayOfWeek.monday, + calendarFormat: _calendarFormat, + headerStyle: HeaderStyle( + titleCentered: true, + formatButtonVisible: false, + leftChevronVisible: false, + rightChevronVisible: false, + ), + onDaySelected: (selectedDay, focusedDay) { + if (!isSameDay(_selectedDay, selectedDay)) { + setState(() { + _selectedDay = selectedDay; + _focusedDay = focusedDay; + }); + context.read().setSelectedDay(_selectedDay); + } + }, + selectedDayPredicate: (day) { + return isSameDay(_selectedDay, day); + }, + onPageChanged: (focusedDay) { + _focusedDay = focusedDay; + }, + onFormatChanged: (format) { + if (_calendarFormat != format) { + setState(() { + _calendarFormat = format; + }); + } + }, + calendarStyle: CalendarStyle( + weekendDecoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + defaultDecoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + selectedDecoration: BoxDecoration( + color: appTheme.blueA400, + shape: BoxShape.circle, + ), + todayTextStyle: TextStyle( + color: Colors.black, + ), + todayDecoration: BoxDecoration( + border: Border.all( + color: appTheme.blueA400, + width: 2, + ), + color: Colors.white, + shape: BoxShape.circle, + ), + ), + ), + ), + _arrowDown(), + ], + ); + }, + ); + } + + _nextCalendarFormat() { + setState(() { + _calendarFormat = CalendarFormat.values[(_calendarFormat.index - 1) % CalendarFormat.values.length]; + }); + } + + _arrowDown() { + return GestureDetector( + behavior: HitTestBehavior.translucent, + onVerticalDragStart: (_) { + _nextCalendarFormat(); + }, + onTap: () { + _nextCalendarFormat(); + }, + child: Container( + width: double.infinity, + padding: const EdgeInsets.only(top: 8, bottom: 16), + child: Icon( + _calendarFormat == CalendarFormat.month ? Icons.keyboard_arrow_up_rounded : Icons.keyboard_arrow_down_rounded, + color: Colors.grey.shade600, + ), + ), + ); + } +} diff --git a/lib/features/diary/presentation/widgets/calories_chart_widget.dart b/lib/features/diary/presentation/widgets/calories_chart_widget.dart new file mode 100644 index 00000000..73e40206 --- /dev/null +++ b/lib/features/diary/presentation/widgets/calories_chart_widget.dart @@ -0,0 +1,246 @@ +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:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncfusion_flutter_gauges/gauges.dart'; + +class CaloriesChartWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (BuildContext context, DiaryState state) { + if (state is DiaryStateLoaded) { + return Container( + padding: const EdgeInsets.all(16), + margin: const EdgeInsets.only(top: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.white, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Ваш КБЖУ от дневной\nсуточной нормы', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF323F4B), + fontWeight: FontWeight.w500, + fontSize: 18, + height: 1, + ), + ), + const SizedBox(height: 48), + Stack( + alignment: Alignment.bottomCenter, + children: [ + SfRadialGauge( + axes: [ + RadialAxis( + startAngle: 180, + endAngle: 0, + minimum: 0, + maximum: 100, + showLabels: false, + showTicks: false, + radiusFactor: 0.7, + showAxisLine: false, + pointers: [ + RangePointer( + value: 100, + width: 0.16, + animationDuration: 2000, + color: Color(0xFFF6FAFF), + sizeUnit: GaugeSizeUnit.factor, + enableAnimation: true, + cornerStyle: CornerStyle.bothCurve, + ), + RangePointer( + value: (state.nutrition.dailyNutritionIntake.carbohydrates / state.nutrition.dailyNutritionRecommendation.carbohydrates) * 100, + width: 0.16, + color: state.nutrition.dailyNutritionIntake.carbohydrates < state.nutrition.dailyNutritionRecommendation.carbohydrates ? appTheme.blueA400 : Colors.red, + sizeUnit: GaugeSizeUnit.factor, + enableAnimation: true, + cornerStyle: CornerStyle.bothCurve, + ), + RangePointer( + value: 100, + width: 0.16, + animationDuration: 2000, + color: Color(0xFFF6FAFF), + sizeUnit: GaugeSizeUnit.factor, + enableAnimation: true, + cornerStyle: CornerStyle.bothCurve, + pointerOffset: -0.18, + ), + RangePointer( + value: (state.nutrition.dailyNutritionIntake.fats / state.nutrition.dailyNutritionRecommendation.fats) * 100, + width: 0.16, + color: state.nutrition.dailyNutritionIntake.fats < state.nutrition.dailyNutritionRecommendation.fats ? appTheme.blueA400 : Colors.red, + sizeUnit: GaugeSizeUnit.factor, + enableAnimation: true, + cornerStyle: CornerStyle.bothCurve, + pointerOffset: -0.18, + ), + RangePointer( + value: 100, + width: 0.16, + animationDuration: 2000, + color: Color(0xFFF6FAFF), + sizeUnit: GaugeSizeUnit.factor, + enableAnimation: true, + cornerStyle: CornerStyle.bothCurve, + pointerOffset: -0.36, + ), + RangePointer( + value: (state.nutrition.dailyNutritionIntake.proteins / state.nutrition.dailyNutritionRecommendation.proteins) * 100, + width: 0.16, + color: state.nutrition.dailyNutritionIntake.proteins < state.nutrition.dailyNutritionRecommendation.proteins ? appTheme.blueA400 : Colors.red, + sizeUnit: GaugeSizeUnit.factor, + enableAnimation: true, + cornerStyle: CornerStyle.bothCurve, + pointerOffset: -0.36, + ), + RangePointer( + value: 100, + width: 0.16, + animationDuration: 2000, + color: Color(0xFFF6FAFF), + sizeUnit: GaugeSizeUnit.factor, + enableAnimation: true, + cornerStyle: CornerStyle.bothCurve, + pointerOffset: -0.54, + ), + RangePointer( + value: (state.nutrition.dailyNutritionIntake.energy / state.nutrition.dailyNutritionRecommendation.energy) * 100, + width: 0.16, + color: state.nutrition.dailyNutritionIntake.energy < state.nutrition.dailyNutritionRecommendation.energy ? appTheme.blueA400 : Colors.red, + sizeUnit: GaugeSizeUnit.factor, + enableAnimation: true, + cornerStyle: CornerStyle.bothCurve, + pointerOffset: -0.54, + ), + ], + annotations: [ + GaugeAnnotation( + widget: Transform.rotate( + angle: 30.1, + child: Text( + 'Углеводы', + style: TextStyle( + color: Colors.white, + fontSize: 12, + ), + ), + ), + angle: 198, + positionFactor: 0.92, + ), + GaugeAnnotation( + widget: Transform.rotate( + angle: 30, + child: Text( + 'Жиры', + style: TextStyle( + color: Colors.white, + fontSize: 12, + ), + ), + ), + angle: 190, + positionFactor: 1.1, + ), + GaugeAnnotation( + widget: Transform.rotate( + angle: 30, + child: Text( + 'Белки', + style: TextStyle( + color: Colors.white, + fontSize: 12, + ), + ), + ), + angle: 190, + positionFactor: 1.27, + ), + GaugeAnnotation( + widget: Transform.rotate( + angle: 30, + child: Text( + 'Калории', + style: TextStyle( + color: Colors.white, + fontSize: 12, + ), + ), + ), + angle: 192, + positionFactor: 1.46, + ), + ], + ), + ], + ), + Column( + children: [ + _chartData( + 'Калории', + '${state.nutrition.dailyNutritionIntake.energy.toStringAsFixed(0)} из ${state.nutrition.dailyNutritionRecommendation.energy.toStringAsFixed(0)} ккал', + ), + const SizedBox(height: 8), + _chartData( + 'Белки', + '${state.nutrition.dailyNutritionIntake.proteins.toStringAsFixed(0)} из ${state.nutrition.dailyNutritionRecommendation.proteins.toStringAsFixed( + 0, + )} грамм', + ), + const SizedBox(height: 8), + _chartData( + 'Жиры', + '${state.nutrition.dailyNutritionIntake.fats.toStringAsFixed(0)} из ${state.nutrition.dailyNutritionRecommendation.fats.toStringAsFixed(0)} грамм', + ), + const SizedBox(height: 8), + _chartData( + 'Углеводы', + '${state.nutrition.dailyNutritionIntake.carbohydrates.toStringAsFixed(0)} из ${state.nutrition.dailyNutritionRecommendation.carbohydrates.toStringAsFixed(0)} грамм', + ), + ], + ), + ], + ), + ], + ), + ); + } + return const SizedBox.shrink(); + }, + ); + } + + _chartData(String title, String value) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Color(0xFF323F4B), + ), + ), + Text( + value, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Color(0xFF323F4B), + ), + ), + ], + ); + } +} diff --git a/lib/features/diary/presentation/widgets/calories_chat_widget.dart b/lib/features/diary/presentation/widgets/calories_chat_widget.dart deleted file mode 100644 index c0dc8ae6..00000000 --- a/lib/features/diary/presentation/widgets/calories_chat_widget.dart +++ /dev/null @@ -1,275 +0,0 @@ -import 'package:eatvio_app/core/theme/theme_helper.dart'; -import 'package:eatvio_app/features/diary/entities/nutrition_chart_data_entity.dart'; -import 'package:eatvio_app/features/diary/models/nutrition_model.dart'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_gauges/gauges.dart'; - -class CaloriesChatWidget extends StatefulWidget { - final NutritionDataModel nutrition; - - CaloriesChatWidget({ - super.key, - required this.nutrition, - }); - - @override - State createState() => _CaloriesChatWidgetState(); -} - -class _CaloriesChatWidgetState extends State { - late final List nutritionChartData; - - @override - void initState() { - super.initState(); - nutritionChartData = [ - NutritionChartDataEntity( - widget.nutrition.dailyNutritionIntake.carbohydrates, - widget.nutrition.dailyNutritionRecommendation.carbohydrates, - ), - NutritionChartDataEntity( - widget.nutrition.dailyNutritionIntake.fats, - widget.nutrition.dailyNutritionRecommendation.fats, - ), - NutritionChartDataEntity( - widget.nutrition.dailyNutritionIntake.proteins, - widget.nutrition.dailyNutritionRecommendation.proteins, - ), - NutritionChartDataEntity( - widget.nutrition.dailyNutritionIntake.energy, - widget.nutrition.dailyNutritionRecommendation.energy, - ), - ]; - } - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(16), - margin: const EdgeInsets.only(top: 12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.white, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - 'Ваш КБЖУ от дневной\nсуточной нормы', - textAlign: TextAlign.center, - style: TextStyle( - color: Color(0xFF323F4B), - fontWeight: FontWeight.w500, - fontSize: 18, - height: 1, - ), - ), - const SizedBox(height: 48), - Stack( - alignment: Alignment.bottomCenter, - children: [ - SfRadialGauge( - axes: [ - RadialAxis( - startAngle: 180, - endAngle: 0, - minimum: 0, - maximum: 100, - showLabels: false, - showTicks: false, - radiusFactor: 0.7, - showAxisLine: false, - pointers: [ - RangePointer( - value: 100, - width: 0.16, - animationDuration: 2000, - color: Color(0xFFF6FAFF), - sizeUnit: GaugeSizeUnit.factor, - enableAnimation: true, - cornerStyle: CornerStyle.bothCurve, - ), - RangePointer( - value: (nutritionChartData[0].intake / nutritionChartData[0].recommendation) * 100, - width: 0.16, - color: nutritionChartData[0].intake < nutritionChartData[0].recommendation ? appTheme.blueA400 : Colors.red, - sizeUnit: GaugeSizeUnit.factor, - enableAnimation: true, - cornerStyle: CornerStyle.bothCurve, - ), - RangePointer( - value: 100, - width: 0.16, - animationDuration: 2000, - color: Color(0xFFF6FAFF), - sizeUnit: GaugeSizeUnit.factor, - enableAnimation: true, - cornerStyle: CornerStyle.bothCurve, - pointerOffset: -0.18, - ), - RangePointer( - value: (nutritionChartData[1].intake / nutritionChartData[1].recommendation) * 100, - width: 0.16, - color: nutritionChartData[1].intake < nutritionChartData[1].recommendation ? appTheme.blueA400 : Colors.red, - sizeUnit: GaugeSizeUnit.factor, - enableAnimation: true, - cornerStyle: CornerStyle.bothCurve, - pointerOffset: -0.18, - ), - RangePointer( - value: 100, - width: 0.16, - animationDuration: 2000, - color: Color(0xFFF6FAFF), - sizeUnit: GaugeSizeUnit.factor, - enableAnimation: true, - cornerStyle: CornerStyle.bothCurve, - pointerOffset: -0.36, - ), - RangePointer( - value: (nutritionChartData[2].intake / nutritionChartData[2].recommendation) * 100, - width: 0.16, - color: nutritionChartData[2].intake < nutritionChartData[2].recommendation ? appTheme.blueA400 : Colors.red, - sizeUnit: GaugeSizeUnit.factor, - enableAnimation: true, - cornerStyle: CornerStyle.bothCurve, - pointerOffset: -0.36, - ), - RangePointer( - value: 100, - width: 0.16, - animationDuration: 2000, - color: Color(0xFFF6FAFF), - sizeUnit: GaugeSizeUnit.factor, - enableAnimation: true, - cornerStyle: CornerStyle.bothCurve, - pointerOffset: -0.54, - ), - RangePointer( - value: (nutritionChartData[3].intake / nutritionChartData[3].recommendation) * 100, - width: 0.16, - color: nutritionChartData[3].intake < nutritionChartData[3].recommendation ? appTheme.blueA400 : Colors.red, - sizeUnit: GaugeSizeUnit.factor, - enableAnimation: true, - cornerStyle: CornerStyle.bothCurve, - pointerOffset: -0.54, - ), - ], - annotations: [ - GaugeAnnotation( - widget: Transform.rotate( - angle: 30.1, - child: Text( - 'Углеводы', - style: TextStyle( - color: Colors.white, - fontSize: 12, - ), - ), - ), - angle: 198, - positionFactor: 0.92, - ), - GaugeAnnotation( - widget: Transform.rotate( - angle: 30, - child: Text( - 'Жиры', - style: TextStyle( - color: Colors.white, - fontSize: 12, - ), - ), - ), - angle: 190, - positionFactor: 1.1, - ), - GaugeAnnotation( - widget: Transform.rotate( - angle: 30, - child: Text( - 'Белки', - style: TextStyle( - color: Colors.white, - fontSize: 12, - ), - ), - ), - angle: 190, - positionFactor: 1.27, - ), - GaugeAnnotation( - widget: Transform.rotate( - angle: 30, - child: Text( - 'Калории', - style: TextStyle( - color: Colors.white, - fontSize: 12, - ), - ), - ), - angle: 192, - positionFactor: 1.46, - ), - ], - ), - ], - ), - Column( - children: [ - _chartData( - 'Калории', - '${widget.nutrition.dailyNutritionIntake.energy.toStringAsFixed(0)} из ${widget.nutrition.dailyNutritionRecommendation.energy.toStringAsFixed(0)} ккал', - ), - const SizedBox(height: 8), - _chartData( - 'Белки', - '${widget.nutrition.dailyNutritionIntake.proteins.toStringAsFixed(0)} из ${widget.nutrition.dailyNutritionRecommendation.proteins.toStringAsFixed( - 0, - )} грамм', - ), - const SizedBox(height: 8), - _chartData( - 'Жиры', - '${widget.nutrition.dailyNutritionIntake.fats.toStringAsFixed(0)} из ${widget.nutrition.dailyNutritionRecommendation.fats.toStringAsFixed(0)} грамм', - ), - const SizedBox(height: 8), - _chartData( - 'Углеводы', - '${widget.nutrition.dailyNutritionIntake.carbohydrates.toStringAsFixed(0)} из ${widget.nutrition.dailyNutritionRecommendation.carbohydrates.toStringAsFixed(0)} грамм', - ), - ], - ), - ], - ), - ], - ), - ); - } - - _chartData(String title, String value) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - title, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Color(0xFF323F4B), - ), - ), - Text( - value, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Color(0xFF323F4B), - ), - ), - ], - ); - } -} diff --git a/lib/features/diary/presentation/widgets/finish_day_button_widget.dart b/lib/features/diary/presentation/widgets/finish_day_button_widget.dart index 35a272a0..055c6457 100644 --- a/lib/features/diary/presentation/widgets/finish_day_button_widget.dart +++ b/lib/features/diary/presentation/widgets/finish_day_button_widget.dart @@ -1,50 +1,50 @@ -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/core/theme/theme_helper.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class FinishDayButtonWidget extends StatefulWidget { - final DiaryDayModel diaryDay; - - const FinishDayButtonWidget({ - super.key, - required this.diaryDay, - }); - - @override - State createState() => _FinishDayButtonWidgetState(); -} - -class _FinishDayButtonWidgetState extends State { - @override - Widget build(BuildContext context) { - return InkWell( - onTap: () => context.read().finishDiaryDay( - diaryId: widget.diaryDay.id, - date: widget.diaryDay.date, - ), - borderRadius: BorderRadius.circular(20), - child: Ink( - padding: EdgeInsets.symmetric(horizontal: 8), - width: MediaQuery.of(context).size.width - 16, - 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, - ), - ), - ), - ), - ); - } -} +// 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/core/theme/theme_helper.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_bloc/flutter_bloc.dart'; +// +// class FinishDayButtonWidget extends StatefulWidget { +// final DiaryDayModel diaryDay; +// +// const FinishDayButtonWidget({ +// super.key, +// required this.diaryDay, +// }); +// +// @override +// State createState() => _FinishDayButtonWidgetState(); +// } +// +// class _FinishDayButtonWidgetState extends State { +// @override +// Widget build(BuildContext context) { +// return InkWell( +// onTap: () => context.read().finishDiaryDay( +// diaryId: widget.diaryDay.id, +// date: widget.diaryDay.date, +// ), +// borderRadius: BorderRadius.circular(20), +// child: Ink( +// padding: EdgeInsets.symmetric(horizontal: 8), +// width: MediaQuery.of(context).size.width - 16, +// 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, +// ), +// ), +// ), +// ), +// ); +// } +// } diff --git a/lib/features/diary/presentation/widgets/meal_button_widget.dart b/lib/features/diary/presentation/widgets/meal_button_widget.dart new file mode 100644 index 00000000..5a7bf318 --- /dev/null +++ b/lib/features/diary/presentation/widgets/meal_button_widget.dart @@ -0,0 +1,54 @@ +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:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MealButtonWidget extends StatefulWidget { + final int mealNumber; + + const MealButtonWidget({ + super.key, + required this.mealNumber, + }); + + @override + State createState() => _MealButtonWidgetState(); +} + +class _MealButtonWidgetState extends State { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is DiaryStateLoaded) { + return InkWell( + onTap: () { + context.read().setSelectedMeal(widget.mealNumber); + }, + borderRadius: BorderRadius.circular(100), + child: Ink( + width: MediaQuery.of(context).size.width / 5 - (96 / 5), + height: 32, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: state.selectedMeal == widget.mealNumber ? appTheme.blueA400 : Color(0xFFF1F2F6), + ), + child: Center( + child: Text( + '${widget.mealNumber}', + style: TextStyle( + color: state.selectedMeal == widget.mealNumber ? Colors.white : Color(0xFF6D6F71), + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ); + } + return const SizedBox.shrink(); + }, + ); + } +} diff --git a/lib/features/diary/presentation/widgets/meal_widget.dart b/lib/features/diary/presentation/widgets/meal_widget.dart new file mode 100644 index 00000000..fb981147 --- /dev/null +++ b/lib/features/diary/presentation/widgets/meal_widget.dart @@ -0,0 +1,53 @@ +import 'package:eatvio_app/features/diary/presentation/widgets/add_product_button_widget.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/meal_button_widget.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/products_list_widget.dart'; +import 'package:flutter/material.dart'; + +class MealWidget extends StatefulWidget { + const MealWidget({super.key}); + + @override + State createState() => _MealWidgetState(); +} + +class _MealWidgetState extends State { + @override + Widget build(BuildContext context) { + return Material( + borderRadius: BorderRadius.circular(20), + color: Colors.white, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Прием пищи', + style: TextStyle( + color: Color(0xFF323F4B), + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + MealButtonWidget(mealNumber: 1), + MealButtonWidget(mealNumber: 2), + MealButtonWidget(mealNumber: 3), + MealButtonWidget(mealNumber: 4), + MealButtonWidget(mealNumber: 5), + ], + ), + const SizedBox(height: 12), + // TimeOfMealPickerWidget(), // TODO endpoint for save time of meal + ProductsListWidget(), + const SizedBox(height: 12), + AddProductButtonWidget(), + ], + ), + ), + ); + } +} diff --git a/lib/features/diary/presentation/widgets/photo_gallery_widget.dart b/lib/features/diary/presentation/widgets/photo_gallery_widget.dart new file mode 100644 index 00000000..6388136b --- /dev/null +++ b/lib/features/diary/presentation/widgets/photo_gallery_widget.dart @@ -0,0 +1,167 @@ +// import 'package:flutter/material.dart'; +// +// class PhotoGalleryWidget extends StatefulWidget { +// const PhotoGalleryWidget({super.key}); +// +// @override +// State createState() => _PhotoGalleryWidgetState(); +// } +// +// class _PhotoGalleryWidgetState extends State { +// showImagePicker(DiaryDayModel diaryDay) { +// showCupertinoModalPopup( +// context: context, +// builder: (context) => CupertinoActionSheet( +// actions: [ +// CupertinoActionSheetAction( +// onPressed: () async { +// Navigator.of(context).pop(); +// final picker = ImagePicker(); +// XFile? image = await picker.pickImage(source: ImageSource.camera); +// if (image != null) +// diaryCubit.uploadPhotos( +// files: [File(image.path)], +// diaryId: diaryDay.id, +// mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, +// date: _selectedDay.toString(), +// ); +// }, +// child: const Row( +// children: const [ +// Icon(Icons.photo_camera), +// SizedBox(width: 8), +// Text('Фото'), +// ], +// ), +// ), +// CupertinoActionSheetAction( +// onPressed: () async { +// Navigator.of(context).pop(); +// final picker = ImagePicker(); +// List? images = await picker.pickMultiImage(); +// if (images.isNotEmpty) +// diaryCubit.uploadPhotos( +// files: images.map((f) => File(f.path)).toList(), +// diaryId: diaryDay.id, +// mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, +// date: _selectedDay.toString(), +// ); +// }, +// child: const Row( +// children: const [ +// Icon(Icons.photo), +// SizedBox(width: 8), +// Text('Галерея'), +// ], +// ), +// ), +// ], +// cancelButton: CupertinoActionSheetAction( +// onPressed: () => Navigator.of(context).pop(), +// isDestructiveAction: true, +// child: const Text('Отмена'), +// ), +// ), +// ); +// } +// +// @override +// Widget build(BuildContext context) { +// return Column( +// mainAxisSize: MainAxisSize.min, +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Stack( +// alignment: Alignment.topRight, +// children: [ +// ClipRRect( +// borderRadius: BorderRadius.circular(12), +// child: CachedNetworkImage( +// imageUrl: baseUrl + '/storage/' + photos[currentSelectedPhotoIndex].path, +// ), +// ), +// Padding( +// padding: const EdgeInsets.all(12), +// child: IconButton.filled( +// style: IconButton.styleFrom( +// backgroundColor: Colors.white, +// elevation: 0, +// ), +// onPressed: () async { +// await diaryCubit.deletePhoto( +// photo: photos[currentSelectedPhotoIndex], +// diaryId: diaryDay.id, +// mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, +// date: _selectedDay.toString(), +// ); +// setState(() { +// if (currentSelectedPhotoIndex == photos.length - 1 && currentSelectedPhotoIndex != 0) { +// currentSelectedPhotoIndex = photos.length - 2; +// } +// }); +// }, +// icon: Icon( +// Icons.close_rounded, +// color: Colors.grey, +// ), +// ), +// ), +// ], +// ), +// const SizedBox(height: 8), +// SingleChildScrollView( +// physics: const BouncingScrollPhysics(), +// scrollDirection: Axis.horizontal, +// child: Row( +// mainAxisSize: MainAxisSize.min, +// mainAxisAlignment: MainAxisAlignment.start, +// children: [ +// for (int i = 0; i < photos.length + 1; i++) ...[ +// if (i == photos.length) +// Padding( +// padding: const EdgeInsets.only(left: 8), +// child: InkWell( +// onTap: () => _showImagePicker(diaryDay), +// borderRadius: BorderRadius.circular(8), +// child: Ink( +// height: 52, +// width: 52, +// decoration: BoxDecoration( +// borderRadius: BorderRadius.circular(8), +// color: Color(0xFFF1F2F6), +// ), +// child: Icon( +// Icons.add_rounded, +// color: appTheme.blueA400, +// ), +// ), +// ), +// ), +// if (i < photos.length) +// GestureDetector( +// onTap: () { +// setState(() { +// currentSelectedPhotoIndex = i; +// }); +// }, +// child: ClipRRect( +// borderRadius: BorderRadius.circular(8), +// child: SizedBox( +// height: 52, +// width: 52, +// child: CachedNetworkImage( +// imageUrl: baseUrl + '/storage/' + photos[i].path, +// fit: BoxFit.fitWidth, +// ), +// ), +// ), +// ), +// if (i < 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 new file mode 100644 index 00000000..e097391c --- /dev/null +++ b/lib/features/diary/presentation/widgets/product_widget.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +class ProductWidget extends StatefulWidget { + final String product, portion; + final int quantity; + + const ProductWidget({ + super.key, + required this.product, + required this.quantity, + required this.portion, + }); + + @override + State createState() => _ProductWidgetState(); +} + +class _ProductWidgetState extends State { + @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), + ); + } +} diff --git a/lib/features/diary/presentation/widgets/products_list_widget.dart b/lib/features/diary/presentation/widgets/products_list_widget.dart new file mode 100644 index 00000000..ad8c0ab7 --- /dev/null +++ b/lib/features/diary/presentation/widgets/products_list_widget.dart @@ -0,0 +1,38 @@ +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/product_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ProductsListWidget extends StatefulWidget { + const ProductsListWidget({super.key}); + + @override + State createState() => _ProductsListWidgetState(); +} + +class _ProductsListWidgetState extends State { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (BuildContext context, DiaryState state) { + if (state is DiaryStateLoaded) { + 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(), + ], + ], + ); + } + return const SizedBox.shrink(); + }, + ); + } +} diff --git a/lib/features/diary/presentation/widgets/satiety_rating_widget.dart b/lib/features/diary/presentation/widgets/satiety_rating_widget.dart index 69b8a23c..8eab2866 100644 --- a/lib/features/diary/presentation/widgets/satiety_rating_widget.dart +++ b/lib/features/diary/presentation/widgets/satiety_rating_widget.dart @@ -1,6 +1,4 @@ -import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; import 'package:flutter/cupertino.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_rating_bar/flutter_rating_bar.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -167,11 +165,7 @@ class _SatietyRatingWidgetState extends State { ); } }, - onRatingUpdate: (value) => context.read().updateCurrentSelectedSatietyRating( - diaryId: widget.diaryId, - meal: widget.meal, - value: value.toInt(), - ), + onRatingUpdate: (value) {}, ), SvgPicture.asset( 'assets/icons/satiety_emoji_right.svg', 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 new file mode 100644 index 00000000..7a1ffe24 --- /dev/null +++ b/lib/features/diary/presentation/widgets/time_of_meal_picker_widget.dart @@ -0,0 +1,116 @@ +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/widgets/custom_divider.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class TimeOfMealPickerWidget extends StatefulWidget { + const TimeOfMealPickerWidget({super.key}); + + @override + State createState() => _TimeOfMealPickerWidgetState(); +} + +class _TimeOfMealPickerWidgetState extends State { + String time = "${DateTime.now().hour.toString().padLeft(2, '0')}:${DateTime.now().minute.toString().padLeft(2, '0')}"; + DateTime _time = DateTime.now(); + + showTimePicker() { + showModalBottomSheet( + context: context, + useRootNavigator: true, + builder: (context) => SizedBox( + height: MediaQuery.of(context).copyWith().size.height / 3, + child: Column( + children: [ + Expanded( + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.time, + use24hFormat: true, + initialDateTime: DateTime.now(), + onDateTimeChanged: (newTime) { + setState(() { + _time = newTime; + time = "${_time.hour.toString().padLeft(2, '0')}:${_time.minute.toString().padLeft(2, '0')}"; + }); + }, + ), + ), + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + borderRadius: BorderRadius.circular(20), + child: Ink( + width: MediaQuery.of(context).size.width - 16, + height: 52, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: appTheme.blueA400, + ), + child: Center( + child: Text( + 'Сохранить', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ), + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + CustomDivider(), + Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + 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, + style: TextStyle( + color: Color(0xFF6D6F71), + fontSize: 17, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ], + ), + ), + CustomDivider(), + ], + ); + } +} diff --git a/lib/features/diary/repositories/diary_repository.dart b/lib/features/diary/repositories/diary_repository.dart index 7cb7e888..5af684c2 100644 --- a/lib/features/diary/repositories/diary_repository.dart +++ b/lib/features/diary/repositories/diary_repository.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; +import 'package:eatvio_app/features/diary/models/finish_diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/nutrition_model.dart'; import 'package:eatvio_app/features/diary/models/product_moderation_model.dart'; import 'package:eatvio_app/features/diary/services/diary_service.dart'; @@ -114,11 +115,11 @@ class DiaryRepository { }) async { return await diaryService.finishDiaryDay( diaryId: diaryId, - // finishDiaryDayModel: FinishDiaryDayModel( - // wakeUp: wakeUp, - // retire: retire, - // comment: comment, - // ), + finishDiaryDayModel: FinishDiaryDayModel( + wakeUp: wakeUp, + retire: retire, + comment: comment, + ), ); } diff --git a/lib/features/diary/services/diary_service.dart b/lib/features/diary/services/diary_service.dart index 40598e04..ee22dccc 100644 --- a/lib/features/diary/services/diary_service.dart +++ b/lib/features/diary/services/diary_service.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; +import 'package:eatvio_app/features/diary/models/finish_diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/nutrition_model.dart'; import 'package:retrofit/retrofit.dart'; @@ -68,6 +69,7 @@ abstract class DiaryService { @POST('/api/diary/{diary_id}/finishDay') Future finishDiaryDay({ @Path('diary_id') required int diaryId, + @Body() required FinishDiaryDayModel finishDiaryDayModel, }); @POST('/api/diary/{diary_id}/meal/{meal}/satiety/fill') diff --git a/lib/features/diary/services/diary_service.g.dart b/lib/features/diary/services/diary_service.g.dart index 8c88dd36..e7bde5a4 100644 --- a/lib/features/diary/services/diary_service.g.dart +++ b/lib/features/diary/services/diary_service.g.dart @@ -296,11 +296,14 @@ class _DiaryService implements DiaryService { } @override - Future finishDiaryDay({required int diaryId}) async { + Future finishDiaryDay({ + required int diaryId, + required FinishDiaryDayModel finishDiaryDayModel, + }) async { final _extra = {}; final queryParameters = {}; final _headers = {}; - const Map? _data = null; + final _data = finishDiaryDayModel; final _options = _setStreamType(Options( method: 'POST', headers: _headers,