import 'dart:developer'; import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart'; import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/core/widgets/main_background_bouble.dart'; import 'package:eatvio_app/core/widgets/show_error_dialog.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.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/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_container.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/comment_for_consultant_widget.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/day_comment_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:eatvio_app/features/insights/presentation/widgets/insights_card.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class DiaryScreen extends StatelessWidget { const DiaryScreen({super.key, required this.date}); final DateTime? date; @override Widget build(BuildContext context) { return DiaryScreenContent(date: date); } } class UserDiaryScreen extends StatelessWidget { static const path = '/user-diary-screen'; static const name = 'userDiaryScreen'; const UserDiaryScreen({super.key, required this.diaryId, this.ownerSlug}); final int diaryId; /// Slug владельца дневника (клиента) — нужен консультанту для блока «Аналитика». final String? ownerSlug; @override Widget build(BuildContext context) { return DiaryScreenContent(diaryId: diaryId, ownerSlug: ownerSlug); } } class DiaryScreenContent extends StatefulWidget { const DiaryScreenContent({ super.key, this.diaryId, this.date, this.ownerSlug, }); final int? diaryId; final DateTime? date; final String? ownerSlug; @override State createState() => _DiaryScreenContentState(); } class _DiaryScreenContentState extends State { bool _isDiaryFinishing = false; /// Slug владельца: из `user.slug` дневника, иначе из параметра экрана. String? _ownerSlug(DiaryStateLoaded state) { return state.diary.ownerSlug ?? widget.ownerSlug; } /// Консультант видит аналитику клиента только при известном slug владельца. bool _canShowInsights(DiaryStateLoaded state) { return !state.isConsultantView || _ownerSlug(state) != null; } @override Widget build(BuildContext context) { final s = S.of(context); return MultiBlocProvider( providers: [ BlocProvider( create: (BuildContext context) => sl() ..setInitialDate(widget.date) ..fetchDiaryV2(needLoading: true, diaryId: widget.diaryId), ), BlocProvider(create: (context) => sl()), ], child: BlocBuilder( builder: (BuildContext context, DiaryState state) { if (state is DiaryStateLoaded) { log(state.diary.id.toString()); } if (state is DiaryStateError) { return Scaffold( body: DataErrorWidget( onReload: () => context.read().fetchDiaryV2( needLoading: false, diaryId: widget.diaryId, ), ), ); } return Scaffold( backgroundColor: Colors.grey.shade200, appBar: widget.diaryId == null ? AppBar(toolbarHeight: 0) : AppBar( title: Text( state is DiaryStateLoaded ? DateTime.parse( state.diary.date, ).toLocal().format('d MMMM') : '', style: TextStyles.headingXLSemibold.copyWith( color: appTheme.black, ), ), centerTitle: true, ), body: SingleChildScrollView( padding: const EdgeInsets.only(bottom: 16, top: 8), physics: const BouncingScrollPhysics(), child: Column( children: [ if (widget.diaryId == null) CalendarWidget(date: widget.date), if (state is DiaryStateLoading) ...[ const SizedBox(height: 60), const Loader(), ], if (state is DiaryStateLoaded && _canShowInsights(state)) Padding( padding: const EdgeInsets.fromLTRB(4, 12, 4, 12), child: _DiaryInsights( slug: state.isConsultantView ? _ownerSlug(state) : null, canAdd: !state.isConsultantView, diaryDate: state.diary.date, ), ), if (state is DiaryStateLoaded) WakeupAndRetireWidget( isEnabled: state.diary.typeOfDiary.hasChosenPlan || state.isConsultantView || state.isWakeUpAndRetireEnabled, disable: !state.canEditDiary, ), const SizedBox(height: 12), if (state is DiaryStateLoaded) MealWidget( showMood: state.diary.typeOfDiary.hasChosenPlan || state.isConsultantView || state.isMoodEnabled, showAddButton: state.canEditDiary, ), 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, ); } if (state.isDiaryCommented || state.isDiaryFinished) { return const SizedBox.shrink(); } return const CommentForConsultantWidget(); } return const SizedBox.shrink(); }, ), if (state is DiaryStateLoaded && !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented) Padding( padding: const EdgeInsets.only(top: 12), child: _isDiaryFinishing ? const Center(child: Loader()) : FinishDayButtonWidget( hasProducts: state.diary.hasProducts, onPressed: () async { setState(() { _isDiaryFinishing = true; }); final isFinished = await context .read() .finishDiaryDay( diaryId: state.diary.id, wakeUp: state.diary.wakeup.toString(), retire: state.diary.retire.toString(), ); if (!isFinished && context.mounted) { showErrorDialog(context); } setState(() { _isDiaryFinishing = false; }); }, ), ), if (state is DiaryStateLoaded && state.isConsultantView && !state.isDiaryCommented) ...[ const SizedBox(height: 16), const MainBackgroundBouble( padding: EdgeInsets.all(16), child: DayCommentWidget(), ), ], if (state is DiaryStateLoaded && state.consultantReview?.consultantComments.dayComment != null) ...[ const SizedBox(height: 12), MainBackgroundBouble( padding: const EdgeInsets.all(16), child: Column( children: [ Text( s.dayComment, style: TextStyles.textLSemibold.copyWith( color: appTheme.black, ), ), const SizedBox(height: 12), CommentContainer( comment: state .consultantReview ?.consultantComments .dayComment ?? '', ), ], ), ), const SizedBox(height: 28), ], if (state is DiaryStateLoaded && state.isConsultantView && state.isDiaryFinished && !state.isDiaryCommented) ...[ const SizedBox(height: 16), CustomOutlinedButton( text: s.send, height: 52, buttonTextStyle: TextStyles.textMSemibold.copyWith( color: appTheme.whiteA700, ), buttonStyle: ButtonStyle( side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll( appTheme.blueA400, ), ), onPressed: () async { await context .read() .submitComments(state.diary.id); }, ), const SizedBox(height: 16), ], ], ), ), ); }, ), ); } } /// Блок «Аналитика» в дневнике. Для своего дневника перезапрашивает данные, /// когда в том же дне меняются приёмы пищи или время подъёма/отбоя: от них /// зависят КБЖУ-графики. Чужой дневник консультант не редактирует. class _DiaryInsights extends StatefulWidget { const _DiaryInsights({ required this.slug, required this.canAdd, required this.diaryDate, }); final String? slug; final bool canAdd; /// Дата открытого дня дневника (`YYYY-MM-DD`) — стартовый день «В течение дня». final String diaryDate; @override State<_DiaryInsights> createState() => _DiaryInsightsState(); } class _DiaryInsightsState extends State<_DiaryInsights> { int _refreshToken = 0; bool _isOwnDiaryMutation(DiaryState previous, DiaryState current) { if (!widget.canAdd || previous is! DiaryStateLoaded || current is! DiaryStateLoaded || previous.diary.id != current.diary.id) { return false; } return !listEquals(previous.diary.meals, current.diary.meals) || previous.diary.wakeup != current.diary.wakeup || previous.diary.retire != current.diary.retire; } @override Widget build(BuildContext context) { return BlocListener( listenWhen: _isOwnDiaryMutation, listener: (context, state) => setState(() => _refreshToken++), child: InsightsCard( slug: widget.slug, canAdd: widget.canAdd, refreshToken: _refreshToken, initialIntraDate: widget.diaryDate, ), ); } }