feat: diary

This commit is contained in:
Alex
2024-11-30 13:44:26 +03:00
parent 235d9fa08e
commit 06e8336cd3
14 changed files with 574 additions and 396 deletions

View File

@@ -164,4 +164,15 @@ class DiaryCubit extends Cubit<DiaryState> {
carbohydrates: carbohydrates,
);
}
fillTime({
required int diaryId,
required String time,
}) async {
return await diaryRepository.fillTime(
diaryId: diaryId,
mealId: selectedMeal,
time: time,
);
}
}

View File

@@ -1,5 +1,9 @@
import 'package:eatvio_app/core/theme/theme_helper.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/add_product_modal.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class AddProductButtonWidget extends StatefulWidget {
const AddProductButtonWidget({super.key});
@@ -9,31 +13,61 @@ class AddProductButtonWidget extends StatefulWidget {
}
class _AddProductButtonWidgetState extends State<AddProductButtonWidget> {
late final DiaryCubit diaryCubit;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {}, // todo open modal for product add
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,
),
),
),
void initState() {
super.initState();
diaryCubit = context.read<DiaryCubit>();
}
addProductInMeal(DiaryStateLoaded state, [int? portionId]) {
return showModalBottomSheet(
useRootNavigator: true,
context: context,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
),
isScrollControlled: true,
builder: (context) => AddProductModal(
state: state,
diaryCubit: diaryCubit,
),
);
}
@override
Widget build(BuildContext context) {
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (context, state) {
if (state is DiaryStateLoaded) {
return InkWell(
onTap: () => addProductInMeal(state),
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,
),
),
),
),
);
}
return const SizedBox.shrink();
},
);
}
}

View File

@@ -1,220 +0,0 @@
// import 'package:flutter/material.dart';
//
// class AddProductInMealWidget extends StatefulWidget {
// const AddProductInMealWidget({super.key});
//
// @override
// State<AddProductInMealWidget> createState() => _AddProductInMealWidgetState();
// }
//
// class _AddProductInMealWidgetState extends State<AddProductInMealWidget> {
// addProductInMeal(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),
// Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// SizedBox(
// width: MediaQuery.of(context).size.width * 0.8 - 32,
// child: 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) async {
// setState(() {
// selectedGlossary = newSelectedGlossary;
// productNameController.text = selectedGlossary!.name;
// });
// },
// suggestionsCallback: (query) async {
// if (query.length < 1) return [];
// final suggestionsList = await diaryCubit.getGlossaryListByQuery(query);
// return suggestionsList;
// },
// builder: (context, controller, focusNode) {
// return CustomTextFormField(
// controller: controller,
// autofocus: true,
// focusNode: focusNode,
// hintText: 'Введите название продукта/блюда',
// );
// },
// emptyBuilder: (context) {
// return const SizedBox.shrink();
// },
// errorBuilder: (context, object) => Padding(
// padding: const EdgeInsets.all(8.0),
// child: Text('Нет подходящих вариантов'),
// ),
// ),
// ),
// const SizedBox(width: 4),
// SizedBox(
// width: MediaQuery.of(context).size.width * 0.2 - 4,
// child: CustomTextFormField(
// controller: quantityController,
// keyboardType: TextInputType.number,
// hintText: selectedGlossary == null
// ? '100'
// : selectedGlossary!.grInPortion == 0
// ? '100'
// : '1',
// suffixText: selectedGlossary == null
// ? 'гр'
// : 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 == null
// ? 'gr'
// : 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 == null
// ? 'gr'
// : selectedGlossary!.grInPortion == 0
// ? 'gr'
// : 'pcs',
// quantity: int.parse(quantityController.text),
// date: _selectedDay.toString(),
// );
// }
// Navigator.of(context).pop();
// if (selectedGlossary == null) {
// showModalBottomSheet(
// isScrollControlled: true,
// useRootNavigator: true,
// context: context,
// builder: (context) => Padding(
// padding: EdgeInsets.only(
// bottom: MediaQuery.of(context).viewInsets.bottom,
// ),
// child: GlossaryModerationRequestModalWidget(
// productName: productNameController.text,
// diaryCubit: diaryCubit,
// ),
// ),
// );
// }
// },
// 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,
// ),
// ),
// ),
// ),
// ),
// ],
// ),
// ],
// ),
// );
// },
// );
// },
// );
// }
//
// @override
// Widget build(BuildContext context) {
// return const Placeholder();
// }
// }

View File

@@ -0,0 +1,234 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/custom_divider.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/diary/models/diary_day_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/glossary_moderation_request_modal_widget.dart';
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
class AddProductModal extends StatefulWidget {
final Portion? portion;
final DiaryStateLoaded state;
final DiaryCubit diaryCubit;
const AddProductModal({
super.key,
this.portion,
required this.state,
required this.diaryCubit,
});
@override
State<AddProductModal> createState() => _AddProductModalState();
}
class _AddProductModalState extends State<AddProductModal> {
final productNameController = TextEditingController();
final quantityController = TextEditingController();
GlossaryModel? selectedGlossary;
@override
void initState() {
super.initState();
if (widget.portion != null) {
productNameController.text = widget.portion!.productName;
quantityController.text = '${widget.portion!.quantity}';
}
}
@override
Widget build(BuildContext 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),
Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.8 - 32,
child: 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) => CustomDivider(),
onSelected: (newSelectedGlossary) async {
setState(() {
selectedGlossary = newSelectedGlossary;
productNameController.text = selectedGlossary!.name;
});
},
suggestionsCallback: (query) async {
if (query.length < 1) return [];
final suggestionsList = await widget.diaryCubit.getGlossaryListByQuery(query);
return suggestionsList;
},
builder: (context, controller, focusNode) {
return CustomTextFormField(
controller: controller,
autofocus: true,
focusNode: focusNode,
hintText: 'Введите название продукта/блюда',
);
},
emptyBuilder: (context) {
return const SizedBox.shrink();
},
errorBuilder: (context, object) => Padding(
padding: const EdgeInsets.all(8.0),
child: Text('Нет подходящих вариантов'),
),
),
),
const SizedBox(width: 4),
SizedBox(
width: MediaQuery.of(context).size.width * 0.2 - 4,
child: CustomTextFormField(
controller: quantityController,
keyboardType: TextInputType.number,
hintText: selectedGlossary == null
? '100'
: selectedGlossary!.grInPortion == 0
? '100'
: '1',
suffixText: selectedGlossary == null
? 'гр'
: selectedGlossary?.grInPortion == 0
? 'гр'
: 'шт',
),
),
],
),
const SizedBox(height: 16),
Row(
children: [
if (widget.portion != null)
InkWell(
onTap: () {
widget.diaryCubit.deleteProductFromMeal(
diaryId: widget.state.diary.id,
mealId: widget.state.selectedMeal,
portionId: widget.portion!.id,
);
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 (widget.portion != null) const SizedBox(width: 8),
InkWell(
onTap: () {
if (widget.portion == null) {
widget.diaryCubit.addProductToMeal(
diaryId: widget.state.diary.id,
mealId: widget.state.selectedMeal,
productName: productNameController.text,
portion: selectedGlossary == null
? 'gr'
: selectedGlossary!.grInPortion == 0
? 'gr'
: 'pcs',
quantity: int.parse(quantityController.text),
);
} else {
widget.diaryCubit.updateProductInMeal(
diaryId: widget.state.diary.id,
mealId: widget.state.selectedMeal,
portionId: widget.portion!.id,
productName: productNameController.text,
portion: selectedGlossary == null
? 'gr'
: selectedGlossary!.grInPortion == 0
? 'gr'
: 'pcs',
quantity: int.parse(quantityController.text),
);
}
Navigator.of(context).pop();
if (selectedGlossary == null) {
showModalBottomSheet(
isScrollControlled: true,
useRootNavigator: true,
context: context,
builder: (context) => Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: GlossaryModerationRequestModalWidget(
productName: productNameController.text,
diaryCubit: widget.diaryCubit,
),
),
);
}
},
borderRadius: BorderRadius.circular(20),
child: Ink(
width: MediaQuery.of(context).size.width - (widget.portion == null ? 32 : 92),
height: 52,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: appTheme.blueA400,
),
child: Center(
child: Text(
widget.portion == null ? 'Добавить' : 'Сохранить',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
),
],
),
],
),
);
},
);
}
}

View File

@@ -2,6 +2,7 @@ import 'package:eatvio_app/features/diary/presentation/widgets/add_product_butto
import 'package:eatvio_app/features/diary/presentation/widgets/meal_button_widget.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/photo_gallery_widget.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/products_list_widget.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/time_of_meal_picker_widget.dart';
import 'package:flutter/material.dart';
class MealWidget extends StatefulWidget {
@@ -42,10 +43,12 @@ class _MealWidgetState extends State<MealWidget> {
],
),
const SizedBox(height: 12),
// TimeOfMealPickerWidget(), // TODO endpoint for save time of meal
TimeOfMealPickerWidget(),
const SizedBox(height: 12),
ProductsListWidget(),
const SizedBox(height: 12),
AddProductButtonWidget(),
const SizedBox(height: 12),
PhotoGalleryWidget(),
],
),

View File

@@ -20,8 +20,15 @@ class PhotoGalleryWidget extends StatefulWidget {
}
class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
late final DiaryCubit diaryCubit;
int currentSelectedPhotoIndex = 0;
@override
void initState() {
super.initState();
diaryCubit = context.read<DiaryCubit>();
}
showImagePicker(DiaryDayModel diaryDay) {
showCupertinoModalPopup<void>(
context: context,
@@ -33,7 +40,7 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
final picker = ImagePicker();
XFile? image = await picker.pickImage(source: ImageSource.camera);
if (image != null)
context.read<DiaryCubit>().uploadPhotos(
diaryCubit.uploadPhotos(
files: [File(image.path)],
diaryId: diaryDay.id,
);
@@ -52,10 +59,10 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
final picker = ImagePicker();
List<XFile>? images = await picker.pickMultiImage();
if (images.isNotEmpty)
context.read<DiaryCubit>().uploadPhotos(
files: images.map((f) => File(f.path)).toList(),
diaryId: diaryDay.id,
);
diaryCubit.uploadPhotos(
files: images.map((f) => File(f.path)).toList(),
diaryId: diaryDay.id,
);
},
child: const Row(
children: const [
@@ -80,7 +87,8 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (context, state) {
if (state is DiaryStateLoaded) {
if (state.diary.meals[state.selectedMeal].photos.isEmpty) {
int mealIndex = state.selectedMeal - 1;
if (state.diary.meals[mealIndex].photos.isEmpty) {
return GestureDetector(
onTap: () => showImagePicker(state.diary),
child: SvgPicture.asset(
@@ -99,7 +107,7 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + state.diary.meals[state.selectedMeal].photos[currentSelectedPhotoIndex].path,
imageUrl: baseUrl + '/storage/' + state.diary.meals[mealIndex].photos[currentSelectedPhotoIndex].path,
),
),
Padding(
@@ -110,13 +118,13 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
elevation: 0,
),
onPressed: () async {
await context.read<DiaryCubit>().deletePhoto(
photo: state.diary.meals[state.selectedMeal].photos[currentSelectedPhotoIndex],
await diaryCubit.deletePhoto(
photo: state.diary.meals[mealIndex].photos[currentSelectedPhotoIndex],
diaryId: state.diary.id,
);
setState(() {
if (currentSelectedPhotoIndex == state.diary.meals[state.selectedMeal].photos.length - 1 && currentSelectedPhotoIndex != 0) {
currentSelectedPhotoIndex = state.diary.meals[state.selectedMeal].photos.length - 2;
if (currentSelectedPhotoIndex == state.diary.meals[mealIndex].photos.length - 1 && currentSelectedPhotoIndex != 0) {
currentSelectedPhotoIndex = state.diary.meals[mealIndex].photos.length - 2;
}
});
},
@@ -136,8 +144,8 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
for (int i = 0; i < state.diary.meals[state.selectedMeal].photos.length + 1; i++) ...[
if (i == state.diary.meals[state.selectedMeal].photos.length)
for (int i = 0; i < state.diary.meals[mealIndex].photos.length + 1; i++) ...[
if (i == state.diary.meals[mealIndex].photos.length)
Padding(
padding: const EdgeInsets.only(left: 8),
child: InkWell(
@@ -157,7 +165,7 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
),
),
),
if (i < state.diary.meals[state.selectedMeal].photos.length)
if (i < state.diary.meals[mealIndex].photos.length)
GestureDetector(
onTap: () {
setState(() {
@@ -170,13 +178,13 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
height: 52,
width: 52,
child: CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + state.diary.meals[state.selectedMeal].photos[i].path,
imageUrl: baseUrl + '/storage/' + state.diary.meals[mealIndex].photos[i].path,
fit: BoxFit.fitWidth,
),
),
),
),
if (i < state.diary.meals[state.selectedMeal].photos.length - 1) const SizedBox(width: 8),
if (i < state.diary.meals[mealIndex].photos.length - 1) const SizedBox(width: 8),
],
],
),

View File

@@ -1,13 +1,15 @@
import 'package:eatvio_app/features/diary/models/diary_day_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/add_product_modal.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ProductWidget extends StatefulWidget {
final String product, portion;
final int quantity;
final Portion portion;
const ProductWidget({
super.key,
required this.product,
required this.quantity,
required this.portion,
});
@@ -16,17 +18,48 @@ class ProductWidget extends StatefulWidget {
}
class _ProductWidgetState extends State<ProductWidget> {
late final DiaryCubit diaryCubit;
@override
void initState() {
super.initState();
diaryCubit = context.read<DiaryCubit>();
}
updateProductInMeal(DiaryStateLoaded state, Portion portion) {
return showModalBottomSheet(
useRootNavigator: true,
context: context,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
),
isScrollControlled: true,
builder: (context) => AddProductModal(
state: state,
portion: portion,
diaryCubit: diaryCubit,
),
);
}
@override
Widget build(BuildContext context) {
return ListTile(
onTap: () {}, // todo change product in meal
trailing: Icon(Icons.more_vert_rounded),
title: Text(widget.product),
subtitle: Text('${widget.quantity} ${widget.portion == 'gr' ? 'гр' : 'шт'}'),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
tileColor: Color(0xFFF6FAFF),
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (context, state) {
if (state is DiaryStateLoaded) {
return ListTile(
onTap: () => updateProductInMeal(state, widget.portion),
trailing: Icon(Icons.more_vert_rounded),
title: Text(widget.portion.productName),
subtitle: Text('${widget.portion.quantity} ${widget.portion == 'gr' ? 'гр' : 'шт'}'),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
tileColor: Color(0xFFF6FAFF),
);
}
return const SizedBox.shrink();
},
);
}
}

View File

@@ -17,16 +17,13 @@ class _ProductsListWidgetState extends State<ProductsListWidget> {
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (BuildContext context, DiaryState state) {
if (state is DiaryStateLoaded) {
int mealIndex = state.selectedMeal - 1;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
for (int i = 0; i < state.diary.meals[state.selectedMeal].portions.length; i++) ...[
ProductWidget(
product: state.diary.meals[state.selectedMeal].portions[i].productName,
quantity: state.diary.meals[state.selectedMeal].portions[i].quantity,
portion: state.diary.meals[state.selectedMeal].portions[i].portion,
),
i < state.diary.meals[state.selectedMeal].portions.length - 1 ? const SizedBox(height: 8) : const SizedBox.shrink(),
for (int i = 0; i < state.diary.meals[mealIndex].portions.length; i++) ...[
ProductWidget(portion: state.diary.meals[mealIndex].portions[i]),
i < state.diary.meals[mealIndex].portions.length - 1 ? const SizedBox(height: 8) : const SizedBox.shrink(),
],
],
);

View File

@@ -1,7 +1,10 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/custom_divider.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:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class TimeOfMealPickerWidget extends StatefulWidget {
const TimeOfMealPickerWidget({super.key});
@@ -11,10 +14,17 @@ class TimeOfMealPickerWidget extends StatefulWidget {
}
class _TimeOfMealPickerWidgetState extends State<TimeOfMealPickerWidget> {
late final DiaryCubit diaryCubit;
String time = "${DateTime.now().hour.toString().padLeft(2, '0')}:${DateTime.now().minute.toString().padLeft(2, '0')}";
DateTime _time = DateTime.now();
showTimePicker() {
@override
void initState() {
super.initState();
diaryCubit = context.read<DiaryCubit>();
}
showTimePicker(int diaryId) {
showModalBottomSheet<void>(
context: context,
useRootNavigator: true,
@@ -38,6 +48,10 @@ class _TimeOfMealPickerWidgetState extends State<TimeOfMealPickerWidget> {
InkWell(
onTap: () {
Navigator.of(context).pop();
diaryCubit.fillTime(
diaryId: diaryId,
time: '$time:00',
);
},
borderRadius: BorderRadius.circular(20),
child: Ink(
@@ -67,50 +81,65 @@ class _TimeOfMealPickerWidgetState extends State<TimeOfMealPickerWidget> {
@override
Widget build(BuildContext context) {
return Column(
children: [
CustomDivider(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return BlocConsumer<DiaryCubit, DiaryState>(
listener: (BuildContext context, DiaryState state) {
if (state is DiaryStateLoaded) {
setState(() {
time = state.diary.meals[state.selectedMeal - 1].time;
_time = DateTime.parse('0000-00-00 ${state.diary.meals[state.selectedMeal - 1].time}');
});
}
},
builder: (context, state) {
if (state is DiaryStateLoaded) {
return Column(
children: [
Text(
'Время приема пищи',
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
InkWell(
onTap: () => showTimePicker(),
borderRadius: BorderRadius.circular(100),
child: Ink(
height: 32,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: Color(0xFFF1F2F6),
),
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text(
time,
CustomDivider(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Время приема пищи',
style: TextStyle(
color: Color(0xFF6D6F71),
fontSize: 17,
color: Color(0xFF323F4B),
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
InkWell(
onTap: () => showTimePicker(state.diary.id),
borderRadius: BorderRadius.circular(100),
child: Ink(
height: 32,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: Color(0xFFF1F2F6),
),
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text(
time,
style: TextStyle(
color: Color(0xFF6D6F71),
fontSize: 17,
fontWeight: FontWeight.w500,
),
),
),
),
),
],
),
),
CustomDivider(),
],
),
),
CustomDivider(),
],
);
}
return const SizedBox.shrink();
},
);
}
}

View File

@@ -21,7 +21,7 @@ class DiaryRepository {
}
Future<NutritionDataModel> getNutritionData(int index) async {
return await diaryService.getNutrition(index: index);
return await diaryService.getNutrition(diary: index);
}
Future<void> addPortionToMeal({
@@ -32,8 +32,8 @@ class DiaryRepository {
required int quantity,
}) async {
return await diaryService.addProductToMeal(
diaryId: diaryId,
mealId: mealId,
diary: diaryId,
typeNo: mealId,
productName: productName,
portion: portion,
quantity: quantity,
@@ -49,9 +49,9 @@ class DiaryRepository {
required int quantity,
}) async {
return await diaryService.updateProductInMeal(
diaryId: diaryId,
mealId: mealId,
portionId: portionId,
diary: diaryId,
typeNo: mealId,
portionModel: portionId,
productInMeal: ProductInMeal(
productName: productName,
portion: portion,
@@ -66,9 +66,9 @@ class DiaryRepository {
required int portionId,
}) async {
return await diaryService.deleteProductFromMeal(
diaryId: diaryId,
mealId: mealId,
portionId: portionId,
diary: diaryId,
typeNo: mealId,
portionModel: portionId,
);
}
@@ -80,8 +80,8 @@ class DiaryRepository {
try {
for (int i = 0; i < files.length; i++) {
await diaryService.uploadPhotoOfMeal(
diaryId: diaryId,
mealId: mealId,
diary: diaryId,
typeNo: mealId,
photo: files[i],
);
}
@@ -98,9 +98,8 @@ class DiaryRepository {
try {
await diaryService.deletePhotoOfMeal(
diaryId: diaryId,
mealId: mealId,
id: photo.id,
path: photo.path,
typeNo: mealId,
userAttachment: photo.id,
);
} catch (e) {
rethrow;
@@ -114,7 +113,7 @@ class DiaryRepository {
required String comment,
}) async {
return await diaryService.finishDiaryDay(
diaryId: diaryId,
diary: diaryId,
finishDiaryDayModel: FinishDiaryDayModel(
wakeUp: wakeUp,
retire: retire,
@@ -143,11 +142,31 @@ class DiaryRepository {
Future<void> fillSatiety({
required int diaryId,
required int meal,
required int mealId,
required int satiety,
}) async {
try {
return await diaryService.fillSatiety(diaryId: diaryId, meal: meal, satiety: satiety);
return await diaryService.fillSatiety(
diary: diaryId,
typeNo: mealId,
satiety: satiety,
);
} catch (e) {
rethrow;
}
}
Future<void> fillTime({
required int diaryId,
required int mealId,
required String time,
}) async {
try {
return await diaryService.fillTime(
diary: diaryId,
typeNo: mealId,
time: time,
);
} catch (e) {
rethrow;
}

View File

@@ -16,69 +16,75 @@ abstract class DiaryService {
@Path('date') required String date,
});
@GET('/api/diary/{diary_id}/show')
@GET('/api/diary/{diary}/show')
Future<DiaryDayWrapperModel> getDiaryDay({
@Path('diary_id') required int index,
@Path('diary') required int diary,
});
@GET('/api/diary/{diary_id}/dailyNutritionData')
@GET('/api/diary/{diary}/dailyNutritionData')
Future<NutritionDataModel> getNutrition({
@Path('diary_id') required int index,
@Path('diary') required int diary,
});
@POST('/api/diary/{diary_id}/meal/{meal_id}/photo/upload')
@POST('/api/diary/{diary}/meals/{type_no}/photo/upload')
@MultiPart()
Future<void> uploadPhotoOfMeal({
@Path('diary_id') required int diaryId,
@Path('meal_id') required int mealId,
@Path('diary') required int diary,
@Path('type_no') required int typeNo,
@Part(name: 'photo') required File photo,
});
@DELETE('/api/diary/{diary_id}/meal/{meal_id}/photo/delete')
@DELETE('/api/diary/{diary}/meals/{type_no}/photo/{user_attachment}/delete')
Future<void> deletePhotoOfMeal({
@Path('diary_id') required int diaryId,
@Path('meal_id') required int mealId,
@Query('id') required int id,
@Query('path') required String path,
@Path('diary') required int diaryId,
@Path('type_no') required int typeNo,
@Path('user_attachment') required int userAttachment,
});
@POST('/api/diary/{diary_id}/meal/{meal_id}/portions/create')
@POST('/api/diary/{diary}/meals/{type_no}/portion/create')
Future<void> addProductToMeal({
@Path('diary_id') required int diaryId,
@Path('meal_id') required int mealId,
@Path('diary') required int diary,
@Path('type_no') required int typeNo,
@Part(name: 'product_name') required String productName,
@Part(name: 'portion') required String portion,
@Part(name: 'quantity') required int quantity,
});
@PUT('/api/diary/{diary_id}/meal/{meal_id}/portions/{portion_id}/update')
@PUT('/api/diary/{diary}/meals/{type_no}/portions/{portionModel}/update')
Future<void> updateProductInMeal({
@Path('diary_id') required int diaryId,
@Path('meal_id') required int mealId,
@Path('portion_id') required int portionId,
@Path('diary') required int diary,
@Path('type_no') required int typeNo,
@Path('portionModel') required int portionModel,
@Body() required ProductInMeal productInMeal,
});
@DELETE('/api/diary/{diary_id}/meal/{meal_id}/portions/{portion_id}/delete')
@DELETE('/api/diary/{diary}/meals/{type_no}/portions/{portionModel}/delete')
Future<void> deleteProductFromMeal({
@Path('diary_id') required int diaryId,
@Path('meal_id') required int mealId,
@Path('portion_id') required int portionId,
@Path('diary') required int diary,
@Path('type_no') required int typeNo,
@Path('portionModel') required int portionModel,
});
@POST('/api/diary/{diary_id}/finishDay')
@POST('/api/diary/{diary}/finishDay')
Future<void> finishDiaryDay({
@Path('diary_id') required int diaryId,
@Path('diary') required int diary,
@Body() required FinishDiaryDayModel finishDiaryDayModel,
});
@POST('/api/diary/{diary_id}/meal/{meal}/satiety/fill')
@POST('/api/diary/{diary}/meals/{type_no}/satiety/fill')
Future<void> fillSatiety({
@Path('diary_id') required int diaryId,
@Path('meal') required int meal,
@Path('diary') required int diary,
@Path('type_no') required int typeNo,
@Field() required int satiety,
});
@POST('/api/diary/{diary}/meals/{type_no}/time/fill')
Future<void> fillTime({
@Path('diary') required int diary,
@Path('type_no') required int typeNo,
@Field() required String time,
});
@POST('/api/diary/product_nutritional_values/sendToModeration')
Future<void> sendProductToModeration({
@Body() required Map<String, dynamic> data,

View File

@@ -58,7 +58,7 @@ class _DiaryService implements DiaryService {
}
@override
Future<DiaryDayWrapperModel> getDiaryDay({required int index}) async {
Future<DiaryDayWrapperModel> getDiaryDay({required int diary}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
@@ -70,7 +70,7 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${index}/show',
'/api/diary/${diary}/show',
queryParameters: queryParameters,
data: _data,
)
@@ -91,7 +91,7 @@ class _DiaryService implements DiaryService {
}
@override
Future<NutritionDataModel> getNutrition({required int index}) async {
Future<NutritionDataModel> getNutrition({required int diary}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
@@ -103,7 +103,7 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${index}/dailyNutritionData',
'/api/diary/${diary}/dailyNutritionData',
queryParameters: queryParameters,
data: _data,
)
@@ -125,8 +125,8 @@ class _DiaryService implements DiaryService {
@override
Future<void> uploadPhotoOfMeal({
required int diaryId,
required int mealId,
required int diary,
required int typeNo,
required File photo,
}) async {
final _extra = <String, dynamic>{};
@@ -148,7 +148,7 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${diaryId}/meal/${mealId}/photo/upload',
'/api/diary/${diary}/meals/${typeNo}/photo/upload',
queryParameters: queryParameters,
data: _data,
)
@@ -163,15 +163,11 @@ class _DiaryService implements DiaryService {
@override
Future<void> deletePhotoOfMeal({
required int diaryId,
required int mealId,
required int id,
required String path,
required int typeNo,
required int userAttachment,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{
r'id': id,
r'path': path,
};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<void>(Options(
@@ -181,7 +177,7 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${diaryId}/meal/${mealId}/photo/delete',
'/api/diary/${diaryId}/meals/${typeNo}/photo/${userAttachment}/delete',
queryParameters: queryParameters,
data: _data,
)
@@ -195,8 +191,8 @@ class _DiaryService implements DiaryService {
@override
Future<void> addProductToMeal({
required int diaryId,
required int mealId,
required int diary,
required int typeNo,
required String productName,
required String portion,
required int quantity,
@@ -224,7 +220,7 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${diaryId}/meal/${mealId}/portions/create',
'/api/diary/${diary}/meals/${typeNo}/portion/create',
queryParameters: queryParameters,
data: _data,
)
@@ -238,9 +234,9 @@ class _DiaryService implements DiaryService {
@override
Future<void> updateProductInMeal({
required int diaryId,
required int mealId,
required int portionId,
required int diary,
required int typeNo,
required int portionModel,
required ProductInMeal productInMeal,
}) async {
final _extra = <String, dynamic>{};
@@ -254,7 +250,7 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${diaryId}/meal/${mealId}/portions/${portionId}/update',
'/api/diary/${diary}/meals/${typeNo}/portions/${portionModel}/update',
queryParameters: queryParameters,
data: _data,
)
@@ -268,9 +264,9 @@ class _DiaryService implements DiaryService {
@override
Future<void> deleteProductFromMeal({
required int diaryId,
required int mealId,
required int portionId,
required int diary,
required int typeNo,
required int portionModel,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
@@ -283,7 +279,7 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${diaryId}/meal/${mealId}/portions/${portionId}/delete',
'/api/diary/${diary}/meals/${typeNo}/portions/${portionModel}/delete',
queryParameters: queryParameters,
data: _data,
)
@@ -297,7 +293,7 @@ class _DiaryService implements DiaryService {
@override
Future<void> finishDiaryDay({
required int diaryId,
required int diary,
required FinishDiaryDayModel finishDiaryDayModel,
}) async {
final _extra = <String, dynamic>{};
@@ -311,7 +307,7 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${diaryId}/finishDay',
'/api/diary/${diary}/finishDay',
queryParameters: queryParameters,
data: _data,
)
@@ -325,8 +321,8 @@ class _DiaryService implements DiaryService {
@override
Future<void> fillSatiety({
required int diaryId,
required int meal,
required int diary,
required int typeNo,
required int satiety,
}) async {
final _extra = <String, dynamic>{};
@@ -340,7 +336,36 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${diaryId}/meal/${meal}/satiety/fill',
'/api/diary/${diary}/meals/${typeNo}/satiety/fill',
queryParameters: queryParameters,
data: _data,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
)));
await _dio.fetch<void>(_options);
}
@override
Future<void> fillTime({
required int diary,
required int typeNo,
required String time,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = {'time': time};
final _options = _setStreamType<void>(Options(
method: 'POST',
headers: _headers,
extra: _extra,
)
.compose(
_dio.options,
'/api/diary/${diary}/meals/${typeNo}/time/fill',
queryParameters: queryParameters,
data: _data,
)

View File

@@ -17,8 +17,7 @@ void main() async {
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
await di.init();
await Future.delayed(Duration(milliseconds: 250));
FlutterNativeSplash.remove();
await Future.delayed(Duration(seconds: 1), () => FlutterNativeSplash.remove());
runApp(MyApp());
}

View File

@@ -1,6 +1,6 @@
name: eatvio_app
description: A new Flutter project.
version: 1.0.7+22
version: 1.0.8+23
publish_to: "none"
environment: