From 5d13358fbc322e7ea7a7101dde2cfbfce8230c01 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Fri, 2 Aug 2024 18:08:28 +0300 Subject: [PATCH] feat(diary): Diary ID is passed on calendar click, new layouts for calendar, fixed nutrition chart --- lib/app_router.dart | 15 +- .../calendar/models/calendar_entry_model.dart | 20 ++ .../presentation/pages/calendar_screen.dart | 221 ++++++++++++------ .../diary/presentation/bloc/diary_bloc.dart | 58 +++-- .../presentation/pages/diary_screen.dart | 24 +- 5 files changed, 230 insertions(+), 108 deletions(-) create mode 100644 lib/features/calendar/models/calendar_entry_model.dart diff --git a/lib/app_router.dart b/lib/app_router.dart index 5eb1d0c..0b89f71 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -68,10 +68,14 @@ class AppRouter { builder: (context, state) => FeedScreenNew(), ), GoRoute( - path: '/diary-screen', - name: 'diaryScreen', - builder: (context, state) => DiaryScreen(), - ), + path: '/diary-screen', + name: 'diaryScreen', + builder: (context, state) { + if (state.extra != null) + return DiaryScreen(diaryID: state.extra as String); + else + return DiaryScreen(diaryID: '0'); + }), GoRoute( path: '/chat-select-screen', name: 'chatSelectScreen', @@ -87,7 +91,8 @@ class AppRouter { GoRoute( path: '/blog-post-screen', name: 'blogPostScreen', - builder: (context, state) => BlogPostScreen(blogPostID: state.extra as String), + builder: (context, state) => + BlogPostScreen(blogPostID: state.extra as String), ), GoRoute( path: '/blog-post-edit-screen', diff --git a/lib/features/calendar/models/calendar_entry_model.dart b/lib/features/calendar/models/calendar_entry_model.dart new file mode 100644 index 0000000..d9fa704 --- /dev/null +++ b/lib/features/calendar/models/calendar_entry_model.dart @@ -0,0 +1,20 @@ +import 'package:eatvio_app/core/app_export.dart'; + +/// This class is used in the [userprofile_item_widget] screen. +// ignore_for_file: must_be_immutable + +class CalendarEntryModel { + String? date; + String? status; + String id; + + CalendarEntryModel({ + this.date, + this.status, + required this.id, + }) { + date = date; + status = status; + id = id; + } +} diff --git a/lib/features/calendar/presentation/pages/calendar_screen.dart b/lib/features/calendar/presentation/pages/calendar_screen.dart index ab90403..3824512 100644 --- a/lib/features/calendar/presentation/pages/calendar_screen.dart +++ b/lib/features/calendar/presentation/pages/calendar_screen.dart @@ -1,11 +1,10 @@ // ignore_for_file: unused_local_variable -import 'package:eatvio_app/features/calendar/models/calendar_model.dart'; import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart'; import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_state.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:go_router/go_router.dart'; -import 'package:table_calendar/table_calendar.dart'; +import 'package:intl/intl.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; @@ -54,7 +53,7 @@ class CalendarScreen extends StatelessWidget { toolbarHeight: 0, automaticallyImplyLeading: false, ), - backgroundColor: Color.fromARGB(255, 240, 242, 245), + backgroundColor: Colors.white, resizeToAvoidBottomInset: true, body: SizedBox( width: double.maxFinite, @@ -83,82 +82,154 @@ class CalendarScreen extends StatelessWidget { } _buildCalendarBody(BuildContext context, CalendarStateLoaded state) { - return Container( - margin: EdgeInsets.symmetric(horizontal: 16.h, vertical: 16.h), - padding: EdgeInsets.symmetric( - horizontal: 24.h, - vertical: 12.v, - ), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), + return ListView.separated( + padding: EdgeInsets.all(8), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + separatorBuilder: (context, index) { + return SizedBox( + height: 2.v, + ); + }, + itemCount: state.calendarList.length, + itemBuilder: (context, index) { + Map diary = {}; + if (state.calendarList[index].diary is List) { + diary['status'] = 'Скоро'; + } else if (state.calendarList[index].diary is Map) { + diary = state.calendarList[index].diary as Map; + } + return InkWell( + borderRadius: BorderRadiusStyle.circleBorder8, + onTap: () => GoRouter.of(context).go( + '/diary-screen', + extra: diary['id'].toString(), ), - ], - ), - child: TableCalendar( - locale: 'ru_RU', - firstDay: DateTime.utc(2010, 10, 16), - lastDay: DateTime.utc(2030, 3, 14), - focusedDay: DateTime.now(), - startingDayOfWeek: StartingDayOfWeek.monday, - calendarBuilders: CalendarBuilders( - selectedBuilder: (context, day, focusedDay) => - _buildCellDate(context, day, focusedDay: focusedDay, state), - defaultBuilder: (context, day, focusedDay) => - _buildCellDate(context, day, state), - todayBuilder: (context, day, focusedDay) => - _buildCellDate(context, day, state), - ), - ), + child: Ink( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadiusStyle.circleBorder8, + color: getBackgroundColor(diary['state']), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + formatDate(state.calendarList[index].date), + style: theme.textTheme.titleMedium, + ), + SizedBox(height: 2.v), + if (diary['state'] != null) + Row(children: [ + Container( + padding: EdgeInsets.all( + 4.0), // Отступы внутри контейнера + decoration: BoxDecoration( + color: diary['state'] == 'finished' + ? Color.fromRGBO(1, 154, 62, 1) + : Color.fromRGBO(154, 74, 1, 1), + borderRadius: BorderRadius.circular( + 24.0), // Закругленные углы + ), + child: Text( + _getStateText(diary['state'] ?? ''), + style: CustomTextStyles + .titleSmallBold, // Предполагается, что текст белый + ), + ), + SizedBox(width: 4), + if (diary['state'] == 'started') + Container( + padding: EdgeInsets.all( + 4.0), // Отступы внутри контейнера + decoration: BoxDecoration( + color: Color.fromRGBO(13, 110, 253, 1), + borderRadius: BorderRadius.circular( + 24.0), // Закругленные углы + ), + child: Text( + 'Заполнить отчет', + style: CustomTextStyles + .titleSmallBold, // Предполагается, что текст белый + ), + ), + if (diary['state'] == 'finished') + Container( + padding: EdgeInsets.all( + 4.0), // Отступы внутри контейнера + decoration: BoxDecoration( + color: Color.fromRGBO(13, 110, 253, 1), + borderRadius: BorderRadius.circular( + 24.0), // Закругленные углы + ), + child: Text( + 'Изменить отчет', + style: CustomTextStyles + .titleSmallBold, // Предполагается, что текст белый + ), + ), + ]), + if (state.calendarList[index].diary is List) + Container( + padding: + EdgeInsets.all(4.0), // Отступы внутри контейнера + decoration: BoxDecoration( + color: Color.fromRGBO(108, 117, 125, 1), + borderRadius: BorderRadius.circular( + 24.0), // Закругленные углы + ), + child: Text( + 'Cкоро', + style: CustomTextStyles + .titleSmallBold, // Предполагается, что текст белый + ), + ), + ], + ), + ), + ], + ), + ), + ); + }, ); } } -Widget _buildCellDate( - BuildContext context, DateTime day, CalendarStateLoaded state, - {DateTime? focusedDay}) { - final String dayString = day.toIso8601String().split('T').first; - final List calendarModels = state.calendarList; - - // Находим первую модель, соответствующую дате - final CalendarModel model = calendarModels.firstWhere( - (model) => model.date == dayString, - orElse: () => CalendarModel(date: dayString, diary: {})); - - Color? bgclr; // Объявляем переменную bgclr здесь - - final Map diary = model.diary as Map; - if (model.diary is Map) { - if (diary['state'] == "started") { - bgclr = Colors.grey; - } else if (diary['state'] == "finished") { - bgclr = Colors.green; - } else { - bgclr = Colors.white; - } - } else { - bgclr = Colors.white; // Значение по умолчанию, если diary не является Map +String _getStateText(String state) { + switch (state) { + case 'finished': + return 'Готово'; + case 'started': + return 'В процессе'; + default: + return ''; // Возвращаем пустую строку, если состояние не распознано + } +} + +String formatDate(String dateString) { + // Преобразуем строку в объект DateTime + DateTime dateTime = DateTime.parse(dateString); + + // Форматируем дату + String formattedDate = DateFormat('d MMMM', 'ru_RU').format(dateTime); + + // Возвращаем результат в нужном формате + return '$formattedDate'; +} + +Color getBackgroundColor(String? status) { + switch (status) { + case 'finished': + return Color.fromRGBO(1, 154, 62, .1); + case 'started': + return Color.fromRGBO(154, 74, 1, .1); + default: + return Color.fromRGBO(126, 136, 151, .1); } - - return GestureDetector( - onTap: () => GoRouter.of(context).pushNamed( - 'diaryScreen', - extra: diary['id'], - ), - child: Container( - margin: const EdgeInsets.all(5), - alignment: Alignment.center, - decoration: BoxDecoration( - shape: (day == focusedDay) ? BoxShape.circle : BoxShape.rectangle, - color: bgclr, - borderRadius: (day == focusedDay) ? null : BorderRadius.circular(4), - ), - child: Text(day.day.toString(), style: TextStyle(color: Colors.black)), - )); } diff --git a/lib/features/diary/presentation/bloc/diary_bloc.dart b/lib/features/diary/presentation/bloc/diary_bloc.dart index b81dd21..9c18af5 100644 --- a/lib/features/diary/presentation/bloc/diary_bloc.dart +++ b/lib/features/diary/presentation/bloc/diary_bloc.dart @@ -16,7 +16,8 @@ part 'diary_event.dart'; class DiaryBloc extends Bloc { final DiaryRepository diaryRepository; - DiaryBloc({required this.diaryRepository}) : super(const DiaryState.loading()) { + DiaryBloc({required this.diaryRepository}) + : super(const DiaryState.loading()) { on(loadDiary); on(_changeSatiety); // пользователь нажимает на шкалу сытости on(_changeMood); // пользователь нажимает на шкалу настроения @@ -53,18 +54,33 @@ class DiaryBloc extends Bloc { final DiaryModel diary = await diaryRepository.getDiary(diaryID ?? 45); final Meal currentMeal = diary.meals[0]; - List portionList = [SelectionPopupModel(title: "gr"), SelectionPopupModel(title: "pcs")]; + List portionList = [ + SelectionPopupModel(title: "gr"), + SelectionPopupModel(title: "pcs") + ]; - final Map chartData = await diaryRepository.getChartData(diaryID ?? 45); - final Map dailyNutritionIntake = chartData['dailyNutritionIntake']!; - final Map dailyNutritionRecommendations = chartData['dailyNutritionRecommendation']!; - final double ccal = dailyNutritionIntake['energy']! / dailyNutritionRecommendations['energy']! * 50; - final double proteins = dailyNutritionIntake['proteins']! / dailyNutritionRecommendations['proteins']! * 50; - final double fats = dailyNutritionIntake['fats']! / dailyNutritionRecommendations['fats']! * 50; - final double carbs = dailyNutritionIntake['carbohydrates']! / dailyNutritionRecommendations['carbohydrates']! * 50; + final Map chartData = + await diaryRepository.getChartData(diaryID ?? 45); + print(chartData); + final Map dailyNutritionIntake = + chartData['dailyNutritionIntake']!; + final Map dailyNutritionRecommendations = + chartData['dailyNutritionRecommendation']!; + final double ccal = dailyNutritionIntake['energy']! / + dailyNutritionRecommendations['energy']! * + 100; + final double proteins = dailyNutritionIntake['proteins']! / + dailyNutritionRecommendations['proteins']! * + 100; + final double fats = dailyNutritionIntake['fats']! / + dailyNutritionRecommendations['fats']! * + 100; + final double carbs = dailyNutritionIntake['carbohydrates']! / + dailyNutritionRecommendations['carbohydrates']! * + 100; Color getColor(double value) { - if (value >= 99.1) { + if (value >= 49.1) { return Colors.red; } else { return Colors.blue; @@ -86,7 +102,6 @@ class DiaryBloc extends Bloc { SelectionPopupModel(title: "Прием пищи 5", id: 5), SelectionPopupModel(title: "Прием пищи 6", id: 6), ]; - emit(DiaryState.loaded( diary: diary, currentMeal: currentMeal, @@ -163,9 +178,11 @@ class DiaryBloc extends Bloc { ) async { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; - var updatedState = currentState.copyWith(wakeupController: event.selectedTime); + var updatedState = + currentState.copyWith(wakeupController: event.selectedTime); final DateFormat formatter = DateFormat('HH:mm'); - final String wakeupString = formatter.format(DateTime(2022, 1, 1, event.selectedTime.hour, event.selectedTime.minute)); + final String wakeupString = formatter.format(DateTime( + 2022, 1, 1, event.selectedTime.hour, event.selectedTime.minute)); await diaryRepository.changeWakeup( currentState.diary.id, wakeupString, @@ -180,9 +197,11 @@ class DiaryBloc extends Bloc { ) async { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; - var updatedState = currentState.copyWith(retireController: event.selectedTime); + var updatedState = + currentState.copyWith(retireController: event.selectedTime); final DateFormat formatter = DateFormat('HH:mm'); - final String retireString = formatter.format(DateTime(2022, 1, 1, event.selectedTime.hour, event.selectedTime.minute)); + final String retireString = formatter.format(DateTime( + 2022, 1, 1, event.selectedTime.hour, event.selectedTime.minute)); await diaryRepository.changeRetire( currentState.diary.id, retireString, @@ -208,7 +227,8 @@ class DiaryBloc extends Bloc { ) async { if (state is DiaryStateLoaded) { final currentState = state as DiaryStateLoaded; - final Meal meal = currentState.diary.meals.firstWhere((meal) => meal.type == event.value.id); + final Meal meal = currentState.diary.meals + .firstWhere((meal) => meal.type == event.value.id); DiaryStateLoaded updatedState = currentState.copyWith( currentMeal: meal, portions: meal.portions, @@ -227,10 +247,12 @@ class DiaryBloc extends Bloc { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; - final Map> productNutritionalValues = await diaryRepository.inputProduct(event.input); + final Map> productNutritionalValues = + await diaryRepository.inputProduct(event.input); var updatedState = currentState.copyWith( product_nameController: TextEditingController(text: event.input), - autocompleteList: productNutritionalValues['product_nutritional_value'] ?? [], + autocompleteList: + productNutritionalValues['product_nutritional_value'] ?? [], ); if (event.input.length > 3) { updatedState = updatedState.copyWith(moderationExpanded: true); diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart index e41d052..cd66fb0 100644 --- a/lib/features/diary/presentation/pages/diary_screen.dart +++ b/lib/features/diary/presentation/pages/diary_screen.dart @@ -23,7 +23,13 @@ import 'package:syncfusion_flutter_charts/charts.dart'; // ignore: must_be_immutable class DiaryScreen extends StatelessWidget { - DiaryScreen({Key? key}) : super(key: key); + final String? diaryID; + + const DiaryScreen({ + this.diaryID, + super.key, + }); + Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { @@ -141,7 +147,6 @@ class DiaryScreen extends StatelessWidget { alignment: Alignment.centerRight, widthFactor: 0.5, child: SfCircularChart( - legend: Legend(isVisible: false), series: [ RadialBarSeries( dataSource: state.chartData, @@ -153,30 +158,29 @@ class DiaryScreen extends StatelessWidget { ), ), )), - SizedBox(height: 16.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Калории'), - Text('Белки'), - Text('Жиры'), Text('Углеводы'), + Text('Жиры'), + Text('Белки'), + Text('Калории'), ], ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( - '${state.chartData![0].nutrition.toStringAsFixed(1)}'), // Калории + '${state.chartData![3].nutrition.toStringAsFixed(0)}%'), // Калории Text( - '${state.chartData![1].nutrition.toStringAsFixed(1)}'), // Белки + '${state.chartData![2].nutrition.toStringAsFixed(0)}%'), // Белки Text( - '${state.chartData![2].nutrition.toStringAsFixed(1)}'), // Жиры + '${state.chartData![1].nutrition.toStringAsFixed(0)}%'), // Жиры Text( - '${state.chartData![3].nutrition.toStringAsFixed(1)}'), // Углеводы + '${state.chartData![0].nutrition.toStringAsFixed(0)}% '), // Углеводы ], ), ],