Files
eatvio_app/lib/features/diary/presentation/pages/diary_screen.dart

440 lines
15 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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';
class DiaryScreen extends StatefulWidget {
const DiaryScreen({super.key});
@override
State<DiaryScreen> createState() => _DiaryScreenState();
}
class _DiaryScreenState extends State<DiaryScreen> {
final DiaryCubit diaryCubit = sl<DiaryCubit>();
CalendarFormat _calendarFormat = CalendarFormat.month;
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay = DateTime.now();
int currentSelectedMealNumber = 1;
GlossaryModel? selectedGlossary;
final productNameController = TextEditingController();
final quantityController = TextEditingController();
@override
Widget build(BuildContext context) {
return BlocProvider<DiaryCubit>(
create: (BuildContext context) => diaryCubit..getDiaryDay(_selectedDay!.toString()),
child: BlocConsumer<DiaryCubit, DiaryState>(
listener: (BuildContext context, DiaryState state) {
if (state is DiaryStateLoaded) {
setState(() {
_calendarFormat = CalendarFormat.week;
});
}
},
builder: (BuildContext context, DiaryState state) {
return Scaffold(
backgroundColor: Colors.grey.shade200,
appBar: AppBar(
toolbarHeight: 0,
),
body: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
children: [
_calendar(state),
if (state is DiaryStateLoading) _loader(),
if (state is DiaryStateLoaded) _meals(state.diary),
const SizedBox(height: 32),
],
),
),
);
},
),
);
}
_arrowDown() {
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,
),
);
}
_divider(BuildContext context) {
return Container(
height: 0.4,
width: MediaQuery.of(context).size.width - 32,
decoration: BoxDecoration(
color: Colors.black26,
borderRadius: BorderRadius.circular(16),
),
);
}
_products(List<Portion> portions) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
for (int i = 0; i < portions.length; i++) ...[
_product(
portions[i].productName,
portions[i].quantity,
portions[i].portion,
),
i < portions.length - 1 ? const SizedBox(height: 8) : const SizedBox.shrink(),
],
],
);
}
_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('$quantity $portion'),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
tileColor: Color(0xFFF6FAFF),
);
}
_meals(DiaryDayModel diaryDay) {
return Material(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Прием пищи',
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_mealButton(1),
_mealButton(2),
_mealButton(3),
_mealButton(4),
_mealButton(5),
],
),
const SizedBox(height: 12),
_divider(context),
const SizedBox(height: 12),
Text(
'Время приема пищи',
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 12),
_divider(context),
const SizedBox(height: 12),
_products(diaryDay.meals[currentSelectedMealNumber - 1].portions),
const SizedBox(height: 16),
_addProductButton(diaryDay),
],
),
),
);
}
_mealButton(int mealNumber) {
bool isActive = currentSelectedMealNumber == mealNumber;
return InkWell(
onTap: () {
setState(() {
if (currentSelectedMealNumber == mealNumber) return;
currentSelectedMealNumber = mealNumber;
});
},
borderRadius: BorderRadius.circular(100),
child: Ink(
width: MediaQuery.of(context).size.width / 5 - (96 / 5),
height: 32,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: isActive ? appTheme.blueA400 : Color(0xFFF1F2F6),
),
child: Center(
child: Text(
'$mealNumber',
style: TextStyle(
color: isActive ? Colors.white : Colors.black,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
);
}
_loader() {
return Center(
child: SizedBox(
height: 32,
width: 32,
child: RiveAnimation.asset(
'assets/rive_animations/loading.riv',
),
),
);
}
_calendar(DiaryState state) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(top: 16),
child: TableCalendar(
firstDay: DateTime.utc(2010, 10, 16),
lastDay: DateTime.utc(2030, 3, 14),
locale: 'ru_RU',
startingDayOfWeek: StartingDayOfWeek.monday,
focusedDay: _focusedDay,
calendarFormat: _calendarFormat,
headerStyle: HeaderStyle(
titleCentered: true,
formatButtonVisible: false,
leftChevronVisible: false,
rightChevronVisible: false,
),
selectedDayPredicate: (day) {
return isSameDay(_selectedDay, day);
},
onDaySelected: (selectedDay, focusedDay) {
if (state is! DiaryStateLoaded) return;
if (!isSameDay(_selectedDay, selectedDay)) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
currentSelectedMealNumber = 1;
});
diaryCubit.getDiaryDay(_selectedDay!.toString());
}
},
onFormatChanged: (format) {
if (_calendarFormat != format) {
setState(() {
_calendarFormat = format;
});
}
},
onPageChanged: (focusedDay) {
_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,
),
todayTextStyle: TextStyle(
color: Colors.black,
),
todayDecoration: BoxDecoration(
border: Border.all(
color: appTheme.blueA400,
width: 2,
),
color: Colors.white,
shape: BoxShape.circle,
),
),
),
),
_arrowDown(),
],
);
}
_addProductButton(DiaryDayModel diaryDay) {
return InkWell(
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),
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,
),
),
),
),
);
}
}