feat(diary): wakeup, retire, mood, comment

This commit is contained in:
mso7nueh
2025-03-24 17:37:47 +03:00
parent fcdc57493d
commit a871dd8951
21 changed files with 767 additions and 165 deletions

View File

@@ -17,10 +17,7 @@ class DiaryCubit extends Cubit<DiaryState> {
final DiaryRepository diaryRepository;
final GlossaryRepository glossaryRepository;
DiaryCubit({
required this.diaryRepository,
required this.glossaryRepository,
}) : super(const DiaryState.loading()) {
DiaryCubit({required this.diaryRepository, required this.glossaryRepository}) : super(const DiaryState.loading()) {
today = DateTime.now();
selectedDay = today;
}
@@ -56,16 +53,8 @@ class DiaryCubit extends Cubit<DiaryState> {
}
}
finishDiaryDay({
required int diaryId,
required String wakeUp,
required String retire,
}) async {
await diaryRepository.finishDiaryDay(
diaryId: diaryId,
wakeUp: wakeUp,
retire: retire,
);
finishDiaryDay({required int diaryId, required String wakeUp, required String retire, String? comment}) async {
await diaryRepository.finishDiaryDay(diaryId: diaryId, wakeUp: wakeUp, retire: retire, comment: comment);
await fetchDiary();
}
@@ -126,55 +115,23 @@ class DiaryCubit extends Cubit<DiaryState> {
await fetchDiary();
}
markEaten({
required int diaryId,
required int mealId,
required int portionId,
}) async {
await diaryRepository.markEaten(
diaryId: diaryId,
mealId: mealId,
portionId: portionId,
);
markEaten({required int diaryId, required int mealId, required int portionId}) async {
await diaryRepository.markEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
await fetchDiary();
}
unmarkEaten({
required int diaryId,
required int mealId,
required int portionId,
}) async {
await diaryRepository.unmarkEaten(
diaryId: diaryId,
mealId: mealId,
portionId: portionId,
);
unmarkEaten({required int diaryId, required int mealId, required int portionId}) async {
await diaryRepository.unmarkEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
await fetchDiary();
}
restore({
required int diaryId,
required int mealId,
required int portionId,
}) async {
await diaryRepository.restore(
diaryId: diaryId,
mealId: mealId,
portionId: portionId,
);
restore({required int diaryId, required int mealId, required int portionId}) async {
await diaryRepository.restore(diaryId: diaryId, mealId: mealId, portionId: portionId);
await fetchDiary();
}
deleteProductFromMeal({
required int diaryId,
required int mealId,
required int portionId,
}) async {
await diaryRepository.deleteProductFromMeal(
diaryId: diaryId,
mealId: mealId,
portionId: portionId,
);
deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async {
await diaryRepository.deleteProductFromMeal(diaryId: diaryId, mealId: mealId, portionId: portionId);
await fetchDiary();
}
@@ -182,27 +139,13 @@ class DiaryCubit extends Cubit<DiaryState> {
return await glossaryRepository.getGlossaryListByQuery(query);
}
uploadPhotos({
required List<File> files,
required int diaryId,
}) async {
await diaryRepository.uploadPhotos(
files: await ImageCompressUtility.compressFilesList(files),
diaryId: diaryId,
mealId: selectedMeal,
);
uploadPhotos({required List<File> files, required int diaryId}) async {
await diaryRepository.uploadPhotos(files: await ImageCompressUtility.compressFilesList(files), diaryId: diaryId, mealId: selectedMeal);
await fetchDiary();
}
deletePhoto({
required DiaryPhotoModel photo,
required int diaryId,
}) async {
await diaryRepository.deletePhoto(
photo: photo,
diaryId: diaryId,
mealId: selectedMeal,
);
deletePhoto({required DiaryPhotoModel photo, required int diaryId}) async {
await diaryRepository.deletePhoto(photo: photo, diaryId: diaryId, mealId: selectedMeal);
await fetchDiary();
}
@@ -214,39 +157,37 @@ class DiaryCubit extends Cubit<DiaryState> {
required double carbohydrates,
}) async {
try {
return await diaryRepository.sendProductToModeration(
name: name,
energy: energy,
proteins: proteins,
fats: fats,
carbohydrates: carbohydrates,
);
return await diaryRepository.sendProductToModeration(name: name, energy: energy, proteins: proteins, fats: fats, carbohydrates: carbohydrates);
} catch (e, stackTrace) {
log(e.toString(), stackTrace: stackTrace);
}
return null;
}
fillTime({
required int diaryId,
required String time,
}) async {
await diaryRepository.fillTime(
diaryId: diaryId,
mealId: selectedMeal,
time: time,
);
fillTime({required int diaryId, required String time}) async {
await diaryRepository.fillTime(diaryId: diaryId, mealId: selectedMeal, time: time);
await fetchDiary();
}
fillSatiety({
required int diaryId,
required int satiety,
}) async {
await diaryRepository.fillSatiety(
diaryId: diaryId,
mealId: selectedMeal,
satiety: satiety,
);
fillWakeup({required int diaryId, required String time}) async {
await diaryRepository.fillWakeup(diaryId: diaryId, time: time);
await fetchDiary();
}
fillRetire({required int diaryId, required String time}) async {
await diaryRepository.fillRetire(diaryId: diaryId, time: time);
await fetchDiary();
}
fillSatiety({required int diaryId, required int satiety}) async {
await diaryRepository.fillSatiety(diaryId: diaryId, mealId: selectedMeal, satiety: satiety);
}
fillMood({required int diaryId, required int mood}) async {
await diaryRepository.fillMood(diaryId: diaryId, mealId: selectedMeal, mood: mood);
}
fillComment(String comment) {
emit((state as DiaryStateLoaded).copyWith(diary: (state as DiaryStateLoaded).diary.copyWith(comment: comment)));
}
}

View File

@@ -7,8 +7,10 @@ import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/calendar_widget.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/calories_chart_widget.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/comment_for_consultant_widget.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/finish_day_button_widget.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/meal_widget.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/wakeup_and_retire_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -46,8 +48,12 @@ class _DiaryScreenState extends State<DiaryScreen> {
children: [
CalendarWidget(),
if (state is DiaryStateLoading) const Loader(),
if (state is DiaryStateLoaded) WakeupAndRetireWidget(),
const SizedBox(height: 12),
if (state is DiaryStateLoaded) MealWidget(),
CaloriesChartWidget(),
if (state is DiaryStateLoaded) CaloriesChartWidget(),
const SizedBox(height: 12),
if (state is DiaryStateLoaded) CommentForConsultantWidget(),
if (state is DiaryStateLoaded && state.diary.closed == 0)
Padding(
padding: const EdgeInsets.only(top: 12),

View File

@@ -0,0 +1,44 @@
import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CommentForConsultantWidget extends StatefulWidget {
const CommentForConsultantWidget({super.key});
@override
State<CommentForConsultantWidget> createState() => _CommentForConsultantWidgetState();
}
class _CommentForConsultantWidgetState extends State<CommentForConsultantWidget> {
@override
Widget build(BuildContext context) {
return Material(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Дополнительная информация для консультанта',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF323F4B), height: 1),
),
const SizedBox(height: 12),
CustomTextFormField(
onChanged: (value) {
EasyDebounce.debounce('client-typing', Duration(seconds: 3), () => context.read<DiaryCubit>().fillComment(value));
},
hintText: 'Укажите дополнительную информацию',
minLines: 4,
maxLines: 4,
),
],
),
),
);
}
}

View File

@@ -7,10 +7,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
class FinishDayButtonWidget extends StatefulWidget {
final DiaryDayModel diaryDay;
const FinishDayButtonWidget({
super.key,
required this.diaryDay,
});
const FinishDayButtonWidget({super.key, required this.diaryDay});
@override
State<FinishDayButtonWidget> createState() => _FinishDayButtonWidgetState();
@@ -20,30 +17,22 @@ class _FinishDayButtonWidgetState extends State<FinishDayButtonWidget> {
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => context.read<DiaryCubit>().finishDiaryDay(
onTap:
() => context.read<DiaryCubit>().finishDiaryDay(
diaryId: widget.diaryDay.id,
wakeUp: widget.diaryDay.wakeup.toString(),
retire: widget.diaryDay.retire.toString(),
comment: widget.diaryDay.comment.isEmpty ? null : widget.diaryDay.comment,
),
borderRadius: BorderRadius.circular(20),
child: Ink(
padding: EdgeInsets.symmetric(horizontal: 8),
width: MediaQuery.of(context).size.width - 16,
decoration: BoxDecoration(
color: appTheme.blueA400,
borderRadius: BorderRadius.circular(20),
),
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,
),
),
child: Text('Закончить день', style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600)),
),
),
);

View File

@@ -1,5 +1,6 @@
import 'package:eatvio_app/features/diary/presentation/widgets/add_product_button_widget.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/meal_button_widget.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/mood_rating_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/satiety_rating_widget.dart';
@@ -53,6 +54,8 @@ class _MealWidgetState extends State<MealWidget> {
PhotoGalleryWidget(),
const SizedBox(height: 12),
SatietyRatingWidget(),
const SizedBox(height: 12),
MoodRatingWidget(),
],
),
),

View File

@@ -0,0 +1,199 @@
import 'package:easy_debounce/easy_debounce.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_bloc/flutter_bloc.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:flutter_svg/flutter_svg.dart';
class MoodRatingWidget extends StatefulWidget {
@override
State<MoodRatingWidget> createState() => _MoodRatingWidgetState();
}
class _MoodRatingWidgetState extends State<MoodRatingWidget> {
late double currentSelectedRating;
bool isFirstFetch = true;
@override
Widget build(BuildContext context) {
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (BuildContext context, DiaryState state) {
if (state is DiaryStateLoaded) {
if (isFirstFetch) {
currentSelectedRating = state.diary.meals[state.selectedMeal - 1].mood.toDouble() + 1;
isFirstFetch = false;
}
return Padding(
padding: const EdgeInsets.only(top: 24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Ваше настроение после приёма пищи',
style: TextStyle(
fontSize: 16,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SvgPicture.asset(
'assets/icons/mood_1.svg',
height: 32,
width: 32,
),
RatingBar.builder(
initialRating: state.diary.meals[state.selectedMeal - 1].mood.toDouble() + 1,
itemCount: 6,
itemSize: MediaQuery.of(context).size.width / 6 - (124 / 6),
updateOnDrag: true,
itemBuilder: (context, index) {
switch (index) {
case 0:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 1 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFFC82222),
border: currentSelectedRating == 1
? Border.all(
width: 2,
color: Color(0xFFAA3333),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 1
? BorderRadius.horizontal(
left: Radius.circular(8),
right: Radius.circular(2),
)
: BorderRadius.horizontal(
left: Radius.circular(8),
),
),
);
case 1:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 2 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFFFF3E3E),
border: currentSelectedRating == 2
? Border.all(
width: 2,
color: Color(0xFF550000),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 2 ? BorderRadius.circular(2) : null,
),
);
case 2:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 3 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFFF8C926),
border: currentSelectedRating == 3
? Border.all(
width: 2,
color: Color(0xFF8C5D00),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 3 ? BorderRadius.circular(2) : null,
),
);
case 3:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 4 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFFFFF3BF),
border: currentSelectedRating == 4
? Border.all(
width: 2,
color: Color(0xFFB5911B),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 4 ? BorderRadius.circular(2) : null,
),
);
case 4:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 5 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFF62E833),
border: currentSelectedRating == 5
? Border.all(
width: 2,
color: Color(0xFF276918),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 5 ? BorderRadius.circular(2) : null,
),
);
default:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 6 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFF4DCE20),
border: currentSelectedRating == 6
? Border.all(
width: 2,
color: Color(0xFF184D12),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 6
? BorderRadius.horizontal(
left: Radius.circular(2),
right: Radius.circular(8),
)
: BorderRadius.horizontal(
right: Radius.circular(8),
),
),
);
}
},
onRatingUpdate: (value) {
setState(() {
currentSelectedRating = value;
});
EasyDebounce.debounce(
'change-satiety',
Duration(milliseconds: 250),
() {
context.read<DiaryCubit>().fillMood(diaryId: state.diary.id, mood: currentSelectedRating.toInt() - 1);
},
);
},
),
SvgPicture.asset(
'assets/icons/mood_2.svg',
height: 32,
width: 32,
),
],
),
],
),
);
}
return const SizedBox.shrink();
},
);
}
}

View File

@@ -21,7 +21,7 @@ class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
builder: (BuildContext context, DiaryState state) {
if (state is DiaryStateLoaded) {
if (isFirstFetch) {
currentSelectedRating = state.diary.meals[state.selectedMeal - 1].satiety.toDouble();
currentSelectedRating = state.diary.meals[state.selectedMeal - 1].satiety.toDouble() + 1;
isFirstFetch = false;
}
return Padding(
@@ -44,7 +44,7 @@ class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
width: 32,
),
RatingBar.builder(
initialRating: state.diary.meals[state.selectedMeal - 1].satiety.toDouble(),
initialRating: state.diary.meals[state.selectedMeal - 1].satiety.toDouble() + 1,
itemCount: 6,
itemSize: MediaQuery.of(context).size.width / 6 - (124 / 6),
updateOnDrag: true,
@@ -176,7 +176,7 @@ class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
'change-satiety',
Duration(milliseconds: 250),
() {
context.read<DiaryCubit>().fillSatiety(diaryId: state.diary.id, satiety: currentSelectedRating.toInt());
context.read<DiaryCubit>().fillSatiety(diaryId: state.diary.id, satiety: currentSelectedRating.toInt() - 1);
},
);
},

View File

@@ -0,0 +1,229 @@
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';
import 'package:flutter_svg/flutter_svg.dart';
import '../../../../core/theme/theme_helper.dart';
class WakeupAndRetireWidget extends StatefulWidget {
const WakeupAndRetireWidget({super.key});
@override
State<WakeupAndRetireWidget> createState() => _WakeupAndRetireWidgetState();
}
class _WakeupAndRetireWidgetState extends State<WakeupAndRetireWidget> {
late final DiaryCubit diaryCubit;
DateTime wakeup = DateTime.now().copyWith(hour: 6, minute: 30);
DateTime retire = DateTime.now().copyWith(hour: 22, minute: 30);
@override
void initState() {
super.initState();
diaryCubit = context.read<DiaryCubit>();
}
showTimePicker(int diaryId, bool isWakeup) {
showModalBottomSheet<void>(
context: context,
useRootNavigator: true,
builder:
(context) => SizedBox(
height: MediaQuery.of(context).copyWith().size.height / 3,
child: Column(
children: [
Expanded(
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.time,
use24hFormat: true,
initialDateTime: DateTime.now(),
onDateTimeChanged: (newTime) {
if (isWakeup) {
wakeup = newTime;
} else {
retire = newTime;
}
},
),
),
InkWell(
onTap: () {
Navigator.of(context).pop();
if (isWakeup) {
diaryCubit.fillWakeup(
diaryId: diaryId,
time:
'${wakeup.hour.toString().padLeft(2, '0')}:${wakeup.minute.toString().padLeft(2, '0')}:00',
);
} else {
diaryCubit.fillRetire(
diaryId: diaryId,
time:
'${retire.hour.toString().padLeft(2, '0')}:${retire.minute.toString().padLeft(2, '0')}:00',
);
}
},
borderRadius: BorderRadius.circular(20),
child: Ink(
width: MediaQuery.of(context).size.width - 16,
height: 52,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: appTheme.blueA400,
),
child: Center(
child: Text(
'Сохранить',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (context, state) {
if (state is DiaryStateLoaded) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Row(
children: [
Expanded(
child: Material(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 12,
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset('assets/icons/wakeup.svg'),
const SizedBox(width: 8),
Text(
'Подъём',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
height: 1,
),
),
],
),
const SizedBox(height: 8),
InkWell(
onTap: () => showTimePicker(state.diary.id, true),
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(
state.diary.wakeup.toString().substring(
0,
state.diary.wakeup.toString().length - 3,
),
style: TextStyle(
color: Color(0xFF6D6F71),
fontSize: 17,
fontWeight: FontWeight.w500,
),
),
),
),
),
],
),
),
),
),
const SizedBox(width: 8),
Expanded(
child: Material(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 12,
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset('assets/icons/retire.svg'),
const SizedBox(width: 8),
Text(
'Уход ко сну',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
height: 1,
),
),
],
),
const SizedBox(height: 8),
InkWell(
onTap: () => showTimePicker(state.diary.id, false),
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(
state.diary.retire.toString().substring(
0,
state.diary.retire.toString().length - 3,
),
style: TextStyle(
color: Color(0xFF6D6F71),
fontSize: 17,
fontWeight: FontWeight.w500,
),
),
),
),
),
],
),
),
),
),
],
),
);
} else {
return SizedBox.shrink();
}
},
);
}
}