diff --git a/analysis_options.yaml b/analysis_options.yaml index 7db716b4..fe64289f 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,6 +1,10 @@ +include: package:flutter_lints/flutter.yaml + linter: rules: - - require_trailing_commas + +formatter: + page_width: 80 analyzer: exclude: diff --git a/assets/icons/mood_1.svg b/assets/icons/mood_1.svg new file mode 100644 index 00000000..e3f93b4f --- /dev/null +++ b/assets/icons/mood_1.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/icons/mood_2.svg b/assets/icons/mood_2.svg new file mode 100644 index 00000000..48bcdb0f --- /dev/null +++ b/assets/icons/mood_2.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/retire.svg b/assets/icons/retire.svg new file mode 100644 index 00000000..bde7d06a --- /dev/null +++ b/assets/icons/retire.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/wakeup.svg b/assets/icons/wakeup.svg new file mode 100644 index 00000000..a0bd91c0 --- /dev/null +++ b/assets/icons/wakeup.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lib/core/widgets/custom_text_form_field.dart b/lib/core/widgets/custom_text_form_field.dart index 1cd38c0a..c58dd23e 100644 --- a/lib/core/widgets/custom_text_form_field.dart +++ b/lib/core/widgets/custom_text_form_field.dart @@ -80,8 +80,10 @@ class CustomTextFormField extends StatelessWidget { final TextAlignVertical? textAlignVertical; final bool hasError; final bool enabled; + // final TextAlign final bool readOnly; + @override Widget build(BuildContext context) { return Align( @@ -97,11 +99,7 @@ class CustomTextFormField extends StatelessWidget { controller: controller, focusNode: focusNode ?? null, autofocus: autofocus, - style: TextStyle( - fontSize: 16, - color: Colors.black, - fontWeight: FontWeight.w500, - ), + style: TextStyle(fontSize: 16, color: Colors.black, fontWeight: FontWeight.w500), obscureText: obscureText!, textInputAction: textInputAction, maxLines: maxLines, @@ -112,40 +110,26 @@ class CustomTextFormField extends StatelessWidget { counter: counter, suffixText: suffixText, hintText: hintText ?? "", - hintStyle: TextStyle( - fontSize: 16, - color: Colors.grey.shade400, - fontWeight: FontWeight.w500, - ), + hintStyle: hintStyle ?? TextStyle(fontSize: 16, color: Color(0xFF323F4B).withAlpha((255 / 2).toInt()), fontWeight: FontWeight.w500), prefixIcon: prefix, prefixIconConstraints: prefixConstraints, suffixIcon: suffix, floatingLabelBehavior: FloatingLabelBehavior.always, - suffixStyle: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Color(0xFF323F4B), - ), + suffixStyle: textStyle ?? TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: Color(0xFF323F4B)), suffixIconConstraints: suffixConstraints, isDense: false, contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 12), fillColor: Color(0xFFF6F6F6), filled: true, - border: borderDecoration ?? - OutlineInputBorder( - borderRadius: BorderRadius.circular(20), - borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none, - ), - enabledBorder: borderDecoration ?? - OutlineInputBorder( - borderRadius: BorderRadius.circular(20), - borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none, - ), - focusedBorder: borderDecoration ?? - OutlineInputBorder( - borderRadius: BorderRadius.circular(20), - borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none, - ), + border: + borderDecoration ?? + OutlineInputBorder(borderRadius: BorderRadius.circular(20), borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none), + enabledBorder: + borderDecoration ?? + OutlineInputBorder(borderRadius: BorderRadius.circular(20), borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none), + focusedBorder: + borderDecoration ?? + OutlineInputBorder(borderRadius: BorderRadius.circular(20), borderSide: hasError ? BorderSide(color: appTheme.red, width: 1) : BorderSide.none), ), keyboardType: keyboardType, validator: validator, diff --git a/lib/features/diary/models/finish_diary_day_model.dart b/lib/features/diary/models/finish_diary_day_model.dart index d1522273..8e7dfa34 100644 --- a/lib/features/diary/models/finish_diary_day_model.dart +++ b/lib/features/diary/models/finish_diary_day_model.dart @@ -4,12 +4,9 @@ part 'finish_diary_day_model.freezed.dart'; part 'finish_diary_day_model.g.dart'; -@freezed +@Freezed() class FinishDiaryDayModel with _$FinishDiaryDayModel { - const factory FinishDiaryDayModel({ - required String wakeUp, - required String retire, - }) = _FinishDiaryDayModel; + const factory FinishDiaryDayModel({required String wakeUp, required String retire, @JsonKey(includeIfNull: false) String? comment}) = _FinishDiaryDayModel; - factory FinishDiaryDayModel.fromJson(Map json) => _$FinishDiaryDayModelFromJson(json); + factory FinishDiaryDayModel.fromJson(Map json) => _$FinishDiaryDayModelFromJson(json); } diff --git a/lib/features/diary/models/finish_diary_day_model.freezed.dart b/lib/features/diary/models/finish_diary_day_model.freezed.dart index 8b590859..a6631f37 100644 --- a/lib/features/diary/models/finish_diary_day_model.freezed.dart +++ b/lib/features/diary/models/finish_diary_day_model.freezed.dart @@ -23,6 +23,8 @@ FinishDiaryDayModel _$FinishDiaryDayModelFromJson(Map json) { mixin _$FinishDiaryDayModel { String get wakeUp => throw _privateConstructorUsedError; String get retire => throw _privateConstructorUsedError; + @JsonKey(includeIfNull: false) + String? get comment => throw _privateConstructorUsedError; /// Serializes this FinishDiaryDayModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -41,7 +43,11 @@ abstract class $FinishDiaryDayModelCopyWith<$Res> { $Res Function(FinishDiaryDayModel) then, ) = _$FinishDiaryDayModelCopyWithImpl<$Res, FinishDiaryDayModel>; @useResult - $Res call({String wakeUp, String retire}); + $Res call({ + String wakeUp, + String retire, + @JsonKey(includeIfNull: false) String? comment, + }); } /// @nodoc @@ -58,7 +64,11 @@ class _$FinishDiaryDayModelCopyWithImpl<$Res, $Val extends FinishDiaryDayModel> /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override - $Res call({Object? wakeUp = null, Object? retire = null}) { + $Res call({ + Object? wakeUp = null, + Object? retire = null, + Object? comment = freezed, + }) { return _then( _value.copyWith( wakeUp: @@ -71,6 +81,11 @@ class _$FinishDiaryDayModelCopyWithImpl<$Res, $Val extends FinishDiaryDayModel> ? _value.retire : retire // ignore: cast_nullable_to_non_nullable as String, + comment: + freezed == comment + ? _value.comment + : comment // ignore: cast_nullable_to_non_nullable + as String?, ) as $Val, ); @@ -86,7 +101,11 @@ abstract class _$$FinishDiaryDayModelImplCopyWith<$Res> ) = __$$FinishDiaryDayModelImplCopyWithImpl<$Res>; @override @useResult - $Res call({String wakeUp, String retire}); + $Res call({ + String wakeUp, + String retire, + @JsonKey(includeIfNull: false) String? comment, + }); } /// @nodoc @@ -102,7 +121,11 @@ class __$$FinishDiaryDayModelImplCopyWithImpl<$Res> /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override - $Res call({Object? wakeUp = null, Object? retire = null}) { + $Res call({ + Object? wakeUp = null, + Object? retire = null, + Object? comment = freezed, + }) { return _then( _$FinishDiaryDayModelImpl( wakeUp: @@ -115,6 +138,11 @@ class __$$FinishDiaryDayModelImplCopyWithImpl<$Res> ? _value.retire : retire // ignore: cast_nullable_to_non_nullable as String, + comment: + freezed == comment + ? _value.comment + : comment // ignore: cast_nullable_to_non_nullable + as String?, ), ); } @@ -123,7 +151,11 @@ class __$$FinishDiaryDayModelImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() class _$FinishDiaryDayModelImpl implements _FinishDiaryDayModel { - const _$FinishDiaryDayModelImpl({required this.wakeUp, required this.retire}); + const _$FinishDiaryDayModelImpl({ + required this.wakeUp, + required this.retire, + @JsonKey(includeIfNull: false) this.comment, + }); factory _$FinishDiaryDayModelImpl.fromJson(Map json) => _$$FinishDiaryDayModelImplFromJson(json); @@ -132,10 +164,13 @@ class _$FinishDiaryDayModelImpl implements _FinishDiaryDayModel { final String wakeUp; @override final String retire; + @override + @JsonKey(includeIfNull: false) + final String? comment; @override String toString() { - return 'FinishDiaryDayModel(wakeUp: $wakeUp, retire: $retire)'; + return 'FinishDiaryDayModel(wakeUp: $wakeUp, retire: $retire, comment: $comment)'; } @override @@ -144,12 +179,13 @@ class _$FinishDiaryDayModelImpl implements _FinishDiaryDayModel { (other.runtimeType == runtimeType && other is _$FinishDiaryDayModelImpl && (identical(other.wakeUp, wakeUp) || other.wakeUp == wakeUp) && - (identical(other.retire, retire) || other.retire == retire)); + (identical(other.retire, retire) || other.retire == retire) && + (identical(other.comment, comment) || other.comment == comment)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, wakeUp, retire); + int get hashCode => Object.hash(runtimeType, wakeUp, retire, comment); /// Create a copy of FinishDiaryDayModel /// with the given fields replaced by the non-null parameter values. @@ -172,6 +208,7 @@ abstract class _FinishDiaryDayModel implements FinishDiaryDayModel { const factory _FinishDiaryDayModel({ required final String wakeUp, required final String retire, + @JsonKey(includeIfNull: false) final String? comment, }) = _$FinishDiaryDayModelImpl; factory _FinishDiaryDayModel.fromJson(Map json) = @@ -181,6 +218,9 @@ abstract class _FinishDiaryDayModel implements FinishDiaryDayModel { String get wakeUp; @override String get retire; + @override + @JsonKey(includeIfNull: false) + String? get comment; /// Create a copy of FinishDiaryDayModel /// with the given fields replaced by the non-null parameter values. diff --git a/lib/features/diary/models/finish_diary_day_model.g.dart b/lib/features/diary/models/finish_diary_day_model.g.dart index b20f81a9..5721deda 100644 --- a/lib/features/diary/models/finish_diary_day_model.g.dart +++ b/lib/features/diary/models/finish_diary_day_model.g.dart @@ -11,8 +11,13 @@ _$FinishDiaryDayModelImpl _$$FinishDiaryDayModelImplFromJson( ) => _$FinishDiaryDayModelImpl( wakeUp: json['wakeUp'] as String, retire: json['retire'] as String, + comment: json['comment'] as String?, ); Map _$$FinishDiaryDayModelImplToJson( _$FinishDiaryDayModelImpl instance, -) => {'wakeUp': instance.wakeUp, 'retire': instance.retire}; +) => { + 'wakeUp': instance.wakeUp, + 'retire': instance.retire, + if (instance.comment case final value?) 'comment': value, +}; diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart index 8c18c725..7d57a35d 100644 --- a/lib/features/diary/presentation/bloc/diary_cubit.dart +++ b/lib/features/diary/presentation/bloc/diary_cubit.dart @@ -17,10 +17,7 @@ class DiaryCubit extends Cubit { final DiaryRepository diaryRepository; final GlossaryRepository glossaryRepository; - DiaryCubit({ - required this.diaryRepository, - required this.glossaryRepository, - }) : super(const DiaryState.loading()) { + DiaryCubit({required this.diaryRepository, required this.glossaryRepository}) : super(const DiaryState.loading()) { today = DateTime.now(); selectedDay = today; } @@ -56,16 +53,8 @@ class DiaryCubit extends Cubit { } } - finishDiaryDay({ - required int diaryId, - required String wakeUp, - required String retire, - }) async { - await diaryRepository.finishDiaryDay( - diaryId: diaryId, - wakeUp: wakeUp, - retire: retire, - ); + finishDiaryDay({required int diaryId, required String wakeUp, required String retire, String? comment}) async { + await diaryRepository.finishDiaryDay(diaryId: diaryId, wakeUp: wakeUp, retire: retire, comment: comment); await fetchDiary(); } @@ -126,55 +115,23 @@ class DiaryCubit extends Cubit { await fetchDiary(); } - markEaten({ - required int diaryId, - required int mealId, - required int portionId, - }) async { - await diaryRepository.markEaten( - diaryId: diaryId, - mealId: mealId, - portionId: portionId, - ); + markEaten({required int diaryId, required int mealId, required int portionId}) async { + await diaryRepository.markEaten(diaryId: diaryId, mealId: mealId, portionId: portionId); await fetchDiary(); } - unmarkEaten({ - required int diaryId, - required int mealId, - required int portionId, - }) async { - await diaryRepository.unmarkEaten( - diaryId: diaryId, - mealId: mealId, - portionId: portionId, - ); + unmarkEaten({required int diaryId, required int mealId, required int portionId}) async { + await diaryRepository.unmarkEaten(diaryId: diaryId, mealId: mealId, portionId: portionId); await fetchDiary(); } - restore({ - required int diaryId, - required int mealId, - required int portionId, - }) async { - await diaryRepository.restore( - diaryId: diaryId, - mealId: mealId, - portionId: portionId, - ); + restore({required int diaryId, required int mealId, required int portionId}) async { + await diaryRepository.restore(diaryId: diaryId, mealId: mealId, portionId: portionId); await fetchDiary(); } - deleteProductFromMeal({ - required int diaryId, - required int mealId, - required int portionId, - }) async { - await diaryRepository.deleteProductFromMeal( - diaryId: diaryId, - mealId: mealId, - portionId: portionId, - ); + deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async { + await diaryRepository.deleteProductFromMeal(diaryId: diaryId, mealId: mealId, portionId: portionId); await fetchDiary(); } @@ -182,27 +139,13 @@ class DiaryCubit extends Cubit { return await glossaryRepository.getGlossaryListByQuery(query); } - uploadPhotos({ - required List files, - required int diaryId, - }) async { - await diaryRepository.uploadPhotos( - files: await ImageCompressUtility.compressFilesList(files), - diaryId: diaryId, - mealId: selectedMeal, - ); + uploadPhotos({required List files, required int diaryId}) async { + await diaryRepository.uploadPhotos(files: await ImageCompressUtility.compressFilesList(files), diaryId: diaryId, mealId: selectedMeal); await fetchDiary(); } - deletePhoto({ - required DiaryPhotoModel photo, - required int diaryId, - }) async { - await diaryRepository.deletePhoto( - photo: photo, - diaryId: diaryId, - mealId: selectedMeal, - ); + deletePhoto({required DiaryPhotoModel photo, required int diaryId}) async { + await diaryRepository.deletePhoto(photo: photo, diaryId: diaryId, mealId: selectedMeal); await fetchDiary(); } @@ -214,39 +157,37 @@ class DiaryCubit extends Cubit { required double carbohydrates, }) async { try { - return await diaryRepository.sendProductToModeration( - name: name, - energy: energy, - proteins: proteins, - fats: fats, - carbohydrates: carbohydrates, - ); + return await diaryRepository.sendProductToModeration(name: name, energy: energy, proteins: proteins, fats: fats, carbohydrates: carbohydrates); } catch (e, stackTrace) { log(e.toString(), stackTrace: stackTrace); } return null; } - fillTime({ - required int diaryId, - required String time, - }) async { - await diaryRepository.fillTime( - diaryId: diaryId, - mealId: selectedMeal, - time: time, - ); + fillTime({required int diaryId, required String time}) async { + await diaryRepository.fillTime(diaryId: diaryId, mealId: selectedMeal, time: time); await fetchDiary(); } - fillSatiety({ - required int diaryId, - required int satiety, - }) async { - await diaryRepository.fillSatiety( - diaryId: diaryId, - mealId: selectedMeal, - satiety: satiety, - ); + fillWakeup({required int diaryId, required String time}) async { + await diaryRepository.fillWakeup(diaryId: diaryId, time: time); + await fetchDiary(); + } + + fillRetire({required int diaryId, required String time}) async { + await diaryRepository.fillRetire(diaryId: diaryId, time: time); + await fetchDiary(); + } + + fillSatiety({required int diaryId, required int satiety}) async { + await diaryRepository.fillSatiety(diaryId: diaryId, mealId: selectedMeal, satiety: satiety); + } + + fillMood({required int diaryId, required int mood}) async { + await diaryRepository.fillMood(diaryId: diaryId, mealId: selectedMeal, mood: mood); + } + + fillComment(String comment) { + emit((state as DiaryStateLoaded).copyWith(diary: (state as DiaryStateLoaded).diary.copyWith(comment: comment))); } } diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart index 8c5e784f..7f230e2b 100644 --- a/lib/features/diary/presentation/pages/diary_screen.dart +++ b/lib/features/diary/presentation/pages/diary_screen.dart @@ -7,8 +7,10 @@ import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/calendar_widget.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/calories_chart_widget.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/comment_for_consultant_widget.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/finish_day_button_widget.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/meal_widget.dart'; +import 'package:eatvio_app/features/diary/presentation/widgets/wakeup_and_retire_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -46,8 +48,12 @@ class _DiaryScreenState extends State { children: [ CalendarWidget(), if (state is DiaryStateLoading) const Loader(), + if (state is DiaryStateLoaded) WakeupAndRetireWidget(), + const SizedBox(height: 12), if (state is DiaryStateLoaded) MealWidget(), - CaloriesChartWidget(), + if (state is DiaryStateLoaded) CaloriesChartWidget(), + const SizedBox(height: 12), + if (state is DiaryStateLoaded) CommentForConsultantWidget(), if (state is DiaryStateLoaded && state.diary.closed == 0) Padding( padding: const EdgeInsets.only(top: 12), diff --git a/lib/features/diary/presentation/widgets/comment_for_consultant_widget.dart b/lib/features/diary/presentation/widgets/comment_for_consultant_widget.dart new file mode 100644 index 00000000..0e276f72 --- /dev/null +++ b/lib/features/diary/presentation/widgets/comment_for_consultant_widget.dart @@ -0,0 +1,44 @@ +import 'package:easy_debounce/easy_debounce.dart'; +import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CommentForConsultantWidget extends StatefulWidget { + const CommentForConsultantWidget({super.key}); + + @override + State createState() => _CommentForConsultantWidgetState(); +} + +class _CommentForConsultantWidgetState 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( + 'Дополнительная информация для консультанта', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF323F4B), height: 1), + ), + const SizedBox(height: 12), + CustomTextFormField( + onChanged: (value) { + EasyDebounce.debounce('client-typing', Duration(seconds: 3), () => context.read().fillComment(value)); + }, + hintText: 'Укажите дополнительную информацию', + minLines: 4, + maxLines: 4, + ), + ], + ), + ), + ); + } +} 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 3ed14e27..651f4a66 100644 --- a/lib/features/diary/presentation/widgets/finish_day_button_widget.dart +++ b/lib/features/diary/presentation/widgets/finish_day_button_widget.dart @@ -7,10 +7,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class FinishDayButtonWidget extends StatefulWidget { final DiaryDayModel diaryDay; - const FinishDayButtonWidget({ - super.key, - required this.diaryDay, - }); + const FinishDayButtonWidget({super.key, required this.diaryDay}); @override State createState() => _FinishDayButtonWidgetState(); @@ -20,30 +17,22 @@ class _FinishDayButtonWidgetState extends State { @override Widget build(BuildContext context) { return InkWell( - onTap: () => context.read().finishDiaryDay( + onTap: + () => context.read().finishDiaryDay( diaryId: widget.diaryDay.id, wakeUp: widget.diaryDay.wakeup.toString(), retire: widget.diaryDay.retire.toString(), + comment: widget.diaryDay.comment.isEmpty ? null : widget.diaryDay.comment, ), 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), - ), + 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, - ), - ), + child: Text('Закончить день', style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600)), ), ), ); diff --git a/lib/features/diary/presentation/widgets/meal_widget.dart b/lib/features/diary/presentation/widgets/meal_widget.dart index 43f7976c..d1c1f59f 100644 --- a/lib/features/diary/presentation/widgets/meal_widget.dart +++ b/lib/features/diary/presentation/widgets/meal_widget.dart @@ -1,5 +1,6 @@ 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/mood_rating_widget.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/photo_gallery_widget.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/products_list_widget.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/satiety_rating_widget.dart'; @@ -53,6 +54,8 @@ class _MealWidgetState extends State { PhotoGalleryWidget(), const SizedBox(height: 12), SatietyRatingWidget(), + const SizedBox(height: 12), + MoodRatingWidget(), ], ), ), diff --git a/lib/features/diary/presentation/widgets/mood_rating_widget.dart b/lib/features/diary/presentation/widgets/mood_rating_widget.dart new file mode 100644 index 00000000..9d8c6dab --- /dev/null +++ b/lib/features/diary/presentation/widgets/mood_rating_widget.dart @@ -0,0 +1,199 @@ +import 'package:easy_debounce/easy_debounce.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class MoodRatingWidget extends StatefulWidget { + @override + State createState() => _MoodRatingWidgetState(); +} + +class _MoodRatingWidgetState extends State { + late double currentSelectedRating; + bool isFirstFetch = true; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (BuildContext context, DiaryState state) { + if (state is DiaryStateLoaded) { + if (isFirstFetch) { + currentSelectedRating = state.diary.meals[state.selectedMeal - 1].mood.toDouble() + 1; + isFirstFetch = false; + } + return Padding( + padding: const EdgeInsets.only(top: 24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Ваше настроение после приёма пищи', + style: TextStyle( + fontSize: 16, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SvgPicture.asset( + 'assets/icons/mood_1.svg', + height: 32, + width: 32, + ), + RatingBar.builder( + initialRating: state.diary.meals[state.selectedMeal - 1].mood.toDouble() + 1, + itemCount: 6, + itemSize: MediaQuery.of(context).size.width / 6 - (124 / 6), + updateOnDrag: true, + itemBuilder: (context, index) { + switch (index) { + case 0: + return Container( + width: MediaQuery.of(context).size.width / 6, + height: 24, + margin: currentSelectedRating == 1 ? EdgeInsets.symmetric(horizontal: 2) : null, + decoration: BoxDecoration( + color: Color(0xFFC82222), + border: currentSelectedRating == 1 + ? Border.all( + width: 2, + color: Color(0xFFAA3333), + strokeAlign: BorderSide.strokeAlignOutside, + ) + : null, + borderRadius: currentSelectedRating == 1 + ? BorderRadius.horizontal( + left: Radius.circular(8), + right: Radius.circular(2), + ) + : BorderRadius.horizontal( + left: Radius.circular(8), + ), + ), + ); + case 1: + return Container( + width: MediaQuery.of(context).size.width / 6, + height: 24, + margin: currentSelectedRating == 2 ? EdgeInsets.symmetric(horizontal: 2) : null, + decoration: BoxDecoration( + color: Color(0xFFFF3E3E), + border: currentSelectedRating == 2 + ? Border.all( + width: 2, + color: Color(0xFF550000), + strokeAlign: BorderSide.strokeAlignOutside, + ) + : null, + borderRadius: currentSelectedRating == 2 ? BorderRadius.circular(2) : null, + ), + ); + case 2: + return Container( + width: MediaQuery.of(context).size.width / 6, + height: 24, + margin: currentSelectedRating == 3 ? EdgeInsets.symmetric(horizontal: 2) : null, + decoration: BoxDecoration( + color: Color(0xFFF8C926), + border: currentSelectedRating == 3 + ? Border.all( + width: 2, + color: Color(0xFF8C5D00), + strokeAlign: BorderSide.strokeAlignOutside, + ) + : null, + borderRadius: currentSelectedRating == 3 ? BorderRadius.circular(2) : null, + ), + ); + case 3: + return Container( + width: MediaQuery.of(context).size.width / 6, + height: 24, + margin: currentSelectedRating == 4 ? EdgeInsets.symmetric(horizontal: 2) : null, + decoration: BoxDecoration( + color: Color(0xFFFFF3BF), + border: currentSelectedRating == 4 + ? Border.all( + width: 2, + color: Color(0xFFB5911B), + strokeAlign: BorderSide.strokeAlignOutside, + ) + : null, + borderRadius: currentSelectedRating == 4 ? BorderRadius.circular(2) : null, + ), + ); + case 4: + return Container( + width: MediaQuery.of(context).size.width / 6, + height: 24, + margin: currentSelectedRating == 5 ? EdgeInsets.symmetric(horizontal: 2) : null, + decoration: BoxDecoration( + color: Color(0xFF62E833), + border: currentSelectedRating == 5 + ? Border.all( + width: 2, + color: Color(0xFF276918), + strokeAlign: BorderSide.strokeAlignOutside, + ) + : null, + borderRadius: currentSelectedRating == 5 ? BorderRadius.circular(2) : null, + ), + ); + default: + return Container( + width: MediaQuery.of(context).size.width / 6, + height: 24, + margin: currentSelectedRating == 6 ? EdgeInsets.symmetric(horizontal: 2) : null, + decoration: BoxDecoration( + color: Color(0xFF4DCE20), + border: currentSelectedRating == 6 + ? Border.all( + width: 2, + color: Color(0xFF184D12), + strokeAlign: BorderSide.strokeAlignOutside, + ) + : null, + borderRadius: currentSelectedRating == 6 + ? BorderRadius.horizontal( + left: Radius.circular(2), + right: Radius.circular(8), + ) + : BorderRadius.horizontal( + right: Radius.circular(8), + ), + ), + ); + } + }, + onRatingUpdate: (value) { + setState(() { + currentSelectedRating = value; + }); + EasyDebounce.debounce( + 'change-satiety', + Duration(milliseconds: 250), + () { + context.read().fillMood(diaryId: state.diary.id, mood: currentSelectedRating.toInt() - 1); + }, + ); + }, + ), + SvgPicture.asset( + 'assets/icons/mood_2.svg', + height: 32, + width: 32, + ), + ], + ), + ], + ), + ); + } + 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 89404fcc..28ab9996 100644 --- a/lib/features/diary/presentation/widgets/satiety_rating_widget.dart +++ b/lib/features/diary/presentation/widgets/satiety_rating_widget.dart @@ -21,7 +21,7 @@ class _SatietyRatingWidgetState extends State { builder: (BuildContext context, DiaryState state) { if (state is DiaryStateLoaded) { if (isFirstFetch) { - currentSelectedRating = state.diary.meals[state.selectedMeal - 1].satiety.toDouble(); + currentSelectedRating = state.diary.meals[state.selectedMeal - 1].satiety.toDouble() + 1; isFirstFetch = false; } return Padding( @@ -44,7 +44,7 @@ class _SatietyRatingWidgetState extends State { width: 32, ), RatingBar.builder( - initialRating: state.diary.meals[state.selectedMeal - 1].satiety.toDouble(), + initialRating: state.diary.meals[state.selectedMeal - 1].satiety.toDouble() + 1, itemCount: 6, itemSize: MediaQuery.of(context).size.width / 6 - (124 / 6), updateOnDrag: true, @@ -176,7 +176,7 @@ class _SatietyRatingWidgetState extends State { 'change-satiety', Duration(milliseconds: 250), () { - context.read().fillSatiety(diaryId: state.diary.id, satiety: currentSelectedRating.toInt()); + context.read().fillSatiety(diaryId: state.diary.id, satiety: currentSelectedRating.toInt() - 1); }, ); }, diff --git a/lib/features/diary/presentation/widgets/wakeup_and_retire_widget.dart b/lib/features/diary/presentation/widgets/wakeup_and_retire_widget.dart new file mode 100644 index 00000000..65c3cbe8 --- /dev/null +++ b/lib/features/diary/presentation/widgets/wakeup_and_retire_widget.dart @@ -0,0 +1,229 @@ +import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; +import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../../../../core/theme/theme_helper.dart'; + +class WakeupAndRetireWidget extends StatefulWidget { + const WakeupAndRetireWidget({super.key}); + + @override + State createState() => _WakeupAndRetireWidgetState(); +} + +class _WakeupAndRetireWidgetState extends State { + late final DiaryCubit diaryCubit; + DateTime wakeup = DateTime.now().copyWith(hour: 6, minute: 30); + DateTime retire = DateTime.now().copyWith(hour: 22, minute: 30); + + @override + void initState() { + super.initState(); + diaryCubit = context.read(); + } + + showTimePicker(int diaryId, bool isWakeup) { + 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) { + if (isWakeup) { + wakeup = newTime; + } else { + retire = newTime; + } + }, + ), + ), + InkWell( + onTap: () { + Navigator.of(context).pop(); + if (isWakeup) { + diaryCubit.fillWakeup( + diaryId: diaryId, + time: + '${wakeup.hour.toString().padLeft(2, '0')}:${wakeup.minute.toString().padLeft(2, '0')}:00', + ); + } else { + diaryCubit.fillRetire( + diaryId: diaryId, + time: + '${retire.hour.toString().padLeft(2, '0')}:${retire.minute.toString().padLeft(2, '0')}:00', + ); + } + }, + 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 BlocBuilder( + builder: (context, state) { + if (state is DiaryStateLoaded) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Row( + children: [ + Expanded( + child: Material( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 12, + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset('assets/icons/wakeup.svg'), + const SizedBox(width: 8), + Text( + 'Подъём', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + height: 1, + ), + ), + ], + ), + const SizedBox(height: 8), + InkWell( + onTap: () => showTimePicker(state.diary.id, true), + 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( + state.diary.wakeup.toString().substring( + 0, + state.diary.wakeup.toString().length - 3, + ), + style: TextStyle( + color: Color(0xFF6D6F71), + fontSize: 17, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Material( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 12, + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset('assets/icons/retire.svg'), + const SizedBox(width: 8), + Text( + 'Уход ко сну', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + height: 1, + ), + ), + ], + ), + const SizedBox(height: 8), + InkWell( + onTap: () => showTimePicker(state.diary.id, false), + 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( + state.diary.retire.toString().substring( + 0, + state.diary.retire.toString().length - 3, + ), + style: TextStyle( + color: Color(0xFF6D6F71), + fontSize: 17, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ], + ), + ); + } else { + return SizedBox.shrink(); + } + }, + ); + } +} diff --git a/lib/features/diary/repositories/diary_repository.dart b/lib/features/diary/repositories/diary_repository.dart index 754e1321..ad7a0448 100644 --- a/lib/features/diary/repositories/diary_repository.dart +++ b/lib/features/diary/repositories/diary_repository.dart @@ -158,12 +158,14 @@ class DiaryRepository { required int diaryId, required String wakeUp, required String retire, + String? comment, }) async { return await diaryService.finishDiaryDay( diary: diaryId, finishDiaryDayModel: FinishDiaryDayModel( wakeUp: wakeUp, retire: retire, + comment: comment ), ); } @@ -202,6 +204,22 @@ class DiaryRepository { } } + Future fillMood({ + required int diaryId, + required int mealId, + required int mood, + }) async { + try { + return await diaryService.fillMood( + diary: diaryId, + typeNo: mealId, + mood: mood, + ); + } catch (e) { + rethrow; + } + } + Future fillTime({ required int diaryId, required int mealId, @@ -217,4 +235,32 @@ class DiaryRepository { rethrow; } } + + Future fillWakeup({ + required int diaryId, + required String time, + }) async { + try { + return await diaryService.fillWakeup( + diary: diaryId, + wakeUp: time, + ); + } catch (e) { + rethrow; + } + } + + Future fillRetire({ + required int diaryId, + required String time, + }) async { + try { + return await diaryService.fillRetire( + diary: diaryId, + retire: time, + ); + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/diary/services/diary_service.dart b/lib/features/diary/services/diary_service.dart index 6a519b71..8506e466 100644 --- a/lib/features/diary/services/diary_service.dart +++ b/lib/features/diary/services/diary_service.dart @@ -91,7 +91,7 @@ abstract class DiaryService { @POST('/api/diary/{diary}/finishDay') Future finishDiaryDay({ @Path('diary') required int diary, - @Body() required FinishDiaryDayModel finishDiaryDayModel, + @Body(nullToAbsent: true) required FinishDiaryDayModel finishDiaryDayModel, }); @POST('/api/diary/{diary}/meals/{type_no}/satiety/fill') @@ -101,6 +101,13 @@ abstract class DiaryService { @Field() required int satiety, }); + @POST('/api/diary/{diary}/meals/{type_no}/mood/fill') + Future fillMood({ + @Path('diary') required int diary, + @Path('type_no') required int typeNo, + @Field() required int mood, + }); + @POST('/api/diary/{diary}/meals/{type_no}/time/fill') Future fillTime({ @Path('diary') required int diary, @@ -108,6 +115,18 @@ abstract class DiaryService { @Field() required String time, }); + @POST('/api/diary/{diary}/wakeUp/fill') + Future fillWakeup({ + @Path('diary') required int diary, + @Field() required String wakeUp, + }); + + @POST('/api/diary/{diary}/retire/fill') + Future fillRetire({ + @Path('diary') required int diary, + @Field() required String retire, + }); + @POST('/api/diary/product_nutritional_values/sendToModeration') Future sendProductToModeration({ @Body() required Map data, diff --git a/lib/features/diary/services/diary_service.g.dart b/lib/features/diary/services/diary_service.g.dart index c369088f..16bd7dbe 100644 --- a/lib/features/diary/services/diary_service.g.dart +++ b/lib/features/diary/services/diary_service.g.dart @@ -366,6 +366,29 @@ class _DiaryService implements DiaryService { await _dio.fetch(_options); } + @override + Future fillMood({ + required int diary, + required int typeNo, + required int mood, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = {'mood': mood}; + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/${diary}/meals/${typeNo}/mood/fill', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + await _dio.fetch(_options); + } + @override Future fillTime({ required int diary, @@ -389,6 +412,44 @@ class _DiaryService implements DiaryService { await _dio.fetch(_options); } + @override + Future fillWakeup({required int diary, required String wakeUp}) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = {'wakeUp': wakeUp}; + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/${diary}/wakeUp/fill', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + await _dio.fetch(_options); + } + + @override + Future fillRetire({required int diary, required String retire}) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = {'retire': retire}; + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/${diary}/retire/fill', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + await _dio.fetch(_options); + } + @override Future sendProductToModeration({ required Map data, diff --git a/pubspec.yaml b/pubspec.yaml index a6ab85d4..fc1addf4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -73,6 +73,7 @@ dev_dependencies: json_serializable: ^6.9.4 retrofit_generator: ^9.1.9 mockito: ^5.4.5 + flutter_lints: ^5.0.0 flutter_test: sdk: flutter