diff --git a/lib/core/utils/date_time_utils.dart b/lib/core/utils/date_time_utils.dart index 701fa80e..9b3085c1 100644 --- a/lib/core/utils/date_time_utils.dart +++ b/lib/core/utils/date_time_utils.dart @@ -46,6 +46,17 @@ extension DateTimeExtension on DateTime { final now = DateTime.now(); return now.year == localDate.year && now.month == localDate.month && now.day == localDate.day; } + + String toISO8601WithTimeZoneOffset() { + final formatter = DateFormat("yyyy-MM-ddTHH:mm:ss"); + final formatted = formatter.format(this); + final offset = timeZoneOffset; + final hours = offset.inHours.abs().toString().padLeft(2, '0'); + final minutes = (offset.inMinutes.abs() % 60).toString().padLeft(2, '0'); + final sign = offset.isNegative ? '-' : '+'; + + return "$formatted$sign$hours:$minutes"; + } } String calculateDifference(DateTime dateTime, [String? format]) { diff --git a/lib/features/diary/entities/diary_day_entity.dart b/lib/features/diary/entities/diary_day_entity.dart new file mode 100644 index 00000000..65f45570 --- /dev/null +++ b/lib/features/diary/entities/diary_day_entity.dart @@ -0,0 +1,22 @@ +import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'diary_day_entity.freezed.dart'; + +@freezed +abstract class DiaryDayEntity with _$DiaryDayEntity { + factory DiaryDayEntity({ + @JsonKey(name: "id") required int id, + @JsonKey(name: "date") required String date, + @JsonKey(name: "wakeup") String? wakeup, + @JsonKey(name: "retire") String? retire, + @JsonKey(name: "closed") required int closed, + @JsonKey(name: "comment") required String comment, + @JsonKey(name: "state") required TypeOfDiaryModel typeOfDiary, + @JsonKey(name: "meals") required List meals, + @JsonKey(name: "chosen_plan_id") required int? chosenPlanId, + @JsonKey(name: "day_of_chosen_diet") required int? dayOfChosenDiet, + @JsonKey(name: "chosen_diet", fromJson: parseDiet) required DiaryDietModel? diet, + }) = _DiaryDayEntity; + +} diff --git a/lib/features/diary/entities/diary_future_diet_day_entity.dart b/lib/features/diary/entities/diary_future_diet_day_entity.dart new file mode 100644 index 00000000..4247cecf --- /dev/null +++ b/lib/features/diary/entities/diary_future_diet_day_entity.dart @@ -0,0 +1,18 @@ +import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'diary_future_diet_day_entity.freezed.dart'; + + + +@freezed +abstract class DiaryFutureDietDayEntity with _$DiaryFutureDietDayEntity { + const factory DiaryFutureDietDayEntity({ + required String date, + required TypeOfDiaryModel typeOfDiary, + required List meals, + required DiaryDietModel? diet, + required int? dayOfChosenDiet, + }) = _DiaryFutureDietDayEntity; + +} diff --git a/lib/features/diary/models/create_day_model.dart b/lib/features/diary/models/create_day_model.dart new file mode 100644 index 00000000..9d3eabbd --- /dev/null +++ b/lib/features/diary/models/create_day_model.dart @@ -0,0 +1,11 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'create_day_model.freezed.dart'; +part 'create_day_model.g.dart'; + +@freezed +abstract class CreateDayModel with _$CreateDayModel { + factory CreateDayModel({required String day}) = _CreateDayModel; + + factory CreateDayModel.fromJson(Map json) => _$CreateDayModelFromJson(json); +} diff --git a/lib/features/diary/models/diary_day_model.dart b/lib/features/diary/models/diary_day_model.dart index e1ad4572..c602111f 100644 --- a/lib/features/diary/models/diary_day_model.dart +++ b/lib/features/diary/models/diary_day_model.dart @@ -1,4 +1,6 @@ import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; +import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart'; +import 'package:eatvio_app/features/diary/entities/diary_future_diet_day_entity.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'diary_day_model.freezed.dart'; @@ -30,7 +32,7 @@ abstract class DiaryDayWrapperModel with _$DiaryDayWrapperModel { @freezed abstract class DiaryDayModel with _$DiaryDayModel { const factory DiaryDayModel({ - @JsonKey(name: "id") required int id, + @JsonKey(name: "id") required int? id, @JsonKey(name: "date") required String date, @JsonKey(name: "wakeup") String? wakeup, @JsonKey(name: "retire") String? retire, @@ -40,26 +42,36 @@ abstract class DiaryDayModel with _$DiaryDayModel { @JsonKey(name: "meals") required List meals, @JsonKey(name: "chosen_plan_id") required int? chosenPlanId, @JsonKey(name: "day_of_chosen_diet") required int? dayOfChosenDiet, - @JsonKey(name: "chosen_diet", fromJson: _parseDiet) required DiaryDietModel? diet, + @JsonKey(name: "chosen_diet", fromJson: parseDiet) required DiaryDietModel? diet, }) = _DiaryDayModel; - + const DiaryDayModel._(); factory DiaryDayModel.fromJson(Map json) => _$DiaryDayModelFromJson(json); + + DiaryDayEntity? toEntity() { + if (id == null) { + return null; + } + return DiaryDayEntity( + id: id!, + date: date, + closed: closed, + comment: comment, + typeOfDiary: typeOfDiary, + meals: meals, + chosenPlanId: chosenPlanId, + dayOfChosenDiet: dayOfChosenDiet, + wakeup: wakeup, + retire: retire, + diet: diet, + ); + } + + DiaryFutureDietDayEntity toDiaryFutureDayEntity() { + return DiaryFutureDietDayEntity(date: date, typeOfDiary: typeOfDiary, meals: meals, dayOfChosenDiet: dayOfChosenDiet, diet: diet); + } } -@freezed -abstract class DiaryFutureDietDayModel with _$DiaryFutureDietDayModel { - const factory DiaryFutureDietDayModel({ - @JsonKey(name: "date") required String date, - @JsonKey(name: "state", fromJson: _parseDayTypeForFutureDay) required TypeOfDiaryModel typeOfDiary, - @JsonKey(name: "meals") required List meals, - @JsonKey(name: "chosen_diet", fromJson: _parseDiet) required DiaryDietModel? diet, - @JsonKey(name: "day_of_chosen_diet") required int? dayOfChosenDiet, - }) = _DiaryFutureDietDayModel; - - factory DiaryFutureDietDayModel.fromJson(Map json) => _$DiaryFutureDietDayModelFromJson(json); -} - -DiaryDietModel? _parseDiet(dynamic json) { +DiaryDietModel? parseDiet(dynamic json) { if (json == null) return null; return DiaryDietModel.fromJson(json['diet']); } @@ -71,10 +83,6 @@ abstract class DiaryDietModel with _$DiaryDietModel { factory DiaryDietModel.fromJson(Map json) => _$DiaryDietModelFromJson(json); } -TypeOfDiaryModel _parseDayTypeForFutureDay(dynamic json) { - return TypeOfDiaryModel(hasChosenDiet: json['has_chosen_diet'] as bool, hasChosenPlan: json['has_chosen_plan'] as bool, diaryState: DiaryStateEnum.init); -} - @freezed abstract class MealModel with _$MealModel { const factory MealModel({ @@ -141,7 +149,7 @@ abstract class TypeOfDiaryModel with _$TypeOfDiaryModel { const factory TypeOfDiaryModel({ @JsonKey(name: "has_chosen_diet") required bool hasChosenDiet, @JsonKey(name: "has_chosen_plan") required bool hasChosenPlan, - @JsonKey(name: "last_state") required DiaryStateEnum diaryState, + @JsonKey(name: "last_state") required DiaryStateEnum? diaryState, }) = _TypeOfDiaryModel; factory TypeOfDiaryModel.fromJson(Map json) => _$TypeOfDiaryModelFromJson(json); diff --git a/lib/features/diary/models/get_day_model.dart b/lib/features/diary/models/get_day_model.dart new file mode 100644 index 00000000..bf9d285d --- /dev/null +++ b/lib/features/diary/models/get_day_model.dart @@ -0,0 +1,11 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'get_day_model.freezed.dart'; +part 'get_day_model.g.dart'; + +@freezed +abstract class GetDayModel with _$GetDayModel { + factory GetDayModel({required String day}) = _GetDayModel; + + factory GetDayModel.fromJson(Map json) => _$GetDayModelFromJson(json); +} diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart index 6e6f8891..53beebe0 100644 --- a/lib/features/diary/presentation/bloc/diary_cubit.dart +++ b/lib/features/diary/presentation/bloc/diary_cubit.dart @@ -1,10 +1,13 @@ import 'dart:developer'; import 'dart:io'; +import 'package:dio/dio.dart'; import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart'; import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; +import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart'; +import 'package:eatvio_app/features/diary/entities/diary_future_diet_day_entity.dart'; import 'package:eatvio_app/features/diary/models/consultant_review_model.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/nutrition_model.dart'; @@ -20,7 +23,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class DiaryCubit extends Cubit { late final DateTime today; late DateTime selectedDay; - late int selectedMeal; + int selectedMeal = 1; final DiaryRepository diaryRepository; final GlossaryRepository glossaryRepository; final ProfileRepository profileRepository; @@ -39,19 +42,79 @@ class DiaryCubit extends Cubit { today = DateTime.now(); selectedDay = today; diaryRepository.forceFetchStream.listen((_) { - fetchDiary(true, diaryId); + fetchDiaryV2(needLoading: true, diaryId: diaryId); }); } String comment = ''; - Future setSelectedDay(DateTime day, {bool? forDietOnly}) async { + Future setSelectedDay(DateTime day) async { + final prevDay = selectedDay; selectedDay = day; - if (forDietOnly ?? false) { - final result = await fetchDiaryForDietOnly(); - return result; + final canSelect = await fetchDiaryV2(needLoading: true); + + if (!canSelect) { + selectedDay = prevDay; + } + + return canSelect; + } + + Future fetchDiaryV2({bool needLoading = false, int? diaryId}) async { + final currentState = state; + if (needLoading) { + emit(const DiaryState.loading()); + } + if (diaryId != null) { + final diaryDayById = await diaryRepository.getDiaryDay(diaryId); + final diaryEntity = diaryDayById.toEntity(); + if (diaryEntity != null) { + setDiary(diaryDay: diaryEntity, diaryId: diaryId); + return true; + } + } + final diaryDay = await getDiary(); + if (diaryDay == null) { + emit(const DiaryState.error(errorMessage: 'Diary not found')); + return true; + } + + final diaryEntity = diaryDay.toEntity(); + if (diaryEntity != null) { + setDiary(diaryDay: diaryEntity, diaryId: diaryId); + return true; + } + if (diaryDay.typeOfDiary.hasChosenDiet == true && diaryDay.typeOfDiary.diaryState == null) { + setDiaryForDietOnly(diaryDay.toDiaryFutureDayEntity()); + return true; + } + emit(currentState); + return false; + } + + Future updateCurrentDiary() async { + try { + final diaryDay = await diaryRepository.getDiaryDayV2(day: selectedDay.toISO8601WithTimeZoneOffset()); + final diaryEntity = diaryDay.toEntity(); + if (diaryEntity != null) { + setDiary(diaryDay: diaryEntity, diaryId: diaryId); + } + } catch (e) { + log(e.toString()); + } + } + + Future getDiary() async { + try { + await diaryRepository.createDiaryDay(day: selectedDay.toISO8601WithTimeZoneOffset()); + final diaryDay = await diaryRepository.getDiaryDayV2(day: selectedDay.toISO8601WithTimeZoneOffset()); + return diaryDay; + } on DioException catch (e) { + if (e.response?.statusCode == 422) { + final diaryDay = await diaryRepository.getDiaryDayV2(day: selectedDay.toISO8601WithTimeZoneOffset()); + return diaryDay; + } } - await fetchDiary(true); return null; } @@ -65,9 +128,7 @@ class DiaryCubit extends Cubit { } } - Future fetchDiaryForDietOnly() async { - final stringDate = selectedDay.format('yyyy-MM-dd'); - final diaryDay = await diaryRepository.getDiaryByDateForDietOnly(stringDate); + Future setDiaryForDietOnly(DiaryFutureDietDayEntity diaryDay) async { if (!diaryDay.typeOfDiary.hasChosenDiet) { return false; } @@ -146,22 +207,14 @@ class DiaryCubit extends Cubit { return false; } - Future fetchDiary([bool needLoading = false, int? diaryId, int? currentDay]) async { + Future setDiary({int? diaryId, required DiaryDayEntity diaryDay}) async { this.diaryId ??= diaryId; final isConsultantView = this.diaryId != null; - if (needLoading) { - emit(const DiaryState.loading()); - selectedMeal = 1; - } try { - final DiaryDayModel diaryDay; bool isMoodEnabled = false; bool isWakeUpAndRetireTimeEnabled = false; if (diaryId != null) { - diaryDay = await diaryRepository.getDiaryDay(diaryId); } else { - final stringDate = selectedDay.format('yyyy-MM-dd'); - diaryDay = await diaryRepository.getDiaryByDate(stringDate); final profileSettings = await settingsRepository.getProfileSettings(); isMoodEnabled = profileSettings.isMoodFillable; isWakeUpAndRetireTimeEnabled = profileSettings.isBedtimeFillable; @@ -214,7 +267,7 @@ class DiaryCubit extends Cubit { Future finishDiaryDay({required int diaryId, required String wakeUp, required String retire}) async { await diaryRepository.finishDiaryDay(diaryId: diaryId, wakeUp: wakeUp, retire: retire, comment: comment.isEmpty ? null : comment); - await fetchDiary(); + await updateCurrentDiary(); } Future getNutrition(int index) async { @@ -238,7 +291,7 @@ class DiaryCubit extends Cubit { quantity: quantity, productNutritionalValueId: productNutritionalValueId, ); - await fetchDiary(); + await updateCurrentDiary(); } Future updateProductInMeal({ @@ -272,27 +325,27 @@ class DiaryCubit extends Cubit { portionState: portionState, ); } - await fetchDiary(); + await updateCurrentDiary(); } Future markEaten({required int diaryId, required int mealId, required int portionId}) async { await diaryRepository.markEaten(diaryId: diaryId, mealId: mealId, portionId: portionId); - await fetchDiary(); + await updateCurrentDiary(); } Future unmarkEaten({required int diaryId, required int mealId, required int portionId}) async { await diaryRepository.unmarkEaten(diaryId: diaryId, mealId: mealId, portionId: portionId); - await fetchDiary(); + await updateCurrentDiary(); } Future restore({required int diaryId, required int mealId, required int portionId}) async { await diaryRepository.restore(diaryId: diaryId, mealId: mealId, portionId: portionId); - await fetchDiary(); + await updateCurrentDiary(); } Future deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async { await diaryRepository.deleteProductFromMeal(diaryId: diaryId, mealId: mealId, portionId: portionId); - await fetchDiary(); + await updateCurrentDiary(); } Future> getGlossaryListByQuery(String query) async { @@ -301,12 +354,12 @@ class DiaryCubit extends Cubit { Future uploadPhotos({required List files, required int diaryId}) async { await diaryRepository.uploadPhotos(files: await ImageCompressUtility.compressFilesList(files), diaryId: diaryId, mealId: selectedMeal); - await fetchDiary(); + await updateCurrentDiary(); } Future deletePhoto({required DiaryPhotoModel photo, required int diaryId}) async { await diaryRepository.deletePhoto(photo: photo, diaryId: diaryId, mealId: selectedMeal); - await fetchDiary(); + await updateCurrentDiary(); } Future sendProductToModeration({ @@ -326,30 +379,32 @@ class DiaryCubit extends Cubit { Future fillTime({required int diaryId, required String time}) async { await diaryRepository.fillTime(diaryId: diaryId, mealId: selectedMeal, time: time); - await fetchDiary(); + await updateCurrentDiary(); } Future fillWakeup({required int diaryId, required String time}) async { await diaryRepository.fillWakeup(diaryId: diaryId, time: time); - await fetchDiary(); + await updateCurrentDiary(); } Future fillRetire({required int diaryId, required String time}) async { await diaryRepository.fillRetire(diaryId: diaryId, time: time); - await fetchDiary(); + await updateCurrentDiary(); } Future fillSatiety({required int diaryId, required int satiety}) async { await diaryRepository.fillSatiety(diaryId: diaryId, mealId: selectedMeal, satiety: satiety); - await fetchDiary(); + await updateCurrentDiary(); } Future fillMood({required int diaryId, required int mood}) async { await diaryRepository.fillMood(diaryId: diaryId, mealId: selectedMeal, mood: mood); - await fetchDiary(); + await updateCurrentDiary(); } void fillComment(String comment) { this.comment = comment; } } + +enum DiaryFetchingErrors { canNotCreateFutureDays, dayAlreadyExists } diff --git a/lib/features/diary/presentation/bloc/diary_state.dart b/lib/features/diary/presentation/bloc/diary_state.dart index 4779e2ea..02209371 100644 --- a/lib/features/diary/presentation/bloc/diary_state.dart +++ b/lib/features/diary/presentation/bloc/diary_state.dart @@ -1,5 +1,6 @@ +import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart'; +import 'package:eatvio_app/features/diary/entities/diary_future_diet_day_entity.dart'; import 'package:eatvio_app/features/diary/models/consultant_review_model.dart'; -import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/nutrition_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -11,12 +12,10 @@ abstract class DiaryState with _$DiaryState { const factory DiaryState.loading() = DiaryStateLoading; - const factory DiaryState.error({ - required String errorMessage, - }) = DiaryStateError; + const factory DiaryState.error({required String errorMessage}) = DiaryStateError; const factory DiaryState.loaded({ - required DiaryDayModel diary, + required DiaryDayEntity diary, required NutritionDataModel nutrition, required DateTime selectedDay, required int selectedMeal, @@ -32,9 +31,6 @@ abstract class DiaryState with _$DiaryState { @Default(false) bool isLoadingAddingProductInMeal, }) = DiaryStateLoaded; - const factory DiaryState.loadedTomorrowWithDiet({ - required DiaryFutureDietDayModel diary, - required DateTime selectedDay, - required int selectedMeal, - }) = DiaryStateLoadedTomorrowWithDiet; + const factory DiaryState.loadedTomorrowWithDiet({required DiaryFutureDietDayEntity diary, required DateTime selectedDay, required int selectedMeal}) = + DiaryStateLoadedTomorrowWithDiet; } diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart index fb1987ef..ae6f9e10 100644 --- a/lib/features/diary/presentation/pages/diary_screen.dart +++ b/lib/features/diary/presentation/pages/diary_screen.dart @@ -35,19 +35,12 @@ class DiaryScreen extends StatelessWidget { class UserDiaryScreen extends StatelessWidget { static const path = '/user-diary-screen'; static const name = 'userDiaryScreen'; - const UserDiaryScreen({ - super.key, - required this.diaryId, - required this.currentPlanDay, - }); + const UserDiaryScreen({super.key, required this.diaryId, required this.currentPlanDay}); final int diaryId; final int currentPlanDay; @override Widget build(BuildContext context) { - return DiaryScreenContent( - diaryId: diaryId, - currentPlanDay: currentPlanDay, - ); + return DiaryScreenContent(diaryId: diaryId, currentPlanDay: currentPlanDay); } } @@ -60,12 +53,8 @@ class DiaryScreenContent extends StatelessWidget { final s = S.of(context); return MultiBlocProvider( providers: [ - BlocProvider( - create: (BuildContext context) => sl()..fetchDiary(true, diaryId, currentPlanDay), - ), - BlocProvider( - create: (context) => sl(), - ), + BlocProvider(create: (BuildContext context) => sl()..fetchDiaryV2(needLoading: true, diaryId: diaryId)), + BlocProvider(create: (context) => sl()), ], child: BlocBuilder( builder: (BuildContext context, DiaryState state) { @@ -74,16 +63,14 @@ class DiaryScreenContent extends StatelessWidget { } if (state is DiaryStateError) { return Scaffold( - body: DataErrorWidget(onReload: () => context.read().fetchDiary(false, diaryId)), + body: DataErrorWidget(onReload: () => context.read().fetchDiaryV2(needLoading: false, diaryId: diaryId)), ); } return Scaffold( backgroundColor: Colors.grey.shade200, appBar: diaryId == null - ? AppBar( - toolbarHeight: 0, - ) + ? AppBar(toolbarHeight: 0) : AppBar( title: Text( state is DiaryStateLoaded ? DateTime.parse(state.diary.date).toLocal().format('d MMMM') : '', @@ -97,12 +84,7 @@ class DiaryScreenContent extends StatelessWidget { child: Column( children: [ if (diaryId == null) const CalendarWidget(), - if (state is DiaryStateLoading) ...[ - const SizedBox( - height: 60, - ), - const Loader() - ], + if (state is DiaryStateLoading) ...[const SizedBox(height: 60), const Loader()], if (state is DiaryStateLoaded) WakeupAndRetireWidget( isEnabled: state.diary.typeOfDiary.hasChosenPlan || state.isConsultantView || state.isWakeUpAndRetireEnabled, @@ -114,40 +96,31 @@ class DiaryScreenContent extends StatelessWidget { showMood: state.diary.typeOfDiary.hasChosenPlan || state.isConsultantView || state.isMoodEnabled, showAddButton: !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented, ), - if (state is DiaryStateLoadedTomorrowWithDiet) - const MealWidget( - showMood: false, - showAddButton: false, - ), + if (state is DiaryStateLoadedTomorrowWithDiet) const MealWidget(showMood: false, showAddButton: false), if (state is DiaryStateLoaded) const CaloriesChartWidget(), const SizedBox(height: 12), - Builder(builder: (context) { - if (state is DiaryStateLoaded && state.diary.typeOfDiary.hasChosenPlan) { - if (state.diary.comment.isNotEmpty) { - return CommentForConsultantWidget( - comment: state.diary.comment, - ); + Builder( + builder: (context) { + if (state is DiaryStateLoaded && state.diary.typeOfDiary.hasChosenPlan) { + if (state.diary.comment.isNotEmpty) { + return CommentForConsultantWidget(comment: state.diary.comment); + } + if (state.isDiaryCommented || state.isDiaryFinished) { + return const SizedBox.shrink(); + } + return const CommentForConsultantWidget(); } - if (state.isDiaryCommented || state.isDiaryFinished) { - return const SizedBox.shrink(); - } - return const CommentForConsultantWidget(); - } - return const SizedBox.shrink(); - }), + return const SizedBox.shrink(); + }, + ), if (state is DiaryStateLoaded && !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented) Padding( padding: const EdgeInsets.only(top: 12), - child: FinishDayButtonWidget( - diaryDay: state.diary, - ), + child: FinishDayButtonWidget(diaryDay: state.diary), ), if (state is DiaryStateLoaded && state.isConsultantView && !state.isDiaryCommented) ...[ const SizedBox(height: 16), - const MainBackgroundBouble( - padding: EdgeInsets.all(16), - child: DayCommentWidget(), - ) + const MainBackgroundBouble(padding: EdgeInsets.all(16), child: DayCommentWidget()), ], if (state is DiaryStateLoaded && state.consultantReview?.consultantComments.dayComment != null) ...[ const SizedBox(height: 12), @@ -155,10 +128,7 @@ class DiaryScreenContent extends StatelessWidget { padding: const EdgeInsets.all(16), child: Column( children: [ - Text( - s.dayComment, - style: TextStyles.textLSemibold.copyWith(color: appTheme.black), - ), + Text(s.dayComment, style: TextStyles.textLSemibold.copyWith(color: appTheme.black)), const SizedBox(height: 12), CommentContainer(comment: state.consultantReview?.consultantComments.dayComment ?? ''), ], diff --git a/lib/features/diary/presentation/widgets/add_product_modal.dart b/lib/features/diary/presentation/widgets/add_product_modal.dart index 9b6c2536..4b0a3806 100644 --- a/lib/features/diary/presentation/widgets/add_product_modal.dart +++ b/lib/features/diary/presentation/widgets/add_product_modal.dart @@ -25,26 +25,30 @@ class _AddProductModalState extends State { final quantityController = TextEditingController(); GlossaryModel? selectedGlossary; List glossariesList = []; - + late final String? _previousProductName; @override void initState() { super.initState(); if (widget.portion != null) { + _previousProductName = widget.portion!.productName; productNameController.text = widget.portion!.productName; quantityController.text = '${widget.portion!.quantity}'; - selectedGlossary = GlossaryModel( - id: widget.portion!.id, - name: widget.portion!.productName, - grInPortion: widget.portion!.portion == 'gr' ? 0 : 1, - energyOn100Gr: 0, - proteinsOn100Gr: 0, - fatsOn100Gr: 0, - carbohydratesOn100Gr: 0, - energyOnPortion: 0, - proteinsOnPortion: 0, - fatsOnPortion: 0, - carbohydratesOnPortion: 0, - ); + final productNutritionalValueId = widget.portion?.productNutritionalValueId; + if (productNutritionalValueId != null) { + selectedGlossary = GlossaryModel( + id: productNutritionalValueId, + name: widget.portion!.productName, + grInPortion: widget.portion!.portion == 'gr' ? 0 : 1, + energyOn100Gr: 0, + proteinsOn100Gr: 0, + fatsOn100Gr: 0, + carbohydratesOn100Gr: 0, + energyOnPortion: 0, + proteinsOnPortion: 0, + fatsOnPortion: 0, + carbohydratesOnPortion: 0, + ); + } } } @@ -191,7 +195,7 @@ class _AddProductModalState extends State { ); } Navigator.of(context).pop(); - if (selectedGlossary == null) { + if (selectedGlossary == null && _previousProductName != productNameController.text) { showModalBottomSheet( isScrollControlled: true, useRootNavigator: true, diff --git a/lib/features/diary/presentation/widgets/calendar_widget.dart b/lib/features/diary/presentation/widgets/calendar_widget.dart index b0e5c442..cce80a55 100644 --- a/lib/features/diary/presentation/widgets/calendar_widget.dart +++ b/lib/features/diary/presentation/widgets/calendar_widget.dart @@ -43,46 +43,24 @@ class _CalendarWidgetState extends State { locale: 'ru_RU', startingDayOfWeek: StartingDayOfWeek.monday, calendarFormat: _calendarFormat, - headerStyle: const HeaderStyle( - titleCentered: true, - formatButtonVisible: false, - leftChevronVisible: false, - rightChevronVisible: false, - ), + headerStyle: const HeaderStyle(titleCentered: true, formatButtonVisible: false, leftChevronVisible: false, rightChevronVisible: false), onDaySelected: (selectedDay, focusedDay) async { if (!isSameDay(_selectedDay, selectedDay)) { - final today = DateTime.now(); - if (selectedDay.isAfter(today)) { - if ((state is DiaryStateLoaded)) { - final result = await context.read().setSelectedDay(_selectedDay, forDietOnly: true); - if (result == false && context.mounted) { - showErrorDialog(context, title: s.want_to_see_tomorrow, description: s.can_not_see_next_day); - return; - } - setState(() { - _selectedDay = selectedDay; - _focusedDay = focusedDay; - }); - return; - } - if (state is DiaryStateLoadedTomorrowWithDiet) { - final result = await context.read().setSelectedDay(_selectedDay, forDietOnly: true); - if (result == false && context.mounted) { - showErrorDialog(context, title: s.want_to_see_tomorrow, description: s.can_not_see_next_day); - return; - } - setState(() { - _selectedDay = selectedDay; - _focusedDay = focusedDay; - }); - return; - } - } + final previousSelectedDay = _selectedDay; + final previousFocusedDay = _selectedDay; setState(() { _selectedDay = selectedDay; _focusedDay = focusedDay; }); - context.read().setSelectedDay(_selectedDay); + final canOpenNextDay = await context.read().setSelectedDay(selectedDay); + if (!canOpenNextDay && context.mounted) { + showErrorDialog(context, title: s.want_to_see_tomorrow, description: s.can_not_see_next_day); + setState(() { + _selectedDay = previousSelectedDay; + _focusedDay = previousFocusedDay; + }); + return; + } } }, selectedDayPredicate: (day) { @@ -101,26 +79,12 @@ class _CalendarWidgetState extends State { } }, calendarStyle: CalendarStyle( - weekendDecoration: const BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - ), - defaultDecoration: const BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - ), - selectedDecoration: BoxDecoration( - color: appTheme.blueA400, - shape: BoxShape.circle, - ), - todayTextStyle: const TextStyle( - color: Colors.black, - ), + weekendDecoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle), + defaultDecoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle), + selectedDecoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle), + todayTextStyle: const TextStyle(color: Colors.black), todayDecoration: BoxDecoration( - border: Border.all( - color: appTheme.blueA400, - width: 2, - ), + border: Border.all(color: appTheme.blueA400, width: 2), color: Colors.white, shape: BoxShape.circle, ), @@ -152,10 +116,7 @@ class _CalendarWidgetState extends State { 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, - ), + 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/finish_day_button_widget.dart b/lib/features/diary/presentation/widgets/finish_day_button_widget.dart index 042c418c..b3f170ec 100644 --- a/lib/features/diary/presentation/widgets/finish_day_button_widget.dart +++ b/lib/features/diary/presentation/widgets/finish_day_button_widget.dart @@ -1,11 +1,11 @@ -import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; +import 'package:eatvio_app/features/diary/entities/diary_day_entity.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; + final DiaryDayEntity diaryDay; const FinishDayButtonWidget({super.key, required this.diaryDay}); diff --git a/lib/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart b/lib/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart index 69b316a7..c9359cd9 100644 --- a/lib/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart +++ b/lib/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart @@ -9,11 +9,7 @@ class GlossaryModerationRequestModalWidget extends StatefulWidget { final DiaryCubit diaryCubit; final String productName; - const GlossaryModerationRequestModalWidget({ - super.key, - required this.productName, - required this.diaryCubit, - }); + const GlossaryModerationRequestModalWidget({super.key, required this.productName, required this.diaryCubit}); @override State createState() => _GlossaryModerationRequestModalWidgetState(); @@ -55,21 +51,13 @@ class _GlossaryModerationRequestModalWidgetState extends State { diaryCubit = context.read(); } - void showImagePicker(DiaryDayModel diaryDay) { + void showImagePicker(DiaryDayEntity diaryDay) { showCupertinoModalPopup( context: context, builder: (context) { diff --git a/lib/features/diary/repositories/diary_repository.dart b/lib/features/diary/repositories/diary_repository.dart index f3ff8bb5..6a2349b0 100644 --- a/lib/features/diary/repositories/diary_repository.dart +++ b/lib/features/diary/repositories/diary_repository.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart'; import 'package:eatvio_app/features/diary/models/consultant_review_model.dart'; +import 'package:eatvio_app/features/diary/models/create_day_model.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'; @@ -69,14 +70,6 @@ class DiaryRepository { } } - Future getDiaryByDateForDietOnly(String date) async { - try { - return (await diaryService.getDiaryIndexByDateForDietOnly(date: date)).last; - } catch (e) { - rethrow; - } - } - Future> getDiaryIndexByDate(String date) async { return (await diaryService.getDiaryIndexByDate(date: date)); } @@ -164,7 +157,7 @@ class DiaryRepository { required PortionState portionState, }) async { Map data = ProductInMeal(productName: productName, portion: portion, quantity: quantity).toJson(); - data['product_nutritional_value_id'] = productNutritionalValueId; + if (productNutritionalValueId != null) data['product_nutritional_value_id'] = productNutritionalValueId; data['state'] = portionState.name; return await diaryService.updateProductInMeal(diary: diaryId, typeNo: mealId, portionModel: portionId, productInMeal: data); } @@ -300,4 +293,69 @@ class DiaryRepository { rethrow; } } + + Future getDiaryDayV2({required String day}) async { + try { + final diaryDay = await diaryService.getDiaryDayV2(day: day); + return setCPFCToDiary(diaryDay); + } catch (e) { + rethrow; + } + } + + Future createDiaryDay({required String day}) async { + try { + await diaryService.createDay(body: CreateDayModel(day: day)); + await getDiaryByDate(day); + } catch (e) { + rethrow; + } + } + + Future setCPFCToDiary(DiaryDayModel diaryDay) async { + Set ids = {}; + for (var meal in diaryDay.meals) { + for (var portion in meal.portions) { + if (portion.productNutritionalValueId != null) { + ids.add(portion.productNutritionalValueId!); + } + } + } + if (ids.isNotEmpty) { + final glossary = await getGlossariesIndex(ids: ids.toList()); + final List updatedMeals = List.from(diaryDay.meals); + for (var mealIndex = 0; mealIndex < updatedMeals.length; mealIndex++) { + final meal = updatedMeals[mealIndex]; + final updatedPortions = meal.portions.map((portion) { + if (portion.productNutritionalValueId != null) { + try { + final glossaryItem = glossary.glossariesList.firstWhere((g) => g.id == portion.productNutritionalValueId); + return portion.copyWith( + productCaloriesInfo: ProductCaloriesInfoModel( + id: glossaryItem.id, + productName: glossaryItem.name, + calories: glossaryItem.energyOn100Gr, + proteins: glossaryItem.proteinsOn100Gr, + fats: glossaryItem.fatsOn100Gr, + carbohydrates: glossaryItem.carbohydratesOn100Gr, + energyOnPortion: glossaryItem.energyOnPortion, + proteinsOnPortion: glossaryItem.proteinsOnPortion, + fatsOnPortion: glossaryItem.fatsOnPortion, + carbohydratesOnPortion: glossaryItem.carbohydratesOnPortion, + ), + ); + } catch (e) { + return portion; + } + } + return portion; + }).toList(); + + updatedMeals[mealIndex] = meal.copyWith(portions: updatedPortions); + } + final updatedDiaryDay = diaryDay.copyWith(meals: updatedMeals); + return updatedDiaryDay; + } + return diaryDay; + } } diff --git a/lib/features/diary/services/diary_service.dart b/lib/features/diary/services/diary_service.dart index 8cba6f33..985fb885 100644 --- a/lib/features/diary/services/diary_service.dart +++ b/lib/features/diary/services/diary_service.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart'; import 'package:eatvio_app/features/diary/models/consultant_review_model.dart'; +import 'package:eatvio_app/features/diary/models/create_day_model.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'; @@ -17,8 +18,6 @@ abstract class DiaryService { @GET('/api/diary/index/{date}') Future> getDiaryIndexByDate({@Path('date') required String date}); - @GET('/api/diary/index/{date}') - Future> getDiaryIndexByDateForDietOnly({@Path('date') required String date}); @GET('/api/diary/{diary}/show') Future getDiaryDay({@Path('diary') required int diary}); @@ -103,4 +102,10 @@ abstract class DiaryService { @GET('/api/glossaries/index') Future getGlossariesIndex({@Queries() Map? filters}); + + @POST('/api/v2/diary/create/day') + Future createDay({@Body() required CreateDayModel body}); + + @GET('/api/v2/diary/index/{day?}') + Future getDiaryDayV2({@Query('day') required String day}); } diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index 5c25dfe5..5adc0ae5 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -95,6 +95,31 @@ class _ProfileScreenState extends State { isFirstFetch = false; } + // Проверяем, заблокировал ли пользователь меня + if (state.profile.isBlockedByUser) { + return Scaffold( + appBar: AppBar(), + body: Center( + child: Padding( + padding: const EdgeInsets.all(32.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.block, size: 64, color: Colors.grey[400]), + const SizedBox(height: 16), + Text( + 'Пользователь ограничил доступ к своей странице', + textAlign: TextAlign.center, + + style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, color: Colors.grey[600]), + ), + ], + ), + ), + ), + ); + } + return widget.author == null || widget.author!.authorIsMe ? _myProfileScreen(state) : _profileScreen(state); } else if (state is ProfileStateError) { return Scaffold(