forked from eatvio/eatvio_app
1177 lines
39 KiB
Dart
1177 lines
39 KiB
Dart
// ignore_for_file: unused_local_variable
|
||
|
||
import 'package:carousel_slider_plus/carousel_slider_plus.dart';
|
||
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/models/product.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: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_scale_element.dart';
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:flutter/cupertino.dart';
|
||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||
import 'package:image_picker/image_picker.dart';
|
||
import 'package:syncfusion_flutter_charts/charts.dart';
|
||
|
||
class DiaryScreen extends StatelessWidget {
|
||
final String? diaryID;
|
||
|
||
const DiaryScreen({
|
||
this.diaryID,
|
||
super.key,
|
||
});
|
||
|
||
Widget build(BuildContext context) {
|
||
if (diaryID != "0") {
|
||
context.read<DiaryBloc>().add(LoadDiaryById(diaryID));
|
||
}
|
||
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: 8.v),
|
||
_buildWakupRetire(context, state),
|
||
SizedBox(height: 8.v),
|
||
_buildMeal(context, state),
|
||
SizedBox(height: 8.v),
|
||
_buildMood(context, state),
|
||
SizedBox(height: 8.v),
|
||
_buildNutritionChart(context, state),
|
||
SizedBox(height: 8.v),
|
||
// _buildComment(context, state),
|
||
// SizedBox(height: 8.v),
|
||
_buildEndDay(context),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
_buildNutritionChart(BuildContext context, DiaryStateLoaded state) {
|
||
return Container(
|
||
padding: EdgeInsets.symmetric(
|
||
horizontal: 24.h,
|
||
vertical: 12.v,
|
||
),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||
),
|
||
child: Column(
|
||
children: [
|
||
Text(
|
||
"Ваш КБЖУ",
|
||
style: theme.textTheme.titleMedium,
|
||
textAlign: TextAlign.center,
|
||
),
|
||
Text(
|
||
"от дневной суточной нормы",
|
||
),
|
||
Transform.rotate(
|
||
angle: -1.5708,
|
||
child: ClipRect(
|
||
child: Align(
|
||
alignment: Alignment.centerRight,
|
||
widthFactor: 0.5,
|
||
child: SfCircularChart(
|
||
series: <CircularSeries>[
|
||
RadialBarSeries<NutritionData, String>(
|
||
dataSource: state.chartDataCounted,
|
||
xValueMapper: (NutritionData data, _) => data.category,
|
||
yValueMapper: (NutritionData data, _) => data.nutrition,
|
||
pointColorMapper: (NutritionData data, _) => data.color,
|
||
trackColor: Color.fromRGBO(59, 130, 246, 1),
|
||
trackOpacity: 0.1,
|
||
gap: "8%",
|
||
radius: "110%",
|
||
innerRadius: '20%', // Измените значение по вашему усмотрению
|
||
)
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(style: theme.textTheme.titleMedium, 'Углеводы'),
|
||
Text(style: theme.textTheme.titleMedium, 'Жиры'),
|
||
Text(style: theme.textTheme.titleMedium, 'Белки'),
|
||
Text(style: theme.textTheme.titleMedium, 'Калории'),
|
||
],
|
||
),
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.end,
|
||
children: [
|
||
Text(
|
||
'${state.chartData['dailyNutritionIntake']['carbohydrates'].toStringAsFixed(1)} из ${state.chartData['dailyNutritionRecommendation']['carbohydrates'].toStringAsFixed(1)} грамм'),
|
||
SizedBox(height: 8.v),
|
||
Text(
|
||
'${state.chartData['dailyNutritionIntake']['fats'].toStringAsFixed(1)} из ${state.chartData['dailyNutritionRecommendation']['fats'].toStringAsFixed(1)} грамм'),
|
||
SizedBox(height: 8.v),
|
||
Text(
|
||
'${state.chartData['dailyNutritionIntake']['proteins'].toStringAsFixed(1)} из ${state.chartData['dailyNutritionRecommendation']['proteins'].toStringAsFixed(1)} грамм'),
|
||
SizedBox(height: 8.v),
|
||
Text(
|
||
'${state.chartData['dailyNutritionIntake']['energy'].toStringAsFixed(1)} из ${state.chartData['dailyNutritionRecommendation']['energy'].toStringAsFixed(1)} Ккал'),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
_buildEndDay(BuildContext context) {
|
||
return Container(
|
||
height: 48,
|
||
width: MediaQuery.of(context).size.width,
|
||
padding: EdgeInsets.only(left: 4, right: 4),
|
||
child: FloatingActionButton.extended(
|
||
heroTag: 'endDayButton',
|
||
highlightElevation: 0,
|
||
elevation: 0,
|
||
focusElevation: 0,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(16),
|
||
),
|
||
backgroundColor: appTheme.blueA400,
|
||
onPressed: () => onFinishDayPressed,
|
||
label: Text(
|
||
'Закончить день',
|
||
style: theme.textTheme.titleSmall,
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
refreshPage(BuildContext context) {
|
||
return () async {
|
||
context.read<DiaryBloc>().add(RefreshEvent());
|
||
};
|
||
}
|
||
|
||
/// Section Widget
|
||
_buildHeaderRow(BuildContext context, DiaryStateLoaded state) {
|
||
final day = state.diary.id;
|
||
return Container(
|
||
padding: EdgeInsets.symmetric(
|
||
horizontal: 24.h,
|
||
vertical: 12.v,
|
||
),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||
),
|
||
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,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// Section Widget
|
||
_buildAddProduct(BuildContext context, DiaryState state) {
|
||
return Container(
|
||
height: 48,
|
||
width: MediaQuery.of(context).size.width,
|
||
padding: EdgeInsets.only(left: 4, right: 4),
|
||
child: FloatingActionButton.extended(
|
||
heroTag: 'addProduct',
|
||
highlightElevation: 0,
|
||
elevation: 0,
|
||
focusElevation: 0,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(16),
|
||
),
|
||
backgroundColor: appTheme.blueA400,
|
||
onPressed: () => addProduct(context),
|
||
label: Text(
|
||
'Добавить продукт',
|
||
style: theme.textTheme.titleSmall,
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
addProduct(BuildContext context) {
|
||
context.read<DiaryBloc>().add(AddPortionEvent());
|
||
}
|
||
|
||
/// Section Widget
|
||
_buildAddPhoto(BuildContext context, DiaryStateLoaded state) {
|
||
bool isLoading = state.isLoading;
|
||
if (state.currentMeal.photos.isEmpty)
|
||
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), // Замените на свой прелоадер при необходимости
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
else
|
||
return Container();
|
||
}
|
||
|
||
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((Product item) => item.name).toList();
|
||
FocusNode focusNodeName = FocusNode();
|
||
return Container(
|
||
padding: EdgeInsets.symmetric(
|
||
horizontal: 24.h,
|
||
vertical: 12.v,
|
||
),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||
),
|
||
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: 24.v),
|
||
_buildAddProduct(context, state),
|
||
SizedBox(height: 24.v),
|
||
if (!state.diary.state.has_chosen_diet) ...state.portions.map((portion) => _buildProduct(context, portion)).toList(),
|
||
if (state.diary.state.has_chosen_diet) ...state.portions.map((portion) => _buildProductDiet(context, portion)).toList(),
|
||
SizedBox(height: 24.v),
|
||
_buildCarousel(context, state),
|
||
SizedBox(height: 24.v),
|
||
_buildAddPhoto(context, state),
|
||
SizedBox(height: 24.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.product_nameController,
|
||
),
|
||
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.product_nameController.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 () {
|
||
context.read<DiaryBloc>().add(
|
||
SendModeration(
|
||
name: name,
|
||
energyOn100gr: energyOn100gr,
|
||
proteinsOn100gr: proteinsOn100gr,
|
||
fatsOn100gr: fatsOn100gr,
|
||
carbohydratesOn100gr: carbohydratesOn100gr,
|
||
),
|
||
);
|
||
Navigator.of(dialogContext).pop();
|
||
};
|
||
}
|
||
|
||
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.product_nameController.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: 200.0),
|
||
items: state.currentMeal.photos.map<Widget>((photo) {
|
||
return _buildPhoto(context, photo, state);
|
||
}).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.product_name.length > 8 ? portion.product_name.substring(0, 8) : portion.product_name,
|
||
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,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
_buildProductDiet(BuildContext context, Portion portion) {
|
||
return Container(
|
||
margin: EdgeInsets.symmetric(
|
||
horizontal: 10.h,
|
||
vertical: 4.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: [
|
||
Checkbox(
|
||
value: !portion.from_diet,
|
||
onChanged: (bool? value) => context.read<DiaryBloc>().add(ConfirmPortion(portion.id)),
|
||
),
|
||
Padding(
|
||
padding: EdgeInsets.only(
|
||
left: 7.h,
|
||
top: 5.v,
|
||
),
|
||
child: Text(
|
||
portion.product_name.length > 18 ? portion.product_name.substring(0, 18) : portion.product_name,
|
||
style: CustomTextStyles.titleMedium16,
|
||
),
|
||
),
|
||
Spacer(),
|
||
Padding(
|
||
padding: EdgeInsets.only(
|
||
left: 20.h,
|
||
top: 3.v,
|
||
bottom: 3.v,
|
||
),
|
||
child: Text(
|
||
portion.quantity.toString() + ' ' + portion.portion.toString().tr,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
_buildPhoto(BuildContext context, Photo photo, DiaryStateLoaded state) {
|
||
return Stack(
|
||
children: [
|
||
Container(
|
||
margin: EdgeInsets.symmetric(horizontal: 10.h, vertical: 10.h),
|
||
decoration: AppDecoration.fillGray.copyWith(
|
||
borderRadius: BorderRadiusStyle.roundedBorder5,
|
||
),
|
||
child: Image.network(
|
||
baseUrl + '/storage/' + photo.path,
|
||
fit: BoxFit.cover, // Чтобы изображение занимало весь контейнер
|
||
),
|
||
),
|
||
Positioned(
|
||
bottom: 0,
|
||
left: 0,
|
||
right: 0,
|
||
child: Container(
|
||
height: 50.h, // Высота затемненной области
|
||
decoration: BoxDecoration(
|
||
gradient: LinearGradient(
|
||
colors: [
|
||
Colors.black.withOpacity(0.7), // Цвет затемнения
|
||
Colors.transparent,
|
||
],
|
||
begin: Alignment.bottomCenter,
|
||
end: Alignment.topCenter,
|
||
),
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||
children: [
|
||
IconButton(
|
||
icon: Icon(Icons.photo_outlined),
|
||
color: Colors.white,
|
||
onPressed: addImage(context, state.isLoading),
|
||
),
|
||
IconButton(
|
||
icon: Icon(Icons.delete_outlined),
|
||
color: Colors.white,
|
||
onPressed: removePhoto(context, photo),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
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));
|
||
};
|
||
}
|
||
|
||
_buildComment(BuildContext context, DiaryStateLoaded state) {
|
||
return Container(
|
||
padding: EdgeInsets.symmetric(
|
||
horizontal: 24.h,
|
||
vertical: 12.v,
|
||
),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||
),
|
||
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(
|
||
padding: EdgeInsets.symmetric(
|
||
horizontal: 24.h,
|
||
vertical: 12.v,
|
||
),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||
),
|
||
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(
|
||
padding: EdgeInsets.symmetric(
|
||
horizontal: 24.h,
|
||
vertical: 12.v,
|
||
),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||
),
|
||
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,
|
||
);
|
||
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,
|
||
);
|
||
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();
|
||
}
|