forked from eatvio/eatvio_app
1274 lines
42 KiB
Dart
1274 lines
42 KiB
Dart
// ignore_for_file: unused_local_variable
|
||
|
||
import 'package:eatvio_app/features/diary/models/diary_model.dart';
|
||
import 'package:eatvio_app/features/diary/models/meal.dart';
|
||
import 'package:eatvio_app/features/diary/models/photo.dart';
|
||
import 'package:eatvio_app/features/diary/models/portion.dart';
|
||
import 'package:eatvio_app/features/diary/presentation/bloc/diary_bloc.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/services.dart';
|
||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||
import 'package:fl_chart/fl_chart.dart';
|
||
import 'package:image_picker/image_picker.dart';
|
||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||
import 'package:eatvio_app/core/app_export.dart';
|
||
import 'package:eatvio_app/widgets/custom_edit_text_form_field.dart';
|
||
import 'package:eatvio_app/widgets/custom_edit_drop_down.dart';
|
||
import 'package:eatvio_app/widgets/custom_outlined_button.dart';
|
||
import 'package:eatvio_app/widgets/custom_scale_element.dart';
|
||
import 'package:carousel_slider/carousel_slider.dart';
|
||
|
||
// ignore: must_be_immutable
|
||
class DiaryScreen extends StatelessWidget {
|
||
DiaryScreen({Key? key}) : super(key: key);
|
||
Widget build(BuildContext context) {
|
||
return BlocBuilder<DiaryBloc, DiaryState>(
|
||
builder: (context, state) {
|
||
if (state is DiaryStateLoaded) {
|
||
return _buildDiary(context, state);
|
||
} else if (state is DiaryStateLoading) {
|
||
return Scaffold(
|
||
body: Center(
|
||
child: CupertinoActivityIndicator(),
|
||
),
|
||
);
|
||
} else if (state is DiaryStateError) {
|
||
return Scaffold(
|
||
body: Padding(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Center(
|
||
child: Text(state.errorMessage),
|
||
),
|
||
),
|
||
);
|
||
} else
|
||
return Scaffold(
|
||
body: Padding(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Center(
|
||
child: Text("Неопределенная ошибка"),
|
||
),
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
Widget _buildDiary(BuildContext context, DiaryStateLoaded state) {
|
||
return RefreshIndicator(
|
||
onRefresh: refreshPage(context),
|
||
child: Stack(
|
||
alignment: Alignment.bottomCenter,
|
||
children: [
|
||
FormBuilder(
|
||
child: Scaffold(
|
||
appBar: AppBar(
|
||
toolbarHeight: 0,
|
||
automaticallyImplyLeading: false,
|
||
),
|
||
backgroundColor: Color.fromARGB(255, 240, 242, 245),
|
||
resizeToAvoidBottomInset: true,
|
||
body: SizedBox(
|
||
width: double.maxFinite,
|
||
child: Column(
|
||
children: [
|
||
Expanded(
|
||
child: SingleChildScrollView(
|
||
child: Padding(
|
||
padding: EdgeInsets.only(bottom: 5.v),
|
||
child: Column(
|
||
children: [
|
||
_buildHeaderRow(context, state),
|
||
SizedBox(height: 30.v),
|
||
_buildWakupRetire(context, state),
|
||
SizedBox(height: 30.v),
|
||
_buildMeal(context, state),
|
||
SizedBox(height: 20.v),
|
||
_buildMood(context, state),
|
||
SizedBox(height: 30.v),
|
||
_buildNutritionChart(context, state),
|
||
SizedBox(height: 30.v),
|
||
_buildComment(context, state),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
_buildEndDay(context),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
_buildEndDay(BuildContext context) {
|
||
return CustomOutlinedButton(
|
||
text: "Закончить день",
|
||
margin: EdgeInsets.only(
|
||
left: 16.h,
|
||
right: 16.h,
|
||
bottom: 16.v,
|
||
),
|
||
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
|
||
decoration: BoxDecoration(
|
||
color: Colors.transparent,
|
||
),
|
||
onPressed: onFinishDayPressed(context),
|
||
// Установите прозрачный фон
|
||
);
|
||
}
|
||
|
||
refreshPage(BuildContext context) {
|
||
return () async {
|
||
context.read<DiaryBloc>().add(RefreshEvent());
|
||
};
|
||
}
|
||
|
||
/// Section Widget
|
||
_buildHeaderRow(BuildContext context, DiaryStateLoaded state) {
|
||
final day = state.diary.id;
|
||
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),
|
||
),
|
||
],
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Padding(
|
||
padding: EdgeInsets.only(top: 1.v),
|
||
child: Column(
|
||
children: [
|
||
Align(
|
||
alignment: Alignment.centerRight,
|
||
child: Text(
|
||
"$day день",
|
||
style: CustomTextStyles.titleMediumBlack,
|
||
),
|
||
),
|
||
SizedBox(height: 2.v),
|
||
Text(
|
||
state.diary.date.toString(),
|
||
style: CustomTextStyles.bodyMediumBluegray800,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// Section Widget
|
||
_buildAddProduct(BuildContext context, DiaryState state) {
|
||
return CustomOutlinedButton(
|
||
text: "Добавить продукт",
|
||
margin: EdgeInsets.symmetric(horizontal: 10.h),
|
||
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
|
||
onPressed: addProduct(context),
|
||
);
|
||
}
|
||
|
||
addProduct(BuildContext context) {
|
||
return () {
|
||
context.read<DiaryBloc>().add(AddPortionEvent());
|
||
};
|
||
}
|
||
|
||
/// Section Widget
|
||
_buildAddPhoto(BuildContext context, DiaryStateLoaded state) {
|
||
bool isLoading = state.isLoading;
|
||
return GestureDetector(
|
||
onTap: addImage(context, isLoading),
|
||
child: Stack(
|
||
children: [
|
||
if (!isLoading)
|
||
Container(
|
||
margin: EdgeInsets.symmetric(horizontal: 10.h),
|
||
padding: EdgeInsets.symmetric(
|
||
horizontal: 40.h,
|
||
vertical: 80.v,
|
||
),
|
||
decoration: AppDecoration.fillGray.copyWith(
|
||
borderRadius: BorderRadiusStyle.roundedBorder5,
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.end,
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
// Показывайте прелоадер, если загрузка активна
|
||
|
||
Padding(
|
||
padding: EdgeInsets.only(
|
||
left: 7.h,
|
||
top: 5.v,
|
||
),
|
||
child: Text(
|
||
"Добавить фото",
|
||
selectionColor: appTheme.blueA400,
|
||
),
|
||
),
|
||
Spacer(),
|
||
Icon(
|
||
Icons.photo_library,
|
||
color: appTheme.blueGray300,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
if (isLoading)
|
||
Container(
|
||
margin: EdgeInsets.symmetric(horizontal: 10.h),
|
||
padding: EdgeInsets.symmetric(
|
||
horizontal: 40.h,
|
||
vertical: 80.v,
|
||
),
|
||
decoration: AppDecoration.fillGray.copyWith(
|
||
borderRadius: BorderRadiusStyle.roundedBorder5,
|
||
),
|
||
child: Center(
|
||
child: CircularProgressIndicator(color: appTheme.blueGray300), // Замените на свой прелоадер при необходимости
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
addImage(BuildContext context, bool isLoading) {
|
||
if (!isLoading) {
|
||
return () async {
|
||
final picker = ImagePicker();
|
||
List<XFile>? images = await picker.pickMultiImage();
|
||
if (images.isNotEmpty) {
|
||
for (var image in images) {
|
||
context.read<DiaryBloc>().add(UploadImages(image));
|
||
}
|
||
}
|
||
};
|
||
}
|
||
}
|
||
|
||
_buildMeal(BuildContext context, DiaryStateLoaded state) {
|
||
final digitsOnly = FilteringTextInputFormatter.digitsOnly;
|
||
List<String> _options = state.autocompleteList.map((dynamic item) => item as String).toList();
|
||
FocusNode focusNodeName = FocusNode();
|
||
return Container(
|
||
margin: EdgeInsets.symmetric(horizontal: 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),
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 10.h),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
"Время приема пищи",
|
||
style: CustomTextStyles.bodyMediumBlack900,
|
||
),
|
||
Expanded(
|
||
// Adding Expanded widget
|
||
child: Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 10.h),
|
||
child: ConstrainedBox(
|
||
constraints: BoxConstraints(
|
||
maxWidth: MediaQuery.of(context).size.width - 260,
|
||
),
|
||
child: GestureDetector(
|
||
onTap: () => _selectMealTime(context, state),
|
||
child: AbsorbPointer(
|
||
child: CustomTextFormField(
|
||
hintText: state.mealTimeController?.format(context).toString(),
|
||
autofocus: false,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
SizedBox(height: 20.v),
|
||
Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 10.h),
|
||
child: BlocSelector<DiaryBloc, DiaryState, DiaryModel?>(
|
||
selector: (state) {
|
||
if (state is DiaryStateLoaded)
|
||
return state.diary;
|
||
else
|
||
return null;
|
||
},
|
||
builder: (context, diary) {
|
||
return CustomDropDown(
|
||
items: state.mealList,
|
||
contentPadding: EdgeInsets.symmetric(
|
||
horizontal: 10.h,
|
||
vertical: 9.v,
|
||
),
|
||
defaultValue: state.mealList[0],
|
||
onChanged: changeMeal(context, state),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
SizedBox(height: 20.v),
|
||
Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 10.h),
|
||
child: RawAutocomplete<String>(
|
||
optionsBuilder: buildOptions(_options),
|
||
fieldViewBuilder: (
|
||
BuildContext context,
|
||
TextEditingController productEditingController,
|
||
FocusNode focusNodeName,
|
||
VoidCallback onFieldSubmitted,
|
||
) {
|
||
if (state.isAdded) {
|
||
print("Clear");
|
||
}
|
||
return CustomTextFormField(
|
||
hintText: "Введите название продукта",
|
||
controller: productEditingController,
|
||
focusNode: focusNodeName,
|
||
onChanged: getAutocomplete(context, focusNodeName),
|
||
);
|
||
},
|
||
optionsViewBuilder: (
|
||
BuildContext context,
|
||
AutocompleteOnSelected<String> onSelected,
|
||
Iterable<String> options,
|
||
) {
|
||
return Align(
|
||
alignment: Alignment.topLeft,
|
||
child: Material(
|
||
elevation: 4.0,
|
||
child: SizedBox(
|
||
height: 200.0,
|
||
child: ListView.builder(
|
||
padding: const EdgeInsets.all(8.0),
|
||
itemCount: options.length,
|
||
itemBuilder: (BuildContext context, int index) {
|
||
final String option = options.elementAt(index);
|
||
return GestureDetector(
|
||
onTap: selectProduct(onSelected, option, state),
|
||
child: ListTile(
|
||
title: Text(option),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
SizedBox(height: 20.v),
|
||
if (state.moderationExpanded)
|
||
Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 10.h),
|
||
child: GestureDetector(
|
||
onTap: () => openCaloriesDialog(context, state),
|
||
child: Text("Отправить на модерацию"),
|
||
),
|
||
),
|
||
SizedBox(height: 20.v),
|
||
Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 10.h),
|
||
child: SingleChildScrollView(
|
||
scrollDirection: Axis.horizontal,
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
CustomTextFormField(
|
||
width: 150.h,
|
||
hintText: "Кол-во",
|
||
textInputAction: TextInputAction.done,
|
||
// focusNode: focusNodeQuantity,
|
||
controller: state.productQuantityController,
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: <TextInputFormatter>[digitsOnly],
|
||
),
|
||
Padding(
|
||
padding: EdgeInsets.only(left: 30.h),
|
||
child: BlocSelector<DiaryBloc, DiaryState, DiaryModel?>(
|
||
selector: (state) {
|
||
if (state is DiaryStateLoaded) {
|
||
return state.diary;
|
||
} else
|
||
return null;
|
||
},
|
||
builder: (context, diary) {
|
||
return CustomDropDown(
|
||
items: state.portionList,
|
||
defaultValue: state.portionController,
|
||
onChanged: changePortion(context, state),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
SizedBox(height: 30.v),
|
||
_buildAddProduct(context, state),
|
||
SizedBox(height: 30.v),
|
||
...state.portions.map((portion) => _buildProduct(context, portion)).toList(),
|
||
SizedBox(height: 30.v),
|
||
/*...state.photos != null
|
||
? state.photos!.isEmpty
|
||
? [Text('Фото пока нет')]
|
||
: state.photos!
|
||
.map((photo) => _buildPhoto(context, photo))
|
||
.toList()
|
||
: [Text('Фото нет')],*/
|
||
_buildCarousel(context, state),
|
||
SizedBox(height: 20.v),
|
||
_buildAddPhoto(context, state),
|
||
SizedBox(height: 30.v),
|
||
Text(
|
||
"Ваша сытость после приёма пищи",
|
||
style: CustomTextStyles.bodyMediumBlack900,
|
||
),
|
||
SizedBox(height: 15.v),
|
||
BlocBuilder<DiaryBloc, DiaryState>(
|
||
builder: (context, state) {
|
||
if (state is DiaryStateLoaded) {
|
||
return Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text("😭"),
|
||
ScaleElement(
|
||
color: Colors.red,
|
||
isSelected: state.selectedSatiety == 0,
|
||
onTap: changeSatiety(context, 0),
|
||
),
|
||
ScaleElement(
|
||
color: Colors.orange,
|
||
isSelected: state.selectedSatiety == 1,
|
||
onTap: changeSatiety(context, 1),
|
||
),
|
||
ScaleElement(
|
||
color: Colors.yellow,
|
||
isSelected: state.selectedSatiety == 2,
|
||
onTap: changeSatiety(context, 2),
|
||
),
|
||
ScaleElement(
|
||
color: Colors.lime,
|
||
isSelected: state.selectedSatiety == 3,
|
||
onTap: changeSatiety(context, 3),
|
||
),
|
||
ScaleElement(
|
||
color: Colors.lightGreen,
|
||
isSelected: state.selectedSatiety == 4,
|
||
onTap: changeSatiety(context, 4),
|
||
),
|
||
ScaleElement(
|
||
color: Colors.green,
|
||
isSelected: state.selectedSatiety == 5,
|
||
onTap: changeSatiety(context, 5),
|
||
),
|
||
Text("😊"),
|
||
// Остальные элементы шкалы
|
||
],
|
||
);
|
||
} else {
|
||
return Container(); // Возвращаем пустой контейнер, если состояние не является DiaryState
|
||
}
|
||
},
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
openCaloriesDialog(BuildContext context, DiaryStateLoaded state) {
|
||
showDialog(
|
||
context: context,
|
||
builder: (BuildContext dialogContext) {
|
||
return AlertDialog(
|
||
title: Text('Введите информацию о продукте'),
|
||
content: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
CustomTextFormField(
|
||
hintText: "Название",
|
||
controller: state.productNameController,
|
||
),
|
||
SizedBox(height: 10.v),
|
||
CustomTextFormField(
|
||
hintText: "Энергетическая ценность на 100",
|
||
keyboardType: TextInputType.number,
|
||
controller: state.caloriesController,
|
||
),
|
||
SizedBox(height: 10.v),
|
||
CustomTextFormField(
|
||
hintText: "Белки на 100г",
|
||
keyboardType: TextInputType.number,
|
||
controller: state.proteinsController,
|
||
),
|
||
SizedBox(height: 10.v),
|
||
CustomTextFormField(
|
||
hintText: "Жиры на 100г",
|
||
keyboardType: TextInputType.number,
|
||
controller: state.fatsController,
|
||
),
|
||
SizedBox(height: 10.v),
|
||
CustomTextFormField(
|
||
hintText: "Углеводы на 100г",
|
||
keyboardType: TextInputType.number,
|
||
controller: state.carbsController,
|
||
),
|
||
],
|
||
),
|
||
actions: <Widget>[
|
||
TextButton(
|
||
onPressed: popDialog(context),
|
||
child: Text('Отмена'),
|
||
),
|
||
TextButton(
|
||
onPressed: triggerModeration(
|
||
context,
|
||
state.productNameController.text,
|
||
state.caloriesController.text,
|
||
state.proteinsController.text,
|
||
state.fatsController.text,
|
||
state.carbsController.text,
|
||
dialogContext,
|
||
),
|
||
child: Text('Отправить'),
|
||
),
|
||
],
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
triggerModeration(
|
||
BuildContext context,
|
||
String name,
|
||
String energyOn100gr,
|
||
String proteinsOn100gr,
|
||
String fatsOn100gr,
|
||
String carbohydratesOn100gr,
|
||
BuildContext dialogContext,
|
||
) {
|
||
return () {
|
||
// Call function to send moderation with the entered values
|
||
context.read<DiaryBloc>().add(
|
||
SendModeration(
|
||
name: name,
|
||
energyOn100gr: energyOn100gr,
|
||
proteinsOn100gr: proteinsOn100gr,
|
||
fatsOn100gr: fatsOn100gr,
|
||
carbohydratesOn100gr: carbohydratesOn100gr,
|
||
),
|
||
);
|
||
Navigator.of(dialogContext).pop(); // Dismiss the dialog
|
||
};
|
||
}
|
||
|
||
popDialog(BuildContext dialogContext) {
|
||
return () {
|
||
Navigator.of(dialogContext).pop(); // Dismiss the dialog on Cancel button press
|
||
};
|
||
}
|
||
|
||
changeSatiety(BuildContext context, int satiety) {
|
||
return () {
|
||
context.read<DiaryBloc>().add(ChangeSatiety(satiety));
|
||
};
|
||
}
|
||
|
||
selectProduct(AutocompleteOnSelected<String> onSelected, String option, DiaryStateLoaded state) {
|
||
state.productNameController.text = option;
|
||
return () {
|
||
onSelected(option);
|
||
};
|
||
}
|
||
|
||
getAutocomplete(BuildContext context, FocusNode focusNode) {
|
||
return (value) {
|
||
if (value.length > 2) {
|
||
context.read<DiaryBloc>().add(ProductInput(value));
|
||
}
|
||
};
|
||
}
|
||
|
||
buildOptions(List<String> _options) {
|
||
return (TextEditingValue textEditingValue) {
|
||
return _options.where((String option) {
|
||
return true;
|
||
});
|
||
};
|
||
}
|
||
|
||
changeMeal(BuildContext context, DiaryStateLoaded state) {
|
||
return (value) {
|
||
context.read<DiaryBloc>().add(ChangeMeal(value: value));
|
||
};
|
||
}
|
||
|
||
changePortion(BuildContext context, DiaryStateLoaded state) {
|
||
return (value) {
|
||
context.read<DiaryBloc>().add(ChangePortion(value: value));
|
||
};
|
||
}
|
||
|
||
_buildCarousel(BuildContext context, DiaryStateLoaded state) {
|
||
if (state.currentMeal.photos.isEmpty) {
|
||
return Text("Фото нет");
|
||
} else {
|
||
return CarouselSlider(
|
||
options: CarouselOptions(height: 340.0),
|
||
items: state.currentMeal.photos.map<Widget>((photo) {
|
||
return _buildPhoto(context, photo); // Assuming photos are stored as strings
|
||
}).toList(),
|
||
);
|
||
}
|
||
}
|
||
|
||
_buildProduct(BuildContext context, Portion portion) {
|
||
return Container(
|
||
margin: EdgeInsets.symmetric(horizontal: 10.h),
|
||
padding: EdgeInsets.symmetric(
|
||
horizontal: 8.h,
|
||
vertical: 6.v,
|
||
),
|
||
decoration: AppDecoration.fillGray.copyWith(
|
||
borderRadius: BorderRadiusStyle.roundedBorder5,
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.end,
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Padding(
|
||
padding: EdgeInsets.only(
|
||
left: 7.h,
|
||
top: 5.v,
|
||
),
|
||
child: Text(
|
||
portion.productName.length > 8 ? portion.productName.substring(0, 8) : portion.productName,
|
||
style: CustomTextStyles.titleMedium16,
|
||
),
|
||
),
|
||
Padding(
|
||
padding: EdgeInsets.only(
|
||
left: 20.h,
|
||
top: 3.v,
|
||
bottom: 3.v,
|
||
),
|
||
child: Text(
|
||
portion.quantity.toString() + ' ' + portion.portion.toString().tr,
|
||
),
|
||
),
|
||
Spacer(),
|
||
GestureDetector(
|
||
onTap: removePortion(context, portion),
|
||
child: Padding(
|
||
padding: const EdgeInsets.only(right: 10),
|
||
child: Icon(
|
||
Icons.close,
|
||
color: appTheme.blueGray300,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
_buildPhoto(BuildContext context, Photo photo) {
|
||
return Stack(
|
||
children: [
|
||
Container(
|
||
margin: EdgeInsets.symmetric(horizontal: 10.h, vertical: 10.h),
|
||
decoration: AppDecoration.fillGray.copyWith(
|
||
borderRadius: BorderRadiusStyle.roundedBorder5,
|
||
),
|
||
child: Image.network(
|
||
dotenv.env['STORAGE_URL']! + photo.path,
|
||
),
|
||
),
|
||
Positioned(
|
||
top: 5,
|
||
right: 5,
|
||
child: GestureDetector(
|
||
onTap: removePhoto(context, photo),
|
||
child: Container(
|
||
padding: EdgeInsets.all(5),
|
||
decoration: BoxDecoration(
|
||
shape: BoxShape.circle,
|
||
color: Colors.transparent,
|
||
),
|
||
child: Icon(
|
||
Icons.close,
|
||
color: appTheme.blueGray300,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
removePortion(BuildContext context, Portion portion) {
|
||
return () {
|
||
context.read<DiaryBloc>().add(RemovePortionEvent(portion.id));
|
||
};
|
||
}
|
||
|
||
removePhoto(BuildContext context, Photo photo) {
|
||
return () {
|
||
context.read<DiaryBloc>().add(RemoveImageEvent(photo.id.toString(), photo.path));
|
||
};
|
||
}
|
||
|
||
_buildNutritionChart(BuildContext context, DiaryStateLoaded state) {
|
||
final List<int> recommendedValues = state.recommendedChartValues; // Рекомендуемые значения: калории, жиры, белки, углеводы
|
||
final List<int> consumedValues = state.consumedChartValues; //
|
||
return Container(
|
||
margin: EdgeInsets.symmetric(horizontal: 16.h),
|
||
padding: EdgeInsets.symmetric(
|
||
horizontal: 10.h,
|
||
vertical: 16.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),
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
SizedBox(height: 3.v),
|
||
Text(
|
||
"Ваш КБЖУ",
|
||
style: theme.textTheme.titleMedium,
|
||
),
|
||
SizedBox(height: 8.v),
|
||
Text(
|
||
"От дневной суточной нормы",
|
||
style: CustomTextStyles.bodyMediumBluegray800,
|
||
),
|
||
SizedBox(height: 19.v),
|
||
Align(
|
||
alignment: Alignment.centerRight,
|
||
child: Padding(
|
||
padding: EdgeInsets.only(
|
||
left: 30.h,
|
||
right: 12.h,
|
||
),
|
||
child: AspectRatio(
|
||
aspectRatio: 1.5,
|
||
child: BarChart(
|
||
BarChartData(
|
||
alignment: BarChartAlignment.spaceAround,
|
||
barTouchData: BarTouchData(enabled: false),
|
||
titlesData: FlTitlesData(
|
||
show: true,
|
||
bottomTitles: AxisTitles(
|
||
sideTitles: SideTitles(
|
||
showTitles: true,
|
||
//getTitlesWidget: (value, meta) => newMethod(value),
|
||
),
|
||
),
|
||
),
|
||
borderData: FlBorderData(show: false),
|
||
barGroups: [
|
||
BarChartGroupData(
|
||
x: 0,
|
||
barRods: [
|
||
BarChartRodData(
|
||
toY: consumedValues[0] / recommendedValues[0] * 100,
|
||
color: checkConsumedValues([recommendedValues[0]], [consumedValues[1]]) ? Colors.red : Colors.blue,
|
||
),
|
||
],
|
||
),
|
||
BarChartGroupData(
|
||
x: 1,
|
||
barRods: [
|
||
BarChartRodData(
|
||
toY: consumedValues[1] / recommendedValues[1] * 100,
|
||
color: checkConsumedValues([recommendedValues[1]], [consumedValues[1]]) ? Colors.red : Colors.blue,
|
||
),
|
||
],
|
||
),
|
||
BarChartGroupData(
|
||
x: 2,
|
||
barRods: [
|
||
BarChartRodData(
|
||
toY: consumedValues[2] / recommendedValues[2] * 100,
|
||
color: checkConsumedValues([recommendedValues[2]], [consumedValues[2]]) ? Colors.red : Colors.blue,
|
||
),
|
||
],
|
||
),
|
||
BarChartGroupData(
|
||
x: 3,
|
||
barRods: [
|
||
BarChartRodData(
|
||
toY: consumedValues[3] / recommendedValues[3] * 100,
|
||
color: checkConsumedValues([recommendedValues[3]], [consumedValues[3]]) ? Colors.red : Colors.blue,
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
SizedBox(height: 8.v),
|
||
SizedBox(height: 2.v),
|
||
Padding(
|
||
padding: EdgeInsets.only(
|
||
left: 10.h,
|
||
right: 2.h,
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Калорий",
|
||
style: theme.textTheme.bodySmall,
|
||
),
|
||
Padding(
|
||
padding: EdgeInsets.only(
|
||
left: 25.h,
|
||
bottom: 11.v,
|
||
),
|
||
child: Text(
|
||
"Белков",
|
||
style: theme.textTheme.bodySmall,
|
||
),
|
||
),
|
||
Padding(
|
||
padding: EdgeInsets.only(
|
||
left: 19.h,
|
||
bottom: 11.v,
|
||
),
|
||
child: Text(
|
||
"Жиров",
|
||
style: theme.textTheme.bodySmall,
|
||
),
|
||
),
|
||
Padding(
|
||
padding: EdgeInsets.only(
|
||
left: 14.h,
|
||
bottom: 11.v,
|
||
),
|
||
child: Text(
|
||
"Углеводов",
|
||
style: theme.textTheme.bodySmall,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
SizedBox(height: 2.v),
|
||
Padding(
|
||
padding: EdgeInsets.only(
|
||
left: 10.h,
|
||
right: 2.h,
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"${consumedValues[0].toInt()} из ${recommendedValues[0].toInt()}",
|
||
style: theme.textTheme.bodySmall,
|
||
),
|
||
Padding(
|
||
padding: EdgeInsets.only(
|
||
left: 25.h,
|
||
bottom: 11.v,
|
||
),
|
||
child: Text(
|
||
"${consumedValues[1].toInt()} из ${recommendedValues[1].toInt()}",
|
||
style: theme.textTheme.bodySmall,
|
||
),
|
||
),
|
||
Padding(
|
||
padding: EdgeInsets.only(
|
||
left: 19.h,
|
||
bottom: 11.v,
|
||
),
|
||
child: Text(
|
||
"${consumedValues[2].toInt()} из ${recommendedValues[2].toInt()}",
|
||
style: theme.textTheme.bodySmall,
|
||
),
|
||
),
|
||
Padding(
|
||
padding: EdgeInsets.only(
|
||
left: 14.h,
|
||
bottom: 11.v,
|
||
),
|
||
child: Text(
|
||
"${consumedValues[3].toInt()} из ${recommendedValues[3].toInt()}",
|
||
style: theme.textTheme.bodySmall,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
SizedBox(height: 19.v),
|
||
if (checkConsumedValues(recommendedValues, consumedValues))
|
||
Text(
|
||
"*Превышенная суточная норма",
|
||
style: theme.textTheme.labelLarge,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// Section Widget
|
||
_buildComment(BuildContext context, DiaryStateLoaded state) {
|
||
return Container(
|
||
margin: EdgeInsets.symmetric(horizontal: 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),
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Text(
|
||
"Дополнительная информация для консультанта",
|
||
style: theme.textTheme.titleMedium,
|
||
textAlign: TextAlign.center,
|
||
),
|
||
SizedBox(height: 20.v),
|
||
Container(
|
||
width: 320.h,
|
||
child: CustomTextFormField(
|
||
autofocus: false,
|
||
hintText: "Введите информацию для консультанта",
|
||
maxLines: 7,
|
||
borderDecoration: InputBorder.none,
|
||
controller: state.commentController,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
_buildWakupRetire(BuildContext context, DiaryStateLoaded state) {
|
||
return Container(
|
||
margin: EdgeInsets.symmetric(horizontal: 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),
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
children: [
|
||
GestureDetector(
|
||
onTap: toggleWakeup(context),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
r"Подъем \ отход ко сну",
|
||
style: CustomTextStyles.bodyMediumBlack900,
|
||
),
|
||
Icon(state.wakeupExpanded ? Icons.arrow_drop_up : Icons.arrow_drop_down),
|
||
],
|
||
),
|
||
),
|
||
if (state.wakeupExpanded)
|
||
Padding(
|
||
padding: const EdgeInsets.only(left: 16.0),
|
||
child: Column(
|
||
children: [
|
||
SizedBox(height: 20.v),
|
||
GestureDetector(
|
||
onTap: () => _selectWakeupTime(context, state),
|
||
child: AbsorbPointer(
|
||
child: CustomTextFormField(
|
||
hintText: state.wakeupController?.format(context).toString(),
|
||
prefix: Icon(Icons.sunny),
|
||
autofocus: false,
|
||
),
|
||
),
|
||
),
|
||
SizedBox(height: 20.v),
|
||
GestureDetector(
|
||
onTap: () => _selectRetireTime(context, state),
|
||
child: AbsorbPointer(
|
||
child: CustomTextFormField(
|
||
hintText: state.retireController?.format(context).toString(),
|
||
prefix: Icon(Icons.bed),
|
||
autofocus: false,
|
||
),
|
||
),
|
||
),
|
||
SizedBox(height: 20.v),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
toggleWakeup(BuildContext context) {
|
||
return () {
|
||
BlocProvider.of<DiaryBloc>(context).add(ToggleWakeupExpandedEvent());
|
||
};
|
||
}
|
||
|
||
/// Section Widget
|
||
|
||
Function() onFinishDayPressed(BuildContext context) {
|
||
return () {
|
||
showDialog(
|
||
context: context,
|
||
builder: (BuildContext dialogContext) {
|
||
return AlertDialog(
|
||
title: Text("Подтвердите действие"),
|
||
content: Text("Вы уверены, что хотите завершить день?"),
|
||
actions: <Widget>[
|
||
TextButton(
|
||
child: Text("Отмена"),
|
||
onPressed: closeDialog(dialogContext),
|
||
),
|
||
TextButton(
|
||
child: Text("Подтвердить"),
|
||
onPressed: sendFinishDayData(dialogContext, context),
|
||
),
|
||
],
|
||
);
|
||
},
|
||
);
|
||
};
|
||
}
|
||
|
||
Function() sendFinishDayData(BuildContext dialogContext, BuildContext context) {
|
||
return () {
|
||
closeDialog(dialogContext);
|
||
context.read<DiaryBloc>().add(FinishDay());
|
||
};
|
||
}
|
||
|
||
Function() closeDialog(BuildContext dialogContext) {
|
||
return () {
|
||
Navigator.of(dialogContext).pop();
|
||
};
|
||
}
|
||
|
||
_buildMood(BuildContext context, DiaryStateLoaded state) {
|
||
return Container(
|
||
margin: EdgeInsets.symmetric(horizontal: 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),
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
children: [
|
||
GestureDetector(
|
||
onTap: toggleMood(context),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
"Ваше настроение после приема пищи",
|
||
style: CustomTextStyles.bodyMediumBlack900,
|
||
),
|
||
Icon(state.moodExpanded ? Icons.arrow_drop_up : Icons.arrow_drop_down),
|
||
],
|
||
),
|
||
),
|
||
if (state.moodExpanded)
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text("😭"),
|
||
ScaleElement(
|
||
color: Colors.red,
|
||
isSelected: state.selectedMood == 0,
|
||
onTap: changeMood(context, 0),
|
||
),
|
||
ScaleElement(
|
||
color: Colors.orange,
|
||
isSelected: state.selectedMood == 1,
|
||
onTap: changeMood(context, 1),
|
||
),
|
||
ScaleElement(
|
||
color: Colors.yellow,
|
||
isSelected: state.selectedMood == 2,
|
||
onTap: changeMood(context, 2),
|
||
),
|
||
ScaleElement(
|
||
color: Colors.lime,
|
||
isSelected: state.selectedMood == 3,
|
||
onTap: changeMood(context, 3),
|
||
),
|
||
ScaleElement(
|
||
color: Colors.lightGreen,
|
||
isSelected: state.selectedMood == 4,
|
||
onTap: changeMood(context, 4),
|
||
),
|
||
ScaleElement(
|
||
color: Colors.green,
|
||
isSelected: state.selectedMood == 5,
|
||
onTap: changeMood(context, 5),
|
||
),
|
||
Text("😊"),
|
||
// Остальные элементы шкалы
|
||
],
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
changeMood(BuildContext context, int satiety) {
|
||
return () {
|
||
context.read<DiaryBloc>().add(ChangeMood(satiety));
|
||
};
|
||
}
|
||
|
||
toggleMood(BuildContext context) {
|
||
return () {
|
||
BlocProvider.of<DiaryBloc>(context).add(ToggleMoodExpandedEvent());
|
||
};
|
||
}
|
||
|
||
Future<void> _selectWakeupTime(BuildContext context, DiaryStateLoaded state) async {
|
||
final TimeOfDay? pickedTime = await showTimePicker(
|
||
context: context,
|
||
initialTime: state.wakeupController ?? TimeOfDay.now(),
|
||
);
|
||
if (pickedTime != null && pickedTime != state.wakeupController) {
|
||
context.read<DiaryBloc>().add(WakeupEvent(pickedTime));
|
||
}
|
||
}
|
||
|
||
Future<void> _selectRetireTime(BuildContext context, DiaryStateLoaded state) async {
|
||
final TimeOfDay? pickedTime = await showTimePicker(
|
||
context: context,
|
||
initialTime: state.retireController ?? TimeOfDay.now(),
|
||
);
|
||
if (pickedTime != null && pickedTime != state.retireController) {
|
||
context.read<DiaryBloc>().add(RetireEvent(pickedTime));
|
||
}
|
||
}
|
||
|
||
Future<void> _selectMealTime(BuildContext context, DiaryStateLoaded state) async {
|
||
final TimeOfDay? pickedTime = await showTimePicker(
|
||
context: context,
|
||
initialTime: state.mealTimeController ?? TimeOfDay.now(),
|
||
);
|
||
if (pickedTime != null && pickedTime != state.mealTimeController) {
|
||
context.read<DiaryBloc>().add(ChangeMealTime(pickedTime));
|
||
}
|
||
}
|
||
}
|
||
|
||
bool checkConsumedValues(List<int> recommendedValues, List<int> consumedValues) {
|
||
for (int i = 0; i < recommendedValues.length; i++) {
|
||
if (consumedValues[i] > recommendedValues[i]) {
|
||
// Если хотя бы одно потребленное значение превышает рекомендованное
|
||
return true; // Возвращаем false
|
||
}
|
||
}
|
||
// Если ни одно потребленное значение не превышает рекомендованное с соответствующим индексом
|
||
return false; // Возвращаем true
|
||
}
|
||
|
||
List<SelectionPopupModel> mealsToDropdown(List<Meal>? meals) {
|
||
return meals!
|
||
.map((meal) => SelectionPopupModel(
|
||
id: meal.id,
|
||
title: "Прием пищи ${meal.type}",
|
||
isSelected: false,
|
||
))
|
||
.toList();
|
||
}
|