Files
eatvio_app/lib/features/diary/presentation/pages/diary_screen.dart
2024-11-11 15:32:14 +03:00

936 lines
34 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/satiety_rating_widget.dart';
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
import 'package:eatvio_app/locator_service.dart';
import 'package:eatvio_app/theme/theme_helper.dart';
import 'package:eatvio_app/widgets/custom_text_form_field.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 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:image_picker/image_picker.dart';
import 'package:rive/rive.dart';
import 'package:syncfusion_flutter_gauges/gauges.dart';
import 'package:table_calendar/table_calendar.dart';
class DiaryScreen extends StatefulWidget {
const DiaryScreen({super.key});
@override
State<DiaryScreen> createState() => _DiaryScreenState();
}
class _DiaryScreenState extends State<DiaryScreen> {
final DiaryCubit diaryCubit = sl<DiaryCubit>();
CalendarFormat _calendarFormat = CalendarFormat.month;
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay = DateTime.now();
int currentSelectedMealNumber = 1;
GlossaryModel? selectedGlossary;
final productNameController = TextEditingController();
final quantityController = TextEditingController();
int currentSelectedPhotoIndex = 0;
@override
Widget build(BuildContext context) {
return BlocProvider<DiaryCubit>(
create: (BuildContext context) => diaryCubit..getDiaryDay(_selectedDay!.toString()),
child: BlocConsumer<DiaryCubit, DiaryState>(
listener: (BuildContext context, DiaryState state) {
if (state is DiaryStateLoaded) {
setState(() {
_calendarFormat = CalendarFormat.week;
});
}
},
builder: (BuildContext context, DiaryState state) {
return Scaffold(
backgroundColor: Colors.grey.shade200,
appBar: AppBar(
toolbarHeight: 0,
),
body: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
children: [
_calendar(state),
if (state is DiaryStateLoading) _loader(),
if (state is DiaryStateLoaded)
_meals(
state.diary,
state.currentSelectedSatietyRating,
),
if (state is DiaryStateLoaded) _chart(state.nutrition),
const SizedBox(height: 32),
],
),
),
);
},
),
);
}
_arrowDown() {
return Container(
padding: const EdgeInsets.only(top: 8, bottom: 16),
width: double.infinity,
child: Icon(
_calendarFormat == CalendarFormat.month ? Icons.keyboard_arrow_up_rounded : Icons.keyboard_arrow_down_rounded,
color: Colors.grey.shade600,
),
);
}
_divider(BuildContext context) {
return Container(
height: 0.4,
width: MediaQuery.of(context).size.width - 32,
decoration: BoxDecoration(
color: Colors.black26,
borderRadius: BorderRadius.circular(16),
),
);
}
_products(List<Portion> portions, DiaryDayModel diaryDay) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
for (int i = 0; i < portions.length; i++) ...[
_product(
diaryDay,
portions[i].id,
portions[i].productName,
portions[i].quantity,
portions[i].portion,
),
i < portions.length - 1 ? const SizedBox(height: 8) : const SizedBox.shrink(),
],
],
);
}
_product(DiaryDayModel diaryDay, int id, String product, int quantity, String portion) {
portion == 'gr' ? portion = 'г' : portion = 'шт';
return ListTile(
onTap: () {
setState(() {
productNameController.text = product;
quantityController.text = quantity.toString();
});
_modalBottomSheet(diaryDay, id);
},
trailing: Icon(Icons.more_vert_rounded),
title: Text(product),
subtitle: Text('$quantity $portion'),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
tileColor: Color(0xFFF6FAFF),
);
}
_meals(
DiaryDayModel diaryDay,
double currentSelectedSatietyRating,
) {
return Material(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Прием пищи',
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_mealButton(1),
_mealButton(2),
_mealButton(3),
_mealButton(4),
_mealButton(5),
],
),
const SizedBox(height: 12),
_divider(context),
const SizedBox(height: 12),
Text(
'Время приема пищи',
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 12),
_divider(context),
const SizedBox(height: 12),
_products(diaryDay.meals[currentSelectedMealNumber - 1].portions, diaryDay),
const SizedBox(height: 16),
_addProductButton(diaryDay),
const SizedBox(height: 24),
if (diaryDay.meals[currentSelectedMealNumber - 1].photos.isEmpty)
GestureDetector(
onTap: () => _showImagePicker(diaryDay),
child: SvgPicture.asset(
width: MediaQuery.of(context).size.width - 32,
'assets/no_one_image_was_added.svg',
),
),
if (diaryDay.meals[currentSelectedMealNumber - 1].photos.isNotEmpty)
_photoGallery(
diaryDay.meals[currentSelectedMealNumber - 1].photos,
diaryDay,
),
SatietyRatingWidget(
currentSelectedSatietyRating: currentSelectedSatietyRating,
),
],
),
),
);
}
_chart(NutritionDataModel nutrition) {
final List<ChartData> chartData = [
ChartData(
nutrition.dailyNutritionIntake.carbohydrates,
nutrition.dailyNutritionRecommendation.carbohydrates,
),
ChartData(
nutrition.dailyNutritionIntake.fats,
nutrition.dailyNutritionRecommendation.fats,
),
ChartData(
nutrition.dailyNutritionIntake.proteins,
nutrition.dailyNutritionRecommendation.proteins,
),
ChartData(
nutrition.dailyNutritionIntake.energy,
nutrition.dailyNutritionRecommendation.energy,
),
];
return Container(
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.only(top: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Ваш КБЖУ от дневной\nсуточной нормы',
textAlign: TextAlign.center,
style: TextStyle(
color: Color(0xFF323F4B),
fontWeight: FontWeight.w500,
fontSize: 18,
height: 1,
),
),
const SizedBox(height: 48),
Stack(
alignment: Alignment.bottomCenter,
children: [
SfRadialGauge(
axes: <RadialAxis>[
RadialAxis(
startAngle: 180,
endAngle: 0,
minimum: 0,
maximum: 100,
showLabels: false,
showTicks: false,
radiusFactor: 0.7,
showAxisLine: false,
pointers: <GaugePointer>[
RangePointer(
value: 100,
width: 0.16,
animationDuration: 2000,
color: Color(0xFFF6FAFF),
sizeUnit: GaugeSizeUnit.factor,
enableAnimation: true,
cornerStyle: CornerStyle.bothCurve,
),
RangePointer(
value: (chartData[0].intake / chartData[0].recommendation) * 100,
width: 0.16,
color: chartData[0].intake < chartData[0].recommendation ? appTheme.blueA400 : Colors.red,
sizeUnit: GaugeSizeUnit.factor,
enableAnimation: true,
cornerStyle: CornerStyle.bothCurve,
),
RangePointer(
value: 100,
width: 0.16,
animationDuration: 2000,
color: Color(0xFFF6FAFF),
sizeUnit: GaugeSizeUnit.factor,
enableAnimation: true,
cornerStyle: CornerStyle.bothCurve,
pointerOffset: -0.18,
),
RangePointer(
value: (chartData[1].intake / chartData[1].recommendation) * 100,
width: 0.16,
color: chartData[1].intake < chartData[1].recommendation ? appTheme.blueA400 : Colors.red,
sizeUnit: GaugeSizeUnit.factor,
enableAnimation: true,
cornerStyle: CornerStyle.bothCurve,
pointerOffset: -0.18,
),
RangePointer(
value: 100,
width: 0.16,
animationDuration: 2000,
color: Color(0xFFF6FAFF),
sizeUnit: GaugeSizeUnit.factor,
enableAnimation: true,
cornerStyle: CornerStyle.bothCurve,
pointerOffset: -0.36,
),
RangePointer(
value: (chartData[2].intake / chartData[2].recommendation) * 100,
width: 0.16,
color: chartData[2].intake < chartData[2].recommendation ? appTheme.blueA400 : Colors.red,
sizeUnit: GaugeSizeUnit.factor,
enableAnimation: true,
cornerStyle: CornerStyle.bothCurve,
pointerOffset: -0.36,
),
RangePointer(
value: 100,
width: 0.16,
animationDuration: 2000,
color: Color(0xFFF6FAFF),
sizeUnit: GaugeSizeUnit.factor,
enableAnimation: true,
cornerStyle: CornerStyle.bothCurve,
pointerOffset: -0.54,
),
RangePointer(
value: (chartData[3].intake / chartData[3].recommendation) * 100,
width: 0.16,
color: chartData[3].intake < chartData[3].recommendation ? appTheme.blueA400 : Colors.red,
sizeUnit: GaugeSizeUnit.factor,
enableAnimation: true,
cornerStyle: CornerStyle.bothCurve,
pointerOffset: -0.54,
),
],
annotations: <GaugeAnnotation>[
GaugeAnnotation(
widget: Transform.rotate(
angle: 30.1,
child: Text(
'Углеводы',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
angle: 198,
positionFactor: 0.92,
),
GaugeAnnotation(
widget: Transform.rotate(
angle: 30,
child: Text(
'Жиры',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
angle: 190,
positionFactor: 1.1,
),
GaugeAnnotation(
widget: Transform.rotate(
angle: 30,
child: Text(
'Белки',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
angle: 190,
positionFactor: 1.27,
),
GaugeAnnotation(
widget: Transform.rotate(
angle: 30,
child: Text(
'Калории',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
angle: 192,
positionFactor: 1.46,
),
],
),
],
),
Column(
children: [
_chartData('Калории',
'${nutrition.dailyNutritionIntake.energy.toStringAsFixed(0)} из ${nutrition.dailyNutritionRecommendation.energy.toStringAsFixed(0)} ккал'),
const SizedBox(height: 8),
_chartData('Белки',
'${nutrition.dailyNutritionIntake.proteins.toStringAsFixed(0)} из ${nutrition.dailyNutritionRecommendation.proteins.toStringAsFixed(0)} грамм'),
const SizedBox(height: 8),
_chartData('Жиры',
'${nutrition.dailyNutritionIntake.fats.toStringAsFixed(0)} из ${nutrition.dailyNutritionRecommendation.fats.toStringAsFixed(0)} грамм'),
const SizedBox(height: 8),
_chartData('Углеводы',
'${nutrition.dailyNutritionIntake.carbohydrates.toStringAsFixed(0)} из ${nutrition.dailyNutritionRecommendation.carbohydrates.toStringAsFixed(0)} грамм'),
],
),
],
),
],
),
);
}
_chartData(String title, String value) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Color(0xFF323F4B),
),
),
Text(
value,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Color(0xFF323F4B),
),
),
],
);
}
_showImagePicker(DiaryDayModel diaryDay) {
showCupertinoModalPopup<void>(
context: context,
builder: (context) => CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
onPressed: () async {
final picker = ImagePicker();
XFile? image = await picker.pickImage(source: ImageSource.camera);
if (image != null)
diaryCubit.uploadPhotos(
files: [File(image.path)],
diaryId: diaryDay.id,
mealId: diaryDay.meals[currentSelectedMealNumber - 1].id,
date: _selectedDay.toString(),
);
Navigator.of(context).pop();
},
child: const Row(
children: const [
Icon(Icons.photo_camera),
SizedBox(width: 8),
Text('Фото'),
],
),
),
CupertinoActionSheetAction(
onPressed: () async {
final picker = ImagePicker();
List<XFile>? images = await picker.pickMultiImage();
if (images.isNotEmpty)
diaryCubit.uploadPhotos(
files: images.map((f) => File(f.path)).toList(),
diaryId: diaryDay.id,
mealId: diaryDay.meals[currentSelectedMealNumber - 1].id,
date: _selectedDay.toString(),
);
Navigator.of(context).pop();
},
child: const Row(
children: const [
Icon(Icons.photo),
SizedBox(width: 8),
Text('Галерея'),
],
),
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: const Text('Отмена'),
),
),
);
}
_photoGallery(List<DiaryPhotoModel> photos, DiaryDayModel diaryDay) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
alignment: Alignment.topRight,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + photos[currentSelectedPhotoIndex].path,
),
),
Padding(
padding: const EdgeInsets.all(12),
child: IconButton.filled(
style: IconButton.styleFrom(
backgroundColor: Colors.white,
elevation: 0,
),
onPressed: () async {
await diaryCubit.deletePhoto(
photo: photos[currentSelectedPhotoIndex],
diaryId: diaryDay.id,
mealId: diaryDay.meals[currentSelectedMealNumber - 1].id,
date: _selectedDay.toString(),
);
setState(() {
if (currentSelectedPhotoIndex == photos.length - 1 && currentSelectedPhotoIndex != 0) {
currentSelectedPhotoIndex = photos.length - 2;
}
});
},
icon: Icon(
Icons.close_rounded,
color: Colors.grey,
),
),
),
],
),
const SizedBox(height: 8),
SingleChildScrollView(
physics: const BouncingScrollPhysics(),
scrollDirection: Axis.horizontal,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
for (int i = 0; i < photos.length + 1; i++) ...[
if (i == photos.length)
Padding(
padding: const EdgeInsets.only(left: 8),
child: InkWell(
onTap: () => _showImagePicker(diaryDay),
borderRadius: BorderRadius.circular(8),
child: Ink(
height: 52,
width: 52,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Color(0xFFF1F2F6),
),
child: Icon(
Icons.add_rounded,
color: appTheme.blueA400,
),
),
),
),
if (i < photos.length)
GestureDetector(
onTap: () {
setState(() {
currentSelectedPhotoIndex = i;
});
},
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: SizedBox(
height: 52,
width: 52,
child: CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + photos[i].path,
fit: BoxFit.fitWidth,
),
),
),
),
if (i < photos.length - 1) const SizedBox(width: 8),
],
],
),
),
],
);
}
_mealButton(int mealNumber) {
bool isActive = currentSelectedMealNumber == mealNumber;
return InkWell(
onTap: () {
setState(() {
if (currentSelectedMealNumber == mealNumber) return;
currentSelectedMealNumber = mealNumber;
});
},
borderRadius: BorderRadius.circular(100),
child: Ink(
width: MediaQuery.of(context).size.width / 5 - (96 / 5),
height: 32,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: isActive ? appTheme.blueA400 : Color(0xFFF1F2F6),
),
child: Center(
child: Text(
'$mealNumber',
style: TextStyle(
color: isActive ? Colors.white : Colors.black,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
);
}
_loader() {
return Center(
child: SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
);
}
_calendar(DiaryState state) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(top: 16),
child: TableCalendar(
firstDay: DateTime.utc(2010, 10, 16),
lastDay: DateTime.utc(2030, 3, 14),
locale: 'ru_RU',
startingDayOfWeek: StartingDayOfWeek.monday,
focusedDay: _focusedDay,
calendarFormat: _calendarFormat,
headerStyle: HeaderStyle(
titleCentered: true,
formatButtonVisible: false,
leftChevronVisible: false,
rightChevronVisible: false,
),
selectedDayPredicate: (day) {
return isSameDay(_selectedDay, day);
},
onDaySelected: (selectedDay, focusedDay) {
if (state is! DiaryStateLoaded) return;
if (!isSameDay(_selectedDay, selectedDay)) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
currentSelectedMealNumber = 1;
});
diaryCubit.getDiaryDay(_selectedDay!.toString());
}
},
onFormatChanged: (format) {
if (_calendarFormat != format) {
setState(() {
_calendarFormat = format;
});
}
},
onPageChanged: (focusedDay) {
_focusedDay = focusedDay;
},
calendarStyle: CalendarStyle(
weekendDecoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
defaultDecoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
selectedDecoration: BoxDecoration(
color: appTheme.blueA400,
shape: BoxShape.circle,
),
todayTextStyle: TextStyle(
color: Colors.black,
),
todayDecoration: BoxDecoration(
border: Border.all(
color: appTheme.blueA400,
width: 2,
),
color: Colors.white,
shape: BoxShape.circle,
),
),
),
),
_arrowDown(),
],
);
}
_modalBottomSheet(DiaryDayModel diaryDay, [int? portionId]) {
return showModalBottomSheet(
useRootNavigator: true,
context: context,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
),
isScrollControlled: true,
builder: (context) {
return StatefulBuilder(
builder: (BuildContext context, void Function(void Function()) setState) {
return Padding(
padding: const EdgeInsets.all(16) + EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Добавление блюда',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Color(0xFF323F4B),
),
),
const SizedBox(height: 16),
TypeAheadField(
controller: productNameController,
itemBuilder: (context, glossary) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
glossary.name,
style: TextStyle(
height: 1,
fontSize: 16,
),
),
);
},
itemSeparatorBuilder: (context, index) => _divider(context),
onSelected: (newSelectedGlossary) {
setState(() {
selectedGlossary = newSelectedGlossary;
productNameController.text = selectedGlossary!.name;
});
},
suggestionsCallback: (query) async {
if (query.length < 1) return [];
final suggestionsList = await diaryCubit.getGlossaryListByQuery(query);
if (suggestionsList.length == 1) {
setState(() {
selectedGlossary = suggestionsList.first;
});
}
return suggestionsList;
},
builder: (context, controller, focusNode) {
return CustomTextFormField(
controller: controller,
autofocus: true,
focusNode: focusNode,
hintText: 'Введите название продукта/блюда',
);
},
emptyBuilder: (context) => const SizedBox.shrink(),
errorBuilder: (context, object) => Padding(
padding: const EdgeInsets.all(8.0),
child: Text('Нет подходящих вариантов'),
),
),
if (selectedGlossary != null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: CustomTextFormField(
controller: quantityController,
keyboardType: TextInputType.number,
hintText: selectedGlossary!.grInPortion == 0 ? '100' : '1',
autofocus: true,
suffixText: selectedGlossary!.grInPortion == 0 ? 'гр' : 'шт',
),
),
const SizedBox(height: 16),
Row(
children: [
if (portionId != null)
InkWell(
onTap: () {
diaryCubit.deleteProductFromMeal(
diaryId: diaryDay.id,
mealId: diaryDay.meals[currentSelectedMealNumber - 1].id,
portionId: portionId,
date: _selectedDay.toString(),
);
Navigator.of(context).pop();
},
borderRadius: BorderRadius.circular(20),
child: Ink(
width: 52,
height: 52,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Color(0xFFF6F6F6),
),
child: Icon(
Icons.delete_outline_rounded,
color: Colors.red,
),
),
),
if (portionId != null) const SizedBox(width: 8),
InkWell(
onTap: () {
if (portionId == null) {
diaryCubit.addProductToMeal(
diaryId: diaryDay.id,
mealId: diaryDay.meals[currentSelectedMealNumber - 1].id,
productName: productNameController.text,
portion: selectedGlossary!.grInPortion == 0 ? 'gr' : 'pcs',
quantity: int.parse(quantityController.text),
date: _selectedDay.toString(),
);
} else {
diaryCubit.updateProductInMeal(
diaryId: diaryDay.id,
mealId: diaryDay.meals[currentSelectedMealNumber - 1].id,
portionId: portionId,
productName: productNameController.text,
portion: selectedGlossary!.grInPortion == 0 ? 'gr' : 'pcs',
quantity: int.parse(quantityController.text),
date: _selectedDay.toString(),
);
}
Navigator.of(context).pop();
},
borderRadius: BorderRadius.circular(20),
child: Ink(
width: MediaQuery.of(context).size.width - (portionId == null ? 32 : 92),
height: 52,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: appTheme.blueA400,
),
child: Center(
child: Text(
portionId == null ? 'Добавить' : 'Сохранить',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
),
],
),
],
),
);
},
);
},
);
}
_addProductButton(DiaryDayModel diaryDay) {
return InkWell(
onTap: () {
setState(() {
selectedGlossary = null;
productNameController.clear();
quantityController.clear();
});
_modalBottomSheet(diaryDay);
},
borderRadius: BorderRadius.circular(20),
child: Ink(
padding: EdgeInsets.symmetric(horizontal: 16),
width: MediaQuery.of(context).size.width - 32,
decoration: BoxDecoration(
color: appTheme.blueA400,
borderRadius: BorderRadius.circular(20),
),
child: Container(
padding: EdgeInsets.all(16),
alignment: Alignment.center,
child: Text(
'Добавить',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
),
);
}
}
class ChartData {
final double intake;
final double recommendation;
ChartData(
this.intake,
this.recommendation,
);
}