feat(diary): add diary, calendar, meals, products, add product button, modal add product

This commit is contained in:
mso7nueh
2024-10-30 11:44:16 +03:00
parent 1883b93315
commit 784b550673
55 changed files with 867 additions and 1407 deletions

View File

@@ -1,17 +1,20 @@
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
import 'package:eatvio_app/features/diary/repositories/diary_repository.dart';
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class DiaryCubit extends Cubit<DiaryState> {
final DiaryRepository diaryRepository;
final GlossaryRepository glossaryRepository;
DiaryCubit({
required this.diaryRepository,
required this.glossaryRepository,
}) : super(const DiaryState.loading());
getDiaryDay(String date) async {
print(date);
emit(const DiaryState.loading());
getDiaryDay(String date, [bool needLoading = true]) async {
if (needLoading) emit(const DiaryState.loading());
try {
emit(DiaryState.loaded(diary: await diaryRepository.getDiaryByDate(date)));
} catch (e) {
@@ -19,4 +22,26 @@ class DiaryCubit extends Cubit<DiaryState> {
throw e;
}
}
addProductToMeal({
required int diaryId,
required int mealId,
required String productName,
required String portion,
required int quantity,
required String date,
}) async {
await diaryRepository.addPortionToMeal(
diaryId: diaryId,
mealId: mealId,
productName: productName,
portion: portion,
quantity: quantity,
);
await getDiaryDay(date, false);
}
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
return await glossaryRepository.getGlossaryListByQuery(query);
}
}

View File

@@ -1,10 +1,13 @@
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/glossary/models/glossary_model.dart';
import 'package:eatvio_app/locator_service.dart';
import 'package:eatvio_app/theme/theme_helper.dart';
import 'package:eatvio_app/widgets/custom_text_form_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:rive/rive.dart';
import 'package:table_calendar/table_calendar.dart';
@@ -21,6 +24,9 @@ class _DiaryScreenState extends State<DiaryScreen> {
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay = DateTime.now();
int currentSelectedMealNumber = 1;
GlossaryModel? selectedGlossary;
final productNameController = TextEditingController();
final quantityController = TextEditingController();
@override
Widget build(BuildContext context) {
@@ -47,6 +53,7 @@ class _DiaryScreenState extends State<DiaryScreen> {
_calendar(state),
if (state is DiaryStateLoading) _loader(),
if (state is DiaryStateLoaded) _meals(state.diary),
const SizedBox(height: 32),
],
),
),
@@ -57,23 +64,12 @@ class _DiaryScreenState extends State<DiaryScreen> {
}
_arrowDown() {
return GestureDetector(
onTapDown: (_) {
setState(() {
if (_calendarFormat == CalendarFormat.week) {
_calendarFormat = CalendarFormat.month;
} else {
_calendarFormat = CalendarFormat.week;
}
});
},
child: Container(
padding: const EdgeInsets.only(top: 8, bottom: 16),
width: double.infinity,
child: Icon(
_calendarFormat == CalendarFormat.month ? Icons.keyboard_arrow_up_rounded : Icons.keyboard_arrow_down_rounded,
color: Colors.grey.shade600,
),
return Container(
padding: const EdgeInsets.only(top: 8, bottom: 16),
width: double.infinity,
child: Icon(
_calendarFormat == CalendarFormat.month ? Icons.keyboard_arrow_up_rounded : Icons.keyboard_arrow_down_rounded,
color: Colors.grey.shade600,
),
);
}
@@ -97,6 +93,7 @@ class _DiaryScreenState extends State<DiaryScreen> {
_product(
portions[i].productName,
portions[i].quantity,
portions[i].portion,
),
i < portions.length - 1 ? const SizedBox(height: 8) : const SizedBox.shrink(),
],
@@ -104,12 +101,13 @@ class _DiaryScreenState extends State<DiaryScreen> {
);
}
_product(String product, int portion) {
_product(String product, int quantity, String portion) {
portion == 'gr' ? portion = 'г' : portion = 'шт';
return ListTile(
onTap: () {},
trailing: Icon(Icons.more_vert_rounded),
title: Text(product),
subtitle: Text('$portion г'),
subtitle: Text('$quantity $portion'),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
@@ -161,7 +159,7 @@ class _DiaryScreenState extends State<DiaryScreen> {
const SizedBox(height: 12),
_products(diaryDay.meals[currentSelectedMealNumber - 1].portions),
const SizedBox(height: 16),
_addProductButton(),
_addProductButton(diaryDay),
],
),
),
@@ -240,6 +238,7 @@ class _DiaryScreenState extends State<DiaryScreen> {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
currentSelectedMealNumber = 1;
});
diaryCubit.getDiaryDay(_selectedDay!.toString());
}
@@ -255,6 +254,14 @@ class _DiaryScreenState extends State<DiaryScreen> {
_focusedDay = focusedDay;
},
calendarStyle: CalendarStyle(
weekendDecoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
defaultDecoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
selectedDecoration: BoxDecoration(
color: appTheme.blueA400,
shape: BoxShape.circle,
@@ -267,6 +274,7 @@ class _DiaryScreenState extends State<DiaryScreen> {
color: appTheme.blueA400,
width: 2,
),
color: Colors.white,
shape: BoxShape.circle,
),
),
@@ -277,9 +285,134 @@ class _DiaryScreenState extends State<DiaryScreen> {
);
}
_addProductButton() {
_addProductButton(DiaryDayModel diaryDay) {
return InkWell(
onTap: () {},
onTap: () {
setState(() {
selectedGlossary = null;
productNameController.clear();
quantityController.clear();
});
showModalBottomSheet(
useRootNavigator: true,
context: context,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
),
isScrollControlled: true,
builder: (context) {
return StatefulBuilder(
builder: (BuildContext context, void Function(void Function()) setState) {
return Padding(
padding: const EdgeInsets.all(16) + EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Добавление блюда',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Color(0xFF323F4B),
),
),
const SizedBox(height: 16),
TypeAheadField(
controller: productNameController,
itemBuilder: (context, glossary) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
glossary.name,
style: TextStyle(
height: 1,
fontSize: 16,
),
),
);
},
itemSeparatorBuilder: (context, index) => _divider(context),
onSelected: (newSelectedGlossary) {
setState(() {
selectedGlossary = newSelectedGlossary;
productNameController.text = selectedGlossary!.name;
});
},
suggestionsCallback: (query) async {
if (query.length < 1) return [];
return await diaryCubit.getGlossaryListByQuery(query);
},
builder: (context, controller, focusNode) {
return CustomTextFormField(
controller: controller,
focusNode: focusNode,
autofocus: true,
hintText: 'Введите название продукта/блюда',
);
},
emptyBuilder: (context) => const SizedBox.shrink(),
errorBuilder: (context, object) => Padding(
padding: const EdgeInsets.all(8.0),
child: Text('Нет подходящих вариантов'),
),
),
if (selectedGlossary != null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: CustomTextFormField(
controller: quantityController,
keyboardType: TextInputType.number,
hintText: selectedGlossary!.grInPortion == 0 ? '100' : '1',
suffixText: selectedGlossary != null
? selectedGlossary!.grInPortion == 0
? 'гр'
: 'шт'
: null,
),
),
const SizedBox(height: 16),
InkWell(
onTap: () {
diaryCubit.addProductToMeal(
diaryId: diaryDay.id,
mealId: diaryDay.meals[currentSelectedMealNumber - 1].id,
productName: productNameController.text,
portion: selectedGlossary!.grInPortion == 0 ? 'gr' : 'pcs',
quantity: int.parse(quantityController.text),
date: _selectedDay.toString(),
);
Navigator.of(context).pop();
},
borderRadius: BorderRadius.circular(20),
child: Ink(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: appTheme.blueA400,
),
child: Container(
width: MediaQuery.of(context).size.width - 32,
padding: const EdgeInsets.all(16),
alignment: Alignment.center,
child: Text(
'Добавить',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
),
],
),
);
},
);
},
);
},
borderRadius: BorderRadius.circular(20),
child: Ink(
padding: EdgeInsets.symmetric(horizontal: 16),